diff options
author | ru <ru@FreeBSD.org> | 2001-04-17 12:12:05 +0000 |
---|---|---|
committer | ru <ru@FreeBSD.org> | 2001-04-17 12:12:05 +0000 |
commit | 0e0a0e6429c7113acf15c4c94bd5fe94c45f9e99 (patch) | |
tree | f3137c4283de8869ebcae1dd0fe43f590276c1dc /contrib/groff/src/roff/troff | |
parent | a812d8b090bc4edc23150bff257717b24f282e41 (diff) | |
download | FreeBSD-src-0e0a0e6429c7113acf15c4c94bd5fe94c45f9e99.zip FreeBSD-src-0e0a0e6429c7113acf15c4c94bd5fe94c45f9e99.tar.gz |
Virgin import of FSF groff v1.17
Diffstat (limited to 'contrib/groff/src/roff/troff')
24 files changed, 24339 insertions, 0 deletions
diff --git a/contrib/groff/src/roff/troff/Makefile.sub b/contrib/groff/src/roff/troff/Makefile.sub new file mode 100644 index 0000000..e883959 --- /dev/null +++ b/contrib/groff/src/roff/troff/Makefile.sub @@ -0,0 +1,48 @@ +PROG=troff +MAN1=troff.n +XLIBS=$(LIBGROFF) +MLIB=$(LIBM) +OBJS=\ + env.o \ + node.o \ + input.o \ + div.o \ + symbol.o \ + dictionary.o \ + reg.o \ + number.o \ + majorminor.o +CCSRCS=\ + $(srcdir)/env.cc \ + $(srcdir)/node.cc \ + $(srcdir)/input.cc \ + $(srcdir)/div.cc \ + $(srcdir)/symbol.cc \ + $(srcdir)/dictionary.cc \ + $(srcdir)/reg.cc \ + $(srcdir)/number.cc \ + majorminor.cc +HDRS=\ + $(srcdir)/charinfo.h \ + $(srcdir)/dictionary.h \ + $(srcdir)/div.h \ + $(srcdir)/env.h \ + $(srcdir)/hvunits.h \ + $(srcdir)/input.h \ + $(srcdir)/node.h \ + $(srcdir)/reg.h \ + $(srcdir)/request.h \ + $(srcdir)/symbol.h \ + $(srcdir)/token.h \ + $(srcdir)/troff.h +GENSRCS=majorminor.cc +NAMEPREFIX=$(g) + +majorminor.cc: $(top_srcdir)/VERSION $(top_srcdir)/REVISION + @echo Making $@ + @-rm -f $@ + @echo const char \*major_version = \ + \"`sed -e 's/^\([^.]*\)\..*$$/\1/' $(top_srcdir)/VERSION`\"\; >$@ + @echo const char \*minor_version = \ + \"`sed -e 's/^[^.]*\.\([0-9]*\).*$$/\1/' $(top_srcdir)/VERSION`\"\; >>$@ + @echo const char \*revision = \"`cat $(top_srcdir)/REVISION`\"\; >>$@ diff --git a/contrib/groff/src/roff/troff/TODO b/contrib/groff/src/roff/troff/TODO new file mode 100644 index 0000000..6660597 --- /dev/null +++ b/contrib/groff/src/roff/troff/TODO @@ -0,0 +1,134 @@ +A line prefix request to make e.g. French quotation possible: + + He said: >> blablablabla + >> blablabla blabla bla + >> blabla blabla bla bla + >> bla bla bla blablabla + >> blabla. << + +Give a more helpful error message when the indent is set to a value +greater than the line-length. + +Tracing. This is a pain to implement because requests are responsible +for reading their own arguments. + +Possibly implement -s option (stop every N pages). This functionality +would be more appropriate in a postprocessor. + +Line breaking should be smarter. In particular, it should be possible +to shrink spaces. Also avoid having a line that's been shrunk a lot +next to a line that's been stretched a lot. The difficulty is to +design a mechanism that allows the user complete control over the +decision of where to break the line. + +Provide a mechanism to control the shape of the rag in non-justified +text. + +Add a discretionary break escape sequence. \='...'...'...' like TeX. + +Think about kerning between characters and spaces. (Need to implement +get_breakpoints and split methods for kern_pair_node class.) + +In troff, if .L > 1 when a diversion is reread in no-fill mode, then +extra line-spacing is added on. Groff at the moment treats line-spacing +like vertical spacing and doesn't do this. + +Suppose \(ch comes from a special font S, and that the current font is +R. Suppose that R contains a hyphen character and that S does not. +Suppose that the current font is R. Suppose that \(ch is in a word +and has a non-zero hyphen-type. Then we ought to be able to hyphenate, +but we won't be able to because we will look for the hyphen only in +font S and not in font R. + +Perhaps the current input level should be accessible in a number register. + +Should \w deal with a newline like \X? + +Have another look at uses of token::delimiter. Perhaps we need to +distinguish the case where we want to see if a token could start a +number, from the case where we want to see if it could occur somewhere +in a number expression. + +Provide a facility like copy thru in pic. + +Fancier implementation of font families which doesn't group fonts into +families purely on the basis of their names. + +In the DESC file make the number of fonts optional if they are all on +one line. + +Number register to give the diversion level. + +Time various alternative implementations of scale (both in font.c and +number.c). On a sparc it's faster to always do it in floating point. + +Devise a more compact representation for the hyphenation patterns trie. + +Have a per-environment parameter to increase letter-spacing. + +Number register to return character height. + +Number register to return character slant. + +Request to set character height. + +Request to set character slant. + +Provide some way to upcase or downcase strings. + +Support non-uniformly scalable fonts. Perhaps associate a suffix with +a particular range of sizes. eg + sizesuffix .display 14-512 +Then is you ask for R at pointsize 16, groff will first look for +R.display and then R. Probably necessary to be able to specify a +separate unitwidth for each sizesuffix (eg. for X). + +Variant of `.it' for which a line interrupted with \c counts as one +input line. + +Make it possible to suppress hyphenation on a word-by-word basis. +(Perhaps store hyphenation flags in tfont.) + +Possibly allow multiple simultaneous input line traps. + +Unpaddable, breakable space escape sequence. + +Support hanging punctuation. + +In justified text, if the last line of a paragraph is only a little +bit short it might be desirable to justify the line. Allow the user +control over this. + +The pm request could print where the macro was defined. Also could +optionally print the contents of a macro. + +Provide some way to round numbers to multiples of the current +horizontal or vertical resolution. + +Better string-processing support (search). + +Generalized ligatures. + +Provide some way for a macro to tell whether it was called with `'' or +`.'. This would be useful for implementing a tracing macro package. + +Request to remove an environment. (Maintain a count of the references +to the environment from the environment table, environment dictionary +or environment stack.) + +Perhaps in the nr request a leading `-' should only be recognized as a +decrement when it's at the same input level as the request. + +Don't ever change a charinfo. Create new variants instead and chain +them together. + +Unix troff appears to read the first character of a request name in +copy mode. Should we do the same? + +Number register giving name of end macro. + +More thorough range checking. + +Provide syntax for octal and hexadecimal numeric constants. Perhaps +o#100 and x#7f as per Scheme. Or perhaps PostScript 16#7f. Ambiguity +between whether `c' is treated as digit or scaling indicator. diff --git a/contrib/groff/src/roff/troff/charinfo.h b/contrib/groff/src/roff/troff/charinfo.h new file mode 100644 index 0000000..a4ecd57 --- /dev/null +++ b/contrib/groff/src/roff/troff/charinfo.h @@ -0,0 +1,171 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +class macro; + +class charinfo { + static int next_index; + charinfo *translation; + int index; + int number; + macro *mac; + unsigned char special_translation; + unsigned char hyphenation_code; + unsigned char flags; + unsigned char ascii_code; + char not_found; + char transparent_translate; // non-zero means translation applies to + // to transparent throughput +public: + enum { + ENDS_SENTENCE = 1, + BREAK_BEFORE = 2, + BREAK_AFTER = 4, + OVERLAPS_HORIZONTALLY = 8, + OVERLAPS_VERTICALLY = 16, + TRANSPARENT = 32, + NUMBERED = 64 + }; + enum { + TRANSLATE_NONE, + TRANSLATE_SPACE, + TRANSLATE_DUMMY, + TRANSLATE_STRETCHABLE_SPACE, + TRANSLATE_HYPHEN_INDICATOR + }; + symbol nm; + charinfo(symbol s); + int get_index(); + int ends_sentence(); + int overlaps_vertically(); + int overlaps_horizontally(); + int can_break_before(); + int can_break_after(); + int transparent(); + unsigned char get_hyphenation_code(); + unsigned char get_ascii_code(); + void set_hyphenation_code(unsigned char); + void set_ascii_code(unsigned char); + charinfo *get_translation(int = 0); + void set_translation(charinfo *, int); + void set_flags(unsigned char); + void set_special_translation(int, int); + int get_special_translation(int = 0); + macro *set_macro(macro *); + macro *get_macro(); + int first_time_not_found(); + void set_number(int); + int get_number(); + int numbered(); + symbol *get_symbol(); +}; + +charinfo *get_charinfo(symbol); +extern charinfo *charset_table[]; +charinfo *get_charinfo_by_number(int); + +inline int charinfo::overlaps_horizontally() +{ + return flags & OVERLAPS_HORIZONTALLY; +} + +inline int charinfo::overlaps_vertically() +{ + return flags & OVERLAPS_VERTICALLY; +} + +inline int charinfo::can_break_before() +{ + return flags & BREAK_BEFORE; +} + +inline int charinfo::can_break_after() +{ + return flags & BREAK_AFTER; +} + +inline int charinfo::ends_sentence() +{ + return flags & ENDS_SENTENCE; +} + +inline int charinfo::transparent() +{ + return flags & TRANSPARENT; +} + +inline int charinfo::numbered() +{ + return flags & NUMBERED; +} + +inline charinfo *charinfo::get_translation(int transparent_throughput) +{ + return (transparent_throughput && !transparent_translate + ? 0 + : translation); +} + +inline unsigned char charinfo::get_hyphenation_code() +{ + return hyphenation_code; +} + +inline unsigned char charinfo::get_ascii_code() +{ + return ascii_code; +} + +inline void charinfo::set_flags(unsigned char c) +{ + flags = c; +} + +inline int charinfo::get_index() +{ + return index; +} + +inline int charinfo::get_special_translation(int transparent_throughput) +{ + return (transparent_throughput && !transparent_translate + ? int(TRANSLATE_NONE) + : special_translation); +} + +inline macro *charinfo::get_macro() +{ + return mac; +} + +inline int charinfo::first_time_not_found() +{ + if (not_found) + return 0; + else { + not_found = 1; + return 1; + } +} + +inline symbol *charinfo::get_symbol() +{ + return( &nm ); +} diff --git a/contrib/groff/src/roff/troff/column.cc b/contrib/groff/src/roff/troff/column.cc new file mode 100644 index 0000000..8d6a6eb --- /dev/null +++ b/contrib/groff/src/roff/troff/column.cc @@ -0,0 +1,732 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#ifdef COLUMN + +#include "troff.h" +#include "symbol.h" +#include "dictionary.h" +#include "hvunits.h" +#include "env.h" +#include "request.h" +#include "node.h" +#include "token.h" +#include "div.h" +#include "reg.h" +#include "stringclass.h" + +void output_file::vjustify(vunits, symbol) +{ + // do nothing +} + +struct justification_spec; +struct output_line; + +class column : public output_file { +private: + output_file *out; + vunits bottom; + output_line *col; + output_line **tail; + void add_output_line(output_line *); + void begin_page(int pageno, vunits page_length); + void flush(); + void print_line(hunits, vunits, node *, vunits, vunits); + void vjustify(vunits, symbol); + void transparent_char(unsigned char c); + void copy_file(hunits, vunits, const char *); + int is_printing(); + void check_bottom(); +public: + column(); + ~column(); + void start(); + void output(); + void justify(const justification_spec &); + void trim(); + void reset(); + vunits get_bottom(); + vunits get_last_extra_space(); + int is_active() { return out != 0; } +}; + +column *the_column = 0; + +struct transparent_output_line; +struct vjustify_output_line; + +class output_line { + output_line *next; +public: + output_line(); + virtual ~output_line(); + virtual void output(output_file *, vunits); + virtual transparent_output_line *as_transparent_output_line(); + virtual vjustify_output_line *as_vjustify_output_line(); + virtual vunits distance(); + virtual vunits height(); + virtual void reset(); + virtual vunits extra_space(); // post line + friend class column; + friend class justification_spec; +}; + +class position_output_line : public output_line { + vunits dist; +public: + position_output_line(vunits); + vunits distance(); +}; + +class node_output_line : public position_output_line { + node *nd; + hunits page_offset; + vunits before; + vunits after; +public: + node_output_line(vunits, node *, hunits, vunits, vunits); + ~node_output_line(); + void output(output_file *, vunits); + vunits height(); + vunits extra_space(); +}; + +class vjustify_output_line : public position_output_line { + vunits current; + symbol typ; +public: + vjustify_output_line(vunits dist, symbol); + vunits height(); + vjustify_output_line *as_vjustify_output_line(); + void vary(vunits amount); + void reset(); + symbol type(); +}; + +inline symbol vjustify_output_line::type() +{ + return typ; +} + +class copy_file_output_line : public position_output_line { + symbol filename; + hunits hpos; +public: + copy_file_output_line(vunits, const char *, hunits); + void output(output_file *, vunits); +}; + +class transparent_output_line : public output_line { + string buf; +public: + transparent_output_line(); + void output(output_file *, vunits); + void append_char(unsigned char c); + transparent_output_line *as_transparent_output_line(); +}; + +output_line::output_line() : next(0) +{ +} + +output_line::~output_line() +{ +} + +void output_line::reset() +{ +} + +transparent_output_line *output_line::as_transparent_output_line() +{ + return 0; +} + +vjustify_output_line *output_line::as_vjustify_output_line() +{ + return 0; +} + +void output_line::output(output_file *, vunits) +{ +} + +vunits output_line::distance() +{ + return V0; +} + +vunits output_line::height() +{ + return V0; +} + +vunits output_line::extra_space() +{ + return V0; +} + +position_output_line::position_output_line(vunits d) +: dist(d) +{ +} + +vunits position_output_line::distance() +{ + return dist; +} + +node_output_line::node_output_line(vunits d, node *n, hunits po, vunits b, vunits a) +: position_output_line(d), nd(n), page_offset(po), before(b), after(a) +{ +} + +node_output_line::~node_output_line() +{ + delete_node_list(nd); +} + +void node_output_line::output(output_file *out, vunits pos) +{ + out->print_line(page_offset, pos, nd, before, after); + nd = 0; +} + +vunits node_output_line::height() +{ + return after; +} + +vunits node_output_line::extra_space() +{ + return after; +} + +vjustify_output_line::vjustify_output_line(vunits d, symbol t) +: position_output_line(d), typ(t) +{ +} + +void vjustify_output_line::reset() +{ + current = V0; +} + +vunits vjustify_output_line::height() +{ + return current; +} + +vjustify_output_line *vjustify_output_line::as_vjustify_output_line() +{ + return this; +} + +inline void vjustify_output_line::vary(vunits amount) +{ + current += amount; +} + +transparent_output_line::transparent_output_line() +{ +} + +transparent_output_line *transparent_output_line::as_transparent_output_line() +{ + return this; +} + +void transparent_output_line::append_char(unsigned char c) +{ + assert(c != 0); + buf += c; +} + +void transparent_output_line::output(output_file *out, vunits) +{ + int len = buf.length(); + for (int i = 0; i < len; i++) + out->transparent_char(buf[i]); +} + +copy_file_output_line::copy_file_output_line(vunits d, const char *f, hunits h) +: position_output_line(d), hpos(h), filename(f) +{ +} + +void copy_file_output_line::output(output_file *out, vunits pos) +{ + out->copy_file(hpos, pos, filename.contents()); +} + +column::column() +: bottom(V0), col(0), tail(&col), out(0) +{ +} + +column::~column() +{ + assert(out != 0); + error("automatically outputting column before exiting"); + output(); + delete the_output; +} + +void column::start() +{ + assert(out == 0); + if (!the_output) + init_output(); + assert(the_output != 0); + out = the_output; + the_output = this; +} + +void column::begin_page(int pageno, vunits page_length) +{ + assert(out != 0); + if (col) { + error("automatically outputting column before beginning next page"); + output(); + the_output->begin_page(pageno, page_length); + } + else + out->begin_page(pageno, page_length); + +} + +void column::flush() +{ + assert(out != 0); + out->flush(); +} + +int column::is_printing() +{ + assert(out != 0); + return out->is_printing(); +} + +vunits column::get_bottom() +{ + return bottom; +} + +void column::add_output_line(output_line *ln) +{ + *tail = ln; + bottom += ln->distance(); + bottom += ln->height(); + ln->next = 0; + tail = &(*tail)->next; +} + +void column::print_line(hunits page_offset, vunits pos, node *nd, + vunits before, vunits after) +{ + assert(out != 0); + add_output_line(new node_output_line(pos - bottom, nd, page_offset, before, after)); +} + +void column::vjustify(vunits pos, symbol typ) +{ + assert(out != 0); + add_output_line(new vjustify_output_line(pos - bottom, typ)); +} + +void column::transparent_char(unsigned char c) +{ + assert(out != 0); + transparent_output_line *tl = 0; + if (*tail) + tl = (*tail)->as_transparent_output_line(); + if (!tl) { + tl = new transparent_output_line; + add_output_line(tl); + } + tl->append_char(c); +} + +void column::copy_file(hunits page_offset, vunits pos, const char *filename) +{ + assert(out != 0); + add_output_line(new copy_file_output_line(pos - bottom, filename, page_offset)); +} + +void column::trim() +{ + output_line **spp = 0; + for (output_line **pp = &col; *pp; pp = &(*pp)->next) + if ((*pp)->as_vjustify_output_line() == 0) + spp = 0; + else if (!spp) + spp = pp; + if (spp) { + output_line *ln = *spp; + *spp = 0; + tail = spp; + while (ln) { + output_line *tem = ln->next; + bottom -= ln->distance(); + bottom -= ln->height(); + delete ln; + ln = tem; + } + } +} + +void column::reset() +{ + bottom = V0; + for (output_line *ln = col; ln; ln = ln->next) { + bottom += ln->distance(); + ln->reset(); + bottom += ln->height(); + } +} + +void column::check_bottom() +{ + vunits b; + for (output_line *ln = col; ln; ln = ln->next) { + b += ln->distance(); + b += ln->height(); + } + assert(b == bottom); +} + +void column::output() +{ + assert(out != 0); + vunits vpos(V0); + output_line *ln = col; + while (ln) { + vpos += ln->distance(); + ln->output(out, vpos); + vpos += ln->height(); + output_line *tem = ln->next; + delete ln; + ln = tem; + } + tail = &col; + bottom = V0; + col = 0; + the_output = out; + out = 0; +} + +vunits column::get_last_extra_space() +{ + if (!col) + return V0; + for (output_line *p = col; p->next; p = p->next) + ; + return p->extra_space(); +} + +class justification_spec { + vunits height; + symbol *type; + vunits *amount; + int n; + int maxn; +public: + justification_spec(vunits); + ~justification_spec(); + void append(symbol t, vunits v); + void justify(output_line *, vunits *bottomp) const; +}; + +justification_spec::justification_spec(vunits h) +: height(h), n(0), maxn(10) +{ + type = new symbol[maxn]; + amount = new vunits[maxn]; +} + +justification_spec::~justification_spec() +{ + a_delete type; + a_delete amount; +} + +void justification_spec::append(symbol t, vunits v) +{ + if (v <= V0) { + if (v < V0) + warning(WARN_RANGE, + "maximum space for vertical justification must not be negative"); + else + warning(WARN_RANGE, + "maximum space for vertical justification must not be zero"); + return; + } + if (n >= maxn) { + maxn *= 2; + symbol *old_type = type; + type = new symbol[maxn]; + int i; + for (i = 0; i < n; i++) + type[i] = old_type[i]; + a_delete old_type; + vunits *old_amount = amount; + amount = new vunits[maxn]; + for (i = 0; i < n; i++) + amount[i] = old_amount[i]; + a_delete old_amount; + } + assert(n < maxn); + type[n] = t; + amount[n] = v; + n++; +} + +void justification_spec::justify(output_line *col, vunits *bottomp) const +{ + if (*bottomp >= height) + return; + vunits total; + output_line *p; + for (p = col; p; p = p->next) { + vjustify_output_line *sp = p->as_vjustify_output_line(); + if (sp) { + symbol t = sp->type(); + for (int i = 0; i < n; i++) { + if (t == type[i]) + total += amount[i]; + } + } + } + vunits gap = height - *bottomp; + for (p = col; p; p = p->next) { + vjustify_output_line *sp = p->as_vjustify_output_line(); + if (sp) { + symbol t = sp->type(); + for (int i = 0; i < n; i++) { + if (t == type[i]) { + if (total <= gap) { + sp->vary(amount[i]); + gap -= amount[i]; + } + else { + // gap < total + vunits v = scale(amount[i], gap, total); + sp->vary(v); + gap -= v; + } + total -= amount[i]; + } + } + } + } + assert(total == V0); + *bottomp = height - gap; +} + +void column::justify(const justification_spec &js) +{ + check_bottom(); + js.justify(col, &bottom); + check_bottom(); +} + +void column_justify() +{ + vunits height; + if (!the_column->is_active()) + error("can't justify column - column not active"); + else if (get_vunits(&height, 'v')) { + justification_spec js(height); + symbol nm = get_long_name(1); + if (!nm.is_null()) { + vunits v; + if (get_vunits(&v, 'v')) { + js.append(nm, v); + int err = 0; + while (has_arg()) { + nm = get_long_name(1); + if (nm.is_null()) { + err = 1; + break; + } + if (!get_vunits(&v, 'v')) { + err = 1; + break; + } + js.append(nm, v); + } + if (!err) + the_column->justify(js); + } + } + } + skip_line(); +} + +void column_start() +{ + if (the_column->is_active()) + error("can't start column - column already active"); + else + the_column->start(); + skip_line(); +} + +void column_output() +{ + if (!the_column->is_active()) + error("can't output column - column not active"); + else + the_column->output(); + skip_line(); +} + +void column_trim() +{ + if (!the_column->is_active()) + error("can't trim column - column not active"); + else + the_column->trim(); + skip_line(); +} + +void column_reset() +{ + if (!the_column->is_active()) + error("can't reset column - column not active"); + else + the_column->reset(); + skip_line(); +} + +class column_bottom_reg : public reg { +public: + const char *get_string(); +}; + +const char *column_bottom_reg::get_string() +{ + return i_to_a(the_column->get_bottom().to_units()); +} + +class column_extra_space_reg : public reg { +public: + const char *get_string(); +}; + +const char *column_extra_space_reg::get_string() +{ + return i_to_a(the_column->get_last_extra_space().to_units()); +} + +class column_active_reg : public reg { +public: + const char *get_string(); +}; + +const char *column_active_reg::get_string() +{ + return the_column->is_active() ? "1" : "0"; +} + +static int no_vjustify_mode = 0; + +class vjustify_node : public node { + symbol typ; +public: + vjustify_node(symbol); + int reread(int *); + const char *type(); + int same(node *); + node *copy(); +}; + +vjustify_node::vjustify_node(symbol t) +: typ(t) +{ +} + +node *vjustify_node::copy() +{ + return new vjustify_node(typ); +} + +const char *vjustify_node::type() +{ + return "vjustify_node"; +} + +int vjustify_node::same(node *nd) +{ + return typ == ((vjustify_node *)nd)->typ; +} + +int vjustify_node::reread(int *bolp) +{ + curdiv->vjustify(typ); + *bolp = 1; + return 1; +} + +void macro_diversion::vjustify(symbol type) +{ + if (!no_vjustify_mode) + mac->append(new vjustify_node(type)); +} + +void top_level_diversion::vjustify(symbol type) +{ + if (no_space_mode || no_vjustify_mode) + return; + assert(first_page_begun); // I'm not sure about this. + the_output->vjustify(vertical_position, type); +} + +void no_vjustify() +{ + skip_line(); + no_vjustify_mode = 1; +} + +void restore_vjustify() +{ + skip_line(); + no_vjustify_mode = 0; +} + +void init_column_requests() +{ + the_column = new column; + init_request("cols", column_start); + init_request("colo", column_output); + init_request("colj", column_justify); + init_request("colr", column_reset); + init_request("colt", column_trim); + init_request("nvj", no_vjustify); + init_request("rvj", restore_vjustify); + number_reg_dictionary.define(".colb", new column_bottom_reg); + number_reg_dictionary.define(".colx", new column_extra_space_reg); + number_reg_dictionary.define(".cola", new column_active_reg); + number_reg_dictionary.define(".nvj", + new constant_int_reg(&no_vjustify_mode)); +} + +#endif /* COLUMN */ diff --git a/contrib/groff/src/roff/troff/dictionary.cc b/contrib/groff/src/roff/troff/dictionary.cc new file mode 100644 index 0000000..169536c --- /dev/null +++ b/contrib/groff/src/roff/troff/dictionary.cc @@ -0,0 +1,212 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +#include "troff.h" +#include "symbol.h" +#include "dictionary.h" + +// is `p' a good size for a hash table + +static int is_good_size(int p) +{ + const int SMALL = 10; + unsigned i; + for (i = 2; i <= p/2; i++) + if (p % i == 0) + return 0; + for (i = 0x100; i != 0; i <<= 8) + if (i % p <= SMALL || i % p > p - SMALL) + return 0; + return 1; +} + +dictionary::dictionary(int n) : size(n), used(0), threshold(0.5), factor(1.5) +{ + table = new association[n]; +} + +// see Knuth, Sorting and Searching, p518, Algorithm L +// we can't use double-hashing because we want a remove function + +void *dictionary::lookup(symbol s, void *v) +{ + int i; + for (i = int(s.hash() % size); + table[i].v != 0; + i == 0 ? i = size - 1: --i) + if (s == table[i].s) { + if (v != 0) { + void *temp = table[i].v; + table[i].v = v; + return temp; + } + else + return table[i].v; + } + if (v == 0) + return 0; + ++used; + table[i].v = v; + table[i].s = s; + if ((double)used/(double)size >= threshold || used + 1 >= size) { + int old_size = size; + size = int(size*factor); + while (!is_good_size(size)) + ++size; + association *old_table = table; + table = new association[size]; + used = 0; + for (i = 0; i < old_size; i++) + if (old_table[i].v != 0) + (void)lookup(old_table[i].s, old_table[i].v); + a_delete old_table; + } + return 0; +} + +void *dictionary::lookup(const char *p) +{ + symbol s(p, MUST_ALREADY_EXIST); + if (s.is_null()) + return 0; + else + return lookup(s); +} + +// see Knuth, Sorting and Searching, p527, Algorithm R + +void *dictionary::remove(symbol s) +{ + // this relies on the fact that we are using linear probing + int i; + for (i = int(s.hash() % size); + table[i].v != 0 && s != table[i].s; + i == 0 ? i = size - 1: --i) + ; + void *p = table[i].v; + while (table[i].v != 0) { + table[i].v = 0; + int j = i; + int r; + do { + --i; + if (i < 0) + i = size - 1; + if (table[i].v == 0) + break; + r = int(table[i].s.hash() % size); + } while ((i <= r && r < j) || (r < j && j < i) || (j < i && i <= r)); + table[j] = table[i]; + } + if (p != 0) + --used; + return p; +} + +dictionary_iterator::dictionary_iterator(dictionary &d) : dict(&d), i(0) +{ +} + +int dictionary_iterator::get(symbol *sp, void **vp) +{ + for (; i < dict->size; i++) + if (dict->table[i].v) { + *sp = dict->table[i].s; + *vp = dict->table[i].v; + i++; + return 1; + } + return 0; +} + +object_dictionary_iterator::object_dictionary_iterator(object_dictionary &od) + : di(od.d) +{ +} + +object::object() : rcount(0) +{ +} + +object::~object() +{ +} + +void object::add_reference() +{ + rcount += 1; +} + +void object::remove_reference() +{ + if (--rcount == 0) + delete this; +} + +object_dictionary::object_dictionary(int n) : d(n) +{ +} + +object *object_dictionary::lookup(symbol nm) +{ + return (object *)d.lookup(nm); +} + +void object_dictionary::define(symbol nm, object *obj) +{ + obj->add_reference(); + obj = (object *)d.lookup(nm, obj); + if (obj) + obj->remove_reference(); +} + +void object_dictionary::rename(symbol oldnm, symbol newnm) +{ + object *obj = (object *)d.remove(oldnm); + if (obj) { + obj = (object *)d.lookup(newnm, obj); + if (obj) + obj->remove_reference(); + } +} + +void object_dictionary::remove(symbol nm) +{ + object *obj = (object *)d.remove(nm); + if (obj) + obj->remove_reference(); +} + +// Return non-zero if oldnm was defined. + +int object_dictionary::alias(symbol newnm, symbol oldnm) +{ + object *obj = (object *)d.lookup(oldnm); + if (obj) { + obj->add_reference(); + obj = (object *)d.lookup(newnm, obj); + if (obj) + obj->remove_reference(); + return 1; + } + return 0; +} + diff --git a/contrib/groff/src/roff/troff/dictionary.h b/contrib/groff/src/roff/troff/dictionary.h new file mode 100644 index 0000000..4f319be --- /dev/null +++ b/contrib/groff/src/roff/troff/dictionary.h @@ -0,0 +1,92 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + + +// there is no distinction between name with no value and name with NULL value +// null names are not permitted (they will be ignored). + +struct association { + symbol s; + void *v; + association() : v(0) {} +}; + +class dictionary; + +class dictionary_iterator { + dictionary *dict; + int i; +public: + dictionary_iterator(dictionary &); + int get(symbol *, void **); +}; + +class dictionary { + int size; + int used; + double threshold; + double factor; + association *table; + void rehash(int); +public: + dictionary(int); + void *lookup(symbol s, void *v=0); // returns value associated with key + void *lookup(const char *); + // if second parameter not NULL, value will be replaced + void *remove(symbol); + friend class dictionary_iterator; +}; + +class object { + int rcount; + public: + object(); + virtual ~object(); + void add_reference(); + void remove_reference(); +}; + +class object_dictionary; + +class object_dictionary_iterator { + dictionary_iterator di; +public: + object_dictionary_iterator(object_dictionary &); + int get(symbol *, object **); +}; + +class object_dictionary { + dictionary d; +public: + object_dictionary(int); + object *lookup(symbol nm); + void define(symbol nm, object *obj); + void rename(symbol oldnm, symbol newnm); + void remove(symbol nm); + int alias(symbol newnm, symbol oldnm); + friend class object_dictionary_iterator; +}; + + +inline int object_dictionary_iterator::get(symbol *sp, object **op) +{ + return di.get(sp, (void **)op); +} diff --git a/contrib/groff/src/roff/troff/div.cc b/contrib/groff/src/roff/troff/div.cc new file mode 100644 index 0000000..281c1af --- /dev/null +++ b/contrib/groff/src/roff/troff/div.cc @@ -0,0 +1,1161 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001 + Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +// diversions + +#include "troff.h" +#include "symbol.h" +#include "dictionary.h" +#include "hvunits.h" +#include "env.h" +#include "request.h" +#include "node.h" +#include "token.h" +#include "div.h" +#include "reg.h" + +int exit_started = 0; // the exit process has started +int done_end_macro = 0; // the end macro (if any) has finished +int seen_last_page_ejector = 0; // seen the LAST_PAGE_EJECTOR cookie +int last_page_number = 0; // if > 0, the number of the last page + // specified with -o +static int began_page_in_end_macro = 0; // a new page was begun during the end macro + +static int last_post_line_extra_space = 0; // needed for \n(.a +static int nl_reg_contents = -1; +static int dl_reg_contents = 0; +static int dn_reg_contents = 0; +static int vertical_position_traps_flag = 1; +static vunits truncated_space; +static vunits needed_space; + +diversion::diversion(symbol s) +: prev(0), nm(s), vertical_position(V0), high_water_mark(V0), marked_place(V0) +{ +} + +struct vertical_size { + vunits pre_extra, post_extra, pre, post; + vertical_size(vunits vs, vunits post_vs); +}; + +vertical_size::vertical_size(vunits vs, vunits post_vs) +: pre_extra(V0), post_extra(V0), pre(vs), post(post_vs) +{ +} + +void node::set_vertical_size(vertical_size *) +{ +} + +void extra_size_node::set_vertical_size(vertical_size *v) +{ + if (n < V0) { + if (-n > v->pre_extra) + v->pre_extra = -n; + } + else if (n > v->post_extra) + v->post_extra = n; +} + +void vertical_size_node::set_vertical_size(vertical_size *v) +{ + if (n < V0) + v->pre = -n; + else + v->post = n; +} + +top_level_diversion *topdiv; + +diversion *curdiv; + +void do_divert(int append, int boxing) +{ + tok.skip(); + symbol nm = get_name(); + if (nm.is_null()) { + if (curdiv->prev) { + if (boxing) { + curenv->line = curdiv->saved_line; + curenv->width_total = curdiv->saved_width_total; + curenv->space_total = curdiv->saved_space_total; + curenv->saved_indent = curdiv->saved_saved_indent; + curenv->target_text_length = curdiv->saved_target_text_length; + curenv->prev_line_interrupted = curdiv->saved_prev_line_interrupted; + } + diversion *temp = curdiv; + curdiv = curdiv->prev; + delete temp; + } + else + warning(WARN_DI, "diversion stack underflow"); + } + else { + macro_diversion *md = new macro_diversion(nm, append); + md->prev = curdiv; + curdiv = md; + if (boxing) { + curdiv->saved_line = curenv->line; + curdiv->saved_width_total = curenv->width_total; + curdiv->saved_space_total = curenv->space_total; + curdiv->saved_saved_indent = curenv->saved_indent; + curdiv->saved_target_text_length = curenv->target_text_length; + curdiv->saved_prev_line_interrupted = curenv->prev_line_interrupted; + curenv->line = 0; + curenv->start_line(); + } + } + skip_line(); +} + +void divert() +{ + do_divert(0, 0); +} + +void divert_append() +{ + do_divert(1, 0); +} + +void box() +{ + do_divert(0, 1); +} + +void box_append() +{ + do_divert(1, 1); +} + +void diversion::need(vunits n) +{ + vunits d = distance_to_next_trap(); + if (d < n) { + space(d, 1); + truncated_space = -d; + needed_space = n; + } +} + +macro_diversion::macro_diversion(symbol s, int append) +: diversion(s), max_width(H0) +{ +#if 0 + if (append) { + /* We don't allow recursive appends eg: + + .da a + .a + .di + + This causes an infinite loop in troff anyway. + This is because the user could do + + .as a foo + + in the diversion, and this would mess things up royally, + since there would be two things appending to the same + macro_header. + To make it work, we would have to copy the _contents_ + of the macro into which we were diverting; this doesn't + strike me as worthwhile. + However, + + .di a + .a + .a + .di + + will work and will make `a' contain two copies of what it contained + before; in troff, `a' would contain nothing. */ + request_or_macro *rm + = (request_or_macro *)request_dictionary.remove(s); + if (!rm || (mac = rm->to_macro()) == 0) + mac = new macro; + } + else + mac = new macro; +#endif + // We can now catch the situation described above by comparing + // the length of the charlist in the macro_header with the length + // stored in the macro. When we detect this, we copy the contents. + mac = new macro; + if (append) { + request_or_macro *rm + = (request_or_macro *)request_dictionary.lookup(s); + if (rm) { + macro *m = rm->to_macro(); + if (m) + *mac = *m; + } + } +} + +macro_diversion::~macro_diversion() +{ + request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm); + macro *m = rm ? rm->to_macro() : 0; + if (m) { + *m = *mac; + delete mac; + } + else + request_dictionary.define(nm, mac); + mac = 0; + dl_reg_contents = max_width.to_units(); + dn_reg_contents = vertical_position.to_units(); +} + +vunits macro_diversion::distance_to_next_trap() +{ + if (!diversion_trap.is_null() && diversion_trap_pos > vertical_position) + return diversion_trap_pos - vertical_position; + else + // Substract vresolution so that vunits::vunits does not overflow. + return vunits(INT_MAX - vresolution); +} + +void macro_diversion::transparent_output(unsigned char c) +{ + mac->append(c); +} + +void macro_diversion::transparent_output(node *n) +{ + mac->append(n); +} + +void macro_diversion::output(node *nd, int retain_size, + vunits vs, vunits post_vs, hunits width) +{ + vertical_size v(vs, post_vs); + while (nd != 0) { + nd->set_vertical_size(&v); + node *temp = nd; + nd = nd->next; + if (temp->interpret(mac)) { + delete temp; + } + else { +#if 1 + temp->freeze_space(); +#endif + mac->append(temp); + } + } + last_post_line_extra_space = v.post_extra.to_units(); + if (!retain_size) { + v.pre = vs; + v.post = post_vs; + } + if (width > max_width) + max_width = width; + vunits x = v.pre + v.pre_extra + v.post + v.post_extra; + if (vertical_position_traps_flag + && !diversion_trap.is_null() && diversion_trap_pos > vertical_position + && diversion_trap_pos <= vertical_position + x) { + vunits trunc = vertical_position + x - diversion_trap_pos; + if (trunc > v.post) + trunc = v.post; + v.post -= trunc; + x -= trunc; + truncated_space = trunc; + spring_trap(diversion_trap); + } + mac->append(new vertical_size_node(-v.pre)); + mac->append(new vertical_size_node(v.post)); + mac->append('\n'); + vertical_position += x; + if (vertical_position - v.post > high_water_mark) + high_water_mark = vertical_position - v.post; +} + +void macro_diversion::space(vunits n, int) +{ + if (vertical_position_traps_flag + && !diversion_trap.is_null() && diversion_trap_pos > vertical_position + && diversion_trap_pos <= vertical_position + n) { + truncated_space = vertical_position + n - diversion_trap_pos; + n = diversion_trap_pos - vertical_position; + spring_trap(diversion_trap); + } + else if (n + vertical_position < V0) + n = -vertical_position; + mac->append(new diverted_space_node(n)); + vertical_position += n; +} + +void macro_diversion::copy_file(const char *filename) +{ + mac->append(new diverted_copy_file_node(filename)); +} + +top_level_diversion::top_level_diversion() +: page_number(0), page_count(0), last_page_count(-1), + page_length(units_per_inch*11), + prev_page_offset(units_per_inch), page_offset(units_per_inch), + page_trap_list(0), have_next_page_number(0), + ejecting_page(0), before_first_page(1), no_space_mode(0) +{ +} + +// find the next trap after pos + +trap *top_level_diversion::find_next_trap(vunits *next_trap_pos) +{ + trap *next_trap = 0; + for (trap *pt = page_trap_list; pt != 0; pt = pt->next) + if (!pt->nm.is_null()) { + if (pt->position >= V0) { + if (pt->position > vertical_position + && pt->position < page_length + && (next_trap == 0 || pt->position < *next_trap_pos)) { + next_trap = pt; + *next_trap_pos = pt->position; + } + } + else { + vunits pos = pt->position; + pos += page_length; + if (pos > 0 && pos > vertical_position && (next_trap == 0 || pos < *next_trap_pos)) { + next_trap = pt; + *next_trap_pos = pos; + } + } + } + return next_trap; +} + +vunits top_level_diversion::distance_to_next_trap() +{ + vunits d; + if (!find_next_trap(&d)) + return page_length - vertical_position; + else + return d - vertical_position; +} + +void top_level_diversion::output(node *nd, int retain_size, + vunits vs, vunits post_vs, hunits width) +{ + no_space_mode = 0; + vunits next_trap_pos; + trap *next_trap = find_next_trap(&next_trap_pos); + if (before_first_page && begin_page()) + fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request"); + vertical_size v(vs, post_vs); + for (node *tem = nd; tem != 0; tem = tem->next) + tem->set_vertical_size(&v); + last_post_line_extra_space = v.post_extra.to_units(); + if (!retain_size) { + v.pre = vs; + v.post = post_vs; + } + vertical_position += v.pre; + vertical_position += v.pre_extra; + the_output->print_line(page_offset, vertical_position, nd, + v.pre + v.pre_extra, v.post_extra, width); + vertical_position += v.post_extra; + if (vertical_position > high_water_mark) + high_water_mark = vertical_position; + if (vertical_position_traps_flag && vertical_position >= page_length) + begin_page(); + else if (vertical_position_traps_flag + && next_trap != 0 && vertical_position >= next_trap_pos) { + nl_reg_contents = vertical_position.to_units(); + truncated_space = v.post; + spring_trap(next_trap->nm); + } + else if (v.post > V0) { + vertical_position += v.post; + if (vertical_position_traps_flag + && next_trap != 0 && vertical_position >= next_trap_pos) { + truncated_space = vertical_position - next_trap_pos; + vertical_position = next_trap_pos; + nl_reg_contents = vertical_position.to_units(); + spring_trap(next_trap->nm); + } + else if (vertical_position_traps_flag && vertical_position >= page_length) + begin_page(); + else + nl_reg_contents = vertical_position.to_units(); + } + else + nl_reg_contents = vertical_position.to_units(); +} + +void top_level_diversion::transparent_output(unsigned char c) +{ + if (before_first_page && begin_page()) + // This can only happen with the transparent() request. + fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request"); + const char *s = asciify(c); + while (*s) + the_output->transparent_char(*s++); +} + +void top_level_diversion::transparent_output(node * /*n*/) +{ + error("can't transparently output node at top level"); +} + +void top_level_diversion::copy_file(const char *filename) +{ + if (before_first_page && begin_page()) + fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request"); + the_output->copy_file(page_offset, vertical_position, filename); +} + +void top_level_diversion::space(vunits n, int forced) +{ + if (no_space_mode) { + if (!forced) + return; + else + no_space_mode = 0; + } + if (before_first_page) { + if (begin_page()) { + // This happens if there's a top of page trap, and the first-page + // transition is caused by `'sp'. + truncated_space = n > V0 ? n : V0; + return; + } + } + vunits next_trap_pos; + trap *next_trap = find_next_trap(&next_trap_pos); + vunits y = vertical_position + n; + if (vertical_position_traps_flag && next_trap != 0 && y >= next_trap_pos) { + vertical_position = next_trap_pos; + nl_reg_contents = vertical_position.to_units(); + truncated_space = y - vertical_position; + spring_trap(next_trap->nm); + } + else if (y < V0) { + vertical_position = V0; + nl_reg_contents = vertical_position.to_units(); + } + else if (vertical_position_traps_flag && y >= page_length && n >= V0) + begin_page(); + else { + vertical_position = y; + nl_reg_contents = vertical_position.to_units(); + } +} + +trap::trap(symbol s, vunits n, trap *p) + : next(p), position(n), nm(s) +{ +} + +void top_level_diversion::add_trap(symbol nm, vunits pos) +{ + trap *first_free_slot = 0; + trap **p; + for (p = &page_trap_list; *p; p = &(*p)->next) { + if ((*p)->nm.is_null()) { + if (first_free_slot == 0) + first_free_slot = *p; + } + else if ((*p)->position == pos) { + (*p)->nm = nm; + return; + } + } + if (first_free_slot) { + first_free_slot->nm = nm; + first_free_slot->position = pos; + } + else + *p = new trap(nm, pos, 0); +} + +void top_level_diversion::remove_trap(symbol nm) +{ + for (trap *p = page_trap_list; p; p = p->next) + if (p->nm == nm) { + p->nm = NULL_SYMBOL; + return; + } +} + +void top_level_diversion::remove_trap_at(vunits pos) +{ + for (trap *p = page_trap_list; p; p = p->next) + if (p->position == pos) { + p->nm = NULL_SYMBOL; + return; + } +} + +void top_level_diversion::change_trap(symbol nm, vunits pos) +{ + for (trap *p = page_trap_list; p; p = p->next) + if (p->nm == nm) { + p->position = pos; + return; + } +} + +void top_level_diversion::print_traps() +{ + for (trap *p = page_trap_list; p; p = p->next) + if (p->nm.is_null()) + fprintf(stderr, " empty\n"); + else + fprintf(stderr, "%s\t%d\n", p->nm.contents(), p->position.to_units()); + fflush(stderr); +} + +void end_diversions() +{ + while (curdiv != topdiv) { + error("automatically ending diversion `%1' on exit", + curdiv->nm.contents()); + diversion *tem = curdiv; + curdiv = curdiv->prev; + delete tem; + } +} + +void cleanup_and_exit(int exit_code) +{ + if (the_output) { + the_output->trailer(topdiv->get_page_length()); + delete the_output; + } + exit(exit_code); +} + +// returns non-zero if it sprung a top of page trap + +int top_level_diversion::begin_page() +{ + if (exit_started) { + if (page_count == last_page_count + ? curenv->is_empty() + : (done_end_macro && (seen_last_page_ejector || began_page_in_end_macro))) + cleanup_and_exit(0); + if (!done_end_macro) + began_page_in_end_macro = 1; + } + if (last_page_number > 0 && page_number == last_page_number) + cleanup_and_exit(0); + if (!the_output) + init_output(); + ++page_count; + if (have_next_page_number) { + page_number = next_page_number; + have_next_page_number = 0; + } + else if (before_first_page == 1) + page_number = 1; + else + page_number++; + // spring the top of page trap if there is one + vunits next_trap_pos; + vertical_position = -vresolution; + trap *next_trap = find_next_trap(&next_trap_pos); + vertical_position = V0; + high_water_mark = V0; + ejecting_page = 0; + // If before_first_page was 2, then the top of page transition was undone + // using eg .nr nl 0-1. See nl_reg::set_value. + if (before_first_page != 2) + the_output->begin_page(page_number, page_length); + before_first_page = 0; + nl_reg_contents = vertical_position.to_units(); + if (vertical_position_traps_flag && next_trap != 0 && next_trap_pos == V0) { + truncated_space = V0; + spring_trap(next_trap->nm); + return 1; + } + else + return 0; +} + +void continue_page_eject() +{ + if (topdiv->get_ejecting()) { + if (curdiv != topdiv) + error("can't continue page ejection because of current diversion"); + else if (!vertical_position_traps_flag) + error("can't continue page ejection because vertical position traps disabled"); + else { + push_page_ejector(); + topdiv->space(topdiv->get_page_length(), 1); + } + } +} + +void top_level_diversion::set_next_page_number(int n) +{ + next_page_number= n; + have_next_page_number = 1; +} + +int top_level_diversion::get_next_page_number() +{ + return have_next_page_number ? next_page_number : page_number + 1; +} + +void top_level_diversion::set_page_length(vunits n) +{ + page_length = n; +} + +diversion::~diversion() +{ +} + +void page_offset() +{ + hunits n; + // The troff manual says that the default scaling indicator is v, + // but it is in fact m: v wouldn't make sense for a horizontally + // oriented request. + if (!has_arg() || !get_hunits(&n, 'm', topdiv->page_offset)) + n = topdiv->prev_page_offset; + topdiv->prev_page_offset = topdiv->page_offset; + topdiv->page_offset = n; + curenv->add_html_tag(".po", n.to_units()); + skip_line(); +} + +void page_length() +{ + vunits n; + if (has_arg() && get_vunits(&n, 'v', topdiv->get_page_length())) + topdiv->set_page_length(n); + else + topdiv->set_page_length(11*units_per_inch); + skip_line(); +} + +void when_request() +{ + vunits n; + if (get_vunits(&n, 'v')) { + symbol s = get_name(); + if (s.is_null()) + topdiv->remove_trap_at(n); + else + topdiv->add_trap(s, n); + } + skip_line(); +} + +void begin_page() +{ + int got_arg = 0; + int n; + if (has_arg() && get_integer(&n, topdiv->get_page_number())) + got_arg = 1; + while (!tok.newline() && !tok.eof()) + tok.next(); + if (curdiv == topdiv) { + if (topdiv->before_first_page) { + if (!break_flag) { + if (got_arg) + topdiv->set_next_page_number(n); + if (got_arg || !topdiv->no_space_mode) + topdiv->begin_page(); + } + else if (topdiv->no_space_mode && !got_arg) + topdiv->begin_page(); + else { + /* Given this + + .wh 0 x + .de x + .tm \\n% + .. + .bp 3 + + troff prints + + 1 + 3 + + This code makes groff do the same. */ + + push_page_ejector(); + topdiv->begin_page(); + if (got_arg) + topdiv->set_next_page_number(n); + topdiv->set_ejecting(); + } + } + else { + push_page_ejector(); + if (break_flag) + curenv->do_break(); + if (got_arg) + topdiv->set_next_page_number(n); + if (!(topdiv->no_space_mode && !got_arg)) + topdiv->set_ejecting(); + } + } + tok.next(); +} + +void no_space() +{ + if (curdiv == topdiv) + topdiv->no_space_mode = 1; + skip_line(); +} + +void restore_spacing() +{ + if (curdiv == topdiv) + topdiv->no_space_mode = 0; + skip_line(); +} + +/* It is necessary to generate a break before before reading the argument, +because otherwise arguments using | will be wrong. But if we just +generate a break as usual, then the line forced out may spring a trap +and thus push a macro onto the input stack before we have had a chance +to read the argument to the sp request. We resolve this dilemma by +setting, before generating the break, a flag which will postpone the +actual pushing of the macro associated with the trap sprung by the +outputting of the line forced out by the break till after we have read +the argument to the request. If the break did cause a trap to be +sprung, then we don't actually do the space. */ + +void space_request() +{ + postpone_traps(); + if (break_flag) + curenv->do_break(); + vunits n; + if (!has_arg() || !get_vunits(&n, 'v')) + n = curenv->get_vertical_spacing(); + while (!tok.newline() && !tok.eof()) + tok.next(); + if (!unpostpone_traps()) + curdiv->space(n); + else + // The line might have had line spacing that was truncated. + truncated_space += n; + curenv->add_html_tag(".sp", n.to_units()); + tok.next(); +} + +void blank_line() +{ + curenv->do_break(); + if (!trap_sprung_flag) + curdiv->space(curenv->get_vertical_spacing()); + else + truncated_space += curenv->get_vertical_spacing(); +} + +/* need_space might spring a trap and so we must be careful that the +BEGIN_TRAP token is not skipped over. */ + +void need_space() +{ + vunits n; + if (!has_arg() || !get_vunits(&n, 'v')) + n = curenv->get_vertical_spacing(); + while (!tok.newline() && !tok.eof()) + tok.next(); + curdiv->need(n); + tok.next(); +} + +void page_number() +{ + int n; + if (has_arg() && get_integer(&n, topdiv->get_page_number())) + topdiv->set_next_page_number(n); + skip_line(); +} + +vunits saved_space; + +void save_vertical_space() +{ + vunits x; + if (!has_arg() || !get_vunits(&x, 'v')) + x = curenv->get_vertical_spacing(); + if (curdiv->distance_to_next_trap() > x) + curdiv->space(x, 1); + else + saved_space = x; + skip_line(); +} + +void output_saved_vertical_space() +{ + while (!tok.newline() && !tok.eof()) + tok.next(); + if (saved_space > V0) + curdiv->space(saved_space, 1); + saved_space = V0; + tok.next(); +} + +void flush_output() +{ + while (!tok.newline() && !tok.eof()) + tok.next(); + if (break_flag) + curenv->do_break(); + if (the_output) + the_output->flush(); + curenv->add_html_tag(".fl"); + tok.next(); +} + +void macro_diversion::set_diversion_trap(symbol s, vunits n) +{ + diversion_trap = s; + diversion_trap_pos = n; +} + +void macro_diversion::clear_diversion_trap() +{ + diversion_trap = NULL_SYMBOL; +} + +void top_level_diversion::set_diversion_trap(symbol, vunits) +{ + error("can't set diversion trap when no current diversion"); +} + +void top_level_diversion::clear_diversion_trap() +{ + error("can't set diversion trap when no current diversion"); +} + +void diversion_trap() +{ + vunits n; + if (has_arg() && get_vunits(&n, 'v')) { + symbol s = get_name(); + if (!s.is_null()) + curdiv->set_diversion_trap(s, n); + else + curdiv->clear_diversion_trap(); + } + else + curdiv->clear_diversion_trap(); + skip_line(); +} + +void change_trap() +{ + symbol s = get_name(1); + if (!s.is_null()) { + vunits x; + if (has_arg() && get_vunits(&x, 'v')) + topdiv->change_trap(s, x); + else + topdiv->remove_trap(s); + } + skip_line(); +} + +void print_traps() +{ + topdiv->print_traps(); + skip_line(); +} + +void mark() +{ + symbol s = get_name(); + if (s.is_null()) + curdiv->marked_place = curdiv->get_vertical_position(); + else if (curdiv == topdiv) + set_number_reg(s, nl_reg_contents); + else + set_number_reg(s, curdiv->get_vertical_position().to_units()); + skip_line(); +} + +// This is truly bizarre. It is documented in the SQ manual. + +void return_request() +{ + vunits dist = curdiv->marked_place - curdiv->get_vertical_position(); + if (has_arg()) { + if (tok.ch() == '-') { + tok.next(); + vunits x; + if (get_vunits(&x, 'v')) + dist = -x; + } + else { + vunits x; + if (get_vunits(&x, 'v')) + dist = x >= V0 ? x - curdiv->get_vertical_position() : V0; + } + } + if (dist < V0) + curdiv->space(dist); + skip_line(); +} + +void vertical_position_traps() +{ + int n; + if (has_arg() && get_integer(&n)) + vertical_position_traps_flag = (n != 0); + else + vertical_position_traps_flag = 1; + skip_line(); +} + +class page_offset_reg : public reg { +public: + int get_value(units *); + const char *get_string(); +}; + +int page_offset_reg::get_value(units *res) +{ + *res = topdiv->get_page_offset().to_units(); + return 1; +} + +const char *page_offset_reg::get_string() +{ + return i_to_a(topdiv->get_page_offset().to_units()); +} + +class page_length_reg : public reg { +public: + int get_value(units *); + const char *get_string(); +}; + +int page_length_reg::get_value(units *res) +{ + *res = topdiv->get_page_length().to_units(); + return 1; +} + +const char *page_length_reg::get_string() +{ + return i_to_a(topdiv->get_page_length().to_units()); +} + +class vertical_position_reg : public reg { +public: + int get_value(units *); + const char *get_string(); +}; + +int vertical_position_reg::get_value(units *res) +{ + if (curdiv == topdiv && topdiv->before_first_page) + *res = -1; + else + *res = curdiv->get_vertical_position().to_units(); + return 1; +} + +const char *vertical_position_reg::get_string() +{ + if (curdiv == topdiv && topdiv->before_first_page) + return "-1"; + else + return i_to_a(curdiv->get_vertical_position().to_units()); +} + +class high_water_mark_reg : public reg { +public: + int get_value(units *); + const char *get_string(); +}; + +int high_water_mark_reg::get_value(units *res) +{ + *res = curdiv->get_high_water_mark().to_units(); + return 1; +} + +const char *high_water_mark_reg::get_string() +{ + return i_to_a(curdiv->get_high_water_mark().to_units()); +} + +class distance_to_next_trap_reg : public reg { +public: + int get_value(units *); + const char *get_string(); +}; + +int distance_to_next_trap_reg::get_value(units *res) +{ + *res = curdiv->distance_to_next_trap().to_units(); + return 1; +} + +const char *distance_to_next_trap_reg::get_string() +{ + return i_to_a(curdiv->distance_to_next_trap().to_units()); +} + +class diversion_name_reg : public reg { +public: + const char *get_string(); +}; + +const char *diversion_name_reg::get_string() +{ + return curdiv->get_diversion_name(); +} + +class page_number_reg : public general_reg { +public: + page_number_reg(); + int get_value(units *); + void set_value(units); +}; + +page_number_reg::page_number_reg() +{ +} + +void page_number_reg::set_value(units n) +{ + topdiv->set_page_number(n); +} + +int page_number_reg::get_value(units *res) +{ + *res = topdiv->get_page_number(); + return 1; +} + +class next_page_number_reg : public reg { +public: + const char *get_string(); +}; + +const char *next_page_number_reg::get_string() +{ + return i_to_a(topdiv->get_next_page_number()); +} + +class page_ejecting_reg : public reg { +public: + const char *get_string(); +}; + +const char *page_ejecting_reg::get_string() +{ + return i_to_a(topdiv->get_ejecting()); +} + +class constant_vunits_reg : public reg { + vunits *p; +public: + constant_vunits_reg(vunits *); + const char *get_string(); +}; + +constant_vunits_reg::constant_vunits_reg(vunits *q) : p(q) +{ +} + +const char *constant_vunits_reg::get_string() +{ + return i_to_a(p->to_units()); +} + +class nl_reg : public variable_reg { +public: + nl_reg(); + void set_value(units); +}; + +nl_reg::nl_reg() : variable_reg(&nl_reg_contents) +{ +} + +void nl_reg::set_value(units n) +{ + variable_reg::set_value(n); + // Setting nl to a negative value when the vertical position in + // the top-level diversion is 0 undoes the top of page transition, + // so that the header macro will be called as if the top of page + // transition hasn't happened. This is used by Larry Wall's + // wrapman program. Setting before_first_page to 2 rather than 1, + // tells top_level_diversion::begin_page not to call + // output_file::begin_page again. + if (n < 0 && topdiv->get_vertical_position() == V0) + topdiv->before_first_page = 2; +} + +void init_div_requests() +{ + init_request("wh", when_request); + init_request("ch", change_trap); + init_request("pl", page_length); + init_request("po", page_offset); + init_request("rs", restore_spacing); + init_request("ns", no_space); + init_request("sp", space_request); + init_request("di", divert); + init_request("da", divert_append); + init_request("box", box); + init_request("boxa", box_append); + init_request("bp", begin_page); + init_request("ne", need_space); + init_request("pn", page_number); + init_request("dt", diversion_trap); + init_request("rt", return_request); + init_request("mk", mark); + init_request("sv", save_vertical_space); + init_request("os", output_saved_vertical_space); + init_request("fl", flush_output); + init_request("vpt", vertical_position_traps); + init_request("ptr", print_traps); + number_reg_dictionary.define(".a", + new constant_int_reg(&last_post_line_extra_space)); + number_reg_dictionary.define(".z", new diversion_name_reg); + number_reg_dictionary.define(".o", new page_offset_reg); + number_reg_dictionary.define(".p", new page_length_reg); + number_reg_dictionary.define(".d", new vertical_position_reg); + number_reg_dictionary.define(".h", new high_water_mark_reg); + number_reg_dictionary.define(".t", new distance_to_next_trap_reg); + number_reg_dictionary.define("dl", new variable_reg(&dl_reg_contents)); + number_reg_dictionary.define("dn", new variable_reg(&dn_reg_contents)); + number_reg_dictionary.define("nl", new nl_reg); + number_reg_dictionary.define(".vpt", + new constant_int_reg(&vertical_position_traps_flag)); + number_reg_dictionary.define("%", new page_number_reg); + number_reg_dictionary.define(".pn", new next_page_number_reg); + number_reg_dictionary.define(".trunc", + new constant_vunits_reg(&truncated_space)); + number_reg_dictionary.define(".ne", + new constant_vunits_reg(&needed_space)); + number_reg_dictionary.define(".pe", new page_ejecting_reg); +} diff --git a/contrib/groff/src/roff/troff/div.h b/contrib/groff/src/roff/troff/div.h new file mode 100644 index 0000000..83f9e33 --- /dev/null +++ b/contrib/groff/src/roff/troff/div.h @@ -0,0 +1,156 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2001 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +class diversion { + friend void do_divert(int append, int boxing); + friend void end_diversions(); + diversion *prev; + node *saved_line; + hunits saved_width_total; + int saved_space_total; + hunits saved_saved_indent; + hunits saved_target_text_length; + int saved_prev_line_interrupted; +protected: + symbol nm; + vunits vertical_position; + vunits high_water_mark; +public: + vunits marked_place; + diversion(symbol s = NULL_SYMBOL); + virtual ~diversion(); + virtual void output(node *nd, int retain_size, vunits vs, vunits post_vs, + hunits width) = 0; + virtual void transparent_output(unsigned char) = 0; + virtual void transparent_output(node *) = 0; + virtual void space(vunits distance, int forced = 0) = 0; +#ifdef COLUMN + virtual void vjustify(symbol) = 0; +#endif /* COLUMN */ + vunits get_vertical_position() { return vertical_position; } + vunits get_high_water_mark() { return high_water_mark; } + virtual vunits distance_to_next_trap() = 0; + void need(vunits); + const char *get_diversion_name() { return nm.contents(); } + virtual void set_diversion_trap(symbol, vunits) = 0; + virtual void clear_diversion_trap() = 0; + virtual void copy_file(const char *filename) = 0; +}; + +class macro; + +class macro_diversion : public diversion { + macro *mac; + hunits max_width; + symbol diversion_trap; + vunits diversion_trap_pos; +public: + macro_diversion(symbol, int); + ~macro_diversion(); + void output(node *nd, int retain_size, vunits vs, vunits post_vs, + hunits width); + void transparent_output(unsigned char); + void transparent_output(node *); + void space(vunits distance, int forced = 0); +#ifdef COLUMN + void vjustify(symbol); +#endif /* COLUMN */ + vunits distance_to_next_trap(); + void set_diversion_trap(symbol, vunits); + void clear_diversion_trap(); + void copy_file(const char *filename); +}; + +struct trap { + trap *next; + vunits position; + symbol nm; + trap(symbol, vunits, trap *); +}; + +struct output_file; + +class top_level_diversion : public diversion { + int page_number; + int page_count; + int last_page_count; + vunits page_length; + hunits prev_page_offset; + hunits page_offset; + trap *page_trap_list; + trap *find_next_trap(vunits *); + int have_next_page_number; + int next_page_number; + int ejecting_page; // Is the current page being ejected? +public: + int before_first_page; + int no_space_mode; + top_level_diversion(); + void output(node *nd, int retain_size, vunits vs, vunits post_vs, + hunits width); + void transparent_output(unsigned char); + void transparent_output(node *); + void space(vunits distance, int forced = 0); +#ifdef COLUMN + void vjustify(symbol); +#endif /* COLUMN */ + hunits get_page_offset() { return page_offset; } + vunits get_page_length() { return page_length; } + vunits distance_to_next_trap(); + void add_trap(symbol nm, vunits pos); + void change_trap(symbol nm, vunits pos); + void remove_trap(symbol); + void remove_trap_at(vunits pos); + void print_traps(); + int get_page_count() { return page_count; } + int get_page_number() { return page_number; } + int get_next_page_number(); + void set_page_number(int n) { page_number = n; } + int begin_page(); + void set_next_page_number(int); + void set_page_length(vunits); + void copy_file(const char *filename); + int get_ejecting() { return ejecting_page; } + void set_ejecting() { ejecting_page = 1; } + friend void page_offset(); + void set_diversion_trap(symbol, vunits); + void clear_diversion_trap(); + void set_last_page() { last_page_count = page_count; } +}; + +extern top_level_diversion *topdiv; +extern diversion *curdiv; + +extern int exit_started; +extern int done_end_macro; +extern int last_page_number; +extern int seen_last_page_ejector; + +void spring_trap(symbol); // implemented by input.c +extern int trap_sprung_flag; +void postpone_traps(); +int unpostpone_traps(); + +void push_page_ejector(); +void continue_page_eject(); +void handle_first_page_transition(); +void blank_line(); + +extern void cleanup_and_exit(int); diff --git a/contrib/groff/src/roff/troff/env.cc b/contrib/groff/src/roff/troff/env.cc new file mode 100644 index 0000000..56f357c --- /dev/null +++ b/contrib/groff/src/roff/troff/env.cc @@ -0,0 +1,3439 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001 + Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "troff.h" +#include "symbol.h" +#include "dictionary.h" +#include "hvunits.h" +#include "env.h" +#include "request.h" +#include "node.h" +#include "token.h" +#include "div.h" +#include "reg.h" +#include "charinfo.h" +#include "macropath.h" +#include <math.h> + +symbol default_family("T"); + +enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 }; + +enum { HYPHEN_LAST_LINE = 2, HYPHEN_LAST_CHARS = 4, HYPHEN_FIRST_CHARS = 8 }; + +struct env_list { + environment *env; + env_list *next; + env_list(environment *e, env_list *p) : env(e), next(p) {} +}; + +env_list *env_stack; +const int NENVIRONMENTS = 10; +environment *env_table[NENVIRONMENTS]; +dictionary env_dictionary(10); +environment *curenv; +static int next_line_number = 0; + +charinfo *field_delimiter_char; +charinfo *padding_indicator_char; + +int translate_space_to_dummy = 0; + +class pending_output_line { + node *nd; + int no_fill; + vunits vs; + vunits post_vs; + hunits width; +#ifdef WIDOW_CONTROL + int last_line; // Is it the last line of the paragraph? +#endif /* WIDOW_CONTROL */ +public: + pending_output_line *next; + + pending_output_line(node *, int, vunits, vunits, hunits, + pending_output_line * = 0); + ~pending_output_line(); + int output(); + +#ifdef WIDOW_CONTROL + friend void environment::mark_last_line(); + friend void environment::output(node *, int, vunits, vunits, hunits); +#endif /* WIDOW_CONTROL */ +}; + +pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv, + hunits w, pending_output_line *p) +: nd(n), no_fill(nf), vs(v), post_vs(pv), width(w), +#ifdef WIDOW_CONTROL + last_line(0), +#endif /* WIDOW_CONTROL */ + next(p) +{ +} + +pending_output_line::~pending_output_line() +{ + delete_node_list(nd); +} + +int pending_output_line::output() +{ + if (trap_sprung_flag) + return 0; +#ifdef WIDOW_CONTROL + if (next && next->last_line && !no_fill) { + curdiv->need(vs + post_vs + vunits(vresolution)); + if (trap_sprung_flag) { + next->last_line = 0; // Try to avoid infinite loops. + return 0; + } + } +#endif + curdiv->output(nd, no_fill, vs, post_vs, width); + nd = 0; + return 1; +} + +void environment::output(node *nd, int no_fill, vunits vs, vunits post_vs, + hunits width) +{ +#ifdef WIDOW_CONTROL + while (pending_lines) { + if (widow_control && !pending_lines->no_fill && !pending_lines->next) + break; + if (!pending_lines->output()) + break; + pending_output_line *tem = pending_lines; + pending_lines = pending_lines->next; + delete tem; + } +#else /* WIDOW_CONTROL */ + output_pending_lines(); +#endif /* WIDOW_CONTROL */ + if (!trap_sprung_flag && !pending_lines +#ifdef WIDOW_CONTROL + && (!widow_control || no_fill) +#endif /* WIDOW_CONTROL */ + ) { + curdiv->output(nd, no_fill, vs, post_vs, width); + emitted_node = 1; + } else { + pending_output_line **p; + for (p = &pending_lines; *p; p = &(*p)->next) + ; + *p = new pending_output_line(nd, no_fill, vs, post_vs, width); + } +} + +// a line from .tl goes at the head of the queue + +void environment::output_title(node *nd, int no_fill, vunits vs, + vunits post_vs, hunits width) +{ + if (!trap_sprung_flag) + curdiv->output(nd, no_fill, vs, post_vs, width); + else + pending_lines = new pending_output_line(nd, no_fill, vs, post_vs, width, + pending_lines); +} + +void environment::output_pending_lines() +{ + while (pending_lines && pending_lines->output()) { + pending_output_line *tem = pending_lines; + pending_lines = pending_lines->next; + delete tem; + } +} + +#ifdef WIDOW_CONTROL + +void environment::mark_last_line() +{ + if (!widow_control || !pending_lines) + return; + for (pending_output_line *p = pending_lines; p->next; p = p->next) + ; + if (!p->no_fill) + p->last_line = 1; +} + +void widow_control_request() +{ + int n; + if (has_arg() && get_integer(&n)) + curenv->widow_control = n != 0; + else + curenv->widow_control = 1; + skip_line(); +} + +#endif /* WIDOW_CONTROL */ + +/* font_size functions */ + +size_range *font_size::size_table = 0; +int font_size::nranges = 0; + +extern "C" { + +int compare_ranges(const void *p1, const void *p2) +{ + return ((size_range *)p1)->min - ((size_range *)p2)->min; +} + +} + +void font_size::init_size_table(int *sizes) +{ + nranges = 0; + while (sizes[nranges*2] != 0) + nranges++; + assert(nranges > 0); + size_table = new size_range[nranges]; + for (int i = 0; i < nranges; i++) { + size_table[i].min = sizes[i*2]; + size_table[i].max = sizes[i*2 + 1]; + } + qsort(size_table, nranges, sizeof(size_range), compare_ranges); +} + +font_size::font_size(int sp) +{ + for (int i = 0; i < nranges; i++) { + if (sp < size_table[i].min) { + if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max) + p = size_table[i - 1].max; + else + p = size_table[i].min; + return; + } + if (sp <= size_table[i].max) { + p = sp; + return; + } + } + p = size_table[nranges - 1].max; +} + +int font_size::to_units() +{ + return scale(p, units_per_inch, sizescale*72); +} + +// we can't do this in a static constructor because various dictionaries +// have to get initialized first + +void init_environments() +{ + curenv = env_table[0] = new environment("0"); +} + +void tab_character() +{ + curenv->tab_char = get_optional_char(); + skip_line(); +} + +void leader_character() +{ + curenv->leader_char = get_optional_char(); + skip_line(); +} + +void environment::add_char(charinfo *ci) +{ + int s; + if (interrupted) + ; + // don't allow fields in dummy environments + else if (ci == field_delimiter_char && !dummy) { + if (current_field) + wrap_up_field(); + else + start_field(); + } + else if (current_field && ci == padding_indicator_char) + add_padding(); + else if (current_tab) { + if (tab_contents == 0) + tab_contents = new line_start_node; + if (ci != hyphen_indicator_char) + tab_contents = tab_contents->add_char(ci, this, &tab_width, &s); + else + tab_contents = tab_contents->add_discretionary_hyphen(); + } + else { + if (line == 0) + start_line(); + if (ci != hyphen_indicator_char) + line = line->add_char(ci, this, &width_total, &space_total); + else + line = line->add_discretionary_hyphen(); + } +} + +node *environment::make_char_node(charinfo *ci) +{ + return make_node(ci, this); +} + +void environment::add_node(node *n) +{ + assert(n != 0); + if (current_tab || current_field) + n->freeze_space(); + if (interrupted) { + delete n; + } + else if (current_tab) { + n->next = tab_contents; + tab_contents = n; + tab_width += n->width(); + } + else { + if (line == 0) { + if (discarding && n->discardable()) { + // XXX possibly: input_line_start -= n->width(); + delete n; + return; + } + start_line(); + } + width_total += n->width(); + space_total += n->nspaces(); + n->next = line; + line = n; + } +} + + +void environment::add_hyphen_indicator() +{ + if (current_tab || interrupted || current_field + || hyphen_indicator_char != 0) + return; + if (line == 0) + start_line(); + line = line->add_discretionary_hyphen(); +} + +int environment::get_hyphenation_flags() +{ + return hyphenation_flags; +} + +int environment::get_hyphen_line_max() +{ + return hyphen_line_max; +} + +int environment::get_hyphen_line_count() +{ + return hyphen_line_count; +} + +int environment::get_center_lines() +{ + return center_lines; +} + +int environment::get_right_justify_lines() +{ + return right_justify_lines; +} + +void environment::add_italic_correction() +{ + if (current_tab) { + if (tab_contents) + tab_contents = tab_contents->add_italic_correction(&tab_width); + } + else if (line) + line = line->add_italic_correction(&width_total); +} + +void environment::space_newline() +{ + assert(!current_tab && !current_field); + if (interrupted) + return; + hunits x = H0; + hunits sw = env_space_width(this); + hunits ssw = env_sentence_space_width(this); + if (!translate_space_to_dummy) { + x = sw; + if (node_list_ends_sentence(line) == 1) + x += ssw; + } + width_list *w = new width_list(sw, ssw); + if (node_list_ends_sentence(line) == 1) + w->next = new width_list(sw, ssw); + if (line != 0 && line->merge_space(x, sw, ssw)) { + width_total += x; + return; + } + add_node(new word_space_node(x, w)); + possibly_break_line(0, spread_flag); + spread_flag = 0; +} + +void environment::space() +{ + space(env_space_width(this), env_sentence_space_width(this)); +} + +void environment::space(hunits space_width, hunits sentence_space_width) +{ + if (interrupted) + return; + if (current_field && padding_indicator_char == 0) { + add_padding(); + return; + } + hunits x = translate_space_to_dummy ? H0 : space_width; + node *p = current_tab ? tab_contents : line; + hunits *tp = current_tab ? &tab_width : &width_total; + if (p && p->nspaces() == 1 && p->width() == x + && node_list_ends_sentence(p->next) == 1) { + hunits xx = translate_space_to_dummy ? H0 : sentence_space_width; + if (p->merge_space(xx, space_width, sentence_space_width)) { + *tp += xx; + return; + } + } + if (p && p->merge_space(x, space_width, sentence_space_width)) { + *tp += x; + return; + } + add_node(new word_space_node(x, + new width_list(space_width, + sentence_space_width))); + possibly_break_line(0, spread_flag); + spread_flag = 0; +} + +node *do_underline_special(int); + +void environment::set_font(symbol nm) +{ + if (interrupted) + return; + if (nm == symbol("P")) { + if (family->make_definite(prev_fontno) < 0) + return; + int tem = fontno; + fontno = prev_fontno; + prev_fontno = tem; + } + else { + prev_fontno = fontno; + int n = symbol_fontno(nm); + if (n < 0) { + n = next_available_font_position(); + if (!mount_font(n, nm)) + return; + } + if (family->make_definite(n) < 0) + return; + fontno = n; + } + if (underline_spaces && fontno != prev_fontno) { + if (fontno == get_underline_fontno()) + add_node(do_underline_special(1)); + if (prev_fontno == get_underline_fontno()) + add_node(do_underline_special(0)); + } +} + +void environment::set_font(int n) +{ + if (interrupted) + return; + if (is_good_fontno(n)) { + prev_fontno = fontno; + fontno = n; + } + else + warning(WARN_FONT, "bad font number"); +} + +void environment::set_family(symbol fam) +{ + if (fam.is_null()) { + if (prev_family->make_definite(fontno) < 0) + return; + font_family *tem = family; + family = prev_family; + prev_family = tem; + } + else { + font_family *f = lookup_family(fam); + if (f->make_definite(fontno) < 0) + return; + prev_family = family; + family = f; + } +} + +void environment::set_size(int n) +{ + if (interrupted) + return; + if (n == 0) { + font_size temp = prev_size; + prev_size = size; + size = temp; + int temp2 = prev_requested_size; + prev_requested_size = requested_size; + requested_size = temp2; + } + else { + prev_size = size; + size = font_size(n); + prev_requested_size = requested_size; + requested_size = n; + } +} + +void environment::set_char_height(int n) +{ + if (interrupted) + return; + if (n == requested_size || n <= 0) + char_height = 0; + else + char_height = n; +} + +void environment::set_char_slant(int n) +{ + if (interrupted) + return; + char_slant = n; +} + +environment::environment(symbol nm) +: dummy(0), + prev_line_length((units_per_inch*13)/2), + line_length((units_per_inch*13)/2), + prev_title_length((units_per_inch*13)/2), + title_length((units_per_inch*13)/2), + prev_size(sizescale*10), + size(sizescale*10), + requested_size(sizescale*10), + prev_requested_size(sizescale*10), + char_height(0), + char_slant(0), + space_size(12), + sentence_space_size(12), + adjust_mode(ADJUST_BOTH), + fill(1), + interrupted(0), + prev_line_interrupted(0), + center_lines(0), + right_justify_lines(0), + prev_vertical_spacing(points_to_units(12)), + vertical_spacing(points_to_units(12)), + prev_post_vertical_spacing(0), + post_vertical_spacing(0), + prev_line_spacing(1), + line_spacing(1), + prev_indent(0), + indent(0), + temporary_indent(0), + have_temporary_indent(0), + underline_lines(0), + underline_spaces(0), + input_trap_count(0), + line(0), + prev_text_length(0), + width_total(0), + space_total(0), + input_line_start(0), + tabs(units_per_inch/2, TAB_LEFT), + line_tabs(0), + current_tab(TAB_NONE), + leader_node(0), + tab_char(0), + leader_char(charset_table['.']), + current_field(0), + discarding(0), + spread_flag(0), + margin_character_flags(0), + margin_character_node(0), + margin_character_distance(points_to_units(10)), + numbering_nodes(0), + number_text_separation(1), + line_number_indent(0), + line_number_multiple(1), + no_number_count(0), + hyphenation_flags(1), + hyphen_line_count(0), + hyphen_line_max(-1), + hyphenation_space(H0), + hyphenation_margin(H0), + composite(0), + pending_lines(0), +#ifdef WIDOW_CONTROL + widow_control(0), +#endif /* WIDOW_CONTROL */ + need_eol(0), + ignore_next_eol(0), + emitted_node(0), + name(nm), + control_char('.'), + no_break_control_char('\''), + hyphen_indicator_char(0) +{ + prev_family = family = lookup_family(default_family); + prev_fontno = fontno = 1; + if (!is_good_fontno(1)) + fatal("font number 1 not a valid font"); + if (family->make_definite(1) < 0) + fatal("invalid default family `%1'", default_family.contents()); + prev_fontno = fontno; +} + +environment::environment(const environment *e) +: dummy(1), + prev_line_length(e->prev_line_length), + line_length(e->line_length), + prev_title_length(e->prev_title_length), + title_length(e->title_length), + prev_size(e->prev_size), + size(e->size), + requested_size(e->requested_size), + prev_requested_size(e->prev_requested_size), + char_height(e->char_height), + char_slant(e->char_slant), + prev_fontno(e->prev_fontno), + fontno(e->fontno), + prev_family(e->prev_family), + family(e->family), + space_size(e->space_size), + sentence_space_size(e->sentence_space_size), + adjust_mode(e->adjust_mode), + fill(e->fill), + interrupted(0), + prev_line_interrupted(0), + center_lines(0), + right_justify_lines(0), + prev_vertical_spacing(e->prev_vertical_spacing), + vertical_spacing(e->vertical_spacing), + prev_post_vertical_spacing(e->prev_post_vertical_spacing), + post_vertical_spacing(e->post_vertical_spacing), + prev_line_spacing(e->prev_line_spacing), + line_spacing(e->line_spacing), + prev_indent(e->prev_indent), + indent(e->indent), + temporary_indent(0), + have_temporary_indent(0), + underline_lines(0), + underline_spaces(0), + input_trap_count(0), + line(0), + prev_text_length(e->prev_text_length), + width_total(0), + space_total(0), + input_line_start(0), + tabs(e->tabs), + line_tabs(e->line_tabs), + current_tab(TAB_NONE), + leader_node(0), + tab_char(e->tab_char), + leader_char(e->leader_char), + current_field(0), + discarding(0), + spread_flag(0), + margin_character_flags(e->margin_character_flags), + margin_character_node(e->margin_character_node), + margin_character_distance(e->margin_character_distance), + numbering_nodes(0), + number_text_separation(e->number_text_separation), + line_number_indent(e->line_number_indent), + line_number_multiple(e->line_number_multiple), + no_number_count(e->no_number_count), + hyphenation_flags(e->hyphenation_flags), + hyphen_line_count(0), + hyphen_line_max(e->hyphen_line_max), + hyphenation_space(e->hyphenation_space), + hyphenation_margin(e->hyphenation_margin), + composite(0), + pending_lines(0), +#ifdef WIDOW_CONTROL + widow_control(e->widow_control), +#endif /* WIDOW_CONTROL */ + need_eol(0), + ignore_next_eol(0), + name(e->name), // so that eg `.if "\n[.ev]"0"' works + control_char(e->control_char), + no_break_control_char(e->no_break_control_char), + hyphen_indicator_char(e->hyphen_indicator_char) +{ +} + +void environment::copy(const environment *e) +{ + prev_line_length = e->prev_line_length; + line_length = e->line_length; + prev_title_length = e->prev_title_length; + title_length = e->title_length; + prev_size = e->prev_size; + size = e->size; + prev_requested_size = e->prev_requested_size; + requested_size = e->requested_size; + char_height = e->char_height; + char_slant = e->char_slant; + space_size = e->space_size; + sentence_space_size = e->sentence_space_size; + adjust_mode = e->adjust_mode; + fill = e->fill; + interrupted = 0; + prev_line_interrupted = 0; + center_lines = 0; + right_justify_lines = 0; + prev_vertical_spacing = e->prev_vertical_spacing; + vertical_spacing = e->vertical_spacing; + prev_post_vertical_spacing = e->prev_post_vertical_spacing, + post_vertical_spacing = e->post_vertical_spacing, + prev_line_spacing = e->prev_line_spacing; + line_spacing = e->line_spacing; + prev_indent = e->prev_indent; + indent = e->indent; + have_temporary_indent = 0; + temporary_indent = 0; + underline_lines = 0; + underline_spaces = 0; + input_trap_count = 0; + prev_text_length = e->prev_text_length; + width_total = 0; + space_total = 0; + input_line_start = 0; + control_char = e->control_char; + no_break_control_char = e->no_break_control_char; + hyphen_indicator_char = e->hyphen_indicator_char; + spread_flag = 0; + line = 0; + pending_lines = 0; + discarding = 0; + tabs = e->tabs; + line_tabs = e->line_tabs; + current_tab = TAB_NONE; + current_field = 0; + margin_character_flags = e->margin_character_flags; + margin_character_node = e->margin_character_node; + margin_character_distance = e->margin_character_distance; + numbering_nodes = 0; + number_text_separation = e->number_text_separation; + line_number_multiple = e->line_number_multiple; + line_number_indent = e->line_number_indent; + no_number_count = e->no_number_count; + tab_char = e->tab_char; + leader_char = e->leader_char; + hyphenation_flags = e->hyphenation_flags; + fontno = e->fontno; + prev_fontno = e->prev_fontno; + dummy = e->dummy; + family = e->family; + prev_family = e->prev_family; + leader_node = 0; +#ifdef WIDOW_CONTROL + widow_control = e->widow_control; +#endif /* WIDOW_CONTROL */ + hyphen_line_max = e->hyphen_line_max; + hyphen_line_count = 0; + hyphenation_space = e->hyphenation_space; + hyphenation_margin = e->hyphenation_margin; + composite = 0; +} + +environment::~environment() +{ + delete leader_node; + delete_node_list(line); + delete_node_list(numbering_nodes); +} + +hunits environment::get_input_line_position() +{ + hunits n; + if (line == 0) + n = -input_line_start; + else + n = width_total - input_line_start; + if (current_tab) + n += tab_width; + return n; +} + +void environment::set_input_line_position(hunits n) +{ + input_line_start = line == 0 ? -n : width_total - n; + if (current_tab) + input_line_start += tab_width; +} + +hunits environment::get_line_length() +{ + return line_length; +} + +hunits environment::get_saved_line_length() +{ + if (line) + return target_text_length + saved_indent; + else + return line_length; +} + +vunits environment::get_vertical_spacing() +{ + return vertical_spacing; +} + +vunits environment::get_post_vertical_spacing() +{ + return post_vertical_spacing; +} + +int environment::get_line_spacing() +{ + return line_spacing; +} + +vunits environment::total_post_vertical_spacing() +{ + vunits tem(post_vertical_spacing); + if (line_spacing > 1) + tem += (line_spacing - 1)*vertical_spacing; + return tem; +} + +int environment::get_bold() +{ + return get_bold_fontno(fontno); +} + +hunits environment::get_digit_width() +{ + return env_digit_width(this); +} + +int environment::get_adjust_mode() +{ + return adjust_mode; +} + +int environment::get_fill() +{ + return fill; +} + +hunits environment::get_indent() +{ + return indent; +} + +hunits environment::get_saved_indent() +{ + if (line) + return saved_indent; + else if (have_temporary_indent) + return temporary_indent; + else + return indent; +} + +hunits environment::get_temporary_indent() +{ + return temporary_indent; +} + +hunits environment::get_title_length() +{ + return title_length; +} + +node *environment::get_prev_char() +{ + for (node *n = current_tab ? tab_contents : line; n; n = n->next) { + node *last = n->last_char_node(); + if (last) + return last; + } + return 0; +} + +hunits environment::get_prev_char_width() +{ + node *last = get_prev_char(); + if (!last) + return H0; + return last->width(); +} + +hunits environment::get_prev_char_skew() +{ + node *last = get_prev_char(); + if (!last) + return H0; + return last->skew(); +} + +vunits environment::get_prev_char_height() +{ + node *last = get_prev_char(); + if (!last) + return V0; + vunits min, max; + last->vertical_extent(&min, &max); + return -min; +} + +vunits environment::get_prev_char_depth() +{ + node *last = get_prev_char(); + if (!last) + return V0; + vunits min, max; + last->vertical_extent(&min, &max); + return max; +} + +hunits environment::get_text_length() +{ + hunits n = line == 0 ? H0 : width_total; + if (current_tab) + n += tab_width; + return n; +} + +hunits environment::get_prev_text_length() +{ + return prev_text_length; +} + + +static int sb_reg_contents = 0; +static int st_reg_contents = 0; +static int ct_reg_contents = 0; +static int rsb_reg_contents = 0; +static int rst_reg_contents = 0; +static int skw_reg_contents = 0; +static int ssc_reg_contents = 0; + +void environment::width_registers() +{ + // this is used to implement \w; it sets the st, sb, ct registers + vunits min = 0, max = 0, cur = 0; + int character_type = 0; + ssc_reg_contents = line ? line->subscript_correction().to_units() : 0; + skw_reg_contents = line ? line->skew().to_units() : 0; + line = reverse_node_list(line); + vunits real_min = V0; + vunits real_max = V0; + vunits v1, v2; + for (node *tem = line; tem; tem = tem->next) { + tem->vertical_extent(&v1, &v2); + v1 += cur; + if (v1 < real_min) + real_min = v1; + v2 += cur; + if (v2 > real_max) + real_max = v2; + if ((cur += tem->vertical_width()) < min) + min = cur; + else if (cur > max) + max = cur; + character_type |= tem->character_type(); + } + line = reverse_node_list(line); + st_reg_contents = -min.to_units(); + sb_reg_contents = -max.to_units(); + rst_reg_contents = -real_min.to_units(); + rsb_reg_contents = -real_max.to_units(); + ct_reg_contents = character_type; +} + +node *environment::extract_output_line() +{ + if (current_tab) + wrap_up_tab(); + node *n = line; + line = 0; + return n; +} + +/* environment related requests */ + +void environment_switch() +{ + int pop = 0; // 1 means pop, 2 means pop but no error message on underflow + if (curenv->is_dummy()) + error("can't switch environments when current environment is dummy"); + else if (!has_arg()) + pop = 1; + else { + symbol nm; + if (!tok.delimiter()) { + // It looks like a number. + int n; + if (get_integer(&n)) { + if (n >= 0 && n < NENVIRONMENTS) { + env_stack = new env_list(curenv, env_stack); + if (env_table[n] == 0) + env_table[n] = new environment(i_to_a(n)); + curenv = env_table[n]; + } + else + nm = i_to_a(n); + } + else + pop = 2; + } + else { + nm = get_long_name(1); + if (nm.is_null()) + pop = 2; + } + if (!nm.is_null()) { + environment *e = (environment *)env_dictionary.lookup(nm); + if (!e) { + e = new environment(nm); + (void)env_dictionary.lookup(nm, e); + } + env_stack = new env_list(curenv, env_stack); + curenv = e; + } + } + if (pop) { + if (env_stack == 0) { + if (pop == 1) + error("environment stack underflow"); + } + else { + curenv = env_stack->env; + env_list *tem = env_stack; + env_stack = env_stack->next; + delete tem; + } + } + skip_line(); +} + +void environment_copy() +{ + symbol nm; + environment *e=0; + tok.skip(); + if (!tok.delimiter()) { + // It looks like a number. + int n; + if (get_integer(&n)) { + if (n >= 0 && n < NENVIRONMENTS) + e = env_table[n]; + else + nm = i_to_a(n); + } + } + else + nm = get_long_name(1); + if (!e && !nm.is_null()) + e = (environment *)env_dictionary.lookup(nm); + if (e == 0) { + error("No environment to copy from"); + return; + } + else + curenv->copy(e); + skip_line(); +} + +static symbol P_symbol("P"); + +void font_change() +{ + symbol s = get_name(); + int is_number = 1; + if (s.is_null() || s == P_symbol) { + s = P_symbol; + is_number = 0; + } + else { + for (const char *p = s.contents(); p != 0 && *p != 0; p++) + if (!csdigit(*p)) { + is_number = 0; + break; + } + } + if (is_number) + curenv->set_font(atoi(s.contents())); + else + curenv->set_font(s); + skip_line(); +} + +void family_change() +{ + symbol s = get_name(); + curenv->set_family(s); + skip_line(); +} + +void point_size() +{ + int n; + if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) { + if (n <= 0) + n = 1; + curenv->set_size(n); + curenv->add_html_tag(".ps", n); + } + else + curenv->set_size(0); + skip_line(); +} + +void space_size() +{ + int n; + if (get_integer(&n) && !compatible_flag) { + curenv->space_size = n; + if (has_arg() && get_integer(&n)) + curenv->sentence_space_size = n; + else + curenv->sentence_space_size = curenv->space_size; + } + skip_line(); +} + +void fill() +{ + while (!tok.newline() && !tok.eof()) + tok.next(); + if (break_flag) + curenv->do_break(); + curenv->fill = 1; + curenv->add_html_tag(".fi"); + tok.next(); +} + +void no_fill() +{ + while (!tok.newline() && !tok.eof()) + tok.next(); + if (break_flag) + curenv->do_break(); + + curenv->fill = 0; + curenv->add_html_tag(".nf"); + curenv->ignore_next_eol = 1; + curenv->add_html_tag(".po", topdiv->get_page_offset().to_units()); + tok.next(); +} + +void center() +{ + int n; + if (!has_arg() || !get_integer(&n)) + n = 1; + else if (n < 0) + n = 0; + while (!tok.newline() && !tok.eof()) + tok.next(); + if (break_flag) + curenv->do_break(); + curenv->right_justify_lines = 0; + curenv->center_lines = n; + curenv->add_html_tag(".ce", n); + tok.next(); +} + +void right_justify() +{ + int n; + if (!has_arg() || !get_integer(&n)) + n = 1; + else if (n < 0) + n = 0; + while (!tok.newline() && !tok.eof()) + tok.next(); + if (break_flag) + curenv->do_break(); + curenv->center_lines = 0; + curenv->right_justify_lines = n; + curenv->add_html_tag(".rj", n); + tok.next(); +} + +void line_length() +{ + hunits temp; + if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) { + if (temp < H0) { + warning(WARN_RANGE, "bad line length %1u", temp.to_units()); + temp = H0; + } + } + else + temp = curenv->prev_line_length; + curenv->prev_line_length = curenv->line_length; + curenv->line_length = temp; + curenv->add_html_tag(".ll", temp.to_units()); + skip_line(); +} + +void title_length() +{ + hunits temp; + if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) { + if (temp < H0) { + warning(WARN_RANGE, "bad title length %1u", temp.to_units()); + temp = H0; + } + } + else + temp = curenv->prev_title_length; + curenv->prev_title_length = curenv->title_length; + curenv->title_length = temp; + skip_line(); +} + +void vertical_spacing() +{ + vunits temp; + if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) { + if (temp <= V0) { + warning(WARN_RANGE, "vertical spacing must be greater than 0"); + temp = vresolution; + } + } + else + temp = curenv->prev_vertical_spacing; + curenv->prev_vertical_spacing = curenv->vertical_spacing; + curenv->vertical_spacing = temp; + skip_line(); +} + +void post_vertical_spacing() +{ + vunits temp; + if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) { + if (temp < V0) { + warning(WARN_RANGE, + "post vertical spacing must be greater than or equal to 0"); + temp = V0; + } + } + else + temp = curenv->prev_post_vertical_spacing; + curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing; + curenv->post_vertical_spacing = temp; + skip_line(); +} + +void line_spacing() +{ + int temp; + if (has_arg() && get_integer(&temp)) { + if (temp < 1) { + warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp); + temp = 1; + } + } + else + temp = curenv->prev_line_spacing; + curenv->prev_line_spacing = curenv->line_spacing; + curenv->line_spacing = temp; + skip_line(); +} + +void indent() +{ + hunits temp; + if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) { + if (temp < H0) { + warning(WARN_RANGE, "indent cannot be negative"); + temp = H0; + } + } + else + temp = curenv->prev_indent; + while (!tok.newline() && !tok.eof()) + tok.next(); + if (break_flag) + curenv->do_break(); + curenv->have_temporary_indent = 0; + curenv->prev_indent = curenv->indent; + curenv->indent = temp; + curenv->add_html_tag(".in", temp.to_units()); + tok.next(); +} + +void temporary_indent() +{ + int err = 0; + hunits temp; + if (!get_hunits(&temp, 'm', curenv->get_indent())) + err = 1; + while (!tok.newline() && !tok.eof()) + tok.next(); + if (break_flag) + curenv->do_break(); + if (temp < H0) { + warning(WARN_RANGE, "total indent cannot be negative"); + temp = H0; + } + if (!err) { + curenv->temporary_indent = temp; + curenv->have_temporary_indent = 1; + curenv->add_html_tag(".ti", temp.to_units()); + } + tok.next(); +} + +node *do_underline_special(int underline_spaces) +{ + macro m; + m.append_str("x u "); + m.append(underline_spaces + '0'); + return new special_node(m, 1); +} + +void do_underline(int underline_spaces) +{ + int n; + if (!has_arg() || !get_integer(&n)) + n = 1; + if (n <= 0) { + if (curenv->underline_lines > 0) { + curenv->prev_fontno = curenv->fontno; + curenv->fontno = curenv->pre_underline_fontno; + if (underline_spaces) { + curenv->underline_spaces = 0; + curenv->add_node(do_underline_special(0)); + } + } + curenv->underline_lines = 0; + } + else { + curenv->underline_lines = n; + curenv->pre_underline_fontno = curenv->fontno; + curenv->fontno = get_underline_fontno(); + if (underline_spaces) { + curenv->underline_spaces = 1; + curenv->add_node(do_underline_special(1)); + } + } + skip_line(); +} + +void continuous_underline() +{ + do_underline(1); +} + +void underline() +{ + do_underline(0); +} + +void control_char() +{ + curenv->control_char = '.'; + if (has_arg()) { + if (tok.ch() == 0) + error("bad control character"); + else + curenv->control_char = tok.ch(); + } + skip_line(); +} + +void no_break_control_char() +{ + curenv->no_break_control_char = '\''; + if (has_arg()) { + if (tok.ch() == 0) + error("bad control character"); + else + curenv->no_break_control_char = tok.ch(); + } + skip_line(); +} + +void margin_character() +{ + while (tok.space()) + tok.next(); + charinfo *ci = tok.get_char(); + if (ci) { + // Call tok.next() only after making the node so that + // .mc \s+9\(br\s0 works. + node *nd = curenv->make_char_node(ci); + tok.next(); + if (nd) { + delete curenv->margin_character_node; + curenv->margin_character_node = nd; + curenv->margin_character_flags = (MARGIN_CHARACTER_ON + |MARGIN_CHARACTER_NEXT); + hunits d; + if (has_arg() && get_hunits(&d, 'm')) + curenv->margin_character_distance = d; + } + } + else { + check_missing_character(); + curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON; + if (curenv->margin_character_flags == 0) { + delete curenv->margin_character_node; + curenv->margin_character_node = 0; + } + } + skip_line(); +} + +void number_lines() +{ + delete_node_list(curenv->numbering_nodes); + curenv->numbering_nodes = 0; + if (has_arg()) { + node *nd = 0; + for (int i = '9'; i >= '0'; i--) { + node *tem = make_node(charset_table[i], curenv); + if (!tem) { + skip_line(); + return; + } + tem->next = nd; + nd = tem; + } + curenv->numbering_nodes = nd; + curenv->line_number_digit_width = env_digit_width(curenv); + int n; + if (!tok.delimiter()) { + if (get_integer(&n, next_line_number)) { + next_line_number = n; + if (next_line_number < 0) { + warning(WARN_RANGE, "negative line number"); + next_line_number = 0; + } + } + } + else + while (!tok.space() && !tok.newline() && !tok.eof()) + tok.next(); + if (has_arg()) { + if (!tok.delimiter()) { + if (get_integer(&n)) { + if (n <= 0) { + warning(WARN_RANGE, "negative or zero line number multiple"); + } + else + curenv->line_number_multiple = n; + } + } + else + while (!tok.space() && !tok.newline() && !tok.eof()) + tok.next(); + if (has_arg()) { + if (!tok.delimiter()) { + if (get_integer(&n)) + curenv->number_text_separation = n; + } + else + while (!tok.space() && !tok.newline() && !tok.eof()) + tok.next(); + if (has_arg() && !tok.delimiter() && get_integer(&n)) + curenv->line_number_indent = n; + } + } + } + skip_line(); +} + +void no_number() +{ + int n; + if (has_arg() && get_integer(&n)) + curenv->no_number_count = n > 0 ? n : 0; + else + curenv->no_number_count = 1; + skip_line(); +} + +void no_hyphenate() +{ + curenv->hyphenation_flags = 0; + skip_line(); +} + +void hyphenate_request() +{ + int n; + if (has_arg() && get_integer(&n)) + curenv->hyphenation_flags = n; + else + curenv->hyphenation_flags = 1; + skip_line(); +} + +void hyphen_char() +{ + curenv->hyphen_indicator_char = get_optional_char(); + skip_line(); +} + +void hyphen_line_max_request() +{ + int n; + if (has_arg() && get_integer(&n)) + curenv->hyphen_line_max = n; + else + curenv->hyphen_line_max = -1; + skip_line(); +} + +void environment::interrupt() +{ + if (!dummy) { + add_node(new transparent_dummy_node); + interrupted = 1; + } +} + +void environment::newline() +{ + if (underline_lines > 0) { + if (--underline_lines == 0) { + prev_fontno = fontno; + fontno = pre_underline_fontno; + if (underline_spaces) { + underline_spaces = 0; + add_node(do_underline_special(0)); + } + } + } + if (current_field) + wrap_up_field(); + if (current_tab) + wrap_up_tab(); + // strip trailing spaces + while (line != 0 && line->discardable()) { + width_total -= line->width(); + space_total -= line->nspaces(); + node *tem = line; + line = line->next; + delete tem; + } + node *to_be_output = 0; + hunits to_be_output_width; + prev_line_interrupted = 0; + if (dummy) + space_newline(); + else if (interrupted) { + interrupted = 0; + // see environment::final_break + prev_line_interrupted = exit_started ? 2 : 1; + } + else if (center_lines > 0) { + --center_lines; + hunits x = target_text_length - width_total; + if (x > H0) + saved_indent += x/2; + to_be_output = line; + to_be_output_width = width_total; + line = 0; + } + else if (right_justify_lines > 0) { + --right_justify_lines; + hunits x = target_text_length - width_total; + if (x > H0) + saved_indent += x; + to_be_output = line; + to_be_output_width = width_total; + line = 0; + } + else if (fill) + space_newline(); + else { + to_be_output = line; + to_be_output_width = width_total; + line = 0; + } + input_line_start = line == 0 ? H0 : width_total; + if (to_be_output) { + output_line(to_be_output, to_be_output_width); + hyphen_line_count = 0; + } + if (input_trap_count > 0) { + if (--input_trap_count == 0) + spring_trap(input_trap); + } +} + +void environment::output_line(node *n, hunits width) +{ + prev_text_length = width; + if (margin_character_flags) { + hunits d = line_length + margin_character_distance - saved_indent - width; + if (d > 0) { + n = new hmotion_node(d, n); + width += d; + } + margin_character_flags &= ~MARGIN_CHARACTER_NEXT; + node *tem; + if (!margin_character_flags) { + tem = margin_character_node; + margin_character_node = 0; + } + else + tem = margin_character_node->copy(); + tem->next = n; + n = tem; + width += tem->width(); + } + node *nn = 0; + while (n != 0) { + node *tem = n->next; + n->next = nn; + nn = n; + n = tem; + } + if (!saved_indent.is_zero()) + nn = new hmotion_node(saved_indent, nn); + width += saved_indent; + if (no_number_count > 0) + --no_number_count; + else if (numbering_nodes) { + hunits w = (line_number_digit_width + *(3+line_number_indent+number_text_separation)); + if (next_line_number % line_number_multiple != 0) + nn = new hmotion_node(w, nn); + else { + hunits x = w; + nn = new hmotion_node(number_text_separation*line_number_digit_width, + nn); + x -= number_text_separation*line_number_digit_width; + char buf[30]; + sprintf(buf, "%3d", next_line_number); + for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) { + node *gn = numbering_nodes; + for (int count = *p - '0'; count > 0; count--) + gn = gn->next; + gn = gn->copy(); + x -= gn->width(); + gn->next = nn; + nn = gn; + } + nn = new hmotion_node(x, nn); + } + width += w; + ++next_line_number; + } + output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width); +} + +void environment::start_line() +{ + assert(line == 0); + discarding = 0; + line = new line_start_node; + if (have_temporary_indent) { + saved_indent = temporary_indent; + have_temporary_indent = 0; + } + else + saved_indent = indent; + target_text_length = line_length - saved_indent; + width_total = H0; + space_total = 0; +} + +hunits environment::get_hyphenation_space() +{ + return hyphenation_space; +} + +void hyphenation_space_request() +{ + hunits n; + if (get_hunits(&n, 'm')) { + if (n < H0) { + warning(WARN_RANGE, "hyphenation space cannot be negative"); + n = H0; + } + curenv->hyphenation_space = n; + } + skip_line(); +} + +hunits environment::get_hyphenation_margin() +{ + return hyphenation_margin; +} + +void hyphenation_margin_request() +{ + hunits n; + if (get_hunits(&n, 'm')) { + if (n < H0) { + warning(WARN_RANGE, "hyphenation margin cannot be negative"); + n = H0; + } + curenv->hyphenation_margin = n; + } + skip_line(); +} + +breakpoint *environment::choose_breakpoint() +{ + hunits x = width_total; + int s = space_total; + node *n = line; + breakpoint *best_bp = 0; // the best breakpoint so far + int best_bp_fits = 0; + while (n != 0) { + x -= n->width(); + s -= n->nspaces(); + breakpoint *bp = n->get_breakpoints(x, s); + while (bp != 0) { + if (bp->width <= target_text_length) { + if (!bp->hyphenated) { + breakpoint *tem = bp->next; + bp->next = 0; + while (tem != 0) { + breakpoint *tem1 = tem; + tem = tem->next; + delete tem1; + } + if (best_bp_fits + // Decide whether to use the hyphenated breakpoint. + && (hyphen_line_max < 0 + // Only choose the hyphenated breakpoint if it would not + // exceed the maximum number of consecutive hyphenated + // lines. + || hyphen_line_count + 1 <= hyphen_line_max) + && !(adjust_mode == ADJUST_BOTH + // Don't choose the hyphenated breakpoint if the line + // can be justified by adding no more than + // hyphenation_space to any word space. + ? (bp->nspaces > 0 + && (((target_text_length - bp->width + + (bp->nspaces - 1)*hresolution)/bp->nspaces) + <= hyphenation_space)) + // Don't choose the hyphenated breakpoint if the line + // is no more than hyphenation_margin short. + : target_text_length - bp->width <= hyphenation_margin)) { + delete bp; + return best_bp; + } + if (best_bp) + delete best_bp; + return bp; + } + else { + if ((adjust_mode == ADJUST_BOTH + ? hyphenation_space == H0 + : hyphenation_margin == H0) + && (hyphen_line_max < 0 + || hyphen_line_count + 1 <= hyphen_line_max)) { + // No need to consider a non-hyphenated breakpoint. + if (best_bp) + delete best_bp; + return bp; + } + // It fits but it's hyphenated. + if (!best_bp_fits) { + if (best_bp) + delete best_bp; + best_bp = bp; + bp = bp->next; + best_bp_fits = 1; + } + else { + breakpoint *tem = bp; + bp = bp->next; + delete tem; + } + } + } + else { + if (best_bp) + delete best_bp; + best_bp = bp; + bp = bp->next; + } + } + n = n->next; + } + if (best_bp) { + if (!best_bp_fits) + warning(WARN_BREAK, "can't break line"); + return best_bp; + } + return 0; +} + +void environment::hyphenate_line(int start_here) +{ + if (line == 0) + return; + hyphenation_type prev_type = line->get_hyphenation_type(); + node **startp; + if (start_here) + startp = &line; + else + for (startp = &line->next; *startp != 0; startp = &(*startp)->next) { + hyphenation_type this_type = (*startp)->get_hyphenation_type(); + if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE) + break; + prev_type = this_type; + } + if (*startp == 0) + return; + node *tem = *startp; + int i = 0; + do { + ++i; + tem = tem->next; + } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE); + int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT); + node *end = tem; + hyphen_list *sl = 0; + tem = *startp; + node *forward = 0; + while (tem != end) { + sl = tem->get_hyphen_list(sl); + node *tem1 = tem; + tem = tem->next; + tem1->next = forward; + forward = tem1; + } + if (!inhibit) { + // this is for characters like hyphen and emdash + int prev_code = 0; + for (hyphen_list *h = sl; h; h = h->next) { + h->breakable = (prev_code != 0 + && h->next != 0 + && h->next->hyphenation_code != 0); + prev_code = h->hyphenation_code; + } + } + if (hyphenation_flags != 0 + && !inhibit + // this may not be right if we have extra space on this line + && !((hyphenation_flags & HYPHEN_LAST_LINE) + && (curdiv->distance_to_next_trap() + <= vertical_spacing + total_post_vertical_spacing())) + && i >= 4) + hyphenate(sl, hyphenation_flags); + while (forward != 0) { + node *tem1 = forward; + forward = forward->next; + tem1->next = 0; + tem = tem1->add_self(tem, &sl); + } + *startp = tem; +} + +static node *node_list_reverse(node *n) +{ + node *res = 0; + while (n) { + node *tem = n; + n = n->next; + tem->next = res; + res = tem; + } + return res; +} + +static void distribute_space(node *n, int nspaces, hunits desired_space, + int force_reverse = 0) +{ + static int reverse = 0; + if (force_reverse || reverse) + n = node_list_reverse(n); + for (node *tem = n; tem; tem = tem->next) + tem->spread_space(&nspaces, &desired_space); + if (force_reverse || reverse) + (void)node_list_reverse(n); + if (!force_reverse) + reverse = !reverse; + assert(desired_space.is_zero() && nspaces == 0); +} + +void environment::possibly_break_line(int start_here, int forced) +{ + if (!fill || current_tab || current_field || dummy) + return; + while (line != 0 + && (forced + // When a macro follows a paragraph in fill mode, the + // current line should not be empty. + || (width_total - line->width()) > target_text_length)) { + hyphenate_line(start_here); + breakpoint *bp = choose_breakpoint(); + if (bp == 0) + // we'll find one eventually + return; + node *pre, *post; + node **ndp = &line; + while (*ndp != bp->nd) + ndp = &(*ndp)->next; + bp->nd->split(bp->index, &pre, &post); + *ndp = post; + hunits extra_space_width = H0; + switch(adjust_mode) { + case ADJUST_BOTH: + if (bp->nspaces != 0) + extra_space_width = target_text_length - bp->width; + break; + case ADJUST_CENTER: + saved_indent += (target_text_length - bp->width)/2; + break; + case ADJUST_RIGHT: + saved_indent += target_text_length - bp->width; + break; + } + distribute_space(pre, bp->nspaces, extra_space_width); + hunits output_width = bp->width + extra_space_width; + input_line_start -= output_width; + if (bp->hyphenated) + hyphen_line_count++; + else + hyphen_line_count = 0; + delete bp; + space_total = 0; + width_total = 0; + node *first_non_discardable = 0; + node *tem; + for (tem = line; tem != 0; tem = tem->next) + if (!tem->discardable()) + first_non_discardable = tem; + node *to_be_discarded; + if (first_non_discardable) { + to_be_discarded = first_non_discardable->next; + first_non_discardable->next = 0; + for (tem = line; tem != 0; tem = tem->next) { + width_total += tem->width(); + space_total += tem->nspaces(); + } + discarding = 0; + } + else { + discarding = 1; + to_be_discarded = line; + line = 0; + } + // Do output_line() here so that line will be 0 iff the + // the environment will be empty. + output_line(pre, output_width); + while (to_be_discarded != 0) { + tem = to_be_discarded; + to_be_discarded = to_be_discarded->next; + input_line_start -= tem->width(); + delete tem; + } + if (line != 0) { + if (have_temporary_indent) { + saved_indent = temporary_indent; + have_temporary_indent = 0; + } + else + saved_indent = indent; + target_text_length = line_length - saved_indent; + } + } +} + +/* +Do the break at the end of input after the end macro (if any). + +Unix troff behaves as follows: if the last line is + +foo bar\c + +it will output foo on the current page, and bar on the next page; +if the last line is + +foo\c + +or + +foo bar + +everything will be output on the current page. This behaviour must be +considered a bug. + +The problem is that some macro packages rely on this. For example, +the ATK macros have an end macro that emits \c if it needs to print a +table of contents but doesn't do a 'bp in the end macro; instead the +'bp is done in the bottom of page trap. This works with Unix troff, +provided that the current environment is not empty at the end of the +input file. + +The following will make macro packages that do that sort of thing work +even if the current environment is empty at the end of the input file. +If the last input line used \c and this line occurred in the end macro, +then we'll force everything out on the current page, but we'll make +sure that the environment isn't empty so that we won't exit at the +bottom of this page. +*/ + +void environment::final_break() +{ + if (prev_line_interrupted == 2) { + do_break(); + add_node(new transparent_dummy_node); + } + else + do_break(); +} + +/* + * add_html_tag_eol - add an end of line tag if appropriate. + */ + +void environment::add_html_tag_eol() +{ + if (is_html) { + if (ignore_next_eol > 0) + ignore_next_eol--; + else if (need_eol > 0) { + need_eol--; + add_html_tag("eol"); + } + else if (!fill && emitted_node) { + add_html_tag("eol"); + emitted_node = 0; + } + } +} + +/* + * add_html_tag - emits a special html-tag: to help post-grohtml understand + * the key troff commands + */ + +void environment::add_html_tag(const char *name) +{ + if (is_html) { + /* + * need to emit tag for post-grohtml + * but we check to see whether we can emit specials + */ + if (curdiv == topdiv && topdiv->before_first_page) + topdiv->begin_page(); + macro *m = new macro; + m->append_str("html-tag:"); + for (const char *p = name; *p; p++) + if (!illegal_input_char((unsigned char)*p)) + m->append(*p); + add_node(new special_node(*m)); + } +} + +/* + * add_html_tag - emits a special html-tag: to help post-grohtml understand + * the key troff commands, it appends a string representation + * of i. + */ + +void environment::add_html_tag(const char *name, int i) +{ + if (is_html) { + if (strcmp(name, ".ce") == 0) { + if (i == 0) + need_eol = 0; + else { + need_eol = i; + ignore_next_eol = 1; // since the .ce creates an eol + } + } + + /* + * need to emit tag for post-grohtml + * but we check to see whether we can emit specials + */ + if (curdiv == topdiv && topdiv->before_first_page) + topdiv->begin_page(); + macro *m = new macro; + m->append_str("html-tag:"); + for (const char *p = name; *p; p++) + if (!illegal_input_char((unsigned char)*p)) + m->append(*p); + m->append(' '); + m->append_int(i); + // output_pending_lines(); + output(new special_node(*m), !fill, 0, 0, 0); + // output_pending_lines(); + } +} + +/* + * add_html_tag_tabs - emits the tab settings for post-grohtml + */ + +void environment::add_html_tag_tabs() +{ + if (is_html) { + /* + * need to emit tag for post-grohtml + * but we check to see whether we can emit specials + */ + if (curdiv == topdiv && topdiv->before_first_page) + topdiv->begin_page(); + macro *m = new macro; + hunits d, l; + enum tab_type t; + m->append_str("html-tag:.ta "); + do { + t = curenv->tabs.distance_to_next_tab(l, &d); + l += d; + switch (t) { + case TAB_LEFT: + m->append_str(" L "); + m->append_int(d.to_units()); + break; + case TAB_CENTER: + m->append_str(" C "); + m->append_int(d.to_units()); + break; + case TAB_RIGHT: + m->append_str(" R "); + m->append_int(d.to_units()); + break; + case TAB_NONE: + break; + } + } while ((t != TAB_NONE) && (l < get_line_length())); + output_pending_lines(); + output(new special_node(*m), !fill, 0, 0, 0); + output_pending_lines(); + } +} + +void environment::do_break() +{ + if (curdiv == topdiv && topdiv->before_first_page) { + topdiv->begin_page(); + return; + } + if (current_tab) + wrap_up_tab(); + if (line) { + line = new space_node(H0, line); // this is so that hyphenation works + space_total++; + possibly_break_line(); + } + while (line != 0 && line->discardable()) { + width_total -= line->width(); + space_total -= line->nspaces(); + node *tem = line; + line = line->next; + delete tem; + } + discarding = 0; + input_line_start = H0; + if (line != 0) { + if (fill) { + switch (adjust_mode) { + case ADJUST_CENTER: + saved_indent += (target_text_length - width_total)/2; + break; + case ADJUST_RIGHT: + saved_indent += target_text_length - width_total; + break; + } + } + node *tem = line; + line = 0; + output_line(tem, width_total); + hyphen_line_count = 0; + } + prev_line_interrupted = 0; +#ifdef WIDOW_CONTROL + mark_last_line(); + output_pending_lines(); +#endif /* WIDOW_CONTROL */ +} + +int environment::is_empty() +{ + return !current_tab && line == 0 && pending_lines == 0; +} + +void break_request() +{ + while (!tok.newline() && !tok.eof()) + tok.next(); + if (break_flag) { + curenv->do_break(); + curenv->add_html_tag(".br"); + } + tok.next(); +} + +void title() +{ + if (curdiv == topdiv && topdiv->before_first_page) { + handle_initial_title(); + return; + } + node *part[3]; + hunits part_width[3]; + part[0] = part[1] = part[2] = 0; + environment env(curenv); + environment *oldenv = curenv; + curenv = &env; + read_title_parts(part, part_width); + curenv = oldenv; + curenv->size = env.size; + curenv->prev_size = env.prev_size; + curenv->requested_size = env.requested_size; + curenv->prev_requested_size = env.prev_requested_size; + curenv->char_height = env.char_height; + curenv->char_slant = env.char_slant; + curenv->fontno = env.fontno; + curenv->prev_fontno = env.prev_fontno; + node *n = 0; + node *p = part[2]; + while (p != 0) { + node *tem = p; + p = p->next; + tem->next = n; + n = tem; + } + hunits title_length(curenv->title_length); + hunits f = title_length - part_width[1]; + hunits f2 = f/2; + n = new hmotion_node(f2 - part_width[2], n); + p = part[1]; + while (p != 0) { + node *tem = p; + p = p->next; + tem->next = n; + n = tem; + } + n = new hmotion_node(f - f2 - part_width[0], n); + p = part[0]; + while (p != 0) { + node *tem = p; + p = p->next; + tem->next = n; + n = tem; + } + curenv->output_title(n, !curenv->fill, curenv->vertical_spacing, + curenv->total_post_vertical_spacing(), title_length); + curenv->hyphen_line_count = 0; + tok.next(); +} + +void adjust() +{ + curenv->adjust_mode |= 1; + if (has_arg()) { + switch (tok.ch()) { + case 'l': + curenv->adjust_mode = ADJUST_LEFT; + break; + case 'r': + curenv->adjust_mode = ADJUST_RIGHT; + break; + case 'c': + curenv->adjust_mode = ADJUST_CENTER; + break; + case 'b': + case 'n': + curenv->adjust_mode = ADJUST_BOTH; + break; + default: + int n; + if (get_integer(&n)) { + if (n < 0) + warning(WARN_RANGE, "negative adjustment mode"); + else if (n > 5) { + curenv->adjust_mode = 5; + warning(WARN_RANGE, "adjustment mode `%1' out of range", n); + } + else + curenv->adjust_mode = n; + } + } + } + skip_line(); +} + +void no_adjust() +{ + curenv->adjust_mode &= ~1; + skip_line(); +} + +void input_trap() +{ + curenv->input_trap_count = 0; + int n; + if (has_arg() && get_integer(&n)) { + if (n <= 0) + warning(WARN_RANGE, + "number of lines for input trap must be greater than zero"); + else { + symbol s = get_name(1); + if (!s.is_null()) { + curenv->input_trap_count = n; + curenv->input_trap = s; + } + } + } + skip_line(); +} + +/* tabs */ + +// must not be R or C or L or a legitimate part of a number expression +const char TAB_REPEAT_CHAR = 'T'; + +struct tab { + tab *next; + hunits pos; + tab_type type; + tab(hunits, tab_type); + enum { BLOCK = 1024 }; + static tab *free_list; + void *operator new(size_t); + void operator delete(void *); +}; + +tab *tab::free_list = 0; + +void *tab::operator new(size_t n) +{ + assert(n == sizeof(tab)); + if (!free_list) { + free_list = (tab *)new char[sizeof(tab)*BLOCK]; + for (int i = 0; i < BLOCK - 1; i++) + free_list[i].next = free_list + i + 1; + free_list[BLOCK-1].next = 0; + } + tab *p = free_list; + free_list = (tab *)(free_list->next); + p->next = 0; + return p; +} + +#ifdef __GNUG__ +/* cfront can't cope with this. */ +inline +#endif +void tab::operator delete(void *p) +{ + if (p) { + ((tab *)p)->next = free_list; + free_list = (tab *)p; + } +} + +tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t) +{ +} + +tab_stops::tab_stops(hunits distance, tab_type type) + : initial_list(0) +{ + repeated_list = new tab(distance, type); +} + +tab_stops::~tab_stops() +{ + clear(); +} + +tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance) +{ + hunits lastpos = 0; + tab *tem; + for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next) + lastpos = tem->pos; + if (tem) { + *distance = tem->pos - curpos; + return tem->type; + } + if (repeated_list == 0) + return TAB_NONE; + hunits base = lastpos; + for (;;) { + for (tem = repeated_list; tem && tem->pos + base <= curpos; tem = tem->next) + lastpos = tem->pos; + if (tem) { + *distance = tem->pos + base - curpos; + return tem->type; + } + assert(lastpos > 0); + base += lastpos; + } + return TAB_NONE; +} + +const char *tab_stops::to_string() +{ + static char *buf = 0; + static int buf_size = 0; + // figure out a maximum on the amount of space we can need + int count = 0; + tab *p; + for (p = initial_list; p; p = p->next) + ++count; + for (p = repeated_list; p; p = p->next) + ++count; + // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0' + int need = count*12 + 3; + if (buf == 0 || need > buf_size) { + if (buf) + a_delete buf; + buf_size = need; + buf = new char[buf_size]; + } + char *ptr = buf; + for (p = initial_list; p; p = p->next) { + strcpy(ptr, i_to_a(p->pos.to_units())); + ptr = strchr(ptr, '\0'); + *ptr++ = 'u'; + *ptr = '\0'; + switch (p->type) { + case TAB_LEFT: + break; + case TAB_RIGHT: + *ptr++ = 'R'; + break; + case TAB_CENTER: + *ptr++ = 'C'; + break; + case TAB_NONE: + default: + assert(0); + } + } + if (repeated_list) + *ptr++ = TAB_REPEAT_CHAR; + for (p = repeated_list; p; p = p->next) { + strcpy(ptr, i_to_a(p->pos.to_units())); + ptr = strchr(ptr, '\0'); + *ptr++ = 'u'; + *ptr = '\0'; + switch (p->type) { + case TAB_LEFT: + break; + case TAB_RIGHT: + *ptr++ = 'R'; + break; + case TAB_CENTER: + *ptr++ = 'C'; + break; + case TAB_NONE: + default: + assert(0); + } + } + *ptr++ = '\0'; + return buf; +} + +tab_stops::tab_stops() : initial_list(0), repeated_list(0) +{ +} + +tab_stops::tab_stops(const tab_stops &ts) + : initial_list(0), repeated_list(0) +{ + tab **p = &initial_list; + tab *t = ts.initial_list; + while (t) { + *p = new tab(t->pos, t->type); + t = t->next; + p = &(*p)->next; + } + p = &repeated_list; + t = ts.repeated_list; + while (t) { + *p = new tab(t->pos, t->type); + t = t->next; + p = &(*p)->next; + } +} + +void tab_stops::clear() +{ + while (initial_list) { + tab *tem = initial_list; + initial_list = initial_list->next; + delete tem; + } + while (repeated_list) { + tab *tem = repeated_list; + repeated_list = repeated_list->next; + delete tem; + } +} + +void tab_stops::add_tab(hunits pos, tab_type type, int repeated) +{ + tab **p; + for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next) + ; + *p = new tab(pos, type); +} + + +void tab_stops::operator=(const tab_stops &ts) +{ + clear(); + tab **p = &initial_list; + tab *t = ts.initial_list; + while (t) { + *p = new tab(t->pos, t->type); + t = t->next; + p = &(*p)->next; + } + p = &repeated_list; + t = ts.repeated_list; + while (t) { + *p = new tab(t->pos, t->type); + t = t->next; + p = &(*p)->next; + } +} + +void set_tabs() +{ + hunits pos; + hunits prev_pos = 0; + int first = 1; + int repeated = 0; + tab_stops tabs; + while (has_arg()) { + if (tok.ch() == TAB_REPEAT_CHAR) { + tok.next(); + repeated = 1; + prev_pos = 0; + } + if (!get_hunits(&pos, 'm', prev_pos)) + break; + tab_type type = TAB_LEFT; + if (tok.ch() == 'C') { + tok.next(); + type = TAB_CENTER; + } + else if (tok.ch() == 'R') { + tok.next(); + type = TAB_RIGHT; + } + else if (tok.ch() == 'L') { + tok.next(); + } + if (pos <= prev_pos && !first) + warning(WARN_RANGE, + "positions of tab stops must be strictly increasing"); + else { + tabs.add_tab(pos, type, repeated); + prev_pos = pos; + first = 0; + } + } + curenv->tabs = tabs; + curenv->add_html_tag_tabs(); + skip_line(); +} + +const char *environment::get_tabs() +{ + return tabs.to_string(); +} + +#if 0 +tab_stops saved_tabs; + +void tabs_save() +{ + saved_tabs = curenv->tabs; + skip_line(); +} + +void tabs_restore() +{ + curenv->tabs = saved_tabs; + skip_line(); +} +#endif + +tab_type environment::distance_to_next_tab(hunits *distance) +{ + return line_tabs + ? curenv->tabs.distance_to_next_tab(get_text_length(), distance) + : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance); +} + +void field_characters() +{ + field_delimiter_char = get_optional_char(); + if (field_delimiter_char) + padding_indicator_char = get_optional_char(); + else + padding_indicator_char = 0; + skip_line(); +} + +void line_tabs_request() +{ + int n; + if (has_arg() && get_integer(&n)) + curenv->line_tabs = n != 0; + else + curenv->line_tabs = 1; + skip_line(); +} + +int environment::get_line_tabs() +{ + return line_tabs; +} + +void environment::wrap_up_tab() +{ + if (!current_tab) + return; + if (line == 0) + start_line(); + hunits tab_amount; + switch (current_tab) { + case TAB_RIGHT: + tab_amount = tab_distance - tab_width; + line = make_tab_node(tab_amount, line); + break; + case TAB_CENTER: + tab_amount = tab_distance - tab_width/2; + line = make_tab_node(tab_amount, line); + break; + case TAB_NONE: + case TAB_LEFT: + default: + assert(0); + } + width_total += tab_amount; + width_total += tab_width; + if (current_field) { + if (tab_precedes_field) { + pre_field_width += tab_amount; + tab_precedes_field = 0; + } + field_distance -= tab_amount; + field_spaces += tab_field_spaces; + } + if (tab_contents != 0) { + node *tem; + for (tem = tab_contents; tem->next != 0; tem = tem->next) + ; + tem->next = line; + line = tab_contents; + } + tab_field_spaces = 0; + tab_contents = 0; + tab_width = H0; + tab_distance = H0; + current_tab = TAB_NONE; +} + +node *environment::make_tab_node(hunits d, node *next) +{ + if (leader_node != 0 && d < 0) { + error("motion generated by leader cannot be negative"); + delete leader_node; + leader_node = 0; + } + if (!leader_node) + return new hmotion_node(d, 1, 0, next); + node *n = new hline_node(d, leader_node, next); + leader_node = 0; + return n; +} + +void environment::handle_tab(int is_leader) +{ + hunits d; + if (current_tab) + wrap_up_tab(); + charinfo *ci = is_leader ? leader_char : tab_char; + delete leader_node; + leader_node = ci ? make_char_node(ci) : 0; + tab_type t = distance_to_next_tab(&d); + switch (t) { + case TAB_NONE: + return; + case TAB_LEFT: + add_html_tag("tab left"); + add_node(make_tab_node(d)); + return; + case TAB_RIGHT: + case TAB_CENTER: + add_html_tag("tab center"); + tab_width = 0; + tab_distance = d; + tab_contents = 0; + current_tab = t; + tab_field_spaces = 0; + return; + default: + assert(0); + } +} + +void environment::start_field() +{ + assert(!current_field); + hunits d; + if (distance_to_next_tab(&d) != TAB_NONE) { + pre_field_width = get_text_length(); + field_distance = d; + current_field = 1; + field_spaces = 0; + tab_field_spaces = 0; + for (node *p = line; p; p = p->next) + if (p->nspaces()) { + p->freeze_space(); + space_total--; + } + tab_precedes_field = current_tab != TAB_NONE; + } + else + error("zero field width"); +} + +void environment::wrap_up_field() +{ + if (!current_tab && field_spaces == 0) + add_padding(); + hunits padding = field_distance - (get_text_length() - pre_field_width); + if (current_tab && tab_field_spaces != 0) { + hunits tab_padding = scale(padding, + tab_field_spaces, + field_spaces + tab_field_spaces); + padding -= tab_padding; + distribute_space(tab_contents, tab_field_spaces, tab_padding, 1); + tab_field_spaces = 0; + tab_width += tab_padding; + } + if (field_spaces != 0) { + distribute_space(line, field_spaces, padding, 1); + width_total += padding; + if (current_tab) { + // the start of the tab has been moved to the right by padding, so + tab_distance -= padding; + if (tab_distance <= H0) { + // use the next tab stop instead + current_tab = tabs.distance_to_next_tab(get_input_line_position() + - tab_width, + &tab_distance); + if (current_tab == TAB_NONE || current_tab == TAB_LEFT) { + width_total += tab_width; + if (current_tab == TAB_LEFT) { + line = make_tab_node(tab_distance, line); + width_total += tab_distance; + current_tab = TAB_NONE; + } + if (tab_contents != 0) { + node *tem; + for (tem = tab_contents; tem->next != 0; tem = tem->next) + ; + tem->next = line; + line = tab_contents; + tab_contents = 0; + } + tab_width = H0; + tab_distance = H0; + } + } + } + } + current_field = 0; +} + +void environment::add_padding() +{ + if (current_tab) { + tab_contents = new space_node(H0, tab_contents); + tab_field_spaces++; + } + else { + if (line == 0) + start_line(); + line = new space_node(H0, line); + field_spaces++; + } +} + +typedef int (environment::*INT_FUNCP)(); +typedef vunits (environment::*VUNITS_FUNCP)(); +typedef hunits (environment::*HUNITS_FUNCP)(); +typedef const char *(environment::*STRING_FUNCP)(); + +class int_env_reg : public reg { + INT_FUNCP func; + public: + int_env_reg(INT_FUNCP); + const char *get_string(); + int get_value(units *val); +}; + +class vunits_env_reg : public reg { + VUNITS_FUNCP func; + public: + vunits_env_reg(VUNITS_FUNCP f); + const char *get_string(); + int get_value(units *val); +}; + + +class hunits_env_reg : public reg { + HUNITS_FUNCP func; + public: + hunits_env_reg(HUNITS_FUNCP f); + const char *get_string(); + int get_value(units *val); +}; + +class string_env_reg : public reg { + STRING_FUNCP func; +public: + string_env_reg(STRING_FUNCP); + const char *get_string(); +}; + +int_env_reg::int_env_reg(INT_FUNCP f) : func(f) +{ +} + +int int_env_reg::get_value(units *val) +{ + *val = (curenv->*func)(); + return 1; +} + +const char *int_env_reg::get_string() +{ + return i_to_a((curenv->*func)()); +} + +vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f) +{ +} + +int vunits_env_reg::get_value(units *val) +{ + *val = (curenv->*func)().to_units(); + return 1; +} + +const char *vunits_env_reg::get_string() +{ + return i_to_a((curenv->*func)().to_units()); +} + +hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f) +{ +} + +int hunits_env_reg::get_value(units *val) +{ + *val = (curenv->*func)().to_units(); + return 1; +} + +const char *hunits_env_reg::get_string() +{ + return i_to_a((curenv->*func)().to_units()); +} + +string_env_reg::string_env_reg(STRING_FUNCP f) : func(f) +{ +} + +const char *string_env_reg::get_string() +{ + return (curenv->*func)(); +} + +class horizontal_place_reg : public general_reg { +public: + horizontal_place_reg(); + int get_value(units *); + void set_value(units); +}; + +horizontal_place_reg::horizontal_place_reg() +{ +} + +int horizontal_place_reg::get_value(units *res) +{ + *res = curenv->get_input_line_position().to_units(); + return 1; +} + +void horizontal_place_reg::set_value(units n) +{ + curenv->set_input_line_position(hunits(n)); +} + +const char *environment::get_font_family_string() +{ + return family->nm.contents(); +} + +const char *environment::get_name_string() +{ + return name.contents(); +} + +// Convert a quantity in scaled points to ascii decimal fraction. + +const char *sptoa(int sp) +{ + assert(sp > 0); + assert(sizescale > 0); + if (sizescale == 1) + return i_to_a(sp); + if (sp % sizescale == 0) + return i_to_a(sp/sizescale); + // See if 1/sizescale is exactly representable as a decimal fraction, + // ie its only prime factors are 2 and 5. + int n = sizescale; + int power2 = 0; + while ((n & 1) == 0) { + n >>= 1; + power2++; + } + int power5 = 0; + while ((n % 5) == 0) { + n /= 5; + power5++; + } + if (n == 1) { + int decimal_point = power5 > power2 ? power5 : power2; + if (decimal_point <= 10) { + int factor = 1; + int t; + for (t = decimal_point - power2; --t >= 0;) + factor *= 2; + for (t = decimal_point - power5; --t >= 0;) + factor *= 5; + if (factor == 1 || sp <= INT_MAX/factor) + return if_to_a(sp*factor, decimal_point); + } + } + double s = double(sp)/double(sizescale); + double factor = 10.0; + double val = s; + int decimal_point = 0; + do { + double v = ceil(s*factor); + if (v > INT_MAX) + break; + val = v; + factor *= 10.0; + } while (++decimal_point < 10); + return if_to_a(int(val), decimal_point); +} + +const char *environment::get_point_size_string() +{ + return sptoa(curenv->get_point_size()); +} + +const char *environment::get_requested_point_size_string() +{ + return sptoa(curenv->get_requested_point_size()); +} + +#define init_int_env_reg(name, func) \ + number_reg_dictionary.define(name, new int_env_reg(&environment::func)) + +#define init_vunits_env_reg(name, func) \ + number_reg_dictionary.define(name, new vunits_env_reg(&environment::func)) + +#define init_hunits_env_reg(name, func) \ + number_reg_dictionary.define(name, new hunits_env_reg(&environment::func)) + +#define init_string_env_reg(name, func) \ + number_reg_dictionary.define(name, new string_env_reg(&environment::func)) + +void init_env_requests() +{ + init_request("it", input_trap); + init_request("ad", adjust); + init_request("na", no_adjust); + init_request("ev", environment_switch); + init_request("evc", environment_copy); + init_request("lt", title_length); + init_request("ps", point_size); + init_request("ft", font_change); + init_request("fam", family_change); + init_request("ss", space_size); + init_request("fi", fill); + init_request("nf", no_fill); + init_request("ce", center); + init_request("rj", right_justify); + init_request("vs", vertical_spacing); + init_request("ls", line_spacing); + init_request("ll", line_length); + init_request("in", indent); + init_request("ti", temporary_indent); + init_request("ul", underline); + init_request("cu", continuous_underline); + init_request("cc", control_char); + init_request("c2", no_break_control_char); + init_request("br", break_request); + init_request("tl", title); + init_request("ta", set_tabs); + init_request("linetabs", line_tabs_request); + init_request("fc", field_characters); + init_request("mc", margin_character); + init_request("nn", no_number); + init_request("nm", number_lines); + init_request("tc", tab_character); + init_request("lc", leader_character); + init_request("hy", hyphenate_request); + init_request("hc", hyphen_char); + init_request("nh", no_hyphenate); + init_request("hlm", hyphen_line_max_request); +#ifdef WIDOW_CONTROL + init_request("wdc", widow_control_request); +#endif /* WIDOW_CONTROL */ +#if 0 + init_request("tas", tabs_save); + init_request("tar", tabs_restore); +#endif + init_request("hys", hyphenation_space_request); + init_request("hym", hyphenation_margin_request); + init_request("pvs", post_vertical_spacing); + init_int_env_reg(".f", get_font); + init_int_env_reg(".b", get_bold); + init_hunits_env_reg(".i", get_indent); + init_hunits_env_reg(".in", get_saved_indent); + init_int_env_reg(".int", get_prev_line_interrupted); + init_int_env_reg(".j", get_adjust_mode); + init_hunits_env_reg(".k", get_text_length); + init_hunits_env_reg(".l", get_line_length); + init_hunits_env_reg(".ll", get_saved_line_length); + init_int_env_reg(".L", get_line_spacing); + init_hunits_env_reg(".n", get_prev_text_length); + init_string_env_reg(".s", get_point_size_string); + init_string_env_reg(".sr", get_requested_point_size_string); + init_int_env_reg(".ps", get_point_size); + init_int_env_reg(".psr", get_requested_point_size); + init_int_env_reg(".u", get_fill); + init_vunits_env_reg(".v", get_vertical_spacing); + init_vunits_env_reg(".pvs", get_post_vertical_spacing); + init_hunits_env_reg(".w", get_prev_char_width); + init_int_env_reg(".ss", get_space_size); + init_int_env_reg(".sss", get_sentence_space_size); + init_string_env_reg(".fam", get_font_family_string); + init_string_env_reg(".ev", get_name_string); + init_int_env_reg(".hy", get_hyphenation_flags); + init_int_env_reg(".hlm", get_hyphen_line_max); + init_int_env_reg(".hlc", get_hyphen_line_count); + init_hunits_env_reg(".lt", get_title_length); + init_string_env_reg(".tabs", get_tabs); + init_int_env_reg(".linetabs", get_line_tabs); + init_hunits_env_reg(".csk", get_prev_char_skew); + init_vunits_env_reg(".cht", get_prev_char_height); + init_vunits_env_reg(".cdp", get_prev_char_depth); + init_int_env_reg(".ce", get_center_lines); + init_int_env_reg(".rj", get_right_justify_lines); + init_hunits_env_reg(".hys", get_hyphenation_space); + init_hunits_env_reg(".hym", get_hyphenation_margin); + number_reg_dictionary.define("ln", new variable_reg(&next_line_number)); + number_reg_dictionary.define("ct", new variable_reg(&ct_reg_contents)); + number_reg_dictionary.define("sb", new variable_reg(&sb_reg_contents)); + number_reg_dictionary.define("st", new variable_reg(&st_reg_contents)); + number_reg_dictionary.define("rsb", new variable_reg(&rsb_reg_contents)); + number_reg_dictionary.define("rst", new variable_reg(&rst_reg_contents)); + number_reg_dictionary.define("ssc", new variable_reg(&ssc_reg_contents)); + number_reg_dictionary.define("skw", new variable_reg(&skw_reg_contents)); + number_reg_dictionary.define("hp", new horizontal_place_reg); +} + +// Hyphenation - TeX's hyphenation algorithm with a less fancy implementation. + +struct trie_node; + +class trie { + trie_node *tp; + virtual void do_match(int len, void *val) = 0; + virtual void do_delete(void *) = 0; + void delete_trie_node(trie_node *); +public: + trie() : tp(0) {} + virtual ~trie(); // virtual to shut up g++ + void insert(const char *, int, void *); + // find calls do_match for each match it finds + void find(const char *pat, int patlen); + void clear(); +}; + +class hyphen_trie : private trie { + int *h; + void do_match(int i, void *v); + void do_delete(void *v); + void insert_pattern(const char *pat, int patlen, int *num); +public: + hyphen_trie() {} + ~hyphen_trie() {} + void hyphenate(const char *word, int len, int *hyphens); + void read_patterns_file(const char *name); +}; + + +struct hyphenation_language { + symbol name; + dictionary exceptions; + hyphen_trie patterns; + hyphenation_language(symbol nm) : name(nm), exceptions(501) {} + ~hyphenation_language() { } +}; + +dictionary language_dictionary(5); +hyphenation_language *current_language = 0; + +static void set_hyphenation_language() +{ + symbol nm = get_name(1); + if (!nm.is_null()) { + current_language = (hyphenation_language *)language_dictionary.lookup(nm); + if (!current_language) { + current_language = new hyphenation_language(nm); + (void)language_dictionary.lookup(nm, (void *)current_language); + } + } + skip_line(); +} + +const int WORD_MAX = 1024; + +static void hyphen_word() +{ + if (!current_language) { + error("no current hyphenation language"); + skip_line(); + return; + } + char buf[WORD_MAX + 1]; + unsigned char pos[WORD_MAX + 2]; + for (;;) { + tok.skip(); + if (tok.newline() || tok.eof()) + break; + int i = 0; + int npos = 0; + while (i < WORD_MAX && !tok.space() && !tok.newline() && !tok.eof()) { + charinfo *ci = tok.get_char(1); + if (ci == 0) { + skip_line(); + return; + } + tok.next(); + if (ci->get_ascii_code() == '-') { + if (i > 0 && (npos == 0 || pos[npos - 1] != i)) + pos[npos++] = i; + } + else { + int c = ci->get_hyphenation_code(); + if (c == 0) + break; + buf[i++] = c; + } + } + if (i > 0) { + pos[npos] = 0; + buf[i] = 0; + unsigned char *tem = new unsigned char[npos + 1]; + memcpy(tem, pos, npos+1); + tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf), + tem); + if (tem) + a_delete tem; + } + } + skip_line(); +} + +struct trie_node { + char c; + trie_node *down; + trie_node *right; + void *val; + trie_node(char, trie_node *); +}; + +trie_node::trie_node(char ch, trie_node *p) +: c(ch), down(0), right(p), val(0) +{ +} + +trie::~trie() +{ + clear(); +} + +void trie::clear() +{ + delete_trie_node(tp); + tp = 0; +} + + +void trie::delete_trie_node(trie_node *p) +{ + if (p) { + delete_trie_node(p->down); + delete_trie_node(p->right); + if (p->val) + do_delete(p->val); + delete p; + } +} + +void trie::insert(const char *pat, int patlen, void *val) +{ + trie_node **p = &tp; + assert(patlen > 0 && pat != 0); + for (;;) { + while (*p != 0 && (*p)->c < pat[0]) + p = &((*p)->right); + if (*p == 0 || (*p)->c != pat[0]) + *p = new trie_node(pat[0], *p); + if (--patlen == 0) { + (*p)->val = val; + break; + } + ++pat; + p = &((*p)->down); + } +} + +void trie::find(const char *pat, int patlen) +{ + trie_node *p = tp; + for (int i = 0; p != 0 && i < patlen; i++) { + while (p != 0 && p->c < pat[i]) + p = p->right; + if (p != 0 && p->c == pat[i]) { + if (p->val != 0) + do_match(i+1, p->val); + p = p->down; + } + else + break; + } +} + +struct operation { + operation *next; + short distance; + short num; + operation(int, int, operation *); +}; + +operation::operation(int i, int j, operation *op) +: next(op), distance(j), num(i) +{ +} + +void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num) +{ + operation *op = 0; + for (int i = 0; i < patlen+1; i++) + if (num[i] != 0) + op = new operation(num[i], patlen - i, op); + insert(pat, patlen, op); +} + +void hyphen_trie::hyphenate(const char *word, int len, int *hyphens) +{ + int j; + for (j = 0; j < len + 1; j++) + hyphens[j] = 0; + for (j = 0; j < len - 1; j++) { + h = hyphens + j; + find(word + j, len - j); + } +} + +inline int max(int m, int n) +{ + return m > n ? m : n; +} + +void hyphen_trie::do_match(int i, void *v) +{ + operation *op = (operation *)v; + while (op != 0) { + h[i - op->distance] = max(h[i - op->distance], op->num); + op = op->next; + } +} + +void hyphen_trie::do_delete(void *v) +{ + operation *op = (operation *)v; + while (op) { + operation *tem = op; + op = tem->next; + delete tem; + } +} + +void hyphen_trie::read_patterns_file(const char *name) +{ + clear(); + char buf[WORD_MAX]; + int num[WORD_MAX+1]; + errno = 0; + char *path = 0; + FILE *fp = mac_path->open_file(name, &path); + if (fp == 0) { + error("can't find hyphenation patterns file `%1'", name); + return; + } + int c = getc(fp); + for (;;) { + for (;;) { + if (c == '%') { + do { + c = getc(fp); + } while (c != EOF && c != '\n'); + } + if (c == EOF || !csspace(c)) + break; + c = getc(fp); + } + if (c == EOF) + break; + int i = 0; + num[0] = 0; + do { + if (csdigit(c)) + num[i] = c - '0'; + else { + buf[i++] = c; + num[i] = 0; + } + c = getc(fp); + } while (i < WORD_MAX && c != EOF && !csspace(c) && c != '%'); + insert_pattern(buf, i, num); + } + fclose(fp); + a_delete path; + return; +} + +void hyphenate(hyphen_list *h, unsigned flags) +{ + if (!current_language) + return; + while (h) { + while (h && h->hyphenation_code == 0) + h = h->next; + int len = 0; + char hbuf[WORD_MAX+2]; + char *buf = hbuf + 1; + hyphen_list *tem; + for (tem = h; tem && len < WORD_MAX; tem = tem->next) { + if (tem->hyphenation_code != 0) + buf[len++] = tem->hyphenation_code; + else + break; + } + hyphen_list *nexth = tem; + if (len > 2) { + buf[len] = 0; + unsigned char *pos + = (unsigned char *)current_language->exceptions.lookup(buf); + if (pos != 0) { + int j = 0; + int i = 1; + for (tem = h; tem != 0; tem = tem->next, i++) + if (pos[j] == i) { + tem->hyphen = 1; + j++; + } + } + else { + hbuf[0] = hbuf[len+1] = '.'; + int num[WORD_MAX+3]; + current_language->patterns.hyphenate(hbuf, len+2, num); + int i; + num[2] = 0; + if (flags & 8) + num[3] = 0; + if (flags & 4) + --len; + for (i = 2, tem = h; i < len && tem; tem = tem->next, i++) + if (num[i] & 1) + tem->hyphen = 1; + } + } + h = nexth; + } +} + +static void hyphenation_patterns_file() +{ + symbol name = get_long_name(1); + if (!name.is_null()) { + if (!current_language) + error("no current hyphenation language"); + else + current_language->patterns.read_patterns_file(name.contents()); + } + skip_line(); +} + +class hyphenation_language_reg : public reg { +public: + const char *get_string(); +}; + +const char *hyphenation_language_reg::get_string() +{ + return current_language ? current_language->name.contents() : ""; +} + +void init_hyphen_requests() +{ + init_request("hw", hyphen_word); + init_request("hla", set_hyphenation_language); + init_request("hpf", hyphenation_patterns_file); + number_reg_dictionary.define(".hla", new hyphenation_language_reg); +} diff --git a/contrib/groff/src/roff/troff/env.h b/contrib/groff/src/roff/troff/env.h new file mode 100644 index 0000000..256db51 --- /dev/null +++ b/contrib/groff/src/roff/troff/env.h @@ -0,0 +1,350 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001 + Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +struct size_range { + int min; + int max; +}; + +class font_size { + static size_range *size_table; + static int nranges; + int p; +public: + font_size(); + font_size(int points); + int to_points(); + int to_scaled_points(); + int to_units(); + int operator==(font_size); + int operator!=(font_size); + static void init_size_table(int *sizes); +}; + +inline font_size::font_size() : p(0) +{ +} + +inline int font_size::operator==(font_size fs) +{ + return p == fs.p; +} + +inline int font_size::operator!=(font_size fs) +{ + return p != fs.p; +} + +inline int font_size::to_scaled_points() +{ + return p; +} + +inline int font_size::to_points() +{ + return p/sizescale; +} + +struct environment; + +hunits env_digit_width(environment *); +hunits env_space_width(environment *); +hunits env_sentence_space_width(environment *); +hunits env_narrow_space_width(environment *); +hunits env_half_narrow_space_width(environment *); + +struct tab; + +enum tab_type { TAB_NONE, TAB_LEFT, TAB_CENTER, TAB_RIGHT }; + +class tab_stops { + tab *initial_list; + tab *repeated_list; +public: + tab_stops(); + tab_stops(hunits distance, tab_type type); + tab_stops(const tab_stops &); + ~tab_stops(); + void operator=(const tab_stops &); + tab_type distance_to_next_tab(hunits pos, hunits *distance); + void clear(); + void add_tab(hunits pos, tab_type type, int repeated); + const char *to_string(); +}; + +const unsigned MARGIN_CHARACTER_ON = 1; +const unsigned MARGIN_CHARACTER_NEXT = 2; + +struct charinfo; +struct node; +struct breakpoint; +struct font_family; +struct pending_output_line; + +class environment { + int dummy; // dummy environment used for \w + hunits prev_line_length; + hunits line_length; + hunits prev_title_length; + hunits title_length; + font_size prev_size; + font_size size; + int requested_size; + int prev_requested_size; + int char_height; + int char_slant; + int prev_fontno; + int fontno; + font_family *prev_family; + font_family *family; + int space_size; // in 36ths of an em + int sentence_space_size; // same but for spaces at the end of sentences + int adjust_mode; + int fill; + int interrupted; + int prev_line_interrupted; + int center_lines; + int right_justify_lines; + vunits prev_vertical_spacing; + vunits vertical_spacing; + vunits prev_post_vertical_spacing; + vunits post_vertical_spacing; + int prev_line_spacing; + int line_spacing; + hunits prev_indent; + hunits indent; + hunits temporary_indent; + int have_temporary_indent; + hunits saved_indent; + hunits target_text_length; + int pre_underline_fontno; + int underline_lines; + int underline_spaces; + symbol input_trap; + int input_trap_count; + node *line; // in reverse order + hunits prev_text_length; + hunits width_total; + int space_total; + hunits input_line_start; + tab_stops tabs; + node *tab_contents; + hunits tab_width; + hunits tab_distance; + int line_tabs; + tab_type current_tab; + node *leader_node; + charinfo *tab_char; + charinfo *leader_char; + int current_field; // is there a current field? + hunits field_distance; + hunits pre_field_width; + int field_spaces; + int tab_field_spaces; + int tab_precedes_field; + int discarding; + int spread_flag; // set by \p + unsigned margin_character_flags; + node *margin_character_node; + hunits margin_character_distance; + node *numbering_nodes; + hunits line_number_digit_width; + int number_text_separation; // in digit spaces + int line_number_indent; // in digit spaces + int line_number_multiple; + int no_number_count; + unsigned hyphenation_flags; + int hyphen_line_count; + int hyphen_line_max; + hunits hyphenation_space; + hunits hyphenation_margin; + int composite; // used for construction of composite char? + pending_output_line *pending_lines; +#ifdef WIDOW_CONTROL + int widow_control; +#endif /* WIDOW_CONTROL */ + int need_eol; + int ignore_next_eol; + int emitted_node; // have we emitted a node since the last html eol tag? + + tab_type distance_to_next_tab(hunits *); + void start_line(); + void output_line(node *, hunits); + void output(node *nd, int retain_size, vunits vs, vunits post_vs, + hunits width); + void output_title(node *nd, int retain_size, vunits vs, vunits post_vs, + hunits width); +#ifdef WIDOW_CONTROL + void mark_last_line(); +#endif /* WIDOW_CONTROL */ + breakpoint *choose_breakpoint(); + void hyphenate_line(int start_here = 0); + void start_field(); + void wrap_up_field(); + void add_padding(); + node *make_tab_node(hunits d, node *next = 0); + node *get_prev_char(); +public: + const symbol name; + unsigned char control_char; + unsigned char no_break_control_char; + charinfo *hyphen_indicator_char; + + environment(symbol); + environment(const environment *); // for temporary environment + ~environment(); + void copy(const environment *); + int is_dummy() { return dummy; } + int is_empty(); + int is_composite() { return composite; } + void set_composite() { composite = 1; } + vunits get_vertical_spacing(); // .v + vunits get_post_vertical_spacing(); // .pvs + int get_line_spacing(); // .L + vunits total_post_vertical_spacing(); + int get_point_size() { return size.to_scaled_points(); } + font_size get_font_size() { return size; } + int get_size() { return size.to_units(); } + int get_requested_point_size() { return requested_size; } + int get_char_height() { return char_height; } + int get_char_slant() { return char_slant; } + hunits get_digit_width(); + int get_font() { return fontno; }; // .f + font_family *get_family() { return family; } + int get_bold(); // .b + int get_adjust_mode(); // .j + int get_fill(); // .u + hunits get_indent(); // .i + hunits get_temporary_indent(); + hunits get_line_length(); // .l + hunits get_saved_line_length(); // .ll + hunits get_saved_indent(); // .in + hunits get_title_length(); + hunits get_prev_char_width(); // .w + hunits get_prev_char_skew(); + vunits get_prev_char_height(); + vunits get_prev_char_depth(); + hunits get_text_length(); // .k + hunits get_prev_text_length(); // .n + hunits get_space_width() { return env_space_width(this); } + int get_space_size() { return space_size; } // in ems/36 + int get_sentence_space_size() { return sentence_space_size; } + hunits get_narrow_space_width() { return env_narrow_space_width(this); } + hunits get_half_narrow_space_width() + { return env_half_narrow_space_width(this); } + hunits get_input_line_position(); + const char *get_tabs(); + int get_line_tabs(); + int get_hyphenation_flags(); + int get_hyphen_line_max(); + int get_hyphen_line_count(); + hunits get_hyphenation_space(); + hunits get_hyphenation_margin(); + int get_center_lines(); + int get_right_justify_lines(); + int get_prev_line_interrupted() { return prev_line_interrupted; } + node *make_char_node(charinfo *); + node *extract_output_line(); + void width_registers(); + void wrap_up_tab(); + void set_font(int); + void set_font(symbol); + void set_family(symbol); + void set_size(int); + void set_char_height(int); + void set_char_slant(int); + void set_input_line_position(hunits); // used by \n(hp + void interrupt(); + void spread() { spread_flag = 1; } + void possibly_break_line(int start_here = 0, int forced = 0); + void do_break(); // .br + void final_break(); + void add_html_tag_eol(); + void add_html_tag(const char *); + void add_html_tag(const char *, int); + void add_html_tag_tabs(); + void newline(); + void handle_tab(int is_leader = 0); // do a tab or leader + void add_node(node *); + void add_char(charinfo *); + void add_hyphen_indicator(); + void add_italic_correction(); + void space(); + void space(hunits, hunits); + void space_newline(); + const char *get_font_family_string(); + const char *get_name_string(); + const char *get_point_size_string(); + const char *get_requested_point_size_string(); + void output_pending_lines(); + + friend void title_length(); + friend void space_size(); + friend void fill(); + friend void no_fill(); + friend void adjust(); + friend void no_adjust(); + friend void center(); + friend void right_justify(); + friend void vertical_spacing(); + friend void post_vertical_spacing(); + friend void line_spacing(); + friend void line_length(); + friend void indent(); + friend void temporary_indent(); + friend void do_underline(int); + friend void input_trap(); + friend void set_tabs(); + friend void margin_character(); + friend void no_number(); + friend void number_lines(); + friend void leader_character(); + friend void tab_character(); + friend void hyphenate_request(); + friend void no_hyphenate(); + friend void hyphen_line_max_request(); + friend void hyphenation_space_request(); + friend void hyphenation_margin_request(); + friend void line_width(); +#if 0 + friend void tabs_save(); + friend void tabs_restore(); +#endif + friend void line_tabs_request(); + friend void title(); +#ifdef WIDOW_CONTROL + friend void widow_control_request(); +#endif /* WIDOW_CONTROL */ + + friend void do_divert(int append, int boxing); +}; + +extern environment *curenv; +extern void pop_env(); +extern void push_env(int); + +void init_environments(); +void read_hyphen_file(const char *name); + +extern int break_flag; +extern int compatible_flag; +extern symbol default_family; +extern int translate_space_to_dummy; diff --git a/contrib/groff/src/roff/troff/hvunits.h b/contrib/groff/src/roff/troff/hvunits.h new file mode 100644 index 0000000..8efb5ab --- /dev/null +++ b/contrib/groff/src/roff/troff/hvunits.h @@ -0,0 +1,340 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +class vunits { + int n; +public: + vunits(); + vunits(units); + units to_units(); + int is_zero(); + vunits& operator+=(const vunits&); + vunits& operator-=(const vunits&); + friend inline vunits scale(vunits n, units x, units y); // scale n by x/y + friend inline vunits scale(vunits n, vunits x, vunits y); + friend inline vunits operator +(const vunits&, const vunits&); + friend inline vunits operator -(const vunits&, const vunits&); + friend inline vunits operator -(const vunits&); + friend inline int operator /(const vunits&, const vunits&); + friend inline vunits operator /(const vunits&, int); + friend inline vunits operator *(const vunits&, int); + friend inline vunits operator *(int, const vunits&); + friend inline int operator <(const vunits&, const vunits&); + friend inline int operator >(const vunits&, const vunits&); + friend inline int operator <=(const vunits&, const vunits&); + friend inline int operator >=(const vunits&, const vunits&); + friend inline int operator ==(const vunits&, const vunits&); + friend inline int operator !=(const vunits&, const vunits&); +}; + +extern vunits V0; + + +class hunits { + int n; +public: + hunits(); + hunits(units); + units to_units(); + int is_zero(); + hunits& operator+=(const hunits&); + hunits& operator-=(const hunits&); + friend inline hunits scale(hunits n, units x, units y); // scale n by x/y + friend inline hunits scale(hunits n, double x); + friend inline hunits operator +(const hunits&, const hunits&); + friend inline hunits operator -(const hunits&, const hunits&); + friend inline hunits operator -(const hunits&); + friend inline int operator /(const hunits&, const hunits&); + friend inline hunits operator /(const hunits&, int); + friend inline hunits operator *(const hunits&, int); + friend inline hunits operator *(int, const hunits&); + friend inline int operator <(const hunits&, const hunits&); + friend inline int operator >(const hunits&, const hunits&); + friend inline int operator <=(const hunits&, const hunits&); + friend inline int operator >=(const hunits&, const hunits&); + friend inline int operator ==(const hunits&, const hunits&); + friend inline int operator !=(const hunits&, const hunits&); +}; + +extern hunits H0; + +extern int get_vunits(vunits *, unsigned char si); +extern int get_hunits(hunits *, unsigned char si); +extern int get_vunits(vunits *, unsigned char si, vunits prev_value); +extern int get_hunits(hunits *, unsigned char si, hunits prev_value); + +inline vunits:: vunits() : n(0) +{ +} + +inline units vunits::to_units() +{ + return n*vresolution; +} + +inline int vunits::is_zero() +{ + return n == 0; +} + +inline vunits operator +(const vunits & x, const vunits & y) +{ + vunits r; + r = x; + r.n += y.n; + return r; +} + +inline vunits operator -(const vunits & x, const vunits & y) +{ + vunits r; + r = x; + r.n -= y.n; + return r; +} + +inline vunits operator -(const vunits & x) +{ + vunits r; + r.n = -x.n; + return r; +} + +inline int operator /(const vunits & x, const vunits & y) +{ + return x.n/y.n; +} + +inline vunits operator /(const vunits & x, int n) +{ + vunits r; + r = x; + r.n /= n; + return r; +} + +inline vunits operator *(const vunits & x, int n) +{ + vunits r; + r = x; + r.n *= n; + return r; +} + +inline vunits operator *(int n, const vunits & x) +{ + vunits r; + r = x; + r.n *= n; + return r; +} + +inline int operator <(const vunits & x, const vunits & y) +{ + return x.n < y.n; +} + +inline int operator >(const vunits & x, const vunits & y) +{ + return x.n > y.n; +} + +inline int operator <=(const vunits & x, const vunits & y) +{ + return x.n <= y.n; +} + +inline int operator >=(const vunits & x, const vunits & y) +{ + return x.n >= y.n; +} + +inline int operator ==(const vunits & x, const vunits & y) +{ + return x.n == y.n; +} + +inline int operator !=(const vunits & x, const vunits & y) +{ + return x.n != y.n; +} + + +inline vunits& vunits::operator+=(const vunits & x) +{ + n += x.n; + return *this; +} + +inline vunits& vunits::operator-=(const vunits & x) +{ + n -= x.n; + return *this; +} + +inline hunits:: hunits() : n(0) +{ +} + +inline units hunits::to_units() +{ + return n*hresolution; +} + +inline int hunits::is_zero() +{ + return n == 0; +} + +inline hunits operator +(const hunits & x, const hunits & y) +{ + hunits r; + r = x; + r.n += y.n; + return r; +} + +inline hunits operator -(const hunits & x, const hunits & y) +{ + hunits r; + r = x; + r.n -= y.n; + return r; +} + +inline hunits operator -(const hunits & x) +{ + hunits r; + r = x; + r.n = -x.n; + return r; +} + +inline int operator /(const hunits & x, const hunits & y) +{ + return x.n/y.n; +} + +inline hunits operator /(const hunits & x, int n) +{ + hunits r; + r = x; + r.n /= n; + return r; +} + +inline hunits operator *(const hunits & x, int n) +{ + hunits r; + r = x; + r.n *= n; + return r; +} + +inline hunits operator *(int n, const hunits & x) +{ + hunits r; + r = x; + r.n *= n; + return r; +} + +inline int operator <(const hunits & x, const hunits & y) +{ + return x.n < y.n; +} + +inline int operator >(const hunits & x, const hunits & y) +{ + return x.n > y.n; +} + +inline int operator <=(const hunits & x, const hunits & y) +{ + return x.n <= y.n; +} + +inline int operator >=(const hunits & x, const hunits & y) +{ + return x.n >= y.n; +} + +inline int operator ==(const hunits & x, const hunits & y) +{ + return x.n == y.n; +} + +inline int operator !=(const hunits & x, const hunits & y) +{ + return x.n != y.n; +} + + +inline hunits& hunits::operator+=(const hunits & x) +{ + n += x.n; + return *this; +} + +inline hunits& hunits::operator-=(const hunits & x) +{ + n -= x.n; + return *this; +} + +inline hunits scale(hunits n, units x, units y) +{ + hunits r; + r.n = scale(n.n, x, y); + return r; +} + +inline vunits scale(vunits n, units x, units y) +{ + vunits r; + r.n = scale(n.n, x, y); + return r; +} + +inline vunits scale(vunits n, vunits x, vunits y) +{ + vunits r; + r.n = scale(n.n, x.n, y.n); + return r; +} + +inline hunits scale(hunits n, double x) +{ + hunits r; + r.n = int(n.n*x); + return r; +} + +inline units scale(units n, double x) +{ + return int(n*x); +} + +inline units points_to_units(units n) +{ + return scale(n, units_per_inch, 72); +} + diff --git a/contrib/groff/src/roff/troff/input.cc b/contrib/groff/src/roff/troff/input.cc new file mode 100644 index 0000000..982e5bd --- /dev/null +++ b/contrib/groff/src/roff/troff/input.cc @@ -0,0 +1,6891 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001 + Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "troff.h" +#include "symbol.h" +#include "dictionary.h" +#include "hvunits.h" +#include "env.h" +#include "request.h" +#include "node.h" +#include "reg.h" +#include "token.h" +#include "div.h" +#include "charinfo.h" +#include "stringclass.h" +#include "font.h" +#include "macropath.h" +#include "defs.h" +#include "input.h" + +// Needed for getpid(). +#include "posix.h" + +#include "nonposix.h" + +#ifdef NEED_DECLARATION_PUTENV +extern "C" { + int putenv(const char *); +} +#endif /* NEED_DECLARATION_PUTENV */ + +#ifdef ISATTY_MISSING +#undef isatty +#define isatty(n) (1) +#else /* not ISATTY_MISSING */ +#ifndef isatty +extern "C" { + int isatty(int); +} +#endif /* not isatty */ +#endif /* not ISATTY_MISSING */ + +#define MACRO_PREFIX "tmac." +#define MACRO_POSTFIX ".tmac" +#define INITIAL_STARTUP_FILE "troffrc" +#define FINAL_STARTUP_FILE "troffrc-end" +#define DEFAULT_INPUT_STACK_LIMIT 1000 + +#ifndef DEFAULT_WARNING_MASK +// warnings that are enabled by default +#define DEFAULT_WARNING_MASK \ + (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT) +#endif + +// initial size of buffer for reading names; expanded as necessary +#define ABUF_SIZE 16 + +#ifdef COLUMN +void init_column_requests(); +#endif /* COLUMN */ + +static node *read_draw_node(); +void handle_first_page_transition(); +static void push_token(const token &); +void copy_file(); +#ifdef COLUMN +void vjustify(); +#endif /* COLUMN */ +void transparent(); +void transparent_file(); + +const char *program_name = 0; +token tok; +int break_flag = 0; +static int backtrace_flag = 0; +#ifndef POPEN_MISSING +char *pipe_command = 0; +#endif +charinfo *charset_table[256]; + +static int warning_mask = DEFAULT_WARNING_MASK; +static int inhibit_errors = 0; +static int ignoring = 0; + +static void enable_warning(const char *); +static void disable_warning(const char *); + +static int escape_char = '\\'; +static symbol end_macro_name; +static symbol blank_line_macro_name; +int compatible_flag = 0; +int ascii_output_flag = 0; +int suppress_output_flag = 0; +int is_html = 0; +int begin_level = 0; // number of nested .begin requests + +int tcommand_flag = 0; +int safer_flag = 1; // safer by default + +search_path *mac_path = &safer_macro_path; + +static int get_copy(node**, int = 0); +static void copy_mode_error(const char *, + const errarg & = empty_errarg, + const errarg & = empty_errarg, + const errarg & = empty_errarg); + +static symbol read_escape_name(); +static void interpolate_string(symbol); +static void interpolate_macro(symbol); +static void interpolate_number_format(symbol); +static void interpolate_environment_variable(symbol); + +static void interpolate_arg(symbol); +static request_or_macro *lookup_request(symbol); +static int get_delim_number(units *, int); +static int get_delim_number(units *, int, units); +static int get_line_arg(units *res, int si, charinfo **cp); +static int read_size(int *); +static symbol get_delim_name(); +static void init_registers(); +static void trapping_blank_line(); + +struct input_iterator; +input_iterator *make_temp_iterator(const char *); +const char *input_char_description(int); + + +void set_escape_char() +{ + if (has_arg()) { + if (tok.ch() == 0) { + error("bad escape character"); + escape_char = '\\'; + } + else + escape_char = tok.ch(); + } + else + escape_char = '\\'; + skip_line(); +} + +void escape_off() +{ + escape_char = 0; + skip_line(); +} + +static int saved_escape_char = '\\'; + +void save_escape_char() +{ + saved_escape_char = escape_char; + skip_line(); +} + +void restore_escape_char() +{ + escape_char = saved_escape_char; + skip_line(); +} + +class input_iterator { +public: + input_iterator(); + virtual ~input_iterator(); + int get(node **); + friend class input_stack; +protected: + const unsigned char *ptr; + const unsigned char *eptr; + input_iterator *next; +private: + virtual int fill(node **); + virtual int peek(); + virtual int has_args() { return 0; } + virtual int nargs() { return 0; } + virtual input_iterator *get_arg(int) { return NULL; } + virtual int get_location(int, const char **, int *) + { return 0; } + virtual void backtrace() {} + virtual int set_location(const char *, int) + { return 0; } + virtual int next_file(FILE *, const char *) { return 0; } + virtual void shift(int) {} + virtual int is_boundary() { return 0; } + virtual int internal_level() { return 0; } + virtual int is_file() { return 0; } + virtual int is_macro() { return 0; } +}; + +input_iterator::input_iterator() +: ptr(0), eptr(0) +{ +} + +input_iterator::~input_iterator() +{ +} + +int input_iterator::fill(node **) +{ + return EOF; +} + +int input_iterator::peek() +{ + return EOF; +} + +inline int input_iterator::get(node **p) +{ + return ptr < eptr ? *ptr++ : fill(p); +} + +class input_boundary : public input_iterator { +public: + int is_boundary() { return 1; } +}; + +class input_return_boundary : public input_iterator { +public: + int is_boundary() { return 2; } +}; + +class file_iterator : public input_iterator { + FILE *fp; + int lineno; + const char *filename; + int popened; + int newline_flag; + enum { BUF_SIZE = 512 }; + unsigned char buf[BUF_SIZE]; + void close(); +public: + file_iterator(FILE *, const char *, int = 0); + ~file_iterator(); + int fill(node **); + int peek(); + int get_location(int, const char **, int *); + void backtrace(); + int set_location(const char *, int); + int next_file(FILE *, const char *); + int is_file(); +}; + +file_iterator::file_iterator(FILE *f, const char *fn, int po) +: fp(f), lineno(1), filename(fn), popened(po), newline_flag(0) +{ + if ((font::use_charnames_in_special) && (fn != 0)) { + if (!the_output) + init_output(); + the_output->put_filename(fn); + } +} + +file_iterator::~file_iterator() +{ + close(); +} + +void file_iterator::close() +{ + if (fp == stdin) + clearerr(stdin); +#ifndef POPEN_MISSING + else if (popened) + pclose(fp); +#endif /* not POPEN_MISSING */ + else + fclose(fp); +} + +int file_iterator::is_file() +{ + return 1; +} + +int file_iterator::next_file(FILE *f, const char *s) +{ + close(); + filename = s; + fp = f; + lineno = 1; + newline_flag = 0; + popened = 0; + ptr = 0; + eptr = 0; + return 1; +} + +int file_iterator::fill(node **) +{ + if (newline_flag) { + curenv->add_html_tag_eol(); + lineno++; + } + newline_flag = 0; + unsigned char *p = buf; + ptr = p; + unsigned char *e = p + BUF_SIZE; + while (p < e) { + int c = getc(fp); + if (c == EOF) + break; + if (illegal_input_char(c)) + warning(WARN_INPUT, "illegal input character code %1", int(c)); + else { + *p++ = c; + if (c == '\n') { + newline_flag = 1; + break; + } + } + } + if (p > buf) { + eptr = p; + return *ptr++; + } + else { + eptr = p; + return EOF; + } +} + +int file_iterator::peek() +{ + int c = getc(fp); + while (illegal_input_char(c)) { + warning(WARN_INPUT, "illegal input character code %1", int(c)); + c = getc(fp); + } + if (c != EOF) + ungetc(c, fp); + return c; +} + +int file_iterator::get_location(int /*allow_macro*/, + const char **filenamep, int *linenop) +{ + *linenop = lineno; + if (filename != 0 && strcmp(filename, "-") == 0) + *filenamep = "<standard input>"; + else + *filenamep = filename; + return 1; +} + +void file_iterator::backtrace() +{ + errprint("%1:%2: backtrace: %3 `%1'\n", filename, lineno, + popened ? "process" : "file"); +} + +int file_iterator::set_location(const char *f, int ln) +{ + if (f) { + filename = f; + if (!the_output) + init_output(); + the_output->put_filename(f); + } + lineno = ln; + return 1; +} + +input_iterator nil_iterator; + +class input_stack { +public: + static int get(node **); + static int peek(); + static void push(input_iterator *); + static input_iterator *get_arg(int); + static int nargs(); + static int get_location(int, const char **, int *); + static int set_location(const char *, int); + static void backtrace(); + static void backtrace_all(); + static void next_file(FILE *, const char *); + static void end_file(); + static void shift(int n); + static void add_boundary(); + static void add_return_boundary(); + static int is_return_boundary(); + static void remove_boundary(); + static int get_level(); + static void clear(); + static void pop_macro(); + + static int limit; +private: + static input_iterator *top; + static int level; + + static int finish_get(node **); + static int finish_peek(); +}; + +input_iterator *input_stack::top = &nil_iterator; +int input_stack::level = 0; +int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT; + +inline int input_stack::get_level() +{ + return level + top->internal_level(); +} + +inline int input_stack::get(node **np) +{ + return (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np); +} + +int input_stack::finish_get(node **np) +{ + for (;;) { + int c = top->fill(np); + if (c != EOF || top->is_boundary()) + return c; + if (top == &nil_iterator) + break; + input_iterator *tem = top; + top = top->next; + level--; + delete tem; + if (top->ptr < top->eptr) + return *top->ptr++; + } + assert(level == 0); + return EOF; +} + +inline int input_stack::peek() +{ + return (top->ptr < top->eptr) ? *top->ptr : finish_peek(); +} + +int input_stack::finish_peek() +{ + for (;;) { + int c = top->peek(); + if (c != EOF || top->is_boundary()) + return c; + if (top == &nil_iterator) + break; + input_iterator *tem = top; + top = top->next; + level--; + delete tem; + if (top->ptr < top->eptr) + return *top->ptr; + } + assert(level == 0); + return EOF; +} + +void input_stack::add_boundary() +{ + push(new input_boundary); +} + +void input_stack::add_return_boundary() +{ + push(new input_return_boundary); +} + +int input_stack::is_return_boundary() +{ + return top->is_boundary() == 2; +} + +void input_stack::remove_boundary() +{ + assert(top->is_boundary()); + input_iterator *temp = top->next; + delete top; + top = temp; + level--; +} + +void input_stack::push(input_iterator *in) +{ + if (in == 0) + return; + if (++level > limit && limit > 0) + fatal("input stack limit exceeded (probable infinite loop)"); + in->next = top; + top = in; +} + +input_iterator *input_stack::get_arg(int i) +{ + input_iterator *p; + for (p = top; p != NULL; p = p->next) + if (p->has_args()) + return p->get_arg(i); + return 0; +} + +void input_stack::shift(int n) +{ + for (input_iterator *p = top; p; p = p->next) + if (p->has_args()) { + p->shift(n); + return; + } +} + +int input_stack::nargs() +{ + for (input_iterator *p =top; p != 0; p = p->next) + if (p->has_args()) + return p->nargs(); + return 0; +} + +int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop) +{ + for (input_iterator *p = top; p; p = p->next) + if (p->get_location(allow_macro, filenamep, linenop)) + return 1; + return 0; +} + +void input_stack::backtrace() +{ + const char *f; + int n; + // only backtrace down to (not including) the topmost file + for (input_iterator *p = top; + p && !p->get_location(0, &f, &n); + p = p->next) + p->backtrace(); +} + +void input_stack::backtrace_all() +{ + for (input_iterator *p = top; p; p = p->next) + p->backtrace(); +} + +int input_stack::set_location(const char *filename, int lineno) +{ + for (input_iterator *p = top; p; p = p->next) + if (p->set_location(filename, lineno)) + return 1; + return 0; +} + +void input_stack::next_file(FILE *fp, const char *s) +{ + input_iterator **pp; + for (pp = ⊤ *pp != &nil_iterator; pp = &(*pp)->next) + if ((*pp)->next_file(fp, s)) + return; + if (++level > limit && limit > 0) + fatal("input stack limit exceeded"); + *pp = new file_iterator(fp, s); + (*pp)->next = &nil_iterator; +} + +void input_stack::end_file() +{ + for (input_iterator **pp = ⊤ *pp != &nil_iterator; pp = &(*pp)->next) + if ((*pp)->is_file()) { + input_iterator *tem = *pp; + *pp = (*pp)->next; + delete tem; + level--; + return; + } +} + +void input_stack::clear() +{ + int nboundaries = 0; + while (top != &nil_iterator) { + if (top->is_boundary()) + nboundaries++; + input_iterator *tem = top; + top = top->next; + level--; + delete tem; + } + // Keep while_request happy. + for (; nboundaries > 0; --nboundaries) + add_return_boundary(); +} + +void input_stack::pop_macro() +{ + int nboundaries = 0; + int is_macro = 0; + do { + if (top->next == &nil_iterator) + break; + if (top->is_boundary()) + nboundaries++; + is_macro = top->is_macro(); + input_iterator *tem = top; + top = top->next; + level--; + delete tem; + } while (!is_macro); + // Keep while_request happy. + for (; nboundaries > 0; --nboundaries) + add_return_boundary(); +} + +void backtrace_request() +{ + input_stack::backtrace_all(); + fflush(stderr); + skip_line(); +} + +void next_file() +{ + symbol nm = get_long_name(0); + while (!tok.newline() && !tok.eof()) + tok.next(); + if (nm.is_null()) + input_stack::end_file(); + else { + errno = 0; + FILE *fp = fopen(nm.contents(), "r"); + if (!fp) + error("can't open `%1': %2", nm.contents(), strerror(errno)); + else + input_stack::next_file(fp, nm.contents()); + } + tok.next(); +} + +void shift() +{ + int n; + if (!has_arg() || !get_integer(&n)) + n = 1; + input_stack::shift(n); + skip_line(); +} + +static int get_char_for_escape_name() +{ + int c = get_copy(NULL); + switch (c) { + case EOF: + copy_mode_error("end of input in escape name"); + return '\0'; + default: + if (!illegal_input_char(c)) + break; + // fall through + case '\n': + if (c == '\n') + input_stack::push(make_temp_iterator("\n")); + case ' ': + case '\t': + case '\001': + case '\b': + copy_mode_error("%1 is not allowed in an escape name", + input_char_description(c)); + return '\0'; + } + return c; +} + +static symbol read_two_char_escape_name() +{ + char buf[3]; + buf[0] = get_char_for_escape_name(); + if (buf[0] != '\0') { + buf[1] = get_char_for_escape_name(); + if (buf[1] == '\0') + buf[0] = 0; + else + buf[2] = 0; + } + return symbol(buf); +} + +static symbol read_long_escape_name() +{ + int start_level = input_stack::get_level(); + char abuf[ABUF_SIZE]; + char *buf = abuf; + int buf_size = ABUF_SIZE; + int i = 0; + for (;;) { + int c = get_char_for_escape_name(); + if (c == 0) { + if (buf != abuf) + a_delete buf; + return NULL_SYMBOL; + } + if (i + 2 > buf_size) { + if (buf == abuf) { + buf = new char[ABUF_SIZE*2]; + memcpy(buf, abuf, buf_size); + buf_size = ABUF_SIZE*2; + } + else { + char *old_buf = buf; + buf = new char[buf_size*2]; + memcpy(buf, old_buf, buf_size); + buf_size *= 2; + a_delete old_buf; + } + } + if (c == ']' && input_stack::get_level() == start_level) + break; + buf[i++] = c; + } + buf[i] = 0; + if (buf == abuf) { + if (i == 0) { + copy_mode_error("empty escape name"); + return NULL_SYMBOL; + } + return symbol(abuf); + } + else { + symbol s(buf); + a_delete buf; + return s; + } +} + +static symbol read_escape_name() +{ + int c = get_char_for_escape_name(); + if (c == 0) + return NULL_SYMBOL; + if (c == '(') + return read_two_char_escape_name(); + if (c == '[' && !compatible_flag) + return read_long_escape_name(); + char buf[2]; + buf[0] = c; + buf[1] = '\0'; + return symbol(buf); +} + +static symbol read_increment_and_escape_name(int *incp) +{ + int c = get_char_for_escape_name(); + switch (c) { + case 0: + *incp = 0; + return NULL_SYMBOL; + case '(': + *incp = 0; + return read_two_char_escape_name(); + case '+': + *incp = 1; + return read_escape_name(); + case '-': + *incp = -1; + return read_escape_name(); + case '[': + if (!compatible_flag) { + *incp = 0; + return read_long_escape_name(); + } + break; + } + *incp = 0; + char buf[2]; + buf[0] = c; + buf[1] = '\0'; + return symbol(buf); +} + +static int get_copy(node **nd, int defining) +{ + for (;;) { + int c = input_stack::get(nd); + if (c == ESCAPE_NEWLINE) { + if (defining) + return c; + do { + c = input_stack::get(nd); + } while (c == ESCAPE_NEWLINE); + } + if (c != escape_char || escape_char <= 0) + return c; + c = input_stack::peek(); + switch(c) { + case 0: + return escape_char; + case '"': + (void)input_stack::get(NULL); + while ((c = input_stack::get(NULL)) != '\n' && c != EOF) + ; + return c; + case '#': // Like \" but newline is ignored. + (void)input_stack::get(NULL); + while ((c = input_stack::get(NULL)) != '\n') + if (c == EOF) + return EOF; + break; + case '$': + { + (void)input_stack::get(NULL); + symbol s = read_escape_name(); + if (!s.is_null()) + interpolate_arg(s); + break; + } + case '*': + { + (void)input_stack::get(NULL); + symbol s = read_escape_name(); + if (!s.is_null()) + interpolate_string(s); + break; + } + case 'a': + (void)input_stack::get(NULL); + return '\001'; + case 'e': + (void)input_stack::get(NULL); + return ESCAPE_e; + case 'E': + (void)input_stack::get(NULL); + return ESCAPE_E; + case 'n': + { + (void)input_stack::get(NULL); + int inc; + symbol s = read_increment_and_escape_name(&inc); + if (!s.is_null()) + interpolate_number_reg(s, inc); + break; + } + case 'g': + { + (void)input_stack::get(NULL); + symbol s = read_escape_name(); + if (!s.is_null()) + interpolate_number_format(s); + break; + } + case 't': + (void)input_stack::get(NULL); + return '\t'; + case 'V': + { + (void)input_stack::get(NULL); + symbol s = read_escape_name(); + if (!s.is_null()) + interpolate_environment_variable(s); + break; + } + case '\n': + (void)input_stack::get(NULL); + if (defining) + return ESCAPE_NEWLINE; + break; + case ' ': + (void)input_stack::get(NULL); + return ESCAPE_SPACE; + case '~': + (void)input_stack::get(NULL); + return ESCAPE_TILDE; + case ':': + (void)input_stack::get(NULL); + return ESCAPE_COLON; + case '|': + (void)input_stack::get(NULL); + return ESCAPE_BAR; + case '^': + (void)input_stack::get(NULL); + return ESCAPE_CIRCUMFLEX; + case '{': + (void)input_stack::get(NULL); + return ESCAPE_LEFT_BRACE; + case '}': + (void)input_stack::get(NULL); + return ESCAPE_RIGHT_BRACE; + case '`': + (void)input_stack::get(NULL); + return ESCAPE_LEFT_QUOTE; + case '\'': + (void)input_stack::get(NULL); + return ESCAPE_RIGHT_QUOTE; + case '-': + (void)input_stack::get(NULL); + return ESCAPE_HYPHEN; + case '_': + (void)input_stack::get(NULL); + return ESCAPE_UNDERSCORE; + case 'c': + (void)input_stack::get(NULL); + return ESCAPE_c; + case '!': + (void)input_stack::get(NULL); + return ESCAPE_BANG; + case '?': + (void)input_stack::get(NULL); + return ESCAPE_QUESTION; + case '&': + (void)input_stack::get(NULL); + return ESCAPE_AMPERSAND; + case ')': + (void)input_stack::get(NULL); + return ESCAPE_RIGHT_PARENTHESIS; + case '.': + (void)input_stack::get(NULL); + return c; + case '%': + (void)input_stack::get(NULL); + return ESCAPE_PERCENT; + default: + if (c == escape_char) { + (void)input_stack::get(NULL); + return c; + } + else + return escape_char; + } + } +} + +class non_interpreted_char_node : public node { + unsigned char c; +public: + non_interpreted_char_node(unsigned char); + node *copy(); + int interpret(macro *); + int same(node *); + const char *type(); + int force_tprint(); +}; + +int non_interpreted_char_node::same(node *nd) +{ + return c == ((non_interpreted_char_node *)nd)->c; +} + +const char *non_interpreted_char_node::type() +{ + return "non_interpreted_char_node"; +} + +int non_interpreted_char_node::force_tprint() +{ + return 0; +} + +non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n) +{ + assert(n != 0); +} + +node *non_interpreted_char_node::copy() +{ + return new non_interpreted_char_node(c); +} + +int non_interpreted_char_node::interpret(macro *mac) +{ + mac->append(c); + return 1; +} + +static void do_width(); +static node *do_non_interpreted(); +static node *do_special(); +static node *do_suppress(); +static void do_register(); + +static node *do_overstrike() +{ + token start; + overstrike_node *on = new overstrike_node; + start.next(); + tok.next(); + while (tok != start) { + if (tok.newline() || tok.eof()) { + warning(WARN_DELIM, "missing closing delimiter"); + break; + } + charinfo *ci = tok.get_char(1); + if (ci) { + node *n = curenv->make_char_node(ci); + if (n) + on->overstrike(n); + } + tok.next(); + } + return on; +} + +static node *do_bracket() +{ + token start; + bracket_node *bn = new bracket_node; + start.next(); + tok.next(); + while (tok != start) { + if (tok.eof()) { + warning(WARN_DELIM, "missing closing delimiter"); + break; + } + if (tok.newline()) { + warning(WARN_DELIM, "missing closing delimiter"); + input_stack::push(make_temp_iterator("\n")); + break; + } + charinfo *ci = tok.get_char(1); + if (ci) { + node *n = curenv->make_char_node(ci); + if (n) + bn->bracket(n); + } + tok.next(); + } + return bn; +} + +static int do_name_test() +{ + token start; + start.next(); + int start_level = input_stack::get_level(); + int bad_char = 0; + int some_char = 0; + for (;;) { + tok.next(); + if (tok.newline() || tok.eof()) { + warning(WARN_DELIM, "missing closing delimiter"); + break; + } + if (tok == start + && (compatible_flag || input_stack::get_level() == start_level)) + break; + if (!tok.ch()) + bad_char = 1; + some_char = 1; + } + return some_char && !bad_char; +} + +static int do_expr_test() +{ + token start; + start.next(); + int start_level = input_stack::get_level(); + if (!start.delimiter(1)) + return 0; + tok.next(); + // disable all warning and error messages temporarily + int saved_warning_mask = warning_mask; + int saved_inhibit_errors = inhibit_errors; + warning_mask = 0; + inhibit_errors = 1; + int dummy; + int result = get_number_rigidly(&dummy, 'u'); + warning_mask = saved_warning_mask; + inhibit_errors = saved_inhibit_errors; + if (tok == start && input_stack::get_level() == start_level) + return result; + // ignore everything up to the delimiter in case we aren't right there + for (;;) { + tok.next(); + if (tok.newline() || tok.eof()) { + warning(WARN_DELIM, "missing closing delimiter"); + break; + } + if (tok == start && input_stack::get_level() == start_level) + break; + } + return 0; +} + +#if 0 +static node *do_zero_width() +{ + token start; + start.next(); + int start_level = input_stack::get_level(); + environment env(curenv); + environment *oldenv = curenv; + curenv = &env; + for (;;) { + tok.next(); + if (tok.newline() || tok.eof()) { + error("missing closing delimiter"); + break; + } + if (tok == start + && (compatible_flag || input_stack::get_level() == start_level)) + break; + tok.process(); + } + curenv = oldenv; + node *rev = env.extract_output_line(); + node *n = 0; + while (rev) { + node *tem = rev; + rev = rev->next; + tem->next = n; + n = tem; + } + return new zero_width_node(n); +} + +#else + +// It's undesirable for \Z to change environments, because then +// \n(.w won't work as expected. + +static node *do_zero_width() +{ + node *rev = new dummy_node; + token start; + start.next(); + int start_level = input_stack::get_level(); + for (;;) { + tok.next(); + if (tok.newline() || tok.eof()) { + warning(WARN_DELIM, "missing closing delimiter"); + break; + } + if (tok == start + && (compatible_flag || input_stack::get_level() == start_level)) + break; + if (!tok.add_to_node_list(&rev)) + error("illegal token in argument to \\Z"); + } + node *n = 0; + while (rev) { + node *tem = rev; + rev = rev->next; + tem->next = n; + n = tem; + } + return new zero_width_node(n); +} + +#endif + +token_node *node::get_token_node() +{ + return 0; +} + +class token_node : public node { +public: + token tk; + token_node(const token &t); + node *copy(); + token_node *get_token_node(); + int same(node *); + const char *type(); + int force_tprint(); +}; + +token_node::token_node(const token &t) : tk(t) +{ +} + +node *token_node::copy() +{ + return new token_node(tk); +} + +token_node *token_node::get_token_node() +{ + return this; +} + +int token_node::same(node *nd) +{ + return tk == ((token_node *)nd)->tk; +} + +const char *token_node::type() +{ + return "token_node"; +} + +int token_node::force_tprint() +{ + return 0; +} + +token::token() : nd(0), type(TOKEN_EMPTY) +{ +} + +token::~token() +{ + delete nd; +} + +token::token(const token &t) +: nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type) +{ + // Use two statements to work around bug in SGI C++. + node *tem = t.nd; + nd = tem ? tem->copy() : 0; +} + +void token::operator=(const token &t) +{ + delete nd; + nm = t.nm; + // Use two statements to work around bug in SGI C++. + node *tem = t.nd; + nd = tem ? tem->copy() : 0; + c = t.c; + val = t.val; + dim = t.dim; + type = t.type; +} + +void token::skip() +{ + while (space()) + next(); +} + +int has_arg() +{ + while (tok.space()) + tok.next(); + return !tok.newline(); +} + +void token::make_space() +{ + type = TOKEN_SPACE; +} + +void token::make_newline() +{ + type = TOKEN_NEWLINE; +} + +void token::next() +{ + if (nd) { + delete nd; + nd = 0; + } + units x; + for (;;) { + node *n; + int cc = input_stack::get(&n); + if (cc != escape_char || escape_char == 0) { + handle_normal_char: + switch(cc) { + case EOF: + type = TOKEN_EOF; + return; + case TRANSPARENT_FILE_REQUEST: + case TITLE_REQUEST: + case COPY_FILE_REQUEST: +#ifdef COLUMN + case VJUSTIFY_REQUEST: +#endif /* COLUMN */ + type = TOKEN_REQUEST; + c = cc; + return; + case BEGIN_TRAP: + type = TOKEN_BEGIN_TRAP; + return; + case END_TRAP: + type = TOKEN_END_TRAP; + return; + case LAST_PAGE_EJECTOR: + seen_last_page_ejector = 1; + // fall through + case PAGE_EJECTOR: + type = TOKEN_PAGE_EJECTOR; + return; + case ESCAPE_PERCENT: + ESCAPE_PERCENT: + type = TOKEN_HYPHEN_INDICATOR; + return; + case ESCAPE_SPACE: + ESCAPE_SPACE: + type = TOKEN_NODE; + nd = new space_char_hmotion_node(curenv->get_space_width()); + return; + case ESCAPE_TILDE: + ESCAPE_TILDE: + type = TOKEN_STRETCHABLE_SPACE; + return; + case ESCAPE_COLON: + ESCAPE_COLON: + type = TOKEN_NODE; + nd = new space_node(H0); + nd->freeze_space(); + nd->is_escape_colon(); + return; + case ESCAPE_e: + ESCAPE_e: + type = TOKEN_ESCAPE; + return; + case ESCAPE_E: + goto handle_escape_char; + case ESCAPE_BAR: + ESCAPE_BAR: + type = TOKEN_NODE; + nd = new hmotion_node(curenv->get_narrow_space_width()); + return; + case ESCAPE_CIRCUMFLEX: + ESCAPE_CIRCUMFLEX: + type = TOKEN_NODE; + nd = new hmotion_node(curenv->get_half_narrow_space_width()); + return; + case ESCAPE_NEWLINE: + break; + case ESCAPE_LEFT_BRACE: + ESCAPE_LEFT_BRACE: + type = TOKEN_LEFT_BRACE; + return; + case ESCAPE_RIGHT_BRACE: + ESCAPE_RIGHT_BRACE: + type = TOKEN_RIGHT_BRACE; + return; + case ESCAPE_LEFT_QUOTE: + ESCAPE_LEFT_QUOTE: + type = TOKEN_SPECIAL; + nm = symbol("ga"); + return; + case ESCAPE_RIGHT_QUOTE: + ESCAPE_RIGHT_QUOTE: + type = TOKEN_SPECIAL; + nm = symbol("aa"); + return; + case ESCAPE_HYPHEN: + ESCAPE_HYPHEN: + type = TOKEN_SPECIAL; + nm = symbol("-"); + return; + case ESCAPE_UNDERSCORE: + ESCAPE_UNDERSCORE: + type = TOKEN_SPECIAL; + nm = symbol("ul"); + return; + case ESCAPE_c: + ESCAPE_c: + type = TOKEN_INTERRUPT; + return; + case ESCAPE_BANG: + ESCAPE_BANG: + type = TOKEN_TRANSPARENT; + return; + case ESCAPE_QUESTION: + ESCAPE_QUESTION: + nd = do_non_interpreted(); + if (nd) { + type = TOKEN_NODE; + return; + } + break; + case ESCAPE_AMPERSAND: + ESCAPE_AMPERSAND: + type = TOKEN_DUMMY; + return; + case ESCAPE_RIGHT_PARENTHESIS: + ESCAPE_RIGHT_PARENTHESIS: + type = TOKEN_TRANSPARENT_DUMMY; + return; + case '\b': + type = TOKEN_BACKSPACE; + return; + case ' ': + type = TOKEN_SPACE; + return; + case '\t': + type = TOKEN_TAB; + return; + case '\n': + type = TOKEN_NEWLINE; + return; + case '\001': + type = TOKEN_LEADER; + return; + case 0: + { + assert(n != 0); + token_node *tn = n->get_token_node(); + if (tn) { + *this = tn->tk; + delete tn; + } + else { + nd = n; + type = TOKEN_NODE; + } + } + return; + default: + type = TOKEN_CHAR; + c = cc; + return; + } + } + else { + handle_escape_char: + cc = input_stack::get(NULL); + switch(cc) { + case '(': + nm = read_two_char_escape_name(); + type = TOKEN_SPECIAL; + return; + case EOF: + type = TOKEN_EOF; + error("end of input after escape character"); + return; + case '`': + goto ESCAPE_LEFT_QUOTE; + case '\'': + goto ESCAPE_RIGHT_QUOTE; + case '-': + goto ESCAPE_HYPHEN; + case '_': + goto ESCAPE_UNDERSCORE; + case '%': + goto ESCAPE_PERCENT; + case ' ': + goto ESCAPE_SPACE; + case '0': + nd = new hmotion_node(curenv->get_digit_width()); + type = TOKEN_NODE; + return; + case '|': + goto ESCAPE_BAR; + case '^': + goto ESCAPE_CIRCUMFLEX; + case '/': + type = TOKEN_ITALIC_CORRECTION; + return; + case ',': + type = TOKEN_NODE; + nd = new left_italic_corrected_node; + return; + case '&': + goto ESCAPE_AMPERSAND; + case ')': + goto ESCAPE_RIGHT_PARENTHESIS; + case '!': + goto ESCAPE_BANG; + case '?': + goto ESCAPE_QUESTION; + case '~': + goto ESCAPE_TILDE; + case ':': + goto ESCAPE_COLON; + case '"': + while ((cc = input_stack::get(NULL)) != '\n' && cc != EOF) + ; + if (cc == '\n') + type = TOKEN_NEWLINE; + else + type = TOKEN_EOF; + return; + case '#': // Like \" but newline is ignored. + while ((cc = input_stack::get(NULL)) != '\n') + if (cc == EOF) { + type = TOKEN_EOF; + return; + } + break; + case '$': + { + symbol nm = read_escape_name(); + if (!nm.is_null()) + interpolate_arg(nm); + break; + } + case '*': + { + symbol nm = read_escape_name(); + if (!nm.is_null()) + interpolate_string(nm); + break; + } + case 'a': + nd = new non_interpreted_char_node('\001'); + type = TOKEN_NODE; + return; + case 'A': + c = '0' + do_name_test(); + type = TOKEN_CHAR; + return; + case 'b': + nd = do_bracket(); + type = TOKEN_NODE; + return; + case 'B': + c = '0' + do_expr_test(); + type = TOKEN_CHAR; + return; + case 'c': + goto ESCAPE_c; + case 'C': + nm = get_delim_name(); + if (nm.is_null()) + break; + type = TOKEN_SPECIAL; + return; + case 'd': + type = TOKEN_NODE; + nd = new vmotion_node(curenv->get_size()/2); + return; + case 'D': + nd = read_draw_node(); + if (!nd) + break; + type = TOKEN_NODE; + return; + case 'e': + goto ESCAPE_e; + case 'E': + goto handle_escape_char; + case 'f': + { + symbol s = read_escape_name(); + if (s.is_null()) + break; + const char *p; + for (p = s.contents(); *p != '\0'; p++) + if (!csdigit(*p)) + break; + if (*p) + curenv->set_font(s); + else + curenv->set_font(atoi(s.contents())); + break; + } + case 'g': + { + symbol s = read_escape_name(); + if (!s.is_null()) + interpolate_number_format(s); + break; + } + case 'h': + if (!get_delim_number(&x, 'm')) + break; + type = TOKEN_NODE; + nd = new hmotion_node(x); + return; + case 'H': + if (get_delim_number(&x, 'z', curenv->get_requested_point_size())) + curenv->set_char_height(x); + break; + case 'k': + nm = read_escape_name(); + if (nm.is_null()) + break; + type = TOKEN_MARK_INPUT; + return; + case 'l': + case 'L': + { + charinfo *s = 0; + if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s)) + break; + if (s == 0) + s = get_charinfo(cc == 'l' ? "ru" : "br"); + type = TOKEN_NODE; + node *n = curenv->make_char_node(s); + if (cc == 'l') + nd = new hline_node(x, n); + else + nd = new vline_node(x, n); + return; + } + case 'n': + { + int inc; + symbol nm = read_increment_and_escape_name(&inc); + if (!nm.is_null()) + interpolate_number_reg(nm, inc); + break; + } + case 'N': + if (!get_delim_number(&val, 0)) + break; + type = TOKEN_NUMBERED_CHAR; + return; + case 'o': + nd = do_overstrike(); + type = TOKEN_NODE; + return; + case 'O': + nd = do_suppress(); + if (!nd) + break; + type = TOKEN_NODE; + return; + case 'p': + type = TOKEN_SPREAD; + return; + case 'r': + type = TOKEN_NODE; + nd = new vmotion_node(-curenv->get_size()); + return; + case 'R': + do_register(); + break; + case 's': + if (read_size(&x)) + curenv->set_size(x); + break; + case 'S': + if (get_delim_number(&x, 0)) + curenv->set_char_slant(x); + break; + case 't': + type = TOKEN_NODE; + nd = new non_interpreted_char_node('\t'); + return; + case 'u': + type = TOKEN_NODE; + nd = new vmotion_node(-curenv->get_size()/2); + return; + case 'v': + if (!get_delim_number(&x, 'v')) + break; + type = TOKEN_NODE; + nd = new vmotion_node(x); + return; + case 'V': + { + symbol nm = read_escape_name(); + if (!nm.is_null()) + interpolate_environment_variable(nm); + break; + } + case 'w': + do_width(); + break; + case 'x': + if (!get_delim_number(&x, 'v')) + break; + type = TOKEN_NODE; + nd = new extra_size_node(x); + return; + case 'X': + nd = do_special(); + if (!nd) + break; + type = TOKEN_NODE; + return; + case 'Y': + { + symbol s = read_escape_name(); + if (s.is_null()) + break; + request_or_macro *p = lookup_request(s); + macro *m = p->to_macro(); + if (!m) { + error("can't transparently throughput a request"); + break; + } + nd = new special_node(*m); + type = TOKEN_NODE; + return; + } + case 'z': + { + next(); + if (type == TOKEN_NODE) + nd = new zero_width_node(nd); + else { + charinfo *ci = get_char(1); + if (ci == 0) + break; + node *gn = curenv->make_char_node(ci); + if (gn == 0) + break; + nd = new zero_width_node(gn); + type = TOKEN_NODE; + } + return; + } + case 'Z': + nd = do_zero_width(); + if (nd == 0) + break; + type = TOKEN_NODE; + return; + case '{': + goto ESCAPE_LEFT_BRACE; + case '}': + goto ESCAPE_RIGHT_BRACE; + case '\n': + break; + case '[': + if (!compatible_flag) { + nm = read_long_escape_name(); + if (nm.is_null()) + break; + type = TOKEN_SPECIAL; + return; + } + goto handle_normal_char; + default: + if (cc != escape_char && cc != '.') + warning(WARN_ESCAPE, "escape character ignored before %1", + input_char_description(cc)); + goto handle_normal_char; + } + } + } +} + +int token::operator==(const token &t) +{ + if (type != t.type) + return 0; + switch(type) { + case TOKEN_CHAR: + return c == t.c; + case TOKEN_SPECIAL: + return nm == t.nm; + case TOKEN_NUMBERED_CHAR: + return val == t.val; + default: + return 1; + } +} + +int token::operator!=(const token &t) +{ + return !(*this == t); +} + +// is token a suitable delimiter (like ')? + +int token::delimiter(int err) +{ + switch(type) { + case TOKEN_CHAR: + switch(c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case '/': + case '*': + case '%': + case '<': + case '>': + case '=': + case '&': + case ':': + case '(': + case ')': + case '.': + if (err) + error("cannot use character `%1' as a starting delimiter", char(c)); + return 0; + default: + return 1; + } + case TOKEN_NODE: + case TOKEN_SPACE: + case TOKEN_STRETCHABLE_SPACE: + case TOKEN_TAB: + case TOKEN_NEWLINE: + if (err) + error("cannot use %1 as a starting delimiter", description()); + return 0; + default: + return 1; + } +} + +const char *token::description() +{ + static char buf[4]; + switch (type) { + case TOKEN_BACKSPACE: + return "a backspace character"; + case TOKEN_CHAR: + buf[0] = '`'; + buf[1] = c; + buf[2] = '\''; + buf[3] = '\0'; + return buf; + case TOKEN_DUMMY: + return "`\\&'"; + case TOKEN_ESCAPE: + return "`\\e'"; + case TOKEN_HYPHEN_INDICATOR: + return "`\\%'"; + case TOKEN_INTERRUPT: + return "`\\c'"; + case TOKEN_ITALIC_CORRECTION: + return "`\\/'"; + case TOKEN_LEADER: + return "a leader character"; + case TOKEN_LEFT_BRACE: + return "`\\{'"; + case TOKEN_MARK_INPUT: + return "`\\k'"; + case TOKEN_NEWLINE: + return "newline"; + case TOKEN_NODE: + return "a node"; + case TOKEN_NUMBERED_CHAR: + return "`\\N'"; + case TOKEN_RIGHT_BRACE: + return "`\\}'"; + case TOKEN_SPACE: + return "a space"; + case TOKEN_SPECIAL: + return "a special character"; + case TOKEN_SPREAD: + return "`\\p'"; + case TOKEN_STRETCHABLE_SPACE: + return "`\\~'"; + case TOKEN_TAB: + return "a tab character"; + case TOKEN_TRANSPARENT: + return "`\\!'"; + case TOKEN_TRANSPARENT_DUMMY: + return "`\\)'"; + case TOKEN_EOF: + return "end of input"; + default: + break; + } + return "a magic token"; +} + +void skip_line() +{ + while (!tok.newline()) + if (tok.eof()) + return; + else + tok.next(); + tok.next(); +} + +void compatible() +{ + int n; + if (has_arg() && get_integer(&n)) + compatible_flag = n != 0; + else + compatible_flag = 1; + skip_line(); +} + +static void empty_name_warning(int required) +{ + if (tok.newline() || tok.eof()) { + if (required) + warning(WARN_MISSING, "missing name"); + } + else if (tok.right_brace() || tok.tab()) { + const char *start = tok.description(); + do { + tok.next(); + } while (tok.space() || tok.right_brace() || tok.tab()); + if (!tok.newline() && !tok.eof()) + error("%1 is not allowed before an argument", start); + else if (required) + warning(WARN_MISSING, "missing name"); + } + else if (required) + error("name expected (got %1)", tok.description()); + else + error("name expected (got %1): treated as missing", tok.description()); +} + +static void non_empty_name_warning() +{ + if (!tok.newline() && !tok.eof() && !tok.space() && !tok.tab() + && !tok.right_brace() + // We don't want to give a warning for .el\{ + && !tok.left_brace()) + error("%1 is not allowed in a name", tok.description()); +} + +symbol get_name(int required) +{ + if (compatible_flag) { + char buf[3]; + tok.skip(); + if ((buf[0] = tok.ch()) != 0) { + tok.next(); + if ((buf[1] = tok.ch()) != 0) { + buf[2] = 0; + tok.make_space(); + } + else + non_empty_name_warning(); + return symbol(buf); + } + else { + empty_name_warning(required); + return NULL_SYMBOL; + } + } + else + return get_long_name(required); +} + +symbol get_long_name(int required) +{ + while (tok.space()) + tok.next(); + char abuf[ABUF_SIZE]; + char *buf = abuf; + int buf_size = ABUF_SIZE; + int i = 0; + for (;;) { + if (i + 1 > buf_size) { + if (buf == abuf) { + buf = new char[ABUF_SIZE*2]; + memcpy(buf, abuf, buf_size); + buf_size = ABUF_SIZE*2; + } + else { + char *old_buf = buf; + buf = new char[buf_size*2]; + memcpy(buf, old_buf, buf_size); + buf_size *= 2; + a_delete old_buf; + } + } + if ((buf[i] = tok.ch()) == 0) + break; + i++; + tok.next(); + } + if (i == 0) { + empty_name_warning(required); + return NULL_SYMBOL; + } + non_empty_name_warning(); + if (buf == abuf) + return symbol(buf); + else { + symbol s(buf); + a_delete buf; + return s; + } +} + +void exit_troff() +{ + exit_started = 1; + topdiv->set_last_page(); + if (!end_macro_name.is_null()) { + spring_trap(end_macro_name); + tok.next(); + process_input_stack(); + } + curenv->final_break(); + tok.next(); + process_input_stack(); + end_diversions(); + done_end_macro = 1; + topdiv->set_ejecting(); + static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' }; + input_stack::push(make_temp_iterator((char *)buf)); + topdiv->space(topdiv->get_page_length(), 1); + tok.next(); + process_input_stack(); + seen_last_page_ejector = 1; // should be set already + topdiv->set_ejecting(); + push_page_ejector(); + topdiv->space(topdiv->get_page_length(), 1); + tok.next(); + process_input_stack(); + // This will only happen if a trap-invoked macro starts a diversion, + // or if vertical position traps have been disabled. + cleanup_and_exit(0); +} + +// This implements .ex. The input stack must be cleared before calling +// exit_troff(). + +void exit_request() +{ + input_stack::clear(); + if (exit_started) + tok.next(); + else + exit_troff(); +} + +void return_macro_request() +{ + input_stack::pop_macro(); + tok.next(); +} + +void end_macro() +{ + end_macro_name = get_name(); + skip_line(); +} + +void blank_line_macro() +{ + blank_line_macro_name = get_name(); + skip_line(); +} + +static void trapping_blank_line() +{ + if (!blank_line_macro_name.is_null()) + spring_trap(blank_line_macro_name); + else + blank_line(); +} + +void do_request() +{ + int saved_compatible_flag = compatible_flag; + compatible_flag = 0; + symbol nm = get_name(); + if (nm.is_null()) + skip_line(); + else + interpolate_macro(nm); + compatible_flag = saved_compatible_flag; +} + +inline int possibly_handle_first_page_transition() +{ + if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) { + handle_first_page_transition(); + return 1; + } + else + return 0; +} + +static int transparent_translate(int cc) +{ + if (!illegal_input_char(cc)) { + charinfo *ci = charset_table[cc]; + switch (ci->get_special_translation(1)) { + case charinfo::TRANSLATE_SPACE: + return ' '; + case charinfo::TRANSLATE_STRETCHABLE_SPACE: + return ESCAPE_TILDE; + case charinfo::TRANSLATE_DUMMY: + return ESCAPE_AMPERSAND; + case charinfo::TRANSLATE_HYPHEN_INDICATOR: + return ESCAPE_PERCENT; + } + // This is really ugly. + ci = ci->get_translation(1); + if (ci) { + int c = ci->get_ascii_code(); + if (c != '\0') + return c; + error("can't translate %1 to special character `%2'" + " in transparent throughput", + input_char_description(cc), + ci->nm.contents()); + } + } + return cc; +} + +class int_stack { + struct int_stack_element { + int n; + int_stack_element *next; + } *top; +public: + int_stack(); + ~int_stack(); + void push(int); + int is_empty(); + int pop(); +}; + +int_stack::int_stack() +{ + top = 0; +} + +int_stack::~int_stack() +{ + while (top != 0) { + int_stack_element *temp = top; + top = top->next; + delete temp; + } +} + +int int_stack::is_empty() +{ + return top == 0; +} + +void int_stack::push(int n) +{ + int_stack_element *p = new int_stack_element; + p->next = top; + p->n = n; + top = p; +} + +int int_stack::pop() +{ + assert(top != 0); + int_stack_element *p = top; + top = top->next; + int n = p->n; + delete p; + return n; +} + +int node::reread(int *) +{ + return 0; +} + +int diverted_space_node::reread(int *bolp) +{ + if (curenv->get_fill()) + trapping_blank_line(); + else + curdiv->space(n); + *bolp = 1; + return 1; +} + +int diverted_copy_file_node::reread(int *bolp) +{ + curdiv->copy_file(filename.contents()); + *bolp = 1; + return 1; +} + +int word_space_node::reread(int *bolp) +{ + if (unformat) { + for (width_list *w = orig_width; w; w = w->next) + curenv->space(w->width, w->sentence_width); + unformat = 0; + return 1; + } + return 0; +} + +int unbreakable_space_node::reread(int *) +{ + return 0; +} + +int hmotion_node::reread(int *bolp) +{ + if (unformat && was_tab) { + curenv->handle_tab(0); + unformat = 0; + return 1; + } + return 0; +} + +void process_input_stack() +{ + int_stack trap_bol_stack; + int bol = 1; + for (;;) { + int suppress_next = 0; + switch (tok.type) { + case token::TOKEN_CHAR: + { + unsigned char ch = tok.c; + if (bol && + (ch == curenv->control_char + || ch == curenv->no_break_control_char)) { + break_flag = ch == curenv->control_char; + // skip tabs as well as spaces here + do { + tok.next(); + } while (tok.white_space()); + symbol nm = get_name(); + if (nm.is_null()) + skip_line(); + else + interpolate_macro(nm); + suppress_next = 1; + } + else { + if (possibly_handle_first_page_transition()) + ; + else { + for (;;) { + curenv->add_char(charset_table[ch]); + tok.next(); + if (tok.type != token::TOKEN_CHAR) + break; + ch = tok.c; + } + suppress_next = 1; + bol = 0; + } + } + break; + } + case token::TOKEN_TRANSPARENT: + { + if (bol) { + if (possibly_handle_first_page_transition()) + ; + else { + int cc; + do { + node *n; + cc = get_copy(&n); + if (cc != EOF) + if (cc != '\0') + curdiv->transparent_output(transparent_translate(cc)); + else + curdiv->transparent_output(n); + } while (cc != '\n' && cc != EOF); + if (cc == EOF) + curdiv->transparent_output('\n'); + } + } + break; + } + case token::TOKEN_NEWLINE: + { + if (bol && !curenv->get_prev_line_interrupted()) + trapping_blank_line(); + else { + curenv->newline(); + bol = 1; + } + break; + } + case token::TOKEN_REQUEST: + { + int request_code = tok.c; + tok.next(); + switch (request_code) { + case TITLE_REQUEST: + title(); + break; + case COPY_FILE_REQUEST: + copy_file(); + break; + case TRANSPARENT_FILE_REQUEST: + transparent_file(); + break; +#ifdef COLUMN + case VJUSTIFY_REQUEST: + vjustify(); + break; +#endif /* COLUMN */ + default: + assert(0); + break; + } + suppress_next = 1; + break; + } + case token::TOKEN_SPACE: + { + if (possibly_handle_first_page_transition()) + ; + else if (bol && !curenv->get_prev_line_interrupted()) { + int nspaces = 0; + do { + nspaces += tok.nspaces(); + tok.next(); + } while (tok.space()); + if (tok.newline()) + trapping_blank_line(); + else { + push_token(tok); + curenv->do_break(); + curenv->add_node(new hmotion_node(curenv->get_space_width() + * nspaces)); + bol = 0; + } + } + else { + curenv->space(); + bol = 0; + } + break; + } + case token::TOKEN_EOF: + return; + case token::TOKEN_NODE: + { + if (possibly_handle_first_page_transition()) + ; + else if (tok.nd->reread(&bol)) { + delete tok.nd; + tok.nd = 0; + } + else { + curenv->add_node(tok.nd); + tok.nd = 0; + bol = 0; + curenv->possibly_break_line(1); + } + break; + } + case token::TOKEN_PAGE_EJECTOR: + { + continue_page_eject(); + // I think we just want to preserve bol. + // bol = 1; + break; + } + case token::TOKEN_BEGIN_TRAP: + { + trap_bol_stack.push(bol); + bol = 1; + break; + } + case token::TOKEN_END_TRAP: + { + if (trap_bol_stack.is_empty()) + error("spurious end trap token detected!"); + else + bol = trap_bol_stack.pop(); + + /* I'm not totally happy about this. But I can't think of any other + way to do it. Doing an output_pending_lines() whenever a + TOKEN_END_TRAP is detected doesn't work: for example, + + .wh -1i x + .de x + 'bp + .. + .wh -.5i y + .de y + .tl ''-%-'' + .. + .br + .ll .5i + .sp |\n(.pu-1i-.5v + a\%very\%very\%long\%word + + will print all but the first lines from the word immediately + after the footer, rather than on the next page. */ + + if (trap_bol_stack.is_empty()) + curenv->output_pending_lines(); + break; + } + default: + { + bol = 0; + tok.process(); + break; + } + } + if (!suppress_next) + tok.next(); + trap_sprung_flag = 0; + } +} + +#ifdef WIDOW_CONTROL + +void flush_pending_lines() +{ + while (!tok.newline() && !tok.eof()) + tok.next(); + curenv->output_pending_lines(); + tok.next(); +} + +#endif /* WIDOW_CONTROL */ + +request_or_macro::request_or_macro() +{ +} + +macro *request_or_macro::to_macro() +{ + return 0; +} + +request::request(REQUEST_FUNCP pp) : p(pp) +{ +} + +void request::invoke(symbol) +{ + (*p)(); +} + +struct char_block { + enum { SIZE = 128 }; + unsigned char s[SIZE]; + char_block *next; + char_block(); +}; + +char_block::char_block() +: next(0) +{ +} + +class char_list { +public: + char_list(); + ~char_list(); + void append(unsigned char); + int length(); +private: + unsigned char *ptr; + int len; + char_block *head; + char_block *tail; + friend class macro_header; + friend class string_iterator; +}; + +char_list::char_list() +: ptr(0), len(0), head(0), tail(0) +{ +} + +char_list::~char_list() +{ + while (head != 0) { + char_block *tem = head; + head = head->next; + delete tem; + } +} + +int char_list::length() +{ + return len; +} + +void char_list::append(unsigned char c) +{ + if (tail == 0) { + head = tail = new char_block; + ptr = tail->s; + } + else { + if (ptr >= tail->s + char_block::SIZE) { + tail->next = new char_block; + tail = tail->next; + ptr = tail->s; + } + } + *ptr++ = c; + len++; +} + +class node_list { + node *head; + node *tail; +public: + node_list(); + ~node_list(); + void append(node *); + int length(); + node *extract(); + + friend class macro_header; + friend class string_iterator; +}; + +void node_list::append(node *n) +{ + if (head == 0) { + n->next = 0; + head = tail = n; + } + else { + n->next = 0; + tail = tail->next = n; + } +} + +int node_list::length() +{ + int total = 0; + for (node *n = head; n != 0; n = n->next) + ++total; + return total; +} + +node_list::node_list() +{ + head = tail = 0; +} + +node *node_list::extract() +{ + node *temp = head; + head = tail = 0; + return temp; +} + +node_list::~node_list() +{ + delete_node_list(head); +} + +struct macro_header { +public: + int count; + char_list cl; + node_list nl; + macro_header() { count = 1; } + macro_header *copy(int); +}; + +macro::~macro() +{ + if (p != 0 && --(p->count) <= 0) + delete p; +} + +macro::macro() +{ + if (!input_stack::get_location(1, &filename, &lineno)) { + filename = 0; + lineno = 0; + } + length = 0; + p = 0; +} + +macro::macro(const macro &m) +: p(m.p), filename(m.filename), lineno(m.lineno), length(m.length) +{ + if (p != 0) + p->count++; +} + +macro ¯o::operator=(const macro &m) +{ + // don't assign object + if (m.p != 0) + m.p->count++; + if (p != 0 && --(p->count) <= 0) + delete p; + p = m.p; + filename = m.filename; + lineno = m.lineno; + length = m.length; + return *this; +} + +void macro::append(unsigned char c) +{ + assert(c != 0); + if (p == 0) + p = new macro_header; + if (p->cl.length() != length) { + macro_header *tem = p->copy(length); + if (--(p->count) <= 0) + delete p; + p = tem; + } + p->cl.append(c); + ++length; +} + +void macro::append_str(const char *s) +{ + int i = 0; + + if (s) { + while (s[i] != (char)0) { + append(s[i]); + i++; + } + } +} + +void macro::append(node *n) +{ + assert(n != 0); + if (p == 0) + p = new macro_header; + if (p->cl.length() != length) { + macro_header *tem = p->copy(length); + if (--(p->count) <= 0) + delete p; + p = tem; + } + p->cl.append(0); + p->nl.append(n); + ++length; +} + +void macro::append_unsigned(unsigned int i) +{ + unsigned int j = i / 10; + if (j != 0) + append_unsigned(j); + append(((unsigned char)(((int)'0') + i % 10))); +} + +void macro::append_int(int i) +{ + if (i < 0) { + append('-'); + i = -i; + } + append_unsigned((unsigned int)i); +} + +void macro::print_size() +{ + errprint("%1", length); +} + +// make a copy of the first n bytes + +macro_header *macro_header::copy(int n) +{ + macro_header *p = new macro_header; + char_block *bp = cl.head; + unsigned char *ptr = bp->s; + node *nd = nl.head; + while (--n >= 0) { + if (ptr >= bp->s + char_block::SIZE) { + bp = bp->next; + ptr = bp->s; + } + int c = *ptr++; + p->cl.append(c); + if (c == 0) { + p->nl.append(nd->copy()); + nd = nd->next; + } + } + return p; +} + +void print_macros() +{ + object_dictionary_iterator iter(request_dictionary); + request_or_macro *rm; + symbol s; + while (iter.get(&s, (object **)&rm)) { + assert(!s.is_null()); + macro *m = rm->to_macro(); + if (m) { + errprint("%1\t", s.contents()); + m->print_size(); + errprint("\n"); + } + } + fflush(stderr); + skip_line(); +} + +class string_iterator : public input_iterator { + macro mac; + const char *how_invoked; + int newline_flag; + int lineno; + char_block *bp; + int count; // of characters remaining + node *nd; +protected: + symbol nm; + string_iterator(); +public: + string_iterator(const macro &m, const char *p = 0, symbol s = NULL_SYMBOL); + int fill(node **); + int peek(); + int get_location(int, const char **, int *); + void backtrace(); +}; + +string_iterator::string_iterator(const macro &m, const char *p, symbol s) +: mac(m), how_invoked(p), newline_flag(0), lineno(1), nm(s) +{ + count = mac.length; + if (count != 0) { + bp = mac.p->cl.head; + nd = mac.p->nl.head; + ptr = eptr = bp->s; + } + else { + bp = 0; + nd = 0; + ptr = eptr = 0; + } +} + +string_iterator::string_iterator() +{ + bp = 0; + nd = 0; + ptr = eptr = 0; + newline_flag = 0; + how_invoked = 0; + lineno = 1; + count = 0; +} + +int string_iterator::fill(node **np) +{ + if (newline_flag) + lineno++; + newline_flag = 0; + if (count <= 0) + return EOF; + const unsigned char *p = eptr; + if (p >= bp->s + char_block::SIZE) { + bp = bp->next; + p = bp->s; + } + if (*p == '\0') { + if (np) + *np = nd->copy(); + nd = nd->next; + eptr = ptr = p + 1; + count--; + return 0; + } + const unsigned char *e = bp->s + char_block::SIZE; + if (e - p > count) + e = p + count; + ptr = p; + while (p < e) { + unsigned char c = *p; + if (c == '\n' || c == ESCAPE_NEWLINE) { + newline_flag = 1; + p++; + break; + } + if (c == '\0') + break; + p++; + } + eptr = p; + count -= p - ptr; + return *ptr++; +} + +int string_iterator::peek() +{ + if (count <= 0) + return EOF; + const unsigned char *p = eptr; + if (count <= 0) + return EOF; + if (p >= bp->s + char_block::SIZE) { + p = bp->next->s; + } + return *p; +} + +int string_iterator::get_location(int allow_macro, + const char **filep, int *linep) +{ + if (!allow_macro) + return 0; + if (mac.filename == 0) + return 0; + *filep = mac.filename; + *linep = mac.lineno + lineno - 1; + return 1; +} + +void string_iterator::backtrace() +{ + if (mac.filename) { + errprint("%1:%2: backtrace", mac.filename, mac.lineno + lineno - 1); + if (how_invoked) { + if (!nm.is_null()) + errprint(": %1 `%2'\n", how_invoked, nm.contents()); + else + errprint(": %1\n", how_invoked); + } + else + errprint("\n"); + } +} + +class temp_iterator : public input_iterator { + unsigned char *base; + temp_iterator(const char *, int len); +public: + ~temp_iterator(); + friend input_iterator *make_temp_iterator(const char *); +}; + +#ifdef __GNUG__ +inline +#endif +temp_iterator::temp_iterator(const char *s, int len) +{ + base = new unsigned char[len]; + memcpy(base, s, len); + ptr = base; + eptr = base + len; +} + +temp_iterator::~temp_iterator() +{ + a_delete base; +} + +class small_temp_iterator : public input_iterator { +private: + small_temp_iterator(const char *, int); + ~small_temp_iterator(); + enum { BLOCK = 16 }; + static small_temp_iterator *free_list; + void *operator new(size_t); + void operator delete(void *); + enum { SIZE = 12 }; + unsigned char buf[SIZE]; + friend input_iterator *make_temp_iterator(const char *); +}; + +small_temp_iterator *small_temp_iterator::free_list = 0; + +void *small_temp_iterator::operator new(size_t n) +{ + assert(n == sizeof(small_temp_iterator)); + if (!free_list) { + free_list = + (small_temp_iterator *)new char[sizeof(small_temp_iterator)*BLOCK]; + for (int i = 0; i < BLOCK - 1; i++) + free_list[i].next = free_list + i + 1; + free_list[BLOCK-1].next = 0; + } + small_temp_iterator *p = free_list; + free_list = (small_temp_iterator *)(free_list->next); + p->next = 0; + return p; +} + +#ifdef __GNUG__ +inline +#endif +void small_temp_iterator::operator delete(void *p) +{ + if (p) { + ((small_temp_iterator *)p)->next = free_list; + free_list = (small_temp_iterator *)p; + } +} + +small_temp_iterator::~small_temp_iterator() +{ +} + +#ifdef __GNUG__ +inline +#endif +small_temp_iterator::small_temp_iterator(const char *s, int len) +{ + for (int i = 0; i < len; i++) + buf[i] = s[i]; + ptr = buf; + eptr = buf + len; +} + +input_iterator *make_temp_iterator(const char *s) +{ + if (s == 0) + return new small_temp_iterator(s, 0); + else { + int n = strlen(s); + if (n <= small_temp_iterator::SIZE) + return new small_temp_iterator(s, n); + else + return new temp_iterator(s, n); + } +} + +// this is used when macros are interpolated using the .macro_name notation + +struct arg_list { + macro mac; + arg_list *next; + arg_list(const macro &); + ~arg_list(); +}; + +arg_list::arg_list(const macro &m) : mac(m), next(0) +{ +} + +arg_list::~arg_list() +{ +} + +class macro_iterator : public string_iterator { + arg_list *args; + int argc; +public: + macro_iterator(symbol, macro &, const char *how_invoked = "macro"); + macro_iterator(); + ~macro_iterator(); + int has_args() { return 1; } + input_iterator *get_arg(int i); + int nargs() { return argc; } + void add_arg(const macro &m); + void shift(int n); + int is_macro() { return 1; } +}; + +input_iterator *macro_iterator::get_arg(int i) +{ + if (i == 0) + return make_temp_iterator(nm.contents()); + if (i > 0 && i <= argc) { + arg_list *p = args; + for (int j = 1; j < i; j++) { + assert(p != 0); + p = p->next; + } + return new string_iterator(p->mac); + } + else + return 0; +} + +void macro_iterator::add_arg(const macro &m) +{ + arg_list **p; + for (p = &args; *p; p = &((*p)->next)) + ; + *p = new arg_list(m); + ++argc; +} + +void macro_iterator::shift(int n) +{ + while (n > 0 && argc > 0) { + arg_list *tem = args; + args = args->next; + delete tem; + --argc; + --n; + } +} + +// This gets used by eg .if '\?xxx\?''. + +int operator==(const macro &m1, const macro &m2) +{ + if (m1.length != m2.length) + return 0; + string_iterator iter1(m1); + string_iterator iter2(m2); + int n = m1.length; + while (--n >= 0) { + node *nd1 = 0; + int c1 = iter1.get(&nd1); + assert(c1 != EOF); + node *nd2 = 0; + int c2 = iter2.get(&nd2); + assert(c2 != EOF); + if (c1 != c2) { + if (c1 == 0) + delete nd1; + else if (c2 == 0) + delete nd2; + return 0; + } + if (c1 == 0) { + assert(nd1 != 0); + assert(nd2 != 0); + int are_same = nd1->type() == nd2->type() && nd1->same(nd2); + delete nd1; + delete nd2; + if (!are_same) + return 0; + } + } + return 1; +} + +static void interpolate_macro(symbol nm) +{ + request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm); + if (p == 0) { + int warned = 0; + const char *s = nm.contents(); + if (strlen(s) > 2) { + request_or_macro *r; + char buf[3]; + buf[0] = s[0]; + buf[1] = s[1]; + buf[2] = '\0'; + r = (request_or_macro *)request_dictionary.lookup(symbol(buf)); + if (r) { + macro *m = r->to_macro(); + if (!m || !m->empty()) + warned = warning(WARN_SPACE, + "`%1' not defined (probable missing space after `%2')", + nm.contents(), buf); + } + } + if (!warned) { + warning(WARN_MAC, "`%1' not defined", nm.contents()); + p = new macro; + request_dictionary.define(nm, p); + } + } + if (p) + p->invoke(nm); + else { + skip_line(); + return; + } +} + +static void decode_args(macro_iterator *mi) +{ + if (!tok.newline() && !tok.eof()) { + node *n; + int c = get_copy(&n); + for (;;) { + while (c == ' ') + c = get_copy(&n); + if (c == '\n' || c == EOF) + break; + macro arg; + int quote_input_level = 0; + int done_tab_warning = 0; + if (c == '\"') { + quote_input_level = input_stack::get_level(); + c = get_copy(&n); + } + while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) { + if (quote_input_level > 0 && c == '\"' + && (compatible_flag + || input_stack::get_level() == quote_input_level)) { + c = get_copy(&n); + if (c == '"') { + arg.append(c); + c = get_copy(&n); + } + else + break; + } + else { + if (c == 0) + arg.append(n); + else { + if (c == '\t' && quote_input_level == 0 && !done_tab_warning) { + warning(WARN_TAB, "tab character in unquoted macro argument"); + done_tab_warning = 1; + } + arg.append(c); + } + c = get_copy(&n); + } + } + mi->add_arg(arg); + } + } +} + +void macro::invoke(symbol nm) +{ + macro_iterator *mi = new macro_iterator(nm, *this); + decode_args(mi); + input_stack::push(mi); + tok.next(); +} + +macro *macro::to_macro() +{ + return this; +} + +int macro::empty() +{ + return length == 0; +} + +macro_iterator::macro_iterator(symbol s, macro &m, const char *how_invoked) +: string_iterator(m, how_invoked, s), args(0), argc(0) +{ +} + +macro_iterator::macro_iterator() : args(0), argc(0) +{ +} + +macro_iterator::~macro_iterator() +{ + while (args != 0) { + arg_list *tem = args; + args = args->next; + delete tem; + } +} + +int trap_sprung_flag = 0; +int postpone_traps_flag = 0; +symbol postponed_trap; + +void spring_trap(symbol nm) +{ + assert(!nm.is_null()); + trap_sprung_flag = 1; + if (postpone_traps_flag) { + postponed_trap = nm; + return; + } + static char buf[2] = { BEGIN_TRAP, 0 }; + static char buf2[2] = { END_TRAP, '\0' }; + input_stack::push(make_temp_iterator(buf2)); + request_or_macro *p = lookup_request(nm); + macro *m = p->to_macro(); + if (m) + input_stack::push(new macro_iterator(nm, *m, "trap-invoked macro")); + else + error("you can't invoke a request with a trap"); + input_stack::push(make_temp_iterator(buf)); +} + +void postpone_traps() +{ + postpone_traps_flag = 1; +} + +int unpostpone_traps() +{ + postpone_traps_flag = 0; + if (!postponed_trap.is_null()) { + spring_trap(postponed_trap); + postponed_trap = NULL_SYMBOL; + return 1; + } + else + return 0; +} + +void read_request() +{ + macro_iterator *mi = new macro_iterator; + int reading_from_terminal = isatty(fileno(stdin)); + int had_prompt = 0; + if (!tok.newline() && !tok.eof()) { + int c = get_copy(NULL); + while (c == ' ') + c = get_copy(NULL); + while (c != EOF && c != '\n' && c != ' ') { + if (!illegal_input_char(c)) { + if (reading_from_terminal) + fputc(c, stderr); + had_prompt = 1; + } + c = get_copy(NULL); + } + if (c == ' ') { + tok.make_space(); + decode_args(mi); + } + } + if (reading_from_terminal) { + fputc(had_prompt ? ':' : '\a', stderr); + fflush(stderr); + } + input_stack::push(mi); + macro mac; + int nl = 0; + int c; + while ((c = getchar()) != EOF) { + if (illegal_input_char(c)) + warning(WARN_INPUT, "illegal input character code %1", int(c)); + else { + if (c == '\n') { + if (nl) + break; + else + nl = 1; + } + else + nl = 0; + mac.append(c); + } + } + if (reading_from_terminal) + clearerr(stdin); + input_stack::push(new string_iterator(mac)); + tok.next(); +} + +void do_define_string(int append) +{ + symbol nm; + node *n; + int c; + nm = get_name(1); + if (nm.is_null()) { + skip_line(); + return; + } + if (tok.newline()) + c = '\n'; + else if (tok.tab()) + c = '\t'; + else if (!tok.space()) { + error("bad string definition"); + skip_line(); + return; + } + else + c = get_copy(&n); + while (c == ' ') + c = get_copy(&n); + if (c == '"') + c = get_copy(&n); + macro mac; + request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm); + macro *mm = rm ? rm->to_macro() : 0; + if (append && mm) + mac = *mm; + while (c != '\n' && c != EOF) { + if (c == 0) + mac.append(n); + else + mac.append((unsigned char)c); + c = get_copy(&n); + } + if (!mm) { + mm = new macro; + request_dictionary.define(nm, mm); + } + *mm = mac; + tok.next(); +} + +void define_string() +{ + do_define_string(0); +} + +void append_string() +{ + do_define_string(1); +} + +void define_character() +{ + node *n; + int c; + tok.skip(); + charinfo *ci = tok.get_char(1); + if (ci == 0) { + skip_line(); + return; + } + tok.next(); + if (tok.newline()) + c = '\n'; + else if (tok.tab()) + c = '\t'; + else if (!tok.space()) { + error("bad character definition"); + skip_line(); + return; + } + else + c = get_copy(&n); + while (c == ' ' || c == '\t') + c = get_copy(&n); + if (c == '"') + c = get_copy(&n); + macro *m = new macro; + while (c != '\n' && c != EOF) { + if (c == 0) + m->append(n); + else + m->append((unsigned char)c); + c = get_copy(&n); + } + m = ci->set_macro(m); + if (m) + delete m; + tok.next(); +} + +static void remove_character() +{ + tok.skip(); + while (!tok.newline() && !tok.eof()) { + if (!tok.space() && !tok.tab()) { + charinfo *ci = tok.get_char(1); + if (!ci) + break; + macro *m = ci->set_macro(0); + if (m) + delete m; + } + tok.next(); + } + skip_line(); +} + +static void interpolate_string(symbol nm) +{ + request_or_macro *p = lookup_request(nm); + macro *m = p->to_macro(); + if (!m) + error("you can only invoke a string using \\*"); + else { + string_iterator *si = new string_iterator(*m, "string", nm); + input_stack::push(si); + } +} + +/* This class is used for the implementation of \$@. It is used for +each of the closing double quotes. It artificially increases the +input level by 2, so that the closing double quote will appear to have +the same input level as the opening quote. */ + +class end_quote_iterator : public input_iterator { + unsigned char buf[1]; +public: + end_quote_iterator(); + ~end_quote_iterator() { } + int internal_level() { return 2; } +}; + +end_quote_iterator::end_quote_iterator() +{ + buf[0] = '"'; + ptr = buf; + eptr = buf + 1; +} + +static void interpolate_arg(symbol nm) +{ + const char *s = nm.contents(); + if (!s || *s == '\0') + copy_mode_error("missing argument name"); + else if (s[1] == 0 && csdigit(s[0])) + input_stack::push(input_stack::get_arg(s[0] - '0')); + else if (s[0] == '*' && s[1] == '\0') { + for (int i = input_stack::nargs(); i > 0; i--) { + input_stack::push(input_stack::get_arg(i)); + if (i != 1) + input_stack::push(make_temp_iterator(" ")); + } + } + else if (s[0] == '@' && s[1] == '\0') { + for (int i = input_stack::nargs(); i > 0; i--) { + input_stack::push(new end_quote_iterator); + input_stack::push(input_stack::get_arg(i)); + input_stack::push(make_temp_iterator(i == 1 ? "\"" : " \"")); + } + } + else { + const char *p; + for (p = s; *p && csdigit(*p); p++) + ; + if (*p) + copy_mode_error("bad argument name `%1'", s); + else + input_stack::push(input_stack::get_arg(atoi(s))); + } +} + +void handle_first_page_transition() +{ + push_token(tok); + topdiv->begin_page(); +} + +// We push back a token by wrapping it up in a token_node, and +// wrapping that up in a string_iterator. + +static void push_token(const token &t) +{ + macro m; + m.append(new token_node(t)); + input_stack::push(new string_iterator(m)); +} + +void push_page_ejector() +{ + static char buf[2] = { PAGE_EJECTOR, '\0' }; + input_stack::push(make_temp_iterator(buf)); +} + +void handle_initial_request(unsigned char code) +{ + char buf[2]; + buf[0] = code; + buf[1] = '\0'; + macro mac; + mac.append(new token_node(tok)); + input_stack::push(new string_iterator(mac)); + input_stack::push(make_temp_iterator(buf)); + topdiv->begin_page(); + tok.next(); +} + +void handle_initial_title() +{ + handle_initial_request(TITLE_REQUEST); +} + +// this should be local to define_macro, but cfront 1.2 doesn't support that +static symbol dot_symbol("."); + +enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE }; + +void do_define_macro(define_mode mode, int indirect) +{ + symbol nm, term; + if (indirect) { + symbol temp1 = get_name(1); + if (temp1.is_null()) { + skip_line(); + return; + } + symbol temp2 = get_name(); + input_stack::push(make_temp_iterator("\n")); + if (!temp2.is_null()) { + interpolate_string(temp2); + input_stack::push(make_temp_iterator(" ")); + } + interpolate_string(temp1); + input_stack::push(make_temp_iterator(" ")); + tok.next(); + } + if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) { + nm = get_name(1); + if (nm.is_null()) { + skip_line(); + return; + } + } + term = get_name(); // the request that terminates the definition + if (term.is_null()) + term = dot_symbol; + while (!tok.newline() && !tok.eof()) + tok.next(); + const char *start_filename; + int start_lineno; + int have_start_location = input_stack::get_location(0, &start_filename, + &start_lineno); + node *n; + // doing this here makes the line numbers come out right + int c = get_copy(&n, 1); + macro mac; + macro *mm = 0; + if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) { + request_or_macro *rm = + (request_or_macro *)request_dictionary.lookup(nm); + if (rm) + mm = rm->to_macro(); + if (mm && mode == DEFINE_APPEND) + mac = *mm; + } + int bol = 1; + for (;;) { + while (c == ESCAPE_NEWLINE) { + if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) + mac.append(c); + c = get_copy(&n, 1); + } + if (bol && c == '.') { + const char *s = term.contents(); + int d; + // see if it matches term + int i; + for (i = 0; s[i] != 0; i++) { + d = get_copy(&n); + if ((unsigned char)s[i] != d) + break; + } + if (s[i] == 0 + && ((i == 2 && compatible_flag) + || (d = get_copy(&n)) == ' ' + || d == '\n')) { // we found it + if (d == '\n') + tok.make_newline(); + else + tok.make_space(); + if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) { + if (!mm) { + mm = new macro; + request_dictionary.define(nm, mm); + } + *mm = mac; + } + if (term != dot_symbol) { + ignoring = 0; + interpolate_macro(term); + } + else + skip_line(); + return; + } + if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) { + mac.append(c); + for (int j = 0; j < i; j++) + mac.append(s[j]); + } + c = d; + } + if (c == EOF) { + if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) { + if (have_start_location) + error_with_file_and_line(start_filename, start_lineno, + "end of file while defining macro `%1'", + nm.contents()); + else + error("end of file while defining macro `%1'", nm.contents()); + } + else { + if (have_start_location) + error_with_file_and_line(start_filename, start_lineno, + "end of file while ignoring input lines"); + else + error("end of file while ignoring input lines"); + } + tok.next(); + return; + } + if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) { + if (c == 0) + mac.append(n); + else + mac.append(c); + } + bol = (c == '\n'); + c = get_copy(&n, 1); + } +} + +void define_macro() +{ + do_define_macro(DEFINE_NORMAL, 0); +} + +void define_indirect_macro() +{ + do_define_macro(DEFINE_NORMAL, 1); +} + +void append_macro() +{ + do_define_macro(DEFINE_APPEND, 0); +} + +void ignore() +{ + ignoring = 1; + do_define_macro(DEFINE_IGNORE, 0); + ignoring = 0; +} + +void remove_macro() +{ + for (;;) { + symbol s = get_name(); + if (s.is_null()) + break; + request_dictionary.remove(s); + } + skip_line(); +} + +void rename_macro() +{ + symbol s1 = get_name(1); + if (!s1.is_null()) { + symbol s2 = get_name(1); + if (!s2.is_null()) + request_dictionary.rename(s1, s2); + } + skip_line(); +} + +void alias_macro() +{ + symbol s1 = get_name(1); + if (!s1.is_null()) { + symbol s2 = get_name(1); + if (!s2.is_null()) { + if (!request_dictionary.alias(s1, s2)) + warning(WARN_MAC, "`%1' not defined", s2.contents()); + } + } + skip_line(); +} + +void chop_macro() +{ + symbol s = get_name(1); + if (!s.is_null()) { + request_or_macro *p = lookup_request(s); + macro *m = p->to_macro(); + if (!m) + error("cannot chop request"); + else if (m->length == 0) + error("cannot chop empty macro"); + else + m->length -= 1; + } + skip_line(); +} + +void substring_macro() +{ + int start; + symbol s = get_name(1); + if (!s.is_null() && get_integer(&start)) { + request_or_macro *p = lookup_request(s); + macro *m = p->to_macro(); + if (!m) + error("cannot substring request"); + else { + if (start <= 0) + start += m->length; + else + start--; + int end = 0; + if (!has_arg() || get_integer(&end)) { + if (end <= 0) + end += m->length; + else + end--; + if (start > end) { + int tem = start; + start = end; + end = tem; + } + if (start >= m->length || start == end) { + m->length = 0; + if (m->p) { + if (--(m->p->count) <= 0) + delete m->p; + m->p = 0; + } + } + else if (start == 0) + m->length = end; + else { + string_iterator iter(*m); + int i; + for (i = 0; i < start; i++) + if (iter.get(0) == EOF) + break; + macro mac; + for (; i < end; i++) { + node *nd; + int c = iter.get(&nd); + if (c == EOF) + break; + if (c == 0) + mac.append(nd); + else + mac.append((unsigned char)c); + } + *m = mac; + } + } + } + } + skip_line(); +} + +void length_macro() +{ + symbol ret; + ret = get_name(1); + if (ret.is_null()) { + skip_line(); + return; + } + int c; + node *n; + if (tok.newline()) + c = '\n'; + else if (tok.tab()) + c = '\t'; + else if (!tok.space()) { + error("bad string definition"); + skip_line(); + return; + } + else + c = get_copy(&n); + while (c == ' ') + c = get_copy(&n); + if (c == '"') + c = get_copy(&n); + int len = 0; + while (c != '\n' && c != EOF) { + ++len; + c = get_copy(&n); + } + tok.next(); + reg *r = (reg*)number_reg_dictionary.lookup(ret); + if (r) + r->set_value(len); + else + set_number_reg(ret, len); +} + +void asciify_macro() +{ + symbol s = get_name(1); + if (!s.is_null()) { + request_or_macro *p = lookup_request(s); + macro *m = p->to_macro(); + if (!m) + error("cannot asciify request"); + else { + macro am; + string_iterator iter(*m); + for (;;) { + node *nd; + int c = iter.get(&nd); + if (c == EOF) + break; + if (c != 0) + am.append(c); + else + nd->asciify(&am); + } + *m = am; + } + } + skip_line(); +} + +void unformat_macro() +{ + symbol s = get_name(1); + if (!s.is_null()) { + request_or_macro *p = lookup_request(s); + macro *m = p->to_macro(); + if (!m) + error("cannot unformat request"); + else { + macro am; + string_iterator iter(*m); + for (;;) { + node *nd; + int c = iter.get(&nd); + if (c == EOF) + break; + if (c != 0) + am.append(c); + else { + if (nd->set_unformat_flag()) + am.append(nd); + } + } + *m = am; + } + } + skip_line(); +} + +static void interpolate_environment_variable(symbol nm) +{ + const char *s = getenv(nm.contents()); + if (s && *s) + input_stack::push(make_temp_iterator(s)); +} + +void interpolate_number_reg(symbol nm, int inc) +{ + reg *r = lookup_number_reg(nm); + if (inc < 0) + r->decrement(); + else if (inc > 0) + r->increment(); + input_stack::push(make_temp_iterator(r->get_string())); +} + +static void interpolate_number_format(symbol nm) +{ + reg *r = (reg *)number_reg_dictionary.lookup(nm); + if (r) + input_stack::push(make_temp_iterator(r->get_format())); +} + +static int get_delim_number(units *n, int si, int prev_value) +{ + token start; + start.next(); + if (start.delimiter(1)) { + tok.next(); + if (get_number(n, si, prev_value)) { + if (start != tok) + warning(WARN_DELIM, "closing delimiter does not match"); + return 1; + } + } + return 0; +} + +static int get_delim_number(units *n, int si) +{ + token start; + start.next(); + if (start.delimiter(1)) { + tok.next(); + if (get_number(n, si)) { + if (start != tok) + warning(WARN_DELIM, "closing delimiter does not match"); + return 1; + } + } + return 0; +} + +static int get_line_arg(units *n, int si, charinfo **cp) +{ + token start; + start.next(); + if (start.delimiter(1)) { + tok.next(); + if (get_number(n, si)) { + if (tok.dummy() || tok.transparent_dummy()) + tok.next(); + if (start != tok) { + *cp = tok.get_char(1); + tok.next(); + } + if (start != tok) + warning(WARN_DELIM, "closing delimiter does not match"); + return 1; + } + } + return 0; +} + +static int read_size(int *x) +{ + tok.next(); + int c = tok.ch(); + int inc = 0; + if (c == '-') { + inc = -1; + tok.next(); + c = tok.ch(); + } + else if (c == '+') { + inc = 1; + tok.next(); + c = tok.ch(); + } + int val; + int bad = 0; + if (c == '(') { + tok.next(); + c = tok.ch(); + if (!inc) { + // allow an increment either before or after the left parenthesis + if (c == '-') { + inc = -1; + tok.next(); + c = tok.ch(); + } + else if (c == '+') { + inc = 1; + tok.next(); + c = tok.ch(); + } + } + if (!csdigit(c)) + bad = 1; + else { + val = c - '0'; + tok.next(); + c = tok.ch(); + if (!csdigit(c)) + bad = 1; + else { + val = val*10 + (c - '0'); + val *= sizescale; + } + } + } + else if (csdigit(c)) { + val = c - '0'; + if (!inc && c != '0' && c < '4') { + tok.next(); + c = tok.ch(); + if (!csdigit(c)) + bad = 1; + else + val = val*10 + (c - '0'); + } + val *= sizescale; + } + else if (!tok.delimiter(1)) + return 0; + else { + token start(tok); + tok.next(); + if (!(inc + ? get_number(&val, 'z') + : get_number(&val, 'z', curenv->get_requested_point_size()))) + return 0; + if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) { + if (start.ch() == '[') + error("missing `]'"); + else + error("missing closing delimiter"); + return 0; + } + } + if (!bad) { + switch (inc) { + case 0: + *x = val; + break; + case 1: + *x = curenv->get_requested_point_size() + val; + break; + case -1: + *x = curenv->get_requested_point_size() - val; + break; + default: + assert(0); + } + return 1; + } + else { + error("bad digit in point size"); + return 0; + } +} + +static symbol get_delim_name() +{ + token start; + start.next(); + if (start.eof()) { + error("end of input at start of delimited name"); + return NULL_SYMBOL; + } + if (start.newline()) { + error("can't delimit name with a newline"); + return NULL_SYMBOL; + } + int start_level = input_stack::get_level(); + char abuf[ABUF_SIZE]; + char *buf = abuf; + int buf_size = ABUF_SIZE; + int i = 0; + for (;;) { + if (i + 1 > buf_size) { + if (buf == abuf) { + buf = new char[ABUF_SIZE*2]; + memcpy(buf, abuf, buf_size); + buf_size = ABUF_SIZE*2; + } + else { + char *old_buf = buf; + buf = new char[buf_size*2]; + memcpy(buf, old_buf, buf_size); + buf_size *= 2; + a_delete old_buf; + } + } + tok.next(); + if (tok == start + && (compatible_flag || input_stack::get_level() == start_level)) + break; + if ((buf[i] = tok.ch()) == 0) { + error("missing delimiter (got %1)", tok.description()); + if (buf != abuf) + a_delete buf; + return NULL_SYMBOL; + } + i++; + } + buf[i] = '\0'; + if (buf == abuf) { + if (i == 0) { + error("empty delimited name"); + return NULL_SYMBOL; + } + else + return symbol(buf); + } + else { + symbol s(buf); + a_delete buf; + return s; + } +} + +// Implement \R + +static void do_register() +{ + token start; + start.next(); + if (!start.delimiter(1)) + return; + tok.next(); + symbol nm = get_long_name(1); + if (nm.is_null()) + return; + while (tok.space()) + tok.next(); + reg *r = (reg *)number_reg_dictionary.lookup(nm); + int prev_value; + if (!r || !r->get_value(&prev_value)) + prev_value = 0; + int val; + if (!get_number(&val, 'u', prev_value)) + return; + if (start != tok) + warning(WARN_DELIM, "closing delimiter does not match"); + if (r) + r->set_value(val); + else + set_number_reg(nm, val); +} + +// this implements the \w escape sequence + +static void do_width() +{ + token start; + start.next(); + int start_level = input_stack::get_level(); + environment env(curenv); + environment *oldenv = curenv; + curenv = &env; + for (;;) { + tok.next(); + if (tok.eof()) { + warning(WARN_DELIM, "missing closing delimiter"); + break; + } + if (tok.newline()) { + warning(WARN_DELIM, "missing closing delimiter"); + input_stack::push(make_temp_iterator("\n")); + break; + } + if (tok == start + && (compatible_flag || input_stack::get_level() == start_level)) + break; + tok.process(); + } + env.wrap_up_tab(); + units x = env.get_input_line_position().to_units(); + input_stack::push(make_temp_iterator(i_to_a(x))); + env.width_registers(); + curenv = oldenv; +} + +charinfo *page_character; + +void set_page_character() +{ + page_character = get_optional_char(); + skip_line(); +} + +static const symbol percent_symbol("%"); + +void read_title_parts(node **part, hunits *part_width) +{ + tok.skip(); + if (tok.newline() || tok.eof()) + return; + token start(tok); + int start_level = input_stack::get_level(); + tok.next(); + for (int i = 0; i < 3; i++) { + while (!tok.newline() && !tok.eof()) { + if (tok == start + && (compatible_flag || input_stack::get_level() == start_level)) { + tok.next(); + break; + } + if (page_character != 0 && tok.get_char() == page_character) + interpolate_number_reg(percent_symbol, 0); + else + tok.process(); + tok.next(); + } + curenv->wrap_up_tab(); + part_width[i] = curenv->get_input_line_position(); + part[i] = curenv->extract_output_line(); + } + while (!tok.newline() && !tok.eof()) + tok.next(); +} + +class non_interpreted_node : public node { + macro mac; +public: + non_interpreted_node(const macro &); + int interpret(macro *); + node *copy(); + int same(node *); + const char *type(); + int force_tprint(); +}; + +non_interpreted_node::non_interpreted_node(const macro &m) : mac(m) +{ +} + +int non_interpreted_node::same(node *nd) +{ + return mac == ((non_interpreted_node *)nd)->mac; +} + +const char *non_interpreted_node::type() +{ + return "non_interpreted_node"; +} + +int non_interpreted_node::force_tprint() +{ + return 0; +} + +node *non_interpreted_node::copy() +{ + return new non_interpreted_node(mac); +} + +int non_interpreted_node::interpret(macro *m) +{ + string_iterator si(mac); + node *n; + for (;;) { + int c = si.get(&n); + if (c == EOF) + break; + if (c == 0) + m->append(n); + else + m->append(c); + } + return 1; +} + +static node *do_non_interpreted() +{ + node *n; + int c; + macro mac; + while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n') + if (c == 0) + mac.append(n); + else + mac.append(c); + if (c == EOF || c == '\n') { + error("missing \\?"); + return 0; + } + return new non_interpreted_node(mac); +} + +static void encode_char(macro *mac, char c) +{ + if (c == '\0') { + if ((font::use_charnames_in_special) && tok.special()) { + charinfo *ci = tok.get_char(1); + const char *s = ci->get_symbol()->contents(); + if (s[0] != (char)0) { + mac->append('\\'); + mac->append('('); + int i = 0; + while (s[i] != (char)0) { + mac->append(s[i]); + i++; + } + mac->append('\\'); + mac->append(')'); + } + } + else { + error("%1 is illegal within \\X", tok.description()); + } + } + else { + if ((font::use_charnames_in_special) && (c == '\\')) { + /* + * add escape escape sequence + */ + mac->append(c); + } + mac->append(c); + } +} + +node *do_special() +{ + token start; + start.next(); + int start_level = input_stack::get_level(); + macro mac; + for (tok.next(); + tok != start || input_stack::get_level() != start_level; + tok.next()) { + if (tok.eof()) { + warning(WARN_DELIM, "missing closing delimiter"); + return 0; + } + if (tok.newline()) { + input_stack::push(make_temp_iterator("\n")); + warning(WARN_DELIM, "missing closing delimiter"); + break; + } + unsigned char c; + if (tok.space()) + c = ' '; + else if (tok.tab()) + c = '\t'; + else if (tok.leader()) + c = '\001'; + else if (tok.backspace()) + c = '\b'; + else + c = tok.ch(); + encode_char(&mac, c); + } + return new special_node(mac); +} + +node *do_suppress() +{ + tok.next(); + int c = tok.ch(); + switch (c) { + case '0': + if (begin_level == 1) + return new suppress_node(0, 0); + break; + case '1': + if (begin_level == 1) + return new suppress_node(1, 0); + break; + case '2': + if (begin_level == 1) + return new suppress_node(1, 1); + break; + case '3': + begin_level++; + break; + case '4': + begin_level--; + break; + case '5': { + symbol filename = get_delim_name(); + if (begin_level == 1) + return new suppress_node(filename, 'i'); + return 0; + break; + } + default: + error("`%1' is an invalid argument to \\O", char(c)); + } + return 0; +} + +void special_node::tprint(troff_output_file *out) +{ + tprint_start(out); + string_iterator iter(mac); + for (;;) { + int c = iter.get(NULL); + if (c == EOF) + break; + for (const char *s = ::asciify(c); *s; s++) + tprint_char(out, *s); + } + tprint_end(out); +} + +int get_file_line(const char **filename, int *lineno) +{ + return input_stack::get_location(0, filename, lineno); +} + +void line_file() +{ + int n; + if (get_integer(&n)) { + const char *filename = 0; + if (has_arg()) { + symbol s = get_long_name(); + filename = s.contents(); + } + (void)input_stack::set_location(filename, n-1); + } + skip_line(); +} + +static int nroff_mode = 0; + +static void nroff_request() +{ + nroff_mode = 1; + skip_line(); +} + +static void troff_request() +{ + nroff_mode = 0; + skip_line(); +} + +static void skip_alternative() +{ + int level = 0; + // ensure that ``.if 0\{'' works as expected + if (tok.left_brace()) + level++; + int c; + for (;;) { + c = input_stack::get(NULL); + if (c == EOF) + break; + if (c == ESCAPE_LEFT_BRACE) + ++level; + else if (c == ESCAPE_RIGHT_BRACE) + --level; + else if (c == escape_char && escape_char > 0) + switch(input_stack::get(NULL)) { + case '{': + ++level; + break; + case '}': + --level; + break; + case '"': + while ((c = input_stack::get(NULL)) != '\n' && c != EOF) + ; + } + /* + Note that the level can properly be < 0, eg + + .if 1 \{\ + .if 0 \{\ + .\}\} + + So don't give an error message in this case. + */ + if (level <= 0 && c == '\n') + break; + } + tok.next(); +} + +static void begin_alternative() +{ + while (tok.space() || tok.left_brace()) + tok.next(); +} + +void nop_request() +{ + while (tok.space()) + tok.next(); +} + +static int_stack if_else_stack; + +int do_if_request() +{ + int invert = 0; + while (tok.space()) + tok.next(); + while (tok.ch() == '!') { + tok.next(); + invert = !invert; + } + int result; + unsigned char c = tok.ch(); + if (c == 't') { + tok.next(); + result = !nroff_mode; + } + else if (c == 'n') { + tok.next(); + result = nroff_mode; + } + else if (c == 'v') { + tok.next(); + result = 0; + } + else if (c == 'o') { + result = (topdiv->get_page_number() & 1); + tok.next(); + } + else if (c == 'e') { + result = !(topdiv->get_page_number() & 1); + tok.next(); + } + else if (c == 'd' || c == 'r') { + tok.next(); + symbol nm = get_name(1); + if (nm.is_null()) { + skip_alternative(); + return 0; + } + result = (c == 'd' + ? request_dictionary.lookup(nm) != 0 + : number_reg_dictionary.lookup(nm) != 0); + } + else if (c == 'c') { + tok.next(); + tok.skip(); + charinfo *ci = tok.get_char(1); + if (ci == 0) { + skip_alternative(); + return 0; + } + result = character_exists(ci, curenv); + tok.next(); + } + else if (tok.space()) + result = 0; + else if (tok.delimiter()) { + token delim = tok; + int delim_level = input_stack::get_level(); + environment env1(curenv); + environment env2(curenv); + environment *oldenv = curenv; + curenv = &env1; + for (int i = 0; i < 2; i++) { + for (;;) { + tok.next(); + if (tok.newline() || tok.eof()) { + warning(WARN_DELIM, "missing closing delimiter"); + tok.next(); + curenv = oldenv; + return 0; + } + if (tok == delim + && (compatible_flag || input_stack::get_level() == delim_level)) + break; + tok.process(); + } + curenv = &env2; + } + node *n1 = env1.extract_output_line(); + node *n2 = env2.extract_output_line(); + result = same_node_list(n1, n2); + delete_node_list(n1); + delete_node_list(n2); + curenv = oldenv; + tok.next(); + } + else { + units n; + if (!get_number(&n, 'u')) { + skip_alternative(); + return 0; + } + else + result = n > 0; + } + if (invert) + result = !result; + if (result) + begin_alternative(); + else + skip_alternative(); + return result; +} + +void if_else_request() +{ + if_else_stack.push(do_if_request()); +} + +void if_request() +{ + do_if_request(); +} + +void else_request() +{ + if (if_else_stack.is_empty()) { + warning(WARN_EL, "unbalanced .el request"); + skip_alternative(); + } + else { + if (if_else_stack.pop()) + skip_alternative(); + else + begin_alternative(); + } +} + +/* + * begin - if this is the outermost html_begin request then execute the + * rest of the line, else skip line + */ + +void begin() +{ + begin_level++; + if (begin_level == 1) + begin_alternative(); + else + skip_alternative(); +} + +/* + * end - if this is the outermost html_end request then execute the + * rest of the line, else skip line + */ + +void end() +{ + begin_level--; + if (begin_level == 0) + begin_alternative(); + else + skip_alternative(); + if (begin_level < 0) + begin_level = 0; +} + +/* + * image - implements the directive `.image {l|r|c|i} filename' + * which places the filename into a node which is later + * written out + * + * . either as a special in the form of an image tag for -Thtml + * . or as an image region definition for all other devices + * + */ + +void image() +{ + if (has_arg()) { + char position = tok.ch(); + if (!(position == 'l' + || position == 'r' + || position == 'c' + || position == 'i')) { + error("l, r, c, or i expected (got %1)", tok.description()); + position = 'c'; + } + tok.next(); + symbol filename = get_long_name(1); + if (!filename.is_null()) + curenv->add_node(new suppress_node(filename, position)); + } + skip_line(); +} + +static int while_depth = 0; +static int while_break_flag = 0; + +void while_request() +{ + macro mac; + int escaped = 0; + int level = 0; + mac.append(new token_node(tok)); + for (;;) { + node *n; + int c = input_stack::get(&n); + if (c == EOF) + break; + if (c == 0) { + escaped = 0; + mac.append(n); + } + else if (escaped) { + if (c == '{') + level += 1; + else if (c == '}') + level -= 1; + escaped = 0; + mac.append(c); + } + else { + if (c == ESCAPE_LEFT_BRACE) + level += 1; + else if (c == ESCAPE_RIGHT_BRACE) + level -= 1; + else if (c == escape_char) + escaped = 1; + mac.append(c); + if (c == '\n' && level <= 0) + break; + } + } + if (level != 0) + error("unbalanced \\{ \\}"); + else { + while_depth++; + input_stack::add_boundary(); + for (;;) { + input_stack::push(new string_iterator(mac, "while loop")); + tok.next(); + if (!do_if_request()) { + while (input_stack::get(NULL) != EOF) + ; + break; + } + process_input_stack(); + if (while_break_flag || input_stack::is_return_boundary()) { + while_break_flag = 0; + break; + } + } + input_stack::remove_boundary(); + while_depth--; + } + tok.next(); +} + +void while_break_request() +{ + if (!while_depth) { + error("no while loop"); + skip_line(); + } + else { + while_break_flag = 1; + while (input_stack::get(NULL) != EOF) + ; + tok.next(); + } +} + +void while_continue_request() +{ + if (!while_depth) { + error("no while loop"); + skip_line(); + } + else { + while (input_stack::get(NULL) != EOF) + ; + tok.next(); + } +} + +// .so + +void source() +{ + symbol nm = get_long_name(1); + if (nm.is_null()) + skip_line(); + else { + while (!tok.newline() && !tok.eof()) + tok.next(); + errno = 0; + FILE *fp = fopen(nm.contents(), "r"); + if (fp) + input_stack::push(new file_iterator(fp, nm.contents())); + else + error("can't open `%1': %2", nm.contents(), strerror(errno)); + tok.next(); + } +} + +// like .so but use popen() + +void pipe_source() +{ + if (safer_flag) { + error(".pso request not allowed in safer mode"); + skip_line(); + } + else { +#ifdef POPEN_MISSING + error("pipes not available on this system"); + skip_line(); +#else /* not POPEN_MISSING */ + if (tok.newline() || tok.eof()) + error("missing command"); + else { + int c; + while ((c = get_copy(NULL)) == ' ' || c == '\t') + ; + int buf_size = 24; + char *buf = new char[buf_size]; + int buf_used = 0; + for (; c != '\n' && c != EOF; c = get_copy(NULL)) { + const char *s = asciify(c); + int slen = strlen(s); + if (buf_used + slen + 1> buf_size) { + char *old_buf = buf; + int old_buf_size = buf_size; + buf_size *= 2; + buf = new char[buf_size]; + memcpy(buf, old_buf, old_buf_size); + a_delete old_buf; + } + strcpy(buf + buf_used, s); + buf_used += slen; + } + buf[buf_used] = '\0'; + errno = 0; + FILE *fp = popen(buf, POPEN_RT); + if (fp) + input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1)); + else + error("can't open pipe to process `%1': %2", buf, strerror(errno)); + a_delete buf; + } + tok.next(); +#endif /* not POPEN_MISSING */ + } +} + +// .psbb + +static int llx_reg_contents = 0; +static int lly_reg_contents = 0; +static int urx_reg_contents = 0; +static int ury_reg_contents = 0; + +struct bounding_box { + int llx, lly, urx, ury; +}; + +/* Parse the argument to a %%BoundingBox comment. Return 1 if it +contains 4 numbers, 2 if it contains (atend), 0 otherwise. */ + +int parse_bounding_box(char *p, bounding_box *bb) +{ + if (sscanf(p, "%d %d %d %d", + &bb->llx, &bb->lly, &bb->urx, &bb->ury) == 4) + return 1; + else { + /* The Document Structuring Conventions say that the numbers + should be integers. Unfortunately some broken applications + get this wrong. */ + double x1, x2, x3, x4; + if (sscanf(p, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) { + bb->llx = (int)x1; + bb->lly = (int)x2; + bb->urx = (int)x3; + bb->ury = (int)x4; + return 1; + } + else { + for (; *p == ' ' || *p == '\t'; p++) + ; + if (strncmp(p, "(atend)", 7) == 0) { + return 2; + } + } + } + bb->llx = bb->lly = bb->urx = bb->ury = 0; + return 0; +} + +// This version is taken from psrm.cc + +#define PS_LINE_MAX 255 +cset white_space("\n\r \t"); + +int ps_get_line(char *buf, FILE *fp, const char* filename) +{ + int c = getc(fp); + if (c == EOF) { + buf[0] = '\0'; + return 0; + } + int i = 0; + int err = 0; + while (c != '\r' && c != '\n' && c != EOF) { + if ((c < 0x1b && !white_space(c)) || c == 0x7f) + error("illegal input character code %1 in `%2'", int(c), filename); + else if (i < PS_LINE_MAX) + buf[i++] = c; + else if (!err) { + err = 1; + error("PostScript file `%1' is non-conforming " + "because length of line exceeds 255", filename); + } + c = getc(fp); + } + buf[i++] = '\n'; + buf[i] = '\0'; + if (c == '\r') { + c = getc(fp); + if (c != EOF && c != '\n') + ungetc(c, fp); + } + return 1; +} + +inline void assign_registers(int llx, int lly, int urx, int ury) +{ + llx_reg_contents = llx; + lly_reg_contents = lly; + urx_reg_contents = urx; + ury_reg_contents = ury; +} + +void do_ps_file(FILE *fp, const char* filename) +{ + bounding_box bb; + int bb_at_end = 0; + char buf[PS_LINE_MAX]; + llx_reg_contents = lly_reg_contents = + urx_reg_contents = ury_reg_contents = 0; + if (!ps_get_line(buf, fp, filename)) { + error("`%1' is empty", filename); + return; + } + if (strncmp("%!PS-Adobe-", buf, 11) != 0) { + error("`%1' is not conforming to the Document Structuring Conventions", + filename); + return; + } + while (ps_get_line(buf, fp, filename) != 0) { + if (buf[0] != '%' || buf[1] != '%' + || strncmp(buf + 2, "EndComments", 11) == 0) + break; + if (strncmp(buf + 2, "BoundingBox:", 12) == 0) { + int res = parse_bounding_box(buf + 14, &bb); + if (res == 1) { + assign_registers(bb.llx, bb.lly, bb.urx, bb.ury); + return; + } else if (res == 2) { + bb_at_end = 1; + break; + } + else { + error("the arguments to the %%%%BoundingBox comment in `%1' are bad", + filename); + return; + } + } + } + if (bb_at_end) { + long offset; + int last_try = 0; + /* in the trailer, the last BoundingBox comment is significant */ + for (offset = 512; !last_try; offset *= 2) { + int had_trailer = 0; + int got_bb = 0; + if (offset > 32768 || fseek(fp, -offset, 2) == -1) { + last_try = 1; + if (fseek(fp, 0L, 0) == -1) + break; + } + while (ps_get_line(buf, fp, filename) != 0) { + if (buf[0] == '%' && buf[1] == '%') { + if (!had_trailer) { + if (strncmp(buf + 2, "Trailer", 7) == 0) + had_trailer = 1; + } + else { + if (strncmp(buf + 2, "BoundingBox:", 12) == 0) { + int res = parse_bounding_box(buf + 14, &bb); + if (res == 1) + got_bb = 1; + else if (res == 2) { + error("`(atend)' not allowed in trailer of `%1'", filename); + return; + } + else { + error("the arguments to the %%%%BoundingBox comment in `%1' are bad", + filename); + return; + } + } + } + } + } + if (got_bb) { + assign_registers(bb.llx, bb.lly, bb.urx, bb.ury); + return; + } + } + } + error("%%%%BoundingBox comment not found in `%1'", filename); +} + +void ps_bbox_request() +{ + symbol nm = get_long_name(1); + if (nm.is_null()) + skip_line(); + else { + while (!tok.newline() && !tok.eof()) + tok.next(); + errno = 0; + // PS files might contain non-printable characters, such as ^Z + // and CRs not followed by an LF, so open them in binary mode. + FILE *fp = fopen(nm.contents(), FOPEN_RB); + if (fp) { + do_ps_file(fp, nm.contents()); + fclose(fp); + } + else + error("can't open `%1': %2", nm.contents(), strerror(errno)); + tok.next(); + } +} + +const char *asciify(int c) +{ + static char buf[3]; + buf[0] = escape_char == '\0' ? '\\' : escape_char; + buf[1] = buf[2] = '\0'; + switch (c) { + case ESCAPE_QUESTION: + buf[1] = '?'; + break; + case ESCAPE_AMPERSAND: + buf[1] = '&'; + break; + case ESCAPE_RIGHT_PARENTHESIS: + buf[1] = ')'; + break; + case ESCAPE_UNDERSCORE: + buf[1] = '_'; + break; + case ESCAPE_BAR: + buf[1] = '|'; + break; + case ESCAPE_CIRCUMFLEX: + buf[1] = '^'; + break; + case ESCAPE_LEFT_BRACE: + buf[1] = '{'; + break; + case ESCAPE_RIGHT_BRACE: + buf[1] = '}'; + break; + case ESCAPE_LEFT_QUOTE: + buf[1] = '`'; + break; + case ESCAPE_RIGHT_QUOTE: + buf[1] = '\''; + break; + case ESCAPE_HYPHEN: + buf[1] = '-'; + break; + case ESCAPE_BANG: + buf[1] = '!'; + break; + case ESCAPE_c: + buf[1] = 'c'; + break; + case ESCAPE_e: + buf[1] = 'e'; + break; + case ESCAPE_E: + buf[1] = 'E'; + break; + case ESCAPE_PERCENT: + buf[1] = '%'; + break; + case ESCAPE_SPACE: + buf[1] = ' '; + break; + case ESCAPE_TILDE: + buf[1] = '~'; + break; + case ESCAPE_COLON: + buf[1] = ':'; + break; + default: + if (illegal_input_char(c)) + buf[0] = '\0'; + else + buf[0] = c; + break; + } + return buf; +} + +const char *input_char_description(int c) +{ + switch (c) { + case '\n': + return "a newline character"; + case '\b': + return "a backspace character"; + case '\001': + return "a leader character"; + case '\t': + return "a tab character"; + case ' ': + return "a space character"; + case '\0': + return "a node"; + } + static char buf[sizeof("magic character code ") + 1 + INT_DIGITS]; + if (illegal_input_char(c)) { + const char *s = asciify(c); + if (*s) { + buf[0] = '`'; + strcpy(buf + 1, s); + strcat(buf, "'"); + return buf; + } + sprintf(buf, "magic character code %d", c); + return buf; + } + if (csprint(c)) { + buf[0] = '`'; + buf[1] = c; + buf[2] = '\''; + return buf; + } + sprintf(buf, "character code %d", c); + return buf; +} + +// .tm, .tm1, and .tmc + +void do_terminal(int newline, int string_like) +{ + if (!tok.newline() && !tok.eof()) { + int c; + for (;;) { + c = get_copy(NULL); + if (string_like && c == '"') { + c = get_copy(NULL); + break; + } + if (c != ' ' && c != '\t') + break; + } + for (; c != '\n' && c != EOF; c = get_copy(NULL)) + fputs(asciify(c), stderr); + } + if (newline) + fputc('\n', stderr); + fflush(stderr); + tok.next(); +} + +void terminal() +{ + do_terminal(1, 0); +} + +void terminal1() +{ + do_terminal(1, 1); +} + +void terminal_continue() +{ + do_terminal(0, 1); +} + +dictionary stream_dictionary(20); + +void do_open(int append) +{ + symbol stream = get_name(1); + if (!stream.is_null()) { + symbol filename = get_long_name(1); + if (!filename.is_null()) { + errno = 0; + FILE *fp = fopen(filename.contents(), append ? "a" : "w"); + if (!fp) { + error("can't open `%1' for %2: %3", + filename.contents(), + append ? "appending" : "writing", + strerror(errno)); + fp = (FILE *)stream_dictionary.remove(stream); + } + else + fp = (FILE *)stream_dictionary.lookup(stream, fp); + if (fp) + fclose(fp); + } + } + skip_line(); +} + +void open_request() +{ + if (safer_flag) { + error(".open request not allowed in safer mode"); + skip_line(); + } + else + do_open(0); +} + +void opena_request() +{ + if (safer_flag) { + error(".opena request not allowed in safer mode"); + skip_line(); + } + else + do_open(1); +} + +void close_request() +{ + symbol stream = get_name(1); + if (!stream.is_null()) { + FILE *fp = (FILE *)stream_dictionary.remove(stream); + if (!fp) + error("no stream named `%1'", stream.contents()); + else + fclose(fp); + } + skip_line(); +} + +void write_request() +{ + symbol stream = get_name(1); + if (stream.is_null()) { + skip_line(); + return; + } + FILE *fp = (FILE *)stream_dictionary.lookup(stream); + if (!fp) { + error("no stream named `%1'", stream.contents()); + skip_line(); + return; + } + int c; + while ((c = get_copy(NULL)) == ' ') + ; + if (c == '"') + c = get_copy(NULL); + for (; c != '\n' && c != EOF; c = get_copy(NULL)) + fputs(asciify(c), fp); + fputc('\n', fp); + fflush(fp); + tok.next(); +} + +void write_macro_request() +{ + symbol stream = get_name(1); + if (stream.is_null()) { + skip_line(); + return; + } + FILE *fp = (FILE *)stream_dictionary.lookup(stream); + if (!fp) { + error("no stream named `%1'", stream.contents()); + skip_line(); + return; + } + symbol s = get_name(1); + if (s.is_null()) { + skip_line(); + return; + } + request_or_macro *p = lookup_request(s); + macro *m = p->to_macro(); + if (!m) + error("cannot write request"); + else { + string_iterator iter(*m); + for (;;) { + int c = iter.get(0); + if (c == EOF) + break; + fputs(asciify(c), fp); + } + fflush(fp); + } + skip_line(); +} + +static void init_charset_table() +{ + char buf[16]; + strcpy(buf, "char"); + for (int i = 0; i < 256; i++) { + strcpy(buf + 4, i_to_a(i)); + charset_table[i] = get_charinfo(symbol(buf)); + charset_table[i]->set_ascii_code(i); + if (csalpha(i)) + charset_table[i]->set_hyphenation_code(cmlower(i)); + } + charset_table['.']->set_flags(charinfo::ENDS_SENTENCE); + charset_table['?']->set_flags(charinfo::ENDS_SENTENCE); + charset_table['!']->set_flags(charinfo::ENDS_SENTENCE); + charset_table['-']->set_flags(charinfo::BREAK_AFTER); + charset_table['"']->set_flags(charinfo::TRANSPARENT); + charset_table['\'']->set_flags(charinfo::TRANSPARENT); + charset_table[')']->set_flags(charinfo::TRANSPARENT); + charset_table[']']->set_flags(charinfo::TRANSPARENT); + charset_table['*']->set_flags(charinfo::TRANSPARENT); + get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT); + get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT); + get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER); + get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY); + get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY); + get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY); + get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY); + get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY); + page_character = charset_table['%']; +} + +static void do_translate(int translate_transparent) +{ + tok.skip(); + while (!tok.newline() && !tok.eof()) { + if (tok.space()) { + // This is a really bizarre troff feature. + tok.next(); + translate_space_to_dummy = tok.dummy(); + if (tok.newline() || tok.eof()) + break; + tok.next(); + continue; + } + charinfo *ci1 = tok.get_char(1); + if (ci1 == 0) + break; + tok.next(); + if (tok.newline() || tok.eof()) { + ci1->set_special_translation(charinfo::TRANSLATE_SPACE, + translate_transparent); + break; + } + if (tok.space()) + ci1->set_special_translation(charinfo::TRANSLATE_SPACE, + translate_transparent); + else if (tok.stretchable_space()) + ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE, + translate_transparent); + else if (tok.dummy()) + ci1->set_special_translation(charinfo::TRANSLATE_DUMMY, + translate_transparent); + else if (tok.hyphen_indicator()) + ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR, + translate_transparent); + else { + charinfo *ci2 = tok.get_char(1); + if (ci2 == 0) + break; + if (ci1 == ci2) + ci1->set_translation(0, translate_transparent); + else + ci1->set_translation(ci2, translate_transparent); + } + tok.next(); + } + skip_line(); +} + +void translate() +{ + do_translate(1); +} + +void translate_no_transparent() +{ + do_translate(0); +} + +void char_flags() +{ + int flags; + if (get_integer(&flags)) + while (has_arg()) { + charinfo *ci = tok.get_char(1); + if (ci) { + charinfo *tem = ci->get_translation(); + if (tem) + ci = tem; + ci->set_flags(flags); + } + tok.next(); + } + skip_line(); +} + +void hyphenation_code() +{ + tok.skip(); + while (!tok.newline() && !tok.eof()) { + charinfo *ci = tok.get_char(1); + if (ci == 0) + break; + tok.next(); + tok.skip(); + unsigned char c = tok.ch(); + if (c == 0) { + error("hyphenation code must be ordinary character"); + break; + } + if (csdigit(c)) { + error("hyphenation code cannot be digit"); + break; + } + ci->set_hyphenation_code(c); + tok.next(); + tok.skip(); + } + skip_line(); +} + +charinfo *token::get_char(int required) +{ + if (type == TOKEN_CHAR) + return charset_table[c]; + if (type == TOKEN_SPECIAL) + return get_charinfo(nm); + if (type == TOKEN_NUMBERED_CHAR) + return get_charinfo_by_number(val); + if (type == TOKEN_ESCAPE) { + if (escape_char != 0) + return charset_table[escape_char]; + else { + error("`\\e' used while no current escape character"); + return 0; + } + } + if (required) { + if (type == TOKEN_EOF || type == TOKEN_NEWLINE) + warning(WARN_MISSING, "missing normal or special character"); + else + error("normal or special character expected (got %1)", description()); + } + return 0; +} + +charinfo *get_optional_char() +{ + while (tok.space()) + tok.next(); + charinfo *ci = tok.get_char(); + if (!ci) + check_missing_character(); + else + tok.next(); + return ci; +} + +void check_missing_character() +{ + if (!tok.newline() && !tok.eof() && !tok.right_brace() && !tok.tab()) + error("normal or special character expected (got %1): " + "treated as missing", + tok.description()); +} + +// this is for \Z + +int token::add_to_node_list(node **pp) +{ + hunits w; + int s; + node *n = 0; + switch (type) { + case TOKEN_CHAR: + *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s); + break; + case TOKEN_DUMMY: + n = new dummy_node; + break; + case TOKEN_ESCAPE: + if (escape_char != 0) + *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s); + break; + case TOKEN_HYPHEN_INDICATOR: + *pp = (*pp)->add_discretionary_hyphen(); + break; + case TOKEN_ITALIC_CORRECTION: + *pp = (*pp)->add_italic_correction(&w); + break; + case TOKEN_LEFT_BRACE: + break; + case TOKEN_MARK_INPUT: + set_number_reg(nm, curenv->get_input_line_position().to_units()); + break; + case TOKEN_NODE: + n = nd; + nd = 0; + break; + case TOKEN_NUMBERED_CHAR: + *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s); + break; + case TOKEN_RIGHT_BRACE: + break; + case TOKEN_SPACE: + n = new hmotion_node(curenv->get_space_width()); + break; + case TOKEN_SPECIAL: + *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s); + break; + case TOKEN_STRETCHABLE_SPACE: + n = new unbreakable_space_node(curenv->get_space_width()); + break; + case TOKEN_TRANSPARENT_DUMMY: + n = new transparent_dummy_node; + break; + default: + return 0; + } + if (n) { + n->next = *pp; + *pp = n; + } + return 1; +} + +void token::process() +{ + if (possibly_handle_first_page_transition()) + return; + switch (type) { + case TOKEN_BACKSPACE: + curenv->add_node(new hmotion_node(-curenv->get_space_width())); + break; + case TOKEN_CHAR: + curenv->add_char(charset_table[c]); + break; + case TOKEN_DUMMY: + curenv->add_node(new dummy_node); + break; + case TOKEN_EMPTY: + assert(0); + break; + case TOKEN_EOF: + assert(0); + break; + case TOKEN_ESCAPE: + if (escape_char != 0) + curenv->add_char(charset_table[escape_char]); + break; + case TOKEN_BEGIN_TRAP: + case TOKEN_END_TRAP: + case TOKEN_PAGE_EJECTOR: + // these are all handled in process_input_stack() + break; + case TOKEN_HYPHEN_INDICATOR: + curenv->add_hyphen_indicator(); + break; + case TOKEN_INTERRUPT: + curenv->interrupt(); + break; + case TOKEN_ITALIC_CORRECTION: + curenv->add_italic_correction(); + break; + case TOKEN_LEADER: + curenv->handle_tab(1); + break; + case TOKEN_LEFT_BRACE: + break; + case TOKEN_MARK_INPUT: + set_number_reg(nm, curenv->get_input_line_position().to_units()); + break; + case TOKEN_NEWLINE: + curenv->newline(); + break; + case TOKEN_NODE: + curenv->add_node(nd); + nd = 0; + break; + case TOKEN_NUMBERED_CHAR: + curenv->add_char(get_charinfo_by_number(val)); + break; + case TOKEN_REQUEST: + // handled in process_input_stack + break; + case TOKEN_RIGHT_BRACE: + break; + case TOKEN_SPACE: + curenv->space(); + break; + case TOKEN_SPECIAL: + curenv->add_char(get_charinfo(nm)); + break; + case TOKEN_SPREAD: + curenv->spread(); + break; + case TOKEN_STRETCHABLE_SPACE: + curenv->add_node(new unbreakable_space_node(curenv->get_space_width())); + break; + case TOKEN_TAB: + curenv->handle_tab(0); + break; + case TOKEN_TRANSPARENT: + break; + case TOKEN_TRANSPARENT_DUMMY: + curenv->add_node(new transparent_dummy_node); + break; + default: + assert(0); + } +} + +class nargs_reg : public reg { +public: + const char *get_string(); +}; + +const char *nargs_reg::get_string() +{ + return i_to_a(input_stack::nargs()); +} + +class lineno_reg : public reg { +public: + const char *get_string(); +}; + +const char *lineno_reg::get_string() +{ + int line; + const char *file; + if (!input_stack::get_location(0, &file, &line)) + line = 0; + return i_to_a(line); +} + +class writable_lineno_reg : public general_reg { +public: + writable_lineno_reg(); + void set_value(units); + int get_value(units *); +}; + +writable_lineno_reg::writable_lineno_reg() +{ +} + +int writable_lineno_reg::get_value(units *res) +{ + int line; + const char *file; + if (!input_stack::get_location(0, &file, &line)) + return 0; + *res = line; + return 1; +} + +void writable_lineno_reg::set_value(units n) +{ + input_stack::set_location(0, n); +} + +class filename_reg : public reg { +public: + const char *get_string(); +}; + +const char *filename_reg::get_string() +{ + int line; + const char *file; + if (input_stack::get_location(0, &file, &line)) + return file; + else + return 0; +} + +class constant_reg : public reg { + const char *s; +public: + constant_reg(const char *); + const char *get_string(); +}; + +constant_reg::constant_reg(const char *p) : s(p) +{ +} + +const char *constant_reg::get_string() +{ + return s; +} + +constant_int_reg::constant_int_reg(int *q) : p(q) +{ +} + +const char *constant_int_reg::get_string() +{ + return i_to_a(*p); +} + +void abort_request() +{ + int c; + if (tok.eof()) + c = EOF; + else if (tok.newline()) + c = '\n'; + else { + while ((c = get_copy(0)) == ' ') + ; + } + if (c == EOF || c == '\n') + fputs("User Abort.", stderr); + else { + for (; c != '\n' && c != EOF; c = get_copy(NULL)) + fputs(asciify(c), stderr); + } + fputc('\n', stderr); + cleanup_and_exit(1); +} + +char *read_string() +{ + int len = 256; + char *s = new char[len]; + int c; + while ((c = get_copy(0)) == ' ') + ; + int i = 0; + while (c != '\n' && c != EOF) { + if (!illegal_input_char(c)) { + if (i + 2 > len) { + char *tem = s; + s = new char[len*2]; + memcpy(s, tem, len); + len *= 2; + a_delete tem; + } + s[i++] = c; + } + c = get_copy(0); + } + s[i] = '\0'; + tok.next(); + if (i == 0) { + a_delete s; + return 0; + } + return s; +} + +void pipe_output() +{ + if (safer_flag) { + error(".pi request not allowed in safer mode"); + skip_line(); + } + else { +#ifdef POPEN_MISSING + error("pipes not available on this system"); + skip_line(); +#else /* not POPEN_MISSING */ + if (the_output) { + error("can't pipe: output already started"); + skip_line(); + } + else { + if ((pipe_command = read_string()) == 0) + error("can't pipe to empty command"); + } +#endif /* not POPEN_MISSING */ + } +} + +static int system_status; + +void system_request() +{ + if (safer_flag) { + error(".sy request not allowed in safer mode"); + skip_line(); + } + else { + char *command = read_string(); + if (!command) + error("empty command"); + else { + system_status = system(command); + a_delete command; + } + } +} + +void copy_file() +{ + if (curdiv == topdiv && topdiv->before_first_page) { + handle_initial_request(COPY_FILE_REQUEST); + return; + } + symbol filename = get_long_name(1); + while (!tok.newline() && !tok.eof()) + tok.next(); + if (break_flag) + curenv->do_break(); + if (!filename.is_null()) + curdiv->copy_file(filename.contents()); + tok.next(); +} + +#ifdef COLUMN + +void vjustify() +{ + if (curdiv == topdiv && topdiv->before_first_page) { + handle_initial_request(VJUSTIFY_REQUEST); + return; + } + symbol type = get_long_name(1); + if (!type.is_null()) + curdiv->vjustify(type); + skip_line(); +} + +#endif /* COLUMN */ + +void transparent_file() +{ + if (curdiv == topdiv && topdiv->before_first_page) { + handle_initial_request(TRANSPARENT_FILE_REQUEST); + return; + } + symbol filename = get_long_name(1); + while (!tok.newline() && !tok.eof()) + tok.next(); + if (break_flag) + curenv->do_break(); + if (!filename.is_null()) { + errno = 0; + FILE *fp = fopen(filename.contents(), "r"); + if (!fp) + error("can't open `%1': %2", filename.contents(), strerror(errno)); + else { + int bol = 1; + for (;;) { + int c = getc(fp); + if (c == EOF) + break; + if (illegal_input_char(c)) + warning(WARN_INPUT, "illegal input character code %1", int(c)); + else { + curdiv->transparent_output(c); + bol = c == '\n'; + } + } + if (!bol) + curdiv->transparent_output('\n'); + fclose(fp); + } + } + tok.next(); +} + +class page_range { + int first; + int last; +public: + page_range *next; + page_range(int, int, page_range *); + int contains(int n); +}; + +page_range::page_range(int i, int j, page_range *p) +: first(i), last(j), next(p) +{ +} + +int page_range::contains(int n) +{ + return n >= first && (last <= 0 || n <= last); +} + +page_range *output_page_list = 0; + +int in_output_page_list(int n) +{ + if (!output_page_list) + return 1; + for (page_range *p = output_page_list; p; p = p->next) + if (p->contains(n)) + return 1; + return 0; +} + +static void parse_output_page_list(char *p) +{ + for (;;) { + int i; + if (*p == '-') + i = 1; + else if (csdigit(*p)) { + i = 0; + do + i = i*10 + *p++ - '0'; + while (csdigit(*p)); + } + else + break; + int j; + if (*p == '-') { + p++; + j = 0; + if (csdigit(*p)) { + do + j = j*10 + *p++ - '0'; + while (csdigit(*p)); + } + } + else + j = i; + if (j == 0) + last_page_number = -1; + else if (last_page_number >= 0 && j > last_page_number) + last_page_number = j; + output_page_list = new page_range(i, j, output_page_list); + if (*p != ',') + break; + ++p; + } + if (*p != '\0') { + error("bad output page list"); + output_page_list = 0; + } +} + +static FILE *open_mac_file(const char *mac, char **path) +{ + // Try first FOOBAR.tmac, then tmac.FOOBAR + char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1]; + strcpy(s1, mac); + strcat(s1, MACRO_POSTFIX); + FILE *fp = mac_path->open_file(s1, path); + a_delete s1; + if (!fp) { + char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1]; + strcpy(s2, MACRO_PREFIX); + strcat(s2, mac); + fp = mac_path->open_file(s2, path); + a_delete s2; + } + return fp; +} + +static void process_macro_file(const char *mac) +{ + char *path; + FILE *fp = open_mac_file(mac, &path); + if (!fp) + fatal("can't find macro file %1", mac); + const char *s = symbol(path).contents(); + a_delete path; + input_stack::push(new file_iterator(fp, s)); + tok.next(); + process_input_stack(); +} + +static void process_startup_file(char *filename) +{ + char *path; + search_path *orig_mac_path = mac_path; + mac_path = &config_macro_path; + FILE *fp = mac_path->open_file(filename, &path); + if (fp) { + input_stack::push(new file_iterator(fp, symbol(path).contents())); + a_delete path; + tok.next(); + process_input_stack(); + } + mac_path = orig_mac_path; +} + +void macro_source() +{ + symbol nm = get_long_name(1); + if (nm.is_null()) + skip_line(); + else { + while (!tok.newline() && !tok.eof()) + tok.next(); + char *path; + FILE *fp = mac_path->open_file(nm.contents(), &path); + // .mso doesn't (and cannot) go through open_mac_file, so we + // need to do it here manually: If we have tmac.FOOBAR, try + // FOOBAR.tmac and vice versa + if (!fp) { + const char *fn = nm.contents(); + if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) { + char *s = new char[strlen(fn) + sizeof(MACRO_POSTFIX)]; + strcpy(s, fn + sizeof(MACRO_PREFIX) - 1); + strcat(s, MACRO_POSTFIX); + fp = mac_path->open_file(s, &path); + a_delete s; + } + if (!fp) { + if (strncasecmp(fn + strlen(fn) - sizeof(MACRO_POSTFIX) + 1, + MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) { + char *s = new char[strlen(fn) + sizeof(MACRO_PREFIX)]; + strcpy(s, MACRO_PREFIX); + strncat(s, fn, strlen(fn) - sizeof(MACRO_POSTFIX) + 1); + fp = mac_path->open_file(s, &path); + a_delete s; + } + } + } + if (fp) { + input_stack::push(new file_iterator(fp, symbol(path).contents())); + a_delete path; + } + else + error("can't find macro file `%1'", nm.contents()); + tok.next(); + } +} + +static void process_input_file(const char *name) +{ + FILE *fp; + if (strcmp(name, "-") == 0) { + clearerr(stdin); + fp = stdin; + } + else { + errno = 0; + fp = fopen(name, "r"); + if (!fp) + fatal("can't open `%1': %2", name, strerror(errno)); + } + input_stack::push(new file_iterator(fp, name)); + tok.next(); + process_input_stack(); +} + +// make sure the_input is empty before calling this + +static int evaluate_expression(const char *expr, units *res) +{ + input_stack::push(make_temp_iterator(expr)); + tok.next(); + int success = get_number(res, 'u'); + while (input_stack::get(NULL) != EOF) + ; + return success; +} + +static void do_register_assignment(const char *s) +{ + const char *p = strchr(s, '='); + if (!p) { + char buf[2]; + buf[0] = s[0]; + buf[1] = 0; + units n; + if (evaluate_expression(s + 1, &n)) + set_number_reg(buf, n); + } + else { + char *buf = new char[p - s + 1]; + memcpy(buf, s, p - s); + buf[p - s] = 0; + units n; + if (evaluate_expression(p + 1, &n)) + set_number_reg(buf, n); + a_delete buf; + } +} + +static void set_string(const char *name, const char *value) +{ + macro *m = new macro; + for (const char *p = value; *p; p++) + if (!illegal_input_char((unsigned char)*p)) + m->append(*p); + request_dictionary.define(name, m); +} + +static void do_string_assignment(const char *s) +{ + const char *p = strchr(s, '='); + if (!p) { + char buf[2]; + buf[0] = s[0]; + buf[1] = 0; + set_string(buf, s + 1); + } + else { + char *buf = new char[p - s + 1]; + memcpy(buf, s, p - s); + buf[p - s] = 0; + set_string(buf, p + 1); + a_delete buf; + } +} + +struct string_list { + const char *s; + string_list *next; + string_list(const char *ss) : s(ss), next(0) {} +}; + +static void prepend_string(const char *s, string_list **p) +{ + string_list *l = new string_list(s); + l->next = *p; + *p = l; +} + +static void add_string(const char *s, string_list **p) +{ + while (*p) + p = &((*p)->next); + *p = new string_list(s); +} + +void usage(FILE *stream, const char *prog) +{ + fprintf(stream, +"usage: %s -abivzCERU -wname -Wname -dcs -ffam -mname -nnum -olist\n" +" -rcn -Tname -Fdir -Mdir [files...]\n", + prog); +} + +int main(int argc, char **argv) +{ + program_name = argv[0]; + static char stderr_buf[BUFSIZ]; + setbuf(stderr, stderr_buf); + int c; + string_list *macros = 0; + string_list *register_assignments = 0; + string_list *string_assignments = 0; + int iflag = 0; + int tflag = 0; + int fflag = 0; + int nflag = 0; + int no_rc = 0; // don't process troffrc and troffrc-end + int next_page_number; + opterr = 0; + hresolution = vresolution = 1; + // restore $PATH if called from groff + char* groff_path = getenv("GROFF_PATH__"); + if (groff_path) { + string e = "PATH"; + e += '='; + if (*groff_path) + e += groff_path; + e += '\0'; + if (putenv(strsave(e.contents()))) + fatal("putenv failed"); + } + static const struct option long_options[] = { + { "help", no_argument, 0, CHAR_MAX + 1 }, + { "version", no_argument, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + while ((c = getopt_long(argc, argv, "abivw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU", + long_options, NULL)) + != EOF) + switch(c) { + case 'v': + { + extern const char *Version_string; + printf("GNU troff (groff) version %s\n", Version_string); + exit(0); + break; + } + case 'T': + device = optarg; + tflag = 1; + is_html = (strcmp(device, "html") == 0); + break; + case 'C': + compatible_flag = 1; + break; + case 'M': + macro_path.command_line_dir(optarg); + safer_macro_path.command_line_dir(optarg); + config_macro_path.command_line_dir(optarg); + break; + case 'F': + font::command_line_font_dir(optarg); + break; + case 'm': + add_string(optarg, ¯os); + break; + case 'E': + inhibit_errors = 1; + break; + case 'R': + no_rc = 1; + break; + case 'w': + enable_warning(optarg); + break; + case 'W': + disable_warning(optarg); + break; + case 'i': + iflag = 1; + break; + case 'b': + backtrace_flag = 1; + break; + case 'a': + ascii_output_flag = 1; + break; + case 'z': + suppress_output_flag = 1; + break; + case 'n': + if (sscanf(optarg, "%d", &next_page_number) == 1) + nflag++; + else + error("bad page number"); + break; + case 'o': + parse_output_page_list(optarg); + break; + case 'd': + if (*optarg == '\0') + error("`-d' requires non-empty argument"); + else + add_string(optarg, &string_assignments); + break; + case 'r': + if (*optarg == '\0') + error("`-r' requires non-empty argument"); + else + add_string(optarg, ®ister_assignments); + break; + case 'f': + default_family = symbol(optarg); + fflag = 1; + break; + case 'q': + case 's': + case 't': + // silently ignore these + break; + case 'U': + safer_flag = 0; // unsafe behaviour + break; + case CHAR_MAX + 1: // --help + usage(stdout, argv[0]); + exit(0); + break; + case '?': + usage(stderr, argv[0]); + exit(1); + break; // never reached + default: + assert(0); + } + if (!safer_flag) + mac_path = ¯o_path; + set_string(".T", device); + init_charset_table(); + if (!font::load_desc()) + fatal("sorry, I can't continue"); + units_per_inch = font::res; + hresolution = font::hor; + vresolution = font::vert; + sizescale = font::sizescale; + tcommand_flag = font::tcommand; + if (!fflag && font::family != 0 && *font::family != '\0') + default_family = symbol(font::family); + font_size::init_size_table(font::sizes); + int i; + int j = 1; + if (font::style_table) { + for (i = 0; font::style_table[i]; i++) + mount_style(j++, symbol(font::style_table[i])); + } + for (i = 0; font::font_name_table[i]; i++, j++) + // In the DESC file a font name of 0 (zero) means leave this + // position empty. + if (strcmp(font::font_name_table[i], "0") != 0) + mount_font(j, symbol(font::font_name_table[i])); + curdiv = topdiv = new top_level_diversion; + if (nflag) + topdiv->set_next_page_number(next_page_number); + init_input_requests(); + init_env_requests(); + init_div_requests(); +#ifdef COLUMN + init_column_requests(); +#endif /* COLUMN */ + init_node_requests(); + init_markup_requests(); + number_reg_dictionary.define(".T", new constant_reg(tflag ? "1" : "0")); + init_registers(); + init_reg_requests(); + init_hyphen_requests(); + init_environments(); + while (string_assignments) { + do_string_assignment(string_assignments->s); + string_list *tem = string_assignments; + string_assignments = string_assignments->next; + delete tem; + } + while (register_assignments) { + do_register_assignment(register_assignments->s); + string_list *tem = register_assignments; + register_assignments = register_assignments->next; + delete tem; + } + if (!no_rc) + process_startup_file(INITIAL_STARTUP_FILE); + while (macros) { + process_macro_file(macros->s); + string_list *tem = macros; + macros = macros->next; + delete tem; + } + if (!no_rc) + process_startup_file(FINAL_STARTUP_FILE); + for (i = optind; i < argc; i++) + process_input_file(argv[i]); + if (optind >= argc || iflag) + process_input_file("-"); + exit_troff(); + return 0; // not reached +} + +void warn_request() +{ + int n; + if (has_arg() && get_integer(&n)) { + if (n & ~WARN_TOTAL) { + warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL); + n &= WARN_TOTAL; + } + warning_mask = n; + } + else + warning_mask = WARN_TOTAL; + skip_line(); +} + +static void init_registers() +{ +#ifdef LONG_FOR_TIME_T + long +#else /* not LONG_FOR_TIME_T */ + time_t +#endif /* not LONG_FOR_TIME_T */ + t = time(0); + // Use struct here to work around misfeature in old versions of g++. + struct tm *tt = localtime(&t); + set_number_reg("dw", int(tt->tm_wday + 1)); + set_number_reg("dy", int(tt->tm_mday)); + set_number_reg("mo", int(tt->tm_mon + 1)); + set_number_reg("year", int(1900 + tt->tm_year)); + set_number_reg("yr", int(tt->tm_year)); + set_number_reg("$$", getpid()); + number_reg_dictionary.define(".A", + new constant_reg(ascii_output_flag + ? "1" + : "0")); +} + +/* + * registers associated with \O + */ + +static int output_reg_minx_contents = -1; +static int output_reg_miny_contents = -1; +static int output_reg_maxx_contents = -1; +static int output_reg_maxy_contents = -1; + +void check_output_limits(int x, int y) +{ + if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents)) + output_reg_minx_contents = x; + if (x > output_reg_maxx_contents) + output_reg_maxx_contents = x; + if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents)) + output_reg_miny_contents = y; + if (y > output_reg_maxy_contents) + output_reg_maxy_contents = y; +} + +void reset_output_registers(int miny) +{ + // fprintf(stderr, "reset_output_registers\n"); + output_reg_minx_contents = -1; + output_reg_miny_contents = -1; + output_reg_maxx_contents = -1; + output_reg_maxy_contents = -1; +} + +void get_output_registers(int *minx, int *miny, int *maxx, int *maxy) +{ + *minx = output_reg_minx_contents; + *miny = output_reg_miny_contents; + *maxx = output_reg_maxx_contents; + *maxy = output_reg_maxy_contents; +} + +void init_markup_requests() +{ + init_request("begin", begin); + init_request("end", end); + init_request("image", image); +} + +void init_input_requests() +{ + init_request("ds", define_string); + init_request("as", append_string); + init_request("de", define_macro); + init_request("dei", define_indirect_macro); + init_request("am", append_macro); + init_request("ig", ignore); + init_request("rm", remove_macro); + init_request("rn", rename_macro); + init_request("nop", nop_request); + init_request("if", if_request); + init_request("ie", if_else_request); + init_request("el", else_request); + init_request("so", source); + init_request("nx", next_file); + init_request("pm", print_macros); + init_request("eo", escape_off); + init_request("ec", set_escape_char); + init_request("ecs", save_escape_char); + init_request("ecr", restore_escape_char); + init_request("pc", set_page_character); + init_request("tm", terminal); + init_request("tm1", terminal1); + init_request("tmc", terminal_continue); + init_request("ex", exit_request); + init_request("return", return_macro_request); + init_request("em", end_macro); + init_request("blm", blank_line_macro); + init_request("tr", translate); + init_request("trnt", translate_no_transparent); + init_request("ab", abort_request); + init_request("pi", pipe_output); + init_request("cf", copy_file); + init_request("sy", system_request); + init_request("lf", line_file); + init_request("cflags", char_flags); + init_request("shift", shift); + init_request("rd", read_request); + init_request("cp", compatible); + init_request("char", define_character); + init_request("rchar", remove_character); + init_request("hcode", hyphenation_code); + init_request("while", while_request); + init_request("break", while_break_request); + init_request("continue", while_continue_request); + init_request("als", alias_macro); + init_request("backtrace", backtrace_request); + init_request("chop", chop_macro); + init_request("substring", substring_macro); + init_request("length", length_macro); + init_request("asciify", asciify_macro); + init_request("unformat", unformat_macro); + init_request("warn", warn_request); + init_request("open", open_request); + init_request("opena", opena_request); + init_request("close", close_request); + init_request("write", write_request); + init_request("writem", write_macro_request); + init_request("trf", transparent_file); +#ifdef WIDOW_CONTROL + init_request("fpl", flush_pending_lines); +#endif /* WIDOW_CONTROL */ + init_request("nroff", nroff_request); + init_request("troff", troff_request); +#ifdef COLUMN + init_request("vj", vjustify); +#endif /* COLUMN */ + init_request("mso", macro_source); + init_request("do", do_request); +#ifndef POPEN_MISSING + init_request("pso", pipe_source); +#endif /* not POPEN_MISSING */ + init_request("psbb", ps_bbox_request); + number_reg_dictionary.define("systat", new variable_reg(&system_status)); + number_reg_dictionary.define("slimit", + new variable_reg(&input_stack::limit)); + number_reg_dictionary.define(".$", new nargs_reg); + number_reg_dictionary.define(".c", new lineno_reg); + number_reg_dictionary.define("c.", new writable_lineno_reg); + number_reg_dictionary.define(".F", new filename_reg); + number_reg_dictionary.define(".C", new constant_int_reg(&compatible_flag)); + number_reg_dictionary.define(".H", new constant_int_reg(&hresolution)); + number_reg_dictionary.define(".V", new constant_int_reg(&vresolution)); + number_reg_dictionary.define(".R", new constant_reg("10000")); + extern const char *major_version; + number_reg_dictionary.define(".x", new constant_reg(major_version)); + extern const char *minor_version; + number_reg_dictionary.define(".y", new constant_reg(minor_version)); + extern const char *revision; + number_reg_dictionary.define(".Y", new constant_reg(revision)); + number_reg_dictionary.define(".g", new constant_reg("1")); + number_reg_dictionary.define(".warn", new constant_int_reg(&warning_mask)); + number_reg_dictionary.define("llx", new variable_reg(&llx_reg_contents)); + number_reg_dictionary.define("lly", new variable_reg(&lly_reg_contents)); + number_reg_dictionary.define("urx", new variable_reg(&urx_reg_contents)); + number_reg_dictionary.define("ury", new variable_reg(&ury_reg_contents)); + number_reg_dictionary.define("opminx", + new variable_reg(&output_reg_minx_contents)); + number_reg_dictionary.define("opminy", + new variable_reg(&output_reg_miny_contents)); + number_reg_dictionary.define("opmaxx", + new variable_reg(&output_reg_maxx_contents)); + number_reg_dictionary.define("opmaxy", + new variable_reg(&output_reg_maxy_contents)); +} + +object_dictionary request_dictionary(501); + +void init_request(const char *s, REQUEST_FUNCP f) +{ + request_dictionary.define(s, new request(f)); +} + +static request_or_macro *lookup_request(symbol nm) +{ + assert(!nm.is_null()); + request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm); + if (p == 0) { + warning(WARN_MAC, "`%1' not defined", nm.contents()); + p = new macro; + request_dictionary.define(nm, p); + } + return p; +} + +node *charinfo_to_node_list(charinfo *ci, const environment *envp) +{ + // Don't interpret character definitions in compatible mode. + int old_compatible_flag = compatible_flag; + compatible_flag = 0; + int old_escape_char = escape_char; + escape_char = '\\'; + macro *mac = ci->set_macro(0); + assert(mac != 0); + environment *oldenv = curenv; + environment env(envp); + curenv = &env; + curenv->set_composite(); + token old_tok = tok; + input_stack::add_boundary(); + string_iterator *si = + new string_iterator(*mac, "composite character", ci->nm); + input_stack::push(si); + // we don't use process_input_stack, because we don't want to recognise + // requests + for (;;) { + tok.next(); + if (tok.eof()) + break; + if (tok.newline()) { + error("composite character mustn't contain newline"); + while (!tok.eof()) + tok.next(); + break; + } + else + tok.process(); + } + node *n = curenv->extract_output_line(); + input_stack::remove_boundary(); + ci->set_macro(mac); + tok = old_tok; + curenv = oldenv; + compatible_flag = old_compatible_flag; + escape_char = old_escape_char; + return n; +} + +static node *read_draw_node() +{ + token start; + start.next(); + if (!start.delimiter(1)){ + do { + tok.next(); + } while (tok != start && !tok.newline() && !tok.eof()); + } + else { + tok.next(); + if (tok == start) + error("missing argument"); + else { + unsigned char type = tok.ch(); + tok.next(); + int maxpoints = 10; + hvpair *point = new hvpair[maxpoints]; + int npoints = 0; + int no_last_v = 0; + int err = 0; + int i; + for (i = 0; tok != start; i++) { + if (i == maxpoints) { + hvpair *oldpoint = point; + point = new hvpair[maxpoints*2]; + for (int j = 0; j < maxpoints; j++) + point[j] = oldpoint[j]; + maxpoints *= 2; + a_delete oldpoint; + } + if (!get_hunits(&point[i].h, + type == 'f' || type == 't' ? 'u' : 'm')) { + err = 1; + break; + } + ++npoints; + tok.skip(); + point[i].v = V0; + if (tok == start) { + no_last_v = 1; + break; + } + if (!get_vunits(&point[i].v, 'v')) { + err = 1; + break; + } + tok.skip(); + } + while (tok != start && !tok.newline() && !tok.eof()) + tok.next(); + if (!err) { + switch (type) { + case 'l': + if (npoints != 1 || no_last_v) { + error("two arguments needed for line"); + npoints = 1; + } + break; + case 'c': + if (npoints != 1 || !no_last_v) { + error("one argument needed for circle"); + npoints = 1; + point[0].v = V0; + } + break; + case 'e': + if (npoints != 1 || no_last_v) { + error("two arguments needed for ellipse"); + npoints = 1; + } + break; + case 'a': + if (npoints != 2 || no_last_v) { + error("four arguments needed for arc"); + npoints = 2; + } + break; + case '~': + if (no_last_v) + error("even number of arguments needed for spline"); + break; + default: + // silently pass it through + break; + } + draw_node *dn = new draw_node(type, point, npoints, + curenv->get_font_size()); + a_delete point; + return dn; + } + else { + a_delete point; + } + } + } + return 0; +} + +static struct { + const char *name; + int mask; +} warning_table[] = { + { "char", WARN_CHAR }, + { "range", WARN_RANGE }, + { "break", WARN_BREAK }, + { "delim", WARN_DELIM }, + { "el", WARN_EL }, + { "scale", WARN_SCALE }, + { "number", WARN_NUMBER }, + { "syntax", WARN_SYNTAX }, + { "tab", WARN_TAB }, + { "right-brace", WARN_RIGHT_BRACE }, + { "missing", WARN_MISSING }, + { "input", WARN_INPUT }, + { "escape", WARN_ESCAPE }, + { "space", WARN_SPACE }, + { "font", WARN_FONT }, + { "di", WARN_DI }, + { "mac", WARN_MAC }, + { "reg", WARN_REG }, + { "ig", WARN_IG }, + { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) }, + { "w", WARN_TOTAL }, + { "default", DEFAULT_WARNING_MASK }, +}; + +static int lookup_warning(const char *name) +{ + for (int i = 0; + i < sizeof(warning_table)/sizeof(warning_table[0]); + i++) + if (strcmp(name, warning_table[i].name) == 0) + return warning_table[i].mask; + return 0; +} + +static void enable_warning(const char *name) +{ + int mask = lookup_warning(name); + if (mask) + warning_mask |= mask; + else + error("unknown warning `%1'", name); +} + +static void disable_warning(const char *name) +{ + int mask = lookup_warning(name); + if (mask) + warning_mask &= ~mask; + else + error("unknown warning `%1'", name); +} + +static void copy_mode_error(const char *format, + const errarg &arg1, + const errarg &arg2, + const errarg &arg3) +{ + if (ignoring) { + static const char prefix[] = "(in ignored input) "; + char *s = new char[sizeof(prefix) + strlen(format)]; + strcpy(s, prefix); + strcat(s, format); + warning(WARN_IG, s, arg1, arg2, arg3); + a_delete s; + } + else + error(format, arg1, arg2, arg3); +} + +enum error_type { WARNING, ERROR, FATAL }; + +static void do_error(error_type type, + const char *format, + const errarg &arg1, + const errarg &arg2, + const errarg &arg3) +{ + const char *filename; + int lineno; + if (inhibit_errors && type < FATAL) + return; + if (backtrace_flag) + input_stack::backtrace(); + if (!get_file_line(&filename, &lineno)) + filename = 0; + if (filename) + errprint("%1:%2: ", filename, lineno); + else if (program_name) + fprintf(stderr, "%s: ", program_name); + switch (type) { + case FATAL: + fputs("fatal error: ", stderr); + break; + case ERROR: + break; + case WARNING: + fputs("warning: ", stderr); + break; + } + errprint(format, arg1, arg2, arg3); + fputc('\n', stderr); + fflush(stderr); + if (type == FATAL) + cleanup_and_exit(1); +} + +int warning(warning_type t, + const char *format, + const errarg &arg1, + const errarg &arg2, + const errarg &arg3) +{ + if ((t & warning_mask) != 0) { + do_error(WARNING, format, arg1, arg2, arg3); + return 1; + } + else + return 0; +} + +void error(const char *format, + const errarg &arg1, + const errarg &arg2, + const errarg &arg3) +{ + do_error(ERROR, format, arg1, arg2, arg3); +} + +void fatal(const char *format, + const errarg &arg1, + const errarg &arg2, + const errarg &arg3) +{ + do_error(FATAL, format, arg1, arg2, arg3); +} + +void fatal_with_file_and_line(const char *filename, int lineno, + const char *format, + const errarg &arg1, + const errarg &arg2, + const errarg &arg3) +{ + fprintf(stderr, "%s:%d: fatal error: ", filename, lineno); + errprint(format, arg1, arg2, arg3); + fputc('\n', stderr); + fflush(stderr); + cleanup_and_exit(1); +} + +void error_with_file_and_line(const char *filename, int lineno, + const char *format, + const errarg &arg1, + const errarg &arg2, + const errarg &arg3) +{ + fprintf(stderr, "%s:%d: error: ", filename, lineno); + errprint(format, arg1, arg2, arg3); + fputc('\n', stderr); + fflush(stderr); +} + +dictionary charinfo_dictionary(501); + +charinfo *get_charinfo(symbol nm) +{ + void *p = charinfo_dictionary.lookup(nm); + if (p != 0) + return (charinfo *)p; + charinfo *cp = new charinfo(nm); + (void)charinfo_dictionary.lookup(nm, cp); + return cp; +} + +int charinfo::next_index = 0; + +charinfo::charinfo(symbol s) +: translation(0), mac(0), special_translation(TRANSLATE_NONE), + hyphenation_code(0), flags(0), ascii_code(0), not_found(0), + transparent_translate(1), nm(s) +{ + index = next_index++; +} + +void charinfo::set_hyphenation_code(unsigned char c) +{ + hyphenation_code = c; +} + +void charinfo::set_translation(charinfo *ci, int tt) +{ + translation = ci; + special_translation = TRANSLATE_NONE; + transparent_translate = tt; +} + +void charinfo::set_special_translation(int c, int tt) +{ + special_translation = c; + translation = 0; + transparent_translate = tt; +} + +void charinfo::set_ascii_code(unsigned char c) +{ + ascii_code = c; +} + +macro *charinfo::set_macro(macro *m) +{ + macro *tem = mac; + mac = m; + return tem; +} + +void charinfo::set_number(int n) +{ + number = n; + flags |= NUMBERED; +} + +int charinfo::get_number() +{ + assert(flags & NUMBERED); + return number; +} + +symbol UNNAMED_SYMBOL("---"); + +// For numbered characters not between 0 and 255, we make a symbol out +// of the number and store them in this dictionary. + +dictionary numbered_charinfo_dictionary(11); + +charinfo *get_charinfo_by_number(int n) +{ + static charinfo *number_table[256]; + + if (n >= 0 && n < 256) { + charinfo *ci = number_table[n]; + if (!ci) { + ci = new charinfo(UNNAMED_SYMBOL); + ci->set_number(n); + number_table[n] = ci; + } + return ci; + } + else { + symbol ns(i_to_a(n)); + charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns); + if (!ci) { + ci = new charinfo(UNNAMED_SYMBOL); + ci->set_number(n); + numbered_charinfo_dictionary.lookup(ns, ci); + } + return ci; + } +} + +int font::name_to_index(const char *nm) +{ + charinfo *ci; + if (nm[1] == 0) + ci = charset_table[nm[0] & 0xff]; + else if (nm[0] == '\\' && nm[2] == 0) + ci = get_charinfo(symbol(nm + 1)); + else + ci = get_charinfo(symbol(nm)); + if (ci == 0) + return -1; + else + return ci->get_index(); +} + +int font::number_to_index(int n) +{ + return get_charinfo_by_number(n)->get_index(); +} diff --git a/contrib/groff/src/roff/troff/input.h b/contrib/groff/src/roff/troff/input.h new file mode 100644 index 0000000..525e1ef --- /dev/null +++ b/contrib/groff/src/roff/troff/input.h @@ -0,0 +1,92 @@ +// -*- C++ -*- +/* Copyright (C) 2001 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +/* special character codes */ + +#ifndef IS_EBCDIC_HOST + +const int ESCAPE_QUESTION = 015; +const int BEGIN_TRAP = 016; +const int END_TRAP = 017; +const int PAGE_EJECTOR = 020; +const int ESCAPE_NEWLINE = 021; +const int ESCAPE_AMPERSAND = 022; +const int ESCAPE_UNDERSCORE = 023; +const int ESCAPE_BAR = 024; +const int ESCAPE_CIRCUMFLEX = 025; +const int ESCAPE_LEFT_BRACE = 026; +const int ESCAPE_RIGHT_BRACE = 027; +const int ESCAPE_LEFT_QUOTE = 030; +const int ESCAPE_RIGHT_QUOTE = 031; +const int ESCAPE_HYPHEN = 032; +const int ESCAPE_BANG = 033; +const int ESCAPE_c = 034; +const int ESCAPE_e = 035; +const int ESCAPE_PERCENT = 036; +const int ESCAPE_SPACE = 037; + +const int TITLE_REQUEST = 0200; +const int COPY_FILE_REQUEST = 0201; +const int TRANSPARENT_FILE_REQUEST = 0202; +#ifdef COLUMN +const int VJUSTIFY_REQUEST = 0203; +#endif /* COLUMN */ +const int ESCAPE_E = 0204; +const int LAST_PAGE_EJECTOR = 0205; +const int ESCAPE_RIGHT_PARENTHESIS = 0206; +const int ESCAPE_TILDE = 0207; +const int ESCAPE_COLON = 0210; + +#else /* IS_EBCDIC_HOST */ + +const int ESCAPE_QUESTION = 010; +const int BEGIN_TRAP = 011; +const int END_TRAP = 013; +const int PAGE_EJECTOR = 015; +const int ESCAPE_NEWLINE = 016; +const int ESCAPE_AMPERSAND = 017; +const int ESCAPE_UNDERSCORE = 020; +const int ESCAPE_BAR = 021; +const int ESCAPE_CIRCUMFLEX = 022; +const int ESCAPE_LEFT_BRACE = 023; +const int ESCAPE_RIGHT_BRACE = 024; +const int ESCAPE_LEFT_QUOTE = 027; +const int ESCAPE_RIGHT_QUOTE = 030; +const int ESCAPE_HYPHEN = 031; +const int ESCAPE_BANG = 032; +const int ESCAPE_c = 033; +const int ESCAPE_e = 034; +const int ESCAPE_PERCENT = 035; +const int ESCAPE_SPACE = 036; + +const int TITLE_REQUEST = 060; +const int COPY_FILE_REQUEST = 061; +const int TRANSPARENT_FILE_REQUEST = 062; +#ifdef COLUMN +const int VJUSTIFY_REQUEST = 063; +#endif /* COLUMN */ +const int ESCAPE_E = 064; +const int LAST_PAGE_EJECTOR = 065; +const int ESCAPE_RIGHT_PARENTHESIS = 066; +const int ESCAPE_TILDE = 067; +const int ESCAPE_COLON = 070; + +#endif /* IS_EBCDIC_HOST */ diff --git a/contrib/groff/src/roff/troff/node.cc b/contrib/groff/src/roff/troff/node.cc new file mode 100644 index 0000000..2d2d799 --- /dev/null +++ b/contrib/groff/src/roff/troff/node.cc @@ -0,0 +1,5621 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001 + Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "troff.h" +#include "symbol.h" +#include "dictionary.h" +#include "hvunits.h" +#include "env.h" +#include "request.h" +#include "node.h" +#include "token.h" +#include "charinfo.h" +#include "font.h" +#include "reg.h" +#include "input.h" + +#include "nonposix.h" + +#ifdef _POSIX_VERSION + +#include <sys/wait.h> + +#else /* not _POSIX_VERSION */ + +/* traditional Unix */ + +#define WIFEXITED(s) (((s) & 0377) == 0) +#define WEXITSTATUS(s) (((s) >> 8) & 0377) +#define WTERMSIG(s) ((s) & 0177) +#define WIFSTOPPED(s) (((s) & 0377) == 0177) +#define WSTOPSIG(s) (((s) >> 8) & 0377) +#define WIFSIGNALED(s) (((s) & 0377) != 0 && (((s) & 0377) != 0177)) + +#endif /* not _POSIX_VERSION */ + +/* + * how many boundaries of images have been written? Useful for + * debugging grohtml + */ + +static int image_no=0; +static int suppress_start_page=0; + +#define STORE_WIDTH 1 + +symbol HYPHEN_SYMBOL("hy"); + +// Character used when a hyphen is inserted at a line break. +static charinfo *soft_hyphen_char; + +enum constant_space_type { + CONSTANT_SPACE_NONE, + CONSTANT_SPACE_RELATIVE, + CONSTANT_SPACE_ABSOLUTE + }; + +struct special_font_list { + int n; + special_font_list *next; +}; + +special_font_list *global_special_fonts; +static int global_ligature_mode = 1; +static int global_kern_mode = 1; + +class track_kerning_function { + int non_zero; + units min_size; + hunits min_amount; + units max_size; + hunits max_amount; +public: + track_kerning_function(); + track_kerning_function(units, hunits, units, hunits); + int operator==(const track_kerning_function &); + int operator!=(const track_kerning_function &); + hunits compute(int point_size); +}; + +// embolden fontno when this is the current font + +struct conditional_bold { + conditional_bold *next; + int fontno; + hunits offset; + conditional_bold(int, hunits, conditional_bold * = 0); +}; + +struct tfont; + +class font_info { + tfont *last_tfont; + int number; + font_size last_size; + int last_height; + int last_slant; + symbol internal_name; + symbol external_name; + font *fm; + char is_bold; + hunits bold_offset; + track_kerning_function track_kern; + constant_space_type is_constant_spaced; + units constant_space; + int last_ligature_mode; + int last_kern_mode; + conditional_bold *cond_bold_list; + void flush(); +public: + special_font_list *sf; + font_info(symbol nm, int n, symbol enm, font *f); + int contains(charinfo *); + void set_bold(hunits); + void unbold(); + void set_conditional_bold(int, hunits); + void conditional_unbold(int); + void set_track_kern(track_kerning_function &); + void set_constant_space(constant_space_type, units = 0); + int is_named(symbol); + symbol get_name(); + tfont *get_tfont(font_size, int, int, int); + hunits get_space_width(font_size, int); + hunits get_narrow_space_width(font_size); + hunits get_half_narrow_space_width(font_size); + int get_bold(hunits *); + int is_special(); + int is_style(); +}; + +class tfont_spec { +protected: + symbol name; + int input_position; + font *fm; + font_size size; + char is_bold; + char is_constant_spaced; + int ligature_mode; + int kern_mode; + hunits bold_offset; + hunits track_kern; // add this to the width + hunits constant_space_width; + int height; + int slant; +public: + tfont_spec(symbol nm, int pos, font *, font_size, int, int); + tfont_spec(const tfont_spec &spec) { *this = spec; } + tfont_spec plain(); + int operator==(const tfont_spec &); + friend tfont *font_info::get_tfont(font_size fs, int, int, int); +}; + +class tfont : public tfont_spec { + static tfont *tfont_list; + tfont *next; + tfont *plain_version; +public: + tfont(tfont_spec &); + int contains(charinfo *); + hunits get_width(charinfo *c); + int get_bold(hunits *); + int get_constant_space(hunits *); + hunits get_track_kern(); + tfont *get_plain(); + font_size get_size(); + symbol get_name(); + charinfo *get_lig(charinfo *c1, charinfo *c2); + int get_kern(charinfo *c1, charinfo *c2, hunits *res); + int get_input_position(); + int get_character_type(charinfo *); + int get_height(); + int get_slant(); + vunits get_char_height(charinfo *); + vunits get_char_depth(charinfo *); + hunits get_char_skew(charinfo *); + hunits get_italic_correction(charinfo *); + hunits get_left_italic_correction(charinfo *); + hunits get_subscript_correction(charinfo *); + friend tfont *make_tfont(tfont_spec &); +}; + +inline int env_definite_font(environment *env) +{ + return env->get_family()->make_definite(env->get_font()); +} + +/* font_info functions */ + +static font_info **font_table = 0; +static int font_table_size = 0; + +font_info::font_info(symbol nm, int n, symbol enm, font *f) +: last_tfont(0), number(n), last_size(0), + internal_name(nm), external_name(enm), fm(f), + is_bold(0), is_constant_spaced(CONSTANT_SPACE_NONE), last_ligature_mode(1), + last_kern_mode(1), cond_bold_list(0), sf(0) +{ +} + +inline int font_info::contains(charinfo *ci) +{ + return fm != 0 && fm->contains(ci->get_index()); +} + +inline int font_info::is_special() +{ + return fm != 0 && fm->is_special(); +} + +inline int font_info::is_style() +{ + return fm == 0; +} + +// this is the current_font, fontno is where we found the character, +// presumably a special font + +tfont *font_info::get_tfont(font_size fs, int height, int slant, int fontno) +{ + if (last_tfont == 0 || fs != last_size + || height != last_height || slant != last_slant + || global_ligature_mode != last_ligature_mode + || global_kern_mode != last_kern_mode + || fontno != number) { + font_info *f = font_table[fontno]; + tfont_spec spec(f->external_name, f->number, f->fm, fs, height, slant); + for (conditional_bold *p = cond_bold_list; p; p = p->next) + if (p->fontno == fontno) { + spec.is_bold = 1; + spec.bold_offset = p->offset; + break; + } + if (!spec.is_bold && is_bold) { + spec.is_bold = 1; + spec.bold_offset = bold_offset; + } + spec.track_kern = track_kern.compute(fs.to_scaled_points()); + spec.ligature_mode = global_ligature_mode; + spec.kern_mode = global_kern_mode; + switch (is_constant_spaced) { + case CONSTANT_SPACE_NONE: + break; + case CONSTANT_SPACE_ABSOLUTE: + spec.is_constant_spaced = 1; + spec.constant_space_width = constant_space; + break; + case CONSTANT_SPACE_RELATIVE: + spec.is_constant_spaced = 1; + spec.constant_space_width + = scale(constant_space*fs.to_scaled_points(), + units_per_inch, + 36*72*sizescale); + break; + default: + assert(0); + } + if (fontno != number) + return make_tfont(spec); + last_tfont = make_tfont(spec); + last_size = fs; + last_height = height; + last_slant = slant; + last_ligature_mode = global_ligature_mode; + last_kern_mode = global_kern_mode; + } + return last_tfont; +} + +int font_info::get_bold(hunits *res) +{ + if (is_bold) { + *res = bold_offset; + return 1; + } + else + return 0; +} + +void font_info::unbold() +{ + if (is_bold) { + is_bold = 0; + flush(); + } +} + +void font_info::set_bold(hunits offset) +{ + if (!is_bold || offset != bold_offset) { + is_bold = 1; + bold_offset = offset; + flush(); + } +} + +void font_info::set_conditional_bold(int fontno, hunits offset) +{ + for (conditional_bold *p = cond_bold_list; p; p = p->next) + if (p->fontno == fontno) { + if (offset != p->offset) { + p->offset = offset; + flush(); + } + return; + } + cond_bold_list = new conditional_bold(fontno, offset, cond_bold_list); +} + +conditional_bold::conditional_bold(int f, hunits h, conditional_bold *x) +: next(x), fontno(f), offset(h) +{ +} + +void font_info::conditional_unbold(int fontno) +{ + for (conditional_bold **p = &cond_bold_list; *p; p = &(*p)->next) + if ((*p)->fontno == fontno) { + conditional_bold *tem = *p; + *p = (*p)->next; + delete tem; + flush(); + return; + } +} + +void font_info::set_constant_space(constant_space_type type, units x) +{ + if (type != is_constant_spaced + || (type != CONSTANT_SPACE_NONE && x != constant_space)) { + flush(); + is_constant_spaced = type; + constant_space = x; + } +} + +void font_info::set_track_kern(track_kerning_function &tk) +{ + if (track_kern != tk) { + track_kern = tk; + flush(); + } +} + +void font_info::flush() +{ + last_tfont = 0; +} + +int font_info::is_named(symbol s) +{ + return internal_name == s; +} + +symbol font_info::get_name() +{ + return internal_name; +} + +hunits font_info::get_space_width(font_size fs, int space_size) +{ + if (is_constant_spaced == CONSTANT_SPACE_NONE) + return scale(hunits(fm->get_space_width(fs.to_scaled_points())), + space_size, 12); + else if (is_constant_spaced == CONSTANT_SPACE_ABSOLUTE) + return constant_space; + else + return scale(constant_space*fs.to_scaled_points(), + units_per_inch, 36*72*sizescale); +} + +hunits font_info::get_narrow_space_width(font_size fs) +{ + charinfo *ci = get_charinfo(symbol("|")); + if (fm->contains(ci->get_index())) + return hunits(fm->get_width(ci->get_index(), fs.to_scaled_points())); + else + return hunits(fs.to_units()/6); +} + +hunits font_info::get_half_narrow_space_width(font_size fs) +{ + charinfo *ci = get_charinfo(symbol("^")); + if (fm->contains(ci->get_index())) + return hunits(fm->get_width(ci->get_index(), fs.to_scaled_points())); + else + return hunits(fs.to_units()/12); +} + +/* tfont */ + +tfont_spec::tfont_spec(symbol nm, int n, font *f, + font_size s, int h, int sl) +: name(nm), input_position(n), fm(f), size(s), + is_bold(0), is_constant_spaced(0), ligature_mode(1), kern_mode(1), + height(h), slant(sl) +{ + if (height == size.to_scaled_points()) + height = 0; +} + +int tfont_spec::operator==(const tfont_spec &spec) +{ + if (fm == spec.fm + && size == spec.size + && input_position == spec.input_position + && name == spec.name + && height == spec.height + && slant == spec.slant + && (is_bold + ? (spec.is_bold && bold_offset == spec.bold_offset) + : !spec.is_bold) + && track_kern == spec.track_kern + && (is_constant_spaced + ? (spec.is_constant_spaced + && constant_space_width == spec.constant_space_width) + : !spec.is_constant_spaced) + && ligature_mode == spec.ligature_mode + && kern_mode == spec.kern_mode) + return 1; + else + return 0; +} + +tfont_spec tfont_spec::plain() +{ + return tfont_spec(name, input_position, fm, size, height, slant); +} + +hunits tfont::get_width(charinfo *c) +{ + if (is_constant_spaced) + return constant_space_width; + else if (is_bold) + return (hunits(fm->get_width(c->get_index(), size.to_scaled_points())) + + track_kern + bold_offset); + else + return (hunits(fm->get_width(c->get_index(), size.to_scaled_points())) + + track_kern); +} + +vunits tfont::get_char_height(charinfo *c) +{ + vunits v = fm->get_height(c->get_index(), size.to_scaled_points()); + if (height != 0 && height != size.to_scaled_points()) + return scale(v, height, size.to_scaled_points()); + else + return v; +} + +vunits tfont::get_char_depth(charinfo *c) +{ + vunits v = fm->get_depth(c->get_index(), size.to_scaled_points()); + if (height != 0 && height != size.to_scaled_points()) + return scale(v, height, size.to_scaled_points()); + else + return v; +} + +hunits tfont::get_char_skew(charinfo *c) +{ + return hunits(fm->get_skew(c->get_index(), size.to_scaled_points(), slant)); +} + +hunits tfont::get_italic_correction(charinfo *c) +{ + return hunits(fm->get_italic_correction(c->get_index(), size.to_scaled_points())); +} + +hunits tfont::get_left_italic_correction(charinfo *c) +{ + return hunits(fm->get_left_italic_correction(c->get_index(), + size.to_scaled_points())); +} + +hunits tfont::get_subscript_correction(charinfo *c) +{ + return hunits(fm->get_subscript_correction(c->get_index(), + size.to_scaled_points())); +} + +inline int tfont::get_input_position() +{ + return input_position; +} + +inline int tfont::contains(charinfo *ci) +{ + return fm->contains(ci->get_index()); +} + +inline int tfont::get_character_type(charinfo *ci) +{ + return fm->get_character_type(ci->get_index()); +} + +inline int tfont::get_bold(hunits *res) +{ + if (is_bold) { + *res = bold_offset; + return 1; + } + else + return 0; +} + +inline int tfont::get_constant_space(hunits *res) +{ + if (is_constant_spaced) { + *res = constant_space_width; + return 1; + } + else + return 0; +} + +inline hunits tfont::get_track_kern() +{ + return track_kern; +} + +inline tfont *tfont::get_plain() +{ + return plain_version; +} + +inline font_size tfont::get_size() +{ + return size; +} + +inline symbol tfont::get_name() +{ + return name; +} + +inline int tfont::get_height() +{ + return height; +} + +inline int tfont::get_slant() +{ + return slant; +} + +symbol SYMBOL_ff("ff"); +symbol SYMBOL_fi("fi"); +symbol SYMBOL_fl("fl"); +symbol SYMBOL_Fi("Fi"); +symbol SYMBOL_Fl("Fl"); + +charinfo *tfont::get_lig(charinfo *c1, charinfo *c2) +{ + if (ligature_mode == 0) + return 0; + charinfo *ci = 0; + if (c1->get_ascii_code() == 'f') { + switch (c2->get_ascii_code()) { + case 'f': + if (fm->has_ligature(font::LIG_ff)) + ci = get_charinfo(SYMBOL_ff); + break; + case 'i': + if (fm->has_ligature(font::LIG_fi)) + ci = get_charinfo(SYMBOL_fi); + break; + case 'l': + if (fm->has_ligature(font::LIG_fl)) + ci = get_charinfo(SYMBOL_fl); + break; + } + } + else if (ligature_mode != 2 && c1->nm == SYMBOL_ff) { + switch (c2->get_ascii_code()) { + case 'i': + if (fm->has_ligature(font::LIG_ffi)) + ci = get_charinfo(SYMBOL_Fi); + break; + case 'l': + if (fm->has_ligature(font::LIG_ffl)) + ci = get_charinfo(SYMBOL_Fl); + break; + } + } + if (ci != 0 && fm->contains(ci->get_index())) + return ci; + return 0; +} + +inline int tfont::get_kern(charinfo *c1, charinfo *c2, hunits *res) +{ + if (kern_mode == 0) + return 0; + else { + int n = fm->get_kern(c1->get_index(), + c2->get_index(), + size.to_scaled_points()); + if (n) { + *res = hunits(n); + return 1; + } + else + return 0; + } +} + +tfont *make_tfont(tfont_spec &spec) +{ + for (tfont *p = tfont::tfont_list; p; p = p->next) + if (*p == spec) + return p; + return new tfont(spec); +} + +tfont *tfont::tfont_list = 0; + +tfont::tfont(tfont_spec &spec) : tfont_spec(spec) +{ + next = tfont_list; + tfont_list = this; + tfont_spec plain_spec = plain(); + tfont *p; + for (p = tfont_list; p; p = p->next) + if (*p == plain_spec) { + plain_version = p; + break; + } + if (!p) + plain_version = new tfont(plain_spec); +} + +/* output_file */ + +class real_output_file : public output_file { +#ifndef POPEN_MISSING + int piped; +#endif + int printing; // decision via optional page list + int output_on; // .output 1 or .output 0 requests + virtual void really_transparent_char(unsigned char) = 0; + virtual void really_print_line(hunits x, vunits y, node *n, + vunits before, vunits after, hunits width) = 0; + virtual void really_begin_page(int pageno, vunits page_length) = 0; + virtual void really_copy_file(hunits x, vunits y, const char *filename); + virtual void really_put_filename(const char *filename); + virtual void really_on(); + virtual void really_off(); +protected: + FILE *fp; +public: + real_output_file(); + ~real_output_file(); + void flush(); + void transparent_char(unsigned char); + void print_line(hunits x, vunits y, node *n, vunits before, vunits after, hunits width); + void begin_page(int pageno, vunits page_length); + void put_filename(const char *filename); + void on(); + void off(); + int is_on(); + int is_printing(); + void copy_file(hunits x, vunits y, const char *filename); +}; + +class suppress_output_file : public real_output_file { +public: + suppress_output_file(); + void really_transparent_char(unsigned char); + void really_print_line(hunits x, vunits y, node *n, vunits, vunits, hunits width); + void really_begin_page(int pageno, vunits page_length); +}; + +class ascii_output_file : public real_output_file { +public: + ascii_output_file(); + void really_transparent_char(unsigned char); + void really_print_line(hunits x, vunits y, node *n, vunits, vunits, hunits width); + void really_begin_page(int pageno, vunits page_length); + void outc(unsigned char c); + void outs(const char *s); +}; + +void ascii_output_file::outc(unsigned char c) +{ + fputc(c, fp); +} + +void ascii_output_file::outs(const char *s) +{ + fputc('<', fp); + if (s) + fputs(s, fp); + fputc('>', fp); +} + +struct hvpair; + +class troff_output_file : public real_output_file { + units hpos; + units vpos; + units output_vpos; + units output_hpos; + int force_motion; + int current_size; + int current_slant; + int current_height; + tfont *current_tfont; + int current_font_number; + symbol *font_position; + int nfont_positions; + enum { TBUF_SIZE = 256 }; + char tbuf[TBUF_SIZE]; + int tbuf_len; + int tbuf_kern; + int begun_page; + void do_motion(); + void put(char c); + void put(unsigned char c); + void put(int i); + void put(const char *s); + void set_font(tfont *tf); + void flush_tbuf(); +public: + troff_output_file(); + ~troff_output_file(); + void trailer(vunits page_length); + void put_char(charinfo *ci, tfont *tf); + void put_char_width(charinfo *ci, tfont *tf, hunits w, hunits k); + void right(hunits); + void down(vunits); + void moveto(hunits, vunits); + void start_special(tfont *tf, int no_init_string = 0); + void start_special(int no_init_string = 0); + void special_char(unsigned char c); + void end_special(); + void word_marker(); + void really_transparent_char(unsigned char c); + void really_print_line(hunits x, vunits y, node *n, vunits before, vunits after, hunits width); + void really_begin_page(int pageno, vunits page_length); + void really_copy_file(hunits x, vunits y, const char *filename); + void really_put_filename(const char *filename); + void really_on(); + void really_off(); + void draw(char, hvpair *, int, font_size); + void determine_line_limits (char code, hvpair *point, int npoints); + void check_charinfo(tfont *tf, charinfo *ci); + int get_hpos() { return hpos; } + int get_vpos() { return vpos; } +}; + +static void put_string(const char *s, FILE *fp) +{ + for (; *s != '\0'; ++s) + putc(*s, fp); +} + +inline void troff_output_file::put(char c) +{ + putc(c, fp); +} + +inline void troff_output_file::put(unsigned char c) +{ + putc(c, fp); +} + +inline void troff_output_file::put(const char *s) +{ + put_string(s, fp); +} + +inline void troff_output_file::put(int i) +{ + put_string(i_to_a(i), fp); +} + +void troff_output_file::start_special(tfont *tf, int no_init_string) +{ + flush_tbuf(); + + /* + * although this is extremely unlikely to have an effect on other devices + * this way is safer. Currently this is only needed for html. + */ + if (is_html && tf) { + if (tf != current_tfont) + set_font(tf); + } + do_motion(); + if (!no_init_string) + put("x X "); +} + +void troff_output_file::start_special(int no_init_string) +{ + flush_tbuf(); + do_motion(); + if (!no_init_string) + put("x X "); +} + +void troff_output_file::special_char(unsigned char c) +{ + put(c); + if (c == '\n') + put('+'); +} + +void troff_output_file::end_special() +{ + put('\n'); +} + +inline void troff_output_file::moveto(hunits h, vunits v) +{ + hpos = h.to_units(); + vpos = v.to_units(); +} + +void troff_output_file::really_print_line(hunits x, vunits y, node *n, + vunits before, vunits after, hunits width) +{ + moveto(x, y); + while (n != 0) { + if (is_on() || (n->force_tprint())) + n->tprint(this); + n = n->next; + } + flush_tbuf(); + // This ensures that transparent throughput will have a more predictable + // position. + do_motion(); + force_motion = 1; + hpos = 0; + put('n'); + put(before.to_units()); + put(' '); + put(after.to_units()); + put('\n'); +} + +inline void troff_output_file::word_marker() +{ + flush_tbuf(); + put('w'); +} + +inline void troff_output_file::right(hunits n) +{ + hpos += n.to_units(); +} + +inline void troff_output_file::down(vunits n) +{ + vpos += n.to_units(); +} + +void troff_output_file::do_motion() +{ + if (force_motion) { + put('V'); + put(vpos); + put('\n'); + put('H'); + put(hpos); + put('\n'); + } + else { + if (hpos != output_hpos) { + units n = hpos - output_hpos; + if (n > 0 && n < hpos) { + put('h'); + put(n); + } + else { + put('H'); + put(hpos); + } + put('\n'); + } + if (vpos != output_vpos) { + units n = vpos - output_vpos; + if (n > 0 && n < vpos) { + put('v'); + put(n); + } + else { + put('V'); + put(vpos); + } + put('\n'); + } + } + output_vpos = vpos; + output_hpos = hpos; + force_motion = 0; +} + +void troff_output_file::flush_tbuf() +{ + if (tbuf_len == 0) + return; + if (tbuf_kern == 0) + put('t'); + else { + put('u'); + put(tbuf_kern); + put(' '); + } + check_output_limits(hpos, vpos); + check_output_limits(hpos, vpos + current_size + current_height); + + for (int i = 0; i < tbuf_len; i++) + put(tbuf[i]); + put('\n'); + tbuf_len = 0; +} + +void troff_output_file::check_charinfo(tfont *tf, charinfo *ci) +{ + int size = tf->get_size().to_scaled_points(); + int height = tf->get_char_height(ci).to_units(); + int width = tf->get_width(ci).to_units() + + tf->get_italic_correction(ci).to_units(); + int depth = tf->get_char_depth(ci).to_units(); + check_output_limits(output_hpos, + output_vpos - height); + check_output_limits(output_hpos + width, + output_vpos + size + depth); +} + +void troff_output_file::put_char_width(charinfo *ci, tfont *tf, hunits w, + hunits k) +{ + if (tf != current_tfont) { + flush_tbuf(); + set_font(tf); + } + char c = ci->get_ascii_code(); + int kk = k.to_units(); + if (c == '\0') { + flush_tbuf(); + do_motion(); + check_charinfo(tf, ci); + if (ci->numbered()) { + put('N'); + put(ci->get_number()); + } + else { + put('C'); + const char *s = ci->nm.contents(); + if (s[1] == 0) { + put('\\'); + put(s[0]); + } + else + put(s); + } + put('\n'); + hpos += w.to_units() + kk; + } + else if (tcommand_flag) { + if (tbuf_len > 0 && hpos == output_hpos && vpos == output_vpos + && kk == tbuf_kern + && tbuf_len < TBUF_SIZE) { + check_charinfo(tf, ci); + tbuf[tbuf_len++] = c; + output_hpos += w.to_units() + kk; + hpos = output_hpos; + return; + } + flush_tbuf(); + do_motion(); + check_charinfo(tf, ci); + tbuf[tbuf_len++] = c; + output_hpos += w.to_units() + kk; + tbuf_kern = kk; + hpos = output_hpos; + } + else { + // flush_tbuf(); + int n = hpos - output_hpos; + check_charinfo(tf, ci); + // check_output_limits(output_hpos, output_vpos); + if (vpos == output_vpos && n > 0 && n < 100 && !force_motion) { + put(char(n/10 + '0')); + put(char(n%10 + '0')); + put(c); + output_hpos = hpos; + } + else { + do_motion(); + put('c'); + put(c); + } + hpos += w.to_units() + kk; + } +} + +void troff_output_file::put_char(charinfo *ci, tfont *tf) +{ + flush_tbuf(); + if (tf != current_tfont) + set_font(tf); + char c = ci->get_ascii_code(); + if (c == '\0') { + do_motion(); + if (ci->numbered()) { + put('N'); + put(ci->get_number()); + } + else { + put('C'); + const char *s = ci->nm.contents(); + if (s[1] == 0) { + put('\\'); + put(s[0]); + } + else + put(s); + } + put('\n'); + } + else { + int n = hpos - output_hpos; + if (vpos == output_vpos && n > 0 && n < 100) { + put(char(n/10 + '0')); + put(char(n%10 + '0')); + put(c); + output_hpos = hpos; + } + else { + do_motion(); + put('c'); + put(c); + } + } +} + +void troff_output_file::set_font(tfont *tf) +{ + if (current_tfont == tf) + return; + int n = tf->get_input_position(); + symbol nm = tf->get_name(); + if (n >= nfont_positions || font_position[n] != nm) { + put("x font "); + put(n); + put(' '); + put(nm.contents()); + put('\n'); + if (n >= nfont_positions) { + int old_nfont_positions = nfont_positions; + symbol *old_font_position = font_position; + nfont_positions *= 3; + nfont_positions /= 2; + if (nfont_positions <= n) + nfont_positions = n + 10; + font_position = new symbol[nfont_positions]; + memcpy(font_position, old_font_position, + old_nfont_positions*sizeof(symbol)); + a_delete old_font_position; + } + font_position[n] = nm; + } + if (current_font_number != n) { + put('f'); + put(n); + put('\n'); + current_font_number = n; + } + int size = tf->get_size().to_scaled_points(); + if (current_size != size) { + put('s'); + put(size); + put('\n'); + current_size = size; + } + int slant = tf->get_slant(); + if (current_slant != slant) { + put("x Slant "); + put(slant); + put('\n'); + current_slant = slant; + } + int height = tf->get_height(); + if (current_height != height) { + put("x Height "); + put(height == 0 ? current_size : height); + put('\n'); + current_height = height; + } + current_tfont = tf; +} + +// determine_line_limits - works out the smallest box which will contain +// the entity, code, built from the point array. +void troff_output_file::determine_line_limits(char code, hvpair *point, + int npoints) +{ + int i, x, y; + switch (code) { + case 'c': + case 'C': + // only the h field is used when defining a circle + check_output_limits(output_hpos, + output_vpos - point[0].h.to_units()/2); + check_output_limits(output_hpos + point[0].h.to_units(), + output_vpos + point[0].h.to_units()/2); + break; + case 'E': + case 'e': + check_output_limits(output_hpos, + output_vpos - point[0].v.to_units()/2); + check_output_limits(output_hpos + point[0].h.to_units(), + output_vpos + point[0].v.to_units()/2); + break; + case 'P': + case 'p': + x = output_hpos; + y = output_vpos; + check_output_limits(x, y); + for (i = 0; i < npoints; i++) { + x += point[i].h.to_units(); + y += point[i].v.to_units(); + check_output_limits(x, y); + } + break; + case 't': + x = output_hpos; + y = output_vpos; + for (i = 0; i < npoints; i++) { + x += point[i].h.to_units(); + y += point[i].v.to_units(); + check_output_limits(x, y); + } + break; + default: + // remember this doesn't work for arc.. yet + x = output_hpos; + y = output_vpos; + for (i = 0; i < npoints; i++) { + x += point[i].h.to_units(); + y += point[i].v.to_units(); + check_output_limits(x, y); + } + } +} + +void troff_output_file::draw(char code, hvpair *point, int npoints, + font_size fsize) +{ + flush_tbuf(); + do_motion(); + int size = fsize.to_scaled_points(); + if (current_size != size) { + put('s'); + put(size); + put('\n'); + current_size = size; + current_tfont = 0; + } + put('D'); + put(code); + int i; + if (code == 'c') { + put(' '); + put(point[0].h.to_units()); + } + else + for (i = 0; i < npoints; i++) { + put(' '); + put(point[i].h.to_units()); + put(' '); + put(point[i].v.to_units()); + } + + determine_line_limits(code, point, npoints); + + for (i = 0; i < npoints; i++) + output_hpos += point[i].h.to_units(); + hpos = output_hpos; + if (code != 'e') { + for (i = 0; i < npoints; i++) + output_vpos += point[i].v.to_units(); + vpos = output_vpos; + } + put('\n'); +} + +void troff_output_file::really_on () +{ + flush_tbuf(); +} + +void troff_output_file::really_off () +{ + flush_tbuf(); +} + +void troff_output_file::really_put_filename(const char *filename) +{ + flush_tbuf(); + put("F "); + put(filename); + put('\n'); +} + +void troff_output_file::really_begin_page(int pageno, vunits page_length) +{ + flush_tbuf(); + if (begun_page) { + if (page_length > V0) { + put('V'); + put(page_length.to_units()); + put('\n'); + } + } + else + begun_page = 1; + current_tfont = 0; + current_font_number = -1; + current_size = 0; + // current_height = 0; + // current_slant = 0; + hpos = 0; + vpos = 0; + output_hpos = 0; + output_vpos = 0; + force_motion = 1; + for (int i = 0; i < nfont_positions; i++) + font_position[i] = NULL_SYMBOL; + put('p'); + put(pageno); + put('\n'); +} + +void troff_output_file::really_copy_file(hunits x, vunits y, const char *filename) +{ + moveto(x, y); + flush_tbuf(); + do_motion(); + errno = 0; + FILE *ifp = fopen(filename, "r"); + if (ifp == 0) + error("can't open `%1': %2", filename, strerror(errno)); + else { + int c; + while ((c = getc(ifp)) != EOF) + put(char(c)); + fclose(ifp); + } + force_motion = 1; + current_size = 0; + current_tfont = 0; + current_font_number = -1; + for (int i = 0; i < nfont_positions; i++) + font_position[i] = NULL_SYMBOL; +} + +void troff_output_file::really_transparent_char(unsigned char c) +{ + put(c); +} + +troff_output_file::~troff_output_file() +{ + a_delete font_position; +} + +void troff_output_file::trailer(vunits page_length) +{ + flush_tbuf(); + if (page_length > V0) { + put("x trailer\n"); + put('V'); + put(page_length.to_units()); + put('\n'); + } + put("x stop\n"); +} + +troff_output_file::troff_output_file() +: current_slant(0), current_height(0), nfont_positions(10), tbuf_len(0), + begun_page(0) +{ + font_position = new symbol[nfont_positions]; + put("x T "); + put(device); + put('\n'); + put("x res "); + put(units_per_inch); + put(' '); + put(hresolution); + put(' '); + put(vresolution); + put('\n'); + put("x init\n"); +} + +/* output_file */ + +output_file *the_output = 0; + +output_file::output_file() +{ +} + +output_file::~output_file() +{ +} + +void output_file::trailer(vunits) +{ +} + +void output_file::put_filename(const char *filename) +{ +} + +void output_file::on() +{ +} + +void output_file::off() +{ +} + +real_output_file::real_output_file() +: printing(0), output_on(1) +{ +#ifndef POPEN_MISSING + if (pipe_command) { + if ((fp = popen(pipe_command, POPEN_WT)) != 0) { + piped = 1; + return; + } + error("pipe open failed: %1", strerror(errno)); + } + piped = 0; +#endif /* not POPEN_MISSING */ + fp = stdout; +} + +real_output_file::~real_output_file() +{ + if (!fp) + return; + // To avoid looping, set fp to 0 before calling fatal(). + if (ferror(fp) || fflush(fp) < 0) { + fp = 0; + fatal("error writing output file"); + } +#ifndef POPEN_MISSING + if (piped) { + int result = pclose(fp); + fp = 0; + if (result < 0) + fatal("pclose failed"); + if (!WIFEXITED(result)) + error("output process `%1' got fatal signal %2", + pipe_command, + WIFSIGNALED(result) ? WTERMSIG(result) : WSTOPSIG(result)); + else { + int exit_status = WEXITSTATUS(result); + if (exit_status != 0) + error("output process `%1' exited with status %2", + pipe_command, exit_status); + } + } + else +#endif /* not POPEN MISSING */ + if (fclose(fp) < 0) { + fp = 0; + fatal("error closing output file"); + } +} + +void real_output_file::flush() +{ + if (fflush(fp) < 0) + fatal("error writing output file"); +} + +int real_output_file::is_printing() +{ + return printing; +} + +void real_output_file::begin_page(int pageno, vunits page_length) +{ + printing = in_output_page_list(pageno); + if (printing && output_on) + really_begin_page(pageno, page_length); +} + +void real_output_file::copy_file(hunits x, vunits y, const char *filename) +{ + if (printing && output_on) + really_copy_file(x, y, filename); + check_output_limits(x.to_units(), y.to_units()); +} + +void real_output_file::transparent_char(unsigned char c) +{ + if (printing && output_on) + really_transparent_char(c); +} + +void real_output_file::print_line(hunits x, vunits y, node *n, + vunits before, vunits after, hunits width) +{ + if (printing) + really_print_line(x, y, n, before, after, width); + delete_node_list(n); +} + +void real_output_file::really_copy_file(hunits, vunits, const char *) +{ + // do nothing +} + +void real_output_file::put_filename(const char *filename) +{ + really_put_filename(filename); +} + +void real_output_file::really_put_filename(const char *filename) +{ +} + +void real_output_file::on() +{ + really_on(); + if (output_on == 0) { + output_on = 1; + } +} + +void real_output_file::off() +{ + really_off(); + output_on = 0; +} + +int real_output_file::is_on() +{ + return( output_on ); +} + +void real_output_file::really_on() +{ +} + +void real_output_file::really_off() +{ +} + +/* ascii_output_file */ + +void ascii_output_file::really_transparent_char(unsigned char c) +{ + putc(c, fp); +} + +void ascii_output_file::really_print_line(hunits, vunits, node *n, vunits, vunits, hunits width) +{ + while (n != 0) { + n->ascii_print(this); + n = n->next; + } + fputc('\n', fp); +} + +void ascii_output_file::really_begin_page(int /*pageno*/, vunits /*page_length*/) +{ + fputs("<beginning of page>\n", fp); +} + +ascii_output_file::ascii_output_file() +{ +} + +/* suppress_output_file */ + +suppress_output_file::suppress_output_file() +{ +} + +void suppress_output_file::really_print_line(hunits, vunits, node *, vunits, vunits, hunits) +{ +} + +void suppress_output_file::really_begin_page(int, vunits) +{ +} + +void suppress_output_file::really_transparent_char(unsigned char) +{ +} + +/* glyphs, ligatures, kerns, discretionary breaks */ + +class charinfo_node : public node { +protected: + charinfo *ci; +public: + charinfo_node(charinfo *, node * = 0); + int ends_sentence(); + int overlaps_vertically(); + int overlaps_horizontally(); +}; + +charinfo_node::charinfo_node(charinfo *c, node *x) +: node(x), ci(c) +{ +} + +int charinfo_node::ends_sentence() +{ + if (ci->ends_sentence()) + return 1; + else if (ci->transparent()) + return 2; + else + return 0; +} + +int charinfo_node::overlaps_horizontally() +{ + return ci->overlaps_horizontally(); +} + +int charinfo_node::overlaps_vertically() +{ + return ci->overlaps_vertically(); +} + +class glyph_node : public charinfo_node { + static glyph_node *free_list; +protected: + tfont *tf; +#ifdef STORE_WIDTH + hunits wid; + glyph_node(charinfo *, tfont *, hunits, node * = 0); +#endif +public: + void *operator new(size_t); + void operator delete(void *); + glyph_node(charinfo *, tfont *, node * = 0); + ~glyph_node() {} + node *copy(); + node *merge_glyph_node(glyph_node *); + node *merge_self(node *); + hunits width(); + node *last_char_node(); + units size(); + void vertical_extent(vunits *, vunits *); + hunits subscript_correction(); + hunits italic_correction(); + hunits left_italic_correction(); + hunits skew(); + hyphenation_type get_hyphenation_type(); + tfont *get_tfont(); + void tprint(troff_output_file *); + void zero_width_tprint(troff_output_file *); + hyphen_list *get_hyphen_list(hyphen_list *ss = 0); + node *add_self(node *, hyphen_list **); + void ascii_print(ascii_output_file *); + void asciify(macro *); + int character_type(); + int same(node *); + const char *type(); + int force_tprint(); +}; + +glyph_node *glyph_node::free_list = 0; + +class ligature_node : public glyph_node { + node *n1; + node *n2; +#ifdef STORE_WIDTH + ligature_node(charinfo *, tfont *, hunits, node *gn1, node *gn2, node *x = 0); +#endif +public: + void *operator new(size_t); + void operator delete(void *); + ligature_node(charinfo *, tfont *, node *gn1, node *gn2, node *x = 0); + ~ligature_node(); + node *copy(); + node *add_self(node *, hyphen_list **); + hyphen_list *get_hyphen_list(hyphen_list *ss = 0); + void ascii_print(ascii_output_file *); + void asciify(macro *); + int same(node *); + const char *type(); + int force_tprint(); +}; + +class kern_pair_node : public node { + hunits amount; + node *n1; + node *n2; +public: + kern_pair_node(hunits n, node *first, node *second, node *x = 0); + ~kern_pair_node(); + node *copy(); + node *merge_glyph_node(glyph_node *); + node *add_self(node *, hyphen_list **); + hyphen_list *get_hyphen_list(hyphen_list *ss = 0); + node *add_discretionary_hyphen(); + hunits width(); + node *last_char_node(); + hunits italic_correction(); + hunits subscript_correction(); + void tprint(troff_output_file *); + hyphenation_type get_hyphenation_type(); + int ends_sentence(); + void ascii_print(ascii_output_file *); + void asciify(macro *); + int same(node *); + const char *type(); + int force_tprint(); + void vertical_extent(vunits *, vunits *); +}; + +class dbreak_node : public node { + node *none; + node *pre; + node *post; +public: + dbreak_node(node *n, node *p, node *x = 0); + ~dbreak_node(); + node *copy(); + node *merge_glyph_node(glyph_node *); + node *add_discretionary_hyphen(); + hunits width(); + node *last_char_node(); + hunits italic_correction(); + hunits subscript_correction(); + void tprint(troff_output_file *); + breakpoint *get_breakpoints(hunits width, int ns, breakpoint *rest = 0, + int is_inner = 0); + int nbreaks(); + int ends_sentence(); + void split(int, node **, node **); + hyphenation_type get_hyphenation_type(); + void ascii_print(ascii_output_file *); + void asciify(macro *); + int same(node *); + const char *type(); + int force_tprint(); +}; + +void *glyph_node::operator new(size_t n) +{ + assert(n == sizeof(glyph_node)); + if (!free_list) { + const int BLOCK = 1024; + free_list = (glyph_node *)new char[sizeof(glyph_node)*BLOCK]; + for (int i = 0; i < BLOCK - 1; i++) + free_list[i].next = free_list + i + 1; + free_list[BLOCK-1].next = 0; + } + glyph_node *p = free_list; + free_list = (glyph_node *)(free_list->next); + p->next = 0; + return p; +} + +void *ligature_node::operator new(size_t n) +{ + return new char[n]; +} + +void glyph_node::operator delete(void *p) +{ + if (p) { + ((glyph_node *)p)->next = free_list; + free_list = (glyph_node *)p; + } +} + +void ligature_node::operator delete(void *p) +{ + delete[] (char *)p; +} + +glyph_node::glyph_node(charinfo *c, tfont *t, node *x) +: charinfo_node(c, x), tf(t) +{ +#ifdef STORE_WIDTH + wid = tf->get_width(ci); +#endif +} + +#ifdef STORE_WIDTH +glyph_node::glyph_node(charinfo *c, tfont *t, hunits w, node *x) +: charinfo_node(c, x), tf(t), wid(w) +{ +} +#endif + +node *glyph_node::copy() +{ +#ifdef STORE_WIDTH + return new glyph_node(ci, tf, wid); +#else + return new glyph_node(ci, tf); +#endif +} + +node *glyph_node::merge_self(node *nd) +{ + return nd->merge_glyph_node(this); +} + +int glyph_node::character_type() +{ + return tf->get_character_type(ci); +} + +node *glyph_node::add_self(node *n, hyphen_list **p) +{ + assert(ci->get_hyphenation_code() == (*p)->hyphenation_code); + next = 0; + node *nn; + if (n == 0 || (nn = n->merge_glyph_node(this)) == 0) { + next = n; + nn = this; + } + if ((*p)->hyphen) + nn = nn->add_discretionary_hyphen(); + hyphen_list *pp = *p; + *p = (*p)->next; + delete pp; + return nn; +} + +units glyph_node::size() +{ + return tf->get_size().to_units(); +} + +hyphen_list *glyph_node::get_hyphen_list(hyphen_list *tail) +{ + return new hyphen_list(ci->get_hyphenation_code(), tail); +} + +tfont *node::get_tfont() +{ + return 0; +} + +tfont *glyph_node::get_tfont() +{ + return tf; +} + +node *node::merge_glyph_node(glyph_node * /*gn*/) +{ + return 0; +} + +node *glyph_node::merge_glyph_node(glyph_node *gn) +{ + if (tf == gn->tf) { + charinfo *lig; + if ((lig = tf->get_lig(ci, gn->ci)) != 0) { + node *next1 = next; + next = 0; + return new ligature_node(lig, tf, this, gn, next1); + } + hunits kern; + if (tf->get_kern(ci, gn->ci, &kern)) { + node *next1 = next; + next = 0; + return new kern_pair_node(kern, this, gn, next1); + } + } + return 0; +} + +#ifdef STORE_WIDTH +inline +#endif +hunits glyph_node::width() +{ +#ifdef STORE_WIDTH + return wid; +#else + return tf->get_width(ci); +#endif +} + +node *glyph_node::last_char_node() +{ + return this; +} + +void glyph_node::vertical_extent(vunits *min, vunits *max) +{ + *min = -tf->get_char_height(ci); + *max = tf->get_char_depth(ci); +} + +hunits glyph_node::skew() +{ + return tf->get_char_skew(ci); +} + +hunits glyph_node::subscript_correction() +{ + return tf->get_subscript_correction(ci); +} + +hunits glyph_node::italic_correction() +{ + return tf->get_italic_correction(ci); +} + +hunits glyph_node::left_italic_correction() +{ + return tf->get_left_italic_correction(ci); +} + +hyphenation_type glyph_node::get_hyphenation_type() +{ + return HYPHEN_MIDDLE; +} + +void glyph_node::ascii_print(ascii_output_file *ascii) +{ + unsigned char c = ci->get_ascii_code(); + if (c != 0) + ascii->outc(c); + else + ascii->outs(ci->nm.contents()); +} + +ligature_node::ligature_node(charinfo *c, tfont *t, + node *gn1, node *gn2, node *x) +: glyph_node(c, t, x), n1(gn1), n2(gn2) +{ +} + +#ifdef STORE_WIDTH +ligature_node::ligature_node(charinfo *c, tfont *t, hunits w, + node *gn1, node *gn2, node *x) +: glyph_node(c, t, w, x), n1(gn1), n2(gn2) +{ +} +#endif + +ligature_node::~ligature_node() +{ + delete n1; + delete n2; +} + +node *ligature_node::copy() +{ +#ifdef STORE_WIDTH + return new ligature_node(ci, tf, wid, n1->copy(), n2->copy()); +#else + return new ligature_node(ci, tf, n1->copy(), n2->copy()); +#endif +} + +void ligature_node::ascii_print(ascii_output_file *ascii) +{ + n1->ascii_print(ascii); + n2->ascii_print(ascii); +} + +hyphen_list *ligature_node::get_hyphen_list(hyphen_list *tail) +{ + return n1->get_hyphen_list(n2->get_hyphen_list(tail)); +} + +node *ligature_node::add_self(node *n, hyphen_list **p) +{ + n = n1->add_self(n, p); + n = n2->add_self(n, p); + n1 = n2 = 0; + delete this; + return n; +} + +kern_pair_node::kern_pair_node(hunits n, node *first, node *second, node *x) +: node(x), amount(n), n1(first), n2(second) +{ +} + +dbreak_node::dbreak_node(node *n, node *p, node *x) +: node(x), none(n), pre(p), post(0) +{ +} + +node *dbreak_node::merge_glyph_node(glyph_node *gn) +{ + glyph_node *gn2 = (glyph_node *)gn->copy(); + node *new_none = none ? none->merge_glyph_node(gn) : 0; + node *new_post = post ? post->merge_glyph_node(gn2) : 0; + if (new_none == 0 && new_post == 0) { + delete gn2; + return 0; + } + if (new_none != 0) + none = new_none; + else { + gn->next = none; + none = gn; + } + if (new_post != 0) + post = new_post; + else { + gn2->next = post; + post = gn2; + } + return this; +} + +node *kern_pair_node::merge_glyph_node(glyph_node *gn) +{ + node *nd = n2->merge_glyph_node(gn); + if (nd == 0) + return 0; + n2 = nd; + nd = n2->merge_self(n1); + if (nd) { + nd->next = next; + n1 = 0; + n2 = 0; + delete this; + return nd; + } + return this; +} + +hunits kern_pair_node::italic_correction() +{ + return n2->italic_correction(); +} + +hunits kern_pair_node::subscript_correction() +{ + return n2->subscript_correction(); +} + +void kern_pair_node::vertical_extent(vunits *min, vunits *max) +{ + n1->vertical_extent(min, max); + vunits min2, max2; + n2->vertical_extent(&min2, &max2); + if (min2 < *min) + *min = min2; + if (max2 > *max) + *max = max2; +} + +node *kern_pair_node::add_discretionary_hyphen() +{ + tfont *tf = n2->get_tfont(); + if (tf) { + if (tf->contains(soft_hyphen_char)) { + node *next1 = next; + next = 0; + node *n = copy(); + glyph_node *gn = new glyph_node(soft_hyphen_char, tf); + node *nn = n->merge_glyph_node(gn); + if (nn == 0) { + gn->next = n; + nn = gn; + } + return new dbreak_node(this, nn, next1); + } + } + return this; +} + +kern_pair_node::~kern_pair_node() +{ + if (n1 != 0) + delete n1; + if (n2 != 0) + delete n2; +} + +dbreak_node::~dbreak_node() +{ + delete_node_list(pre); + delete_node_list(post); + delete_node_list(none); +} + +node *kern_pair_node::copy() +{ + return new kern_pair_node(amount, n1->copy(), n2->copy()); +} + +node *copy_node_list(node *n) +{ + node *p = 0; + while (n != 0) { + node *nn = n->copy(); + nn->next = p; + p = nn; + n = n->next; + } + while (p != 0) { + node *pp = p->next; + p->next = n; + n = p; + p = pp; + } + return n; +} + +void delete_node_list(node *n) +{ + while (n != 0) { + node *tem = n; + n = n->next; + delete tem; + } +} + +node *dbreak_node::copy() +{ + dbreak_node *p = new dbreak_node(copy_node_list(none), copy_node_list(pre)); + p->post = copy_node_list(post); + return p; +} + +hyphen_list *node::get_hyphen_list(hyphen_list *tail) +{ + return tail; +} + +hyphen_list *kern_pair_node::get_hyphen_list(hyphen_list *tail) +{ + return n1->get_hyphen_list(n2->get_hyphen_list(tail)); +} + +class hyphen_inhibitor_node : public node { +public: + hyphen_inhibitor_node(node *nd = 0); + node *copy(); + int same(node *); + const char *type(); + int force_tprint(); + hyphenation_type get_hyphenation_type(); +}; + +hyphen_inhibitor_node::hyphen_inhibitor_node(node *nd) : node(nd) +{ +} + +node *hyphen_inhibitor_node::copy() +{ + return new hyphen_inhibitor_node; +} + +int hyphen_inhibitor_node::same(node *) +{ + return 1; +} + +const char *hyphen_inhibitor_node::type() +{ + return "hyphen_inhibitor_node"; +} + +int hyphen_inhibitor_node::force_tprint() +{ + return 0; +} + +hyphenation_type hyphen_inhibitor_node::get_hyphenation_type() +{ + return HYPHEN_INHIBIT; +} + +/* add_discretionary_hyphen methods */ + +node *dbreak_node::add_discretionary_hyphen() +{ + if (post) + post = post->add_discretionary_hyphen(); + if (none) + none = none->add_discretionary_hyphen(); + return this; +} + +node *node::add_discretionary_hyphen() +{ + tfont *tf = get_tfont(); + if (!tf) + return new hyphen_inhibitor_node(this); + if (tf->contains(soft_hyphen_char)) { + node *next1 = next; + next = 0; + node *n = copy(); + glyph_node *gn = new glyph_node(soft_hyphen_char, tf); + node *n1 = n->merge_glyph_node(gn); + if (n1 == 0) { + gn->next = n; + n1 = gn; + } + return new dbreak_node(this, n1, next1); + } + return this; +} + +node *node::merge_self(node *) +{ + return 0; +} + +node *node::add_self(node *n, hyphen_list ** /*p*/) +{ + next = n; + return this; +} + +node *kern_pair_node::add_self(node *n, hyphen_list **p) +{ + n = n1->add_self(n, p); + n = n2->add_self(n, p); + n1 = n2 = 0; + delete this; + return n; +} + +hunits node::width() +{ + return H0; +} + +node *node::last_char_node() +{ + return 0; +} + +int node::force_tprint() +{ + return 0; +} + +hunits hmotion_node::width() +{ + return n; +} + +units node::size() +{ + return points_to_units(10); +} + +hunits kern_pair_node::width() +{ + return n1->width() + n2->width() + amount; +} + +node *kern_pair_node::last_char_node() +{ + node *nd = n2->last_char_node(); + if (nd) + return nd; + return n1->last_char_node(); +} + +hunits dbreak_node::width() +{ + hunits x = H0; + for (node *n = none; n != 0; n = n->next) + x += n->width(); + return x; +} + +node *dbreak_node::last_char_node() +{ + for (node *n = none; n; n = n->next) { + node *last = n->last_char_node(); + if (last) + return last; + } + return 0; +} + +hunits dbreak_node::italic_correction() +{ + return none ? none->italic_correction() : H0; +} + +hunits dbreak_node::subscript_correction() +{ + return none ? none->subscript_correction() : H0; +} + +class italic_corrected_node : public node { + node *n; + hunits x; +public: + italic_corrected_node(node *, hunits, node * = 0); + ~italic_corrected_node(); + node *copy(); + void ascii_print(ascii_output_file *); + void asciify(macro *); + hunits width(); + node *last_char_node(); + void vertical_extent(vunits *, vunits *); + int ends_sentence(); + int overlaps_horizontally(); + int overlaps_vertically(); + int same(node *); + hyphenation_type get_hyphenation_type(); + tfont *get_tfont(); + hyphen_list *get_hyphen_list(hyphen_list *ss = 0); + int character_type(); + void tprint(troff_output_file *); + hunits subscript_correction(); + hunits skew(); + node *add_self(node *, hyphen_list **); + const char *type(); + int force_tprint(); +}; + +node *node::add_italic_correction(hunits *width) +{ + hunits ic = italic_correction(); + if (ic.is_zero()) + return this; + else { + node *next1 = next; + next = 0; + *width += ic; + return new italic_corrected_node(this, ic, next1); + } +} + +italic_corrected_node::italic_corrected_node(node *nn, hunits xx, node *p) +: node(p), n(nn), x(xx) +{ + assert(n != 0); +} + +italic_corrected_node::~italic_corrected_node() +{ + delete n; +} + +node *italic_corrected_node::copy() +{ + return new italic_corrected_node(n->copy(), x); +} + +hunits italic_corrected_node::width() +{ + return n->width() + x; +} + +void italic_corrected_node::vertical_extent(vunits *min, vunits *max) +{ + n->vertical_extent(min, max); +} + +void italic_corrected_node::tprint(troff_output_file *out) +{ + n->tprint(out); + out->right(x); +} + +hunits italic_corrected_node::skew() +{ + return n->skew() - x/2; +} + +hunits italic_corrected_node::subscript_correction() +{ + return n->subscript_correction() - x; +} + +void italic_corrected_node::ascii_print(ascii_output_file *out) +{ + n->ascii_print(out); +} + +int italic_corrected_node::ends_sentence() +{ + return n->ends_sentence(); +} + +int italic_corrected_node::overlaps_horizontally() +{ + return n->overlaps_horizontally(); +} + +int italic_corrected_node::overlaps_vertically() +{ + return n->overlaps_vertically(); +} + +node *italic_corrected_node::last_char_node() +{ + return n->last_char_node(); +} + +tfont *italic_corrected_node::get_tfont() +{ + return n->get_tfont(); +} + +hyphenation_type italic_corrected_node::get_hyphenation_type() +{ + return n->get_hyphenation_type(); +} + +node *italic_corrected_node::add_self(node *nd, hyphen_list **p) +{ + nd = n->add_self(nd, p); + hunits not_interested; + nd = nd->add_italic_correction(¬_interested); + n = 0; + delete this; + return nd; +} + +hyphen_list *italic_corrected_node::get_hyphen_list(hyphen_list *tail) +{ + return n->get_hyphen_list(tail); +} + +int italic_corrected_node::character_type() +{ + return n->character_type(); +} + +class break_char_node : public node { + node *ch; + char break_code; +public: + break_char_node(node *, int, node * = 0); + ~break_char_node(); + node *copy(); + hunits width(); + vunits vertical_width(); + node *last_char_node(); + int character_type(); + int ends_sentence(); + node *add_self(node *, hyphen_list **); + hyphen_list *get_hyphen_list(hyphen_list *s = 0); + void tprint(troff_output_file *); + void zero_width_tprint(troff_output_file *); + void ascii_print(ascii_output_file *); + void asciify(macro *); + hyphenation_type get_hyphenation_type(); + int overlaps_vertically(); + int overlaps_horizontally(); + units size(); + tfont *get_tfont(); + int same(node *); + const char *type(); + int force_tprint(); +}; + +break_char_node::break_char_node(node *n, int c, node *x) +: node(x), ch(n), break_code(c) +{ +} + +break_char_node::~break_char_node() +{ + delete ch; +} + +node *break_char_node::copy() +{ + return new break_char_node(ch->copy(), break_code); +} + +hunits break_char_node::width() +{ + return ch->width(); +} + +vunits break_char_node::vertical_width() +{ + return ch->vertical_width(); +} + +node *break_char_node::last_char_node() +{ + return ch->last_char_node(); +} + +int break_char_node::character_type() +{ + return ch->character_type(); +} + +int break_char_node::ends_sentence() +{ + return ch->ends_sentence(); +} + +node *break_char_node::add_self(node *n, hyphen_list **p) +{ + assert((*p)->hyphenation_code == 0); + if ((*p)->breakable && (break_code & 1)) { + n = new space_node(H0, n); + n->freeze_space(); + } + next = n; + n = this; + if ((*p)->breakable && (break_code & 2)) { + n = new space_node(H0, n); + n->freeze_space(); + } + hyphen_list *pp = *p; + *p = (*p)->next; + delete pp; + return n; +} + +hyphen_list *break_char_node::get_hyphen_list(hyphen_list *tail) +{ + return new hyphen_list(0, tail); +} + +hyphenation_type break_char_node::get_hyphenation_type() +{ + return HYPHEN_MIDDLE; +} + +void break_char_node::ascii_print(ascii_output_file *ascii) +{ + ch->ascii_print(ascii); +} + +int break_char_node::overlaps_vertically() +{ + return ch->overlaps_vertically(); +} + +int break_char_node::overlaps_horizontally() +{ + return ch->overlaps_horizontally(); +} + +units break_char_node::size() +{ + return ch->size(); +} + +tfont *break_char_node::get_tfont() +{ + return ch->get_tfont(); +} + +node *extra_size_node::copy() +{ + return new extra_size_node(n); +} + +node *vertical_size_node::copy() +{ + return new vertical_size_node(n); +} + +node *hmotion_node::copy() +{ + return new hmotion_node(n, was_tab, unformat); +} + +node *space_char_hmotion_node::copy() +{ + return new space_char_hmotion_node(n); +} + +node *vmotion_node::copy() +{ + return new vmotion_node(n); +} + +node *dummy_node::copy() +{ + return new dummy_node; +} + +node *transparent_dummy_node::copy() +{ + return new transparent_dummy_node; +} + +hline_node::~hline_node() +{ + if (n) + delete n; +} + +node *hline_node::copy() +{ + return new hline_node(x, n ? n->copy() : 0); +} + +hunits hline_node::width() +{ + return x < H0 ? H0 : x; +} + +vline_node::~vline_node() +{ + if (n) + delete n; +} + +node *vline_node::copy() +{ + return new vline_node(x, n ? n->copy() : 0); +} + +hunits vline_node::width() +{ + return n == 0 ? H0 : n->width(); +} + +zero_width_node::zero_width_node(node *nd) : n(nd) +{ +} + +zero_width_node::~zero_width_node() +{ + delete_node_list(n); +} + +node *zero_width_node::copy() +{ + return new zero_width_node(copy_node_list(n)); +} + +int node_list_character_type(node *p) +{ + int t = 0; + for (; p; p = p->next) + t |= p->character_type(); + return t; +} + +int zero_width_node::character_type() +{ + return node_list_character_type(n); +} + +void node_list_vertical_extent(node *p, vunits *min, vunits *max) +{ + *min = V0; + *max = V0; + vunits cur_vpos = V0; + vunits v1, v2; + for (; p; p = p->next) { + p->vertical_extent(&v1, &v2); + v1 += cur_vpos; + if (v1 < *min) + *min = v1; + v2 += cur_vpos; + if (v2 > *max) + *max = v2; + cur_vpos += p->vertical_width(); + } +} + +void zero_width_node::vertical_extent(vunits *min, vunits *max) +{ + node_list_vertical_extent(n, min, max); +} + +overstrike_node::overstrike_node() : list(0), max_width(H0) +{ +} + +overstrike_node::~overstrike_node() +{ + delete_node_list(list); +} + +node *overstrike_node::copy() +{ + overstrike_node *on = new overstrike_node; + for (node *tem = list; tem; tem = tem->next) + on->overstrike(tem->copy()); + return on; +} + +void overstrike_node::overstrike(node *n) +{ + if (n == 0) + return; + hunits w = n->width(); + if (w > max_width) + max_width = w; + node **p; + for (p = &list; *p; p = &(*p)->next) + ; + n->next = 0; + *p = n; +} + +hunits overstrike_node::width() +{ + return max_width; +} + +bracket_node::bracket_node() : list(0), max_width(H0) +{ +} + +bracket_node::~bracket_node() +{ + delete_node_list(list); +} + +node *bracket_node::copy() +{ + bracket_node *on = new bracket_node; + node *last = 0; + node *tem; + for (tem = list; tem; tem = tem->next) { + if (tem->next) + tem->next->last = tem; + last = tem; + } + for (tem = last; tem; tem = tem->last) + on->bracket(tem->copy()); + return on; +} + +void bracket_node::bracket(node *n) +{ + if (n == 0) + return; + hunits w = n->width(); + if (w > max_width) + max_width = w; + n->next = list; + list = n; +} + +hunits bracket_node::width() +{ + return max_width; +} + +int node::nspaces() +{ + return 0; +} + +int node::merge_space(hunits, hunits, hunits) +{ + return 0; +} + +#if 0 +space_node *space_node::free_list = 0; + +void *space_node::operator new(size_t n) +{ + assert(n == sizeof(space_node)); + if (!free_list) { + free_list = (space_node *)new char[sizeof(space_node)*BLOCK]; + for (int i = 0; i < BLOCK - 1; i++) + free_list[i].next = free_list + i + 1; + free_list[BLOCK-1].next = 0; + } + space_node *p = free_list; + free_list = (space_node *)(free_list->next); + p->next = 0; + return p; +} + +inline void space_node::operator delete(void *p) +{ + if (p) { + ((space_node *)p)->next = free_list; + free_list = (space_node *)p; + } +} +#endif + +space_node::space_node(hunits nn, node *p) +: node(p), n(nn), set(0), was_escape_colon(0) +{ +} + +space_node::space_node(hunits nn, int s, int flag, node *p) +: node(p), n(nn), set(s), was_escape_colon(flag) +{ +} + +#if 0 +space_node::~space_node() +{ +} +#endif + +node *space_node::copy() +{ + return new space_node(n, set, was_escape_colon); +} + +int space_node::force_tprint() +{ + return 0; +} + +int space_node::nspaces() +{ + return set ? 0 : 1; +} + +int space_node::merge_space(hunits h, hunits, hunits) +{ + n += h; + return 1; +} + +hunits space_node::width() +{ + return n; +} + +void node::spread_space(int*, hunits*) +{ +} + +void space_node::spread_space(int *nspaces, hunits *desired_space) +{ + if (!set) { + assert(*nspaces > 0); + if (*nspaces == 1) { + n += *desired_space; + *desired_space = H0; + } + else { + hunits extra = *desired_space / *nspaces; + *desired_space -= extra; + n += extra; + } + *nspaces -= 1; + set = 1; + } +} + +void node::freeze_space() +{ +} + +void space_node::freeze_space() +{ + set = 1; +} + +void node::is_escape_colon() +{ +} + +void space_node::is_escape_colon() +{ + was_escape_colon = 1; +} + +diverted_space_node::diverted_space_node(vunits d, node *p) +: node(p), n(d) +{ +} + +node *diverted_space_node::copy() +{ + return new diverted_space_node(n); +} + +diverted_copy_file_node::diverted_copy_file_node(symbol s, node *p) +: node(p), filename(s) +{ +} + +node *diverted_copy_file_node::copy() +{ + return new diverted_copy_file_node(filename); +} + +int node::ends_sentence() +{ + return 0; +} + +int kern_pair_node::ends_sentence() +{ + switch (n2->ends_sentence()) { + case 0: + return 0; + case 1: + return 1; + case 2: + break; + default: + assert(0); + } + return n1->ends_sentence(); +} + +int node_list_ends_sentence(node *n) +{ + for (; n != 0; n = n->next) + switch (n->ends_sentence()) { + case 0: + return 0; + case 1: + return 1; + case 2: + break; + default: + assert(0); + } + return 2; +} + +int dbreak_node::ends_sentence() +{ + return node_list_ends_sentence(none); +} + +int node::overlaps_horizontally() +{ + return 0; +} + +int node::overlaps_vertically() +{ + return 0; +} + +int node::discardable() +{ + return 0; +} + +int space_node::discardable() +{ + return set ? 0 : 1; +} + +vunits node::vertical_width() +{ + return V0; +} + +vunits vline_node::vertical_width() +{ + return x; +} + +vunits vmotion_node::vertical_width() +{ + return n; +} + +int node::set_unformat_flag() +{ + return 1; +} + +int node::character_type() +{ + return 0; +} + +hunits node::subscript_correction() +{ + return H0; +} + +hunits node::italic_correction() +{ + return H0; +} + +hunits node::left_italic_correction() +{ + return H0; +} + +hunits node::skew() +{ + return H0; +} + +/* vertical_extent methods */ + +void node::vertical_extent(vunits *min, vunits *max) +{ + vunits v = vertical_width(); + if (v < V0) { + *min = v; + *max = V0; + } + else { + *max = v; + *min = V0; + } +} + +void vline_node::vertical_extent(vunits *min, vunits *max) +{ + if (n == 0) + node::vertical_extent(min, max); + else { + vunits cmin, cmax; + n->vertical_extent(&cmin, &cmax); + vunits h = n->size(); + if (x < V0) { + if (-x < h) { + *min = x; + *max = V0; + } + else { + // we print the first character and then move up, so + *max = cmax; + // we print the last character and then move up h + *min = cmin + h; + if (*min > V0) + *min = V0; + *min += x; + } + } + else { + if (x < h) { + *max = x; + *min = V0; + } + else { + // we move down by h and then print the first character, so + *min = cmin + h; + if (*min > V0) + *min = V0; + *max = x + cmax; + } + } + } +} + +/* ascii_print methods */ + +static void ascii_print_reverse_node_list(ascii_output_file *ascii, node *n) +{ + if (n == 0) + return; + ascii_print_reverse_node_list(ascii, n->next); + n->ascii_print(ascii); +} + +void dbreak_node::ascii_print(ascii_output_file *ascii) +{ + ascii_print_reverse_node_list(ascii, none); +} + +void kern_pair_node::ascii_print(ascii_output_file *ascii) +{ + n1->ascii_print(ascii); + n2->ascii_print(ascii); +} + +void node::ascii_print(ascii_output_file *) +{ +} + +void space_node::ascii_print(ascii_output_file *ascii) +{ + if (!n.is_zero()) + ascii->outc(' '); +} + +void hmotion_node::ascii_print(ascii_output_file *ascii) +{ + // this is pretty arbitrary + if (n >= points_to_units(2)) + ascii->outc(' '); +} + +void space_char_hmotion_node::ascii_print(ascii_output_file *ascii) +{ + ascii->outc(' '); +} + +/* asciify methods */ + +void node::asciify(macro *m) +{ + m->append(this); +} + +void glyph_node::asciify(macro *m) +{ + unsigned char c = ci->get_ascii_code(); + if (c != 0) { + m->append(c); + delete this; + } + else + m->append(this); +} + +void kern_pair_node::asciify(macro *m) +{ + n1->asciify(m); + n2->asciify(m); + n1 = n2 = 0; + delete this; +} + +static void asciify_reverse_node_list(macro *m, node *n) +{ + if (n == 0) + return; + asciify_reverse_node_list(m, n->next); + n->asciify(m); +} + +void dbreak_node::asciify(macro *m) +{ + asciify_reverse_node_list(m, none); + none = 0; + delete this; +} + +void ligature_node::asciify(macro *m) +{ + n1->asciify(m); + n2->asciify(m); + n1 = n2 = 0; + delete this; +} + +void break_char_node::asciify(macro *m) +{ + ch->asciify(m); + ch = 0; + delete this; +} + +void italic_corrected_node::asciify(macro *m) +{ + n->asciify(m); + n = 0; + delete this; +} + +void left_italic_corrected_node::asciify(macro *m) +{ + if (n) { + n->asciify(m); + n = 0; + } + delete this; +} + +void hmotion_node::asciify(macro *m) +{ + if (was_tab) { + m->append('\t'); + delete this; + } + else + m->append(this); +} + +space_char_hmotion_node::space_char_hmotion_node(hunits i, node *next) +: hmotion_node(i, next) +{ +} + +void space_char_hmotion_node::asciify(macro *m) +{ + m->append(ESCAPE_SPACE); + delete this; +} + +void space_node::asciify(macro *m) +{ + if (was_escape_colon) { + m->append(ESCAPE_COLON); + delete this; + } + else + m->append(this); +} + +void word_space_node::asciify(macro *m) +{ + for (width_list *w = orig_width; w; w = w->next) + m->append(' '); + delete this; +} + +void unbreakable_space_node::asciify(macro *m) +{ + m->append(ESCAPE_TILDE); + delete this; +} + +void line_start_node::asciify(macro *) +{ + delete this; +} + +void vertical_size_node::asciify(macro *) +{ + delete this; +} + +breakpoint *node::get_breakpoints(hunits /*width*/, int /*nspaces*/, + breakpoint *rest, int /*is_inner*/) +{ + return rest; +} + +int node::nbreaks() +{ + return 0; +} + +breakpoint *space_node::get_breakpoints(hunits width, int ns, breakpoint *rest, + int is_inner) +{ + if (next->discardable()) + return rest; + breakpoint *bp = new breakpoint; + bp->next = rest; + bp->width = width; + bp->nspaces = ns; + bp->hyphenated = 0; + if (is_inner) { + assert(rest != 0); + bp->index = rest->index + 1; + bp->nd = rest->nd; + } + else { + bp->nd = this; + bp->index = 0; + } + return bp; +} + +int space_node::nbreaks() +{ + if (next->discardable()) + return 0; + else + return 1; +} + +static breakpoint *node_list_get_breakpoints(node *p, hunits *widthp, + int ns, breakpoint *rest) +{ + if (p != 0) { + rest = p->get_breakpoints(*widthp, + ns, + node_list_get_breakpoints(p->next, widthp, ns, + rest), + 1); + *widthp += p->width(); + } + return rest; +} + +breakpoint *dbreak_node::get_breakpoints(hunits width, int ns, + breakpoint *rest, int is_inner) +{ + breakpoint *bp = new breakpoint; + bp->next = rest; + bp->width = width; + for (node *tem = pre; tem != 0; tem = tem->next) + bp->width += tem->width(); + bp->nspaces = ns; + bp->hyphenated = 1; + if (is_inner) { + assert(rest != 0); + bp->index = rest->index + 1; + bp->nd = rest->nd; + } + else { + bp->nd = this; + bp->index = 0; + } + return node_list_get_breakpoints(none, &width, ns, bp); +} + +int dbreak_node::nbreaks() +{ + int i = 1; + for (node *tem = none; tem != 0; tem = tem->next) + i += tem->nbreaks(); + return i; +} + +void node::split(int /*where*/, node ** /*prep*/, node ** /*postp*/) +{ + assert(0); +} + +void space_node::split(int where, node **pre, node **post) +{ + assert(where == 0); + *pre = next; + *post = 0; + delete this; +} + +static void node_list_split(node *p, int *wherep, node **prep, node **postp) +{ + if (p == 0) + return; + int nb = p->nbreaks(); + node_list_split(p->next, wherep, prep, postp); + if (*wherep < 0) { + p->next = *postp; + *postp = p; + } + else if (*wherep < nb) { + p->next = *prep; + p->split(*wherep, prep, postp); + } + else { + p->next = *prep; + *prep = p; + } + *wherep -= nb; +} + +void dbreak_node::split(int where, node **prep, node **postp) +{ + assert(where >= 0); + if (where == 0) { + *postp = post; + post = 0; + if (pre == 0) + *prep = next; + else { + node *tem; + for (tem = pre; tem->next != 0; tem = tem->next) + ; + tem->next = next; + *prep = pre; + } + pre = 0; + delete this; + } + else { + *prep = next; + where -= 1; + node_list_split(none, &where, prep, postp); + none = 0; + delete this; + } +} + +hyphenation_type node::get_hyphenation_type() +{ + return HYPHEN_BOUNDARY; +} + +hyphenation_type dbreak_node::get_hyphenation_type() +{ + return HYPHEN_INHIBIT; +} + +hyphenation_type kern_pair_node::get_hyphenation_type() +{ + return HYPHEN_MIDDLE; +} + +hyphenation_type dummy_node::get_hyphenation_type() +{ + return HYPHEN_MIDDLE; +} + +hyphenation_type transparent_dummy_node::get_hyphenation_type() +{ + return HYPHEN_MIDDLE; +} + +hyphenation_type hmotion_node::get_hyphenation_type() +{ + return HYPHEN_MIDDLE; +} + +hyphenation_type space_char_hmotion_node::get_hyphenation_type() +{ + return HYPHEN_MIDDLE; +} + +hyphenation_type overstrike_node::get_hyphenation_type() +{ + return HYPHEN_MIDDLE; +} + +hyphenation_type space_node::get_hyphenation_type() +{ + if (was_escape_colon) + return HYPHEN_MIDDLE; + return HYPHEN_BOUNDARY; +} + +hyphenation_type unbreakable_space_node::get_hyphenation_type() +{ + return HYPHEN_MIDDLE; +} + +int node::interpret(macro *) +{ + return 0; +} + +special_node::special_node(const macro &m, int n) +: mac(m), no_init_string(n) +{ + font_size fs = curenv->get_font_size(); + int char_height = curenv->get_char_height(); + int char_slant = curenv->get_char_slant(); + int fontno = curenv->get_font(); + tf = font_table[fontno]->get_tfont(fs, char_height, char_slant, + fontno); + if (curenv->is_composite()) + tf = tf->get_plain(); +} + +special_node::special_node(const macro &m, tfont *t, int n) +: mac(m), tf(t), no_init_string(n) +{ +} + +int special_node::same(node *n) +{ + return ((mac == ((special_node *)n)->mac) + && (tf == ((special_node *)n)->tf) + && (no_init_string == ((special_node *)n)->no_init_string)); +} + +const char *special_node::type() +{ + return "special_node"; +} + +int special_node::ends_sentence() +{ + return 2; +} + +int special_node::force_tprint() +{ + return 0; +} + +node *special_node::copy() +{ + return new special_node(mac, tf, no_init_string); +} + +void special_node::tprint_start(troff_output_file *out) +{ + out->start_special(get_tfont(), no_init_string); +} + +void special_node::tprint_char(troff_output_file *out, unsigned char c) +{ + out->special_char(c); +} + +void special_node::tprint_end(troff_output_file *out) +{ + out->end_special(); +} + +tfont *special_node::get_tfont() +{ + return tf; +} + +/* suppress_node */ + +suppress_node::suppress_node(int on_or_off, int issue_limits) +: is_on(on_or_off), emit_limits(issue_limits), filename(0), position(0) +{ +} + +suppress_node::suppress_node(symbol f, char p) +: is_on(2), emit_limits(0), filename(f), position(p) +{ +} + +suppress_node::suppress_node(int issue_limits, int on_or_off, + symbol f, char p) +: is_on(on_or_off), emit_limits(issue_limits), filename(f), position(p) +{ +} + +int suppress_node::same(node *n) +{ + return ((is_on == ((suppress_node *)n)->is_on) + && (emit_limits == ((suppress_node *)n)->emit_limits) + && (filename == ((suppress_node *)n)->filename) + && (position == ((suppress_node *)n)->position)); +; +} + +const char *suppress_node::type() +{ + return "suppress_node"; +} + +node *suppress_node::copy() +{ + return new suppress_node(emit_limits, is_on, filename, position); +} + +int get_reg_int(const char *p) +{ + reg *r = (reg *)number_reg_dictionary.lookup(p); + units prev_value; + if (r && (r->get_value(&prev_value))) + return (int)prev_value; + else + warning(WARN_REG, "number register `%1' not defined", p); + return 0; +} + +const char *get_reg_str(const char *p) +{ + reg *r = (reg *)number_reg_dictionary.lookup(p); + if (r) + return r->get_string(); + else + warning(WARN_REG, "register `%1' not defined", p); + return 0; +} + +void suppress_node::put(troff_output_file *out, const char *s) +{ + int i = 0; + while (s[i] != (char)0) { + out->special_char(s[i]); + i++; + } +} + +/* + * We need to remember the start of the image and its name. + */ + +static char last_position = 0; +static const char *last_image_filename = 0; + +inline int min(int a, int b) +{ + return a < b ? a : b; +} + +/* + * tprint - if (is_on == 2) + * remember current position (l, r, c, i) and filename + * else + * if (emit_limits) + * if (html) + * emit image tag + * else + * emit postscript bounds for image + * else + * if (suppress boolean differs from current state) + * alter state + * reset registers + * record current page + * set low water mark. + */ + +void suppress_node::tprint(troff_output_file *out) +{ + int current_page = get_reg_int("%"); + // firstly check to see whether this suppress node contains + // an image filename & position. + if (is_on == 2) { + // remember position and filename + last_position = position; + last_image_filename = filename.contents(); + } + else { + // now check whether the suppress node requires us to issue limits. + if (emit_limits) { + char name[8192]; + image_no++; + // remember that the filename will contain a %d in which the + // image_no is placed + sprintf(name, last_image_filename, image_no); + if (is_html) { + switch (last_position) { + case 'c': + out->start_special(); + put(out, "html-tag:.centered-image"); + break; + case 'r': + out->start_special(); + put(out, "html-tag:.right-image"); + break; + case 'l': + out->start_special(); + put(out, "html-tag:.left-image"); + break; + case 'i': + ; + default: + ; + } + out->end_special(); + out->start_special(); + put(out, "html-tag:.auto-image "); + put(out, name); + out->end_special(); + } + else { + // postscript (or other device) + if (current_page != suppress_start_page) + error("suppression limit registers span more than one page;\n" + "image description %1 will be wrong", image_no); + // remember that the filename will contain a %d in which the + // image_no is placed + fprintf(stderr, + "grohtml-info:page %d %d %d %d %d %d %s %d %d %s\n", + current_page, + get_reg_int("opminx"), get_reg_int("opminy"), + get_reg_int("opmaxx"), get_reg_int("opmaxy"), + // page offset + line length + get_reg_int(".o") + get_reg_int(".l"), + name, hresolution, vresolution, get_reg_str(".F")); + fflush(stderr); + } + } + else { + if (is_on) + out->on(); + else + out->off(); + // lastly we reset the output registers + reset_output_registers(out->get_vpos()); + suppress_start_page = current_page; + } + } +} + +int suppress_node::force_tprint() +{ + return is_on; +} + +hunits suppress_node::width() +{ + return H0; +} + +/* composite_node */ + +class composite_node : public charinfo_node { + node *n; + tfont *tf; +public: + composite_node(node *, charinfo *, tfont *, node * = 0); + ~composite_node(); + node *copy(); + hunits width(); + node *last_char_node(); + units size(); + void tprint(troff_output_file *); + hyphenation_type get_hyphenation_type(); + void ascii_print(ascii_output_file *); + void asciify(macro *); + hyphen_list *get_hyphen_list(hyphen_list *tail); + node *add_self(node *, hyphen_list **); + tfont *get_tfont(); + int same(node *); + const char *type(); + int force_tprint(); + void vertical_extent(vunits *, vunits *); + vunits vertical_width(); +}; + +composite_node::composite_node(node *p, charinfo *c, tfont *t, node *x) +: charinfo_node(c, x), n(p), tf(t) +{ +} + +composite_node::~composite_node() +{ + delete_node_list(n); +} + +node *composite_node::copy() +{ + return new composite_node(copy_node_list(n), ci, tf); +} + +hunits composite_node::width() +{ + hunits x; + if (tf->get_constant_space(&x)) + return x; + x = H0; + for (node *tem = n; tem; tem = tem->next) + x += tem->width(); + hunits offset; + if (tf->get_bold(&offset)) + x += offset; + x += tf->get_track_kern(); + return x; +} + +node *composite_node::last_char_node() +{ + return this; +} + +vunits composite_node::vertical_width() +{ + vunits v = V0; + for (node *tem = n; tem; tem = tem->next) + v += tem->vertical_width(); + return v; +} + +units composite_node::size() +{ + return tf->get_size().to_units(); +} + +hyphenation_type composite_node::get_hyphenation_type() +{ + return HYPHEN_MIDDLE; +} + +void composite_node::asciify(macro *m) +{ + unsigned char c = ci->get_ascii_code(); + if (c != 0) { + m->append(c); + delete this; + } + else + m->append(this); +} + +void composite_node::ascii_print(ascii_output_file *ascii) +{ + unsigned char c = ci->get_ascii_code(); + if (c != 0) + ascii->outc(c); + else + ascii->outs(ci->nm.contents()); + +} + +hyphen_list *composite_node::get_hyphen_list(hyphen_list *tail) +{ + return new hyphen_list(ci->get_hyphenation_code(), tail); +} + +node *composite_node::add_self(node *nn, hyphen_list **p) +{ + assert(ci->get_hyphenation_code() == (*p)->hyphenation_code); + next = nn; + nn = this; + if ((*p)->hyphen) + nn = nn->add_discretionary_hyphen(); + hyphen_list *pp = *p; + *p = (*p)->next; + delete pp; + return nn; +} + +tfont *composite_node::get_tfont() +{ + return tf; +} + +node *reverse_node_list(node *n) +{ + node *r = 0; + while (n) { + node *tem = n; + n = n->next; + tem->next = r; + r = tem; + } + return r; +} + +void composite_node::vertical_extent(vunits *min, vunits *max) +{ + n = reverse_node_list(n); + node_list_vertical_extent(n, min, max); + n = reverse_node_list(n); +} + +width_list::width_list(hunits w, hunits s) +: width(w), sentence_width(s), next(0) +{ +} + +width_list::width_list(width_list *w) +: width(w->width), sentence_width(w->sentence_width), next(0) +{ +} + +word_space_node::word_space_node(hunits d, width_list *w, node *x) +: space_node(d, x), orig_width(w), unformat(0) +{ +} + +word_space_node::word_space_node(hunits d, int s, width_list *w, + int flag, node *x) +: space_node(d, s, 0, x), orig_width(w), unformat(flag) +{ +} + +word_space_node::~word_space_node() +{ + width_list *w = orig_width; + while (w != 0) { + width_list *tmp = w; + w = w->next; + delete tmp; + } +} + +node *word_space_node::copy() +{ + assert(orig_width != 0); + width_list *w_old_curr = orig_width; + width_list *w_new_curr = new width_list(w_old_curr); + width_list *w_new = w_new_curr; + w_old_curr = w_old_curr->next; + while (w_old_curr != 0) { + w_new_curr->next = new width_list(w_old_curr); + w_new_curr = w_new_curr->next; + w_old_curr = w_old_curr->next; + } + return new word_space_node(n, set, w_new, unformat); +} + +int word_space_node::set_unformat_flag() +{ + unformat = 1; + return 1; +} + +void word_space_node::tprint(troff_output_file *out) +{ + out->word_marker(); + space_node::tprint(out); +} + +int word_space_node::merge_space(hunits h, hunits sw, hunits ssw) +{ + n += h; + assert(orig_width != 0); + width_list *w = orig_width; + for (; w->next; w = w->next) + ; + w->next = new width_list(sw, ssw); + return 1; +} + +unbreakable_space_node::unbreakable_space_node(hunits d, node *x) +: word_space_node(d, 0, x) +{ +} + +unbreakable_space_node::unbreakable_space_node(hunits d, int s, node *x) +: word_space_node(d, s, 0, 0, x) +{ +} + +node *unbreakable_space_node::copy() +{ + return new unbreakable_space_node(n, set); +} + +int unbreakable_space_node::force_tprint() +{ + return 0; +} + +breakpoint *unbreakable_space_node::get_breakpoints(hunits, int, + breakpoint *rest, int) +{ + return rest; +} + +int unbreakable_space_node::nbreaks() +{ + return 0; +} + +void unbreakable_space_node::split(int, node **, node **) +{ + assert(0); +} + +int unbreakable_space_node::merge_space(hunits, hunits, hunits) +{ + return 0; +} + +hvpair::hvpair() +{ +} + +draw_node::draw_node(char c, hvpair *p, int np, font_size s) +: npoints(np), sz(s), code(c) +{ + point = new hvpair[npoints]; + for (int i = 0; i < npoints; i++) + point[i] = p[i]; +} + +int draw_node::same(node *n) +{ + draw_node *nd = (draw_node *)n; + if (code != nd->code || npoints != nd->npoints || sz != nd->sz) + return 0; + for (int i = 0; i < npoints; i++) + if (point[i].h != nd->point[i].h || point[i].v != nd->point[i].v) + return 0; + return 1; +} + +const char *draw_node::type() +{ + return "draw_node"; +} + +int draw_node::force_tprint() +{ + return 0; +} + +draw_node::~draw_node() +{ + if (point) + a_delete point; +} + +hunits draw_node::width() +{ + hunits x = H0; + for (int i = 0; i < npoints; i++) + x += point[i].h; + return x; +} + +vunits draw_node::vertical_width() +{ + if (code == 'e') + return V0; + vunits x = V0; + for (int i = 0; i < npoints; i++) + x += point[i].v; + return x; +} + +node *draw_node::copy() +{ + return new draw_node(code, point, npoints, sz); +} + +void draw_node::tprint(troff_output_file *out) +{ + out->draw(code, point, npoints, sz); +} + +/* tprint methods */ + +void glyph_node::tprint(troff_output_file *out) +{ + tfont *ptf = tf->get_plain(); + if (ptf == tf) + out->put_char_width(ci, ptf, width(), H0); + else { + hunits offset; + int bold = tf->get_bold(&offset); + hunits w = ptf->get_width(ci); + hunits k = H0; + hunits x; + int cs = tf->get_constant_space(&x); + if (cs) { + x -= w; + if (bold) + x -= offset; + hunits x2 = x/2; + out->right(x2); + k = x - x2; + } + else + k = tf->get_track_kern(); + if (bold) { + out->put_char(ci, ptf); + out->right(offset); + } + out->put_char_width(ci, ptf, w, k); + } +} + +void glyph_node::zero_width_tprint(troff_output_file *out) +{ + tfont *ptf = tf->get_plain(); + hunits offset; + int bold = tf->get_bold(&offset); + hunits x; + int cs = tf->get_constant_space(&x); + if (cs) { + x -= ptf->get_width(ci); + if (bold) + x -= offset; + x = x/2; + out->right(x); + } + out->put_char(ci, ptf); + if (bold) { + out->right(offset); + out->put_char(ci, ptf); + out->right(-offset); + } + if (cs) + out->right(-x); +} + +void break_char_node::tprint(troff_output_file *t) +{ + ch->tprint(t); +} + +void break_char_node::zero_width_tprint(troff_output_file *t) +{ + ch->zero_width_tprint(t); +} + +void hline_node::tprint(troff_output_file *out) +{ + if (x < H0) { + out->right(x); + x = -x; + } + if (n == 0) { + out->right(x); + return; + } + hunits w = n->width(); + if (w <= H0) { + error("horizontal line drawing character must have positive width"); + out->right(x); + return; + } + int i = int(x/w); + if (i == 0) { + hunits xx = x - w; + hunits xx2 = xx/2; + out->right(xx2); + n->tprint(out); + out->right(xx - xx2); + } + else { + hunits rem = x - w*i; + if (rem > H0) + if (n->overlaps_horizontally()) { + n->tprint(out); + out->right(rem - w); + } + else + out->right(rem); + while (--i >= 0) + n->tprint(out); + } +} + +void vline_node::tprint(troff_output_file *out) +{ + if (n == 0) { + out->down(x); + return; + } + vunits h = n->size(); + int overlaps = n->overlaps_vertically(); + vunits y = x; + if (y < V0) { + y = -y; + int i = y / h; + vunits rem = y - i*h; + if (i == 0) { + out->right(n->width()); + out->down(-rem); + } + else { + while (--i > 0) { + n->zero_width_tprint(out); + out->down(-h); + } + if (overlaps) { + n->zero_width_tprint(out); + out->down(-rem); + n->tprint(out); + out->down(-h); + } + else { + n->tprint(out); + out->down(-h - rem); + } + } + } + else { + int i = y / h; + vunits rem = y - i*h; + if (i == 0) { + out->down(rem); + out->right(n->width()); + } + else { + out->down(h); + if (overlaps) + n->zero_width_tprint(out); + out->down(rem); + while (--i > 0) { + n->zero_width_tprint(out); + out->down(h); + } + n->tprint(out); + } + } +} + +void zero_width_node::tprint(troff_output_file *out) +{ + if (!n) + return; + if (!n->next) { + n->zero_width_tprint(out); + return; + } + int hpos = out->get_hpos(); + int vpos = out->get_vpos(); + node *tem = n; + while (tem) { + tem->tprint(out); + tem = tem->next; + } + out->moveto(hpos, vpos); +} + +void overstrike_node::tprint(troff_output_file *out) +{ + hunits pos = H0; + for (node *tem = list; tem; tem = tem->next) { + hunits x = (max_width - tem->width())/2; + out->right(x - pos); + pos = x; + tem->zero_width_tprint(out); + } + out->right(max_width - pos); +} + +void bracket_node::tprint(troff_output_file *out) +{ + if (list == 0) + return; + int npieces = 0; + node *tem; + for (tem = list; tem; tem = tem->next) + ++npieces; + vunits h = list->size(); + vunits totalh = h*npieces; + vunits y = (totalh - h)/2; + out->down(y); + for (tem = list; tem; tem = tem->next) { + tem->zero_width_tprint(out); + out->down(-h); + } + out->right(max_width); + out->down(totalh - y); +} + +void node::tprint(troff_output_file *) +{ +} + +void node::zero_width_tprint(troff_output_file *out) +{ + int hpos = out->get_hpos(); + int vpos = out->get_vpos(); + tprint(out); + out->moveto(hpos, vpos); +} + +void space_node::tprint(troff_output_file *out) +{ + out->right(n); +} + +void hmotion_node::tprint(troff_output_file *out) +{ + out->right(n); +} + +void vmotion_node::tprint(troff_output_file *out) +{ + out->down(n); +} + +void kern_pair_node::tprint(troff_output_file *out) +{ + n1->tprint(out); + out->right(amount); + n2->tprint(out); +} + +static void tprint_reverse_node_list(troff_output_file *out, node *n) +{ + if (n == 0) + return; + tprint_reverse_node_list(out, n->next); + n->tprint(out); +} + +void dbreak_node::tprint(troff_output_file *out) +{ + tprint_reverse_node_list(out, none); +} + +void composite_node::tprint(troff_output_file *out) +{ + hunits bold_offset; + int is_bold = tf->get_bold(&bold_offset); + hunits track_kern = tf->get_track_kern(); + hunits constant_space; + int is_constant_spaced = tf->get_constant_space(&constant_space); + hunits x = H0; + if (is_constant_spaced) { + x = constant_space; + for (node *tem = n; tem; tem = tem->next) + x -= tem->width(); + if (is_bold) + x -= bold_offset; + hunits x2 = x/2; + out->right(x2); + x -= x2; + } + if (is_bold) { + int hpos = out->get_hpos(); + int vpos = out->get_vpos(); + tprint_reverse_node_list(out, n); + out->moveto(hpos, vpos); + out->right(bold_offset); + } + tprint_reverse_node_list(out, n); + if (is_constant_spaced) + out->right(x); + else + out->right(track_kern); +} + +node *make_composite_node(charinfo *s, environment *env) +{ + int fontno = env_definite_font(env); + if (fontno < 0) { + error("no current font"); + return 0; + } + assert(fontno < font_table_size && font_table[fontno] != 0); + node *n = charinfo_to_node_list(s, env); + font_size fs = env->get_font_size(); + int char_height = env->get_char_height(); + int char_slant = env->get_char_slant(); + tfont *tf = font_table[fontno]->get_tfont(fs, char_height, char_slant, + fontno); + if (env->is_composite()) + tf = tf->get_plain(); + return new composite_node(n, s, tf); +} + +node *make_glyph_node(charinfo *s, environment *env, int no_error_message = 0) +{ + int fontno = env_definite_font(env); + if (fontno < 0) { + error("no current font"); + return 0; + } + assert(fontno < font_table_size && font_table[fontno] != 0); + int fn = fontno; + int found = font_table[fontno]->contains(s); + if (!found) { + if (s->numbered()) { + if (!no_error_message) + warning(WARN_CHAR, "can't find numbered character %1", + s->get_number()); + return 0; + } + special_font_list *sf = font_table[fontno]->sf; + while (sf != 0 && !found) { + fn = sf->n; + if (font_table[fn]) + found = font_table[fn]->contains(s); + sf = sf->next; + } + if (!found) { + sf = global_special_fonts; + while (sf != 0 && !found) { + fn = sf->n; + if (font_table[fn]) + found = font_table[fn]->contains(s); + sf = sf->next; + } + } + if (!found +#if 0 + && global_special_fonts == 0 && font_table[fontno]->sf == 0 +#endif + ) { + for (fn = 0; fn < font_table_size; fn++) + if (font_table[fn] + && font_table[fn]->is_special() + && font_table[fn]->contains(s)) { + found = 1; + break; + } + } + if (!found) { + if (!no_error_message && s->first_time_not_found()) { + unsigned char input_code = s->get_ascii_code(); + if (input_code != 0) { + if (csgraph(input_code)) + warning(WARN_CHAR, "can't find character `%1'", input_code); + else + warning(WARN_CHAR, "can't find character with input code %1", + int(input_code)); + } + else if (s->nm.contents()) + warning(WARN_CHAR, "can't find special character `%1'", + s->nm.contents()); + } + return 0; + } + } + font_size fs = env->get_font_size(); + int char_height = env->get_char_height(); + int char_slant = env->get_char_slant(); + tfont *tf = font_table[fontno]->get_tfont(fs, char_height, char_slant, fn); + if (env->is_composite()) + tf = tf->get_plain(); + return new glyph_node(s, tf); +} + +node *make_node(charinfo *ci, environment *env) +{ + switch (ci->get_special_translation()) { + case charinfo::TRANSLATE_SPACE: + return new space_char_hmotion_node(env->get_space_width()); + case charinfo::TRANSLATE_STRETCHABLE_SPACE: + return new unbreakable_space_node(env->get_space_width()); + case charinfo::TRANSLATE_DUMMY: + return new dummy_node; + case charinfo::TRANSLATE_HYPHEN_INDICATOR: + error("translation to \\% ignored in this context"); + break; + } + charinfo *tem = ci->get_translation(); + if (tem) + ci = tem; + macro *mac = ci->get_macro(); + if (mac) + return make_composite_node(ci, env); + else + return make_glyph_node(ci, env); +} + +int character_exists(charinfo *ci, environment *env) +{ + if (ci->get_special_translation() != charinfo::TRANSLATE_NONE) + return 1; + charinfo *tem = ci->get_translation(); + if (tem) + ci = tem; + if (ci->get_macro()) + return 1; + node *nd = make_glyph_node(ci, env, 1); + if (nd) { + delete nd; + return 1; + } + return 0; +} + +node *node::add_char(charinfo *ci, environment *env, + hunits *widthp, int *spacep) +{ + node *res; + switch (ci->get_special_translation()) { + case charinfo::TRANSLATE_SPACE: + res = new space_char_hmotion_node(env->get_space_width(), this); + *widthp += res->width(); + return res; + case charinfo::TRANSLATE_STRETCHABLE_SPACE: + res = new unbreakable_space_node(env->get_space_width(), this); + *widthp += res->width(); + *spacep += res->nspaces(); + return res; + case charinfo::TRANSLATE_DUMMY: + return new dummy_node(this); + case charinfo::TRANSLATE_HYPHEN_INDICATOR: + return add_discretionary_hyphen(); + } + charinfo *tem = ci->get_translation(); + if (tem) + ci = tem; + macro *mac = ci->get_macro(); + if (mac) { + res = make_composite_node(ci, env); + if (res) { + res->next = this; + *widthp += res->width(); + } + else + return this; + } + else { + node *gn = make_glyph_node(ci, env); + if (gn == 0) + return this; + else { + hunits old_width = width(); + node *p = gn->merge_self(this); + if (p == 0) { + *widthp += gn->width(); + gn->next = this; + res = gn; + } + else { + *widthp += p->width() - old_width; + res = p; + } + } + } + int break_code = 0; + if (ci->can_break_before()) + break_code = 1; + if (ci->can_break_after()) + break_code |= 2; + if (break_code) { + node *next1 = res->next; + res->next = 0; + res = new break_char_node(res, break_code, next1); + } + return res; +} + +#ifdef __GNUG__ +inline +#endif +int same_node(node *n1, node *n2) +{ + if (n1 != 0) { + if (n2 != 0) + return n1->type() == n2->type() && n1->same(n2); + else + return 0; + } + else + return n2 == 0; +} + +int same_node_list(node *n1, node *n2) +{ + while (n1 && n2) { + if (n1->type() != n2->type() || !n1->same(n2)) + return 0; + n1 = n1->next; + n2 = n2->next; + } + return !n1 && !n2; +} + +int extra_size_node::same(node *nd) +{ + return n == ((extra_size_node *)nd)->n; +} + +const char *extra_size_node::type() +{ + return "extra_size_node"; +} + +int extra_size_node::force_tprint() +{ + return 0; +} + +int vertical_size_node::same(node *nd) +{ + return n == ((vertical_size_node *)nd)->n; +} + +const char *vertical_size_node::type() +{ + return "vertical_size_node"; +} + +int vertical_size_node::set_unformat_flag() +{ + return 0; +} + +int vertical_size_node::force_tprint() +{ + return 0; +} + +int hmotion_node::same(node *nd) +{ + return n == ((hmotion_node *)nd)->n; +} + +const char *hmotion_node::type() +{ + return "hmotion_node"; +} + +int hmotion_node::set_unformat_flag() +{ + unformat = 1; + return 1; +} + +int hmotion_node::force_tprint() +{ + return 0; +} + +node *hmotion_node::add_self(node *n, hyphen_list **p) +{ + next = n; + hyphen_list *pp = *p; + *p = (*p)->next; + delete pp; + return this; +} + +hyphen_list *hmotion_node::get_hyphen_list(hyphen_list *tail) +{ + return new hyphen_list(0, tail); +} + +int space_char_hmotion_node::same(node *nd) +{ + return n == ((space_char_hmotion_node *)nd)->n; +} + +const char *space_char_hmotion_node::type() +{ + return "space_char_hmotion_node"; +} + +int space_char_hmotion_node::force_tprint() +{ + return 0; +} + +node *space_char_hmotion_node::add_self(node *n, hyphen_list **p) +{ + next = n; + hyphen_list *pp = *p; + *p = (*p)->next; + delete pp; + return this; +} + +hyphen_list *space_char_hmotion_node::get_hyphen_list(hyphen_list *tail) +{ + return new hyphen_list(0, tail); +} + +int vmotion_node::same(node *nd) +{ + return n == ((vmotion_node *)nd)->n; +} + +const char *vmotion_node::type() +{ + return "vmotion_node"; +} + +int vmotion_node::force_tprint() +{ + return 0; +} + +int hline_node::same(node *nd) +{ + return x == ((hline_node *)nd)->x && same_node(n, ((hline_node *)nd)->n); +} + +const char *hline_node::type() +{ + return "hline_node"; +} + +int hline_node::force_tprint() +{ + return 0; +} + +int vline_node::same(node *nd) +{ + return x == ((vline_node *)nd)->x && same_node(n, ((vline_node *)nd)->n); +} + +const char *vline_node::type() +{ + return "vline_node"; +} + +int vline_node::force_tprint() +{ + return 0; +} + +int dummy_node::same(node * /*nd*/) +{ + return 1; +} + +const char *dummy_node::type() +{ + return "dummy_node"; +} + +int dummy_node::force_tprint() +{ + return 0; +} + +int transparent_dummy_node::same(node * /*nd*/) +{ + return 1; +} + +const char *transparent_dummy_node::type() +{ + return "transparent_dummy_node"; +} + +int transparent_dummy_node::force_tprint() +{ + return 0; +} + +int transparent_dummy_node::ends_sentence() +{ + return 2; +} + +int zero_width_node::same(node *nd) +{ + return same_node_list(n, ((zero_width_node *)nd)->n); +} + +const char *zero_width_node::type() +{ + return "zero_width_node"; +} + +int zero_width_node::force_tprint() +{ + return 0; +} + +int italic_corrected_node::same(node *nd) +{ + return (x == ((italic_corrected_node *)nd)->x + && same_node(n, ((italic_corrected_node *)nd)->n)); +} + +const char *italic_corrected_node::type() +{ + return "italic_corrected_node"; +} + +int italic_corrected_node::force_tprint() +{ + return 0; +} + +left_italic_corrected_node::left_italic_corrected_node(node *x) +: node(x), n(0) +{ +} + +left_italic_corrected_node::~left_italic_corrected_node() +{ + delete n; +} + +node *left_italic_corrected_node::merge_glyph_node(glyph_node *gn) +{ + if (n == 0) { + hunits lic = gn->left_italic_correction(); + if (!lic.is_zero()) { + x = lic; + n = gn; + return this; + } + } + else { + node *nd = n->merge_glyph_node(gn); + if (nd) { + n = nd; + x = n->left_italic_correction(); + return this; + } + } + return 0; +} + +node *left_italic_corrected_node::copy() +{ + left_italic_corrected_node *nd = new left_italic_corrected_node; + if (n) { + nd->n = n->copy(); + nd->x = x; + } + return nd; +} + +void left_italic_corrected_node::tprint(troff_output_file *out) +{ + if (n) { + out->right(x); + n->tprint(out); + } +} + +const char *left_italic_corrected_node::type() +{ + return "left_italic_corrected_node"; +} + +int left_italic_corrected_node::force_tprint() +{ + return 0; +} + +int left_italic_corrected_node::same(node *nd) +{ + return (x == ((left_italic_corrected_node *)nd)->x + && same_node(n, ((left_italic_corrected_node *)nd)->n)); +} + +void left_italic_corrected_node::ascii_print(ascii_output_file *out) +{ + if (n) + n->ascii_print(out); +} + +hunits left_italic_corrected_node::width() +{ + return n ? n->width() + x : H0; +} + +void left_italic_corrected_node::vertical_extent(vunits *min, vunits *max) +{ + if (n) + n->vertical_extent(min, max); + else + node::vertical_extent(min, max); +} + +hunits left_italic_corrected_node::skew() +{ + return n ? n->skew() + x/2 : H0; +} + +hunits left_italic_corrected_node::subscript_correction() +{ + return n ? n->subscript_correction() : H0; +} + +hunits left_italic_corrected_node::italic_correction() +{ + return n ? n->italic_correction() : H0; +} + +int left_italic_corrected_node::ends_sentence() +{ + return n ? n->ends_sentence() : 0; +} + +int left_italic_corrected_node::overlaps_horizontally() +{ + return n ? n->overlaps_horizontally() : 0; +} + +int left_italic_corrected_node::overlaps_vertically() +{ + return n ? n->overlaps_vertically() : 0; +} + +node *left_italic_corrected_node::last_char_node() +{ + return n ? n->last_char_node() : 0; +} + +tfont *left_italic_corrected_node::get_tfont() +{ + return n ? n->get_tfont() : 0; +} + +hyphenation_type left_italic_corrected_node::get_hyphenation_type() +{ + if (n) + return n->get_hyphenation_type(); + else + return HYPHEN_MIDDLE; +} + +hyphen_list *left_italic_corrected_node::get_hyphen_list(hyphen_list *tail) +{ + return n ? n->get_hyphen_list(tail) : tail; +} + +node *left_italic_corrected_node::add_self(node *nd, hyphen_list **p) +{ + if (n) { + nd = new left_italic_corrected_node(nd); + nd = n->add_self(nd, p); + n = 0; + delete this; + } + return nd; +} + +int left_italic_corrected_node::character_type() +{ + return n ? n->character_type() : 0; +} + +int overstrike_node::same(node *nd) +{ + return same_node_list(list, ((overstrike_node *)nd)->list); +} + +const char *overstrike_node::type() +{ + return "overstrike_node"; +} + +int overstrike_node::force_tprint() +{ + return 0; +} + +node *overstrike_node::add_self(node *n, hyphen_list **p) +{ + next = n; + hyphen_list *pp = *p; + *p = (*p)->next; + delete pp; + return this; +} + +hyphen_list *overstrike_node::get_hyphen_list(hyphen_list *tail) +{ + return new hyphen_list(0, tail); +} + +int bracket_node::same(node *nd) +{ + return same_node_list(list, ((bracket_node *)nd)->list); +} + +const char *bracket_node::type() +{ + return "bracket_node"; +} + +int bracket_node::force_tprint() +{ + return 0; +} + +int composite_node::same(node *nd) +{ + return ci == ((composite_node *)nd)->ci + && same_node_list(n, ((composite_node *)nd)->n); +} + +const char *composite_node::type() +{ + return "composite_node"; +} + +int composite_node::force_tprint() +{ + return 0; +} + +int glyph_node::same(node *nd) +{ + return ci == ((glyph_node *)nd)->ci && tf == ((glyph_node *)nd)->tf; +} + +const char *glyph_node::type() +{ + return "glyph_node"; +} + +int glyph_node::force_tprint() +{ + return 0; +} + +int ligature_node::same(node *nd) +{ + return (same_node(n1, ((ligature_node *)nd)->n1) + && same_node(n2, ((ligature_node *)nd)->n2) + && glyph_node::same(nd)); +} + +const char *ligature_node::type() +{ + return "ligature_node"; +} + +int ligature_node::force_tprint() +{ + return 0; +} + +int kern_pair_node::same(node *nd) +{ + return (amount == ((kern_pair_node *)nd)->amount + && same_node(n1, ((kern_pair_node *)nd)->n1) + && same_node(n2, ((kern_pair_node *)nd)->n2)); +} + +const char *kern_pair_node::type() +{ + return "kern_pair_node"; +} + +int kern_pair_node::force_tprint() +{ + return 0; +} + +int dbreak_node::same(node *nd) +{ + return (same_node_list(none, ((dbreak_node *)nd)->none) + && same_node_list(pre, ((dbreak_node *)nd)->pre) + && same_node_list(post, ((dbreak_node *)nd)->post)); +} + +const char *dbreak_node::type() +{ + return "dbreak_node"; +} + +int dbreak_node::force_tprint() +{ + return 0; +} + +int break_char_node::same(node *nd) +{ + return (break_code == ((break_char_node *)nd)->break_code + && same_node(ch, ((break_char_node *)nd)->ch)); +} + +const char *break_char_node::type() +{ + return "break_char_node"; +} + +int break_char_node::force_tprint() +{ + return 0; +} + +int line_start_node::same(node * /*nd*/) +{ + return 1; +} + +const char *line_start_node::type() +{ + return "line_start_node"; +} + +int line_start_node::force_tprint() +{ + return 0; +} + +int space_node::same(node *nd) +{ + return n == ((space_node *)nd)->n && set == ((space_node *)nd)->set; +} + +const char *space_node::type() +{ + return "space_node"; +} + +int word_space_node::same(node *nd) +{ + return (n == ((word_space_node *)nd)->n + && set == ((word_space_node *)nd)->set); +} + +const char *word_space_node::type() +{ + return "word_space_node"; +} + +int word_space_node::force_tprint() +{ + return 0; +} + +int unbreakable_space_node::same(node *nd) +{ + return (n == ((unbreakable_space_node *)nd)->n + && set == ((unbreakable_space_node *)nd)->set); +} + +const char *unbreakable_space_node::type() +{ + return "unbreakable_space_node"; +} + +node *unbreakable_space_node::add_self(node *n, hyphen_list **p) +{ + next = n; + hyphen_list *pp = *p; + *p = (*p)->next; + delete pp; + return this; +} + +hyphen_list *unbreakable_space_node::get_hyphen_list(hyphen_list *tail) +{ + return new hyphen_list(0, tail); +} + +int diverted_space_node::same(node *nd) +{ + return n == ((diverted_space_node *)nd)->n; +} + +const char *diverted_space_node::type() +{ + return "diverted_space_node"; +} + +int diverted_space_node::force_tprint() +{ + return 0; +} + +int diverted_copy_file_node::same(node *nd) +{ + return filename == ((diverted_copy_file_node *)nd)->filename; +} + +const char *diverted_copy_file_node::type() +{ + return "diverted_copy_file_node"; +} + +int diverted_copy_file_node::force_tprint() +{ + return 0; +} + +// Grow the font_table so that its size is > n. + +static void grow_font_table(int n) +{ + assert(n >= font_table_size); + font_info **old_font_table = font_table; + int old_font_table_size = font_table_size; + font_table_size = font_table_size ? (font_table_size*3)/2 : 10; + if (font_table_size <= n) + font_table_size = n + 10; + font_table = new font_info *[font_table_size]; + if (old_font_table_size) + memcpy(font_table, old_font_table, + old_font_table_size*sizeof(font_info *)); + a_delete old_font_table; + for (int i = old_font_table_size; i < font_table_size; i++) + font_table[i] = 0; +} + +dictionary font_translation_dictionary(17); + +static symbol get_font_translation(symbol nm) +{ + void *p = font_translation_dictionary.lookup(nm); + return p ? symbol((char *)p) : nm; +} + +dictionary font_dictionary(50); + +static int mount_font_no_translate(int n, symbol name, symbol external_name) +{ + assert(n >= 0); + // We store the address of this char in font_dictionary to indicate + // that we've previously tried to mount the font and failed. + static char a_char; + font *fm = 0; + void *p = font_dictionary.lookup(external_name); + if (p == 0) { + int not_found; + fm = font::load_font(external_name.contents(), ¬_found); + if (!fm) { + if (not_found) + warning(WARN_FONT, "can't find font `%1'", external_name.contents()); + font_dictionary.lookup(external_name, &a_char); + return 0; + } + font_dictionary.lookup(name, fm); + } + else if (p == &a_char) { +#if 0 + error("invalid font `%1'", external_name.contents()); +#endif + return 0; + } + else + fm = (font*)p; + if (n >= font_table_size) { + if (n - font_table_size > 1000) { + error("font position too much larger than first unused position"); + return 0; + } + grow_font_table(n); + } + else if (font_table[n] != 0) + delete font_table[n]; + font_table[n] = new font_info(name, n, external_name, fm); + font_family::invalidate_fontno(n); + return 1; +} + +int mount_font(int n, symbol name, symbol external_name) +{ + assert(n >= 0); + name = get_font_translation(name); + if (external_name.is_null()) + external_name = name; + else + external_name = get_font_translation(external_name); + return mount_font_no_translate(n, name, external_name); +} + +void mount_style(int n, symbol name) +{ + assert(n >= 0); + if (n >= font_table_size) { + if (n - font_table_size > 1000) { + error("font position too much larger than first unused position"); + return; + } + grow_font_table(n); + } + else if (font_table[n] != 0) + delete font_table[n]; + font_table[n] = new font_info(get_font_translation(name), n, NULL_SYMBOL, 0); + font_family::invalidate_fontno(n); +} + +/* global functions */ + +void font_translate() +{ + symbol from = get_name(1); + if (!from.is_null()) { + symbol to = get_name(); + if (to.is_null() || from == to) + font_translation_dictionary.remove(from); + else + font_translation_dictionary.lookup(from, (void *)to.contents()); + } + skip_line(); +} + +void font_position() +{ + int n; + if (get_integer(&n)) { + if (n < 0) + error("negative font position"); + else { + symbol internal_name = get_name(1); + if (!internal_name.is_null()) { + symbol external_name = get_long_name(0); + mount_font(n, internal_name, external_name); // ignore error + } + } + } + skip_line(); +} + +font_family::font_family(symbol s) +: map_size(10), nm(s) +{ + map = new int[map_size]; + for (int i = 0; i < map_size; i++) + map[i] = -1; +} + +font_family::~font_family() +{ + a_delete map; +} + +int font_family::make_definite(int i) +{ + if (i >= 0) { + if (i < map_size && map[i] >= 0) + return map[i]; + else { + if (i < font_table_size && font_table[i] != 0) { + if (i >= map_size) { + int old_map_size = map_size; + int *old_map = map; + map_size *= 3; + map_size /= 2; + if (i >= map_size) + map_size = i + 10; + map = new int[map_size]; + memcpy(map, old_map, old_map_size*sizeof(int)); + a_delete old_map; + for (int j = old_map_size; j < map_size; j++) + map[j] = -1; + } + if (font_table[i]->is_style()) { + symbol sty = font_table[i]->get_name(); + symbol f = concat(nm, sty); + int n; + // don't use symbol_fontno, because that might return a style + // and because we don't want to translate the name + for (n = 0; n < font_table_size; n++) + if (font_table[n] != 0 && font_table[n]->is_named(f) + && !font_table[n]->is_style()) + break; + if (n >= font_table_size) { + n = next_available_font_position(); + if (!mount_font_no_translate(n, f, f)) + return -1; + } + return map[i] = n; + } + else + return map[i] = i; + } + else + return -1; + } + } + else + return -1; +} + +dictionary family_dictionary(5); + +font_family *lookup_family(symbol nm) +{ + font_family *f = (font_family *)family_dictionary.lookup(nm); + if (!f) { + f = new font_family(nm); + (void)family_dictionary.lookup(nm, f); + } + return f; +} + +void font_family::invalidate_fontno(int n) +{ + assert(n >= 0 && n < font_table_size); + dictionary_iterator iter(family_dictionary); + symbol nm; + font_family *fam; + while (iter.get(&nm, (void **)&fam)) { + int map_size = fam->map_size; + if (n < map_size) + fam->map[n] = -1; + for (int i = 0; i < map_size; i++) + if (fam->map[i] == n) + fam->map[i] = -1; + } +} + +void style() +{ + int n; + if (get_integer(&n)) { + if (n < 0) + error("negative font position"); + else { + symbol internal_name = get_name(1); + if (!internal_name.is_null()) + mount_style(n, internal_name); + } + } + skip_line(); +} + +static int get_fontno() +{ + int n; + tok.skip(); + if (tok.delimiter()) { + symbol s = get_name(1); + if (!s.is_null()) { + n = symbol_fontno(s); + if (n < 0) { + n = next_available_font_position(); + if (!mount_font(n, s)) + return -1; + } + return curenv->get_family()->make_definite(n); + } + } + else if (get_integer(&n)) { + if (n < 0 || n >= font_table_size || font_table[n] == 0) + error("bad font number"); + else + return curenv->get_family()->make_definite(n); + } + return -1; +} + +static int underline_fontno = 2; + +void underline_font() +{ + int n = get_fontno(); + if (n >= 0) + underline_fontno = n; + skip_line(); +} + +int get_underline_fontno() +{ + return underline_fontno; +} + +static void read_special_fonts(special_font_list **sp) +{ + special_font_list *s = *sp; + *sp = 0; + while (s != 0) { + special_font_list *tem = s; + s = s->next; + delete tem; + } + special_font_list **p = sp; + while (has_arg()) { + int i = get_fontno(); + if (i >= 0) { + special_font_list *tem = new special_font_list; + tem->n = i; + tem->next = 0; + *p = tem; + p = &(tem->next); + } + } +} + +void font_special_request() +{ + int n = get_fontno(); + if (n >= 0) + read_special_fonts(&font_table[n]->sf); + skip_line(); +} + +void special_request() +{ + read_special_fonts(&global_special_fonts); + skip_line(); +} + +int next_available_font_position() +{ + int i; + for (i = 1; i < font_table_size && font_table[i] != 0; i++) + ; + return i; +} + +int symbol_fontno(symbol s) +{ + s = get_font_translation(s); + for (int i = 0; i < font_table_size; i++) + if (font_table[i] != 0 && font_table[i]->is_named(s)) + return i; + return -1; +} + +int is_good_fontno(int n) +{ + return n >= 0 && n < font_table_size && font_table[n] != NULL; +} + +int get_bold_fontno(int n) +{ + if (n >= 0 && n < font_table_size && font_table[n] != 0) { + hunits offset; + if (font_table[n]->get_bold(&offset)) + return offset.to_units() + 1; + else + return 0; + } + else + return 0; +} + +hunits env_digit_width(environment *env) +{ + node *n = make_glyph_node(charset_table['0'], env); + if (n) { + hunits x = n->width(); + delete n; + return x; + } + else + return H0; +} + +hunits env_space_width(environment *env) +{ + int fn = env_definite_font(env); + font_size fs = env->get_font_size(); + if (fn < 0 || fn >= font_table_size || font_table[fn] == 0) + return scale(fs.to_units()/3, env->get_space_size(), 12); + else + return font_table[fn]->get_space_width(fs, env->get_space_size()); +} + +hunits env_sentence_space_width(environment *env) +{ + int fn = env_definite_font(env); + font_size fs = env->get_font_size(); + if (fn < 0 || fn >= font_table_size || font_table[fn] == 0) + return scale(fs.to_units()/3, env->get_sentence_space_size(), 12); + else + return font_table[fn]->get_space_width(fs, env->get_sentence_space_size()); +} + +hunits env_half_narrow_space_width(environment *env) +{ + int fn = env_definite_font(env); + font_size fs = env->get_font_size(); + if (fn < 0 || fn >= font_table_size || font_table[fn] == 0) + return 0; + else + return font_table[fn]->get_half_narrow_space_width(fs); +} + +hunits env_narrow_space_width(environment *env) +{ + int fn = env_definite_font(env); + font_size fs = env->get_font_size(); + if (fn < 0 || fn >= font_table_size || font_table[fn] == 0) + return 0; + else + return font_table[fn]->get_narrow_space_width(fs); +} + +void bold_font() +{ + int n = get_fontno(); + if (n >= 0) { + if (has_arg()) { + if (tok.delimiter()) { + int f = get_fontno(); + if (f >= 0) { + units offset; + if (has_arg() && get_number(&offset, 'u') && offset >= 1) + font_table[f]->set_conditional_bold(n, hunits(offset - 1)); + else + font_table[f]->conditional_unbold(n); + } + } + else { + units offset; + if (get_number(&offset, 'u') && offset >= 1) + font_table[n]->set_bold(hunits(offset - 1)); + else + font_table[n]->unbold(); + } + } + else + font_table[n]->unbold(); + } + skip_line(); +} + +track_kerning_function::track_kerning_function() : non_zero(0) +{ +} + +track_kerning_function::track_kerning_function(int min_s, hunits min_a, + int max_s, hunits max_a) +: non_zero(1), min_size(min_s), min_amount(min_a), max_size(max_s), + max_amount(max_a) +{ +} + +int track_kerning_function::operator==(const track_kerning_function &tk) +{ + if (non_zero) + return (tk.non_zero + && min_size == tk.min_size + && min_amount == tk.min_amount + && max_size == tk.max_size + && max_amount == tk.max_amount); + else + return !tk.non_zero; +} + +int track_kerning_function::operator!=(const track_kerning_function &tk) +{ + if (non_zero) + return (!tk.non_zero + || min_size != tk.min_size + || min_amount != tk.min_amount + || max_size != tk.max_size + || max_amount != tk.max_amount); + else + return tk.non_zero; +} + +hunits track_kerning_function::compute(int size) +{ + if (non_zero) { + if (max_size <= min_size) + return min_amount; + else if (size <= min_size) + return min_amount; + else if (size >= max_size) + return max_amount; + else + return (scale(max_amount, size - min_size, max_size - min_size) + + scale(min_amount, max_size - size, max_size - min_size)); + } + else + return H0; +} + +void track_kern() +{ + int n = get_fontno(); + if (n >= 0) { + int min_s, max_s; + hunits min_a, max_a; + if (has_arg() + && get_number(&min_s, 'z') + && get_hunits(&min_a, 'p') + && get_number(&max_s, 'z') + && get_hunits(&max_a, 'p')) { + track_kerning_function tk(min_s, min_a, max_s, max_a); + font_table[n]->set_track_kern(tk); + } + else { + track_kerning_function tk; + font_table[n]->set_track_kern(tk); + } + } + skip_line(); +} + +void constant_space() +{ + int n = get_fontno(); + if (n >= 0) { + int x, y; + if (!has_arg() || !get_integer(&x)) + font_table[n]->set_constant_space(CONSTANT_SPACE_NONE); + else { + if (!has_arg() || !get_number(&y, 'z')) + font_table[n]->set_constant_space(CONSTANT_SPACE_RELATIVE, x); + else + font_table[n]->set_constant_space(CONSTANT_SPACE_ABSOLUTE, + scale(y*x, + units_per_inch, + 36*72*sizescale)); + } + } + skip_line(); +} + +void ligature() +{ + int lig; + if (has_arg() && get_integer(&lig) && lig >= 0 && lig <= 2) + global_ligature_mode = lig; + else + global_ligature_mode = 1; + skip_line(); +} + +void kern_request() +{ + int k; + if (has_arg() && get_integer(&k)) + global_kern_mode = k != 0; + else + global_kern_mode = 1; + skip_line(); +} + +void set_soft_hyphen_char() +{ + soft_hyphen_char = get_optional_char(); + if (!soft_hyphen_char) + soft_hyphen_char = get_charinfo(HYPHEN_SYMBOL); + skip_line(); +} + +void init_output() +{ + if (suppress_output_flag) + the_output = new suppress_output_file; + else if (ascii_output_flag) + the_output = new ascii_output_file; + else + the_output = new troff_output_file; +} + +class next_available_font_position_reg : public reg { +public: + const char *get_string(); +}; + +const char *next_available_font_position_reg::get_string() +{ + return i_to_a(next_available_font_position()); +} + +class printing_reg : public reg { +public: + const char *get_string(); +}; + +const char *printing_reg::get_string() +{ + if (the_output) + return the_output->is_printing() ? "1" : "0"; + else + return "0"; +} + +void init_node_requests() +{ + init_request("fp", font_position); + init_request("sty", style); + init_request("cs", constant_space); + init_request("bd", bold_font); + init_request("uf", underline_font); + init_request("lg", ligature); + init_request("kern", kern_request); + init_request("tkf", track_kern); + init_request("special", special_request); + init_request("fspecial", font_special_request); + init_request("ftr", font_translate); + init_request("shc", set_soft_hyphen_char); + number_reg_dictionary.define(".fp", new next_available_font_position_reg); + number_reg_dictionary.define(".kern", + new constant_int_reg(&global_kern_mode)); + number_reg_dictionary.define(".lg", + new constant_int_reg(&global_ligature_mode)); + number_reg_dictionary.define(".P", new printing_reg); + soft_hyphen_char = get_charinfo(HYPHEN_SYMBOL); +} diff --git a/contrib/groff/src/roff/troff/node.h b/contrib/groff/src/roff/troff/node.h new file mode 100644 index 0000000..3e5f615 --- /dev/null +++ b/contrib/groff/src/roff/troff/node.h @@ -0,0 +1,584 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001 + Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +struct hyphen_list { + unsigned char hyphen; + unsigned char breakable; + unsigned char hyphenation_code; + hyphen_list *next; + hyphen_list(unsigned char code, hyphen_list *p = 0); +}; + +void hyphenate(hyphen_list *, unsigned); + +enum hyphenation_type { HYPHEN_MIDDLE, HYPHEN_BOUNDARY, HYPHEN_INHIBIT }; + +class ascii_output_file; + +struct breakpoint; +struct vertical_size; +struct charinfo; + +class macro; + +class troff_output_file; +class tfont; +class environment; + +class glyph_node; +class diverted_space_node; +class token_node; + +struct node { + node *next; + node *last; + node(); + node(node *n); + node *add_char(charinfo *c, environment *, hunits *widthp, int *spacep); + + virtual ~node(); + virtual node *copy() = 0; + virtual int set_unformat_flag(); + virtual int force_tprint() = 0; + virtual hunits width(); + virtual hunits subscript_correction(); + virtual hunits italic_correction(); + virtual hunits left_italic_correction(); + virtual hunits skew(); + virtual int nspaces(); + virtual int merge_space(hunits, hunits, hunits); + virtual vunits vertical_width(); + virtual node *last_char_node(); + virtual void vertical_extent(vunits *min, vunits *max); + virtual int character_type(); + virtual void set_vertical_size(vertical_size *); + virtual int ends_sentence(); + virtual node *merge_self(node *); + virtual node *add_discretionary_hyphen(); + virtual node *add_self(node *, hyphen_list **); + virtual hyphen_list *get_hyphen_list(hyphen_list *s = 0); + virtual void ascii_print(ascii_output_file *); + virtual void asciify(macro *); + virtual int discardable(); + virtual void spread_space(int *, hunits *); + virtual void freeze_space(); + virtual void is_escape_colon(); + virtual breakpoint *get_breakpoints(hunits width, int nspaces, + breakpoint *rest = 0, + int is_inner = 0); + virtual int nbreaks(); + virtual void split(int, node **, node **); + virtual hyphenation_type get_hyphenation_type(); + virtual int reread(int *); + virtual token_node *get_token_node(); + virtual int overlaps_vertically(); + virtual int overlaps_horizontally(); + virtual units size(); + virtual int interpret(macro *); + + virtual node *merge_glyph_node(glyph_node *); + virtual tfont *get_tfont(); + virtual void tprint(troff_output_file *); + virtual void zero_width_tprint(troff_output_file *); + + node *add_italic_correction(hunits *); + + virtual int same(node *) = 0; + virtual const char *type() = 0; +}; + +inline node::node() : next(0) +{ +} + +inline node::node(node *n) : next(n) +{ +} + +inline node::~node() +{ +} + +// 0 means it doesn't, 1 means it does, 2 means it's transparent + +int node_list_ends_sentence(node *); + +struct breakpoint { + breakpoint *next; + hunits width; + int nspaces; + node *nd; + int index; + char hyphenated; +}; + +class line_start_node : public node { +public: + line_start_node() {} + node *copy() { return new line_start_node; } + int same(node *); + int force_tprint(); + const char *type(); + void asciify(macro *); +}; + +class space_node : public node { +private: +#if 0 + enum { BLOCK = 1024 }; + static space_node *free_list; + void operator delete(void *); +#endif +protected: + hunits n; + char set; + char was_escape_colon; + space_node(hunits, int, int, node * = 0); +public: + space_node(hunits d, node *p = 0); +#if 0 + ~space_node(); + void *operator new(size_t); +#endif + node *copy(); + int nspaces(); + hunits width(); + int discardable(); + int merge_space(hunits, hunits, hunits); + void freeze_space(); + void is_escape_colon(); + void spread_space(int *, hunits *); + void tprint(troff_output_file *); + breakpoint *get_breakpoints(hunits width, int nspaces, breakpoint *rest = 0, + int is_inner = 0); + int nbreaks(); + void split(int, node **, node **); + void ascii_print(ascii_output_file *); + int same(node *); + void asciify(macro *); + const char *type(); + int force_tprint(); + hyphenation_type get_hyphenation_type(); +}; + +struct width_list { + width_list *next; + hunits width; + hunits sentence_width; + width_list(hunits, hunits); + width_list(width_list *); +}; + +class word_space_node : public space_node { +protected: + width_list *orig_width; + unsigned char unformat; + word_space_node(hunits, int, width_list *, int, node * = 0); +public: + word_space_node(hunits, width_list *, node * = 0); + ~word_space_node(); + node *copy(); + int reread(int *); + int set_unformat_flag(); + void tprint(troff_output_file *); + int same(node *); + void asciify(macro *); + const char *type(); + int merge_space(hunits, hunits, hunits); + int force_tprint(); +}; + +class unbreakable_space_node : public word_space_node { + unbreakable_space_node(hunits, int, node * = 0); +public: + unbreakable_space_node(hunits, node * = 0); + node *copy(); + int reread(int *); + int same(node *); + void asciify(macro *); + const char *type(); + int force_tprint(); + breakpoint *get_breakpoints(hunits width, int nspaces, breakpoint *rest = 0, + int is_inner = 0); + int nbreaks(); + void split(int, node **, node **); + int merge_space(hunits, hunits, hunits); + node *add_self(node *, hyphen_list **); + hyphen_list *get_hyphen_list(hyphen_list *ss = 0); + hyphenation_type get_hyphenation_type(); +}; + +class diverted_space_node : public node { +public: + vunits n; + diverted_space_node(vunits d, node *p = 0); + node *copy(); + int reread(int *); + int same(node *); + const char *type(); + int force_tprint(); +}; + +class diverted_copy_file_node : public node { + symbol filename; +public: + vunits n; + diverted_copy_file_node(symbol s, node *p = 0); + node *copy(); + int reread(int *); + int same(node *); + const char *type(); + int force_tprint(); +}; + +class extra_size_node : public node { + vunits n; +public: + extra_size_node(vunits i) : n(i) {} + void set_vertical_size(vertical_size *); + node *copy(); + int same(node *); + const char *type(); + int force_tprint(); +}; + +class vertical_size_node : public node { + vunits n; +public: + vertical_size_node(vunits i) : n(i) {} + void set_vertical_size(vertical_size *); + void asciify(macro *); + node *copy(); + int set_unformat_flag(); + int same(node *); + const char *type(); + int force_tprint(); +}; + +class hmotion_node : public node { +protected: + hunits n; + unsigned char was_tab; + unsigned char unformat; +public: + hmotion_node(hunits i, node *next = 0) + : node(next), n(i), was_tab(0), unformat(0) {} + hmotion_node(hunits i, int flag1, int flag2, node *next = 0) + : node(next), n(i), was_tab(flag1), unformat(flag2) {} + node *copy(); + int reread(int *); + int set_unformat_flag(); + void asciify(macro *); + void tprint(troff_output_file *); + hunits width(); + void ascii_print(ascii_output_file *); + int same(node *); + const char *type(); + int force_tprint(); + node *add_self(node *, hyphen_list **); + hyphen_list *get_hyphen_list(hyphen_list *ss = 0); + hyphenation_type get_hyphenation_type(); +}; + +class space_char_hmotion_node : public hmotion_node { +public: + space_char_hmotion_node(hunits i, node *next = 0); + node *copy(); + void ascii_print(ascii_output_file *); + void asciify(macro *); + int same(node *); + const char *type(); + int force_tprint(); + node *add_self(node *, hyphen_list **); + hyphen_list *get_hyphen_list(hyphen_list *ss = 0); + hyphenation_type get_hyphenation_type(); +}; + +class vmotion_node : public node { + vunits n; +public: + vmotion_node(vunits i) : n(i) {} + void tprint(troff_output_file *); + node *copy(); + vunits vertical_width(); + int same(node *); + const char *type(); + int force_tprint(); +}; + +class hline_node : public node { + hunits x; + node *n; +public: + hline_node(hunits i, node *c, node *next = 0) : node(next), x(i), n(c) {} + ~hline_node(); + node *copy(); + hunits width(); + void tprint(troff_output_file *); + int same(node *); + const char *type(); + int force_tprint(); +}; + +class vline_node : public node { + vunits x; + node *n; +public: + vline_node(vunits i, node *c, node *next= 0) : node(next), x(i), n(c) {} + ~vline_node(); + node *copy(); + void tprint(troff_output_file *); + hunits width(); + vunits vertical_width(); + void vertical_extent(vunits *, vunits *); + int same(node *); + const char *type(); + int force_tprint(); +}; + + +class dummy_node : public node { +public: + dummy_node(node *nd = 0) : node(nd) {} + node *copy(); + int same(node *); + const char *type(); + int force_tprint(); + hyphenation_type get_hyphenation_type(); +}; + +class transparent_dummy_node : public node { +public: + transparent_dummy_node(node *nd = 0) : node(nd) {} + node *copy(); + int same(node *); + const char *type(); + int force_tprint(); + int ends_sentence(); + hyphenation_type get_hyphenation_type(); +}; + +class zero_width_node : public node { + node *n; +public: + zero_width_node(node *gn); + ~zero_width_node(); + node *copy(); + void tprint(troff_output_file *); + int same(node *); + const char *type(); + int force_tprint(); + void append(node *); + int character_type(); + void vertical_extent(vunits *min, vunits *max); +}; + +class left_italic_corrected_node : public node { + node *n; + hunits x; +public: + left_italic_corrected_node(node * = 0); + ~left_italic_corrected_node(); + void tprint(troff_output_file *); + void ascii_print(ascii_output_file *); + void asciify(macro *); + node *copy(); + int same(node *); + const char *type(); + int force_tprint(); + hunits width(); + node *last_char_node(); + void vertical_extent(vunits *, vunits *); + int ends_sentence(); + int overlaps_horizontally(); + int overlaps_vertically(); + hyphenation_type get_hyphenation_type(); + tfont *get_tfont(); + int character_type(); + hunits skew(); + hunits italic_correction(); + hunits subscript_correction(); + hyphen_list *get_hyphen_list(hyphen_list *ss = 0); + node *add_self(node *, hyphen_list **); + node *merge_glyph_node(glyph_node *); +}; + +class overstrike_node : public node { + node *list; + hunits max_width; +public: + overstrike_node(); + ~overstrike_node(); + node *copy(); + void tprint(troff_output_file *); + void overstrike(node *); // add another node to be overstruck + hunits width(); + int same(node *); + const char *type(); + int force_tprint(); + node *add_self(node *, hyphen_list **); + hyphen_list *get_hyphen_list(hyphen_list *ss = 0); + hyphenation_type get_hyphenation_type(); +}; + +class bracket_node : public node { + node *list; + hunits max_width; +public: + bracket_node(); + ~bracket_node(); + node *copy(); + void tprint(troff_output_file *); + void bracket(node *); // add another node to be overstruck + hunits width(); + int same(node *); + const char *type(); + int force_tprint(); +}; + +class special_node : public node { + macro mac; + tfont *tf; + int no_init_string; + void tprint_start(troff_output_file *); + void tprint_char(troff_output_file *, unsigned char); + void tprint_end(troff_output_file *); +public: + special_node(const macro &, int = 0); + special_node(const macro &, tfont *, int = 0); + node *copy(); + void tprint(troff_output_file *); + int same(node *); + const char *type(); + int force_tprint(); + int ends_sentence(); + tfont *get_tfont(); +}; + +class suppress_node : public node { + int is_on; + int emit_limits; // must we issue the extent of the area written out? + symbol filename; + char position; +public: + suppress_node(int, int); + suppress_node(symbol f, char p); + suppress_node(int, int, symbol f, char p); + node *copy(); + void tprint(troff_output_file *); + hunits width(); + int same(node *); + const char *type(); + int force_tprint(); +private: + void put(troff_output_file *out, const char *s); +}; + +struct hvpair { + hunits h; + vunits v; + hvpair(); +}; + +class draw_node : public node { + int npoints; + font_size sz; + char code; + hvpair *point; +public: + draw_node(char, hvpair *, int, font_size); + ~draw_node(); + hunits width(); + vunits vertical_width(); + node *copy(); + void tprint(troff_output_file *); + int same(node *); + const char *type(); + int force_tprint(); +}; + +class charinfo; +node *make_node(charinfo *ci, environment *); +int character_exists(charinfo *, environment *); + +int same_node_list(node *n1, node *n2); +node *reverse_node_list(node *n); +void delete_node_list(node *); +node *copy_node_list(node *); + +int get_bold_fontno(int f); + +inline hyphen_list::hyphen_list(unsigned char code, hyphen_list *p) +: hyphen(0), breakable(0), hyphenation_code(code), next(p) +{ +} + +extern void read_desc(); +extern int mount_font(int n, symbol, symbol = NULL_SYMBOL); +extern void mount_style(int n, symbol); +extern int is_good_fontno(int n); +extern int symbol_fontno(symbol); +extern int next_available_font_position(); +extern void init_size_table(int *); +extern int get_underline_fontno(); + +class output_file { + char make_g_plus_plus_shut_up; +public: + output_file(); + virtual ~output_file(); + virtual void trailer(vunits page_length); + virtual void flush() = 0; + virtual void transparent_char(unsigned char) = 0; + virtual void print_line(hunits x, vunits y, node *n, + vunits before, vunits after, hunits width) = 0; + virtual void begin_page(int pageno, vunits page_length) = 0; + virtual void copy_file(hunits x, vunits y, const char *filename) = 0; + virtual int is_printing() = 0; + virtual void put_filename(const char *filename); + virtual void on(); + virtual void off(); +#ifdef COLUMN + virtual void vjustify(vunits, symbol); +#endif /* COLUMN */ +}; + +#ifndef POPEN_MISSING +extern char *pipe_command; +#endif + +extern output_file *the_output; +extern void init_output(); +int in_output_page_list(int n); + +class font_family { + int *map; + int map_size; +public: + const symbol nm; + font_family(symbol); + ~font_family(); + int make_definite(int); + static void invalidate_fontno(int); +}; + +font_family *lookup_family(symbol); diff --git a/contrib/groff/src/roff/troff/number.cc b/contrib/groff/src/roff/troff/number.cc new file mode 100644 index 0000000..5393842 --- /dev/null +++ b/contrib/groff/src/roff/troff/number.cc @@ -0,0 +1,692 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2001 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +#include "troff.h" +#include "symbol.h" +#include "hvunits.h" +#include "env.h" +#include "token.h" +#include "div.h" + +vunits V0; +hunits H0; + +int hresolution = 1; +int vresolution = 1; +int units_per_inch; +int sizescale; + +static int parse_expr(units *v, int scale_indicator, + int parenthesised, int rigid = 0); +static int start_number(); + +int get_vunits(vunits *res, unsigned char si) +{ + if (!start_number()) + return 0; + units x; + if (parse_expr(&x, si, 0)) { + *res = vunits(x); + return 1; + } + else + return 0; +} + +int get_hunits(hunits *res, unsigned char si) +{ + if (!start_number()) + return 0; + units x; + if (parse_expr(&x, si, 0)) { + *res = hunits(x); + return 1; + } + else + return 0; +} + +// for \B + +int get_number_rigidly(units *res, unsigned char si) +{ + if (!start_number()) + return 0; + units x; + if (parse_expr(&x, si, 0, 1)) { + *res = x; + return 1; + } + else + return 0; +} + +int get_number(units *res, unsigned char si) +{ + if (!start_number()) + return 0; + units x; + if (parse_expr(&x, si, 0)) { + *res = x; + return 1; + } + else + return 0; +} + +int get_integer(int *res) +{ + if (!start_number()) + return 0; + units x; + if (parse_expr(&x, 0, 0)) { + *res = x; + return 1; + } + else + return 0; +} + +enum incr_number_result { BAD, ABSOLUTE, INCREMENT, DECREMENT }; + +static incr_number_result get_incr_number(units *res, unsigned char); + +int get_vunits(vunits *res, unsigned char si, vunits prev_value) +{ + units v; + switch (get_incr_number(&v, si)) { + case BAD: + return 0; + case ABSOLUTE: + *res = v; + break; + case INCREMENT: + *res = prev_value + v; + break; + case DECREMENT: + *res = prev_value - v; + break; + default: + assert(0); + } + return 1; +} + +int get_hunits(hunits *res, unsigned char si, hunits prev_value) +{ + units v; + switch (get_incr_number(&v, si)) { + case BAD: + return 0; + case ABSOLUTE: + *res = v; + break; + case INCREMENT: + *res = prev_value + v; + break; + case DECREMENT: + *res = prev_value - v; + break; + default: + assert(0); + } + return 1; +} + +int get_number(units *res, unsigned char si, units prev_value) +{ + units v; + switch (get_incr_number(&v, si)) { + case BAD: + return 0; + case ABSOLUTE: + *res = v; + break; + case INCREMENT: + *res = prev_value + v; + break; + case DECREMENT: + *res = prev_value - v; + break; + default: + assert(0); + } + return 1; +} + +int get_integer(int *res, int prev_value) +{ + units v; + switch (get_incr_number(&v, 0)) { + case BAD: + return 0; + case ABSOLUTE: + *res = v; + break; + case INCREMENT: + *res = prev_value + int(v); + break; + case DECREMENT: + *res = prev_value - int(v); + break; + default: + assert(0); + } + return 1; +} + + +static incr_number_result get_incr_number(units *res, unsigned char si) +{ + if (!start_number()) + return BAD; + incr_number_result result = ABSOLUTE; + if (tok.ch() == '+') { + tok.next(); + result = INCREMENT; + } + else if (tok.ch() == '-') { + tok.next(); + result = DECREMENT; + } + if (parse_expr(res, si, 0)) + return result; + else + return BAD; +} + +static int start_number() +{ + while (tok.space()) + tok.next(); + if (tok.newline()) { + warning(WARN_MISSING, "missing number"); + return 0; + } + if (tok.tab()) { + warning(WARN_TAB, "tab character where number expected"); + return 0; + } + if (tok.right_brace()) { + warning(WARN_RIGHT_BRACE, "`\\}' where number expected"); + return 0; + } + return 1; +} + +enum { OP_LEQ = 'L', OP_GEQ = 'G', OP_MAX = 'X', OP_MIN = 'N' }; + +#define SCALE_INDICATOR_CHARS "icPmnpuvMsz" + +static int parse_term(units *v, int scale_indicator, + int parenthesised, int rigid); + +static int parse_expr(units *v, int scale_indicator, + int parenthesised, int rigid) +{ + int result = parse_term(v, scale_indicator, parenthesised, rigid); + while (result) { + if (parenthesised) + tok.skip(); + int op = tok.ch(); + switch (op) { + case '+': + case '-': + case '/': + case '*': + case '%': + case ':': + case '&': + tok.next(); + break; + case '>': + tok.next(); + if (tok.ch() == '=') { + tok.next(); + op = OP_GEQ; + } + else if (tok.ch() == '?') { + tok.next(); + op = OP_MAX; + } + break; + case '<': + tok.next(); + if (tok.ch() == '=') { + tok.next(); + op = OP_LEQ; + } + else if (tok.ch() == '?') { + tok.next(); + op = OP_MIN; + } + break; + case '=': + tok.next(); + if (tok.ch() == '=') + tok.next(); + break; + default: + return result; + } + units v2; + if (!parse_term(&v2, scale_indicator, parenthesised, rigid)) + return 0; + int overflow = 0; + switch (op) { + case '<': + *v = *v < v2; + break; + case '>': + *v = *v > v2; + break; + case OP_LEQ: + *v = *v <= v2; + break; + case OP_GEQ: + *v = *v >= v2; + break; + case OP_MIN: + if (*v > v2) + *v = v2; + break; + case OP_MAX: + if (*v < v2) + *v = v2; + break; + case '=': + *v = *v == v2; + break; + case '&': + *v = *v > 0 && v2 > 0; + break; + case ':': + *v = *v > 0 || v2 > 0; + case '+': + if (v2 < 0) { + if (*v < INT_MIN - v2) + overflow = 1; + } + else if (v2 > 0) { + if (*v > INT_MAX - v2) + overflow = 1; + } + if (overflow) { + error("addition overflow"); + return 0; + } + *v += v2; + break; + case '-': + if (v2 < 0) { + if (*v > INT_MAX + v2) + overflow = 1; + } + else if (v2 > 0) { + if (*v < INT_MIN + v2) + overflow = 1; + } + if (overflow) { + error("subtraction overflow"); + return 0; + } + *v -= v2; + break; + case '*': + if (v2 < 0) { + if (*v > 0) { + if (*v > -(unsigned)INT_MIN / -(unsigned)v2) + overflow = 1; + } + else if (-(unsigned)*v > INT_MAX / -(unsigned)v2) + overflow = 1; + } + else if (v2 > 0) { + if (*v > 0) { + if (*v > INT_MAX / v2) + overflow = 1; + } + else if (-(unsigned)*v > -(unsigned)INT_MIN / v2) + overflow = 1; + } + if (overflow) { + error("multiplication overflow"); + return 0; + } + *v *= v2; + break; + case '/': + if (v2 == 0) { + error("division by zero"); + return 0; + } + *v /= v2; + break; + case '%': + if (v2 == 0) { + error("modulus by zero"); + return 0; + } + *v %= v2; + break; + default: + assert(0); + } + } + return result; +} + +static int parse_term(units *v, int scale_indicator, + int parenthesised, int rigid) +{ + int negative = 0; + for (;;) + if (parenthesised && tok.space()) + tok.next(); + else if (tok.ch() == '+') + tok.next(); + else if (tok.ch() == '-') { + tok.next(); + negative = !negative; + } + else + break; + unsigned char c = tok.ch(); + switch (c) { + case '|': + // | is not restricted to the outermost level + // tbl uses this + tok.next(); + if (!parse_term(v, scale_indicator, parenthesised, rigid)) + return 0; + int tem; + tem = (scale_indicator == 'v' + ? curdiv->get_vertical_position().to_units() + : curenv->get_input_line_position().to_units()); + if (tem >= 0) { + if (*v < INT_MIN + tem) { + error("numeric overflow"); + return 0; + } + } + else { + if (*v > INT_MAX + tem) { + error("numeric overflow"); + return 0; + } + } + *v -= tem; + if (negative) { + if (*v == INT_MIN) { + error("numeric overflow"); + return 0; + } + *v = -*v; + } + return 1; + case '(': + tok.next(); + c = tok.ch(); + if (c == ')') { + if (rigid) + return 0; + warning(WARN_SYNTAX, "empty parentheses"); + tok.next(); + *v = 0; + return 1; + } + else if (c != 0 && strchr(SCALE_INDICATOR_CHARS, c) != 0) { + tok.next(); + if (tok.ch() == ';') { + tok.next(); + scale_indicator = c; + } + else { + error("expected `;' after scale-indicator (got %1)", + tok.description()); + return 0; + } + } + else if (c == ';') { + scale_indicator = 0; + tok.next(); + } + if (!parse_expr(v, scale_indicator, 1, rigid)) + return 0; + tok.skip(); + if (tok.ch() != ')') { + if (rigid) + return 0; + warning(WARN_SYNTAX, "missing `)' (got %1)", tok.description()); + } + else + tok.next(); + if (negative) { + if (*v == INT_MIN) { + error("numeric overflow"); + return 0; + } + *v = -*v; + } + return 1; + case '.': + *v = 0; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + *v = 0; + do { + if (*v > INT_MAX/10) { + error("numeric overflow"); + return 0; + } + *v *= 10; + if (*v > INT_MAX - (int(c) - '0')) { + error("numeric overflow"); + return 0; + } + *v += c - '0'; + tok.next(); + c = tok.ch(); + } while (csdigit(c)); + break; + case '/': + case '*': + case '%': + case ':': + case '&': + case '>': + case '<': + case '=': + warning(WARN_SYNTAX, "empty left operand"); + *v = 0; + return rigid ? 0 : 1; + default: + warning(WARN_NUMBER, "numeric expression expected (got %1)", + tok.description()); + return 0; + } + int divisor = 1; + if (tok.ch() == '.') { + tok.next(); + for (;;) { + c = tok.ch(); + if (!csdigit(c)) + break; + // we may multiply the divisor by 254 later on + if (divisor <= INT_MAX/2540 && *v <= (INT_MAX - 9)/10) { + *v *= 10; + *v += c - '0'; + divisor *= 10; + } + tok.next(); + } + } + int si = scale_indicator; + int do_next = 0; + if ((c = tok.ch()) != 0 && strchr(SCALE_INDICATOR_CHARS, c) != 0) { + switch (scale_indicator) { + case 'z': + if (c != 'u' && c != 'z') { + warning(WARN_SCALE, + "only `z' and `u' scale indicators valid in this context"); + break; + } + si = c; + break; + case 0: + warning(WARN_SCALE, "scale indicator invalid in this context"); + break; + case 'u': + si = c; + break; + default: + if (c == 'z') { + warning(WARN_SCALE, "`z' scale indicator invalid in this context"); + break; + } + si = c; + break; + } + // Don't do tok.next() here because the next token might be \s, which + // would affect the interpretation of m. + do_next = 1; + } + switch (si) { + case 'i': + *v = scale(*v, units_per_inch, divisor); + break; + case 'c': + *v = scale(*v, units_per_inch*100, divisor*254); + break; + case 0: + case 'u': + if (divisor != 1) + *v /= divisor; + break; + case 'p': + *v = scale(*v, units_per_inch, divisor*72); + break; + case 'P': + *v = scale(*v, units_per_inch, divisor*6); + break; + case 'm': + { + // Convert to hunits so that with -Tascii `m' behaves as in nroff. + hunits em = curenv->get_size(); + *v = scale(*v, em.is_zero() ? hresolution : em.to_units(), divisor); + } + break; + case 'M': + { + hunits em = curenv->get_size(); + *v = scale(*v, em.is_zero() ? hresolution : em.to_units(), divisor*100); + } + break; + case 'n': + { + // Convert to hunits so that with -Tascii `n' behaves as in nroff. + hunits en = curenv->get_size()/2; + *v = scale(*v, en.is_zero() ? hresolution : en.to_units(), divisor); + } + break; + case 'v': + *v = scale(*v, curenv->get_vertical_spacing().to_units(), divisor); + break; + case 's': + while (divisor > INT_MAX/(sizescale*72)) { + divisor /= 10; + *v /= 10; + } + *v = scale(*v, units_per_inch, divisor*sizescale*72); + break; + case 'z': + *v = scale(*v, sizescale, divisor); + break; + default: + assert(0); + } + if (do_next) + tok.next(); + if (negative) { + if (*v == INT_MIN) { + error("numeric overflow"); + return 0; + } + *v = -*v; + } + return 1; +} + +units scale(units n, units x, units y) +{ + assert(x >= 0 && y > 0); + if (x == 0) + return 0; + if (n >= 0) { + if (n <= INT_MAX/x) + return (n*x)/y; + } + else { + if (-(unsigned)n <= -(unsigned)INT_MIN/x) + return (n*x)/y; + } + double res = n*double(x)/double(y); + if (res > INT_MAX) { + error("numeric overflow"); + return INT_MAX; + } + else if (res < INT_MIN) { + error("numeric overflow"); + return INT_MIN; + } + return int(res); +} + +vunits::vunits(units x) +{ + // don't depend on the rounding direction for division of negative integers + if (vresolution == 1) + n = x; + else + n = (x < 0 + ? -((-x + vresolution/2 - 1)/vresolution) + : (x + vresolution/2 - 1)/vresolution); +} + +hunits::hunits(units x) +{ + // don't depend on the rounding direction for division of negative integers + if (hresolution == 1) + n = x; + else + n = (x < 0 + ? -((-x + hresolution/2 - 1)/hresolution) + : (x + hresolution/2 - 1)/hresolution); +} diff --git a/contrib/groff/src/roff/troff/reg.cc b/contrib/groff/src/roff/troff/reg.cc new file mode 100644 index 0000000..254b0ff --- /dev/null +++ b/contrib/groff/src/roff/troff/reg.cc @@ -0,0 +1,473 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "troff.h" +#include "symbol.h" +#include "dictionary.h" +#include "token.h" +#include "request.h" +#include "reg.h" + +object_dictionary number_reg_dictionary(101); + +int reg::get_value(units * /*d*/) +{ + return 0; +} + +void reg::increment() +{ + error("can't increment read-only register"); +} + +void reg::decrement() +{ + error("can't decrement read-only register"); +} + +void reg::set_increment(units /*n*/) +{ + error("can't auto increment read-only register"); +} + +void reg::alter_format(char /*f*/, int /*w*/) +{ + error("can't alter format of read-only register"); +} + +const char *reg::get_format() +{ + return "0"; +} + +void reg::set_value(units /*n*/) +{ + error("can't write read-only register"); +} + +general_reg::general_reg() : format('1'), width(0), inc(0) +{ +} + +static char uppercase_array[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', +}; + +static char lowercase_array[] = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', +}; + +static const char *number_value_to_ascii(int value, char format, int width) +{ + static char buf[128]; // must be at least 21 + switch(format) { + case '1': + if (width <= 0) + return i_to_a(value); + else if (width > sizeof(buf) - 2) + sprintf(buf, "%.*d", int(sizeof(buf) - 2), int(value)); + else + sprintf(buf, "%.*d", width, int(value)); + break; + case 'i': + case 'I': + { + char *p = buf; + // troff uses z and w to represent 10000 and 5000 in Roman + // numerals; I can find no historical basis for this usage + const char *s = format == 'i' ? "zwmdclxvi" : "ZWMDCLXVI"; + int n = int(value); + if (n >= 40000 || n <= -40000) { + error("magnitude of `%1' too big for i or I format", n); + return i_to_a(n); + } + if (n == 0) { + *p++ = '0'; + *p = 0; + break; + } + if (n < 0) { + *p++ = '-'; + n = -n; + } + while (n >= 10000) { + *p++ = s[0]; + n -= 10000; + } + for (int i = 1000; i > 0; i /= 10, s += 2) { + int m = n/i; + n -= m*i; + switch (m) { + case 3: + *p++ = s[2]; + /* falls through */ + case 2: + *p++ = s[2]; + /* falls through */ + case 1: + *p++ = s[2]; + break; + case 4: + *p++ = s[2]; + *p++ = s[1]; + break; + case 8: + *p++ = s[1]; + *p++ = s[2]; + *p++ = s[2]; + *p++ = s[2]; + break; + case 7: + *p++ = s[1]; + *p++ = s[2]; + *p++ = s[2]; + break; + case 6: + *p++ = s[1]; + *p++ = s[2]; + break; + case 5: + *p++ = s[1]; + break; + case 9: + *p++ = s[2]; + *p++ = s[0]; + } + } + *p = 0; + break; + } + case 'a': + case 'A': + { + int n = value; + char *p = buf; + if (n == 0) { + *p++ = '0'; + *p = 0; + } + else { + if (n < 0) { + n = -n; + *p++ = '-'; + } + // this is a bit tricky + while (n > 0) { + int d = n % 26; + if (d == 0) + d = 26; + n -= d; + n /= 26; + *p++ = format == 'a' ? lowercase_array[d - 1] : + uppercase_array[d - 1]; + } + *p-- = 0; + char *q = buf[0] == '-' ? buf + 1 : buf; + while (q < p) { + char temp = *q; + *q = *p; + *p = temp; + --p; + ++q; + } + } + break; + } + default: + assert(0); + break; + } + return buf; +} + +const char *general_reg::get_string() +{ + units n; + if (!get_value(&n)) + return ""; + return number_value_to_ascii(n, format, width); +} + + +void general_reg::increment() +{ + int n; + if (get_value(&n)) + set_value(n + inc); +} + +void general_reg::decrement() +{ + int n; + if (get_value(&n)) + set_value(n - inc); +} + +void general_reg::set_increment(units n) +{ + inc = n; +} + +void general_reg::alter_format(char f, int w) +{ + format = f; + width = w; +} + +static const char *number_format_to_ascii(char format, int width) +{ + static char buf[24]; + if (format == '1') { + if (width > 0) { + int n = width; + if (n > int(sizeof(buf)) - 1) + n = int(sizeof(buf)) - 1; + sprintf(buf, "%.*d", n, 0); + return buf; + } + else + return "0"; + } + else { + buf[0] = format; + buf[1] = '\0'; + return buf; + } +} + +const char *general_reg::get_format() +{ + return number_format_to_ascii(format, width); +} + +class number_reg : public general_reg { + units value; +public: + number_reg(); + int get_value(units *); + void set_value(units); +}; + +number_reg::number_reg() : value(0) +{ +} + +int number_reg::get_value(units *res) +{ + *res = value; + return 1; +} + +void number_reg::set_value(units n) +{ + value = n; +} + +variable_reg::variable_reg(units *p) : ptr(p) +{ +} + +void variable_reg::set_value(units n) +{ + *ptr = n; +} + +int variable_reg::get_value(units *res) +{ + *res = *ptr; + return 1; +} + +void define_number_reg() +{ + symbol nm = get_name(1); + if (nm.is_null()) { + skip_line(); + return; + } + reg *r = (reg *)number_reg_dictionary.lookup(nm); + units v; + units prev_value; + if (!r || !r->get_value(&prev_value)) + prev_value = 0; + if (get_number(&v, 'u', prev_value)) { + if (r == 0) { + r = new number_reg; + number_reg_dictionary.define(nm, r); + } + r->set_value(v); + if (tok.space() && has_arg() && get_number(&v, 'u')) + r->set_increment(v); + } + skip_line(); +} + +#if 0 +void inline_define_reg() +{ + token start; + start.next(); + if (!start.delimiter(1)) + return; + tok.next(); + symbol nm = get_name(1); + if (nm.is_null()) + return; + reg *r = (reg *)number_reg_dictionary.lookup(nm); + if (r == 0) { + r = new number_reg; + number_reg_dictionary.define(nm, r); + } + units v; + units prev_value; + if (!r->get_value(&prev_value)) + prev_value = 0; + if (get_number(&v, 'u', prev_value)) { + r->set_value(v); + if (start != tok) { + if (get_number(&v, 'u')) { + r->set_increment(v); + if (start != tok) + warning(WARN_DELIM, "closing delimiter does not match"); + } + } + } +} +#endif + +void set_number_reg(symbol nm, units n) +{ + reg *r = (reg *)number_reg_dictionary.lookup(nm); + if (r == 0) { + r = new number_reg; + number_reg_dictionary.define(nm, r); + } + r->set_value(n); +} + +reg *lookup_number_reg(symbol nm) +{ + reg *r = (reg *)number_reg_dictionary.lookup(nm); + if (r == 0) { + warning(WARN_REG, "number register `%1' not defined", nm.contents()); + r = new number_reg; + number_reg_dictionary.define(nm, r); + } + return r; +} + +void alter_format() +{ + symbol nm = get_name(1); + if (nm.is_null()) { + skip_line(); + return; + } + reg *r = (reg *)number_reg_dictionary.lookup(nm); + if (r == 0) { + r = new number_reg; + number_reg_dictionary.define(nm, r); + } + tok.skip(); + char c = tok.ch(); + if (csdigit(c)) { + int n = 0; + do { + ++n; + tok.next(); + } while (csdigit(tok.ch())); + r->alter_format('1', n); + } + else if (c == 'i' || c == 'I' || c == 'a' || c == 'A') + r->alter_format(c); + else if (tok.newline() || tok.eof()) + warning(WARN_MISSING, "missing number register format"); + else + error("bad number register format (got %1)", tok.description()); + skip_line(); +} + +void remove_reg() +{ + for (;;) { + symbol s = get_name(); + if (s.is_null()) + break; + number_reg_dictionary.remove(s); + } + skip_line(); +} + +void alias_reg() +{ + symbol s1 = get_name(1); + if (!s1.is_null()) { + symbol s2 = get_name(1); + if (!s2.is_null()) { + if (!number_reg_dictionary.alias(s1, s2)) + warning(WARN_REG, "number register `%1' not defined", s2.contents()); + } + } + skip_line(); +} + +void rename_reg() +{ + symbol s1 = get_name(1); + if (!s1.is_null()) { + symbol s2 = get_name(1); + if (!s2.is_null()) + number_reg_dictionary.rename(s1, s2); + } + skip_line(); +} + +void print_number_regs() +{ + object_dictionary_iterator iter(number_reg_dictionary); + reg *r; + symbol s; + while (iter.get(&s, (object **)&r)) { + assert(!s.is_null()); + errprint("%1\t", s.contents()); + const char *p = r->get_string(); + if (p) + errprint(p); + errprint("\n"); + } + fflush(stderr); + skip_line(); +} + +void init_reg_requests() +{ + init_request("rr", remove_reg); + init_request("nr", define_number_reg); + init_request("af", alter_format); + init_request("aln", alias_reg); + init_request("rnn", rename_reg); + init_request("pnr", print_number_regs); +} diff --git a/contrib/groff/src/roff/troff/reg.h b/contrib/groff/src/roff/troff/reg.h new file mode 100644 index 0000000..8d403d4 --- /dev/null +++ b/contrib/groff/src/roff/troff/reg.h @@ -0,0 +1,76 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001 + Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +class reg : public object { +public: + virtual const char *get_string() = 0; + virtual int get_value(units *); + virtual void increment(); + virtual void decrement(); + virtual void set_increment(units); + virtual void alter_format(char f, int w = 0); + virtual const char *get_format(); + virtual void set_value(units); +}; + +class constant_int_reg : public reg { + int *p; +public: + constant_int_reg(int *); + const char *get_string(); +}; + +class general_reg : public reg { + char format; + int width; + int inc; +public: + general_reg(); + const char *get_string(); + void increment(); + void decrement(); + void alter_format(char f, int w = 0); + void set_increment(units); + const char *get_format(); + void add_value(units); + + void set_value(units) = 0; + int get_value(units *) = 0; +}; + +class variable_reg : public general_reg { + units *ptr; +public: + variable_reg(int *); + void set_value(units); + int get_value(units *); +}; + +extern object_dictionary number_reg_dictionary; +extern void set_number_reg(symbol nm, units n); +extern void check_output_limits(int x, int y); +extern void reset_output_registers (int miny); + +reg *lookup_number_reg(symbol); +#if 0 +void inline_define_reg(); +#endif diff --git a/contrib/groff/src/roff/troff/request.h b/contrib/groff/src/roff/troff/request.h new file mode 100644 index 0000000..5654b83 --- /dev/null +++ b/contrib/groff/src/roff/troff/request.h @@ -0,0 +1,84 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001 + Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +typedef void (*REQUEST_FUNCP)(); + +class macro; + +class request_or_macro : public object { +public: + request_or_macro(); + virtual void invoke(symbol s) = 0; + virtual macro *to_macro(); +}; + +class request : public request_or_macro { + REQUEST_FUNCP p; +public: + void invoke(symbol); + request(REQUEST_FUNCP); +}; + +void delete_request_or_macro(request_or_macro *); + +extern object_dictionary request_dictionary; + +struct macro_header; +struct node; + +class macro : public request_or_macro { + macro_header *p; + const char *filename; // where was it defined? + int lineno; + int length; +public: + macro(); + ~macro(); + macro(const macro &); + macro &operator=(const macro &); + void append(unsigned char); + void append(node *); + void append_unsigned(unsigned int i); + void append_int(int i); + void append_str(const char *); + void invoke(symbol); + macro *to_macro(); + void print_size(); + int empty(); + friend class string_iterator; + friend void chop_macro(); + friend void substring_macro(); + friend int operator==(const macro &, const macro &); +}; + +extern void init_input_requests(); +extern void init_markup_requests(); +extern void init_div_requests(); +extern void init_node_requests(); +extern void init_reg_requests(); +extern void init_env_requests(); +extern void init_hyphen_requests(); +extern void init_request(const char *s, REQUEST_FUNCP f); + +class charinfo; +class environment; + +node *charinfo_to_node_list(charinfo *, const environment *); diff --git a/contrib/groff/src/roff/troff/symbol.cc b/contrib/groff/src/roff/troff/symbol.cc new file mode 100644 index 0000000..ce09e39 --- /dev/null +++ b/contrib/groff/src/roff/troff/symbol.cc @@ -0,0 +1,150 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +#include "troff.h" +#include "symbol.h" + +const char **symbol::table = 0; +int symbol::table_used = 0; +int symbol::table_size = 0; +char *symbol::block = 0; +int symbol::block_size = 0; + +const symbol NULL_SYMBOL; + +#ifdef BLOCK_SIZE +#undef BLOCK_SIZE +#endif + +const int BLOCK_SIZE = 1024; +// the table will increase in size as necessary +// the size will be chosen from the following array +// add some more if you want +// I think it unlikely that we'll need more than a million symbols +static const unsigned int table_sizes[] = { +101, 503, 1009, 2003, 3001, 4001, 5003, 10007, 20011, 40009, 80021, 160001, 500009, 1000003, 0 +}; +const double FULL_MAX = 0.3; // don't let the table get more than this full + +static unsigned int hash_string(const char *p) +{ + // compute a hash code; this assumes 32-bit unsigned ints + // see p436 of Compilers by Aho, Sethi & Ullman + // give special treatment to two-character names + unsigned int hc = 0, g; + if (*p != 0) { + hc = *p++; + if (*p != 0) { + hc <<= 7; + hc += *p++; + for (; *p != 0; p++) { + hc <<= 4; + hc += *p; + if ((g = (hc & 0xf0000000)) == 0) { + hc ^= g >> 24; + hc ^= g; + } + } + } + } + return hc; +} + +// Tell compiler that a variable is intentionally unused. +inline void unused(void *) { } + +symbol::symbol(const char *p, int how) +{ + if (p == 0 || *p == 0) { + s = 0; + return; + } + if (table == 0) { + table_size = table_sizes[0]; + table = (const char **)new char*[table_size]; + for (int i = 0; i < table_size; i++) + table[i] = 0; + table_used = 0; + } + unsigned int hc = hash_string(p); + const char **pp; + for (pp = table + hc % table_size; + *pp != 0; + (pp == table ? pp = table + table_size - 1 : --pp)) + if (strcmp(p, *pp) == 0) { + s = *pp; + return; + } + if (how == MUST_ALREADY_EXIST) { + s = 0; + return; + } + if (table_used >= table_size - 1 || table_used >= table_size*FULL_MAX) { + const char **old_table = table; + unsigned int old_table_size = table_size; + int i; + for (i = 1; table_sizes[i] <= old_table_size; i++) + if (table_sizes[i] == 0) + fatal("too many symbols"); + table_size = table_sizes[i]; + table_used = 0; + table = (const char **)new char*[table_size]; + for (i = 0; i < table_size; i++) + table[i] = 0; + for (pp = old_table + old_table_size - 1; + pp >= old_table; + --pp) { + symbol temp(*pp, 1); /* insert it into the new table */ + unused(&temp); + } + a_delete old_table; + for (pp = table + hc % table_size; + *pp != 0; + (pp == table ? pp = table + table_size - 1 : --pp)) + ; + } + ++table_used; + if (how == DONT_STORE) { + s = *pp = p; + } + else { + int len = strlen(p)+1; + if (block == 0 || block_size < len) { + block_size = len > BLOCK_SIZE ? len : BLOCK_SIZE; + block = new char [block_size]; + } + (void)strcpy(block, p); + s = *pp = block; + block += len; + block_size -= len; + } +} + +symbol concat(symbol s1, symbol s2) +{ + char *buf = new char [strlen(s1.contents()) + strlen(s2.contents()) + 1]; + strcpy(buf, s1.contents()); + strcat(buf, s2.contents()); + symbol res(buf); + a_delete buf; + return res; +} + diff --git a/contrib/groff/src/roff/troff/symbol.h b/contrib/groff/src/roff/troff/symbol.h new file mode 100644 index 0000000..88e0fff --- /dev/null +++ b/contrib/groff/src/roff/troff/symbol.h @@ -0,0 +1,73 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#define DONT_STORE 1 +#define MUST_ALREADY_EXIST 2 + +class symbol { + static const char **table; + static int table_used; + static int table_size; + static char *block; + static int block_size; + const char *s; +public: + symbol(const char *p, int how = 0); + symbol(); + unsigned long hash() const; + int operator ==(symbol) const; + int operator !=(symbol) const; + const char *contents() const; + int is_null() const; +}; + + +extern const symbol NULL_SYMBOL; + +inline symbol::symbol() : s(0) +{ +} + +inline int symbol::operator==(symbol p) const +{ + return s == p.s; +} + +inline int symbol::operator!=(symbol p) const +{ + return s != p.s; +} + +inline unsigned long symbol::hash() const +{ + return (unsigned long)s; +} + +inline const char *symbol::contents() const +{ + return s; +} + +inline int symbol::is_null() const +{ + return s == 0; +} + +symbol concat(symbol, symbol); diff --git a/contrib/groff/src/roff/troff/token.h b/contrib/groff/src/roff/troff/token.h new file mode 100644 index 0000000..40283ad --- /dev/null +++ b/contrib/groff/src/roff/troff/token.h @@ -0,0 +1,218 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001 + Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +struct charinfo; +struct node; +struct vunits; + +class token { + symbol nm; + node *nd; + unsigned char c; + int val; + units dim; + enum token_type { + TOKEN_BACKSPACE, + TOKEN_BEGIN_TRAP, + TOKEN_CHAR, // a normal printing character + TOKEN_DUMMY, // \& + TOKEN_EMPTY, // this is the initial value + TOKEN_END_TRAP, + TOKEN_ESCAPE, // \e + TOKEN_HYPHEN_INDICATOR, + TOKEN_INTERRUPT, // \c + TOKEN_ITALIC_CORRECTION, // \/ + TOKEN_LEADER, // ^A + TOKEN_LEFT_BRACE, + TOKEN_MARK_INPUT, // \k -- `nm' is the name of the register + TOKEN_NEWLINE, // newline + TOKEN_NODE, + TOKEN_NUMBERED_CHAR, + TOKEN_PAGE_EJECTOR, + TOKEN_REQUEST, + TOKEN_RIGHT_BRACE, + TOKEN_SPACE, // ` ' -- ordinary space + TOKEN_SPECIAL, // a special character -- \' \` \- \(xx + TOKEN_SPREAD, // \p -- break and spread output line + TOKEN_STRETCHABLE_SPACE, // \~ + TOKEN_TAB, // tab + TOKEN_TRANSPARENT, // \! + TOKEN_TRANSPARENT_DUMMY, // \) + TOKEN_EOF // end of file + } type; +public: + token(); + ~token(); + token(const token &); + void operator=(const token &); + void next(); + void process(); + void skip(); + int eof(); + int nspaces(); // 1 if space, 2 if double space, 0 otherwise + int space(); // is the current token a space? + int stretchable_space(); // is the current token a stretchable space? + int white_space(); // is the current token space or tab? + int special(); // is the current token a special character? + int newline(); // is the current token a newline? + int tab(); // is the current token a tab? + int leader(); + int backspace(); + int delimiter(int warn = 0); // is it suitable for use as a delimiter? + int dummy(); + int transparent_dummy(); + int transparent(); + int left_brace(); + int right_brace(); + int page_ejector(); + int hyphen_indicator(); + int operator==(const token &); // need this for delimiters, and for conditions + int operator!=(const token &); // ditto + unsigned char ch(); + charinfo *get_char(int required = 0); + int add_to_node_list(node **); + int title(); + void make_space(); + void make_newline(); + const char *description(); + + friend void process_input_stack(); +}; + +extern token tok; // the current token + +extern symbol get_name(int required = 0); +extern symbol get_long_name(int required = 0); +extern charinfo *get_optional_char(); +extern void check_missing_character(); +extern void skip_line(); +extern void handle_initial_title(); + +struct hunits; +extern void read_title_parts(node **part, hunits *part_width); + +extern int get_number_rigidly(units *result, unsigned char si); + +extern int get_number(units *result, unsigned char si); +extern int get_integer(int *result); + +extern int get_number(units *result, unsigned char si, units prev_value); +extern int get_integer(int *result, int prev_value); + +void interpolate_number_reg(symbol, int); + +const char *asciify(int c); + +inline int token::newline() +{ + return type == TOKEN_NEWLINE; +} + +inline int token::space() +{ + return type == TOKEN_SPACE; +} + +inline int token::stretchable_space() +{ + return type == TOKEN_STRETCHABLE_SPACE; +} + +inline int token::special() +{ + return type == TOKEN_SPECIAL; +} + +inline int token::nspaces() +{ + if (type == TOKEN_SPACE) + return 1; + else + return 0; +} + +inline int token::white_space() +{ + return type == TOKEN_SPACE || type == TOKEN_TAB; +} + +inline int token::transparent() +{ + return type == TOKEN_TRANSPARENT; +} + +inline int token::page_ejector() +{ + return type == TOKEN_PAGE_EJECTOR; +} + +inline unsigned char token::ch() +{ + return type == TOKEN_CHAR ? c : 0; +} + +inline int token::eof() +{ + return type == TOKEN_EOF; +} + +inline int token::dummy() +{ + return type == TOKEN_DUMMY; +} + +inline int token::transparent_dummy() +{ + return type == TOKEN_TRANSPARENT_DUMMY; +} + +inline int token::left_brace() +{ + return type == TOKEN_LEFT_BRACE; +} + +inline int token::right_brace() +{ + return type == TOKEN_RIGHT_BRACE; +} + +inline int token::tab() +{ + return type == TOKEN_TAB; +} + +inline int token::leader() +{ + return type == TOKEN_LEADER; +} + +inline int token::backspace() +{ + return type == TOKEN_BACKSPACE; +} + +inline int token::hyphen_indicator() +{ + return type == TOKEN_HYPHEN_INDICATOR; +} + +int has_arg(); diff --git a/contrib/groff/src/roff/troff/troff.h b/contrib/groff/src/roff/troff/troff.h new file mode 100644 index 0000000..254c626 --- /dev/null +++ b/contrib/groff/src/roff/troff/troff.h @@ -0,0 +1,88 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001 + Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#include <stddef.h> +#include <stdlib.h> +#include <errno.h> + +#include "lib.h" +#include "assert.h" +#include "device.h" +#include "searchpath.h" + +void cleanup_and_exit(int n); + +typedef int units; + +extern units scale(units n, units x, units y); // scale n by x/y + +extern units units_per_inch; + +extern int ascii_output_flag; +extern int suppress_output_flag; +extern int is_html; + +extern int tcommand_flag; +extern int vresolution; +extern int hresolution; +extern int sizescale; + +extern search_path *mac_path; + +#include "cset.h" +#include "cmap.h" +#include "errarg.h" +#include "error.h" + +enum warning_type { + WARN_CHAR = 01, + WARN_NUMBER = 02, + WARN_BREAK = 04, + WARN_DELIM = 010, + WARN_EL = 020, + WARN_SCALE = 040, + WARN_RANGE = 0100, + WARN_SYNTAX = 0200, + WARN_DI = 0400, + WARN_MAC = 01000, + WARN_REG = 02000, + WARN_TAB = 04000, + WARN_RIGHT_BRACE = 010000, + WARN_MISSING = 020000, + WARN_INPUT = 040000, + WARN_ESCAPE = 0100000, + WARN_SPACE = 0200000, + WARN_FONT = 0400000, + WARN_IG = 01000000 + // change WARN_TOTAL if you add more warning types +}; + +const int WARN_TOTAL = 01777777; + +int warning(warning_type, const char *, + const errarg & = empty_errarg, + const errarg & = empty_errarg, + const errarg & = empty_errarg); diff --git a/contrib/groff/src/roff/troff/troff.man b/contrib/groff/src/roff/troff/troff.man new file mode 100644 index 0000000..7fe052b --- /dev/null +++ b/contrib/groff/src/roff/troff/troff.man @@ -0,0 +1,2462 @@ +.ig \"-*- nroff -*- +Copyright (C) 1989-2000, 2001 Free Software Foundation, Inc. + +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. + +Permission is granted to copy and distribute translations of this +manual into another language, under the above conditions for modified +versions, except that this permission notice may be included in +translations approved by the Free Software Foundation instead of in +the original English. +.. +. +.\" define a string tx for the TeX logo +.ie t .ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X +.el .ds tx TeX +. +.de TQ +.br +.ns +.TP \\$1 +.. +. +.\" Like TP, but if specified indent is more than half +.\" the current line-length - indent, use the default indent. +.de Tp +.ie \\n(.$=0:((0\\$1)*2u>(\\n(.lu-\\n(.iu)) .TP +.el .TP "\\$1" +.. +. +.\" The BSD man macros can't handle " in arguments to font change macros, +.\" so use \(ts instead of ". +.tr \(ts" +. +. +.TH @G@TROFF @MAN1EXT@ "@MDATE@" "Groff Version @VERSION@" +. +. +.SH NAME +. +. +@g@troff \- format documents +. +. +.SH SYNOPSIS +. +. +.nr a \n(.j +.ad l +.nr i \n(.i +.in +\w'\fB@g@troff 'u +.ti \niu +.B @g@troff +.de OP +.ie \\n(.$-1 .RI "[\ \fB\\$1\fP" "\\$2" "\ ]" +.el .RB "[\ " "\\$1" "\ ]" +.. +.OP \-abivzCERU +.OP \-w name +.OP \-W name +.OP \-d cs +.OP \-f fam +.OP \-m name +.OP \-n num +.OP \-o list +.OP \-r cn +.OP \-T name +.OP \-F dir +.OP \-M dir +.RI "[\ " files\|.\|.\|. "\ ]" +.br +.ad \na +.PP +It is possible to have whitespace between a command line option and its +parameter. +. +. +.SH DESCRIPTION +. +. +This manual page describes the GNU version of +.BR troff , +which is part of the groff document formatting system. +It is highly compatible with UNIX troff. +Usually it should be invoked using the groff command, which will +also run preprocessors and postprocessors in the appropriate +order and with the appropriate options. +. +. +.SH OPTIONS +. +. +.TP \w'\-dname=s'u+2n +.B \-a +Generate an +.SM ASCII +approximation of the typeset output. +.TP +.B \-b +Print a backtrace with each warning or error message. This backtrace +should help track down the cause of the error. The line numbers given +in the backtrace may not always be correct: +.BR troff 's +idea of line numbers +gets confused by +.B as +or +.B am +requests. +.TP +.B \-i +Read the standard input after all the named input files have been +processed. +.TP +.B \-v +Print the version number. +.TP +.BI \-w name +Enable warning +.IR name . +Available warnings are described in +the Warnings subsection below. +Multiple +.B \-w +options are allowed. +.TP +.BI \-W name +Inhibit warning +.IR name . +Multiple +.B \-W +options are allowed. +.TP +.B \-E +Inhibit all error messages. +.TP +.B \-z +Suppress formatted output. +.TP +.B \-C +Enable compatibility mode. +.TP +.BI \-d cs +.TQ +.BI \-d name = s +Define +.I c +or +.I name +to be a string +.IR s ; +.I c +must be a one letter name. +.TP +.BI \-f fam +Use +.I fam +as the default font family. +.TP +.BI \-m name +Read in the file +.IB name .tmac\fR. +If it isn't found, try +.BI tmac. name +instead. +It will be first searched for in directories given with the +.B \-M +command line option, then in directories given +in the +.B GROFF_TMAC_PATH +environment variable, then in the current directory (only if in unsafe +mode), the home directory, @SYSTEMMACRODIR@, @LOCALMACRODIR@, and +@MACRODIR@. +.TP +.B \-U +Unsafe mode. +This will enable the following requests: +.BR .open , +.BR .opena , +.BR .pso , +.BR .sy , +and +.BR .pi . +For security reasons, these potentially dangerous requests are disabled +otherwise. It will also add the current directory to the macro search path. +.TP +.B \-R +Don't load +.B troffrc +and +.BR troffrc-end . +.TP +.BI \-n num +Number the first page +.IR num . +.TP +.BI \-o list +Output only pages in +.IR list , +which is a comma-separated list of page ranges; +.I n +means print page +.IR n , +.IB m \- n +means print every page between +.I m +and +.IR n , +.BI \- n +means print every page up to +.IR n , +.IB n \- +means print every page from +.IR n . +.B Troff +will exit after printing the last page in the list. +.TP +.BI \-r cn +.TQ +.BI \-r name = n +Set number register +.I c +or +.I name +to +.IR n ; +.I c +must be a one character name; +.I n +can be any troff numeric expression. +.TP +.BI \-T name +Prepare output for device +.IR name , +rather than the default +.BR @DEVICE@ . +.TP +.BI \-F dir +Search in directory (or directory path) +.I dir +for subdirectories +.BI dev name +.RI ( name +is the name of the device) and there for the +.B DESC +file and font files. +.I dir +is scanned before all other font directories. +.TP +.BI \-M dir +Search directory (or directory path) +.I dir +for macro files. +This is scanned before all other macro directories. +. +. +.SH USAGE +. +. +Only the features not in UNIX troff are described here. +. +.SS Long names +. +The names of number registers, fonts, strings/macros/diversions, +special characters can be of any length. In escape sequences, where +you can use +.BI ( xx +for a two character name, you can use +.BI [ xxx ] +for a name of arbitrary length: +.TP +.BI \e[ xxx ] +Print the special character called +.IR xxx . +.TP +.BI \ef[ xxx ] +Set font +.IR xxx . +.TP +.BI \e*[ xxx ] +Interpolate string +.IR xxx . +.TP +.BI \en[ xxx ] +Interpolate number register +.IR xxx . +. +.SS Fractional pointsizes +. +A +.I +scaled point +is equal to 1/sizescale +points, where +sizescale is specified in the +.B DESC +file (1 by default). +There is a new scale indicator +.B z +which has the effect of multiplying by sizescale. +Requests and escape sequences in troff +interpret arguments that represent a pointsize as being in units +of scaled points, but they evaluate each such argument +using a default scale indicator of +.BR z . +Arguments treated in this way are +the argument to the +.B ps +request, +the third argument to the +.B cs +request, +the second and fourth arguments to the +.B tkf +request, +the argument to the +.B \eH +escape sequence, +and those variants of the +.B \es +escape sequence that take a numeric expression as their argument. +.LP +For example, suppose sizescale is 1000; +then a scaled point will be equivalent to a millipoint; +the request +.B .ps 10.25 +is equivalent to +.B .ps 10.25z +and so sets the pointsize to 10250 scaled points, +which is equal to 10.25 points. +.LP +The number register +.B \en[.s] +returns the pointsize in points as decimal fraction. +There is also a new number register +.B \en[.ps] +that returns the pointsize in scaled points. +.LP +It would make no sense to use the +.B z +scale indicator in a numeric expression +whose default scale indicator was neither +.B u +nor +.BR z , +and so +.B troff +disallows this. +Similarly it would make no sense to use a scaling indicator +other than +.B z +or +.B u +in a numeric expression whose default scale indicator was +.BR z , +and so +.B troff +disallows this as well. +.LP +There is also new scale indicator +.B s +which multiplies by the number of units in a scaled point. +So, for example, +.B \en[.ps]s +is equal to +.BR 1m . +Be sure not to confuse the +.B s +and +.B z +scale indicators. +. +.SS Numeric expressions +. +.LP +Spaces are permitted in a number expression within parentheses. +.LP +.B M +indicates a scale of 100ths of an em. +.TP +.IB e1 >? e2 +The maximum of +.I e1 +and +.IR e2 . +.TP +.IB e1 <? e2 +The minimum of +.I e1 +and +.IR e2 . +.TP +.BI ( c ; e ) +Evaluate +.I e +using +.I c +as the default scaling indicator. +If +.I c +is missing, ignore scaling indicators in the evaluation of +.IR e . +. +.SS New escape sequences +. +.TP +.BI \eA' anything ' +This expands to +.B 1 +or +.B 0 +according as +.I anything +is or is not acceptable as the name of a string, macro, diversion, +number register, environment or font. +It will return +.B 0 +if +.I anything +is empty. +This is useful if you want to lookup user input in some sort of +associative table. +.TP +.BI \eB' anything ' +This expands to +.B 1 +or +.B 0 +according as +.I anything +is or is not a valid numeric expression. +It will return +.B 0 +if +.I anything +is empty. +.TP +.BI \eC' xxx ' +Typeset character named +.IR xxx . +Normally it is more convenient to use +.BI \e[ xxx ]\fR. +But +.B \eC +has the advantage that it is compatible with recent versions of +.SM UNIX +and is available in compatibility mode. +.TP +.B \eE +This is equivalent to an escape character, +but it's not interpreted in copy-mode. +For example, strings to start and end superscripting could be defined +like this: +.RS +.IP +\&.ds { \ev'\-.3m'\es'\eEn[.s]*6u/10u' +.br +\&.ds } \es0\ev'.3m' +.LP +The use of +.B \eE +ensures that these definitions will work even if +.B \e*{ +gets interpreted in copy-mode +(for example, by being used in a macro argument). +.RE +.TP +.BI \eN' n ' +Typeset the character with code +.I n +in the current font. +.I n +can be any integer. +Most devices only have characters with codes between 0 and 255. +If the current font does not contain a character with that code, +special fonts will +.I not +be searched. +The +.B \eN +escape sequence can be conveniently used on conjunction with the +.B char +request: +.RS +.IP +.B +\&.char \e[phone] \ef(ZD\eN'37' +.RE +.IP +The code of each character is given in the fourth column in the font +description file after the +.B charset +command. +It is possible to include unnamed characters in the font description +file by using a name of +.BR \-\-\- ; +the +.B \eN +escape sequence is the only way to use these. +.TP +.BI \eR' name\ \(+-n ' +This has the same effect as +.RS +.IP +.BI .nr\ name\ \(+-n +.RE +.TP +.BI \es( nn +.TQ +.BI \es\(+-( nn +Set the point size to +.I nn +points; +.I nn +must be exactly two digits. +.TP +.BI \es[\(+- n ] +.TQ +.BI \es\(+-[ n ] +.TQ +.BI \es'\(+- n ' +.TQ +.BI \es\(+-' n ' +Set the point size to +.I n +scaled points; +.I n +is a numeric expression with a default scale indicator of +.BR z . +.TP +.BI \eV x +.TQ +.BI \eV( xx +.TQ +.BI \eV[ xxx ] +Interpolate the contents of the environment variable +.IR xxx , +as returned by +.BR getenv (3). +.B \eV +is interpreted in copy-mode. +.TP +.BI \eY x +.TQ +.BI \eY( xx +.TQ +.BI \eY[ xxx ] +This is approximately equivalent to +.BI \eX'\e*[ xxx ]'\fR. +However the contents of the string or macro +.I xxx +are not interpreted; +also it is permitted for +.I xxx +to have been defined as a macro and thus contain newlines +(it is not permitted for the argument to +.B \eX +to contain newlines). +The inclusion of newlines requires an extension to the UNIX troff output +format, and will confuse drivers that do not know about this +extension. +.TP +.BI \eZ' anything ' +Print anything and then restore the horizontal and vertical +position; +.I anything +may not contain tabs or leaders. +.TP +.B \e$0 +The name by which the current macro was invoked. +The +.B als +request can make a macro have more than one name. +.TP +.B \e$* +In a macro, the concatenation of all the arguments separated by spaces. +.TP +.B \e$@ +In a macro, the concatenation of all the arguments with each surrounded by +double quotes, and separated by spaces. +.TP +.BI \e$( nn +.TQ +.BI \e$[ nnn ] +In a macro, this gives the +.IR nn -th +or +.IR nnn -th +argument. +Macros can have an unlimited number of arguments. +.TP +.BI \e? anything \e? +When used in a diversion, this will transparently embed +.I anything +in the diversion. +.I anything +is read in copy mode. +When the diversion is reread, +.I anything +will be interpreted. +.I anything +may not contain newlines; use +.B \e!\& +if you want to embed newlines in a diversion. +The escape sequence +.B \e?\& +is also recognised in copy mode and turned into a single internal +code; it is this code that terminates +.IR anything . +Thus +.RS +.RS +.ft B +.nf +.ne 15 +\&.nr x 1 +\&.nf +\&.di d +\e?\e\e?\e\e\e\e?\e\e\e\e\e\e\e\enx\e\e\e\e?\e\e?\e? +\&.di +\&.nr x 2 +\&.di e +\&.d +\&.di +\&.nr x 3 +\&.di f +\&.e +\&.di +\&.nr x 4 +\&.f +.fi +.ft +.RE +.RE +.IP +will print +.BR 4 . +.TP +.B \e/ +This increases the width of the preceding character so that +the spacing between that character and the following character +will be correct if the following character is a roman character. +For example, if an italic f is immediately followed by a roman +right parenthesis, then in many fonts the top right portion of the f +will overlap the top left of the right parenthesis producing \fIf\fR)\fR, +which is ugly. +Inserting +.B \e/ +produces +.ie \n(.g \fIf\/\fR)\fR +.el \fIf\|\fR)\fR +and avoids this problem. +It is a good idea to use this escape sequence whenever an +italic character is immediately followed by a roman character without any +intervening space. +.TP +.B \e, +This modifies the spacing of the following character so that the spacing +between that character and the preceding character will correct if +the preceding character is a roman character. +For example, inserting +.B \e, +between the parenthesis and the f changes +\fR(\fIf\fR to +.ie \n(.g \fR(\,\fIf\fR. +.el \fR(\^\fIf\fR. +It is a good idea to use this escape sequence whenever a +roman character is immediately followed by an italic character without any +intervening space. +.TP +.B \e) +Like +.B \e& +except that it behaves like a character declared with the +.B cflags +request to be transparent for the purposes of end of sentence recognition. +.TP +.B \e~ +This produces an unbreakable space that stretches like a normal inter-word +space when a line is adjusted. +.TP +.B \e: +This causes the insertion of a zero-width break point. +It is equal to +.B \e% +but without insertion of a soft hyphen character. +.TP +.B \e# +Everything up to and including the next newline is ignored. +This is interpreted in copy mode. +This is like +.B \e" +except that +.B \e" +does not ignore the terminating newline. +. +.SS New requests +. +.TP +.BI .aln\ xx\ yy +Create an alias +.I xx +for number register object named +.IR yy . +The new name and the old name will be exactly equivalent. +If +.I yy +is undefined, a warning of type +.B reg +will be generated, and the request will be ignored. +.TP +.BI .als\ xx\ yy +Create an alias +.I xx +for request, string, macro, or diversion object named +.IR yy . +The new name and the old name will be exactly equivalent (it is similar to a +hard rather than a soft link). +If +.I yy +is undefined, a warning of type +.B mac +will be generated, and the request will be ignored. +The +.BR de , +.BR am , +.BR di , +.BR da , +.BR ds , +and +.B as +requests only create a new object if the name of the macro, diversion +or string diversion is currently undefined or if it is defined to be a +request; normally they modify the value of an existing object. +.TP +.BI .asciify\ xx +This request `unformats' the diversion +.I xx +in such a way that +.SM ASCII +and space characters (and some escape sequences) that were formatted and +diverted into +.I xx +will be treated like ordinary input characters when +.I xx +is reread. +Useful for diversions in conjunction with the +.B .writem +request. +It can be also used for gross hacks; for example, this +.RS +.IP +.ne 7v+\n(.Vu +.ft B +.nf +\&.tr @. +\&.di x +\&@nr n 1 +\&.br +\&.di +\&.tr @@ +\&.asciify x +\&.x +.fi +.RE +.IP +will set register +.B n +to 1. +Note that glyph information (font, font size, etc.) is not preserved; use +.B .unformat +instead. +.TP +.B .backtrace +Print a backtrace of the input stack on stderr. +.TP +.BI .blm\ xx +Set the blank line macro to +.IR xx . +If there is a blank line macro, +it will be invoked when a blank line is encountered instead of the usual +troff behaviour. +.TP +.BI .box\ xx +.TQ +.BI .boxa\ xx +These requests are similar to the +.B di +and +.B da +requests with the exception that a partially filled line will not become +part of the diversion (i.e., the diversion always starts with a new line) +but restored after ending the diversion, discarding the partially filled +line which possibly comes from the diversion. +.TP +.B .break +Break out of a while loop. +See also the +.B while +and +.B continue +requests. +Be sure not to confuse this with the +.B br +request. +.TP +.BI .cflags\ n\ c1\ c2\|.\|.\|. +Characters +.IR c1 , +.IR c2 ,\|.\|.\|. +have properties determined by +.IR n , +which is ORed from the following: +.RS +.TP +1 +the character ends sentences +(initially characters +.B .?!\& +have this property); +.TP +2 +lines can be broken before the character +(initially no characters have this property); +a line will not be broken at a character with this property +unless the characters on each side both have non-zero +hyphenation codes. +.TP +4 +lines can be broken after the character +(initially characters +.B \-\e(hy\e(em +have this property); +a line will not be broken at a character with this property +unless the characters on each side both have non-zero +hyphenation codes. +.TP +8 +the character overlaps horizontally +(initially characters +.B \e(ul\e(rn\e(ru +have this property); +.TP +16 +the character overlaps vertically +(initially character +.B \e(br +has this property); +.TP +32 +an end of sentence character followed by any number of characters +with this property will be treated +as the end of a sentence if followed by a newline or two spaces; +in other words +the character is transparent for the purposes of end of sentence +recognition; +this is the same as having a zero space factor in \*(tx +(initially characters +.B \(ts')]*\e(dg\e(rq +have this property). +.RE +.TP +.BI .char\ c\ string +Define character +.I c +to be +.IR string . +Every time character +.I c +needs to be printed, +.I string +will be processed in a temporary environment and the result +will be wrapped up into a single object. +Compatibility mode will be turned off +and the escape character will be set to +.B \e +while +.I string +is being processed. +Any emboldening, constant spacing or track kerning will be applied +to this object rather than to individual characters in +.IR string . +A character defined by this request can be used just like +a normal character provided by the output device. +In particular other characters can be translated to it +with the +.B tr +request; +it can be made the leader character by the +.B lc +request; +repeated patterns can be drawn with the character using the +.B \el +and +.B \eL +escape sequences; +words containing the character can be hyphenated +correctly, if the +.B hcode +request is used to give the character a hyphenation code. +There is a special anti-recursion feature: +use of character within the character's definition +will be handled like normal characters not defined with +.BR char . +A character definition can be removed with the +.B rchar +request. +.TP +.BI .chop\ xx +Chop the last character off macro, string, or diversion +.IR xx . +This is useful for removing the newline from the end of diversions +that are to be interpolated as strings. +.TP +.BI .close\ stream +Close the stream named +.IR stream ; +.I stream +will no longer be an acceptable argument to the +.B write +request. +See the +.B open +request. +.TP +.B .continue +Finish the current iteration of a while loop. +See also the +.B while +and +.B break +requests. +.TP +.BI .cp\ n +If +.I n +is non-zero or missing, enable compatibility mode, otherwise +disable it. +In compatibility mode, long names are not recognised, and the +incompatibilities caused by long names do not arise. +.TP +.BI .dei\ xx\ yy +Define macro indirectly. +The following example +.RS +.IP +.ne 2v+\n(.Vu +.ft B +.nf +\&.ds xx aa +\&.ds yy bb +\&.dei xx yy +.fi +.RE +.IP +is equivalent to +.RS +.IP +.B +\&.de aa bb +.RE +.TP +.BI .do\ xxx +Interpret +.I .xxx +with compatibility mode disabled. +For example, +.RS +.IP +.B +\&.do fam T +.LP +would have the same effect as +.IP +.B +\&.fam T +.LP +except that it would work even if compatibility mode had been enabled. +Note that the previous compatibility mode is restored before any files +sourced by +.I xxx +are interpreted. +.RE +.TP +.B .ecs +Save current escape character. +.TP +.B .ecr +Restore escape character saved with +.BR ecs . +Without a previous call to +.BR ecs , +.RB ` \e ' +will be the new escape character. +.TP +.BI .evc\ xx +Copy the contents of environment +.I xx +to the current environment. +No pushing or popping of environents will be done. +.TP +.BI .fam\ xx +Set the current font family to +.IR xx . +The current font family is part of the current environment. +If +.I xx +is missing, switch back to previous font family. +See the description of the +.B sty +request for more information on font families. +.TP +.BI .fspecial\ f\ s1\ s2\|.\|.\|. +When the current font is +.IR f , +fonts +.IR s1 , +.IR s2 ,\|.\|.\|. +will be special, that is, they will searched for characters not in +the current font. +Any fonts specified in the +.B special +request will be searched after fonts specified in the +.B fspecial +request. +.TP +.BI .ftr\ f\ g +Translate font +.I f +to +.IR g . +Whenever a font named +.I f +is referred to in +.B \ef +escape sequence, +or in the +.BR ft , +.BR ul , +.BR bd , +.BR cs , +.BR tkf , +.BR special , +.BR fspecial , +.BR fp , +or +.BR sty +requests, +font +.I g +will be used. +If +.I g +is missing, +or equal to +.I f +then font +.I f +will not be translated. +.TP +.BI .hcode \ c1\ code1\ c2\ code2\|.\|.\|. +Set the hyphenation code of character +.I c1 +to +.I code1 +and that of +.I c2 +to +.IR code2 . +A hyphenation code must be a single input +character (not a special character) other than a digit or a space. +Initially each lower-case letter has a hyphenation code, which +is itself, and each upper-case letter has a hyphenation code +which is the lower case version of itself. +See also the +.B hpf +request. +.TP +.BI .hla\ lang +Set the current hyphenation language to +.IR lang . +Hyphenation exceptions specified with the +.B hw +request and hyphenation patterns specified with the +.B hpf +request are both associated with the current hyphenation language. +The +.B hla +request is usually invoked by the +.B troffrc +file. +.TP +.BI .hlm\ n +Set the maximum number of consecutive hyphenated lines to +.IR n . +If +.I n +is negative, there is no maximum. +The default value is \-1. +This value is associated with the current environment. +Only lines output from an environment count towards the maximum associated +with that environment. +Hyphens resulting from +.B \e% +are counted; explicit hyphens are not. +.TP +.BI .hpf\ file +Read hyphenation patterns from +.IR file ; +this will be searched for in the same way that +.IB name .tmac +is searched for when the +.BI \-m name +option is specified. +It should have the same format as the argument to +the \epatterns primitive in \*(tx; +the letters appearing in this file are interpreted as hyphenation +codes. +A +.B % +character in the patterns file introduces a comment that continues +to the end of the line. +The set of hyphenation patterns is associated with the current language +set by the +.B hla +request. +The +.B hpf +request +is usually invoked by the +.B troffrc +file. +.TP +.BI .hym\ n +Set the +.I hyphenation margin +to +.IR n : +when the current adjustment mode is not +.BR b , +the line will not be hyphenated if the line is no more than +.I n +short. +The default hyphenation margin is 0. +The default scaling indicator for this request is +.IR m . +The hyphenation margin is associated with the current environment. +The current hyphenation margin is available in the +.B \en[.hym] +register. +.TP +.BI .hys\ n +Set the +.I hyphenation space +to +.IR n : +when the current adjustment mode is +.B b +don't hyphenate the line if the line can be justified by adding no more than +.I n +extra space to each word space. +The default hyphenation space is 0. +The default scaling indicator for this request is +.BR m . +The hyphenation space is associated with the current environment. +The current hyphenation space is available in the +.B \en[.hys] +register. +.TP +.BI .kern\ n +If +.I n +is non-zero or missing, enable pairwise kerning, otherwise disable it. +.TP +.BI .length\ xx\ string +Compute the length of +.I string +and return it in the number register +.I xx +(which is not necessarily defined before). +.TP +.BI .linetabs\ n +If +.I n +is non-zero or missing, enable line-tabs mode, otherwise disable it (which +is the default). +In line-tabs mode, tab distances are computed relative to the (current) +output line. +Otherwise they are taken relative to the input line. +For example, the following +.RS +.IP +.ne 6v+\n(.Vu +.ft B +.nf +\&.ds x a\et\ec +\&.ds y b\et\ec +\&.ds z c +\&.ta 1i 3i +\e*x +\e*y +\e*z +.fi +.RE +.IP +yields +.RS +.IP +a b c +.RE +.IP +In line-tabs mode, the same code gives +.RS +.IP +a b c +.RE +.IP +Line-tabs mode is associated with the current environment; the read-only +number register +.B \\en[.linetabs] +is set to\~1 if in line-tabs mode, and 0 otherwise. +.TP +.BI .mso\ file +The same as the +.B so +request except that +.I file +is searched for in the same directories as macro files for the +the +.B \-m +command line option. +If the file name to be included +has the form +.IB name .tmac +and it isn't found, +.B mso +tries to include +.BI tmac. name +instead and vice versa. +.TP +.BI .nop \ anything +Execute +.IR anything . +This is similar to `.if\ 1'. +.TP +.B .nroff +Make the +.B n +built-in condition true +and the +.B t +built-in condition false. +This can be reversed using the +.B troff +request. +.TP +.BI .open\ stream\ filename +Open +.I filename +for writing and associate the stream named +.I stream +with it. +See also the +.B close +and +.B write +requests. +.TP +.BI .opena\ stream\ filename +Like +.BR open , +but if +.I filename +exists, append to it instead of truncating it. +.TP +.B .pnr +Print the names and contents of all currently defined number registers +on stderr. +.TP +.BI .psbb \ filename +Get the bounding box of a PostScript image +.IR filename . +This file must conform to Adobe's Document Structuring Conventions; the +command looks for a +.B %%BoundingBox +comment to extract the bounding box values. +After a successful call, the coordinates (in PostScript units) of the lower +left and upper right corner can be found in the registers +.BR \en[llx] , +.BR \en[lly] , +.BR \en[urx] , +and +.BR \en[ury] , +respectively. +If some error has occurred, the four registers are set to zero. +.TP +.BI .pso \ command +This behaves like the +.B so +request except that input comes from the standard output of +.IR command . +.TP +.B .ptr +Print the names and positions of all traps (not including input line +traps and diversion traps) on stderr. Empty slots in the page trap +list are printed as well, because they can affect the priority of +subsequently planted traps. +.TP +.BI .rchar\ c1\ c2\|.\|.\|. +Remove the definitions of characters +.IR c1 , +.IR c2 ,\|.\|.\|. +This undoes the effect of a +.B char +request. +.TP +.B .return +Within a macro, return immediately. +No effect otherwise. +.TP +.B .rj +.TQ +.BI .rj\ n +Right justify the next +.I n +input lines. +Without an argument right justify the next input line. +The number of lines to be right justified is available in the +.B \en[.rj] +register. +This implicitly does +.BR .ce\ 0 . +The +.B ce +request implicitly does +.BR .rj\ 0 . +.TP +.BI .rnn \ xx\ yy +Rename number register +.I xx +to +.IR yy . +.TP +.BI .shc\ c +Set the soft hyphen character to +.IR c . +If +.I c +is omitted, +the soft hyphen character will be set to the default +.BR \e(hy . +The soft hyphen character is the character which will be inserted +when a word is hyphenated at a line break. +If the soft hyphen character does not exist in the font of the character +immediately preceding a potential break point, +then the line will not be broken at that point. +Neither definitions (specified with the +.B char +request) +nor translations (specified with the +.B tr +request) +are considered when finding the soft hyphen character. +.TP +.BI .shift\ n +In a macro, shift the arguments by +.I n +positions: +argument +.I i +becomes argument +.IR i \- n ; +arguments 1 to +.I n +will no longer be available. +If +.I n +is missing, +arguments will be shifted by 1. +Shifting by negative amounts is currently undefined. +.TP +.BI .special\ s1\ s2\|.\|.\|. +Fonts +.IR s1 , +.IR s2 , +are special and will be searched for characters not in the +current font. +.TP +.BI .sty\ n\ f +Associate style +.I f +with font position +.IR n . +A font position can be associated either with a font or +with a style. +The current font is the index of a font position and so is also +either a font or a style. +When it is a style, the font that is actually used is the font the +name of which is the concatenation of the name of the current family +and the name of the current style. +For example, if the current font is 1 and font position 1 is +associated with style +.B R +and the current +font family is +.BR T , +then font +.BR TR +will be used. +If the current font is not a style, then the current family is ignored. +When the requests +.BR cs , +.BR bd , +.BR tkf , +.BR uf , +or +.B fspecial +are applied to a style, +then they will instead be applied to the member of the +current family corresponding to that style. +The default family can be set with the +.B \-f +option. +The styles command in the +.SM DESC +file controls which font positions +(if any) are initially associated with styles rather than fonts. +.TP +.BI .substring\ xx\ n1\ [ n2 ] +Replace the string in register +.I xx +with the substring defined by the indices +.I n1 +and +.IR n2 . +The first character in the string has index one. +If +.I n2 +is omitted, it is taken to be equal to the string's length. If the +index value +.I n1 +or +.I n2 +is negative or zero, it will be counted from the end of the string, +going backwards: The last character has index 0, the character before +the last character has index -1, etc. +.TP +.BI .tkf\ f\ s1\ n1\ s2\ n2 +Enable track kerning for font +.IR f . +When the current font is +.I f +the width of every character will be increased by an amount +between +.I n1 +and +.IR n2 ; +when the current point size is less than or equal to +.I s1 +the width will be increased by +.IR n1 ; +when it is greater than or equal to +.I s2 +the width will be increased by +.IR n2 ; +when the point size is greater than or equal to +.I s1 +and less than or equal to +.I s2 +the increase in width is a linear function of the point size. +.TP +.BI .tm1\ string +Similar to the +.B tm +request, +.I string +is read in copy mode and written on the standard error, but an initial +double quote in +.I string +is stripped off to allow initial blanks. +.TP +.BI .tmc\ string +Similar to +.BR tm1 +but without writing a final newline. +.TP +.BI .trf\ filename +Transparently output the contents of file +.IR filename . +Each line is output as it would be were it preceded by +.BR \e! ; +however, the lines are not subject to copy-mode interpretation. +If the file does not end with a newline, then a newline will +be added. +For example, you can define a macro +.I x +containing the contents of file +.IR f , +using +.RS +.IP +.BI .di\ x +.br +.BI .trf\ f +.br +.B .di +.LP +Unlike with the +.B cf +request, +the file cannot contain characters such as +.SM NUL +that are not legal troff input characters. +.RE +.TP +.B .trnt abcd +This is the same as the +.B tr +request except that the translations do not apply to text that is +transparently throughput into a diversion with +.BR \e! . +For example, +.RS +.IP +.nf +.ft B +\&.tr ab +\&.di x +\e!.tm a +\&.di +\&.x +.fi +.ft +.LP +will print +.BR b ; +if +.B trnt +is used instead of +.B tr +it will print +.BR a . +.RE +.TP +.B .troff +Make the +.B n +built-in condition false, +and the +.B t +built-in condition true. +This undoes the effect of the +.B nroff +request. +.TP +.BI .unformat\ xx +This request `unformats' the diversion +.IR xx . +Contrary to the +.B .asciify +request, which tries to convert formatted elements of the diversion back +to input tokens as much as possible, +.B .unformat +will only handle tabs and spaces between words (usually caused by spaces +or newlines in the input) specially. +The former are treated as if they were input tokens, and the latter are +stretchable again. +Note that the vertical size of lines is not preserved. +Glyph information (font, font size, space width, etc.) is retained. +Useful in conjunction with the +.B .box +and +.B .boxa +requests. +.TP +.BI .vpt\ n +Enable vertical position traps if +.I n +is non-zero, disable them otherwise. +Vertical position traps are traps set by the +.B wh +or +.B dt +requests. +Traps set by the +.B it +request are not vertical position traps. +The parameter that controls whether vertical position traps are enabled +is global. +Initially vertical position traps are enabled. +.TP +.BI .warn\ n +Control warnings. +.I n +is the sum of the numbers associated with each warning that is to be enabled; +all other warnings will be disabled. +The number associated with each warning is listed in the `Warnings' section. +For example, +.B .warn 0 +will disable all warnings, and +.B .warn 1 +will disable all warnings except that about missing characters. +If +.I n +is not given, +all warnings will be enabled. +.TP +.BI .while \ c\ anything +While condition +.I c +is true, accept +.I anything +as input; +.I c +can be any condition acceptable to an +.B if +request; +.I anything +can comprise multiple lines if the first line starts with +.B \e{ +and the last line ends with +.BR \e} . +See also the +.B break +and +.B continue +requests. +.TP +.BI .write\ stream\ anything +Write +.I anything +to the stream named +.IR stream . +.I stream +must previously have been the subject of an +.B open +request. +.I anything +is read in copy mode; +a leading +.B \(ts +will be stripped. +.TP +.BI .writem\ stream\ xx +Write the contents of the macro or string +.I xx +to the stream named +.IR stream . +.I stream +must previously have been the subject of an +.B open +request. +.I xx +is read in copy mode. +. +.SS Extended requests +. +.TP +.BI .cf\ filename +When used in a diversion, this will embed in the diversion an object which, +when reread, will cause the contents of +.I filename +to be transparently copied through to the output. +In UNIX troff, the +contents of +.I filename +is immediately copied through to the output regardless of whether +there is a current diversion; this behaviour is so anomalous that it +must be considered a bug. +.TP +.BI .ev\ xx +If +.I xx +is not a number, this will switch to a named environment called +.IR xx . +The environment should be popped with a matching +.B ev +request without any arguments, just as for numbered environments. +There is no limit on the number of named environments; they will be +created the first time that they are referenced. +.TP +.BI .fp\ n\ f1\ f2 +The +.B fp +request has an optional third argument. +This argument gives the external name of the font, +which is used for finding the font description file. +The second argument gives the internal name of the font +which is used to refer to the font in troff after it has been mounted. +If there is no third argument then the internal name will be used +as the external name. +This feature allows you to use fonts with long names in compatibility mode. +.TP +.BI .ss\ m\ n +When two arguments are given to the +.B ss +request, the second argument gives the +.IR "sentence space size" . +If the second argument is not given, the sentence space size +will be the same as the word space size. +Like the word space size, the sentence space is in units of +one twelfth of the spacewidth parameter for the current font. +Initially both the word space size and the sentence +space size are 12. +Contrary to UNIX troff, GNU troff handles this request in nroff mode +also (if not in compatibility mode); a given value is then rounded down +to the nearest multiple of\~12. +The sentence space size is used in two circumstances: +if the end of a sentence occurs at the end of a line in fill mode, then +both an inter-word space and a sentence space will be added; +if two spaces follow the end of a sentence in the middle of a line, +then the second space will be a sentence space. +Note that the behaviour of UNIX troff will be exactly +that exhibited by GNU troff if a second argument is never given to the +.B ss +request. +In GNU troff, as in UNIX troff, you should always +follow a sentence with either a newline or two spaces. +.TP +.BI .ta\ n1\ n2\|.\|.\|.nn \ T\ r1\ r2\|.\|.\|.\|rn +Set tabs at positions +.IR n1 , +.IR n2 ,\|.\|.\|.\|, +.I nn +and then set tabs at +.IR nn + r1 , +.IR nn + r2 ,\|.\|.\|.\|.\|, +.IR nn + rn +and then at +.IR nn + rn + r1 , +.IR nn + rn + r2 ,\|.\|.\|.\|, +.IR nn + rn + rn , +and so on. +For example, +.RS +.IP +.B +\&.ta T .5i +.LP +will set tabs every half an inch. +.RE +. +.SS New number registers +. +The following read-only registers are available: +.TP +.B \en[.C] +1 if compatibility mode is in effect, 0 otherwise. +.TP +.B \en[.cdp] +The depth of the last character added to the current environment. +It is positive if the character extends below the baseline. +.TP +.B \en[.ce] +The number of lines remaining to be centered, as set by the +.B ce +request. +.TP +.B \en[.cht] +The height of the last character added to the current environment. +It is positive if the character extends above the baseline. +.TP +.B \en[.csk] +The skew of the last character added to the current environment. +The +.I skew +of a character is how far to the right of the center of a character +the center of an accent over that character should be placed. +.TP +.B \en[.ev] +The name or number of the current environment. +This is a string-valued register. +.TP +.B \en[.fam] +The current font family. +This is a string-valued register. +.TP +.B \en[.fp] +The number of the next free font position. +.TP +.B \en[.g] +Always 1. +Macros should use this to determine whether they are running +under GNU troff. +.TP +.B \en[.hla] +The current hyphenation language as set by the +.B hla +request. +.TP +.B \en[.hlc] +The number of immediately preceding consecutive hyphenated lines. +.TP +.B \en[.hlm] +The maximum allowed number of consecutive hyphenated lines, as set by the +.B hlm +request. +.TP +.B \en[.hy] +The current hyphenation flags (as set by the +.B hy +request). +.TP +.B \en[.hym] +The current hyphenation margin (as set by the +.B hym +request). +.TP +.B \en[.hys] +The current hyphenation space (as set by the +.B hys +request). +.TP +.B \en[.in] +The indent that applies to the current output line. +.TP +.B \en[.int] +Set to a positive value if last output line is interrupted (i.e., if it +contains +.IR \ec ). +.TP +.B \en[.kern] +.B 1 +if pairwise kerning is enabled, +.B 0 +otherwise. +.TP +.B \en[.lg] +The current ligature mode (as set by the +.B lg +request). +.TP +.B \en[.linetabs] +The current line-tabs mode (as set by the +.B linetabs +request). +.TP +.B \en[.ll] +The line length that applies to the current output line. +.TP +.B \en[.lt] +The title length as set by the +.B lt +request. +.TP +.B \en[.ne] +The amount of space that was needed in the last +.B ne +request that caused a trap to be sprung. +Useful in conjunction with the +.B \en[.trunc] +register. +.TP +.B \en[.pn] +The number of the next page: +either the value set by a +.B pn +request, or the number of the current page plus 1. +.TP +.B \en[.ps] +The current pointsize in scaled points. +.TP +.B \en[.psr] +The last-requested pointsize in scaled points. +.TP +.B \en[.rj] +The number of lines to be right-justified as set by the +.B rj +request. +.TP +.B \en[.sr] +The last requested pointsize in points as a decimal fraction. +This is a string-valued register. +.TP +.B \en[.tabs] +A string representation of the current tab settings suitable for use as +an argument to the +.B ta +request. +.TP +.B \en[.trunc] +The amount of vertical space truncated by the most recently sprung +vertical position trap, or, +if the trap was sprung by a +.B ne +request, +minus the amount of vertical motion produced by the +.B ne +request. +In other words, at the point a trap is sprung, it represents the difference +of what the vertical position would have been but for the trap, +and what the vertical position actually is. +Useful in conjunction with the +.B \en[.ne] +register. +.TP +.B \en[.ss] +.TQ +.B \en[.sss] +These give the values of the parameters set by the +first and second arguments of the +.B ss +request. +.TP +.B \en[.vpt] +1 if vertical position traps are enabled, 0 otherwise. +.TP +.B \en[.warn] +The sum of the numbers associated with each of the currently enabled +warnings. +The number associated with each warning is listed in the `Warnings' +subsection. +.TP +.B \en[.x] +The major version number. +For example, if the version number is +.B 1.03 +then +.B \en[.x] +will contain +.BR 1 . +.TP +.B \en[.y] +The minor version number. +For example, if the version number is +.B 1.03 +then +.B \en[.y] +will contain +.BR 03 . +.TP +.B \en[.Y] +The revision number of groff. +.TP +.B \en[llx] +.TQ +.B \en[lly] +.TQ +.B \en[urx] +.TQ +.B \en[ury] +These four registers are set by the +.B \&.psbb +request and contain the bounding box values (in PostScript units) of a given +PostScript image. +.LP +The following read/write registers are set by the +.B \ew +escape sequence: +.TP +.B \en[rst] +.TQ +.B \en[rsb] +Like the +.B st +and +.B sb +registers, but takes account of the heights and depths of characters. +.TP +.B \en[ssc] +The amount of horizontal space (possibly negative) that should +be added to the last character before a subscript. +.TP +.B \en[skw] +How far to right of the center of the last character +in the +.B \ew +argument, +the center of an accent from a roman font should be placed over that character. +.LP +Other available read/write number registers are: +.TP +.B \en[c.] +The current input line number. +.B \en[.c] +is a read-only alias to this register. +.TP +.B \en[hp] +The current horizontal position at input line. +.TP +.B \en[systat] +The return value of the system() function executed by the last +.B sy +request. +.TP +.B \en[slimit] +If greater than 0, the maximum number of objects on the input stack. +If less than or equal to 0, there is no limit on the number of objects +on the input stack. With no limit, recursion can continue until +virtual memory is exhausted. +.TP +.B \en[year] +The current year. +Note that the traditional +.B troff +number register +.B \en[yr] +is the current year minus 1900. +. +.SS Miscellaneous +. +.B @g@troff +predefines a single (read/write) string-based register, +.BR \e*(.T , +which contains the argument given to the +.B -T +command line option, namely the current output device (for example, +.I latin1 +or +.IR ascii ). +Note that this is not the same as the (read-only) number register +.B \en[.T] +which is defined to be\ 1 if +.B troff +is called with the +.B -T +command line option, and zero otherwise. This behaviour is different to +UNIX troff. +.LP +Fonts not listed in the +.SM DESC +file are automatically mounted on the next available font position +when they are referenced. +If a font is to be mounted explicitly with the +.B fp +request on an unused font position, +it should be mounted on the first unused font position, +which can be found in the +.B \en[.fp] +register; +although +.B troff +does not enforce this strictly, +it will not allow a font to be mounted at a position whose number is much +greater than that of any currently used position. +.LP +Interpolating a string does not hide existing macro arguments. +Thus in a macro, a more efficient way of doing +.IP +.BI . xx\ \e\e$@ +.LP +is +.IP +.BI \e\e*[ xx ]\e\e +.LP +If the font description file contains pairwise kerning information, +characters from that font will be kerned. +Kerning between two characters can be inhibited by placing a +.B \e& +between them. +.LP +In a string comparison in a condition, +characters that appear at different input levels +to the first delimiter character will not be recognised +as the second or third delimiters. +This applies also to the +.B tl +request. +In a +.B \ew +escape sequence, +a character that appears at a different input level to +the starting delimiter character will not be recognised +as the closing delimiter character. +When decoding a macro argument that is delimited +by double quotes, a character that appears at a different +input level to the starting delimiter character will not +be recognised as the closing delimiter character. +The implementation of +.B \e$@ +ensures that the double quotes surrounding an argument +will appear the same input level, which will be different +to the input level of the argument itself. +In a long escape name +.B ] +will not be recognized as a closing delimiter except +when it occurs at the same input level as the opening +.BR ] . +In compatibility mode, no attention is paid to the input-level. +.LP +There are some new types of condition: +.TP +.BI .if\ r xxx +True if there is a number register named +.IR xxx . +.TP +.BI .if\ d xxx +True if there is a string, macro, diversion, or request named +.IR xxx . +.TP +.BI .if\ c ch +True if there is a character +.IR ch +available; +.I ch +is either an +.SM ASCII +character +or a special character +.BI \e( xx +or +.BI \e[ xxx ]\fR; +the condition will also be true if +.I ch +has been defined by the +.B char +request. +.LP +The +.B tr +request can now map characters onto +.BR \e~ . +. +.SS Warnings +. +The warnings that can be given by +.B troff +are divided into the following categories. +The name associated with each warning is used by the +.B \-w +and +.B \-W +options; +the number is used by the +.B warn +request, and by the +.B .warn +register. +.nr x \w'\fBright-brace'+1n+\w'0000'u +.ta \nxuR +.TP \nxu+3n +.BR char \t1 +Non-existent characters. +This is enabled by default. +.TP +.BR number \t2 +Invalid numeric expressions. +This is enabled by default. +.TP +.BR break \t4 +In fill mode, lines which could not be broken so that their length was +less than the line length. +This is enabled by default. +.TP +.BR delim \t8 +Missing or mismatched closing delimiters. +.TP +.BR el \t16 +Use of the +.B el +request with no matching +.B ie +request. +.TP +.BR scale \t32 +Meaningless scaling indicators. +.TP +.BR range \t64 +Out of range arguments. +.TP +.BR syntax \t128 +Dubious syntax in numeric expressions. +.TP +.BR di \t256 +Use of +.B di +or +.B da +without an argument when there is no current diversion. +.TP +.BR mac \t512 +Use of undefined strings, macros and diversions. +When an undefined string, macro or diversion is used, +that string is automatically defined as empty. +So, in most cases, at most one warning will be given for +each name. +.TP +.BR reg \t1024 +Use of undefined number registers. +When an undefined number register is used, +that register is automatically defined to have a value of 0. +a definition is automatically made with a value of 0. +So, in most cases, at most one warning will be given for +use of a particular name. +.TP +.BR tab \t2048 +Inappropriate use of a tab character. +Either use of a tab character where a number was expected, +or use of tab character in an unquoted macro argument. +.TP +.BR right-brace \t4096 +Use of +.B \e} +where a number was expected. +.TP +.BR missing \t8192 +Requests that are missing non-optional arguments. +.TP +.BR input \t16384 +Illegal input characters. +.TP +.BR escape \t32768 +Unrecognized escape sequences. +When an unrecognized escape sequence is encountered, +the escape character is ignored. +.TP +.BR space \t65536 +Missing space between a request or macro and its argument. +This warning will be given +when an undefined name longer than two characters is encountered, +and the first two characters of the name make a defined name. +The request or macro will not be invoked. +When this warning is given, no macro is automatically defined. +This is enabled by default. +This warning will never occur in compatibility mode. +.TP +.BR font \t131072 +Non-existent fonts. +This is enabled by default. +.TP +.BR ig \t262144 +Illegal escapes in text ignored with the +.B ig +request. +These are conditions that are errors when they do not occur +in ignored text. +.LP +There are also names that can be used to refer to groups of warnings: +.TP +.B all +All warnings except +.BR di , +.B mac +and +.BR reg . +It is intended that this covers all warnings +that are useful with traditional macro packages. +.TP +.B w +All warnings. +. +.SS Incompatibilities +. +.LP +Long names cause some incompatibilities. +UNIX troff will interpret +.IP +.B +\&.dsabcd +.LP +as defining a string +.B ab +with contents +.BR cd . +Normally, GNU troff will interpret this as a call of a macro named +.BR dsabcd . +Also UNIX troff will interpret +.B \e*[ +or +.B \en[ +as references to a string or number register called +.BR [ . +In GNU troff, however, this will normally be interpreted as the start +of a long name. +In +.I compatibility mode +GNU troff will interpret these things in the traditional way. +In compatibility mode, however, long names are not recognised. +Compatibility mode can be turned on with the +.B \-C +command line option, and turned on or off with the +.B cp +request. +The number register +.B \en[.C] +is 1 if compatibility mode is on, 0 otherwise. +.LP +GNU troff +does not allow the use of the escape sequences +.BR \\e\e|\e^\e&\e}\e{\e (space) \e'\e`\e-\e_\e!\e%\ec +in names of strings, macros, diversions, number registers, +fonts or environments; UNIX troff does. +The +.B \eA +escape sequence may be helpful in avoiding use of these +escape sequences in names. +.LP +Fractional pointsizes cause one noteworthy incompatibility. +In UNIX troff the +.B ps +request ignores scale indicators and so +.IP +.B .ps\ 10u +.LP +will set the pointsize to 10 points, whereas in +GNU troff it will set the pointsize to 10 scaled points. +.LP +In GNU troff there is a fundamental difference between unformatted, +input characters, and formatted, output characters. +Everything that affects how an output character +will be output is stored with the character; once an output +character has been constructed it is unaffected by any subsequent +requests that are executed, including +.BR bd , +.BR cs , +.BR tkf , +.BR tr , +or +.B fp +requests. +Normally output characters are constructed from input +characters at the moment immediately before the character +is added to the current output line. +Macros, diversions and strings are all, in fact, the same type +of object; they contain lists of input characters and output +characters in any combination. +An output character does not behave like an input character +for the purposes of macro processing; it does not inherit any +of the special properties that the input character from which it +was constructed might have had. +For example, +.IP +.nf +.ft B +\&.di x +\e\e\e\e +\&.br +\&.di +\&.x +.ft +.fi +.LP +will print +.B \e\e +in GNU troff; +each pair of input +.BR \e s +is turned into one output +.B \e +and the resulting output +.BR \e s +are not interpreted as escape characters when they are reread. +UNIX troff would interpret them as escape characters +when they were reread and would end up printing one +.BR \e . +The correct way to obtain a printable +.B \e +is to use the +.B \ee +escape sequence: this will always print a single instance of the +current escape character, regardless of whether or not it is used in a +diversion; it will also work in both GNU troff and UNIX troff. +If you wish for some reason to store in a diversion an escape +sequence that will be interpreted when the diversion is reread, +you can either use the traditional +.B \e!\& +transparent output facility, or, if this is unsuitable, the new +.B \e?\& +escape sequence. +. +. +.SH ENVIRONMENT +. +. +.TP +.SM +.B GROFF_TMAC_PATH +A colon separated list of directories in which to search for +macro files. +.B troff +will scan directories given in +the +.B \-M +option before these, and in standard directories (current directory if in +unsafe mode, home directory, +.BR @LOCALMACRODIR@ , +.BR @SYSTEMMACRODIR@ , +.BR @MACRODIR@ ) +after these. +.TP +.SM +.B GROFF_TYPESETTER +Default device. +.TP +.SM +.B GROFF_FONT_PATH +A colon separated list of directories in which to search for the +.BI dev name +directory. +.B troff +will scan directories given in the +.B \-F +option before these, and in standard directories +.RB ( @FONTPATH@ ) +after these. +. +. +.SH FILES +. +. +.Tp \w'@FONTDIR@/devname/DESC'u+3n +.B @MACRODIR@/troffrc +Initialization file (called before any other macro package). +.TP +.B @MACRODIR@/troffrc-end +Initialization file (called after any other macro package). +.TP +.BI @MACRODIR@/ name .tmac +.TQ +.BI @MACRODIR@/tmac. name +Macro files +.TP +.BI @FONTDIR@/dev name /DESC +Device description file for device +.IR name . +.TP +.BI @FONTDIR@/dev name / F +Font file for font +.I F +of device +.IR name . +.LP +Note that +.B troffrc +and +.B troffrc-end +are neither searched in the current nor in the home directory by default for +security reasons (even if the +.B \-U +option is given). +Use the +.B \-M +command line option or the +.B GROFF_TMAC_PATH +environment variable to add these directories to the search path if +necessary. +. +. +.SH "SEE ALSO" +. +. +.BR groff (@MAN1EXT@), +.BR @g@tbl (@MAN1EXT@), +.BR @g@pic (@MAN1EXT@), +.BR @g@eqn (@MAN1EXT@), +.BR @g@refer (@MAN1EXT@), +.BR @g@soelim (@MAN1EXT@), +.BR @g@grn (@MAN1EXT@), +.BR grops (@MAN1EXT@), +.BR grodvi (@MAN1EXT@), +.BR grotty (@MAN1EXT@), +.BR grohtml (@MAN1EXT@), +.BR grolj4 (@MAN1EXT@), +.BR groff_font (@MAN5EXT@), +.BR groff_out (@MAN5EXT@), +.BR groff_char (@MAN7EXT@) |