node.cpp   [plain text]


// -*- C++ -*-
/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005
   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, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */

extern int debug_state;

#include "troff.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "dictionary.h"
#include "hvunits.h"
#include "stringclass.h"
#include "mtsm.h"
#include "env.h"
#include "request.h"
#include "node.h"
#include "token.h"
#include "div.h"
#include "reg.h"
#include "charinfo.h"
#include "font.h"
#include "input.h"
#include "geometry.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 */

// declarations to avoid friend name injections
class tfont;
class tfont_spec;
tfont *make_tfont(tfont_spec &);


/*
 *  how many boundaries of images have been written? Useful for
 *  debugging grohtml
 */

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);
};

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, int, symbol, font *);
  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();
  friend symbol get_font_name(int, environment *);
  friend symbol get_style_name(int);
};

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, int, 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;
}

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);
}

// 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;
}

symbol get_font_name(int fontno, environment *env)
{
  symbol f = font_table[fontno]->get_name();
  if (font_table[fontno]->is_style()) {
    return concat(env->get_family()->nm, f);
  }
  return f;
}

symbol get_style_name(int fontno)
{
  if (font_table[fontno]->is_style())
    return font_table[fontno]->get_name();
  else
    return EMPTY_SYMBOL;
}

hunits font_info::get_space_width(font_size fs, int space_sz)
{
  if (is_constant_spaced == CONSTANT_SPACE_NONE)
    return scale(hunits(fm->get_space_width(fs.to_scaled_points())),
			space_sz, 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 *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;       // \O[0] or \O[1] escape calls
  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();
public:
  FILE *fp;
  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;
  color *current_fill_color;
  color *current_glyph_color;
  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;
  int cur_div_level;
  string tag_list;
  void do_motion();
  void put(char c);
  void put(unsigned char c);
  void put(int i);
  void put(unsigned 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 *, tfont *, color *, color *);
  void put_char_width(charinfo *, tfont *, color *, color *, hunits, hunits);
  void right(hunits);
  void down(vunits);
  void moveto(hunits, vunits);
  void start_special(tfont *, color *, color *, int = 0);
  void start_special();
  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, color *, color *);
  void determine_line_limits (char code, hvpair *point, int npoints);
  void check_charinfo(tfont *tf, charinfo *ci);
  void glyph_color(color *c);
  void fill_color(color *c);
  int get_hpos() { return hpos; }
  int get_vpos() { return vpos; }
  void add_to_tag_list(string s);
  friend void space_char_hmotion_node::tprint(troff_output_file *);
  friend void unbreakable_space_node::tprint(troff_output_file *);
};

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);
}

inline void troff_output_file::put(unsigned int i)
{
  put_string(ui_to_a(i), fp);
}

void troff_output_file::start_special(tfont *tf, color *gcol, color *fcol,
				      int no_init_string)
{
  set_font(tf);
  glyph_color(gcol);
  fill_color(fcol);
  flush_tbuf();
  do_motion();
  if (!no_init_string)
    put("x X ");
}

void troff_output_file::start_special()
{
  flush_tbuf();
  do_motion();
  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)
{
  moveto(x, y);
  while (n != 0) {
    // Check whether we should push the current troff state and use
    // the state at the start of the invocation of this diversion.
    if (n->div_nest_level > cur_div_level && n->push_state) {
      state.push_state(n->push_state);
      cur_div_level = n->div_nest_level;
    }
    // Has the current diversion level decreased?  Then we must pop the
    // troff state.
    while (n->div_nest_level < cur_div_level) {
      state.pop_state();
      cur_div_level = n->div_nest_level;
    }
    // Now check whether the state has changed.
    if ((is_on() || n->force_tprint())
	&& (state.changed(n->state) || n->is_tag() || n->is_special)) {
      flush_tbuf();
      do_motion();
      force_motion = 1;
      flush();
      state.flush(fp, n->state, tag_list);
      tag_list = string("");
      flush();
    }
    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();
  if (is_on())
    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 (!is_on()) {
    tbuf_len = 0;
    return;
  }

  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);

  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)
{
  if (!is_on())
    return;

  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 + depth);
}

void troff_output_file::put_char_width(charinfo *ci, tfont *tf,
				       color *gcol, color *fcol,
				       hunits w, hunits k)
{
  int kk = k.to_units();
  if (!is_on()) {
    flush_tbuf();
    hpos += w.to_units() + kk;
    return;
  }
  set_font(tf);
  unsigned char c = ci->get_ascii_code();
  if (c == '\0') {
    glyph_color(gcol);
    fill_color(fcol);
    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
	&& (!gcol || gcol == current_glyph_color)
	&& (!fcol || fcol == current_fill_color)
	&& 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;
    }
    glyph_color(gcol);
    fill_color(fcol);
    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
	&& (!gcol || gcol == current_glyph_color)
	&& (!fcol || fcol == current_fill_color)
	&& n > 0 && n < 100 && !force_motion) {
      put(char(n/10 + '0'));
      put(char(n%10 + '0'));
      put(c);
      output_hpos = hpos;
    }
    else {
      glyph_color(gcol);
      fill_color(fcol);
      do_motion();
      put('c');
      put(c);
    }
    hpos += w.to_units() + kk;
  }
}

void troff_output_file::put_char(charinfo *ci, tfont *tf,
				 color *gcol, color *fcol)
{
  flush_tbuf();
  if (!is_on())
    return;
  set_font(tf);
  unsigned char c = ci->get_ascii_code();
  if (c == '\0') {
    glyph_color(gcol);
    fill_color(fcol);
    flush_tbuf();
    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
	&& (!gcol || gcol == current_glyph_color)
	&& (!fcol || fcol == current_fill_color)
	&& n > 0 && n < 100) {
      put(char(n/10 + '0'));
      put(char(n%10 + '0'));
      put(c);
      output_hpos = hpos;
    }
    else {
      glyph_color(gcol);
      fill_color(fcol);
      flush_tbuf();
      do_motion();
      put('c');
      put(c);
    }
  }
}

// set_font calls `flush_tbuf' if necessary.

void troff_output_file::set_font(tfont *tf)
{
  if (current_tfont == tf)
    return;
  flush_tbuf();
  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;
}

// fill_color calls `flush_tbuf' and `do_motion' if necessary.

void troff_output_file::fill_color(color *col)
{
  if (!col || current_fill_color == col)
    return;
  current_fill_color = col;
  if (!color_flag)
    return;
  flush_tbuf();
  do_motion();
  put("DF");
  unsigned int components[4];
  color_scheme cs;
  cs = col->get_components(components);
  switch (cs) {
  case DEFAULT:
    put('d');
    break;
  case RGB:
    put("r ");
    put(Red);
    put(' ');
    put(Green);
    put(' ');
    put(Blue);
    break;
  case CMY:
    put("c ");
    put(Cyan);
    put(' ');
    put(Magenta);
    put(' ');
    put(Yellow);
    break;
  case CMYK:
    put("k ");
    put(Cyan);
    put(' ');
    put(Magenta);
    put(' ');
    put(Yellow);
    put(' ');
    put(Black);
    break;
  case GRAY:
    put("g ");
    put(Gray);
    break;
  }
  put('\n');
}

// glyph_color calls `flush_tbuf' and `do_motion' if necessary.

void troff_output_file::glyph_color(color *col)
{
  if (!col || current_glyph_color == col)
    return;
  current_glyph_color = col;
  if (!color_flag)
    return;
  flush_tbuf();
  // grotty doesn't like a color command if the vertical position is zero.
  do_motion();
  put("m");
  unsigned int components[4];
  color_scheme cs;
  cs = col->get_components(components);
  switch (cs) {
  case DEFAULT:
    put('d');
    break;
  case RGB:
    put("r ");
    put(Red);
    put(' ');
    put(Green);
    put(' ');
    put(Blue);
    break;
  case CMY:
    put("c ");
    put(Cyan);
    put(' ');
    put(Magenta);
    put(' ');
    put(Yellow);
    break;
  case CMYK:
    put("k ");
    put(Cyan);
    put(' ');
    put(Magenta);
    put(' ');
    put(Yellow);
    put(' ');
    put(Black);
    break;
  case GRAY:
    put("g ");
    put(Gray);
    break;
  }
  put('\n');
}

void troff_output_file::add_to_tag_list(string s)
{
  if (tag_list == string(""))
    tag_list = s;
  else {
    tag_list += string("\n");
    tag_list += s;
  }
}

// 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;

  if (!is_on())
    return;

  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;
  case 'a':
    double c[2];
    int p[4];
    int minx, miny, maxx, maxy;
    x = output_hpos;
    y = output_vpos;
    p[0] = point[0].h.to_units();
    p[1] = point[0].v.to_units();
    p[2] = point[1].h.to_units();
    p[3] = point[1].v.to_units();
    if (adjust_arc_center(p, c)) {
      check_output_arc_limits(x, y,
			      p[0], p[1], p[2], p[3],
			      c[0], c[1],
			      &minx, &maxx, &miny, &maxy);
      check_output_limits(minx, miny);
      check_output_limits(maxx, maxy);
      break;
    }
    // fall through
  case 'l':
    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;
  default:
    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, color *gcol, color *fcol)
{
  int i;
  glyph_color(gcol);
  fill_color(fcol);
  flush_tbuf();
  do_motion();
  if (is_on()) {
    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);
    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;
  }
  if (is_on())
    put('\n');
}

void troff_output_file::really_on()
{
  flush_tbuf();
  force_motion = 1;
  do_motion();
}

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 = include_search_path.open_file_cautious(filename);
  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), current_fill_color(0),
  current_glyph_color(0), nfont_positions(10), tbuf_len(0), begun_page(0),
  cur_div_level(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 *)
{
}

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)
    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 *)
{
}

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)
{
  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 *, statem *, int, node * = 0);
  int ends_sentence();
  int overlaps_vertically();
  int overlaps_horizontally();
};

charinfo_node::charinfo_node(charinfo *c, statem *s, int pop, node *x)
: node(x, s, pop), 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;
  color *gcol;
  color *fcol;		/* this is needed for grotty */
#ifdef STORE_WIDTH
  hunits wid;
  glyph_node(charinfo *, tfont *, color *, color *, hunits,
	     statem *, int, node * = 0);
#endif
public:
  void *operator new(size_t);
  void operator delete(void *);
  glyph_node(charinfo *, tfont *, color *, color *,
	     statem *, int, 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();
  color *get_glyph_color();
  color *get_fill_color();
  void tprint(troff_output_file *);
  void zero_width_tprint(troff_output_file *);
  hyphen_list *get_hyphen_list(hyphen_list *, int *);
  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();
  int is_tag();
  void debug_node();
};

glyph_node *glyph_node::free_list = 0;

class ligature_node : public glyph_node {
  node *n1;
  node *n2;
#ifdef STORE_WIDTH
  ligature_node(charinfo *, tfont *, color *, color *, hunits,
		node *, node *, statem *, int, node * = 0);
#endif
public:
  void *operator new(size_t);
  void operator delete(void *);
  ligature_node(charinfo *, tfont *, color *, color *,
		node *, node *, statem *, int, node * = 0);
  ~ligature_node();
  node *copy();
  node *add_self(node *, hyphen_list **);
  hyphen_list *get_hyphen_list(hyphen_list *, int *);
  void ascii_print(ascii_output_file *);
  void asciify(macro *);
  int same(node *);
  const char *type();
  int force_tprint();
  int is_tag();
};

class kern_pair_node : public node {
  hunits amount;
  node *n1;
  node *n2;
public:
  kern_pair_node(hunits, node *, node *, statem *, int, node * = 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 *, int *);
  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();
  int is_tag();
  void vertical_extent(vunits *, vunits *);
};

class dbreak_node : public node {
  node *none;
  node *pre;
  node *post;
public:
  dbreak_node(node *, node *, statem *, int, node * = 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();
  int is_tag();
};

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, color *gc, color *fc,
		       statem *s, int pop, node *x)
: charinfo_node(c, s, pop, x), tf(t), gcol(gc), fcol(fc)
{
#ifdef STORE_WIDTH
  wid = tf->get_width(ci);
#endif
}

#ifdef STORE_WIDTH
glyph_node::glyph_node(charinfo *c, tfont *t,
		       color *gc, color *fc, hunits w,
		       statem *s, int pop, node *x)
: charinfo_node(c, s, pop, x), tf(t), gcol(gc), fcol(fc), wid(w)
{
}
#endif

node *glyph_node::copy()
{
#ifdef STORE_WIDTH
  return new glyph_node(ci, tf, gcol, fcol, wid, state, div_nest_level);
#else
  return new glyph_node(ci, tf, gcol, fcol, state, div_nest_level);
#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, int *count)
{
  (*count)++;
  return new hyphen_list(ci->get_hyphenation_code(), tail);
}

tfont *node::get_tfont()
{
  return 0;
}

tfont *glyph_node::get_tfont()
{
  return tf;
}

color *node::get_glyph_color()
{
  return 0;
}

color *glyph_node::get_glyph_color()
{
  return gcol;
}

color *node::get_fill_color()
{
  return 0;
}

color *glyph_node::get_fill_color()
{
  return fcol;
}

node *node::merge_glyph_node(glyph_node *)
{
  return 0;
}

node *glyph_node::merge_glyph_node(glyph_node *gn)
{
  if (tf == gn->tf && gcol == gn->gcol && fcol == gn->fcol) {
    charinfo *lig;
    if ((lig = tf->get_lig(ci, gn->ci)) != 0) {
      node *next1 = next;
      next = 0;
      return new ligature_node(lig, tf, gcol, fcol, this, gn, state,
			       gn->div_nest_level, next1);
    }
    hunits kern;
    if (tf->get_kern(ci, gn->ci, &kern)) {
      node *next1 = next;
      next = 0;
      return new kern_pair_node(kern, this, gn, state,
				gn->div_nest_level, 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());
}

void glyph_node::debug_node()
{
  unsigned char c = ci->get_ascii_code();
  fprintf(stderr, "{ %s [", type());
  if (c)
    fprintf(stderr, "%c", c);
  else
    fprintf(stderr, ci->nm.contents());
  if (push_state)
    fprintf(stderr, " <push_state>");
  if (state)
    state->display_state();
  fprintf(stderr, " nest level %d", div_nest_level);
  fprintf(stderr, "]}\n");
  fflush(stderr);
}

ligature_node::ligature_node(charinfo *c, tfont *t, color *gc, color *fc,
			     node *gn1, node *gn2, statem *s,
			     int pop, node *x)
: glyph_node(c, t, gc, fc, s, pop, x), n1(gn1), n2(gn2)
{
}

#ifdef STORE_WIDTH
ligature_node::ligature_node(charinfo *c, tfont *t, color *gc, color *fc,
			     hunits w, node *gn1, node *gn2, statem *s,
			     int pop, node *x)
: glyph_node(c, t, gc, fc, w, s, pop, 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, gcol, fcol, wid, n1->copy(), n2->copy(),
			   state, div_nest_level);
#else
  return new ligature_node(ci, tf, gcol, fcol, n1->copy(), n2->copy(),
			   state, div_nest_level);
#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, int *count)
{
  hyphen_list *hl = n2->get_hyphen_list(tail, count);
  return n1->get_hyphen_list(hl, count);
}

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,
			       statem* s, int pop, node *x)
: node(x, s, pop), amount(n), n1(first), n2(second)
{
}

dbreak_node::dbreak_node(node *n, node *p, statem *s, int pop, node *x)
: node(x, s, pop), 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)) {
      color *gcol = n2->get_glyph_color();
      color *fcol = n2->get_fill_color();
      node *next1 = next;
      next = 0;
      node *n = copy();
      glyph_node *gn = new glyph_node(soft_hyphen_char, tf, gcol, fcol,
				      state, div_nest_level);
      node *nn = n->merge_glyph_node(gn);
      if (nn == 0) {
	gn->next = n;
	nn = gn;
      }
      return new dbreak_node(this, nn, state, div_nest_level, 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(), state,
			    div_nest_level);
}

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),
				   state, div_nest_level);
  p->post = copy_node_list(post);
  return p;
}

hyphen_list *node::get_hyphen_list(hyphen_list *tail, int *)
{
  return tail;
}

hyphen_list *kern_pair_node::get_hyphen_list(hyphen_list *tail, int *count)
{
  hyphen_list *hl = n2->get_hyphen_list(tail, count);
  return n1->get_hyphen_list(hl, count);
}

class hyphen_inhibitor_node : public node {
public:
  hyphen_inhibitor_node(node * = 0);
  node *copy();
  int same(node *);
  const char *type();
  int force_tprint();
  int is_tag();
  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;
}

int hyphen_inhibitor_node::is_tag()
{
  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)) {
    color *gcol = get_glyph_color();
    color *fcol = get_fill_color();
    node *next1 = next;
    next = 0;
    node *n = copy();
    glyph_node *gn = new glyph_node(soft_hyphen_char, tf, gcol, fcol,
				    state, div_nest_level);
    node *n1 = n->merge_glyph_node(gn);
    if (n1 == 0) {
      gn->next = n;
      n1 = gn;
    }
    return new dbreak_node(this, n1, state, div_nest_level, 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;
}

int node::is_tag()
{
  return 0;
}

hunits hmotion_node::width()
{
  return n;
}

units node::size()
{
  return points_to_units(10);
}

void node::debug_node()
{
  fprintf(stderr, "{ %s ", type());
  if (push_state)
    fprintf(stderr, " <push_state>");
  if (state)
    fprintf(stderr, " <state>");
  fprintf(stderr, " nest level %d", div_nest_level);
  fprintf(stderr, " }\n");
  fflush(stderr);
}

void node::debug_node_list()
{
  node *n = next;

  debug_node();
  while (n != 0) {
    n->debug_node();
    n = n->next;
  }
}

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_node = n->last_char_node();
    if (last_node)
      return last_node;
  }
  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, statem *, int, 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 *, int *);
  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();
  int is_tag();
};

node *node::add_italic_correction(hunits *wd)
{
  hunits ic = italic_correction();
  if (ic.is_zero())
    return this;
  else {
    node *next1 = next;
    next = 0;
    *wd += ic;
    return new italic_corrected_node(this, ic, state, div_nest_level, next1);
  }
}

italic_corrected_node::italic_corrected_node(node *nn, hunits xx, statem *s,
					     int pop, node *p)
: node(p, s, pop), 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, state, div_nest_level);
}

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(&not_interested);
  n = 0;
  delete this;
  return nd;
}

hyphen_list *italic_corrected_node::get_hyphen_list(hyphen_list *tail,
						    int *count)
{
  return n->get_hyphen_list(tail, count);
}

int italic_corrected_node::character_type()
{
  return n->character_type();
}

class break_char_node : public node {
  node *ch;
  char break_code;
  color *col;
public:
  break_char_node(node *, int, color *, node * = 0);
  break_char_node(node *, int, color *, statem *, 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 *, int *);
  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();
  int is_tag();
};

break_char_node::break_char_node(node *n, int bc, color *c, node *x)
: node(x), ch(n), break_code(bc), col(c)
{
}

break_char_node::break_char_node(node *n, int bc, color *c, statem *s,
				 int pop, node *x)
: node(x, s, pop), ch(n), break_code(bc), col(c)
{
}

break_char_node::~break_char_node()
{
  delete ch;
}

node *break_char_node::copy()
{
  return new break_char_node(ch->copy(), break_code, col, state,
			     div_nest_level);
}

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, col, n);
    n->freeze_space();
  }
  next = n;
  n = this;
  if ((*p)->breakable && (break_code & 2)) {
    n = new space_node(H0, col, 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, int *)
{
  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, state, div_nest_level);
}

extra_size_node::extra_size_node(vunits i, statem *s, int pop)
: node(0, s, pop), n(i)
{
}

extra_size_node::extra_size_node(vunits i)
: n(i)
{
}

node *vertical_size_node::copy()
{
  return new vertical_size_node(n, state, div_nest_level);
}

vertical_size_node::vertical_size_node(vunits i, statem *s, int pop)
: node(0, s, pop), n(i)
{
}

vertical_size_node::vertical_size_node(vunits i)
: n(i)
{
}

node *hmotion_node::copy()
{
  return new hmotion_node(n, was_tab, unformat, col, state, div_nest_level);
}

node *space_char_hmotion_node::copy()
{
  return new space_char_hmotion_node(n, col, state, div_nest_level);
}

vmotion_node::vmotion_node(vunits i, color *c)
: n(i), col(c)
{
}

vmotion_node::vmotion_node(vunits i, color *c, statem *s, int pop)
: node(0, s, pop), n(i), col(c)
{
}

node *vmotion_node::copy()
{
  return new vmotion_node(n, col, state, div_nest_level);
}

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;
}

hline_node::hline_node(hunits i, node *c, node *nxt)
: node(nxt), x(i), n(c)
{
}

hline_node::hline_node(hunits i, node *c, statem *s, int pop, node *nxt)
: node(nxt, s, pop), x(i), n(c)
{
}

node *hline_node::copy()
{
  return new hline_node(x, n ? n->copy() : 0, state, div_nest_level);
}

hunits hline_node::width()
{
  return x < H0 ? H0 : x;
}

vline_node::vline_node(vunits i, node *c, node *nxt)
: node(nxt), x(i), n(c)
{
}

vline_node::vline_node(vunits i, node *c, statem *s, int pop, node *nxt)
: node(nxt, s, pop), x(i), n(c)
{
}

vline_node::~vline_node()
{
  if (n)
    delete n;
}

node *vline_node::copy()
{
  return new vline_node(x, n ? n->copy() : 0, state, div_nest_level);
}

hunits vline_node::width()
{
  return n == 0 ? H0 : n->width();
}

zero_width_node::zero_width_node(node *nd, statem *s, int pop)
: node(0, s, pop), n(nd)
{
}

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), state, div_nest_level);
}

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(statem *s, int pop)
: node(0, s, pop), list(0), max_width(H0)
{
}

overstrike_node::~overstrike_node()
{
  delete_node_list(list);
}

node *overstrike_node::copy()
{
  overstrike_node *on = new overstrike_node(state, div_nest_level);
  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(statem *s, int pop)
: node(0, s, pop), list(0), max_width(H0)
{
}

bracket_node::~bracket_node()
{
  delete_node_list(list);
}

node *bracket_node::copy()
{
  bracket_node *on = new bracket_node(state, div_nest_level);
  node *last_node = 0;
  node *tem;
  if (list)
    list->last = 0;
  for (tem = list; tem; tem = tem->next) {
    if (tem->next)
      tem->next->last = tem;
    last_node = tem;
  }
  for (tem = last_node; 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, color *c, node *p)
: node(p, 0, 0), n(nn), set(0), was_escape_colon(0), col(c)
{
}

space_node::space_node(hunits nn, color *c, statem *s, int pop, node *p)
: node(p, s, pop), n(nn), set(0), was_escape_colon(0), col(c)
{
}

space_node::space_node(hunits nn, int s, int flag, color *c, statem *st,
		       int pop, node *p)
: node(p, st, pop), n(nn), set(s), was_escape_colon(flag), col(c)
{
}

#if 0
space_node::~space_node()
{
}
#endif

node *space_node::copy()
{
  return new space_node(n, set, was_escape_colon, col, state, div_nest_level);
}

int space_node::force_tprint()
{
  return 0;
}

int space_node::is_tag()
{
  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 *n_spaces, hunits *desired_space)
{
  if (!set) {
    assert(*n_spaces > 0);
    if (*n_spaces == 1) {
      n += *desired_space;
      *desired_space = H0;
    }
    else {
      hunits extra = *desired_space / *n_spaces;
      *desired_space -= extra;
      n += extra;
    }
    *n_spaces -= 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, statem *s, int pop,
					 node *p)
: node(p, s, pop), n(d)
{
}

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, state, div_nest_level);
}

diverted_copy_file_node::diverted_copy_file_node(symbol s, statem *st,
						 int pop, node *p)
: node(p, st, pop), filename(s)
{
}

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, state, div_nest_level);
}

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_asciify_code();
  if (c == 0)
    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, color *c,
						 statem *s, int pop,
						 node *nxt)
: hmotion_node(i, c, s, pop, nxt)
{
}

space_char_hmotion_node::space_char_hmotion_node(hunits i, color *c,
 						 node *nxt)
: hmotion_node(i, c, 0, 0, nxt)
{
}

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 wd, int ns,
					breakpoint *rest, int is_inner)
{
  if (next && next->discardable())
    return rest;
  breakpoint *bp = new breakpoint;
  bp->next = rest;
  bp->width = wd;
  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 && 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 wd, int ns,
					 breakpoint *rest, int is_inner)
{
  breakpoint *bp = new breakpoint;
  bp->next = rest;
  bp->width = wd;
  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, &wd, 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 = env_definite_font(curenv);
  tf = font_table[fontno]->get_tfont(fs, char_height, char_slant, fontno);
  if (curenv->is_composite())
    tf = tf->get_plain();
  gcol = curenv->get_glyph_color();
  fcol = curenv->get_fill_color();
  is_special = 1;
}

special_node::special_node(const macro &m, tfont *t,
			   color *gc, color *fc,
			   statem *s, int pop,
			   int n)
: node(0, s, pop), mac(m), tf(t), gcol(gc), fcol(fc), no_init_string(n)
{
  is_special = 1;
}

int special_node::same(node *n)
{
  return mac == ((special_node *)n)->mac
	 && tf == ((special_node *)n)->tf
	 && gcol == ((special_node *)n)->gcol
	 && fcol == ((special_node *)n)->fcol
	 && 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;
}

int special_node::is_tag()
{
  return 0;
}

node *special_node::copy()
{
  return new special_node(mac, tf, gcol, fcol, state, div_nest_level,
			  no_init_string);
}

void special_node::tprint_start(troff_output_file *out)
{
  out->start_special(tf, gcol, fcol, 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),
  image_id(0)
{
}

suppress_node::suppress_node(symbol f, char p, int id)
: is_on(2), emit_limits(0), filename(f), position(p), image_id(id)
{
  is_special = 1;
}

suppress_node::suppress_node(int issue_limits, int on_or_off,
			     symbol f, char p, int id,
			     statem *s, int pop)
: node(0, s, pop), is_on(on_or_off), emit_limits(issue_limits), filename(f),
  position(p), image_id(id)
{
}

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)
	  && (image_id == ((suppress_node *)n)->image_id));
}

const char *suppress_node::type()
{
  return "suppress_node";
}

node *suppress_node::copy()
{
  return new suppress_node(emit_limits, is_on, filename, position, image_id,
			   state, div_nest_level);
}

/* tag_node */

tag_node::tag_node()
: delayed(0)
{
  is_special = 1;
}

tag_node::tag_node(string s, int delay)
: tag_string(s), delayed(delay)
{
  is_special = !delay;
}

tag_node::tag_node(string s, statem *st, int pop, int delay)
: node(0, st, pop), tag_string(s), delayed(delay)
{
  is_special = !delay;
}

node *tag_node::copy()
{
  return new tag_node(tag_string, state, div_nest_level, delayed);
}

void tag_node::tprint(troff_output_file *out)
{
  if (delayed)
    out->add_to_tag_list(tag_string);
  else
    out->state.add_tag(out->fp, tag_string);
}

int tag_node::same(node *nd)
{
  return tag_string == ((tag_node *)nd)->tag_string
	 && delayed == ((tag_node *)nd)->delayed;
}

const char *tag_node::type()
{
  return "tag_node";
}

int tag_node::force_tprint()
{
  return !delayed;
}

int tag_node::is_tag()
{
  return !delayed;
}

int tag_node::ends_sentence()
{
  return 2;
}

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;
static int last_image_id = 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 = topdiv->get_page_number();
  // firstly check to see whether this suppress node contains
  // an image filename & position.
  if (is_on == 2) {
    // remember position and filename
    last_position = position;
    char *tem = (char *)last_image_filename;
    last_image_filename = strsave(filename.contents());
    if (tem)
      a_delete tem;
    last_image_id = image_id;
    // printf("start of image and page = %d\n", current_page);
  }
  else {
    // now check whether the suppress node requires us to issue limits.
    if (emit_limits) {
      char name[8192];
      // remember that the filename will contain a %d in which the
      // last_image_id is placed
      if (last_image_filename == (char *) 0)
	*name = '\0';
      else
	sprintf(name, last_image_filename, last_image_id);
      if (is_html) {
	switch (last_position) {
	case 'c':
	  out->start_special();
	  put(out, "devtag:.centered-image");
	  break;
	case 'r':
	  out->start_special();
	  put(out, "devtag:.right-image");
	  break;
	case 'l':
	  out->start_special();
	  put(out, "devtag:.left-image");
	  break;
	case 'i':
	  ;
	default:
	  ;
	}
	out->end_special();
	out->start_special();
	put(out, "devtag:.auto-image ");
	put(out, name);
	out->end_special();
      }
      else {
	// postscript (or other device)
	if (suppress_start_page > 0 && current_page != suppress_start_page)
	  error("suppression limit registers span more than one page;\n"
	        "image description %1 will be wrong", image_no);
	// if (topdiv->get_page_number() != suppress_start_page)
	//  fprintf(stderr, "end of image and topdiv page = %d   and  suppress_start_page = %d\n",
	//	  topdiv->get_page_number(), suppress_start_page);

	// 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",
		topdiv->get_page_number(),
		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();
	// lastly we reset the output registers
	reset_output_registers();
      }
      else
	out->off();
      suppress_start_page = current_page;
    }
  }
}

int suppress_node::force_tprint()
{
  return is_on;
}

int suppress_node::is_tag()
{
  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 *, statem *, int, 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 *, int *);
  node *add_self(node *, hyphen_list **);
  tfont *get_tfont();
  int same(node *);
  const char *type();
  int force_tprint();
  int is_tag();
  void vertical_extent(vunits *, vunits *);
  vunits vertical_width();
};

composite_node::composite_node(node *p, charinfo *c, tfont *t, statem *s,
			       int pop, node *x)
: charinfo_node(c, s, pop, 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, state, div_nest_level);
}

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_asciify_code();
  if (c == 0)
    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, int *count)
{
  (*count)++;
  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 *minimum, vunits *maximum)
{
  n = reverse_node_list(n);
  node_list_vertical_extent(n, minimum, maximum);
  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, color *c, width_list *w, node *x)
: space_node(d, c, x), orig_width(w), unformat(0)
{
}

word_space_node::word_space_node(hunits d, int s, color *c, width_list *w,
				 int flag, statem *st, int pop, node *x)
: space_node(d, s, 0, c, st, pop, 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, col, w_new, unformat, state,
			     div_nest_level);
}

int word_space_node::set_unformat_flag()
{
  unformat = 1;
  return 1;
}

void word_space_node::tprint(troff_output_file *out)
{
  out->fill_color(col);
  out->word_marker();
  out->right(n);
}

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, color *c, node *x)
: word_space_node(d, c, 0, x)
{
}

unbreakable_space_node::unbreakable_space_node(hunits d, int s,
					       color *c, statem *st, int pop,
					       node *x)
: word_space_node(d, s, c, 0, 0, st, pop, x)
{
}

node *unbreakable_space_node::copy()
{
  return new unbreakable_space_node(n, set, col, state, div_nest_level);
}

int unbreakable_space_node::force_tprint()
{
  return 0;
}

int unbreakable_space_node::is_tag()
{
  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,
		     color *gc, color *fc)
: npoints(np), sz(s), gcol(gc), fcol(fc), code(c)
{
  point = new hvpair[npoints];
  for (int i = 0; i < npoints; i++)
    point[i] = p[i];
}

draw_node::draw_node(char c, hvpair *p, int np, font_size s,
		     color *gc, color *fc, statem *st, int pop)
: node(0, st, pop), npoints(np), sz(s), gcol(gc), fcol(fc), 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
      || gcol != nd->gcol || fcol != nd->fcol)
    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;
}

int draw_node::is_tag()
{
  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, gcol, fcol, state,
		       div_nest_level);
}

void draw_node::tprint(troff_output_file *out)
{
  out->draw(code, point, npoints, sz, gcol, fcol);
}

/* tprint methods */

void glyph_node::tprint(troff_output_file *out)
{
  tfont *ptf = tf->get_plain();
  if (ptf == tf)
    out->put_char_width(ci, ptf, gcol, fcol, 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, gcol, fcol);
      out->right(offset);
    }
    out->put_char_width(ci, ptf, gcol, fcol, 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, gcol, fcol);
  if (bold) {
    out->right(offset);
    out->put_char(ci, ptf, gcol, fcol);
    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);
    if (out->is_on())
      n->tprint(out);
    out->right(xx - xx2);
  }
  else {
    hunits rem = x - w*i;
    if (rem > H0)
      if (n->overlaps_horizontally()) {
	if (out->is_on())
	  n->tprint(out);
	out->right(rem - w);
      }
      else
	out->right(rem);
    while (--i >= 0)
      if (out->is_on())
	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);
	if (out->is_on())
	  n->tprint(out);
	out->down(-h);
      }
      else {
	if (out->is_on())
	  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);
      }
      if (out->is_on())
	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->fill_color(col);
  out->right(n);
}

void hmotion_node::tprint(troff_output_file *out)
{
  out->fill_color(col);
  out->right(n);
}

void space_char_hmotion_node::tprint(troff_output_file *out)
{
  out->fill_color(col);
  if (is_html) {
    // we emit the space width as a negative glyph index
    out->flush_tbuf();
    out->do_motion();
    out->put('N');
    out->put(-n.to_units());
    out->put('\n');
  }
  out->right(n);
}

void vmotion_node::tprint(troff_output_file *out)
{
  out->fill_color(col);
  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, 0, 0, 0);
}

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) {
    macro *mac = s->get_macro();
    if (mac && s->is_fallback())
      return make_composite_node(s, env);
    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) {
      symbol f = font_table[fontno]->get_name();
      string gl(f.contents());
      gl += ' ';
      gl += s->nm.contents();
      gl += '\0';
      charinfo *ci = get_charinfo(symbol(gl.contents()));
      if (ci && ci->get_macro())
	return make_composite_node(ci, env);
    }
    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 (mac && s->is_special())
	return make_composite_node(s, env);
    if (!found) {
      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();
  color *gcol = env->get_glyph_color();
  color *fcol = env->get_fill_color();
  return new glyph_node(s, tf, gcol, fcol, 0, 0);
}

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(),
				       env->get_fill_color());
  case charinfo::TRANSLATE_STRETCHABLE_SPACE:
    return new unbreakable_space_node(env->get_space_width(),
				      env->get_fill_color());
  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 && ci->is_normal())
    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 **glyph_comp_np)
{
  node *res;
  switch (ci->get_special_translation()) {
  case charinfo::TRANSLATE_SPACE:
    res = new space_char_hmotion_node(env->get_space_width(),
				      env->get_fill_color(), this);
    *widthp += res->width();
    return res;
  case charinfo::TRANSLATE_STRETCHABLE_SPACE:
    res = new unbreakable_space_node(env->get_space_width(),
				     env->get_fill_color(), this);
    res->freeze_space();
    *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 && ci->is_normal()) {
    res = make_composite_node(ci, env);
    if (res) {
      res->next = this;
      *widthp += res->width();
      if (glyph_comp_np)
	*glyph_comp_np = res;
    }
    else {
      if (glyph_comp_np)
	*glyph_comp_np = res;
      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;
      }
      if (glyph_comp_np)
	*glyph_comp_np = res;
    }
  }
  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, env->get_fill_color(), 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 extra_size_node::is_tag()
{
  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 vertical_size_node::is_tag()
{
  return 0;
}

int hmotion_node::same(node *nd)
{
  return n == ((hmotion_node *)nd)->n
	 && col == ((hmotion_node *)nd)->col;
}

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;
}

int hmotion_node::is_tag()
{
  return 0;
}

node *hmotion_node::add_self(node *nd, hyphen_list **p)
{
  next = nd;
  hyphen_list *pp = *p;
  *p = (*p)->next;
  delete pp;
  return this;
}

hyphen_list *hmotion_node::get_hyphen_list(hyphen_list *tail, int *)
{
  return new hyphen_list(0, tail);
}

int space_char_hmotion_node::same(node *nd)
{
  return n == ((space_char_hmotion_node *)nd)->n
	 && col == ((space_char_hmotion_node *)nd)->col;
}

const char *space_char_hmotion_node::type()
{
  return "space_char_hmotion_node";
}

int space_char_hmotion_node::force_tprint()
{
  return 0;
}

int space_char_hmotion_node::is_tag()
{
  return 0;
}

node *space_char_hmotion_node::add_self(node *nd, hyphen_list **p)
{
  next = nd;
  hyphen_list *pp = *p;
  *p = (*p)->next;
  delete pp;
  return this;
}

hyphen_list *space_char_hmotion_node::get_hyphen_list(hyphen_list *tail,
						      int *)
{
  return new hyphen_list(0, tail);
}

int vmotion_node::same(node *nd)
{
  return n == ((vmotion_node *)nd)->n
	 && col == ((vmotion_node *)nd)->col;
}

const char *vmotion_node::type()
{
  return "vmotion_node";
}

int vmotion_node::force_tprint()
{
  return 0;
}

int vmotion_node::is_tag()
{
  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 hline_node::is_tag()
{
  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 vline_node::is_tag()
{
  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 dummy_node::is_tag()
{
  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::is_tag()
{
  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 zero_width_node::is_tag()
{
  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;
}

int italic_corrected_node::is_tag()
{
  return 0;
}

left_italic_corrected_node::left_italic_corrected_node(node *xx)
: node(xx), n(0)
{
}

left_italic_corrected_node::left_italic_corrected_node(statem *s, int pop,
						       node *xx)
: node(xx, s, pop), 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(state, div_nest_level);
  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::is_tag()
{
  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 *minimum,
						 vunits *maximum)
{
  if (n)
    n->vertical_extent(minimum, maximum);
  else
    node::vertical_extent(minimum, maximum);
}

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,
							 int *count)
{
  return n ? n->get_hyphen_list(tail, count) : tail;
}

node *left_italic_corrected_node::add_self(node *nd, hyphen_list **p)
{
  if (n) {
    nd = new left_italic_corrected_node(state, div_nest_level, 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;
}

int overstrike_node::is_tag()
{
  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, int *)
{
  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 bracket_node::is_tag()
{
  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 composite_node::is_tag()
{
  return 0;
}

int glyph_node::same(node *nd)
{
  return ci == ((glyph_node *)nd)->ci
	 && tf == ((glyph_node *)nd)->tf
	 && gcol == ((glyph_node *)nd)->gcol
	 && fcol == ((glyph_node *)nd)->fcol;
}

const char *glyph_node::type()
{
  return "glyph_node";
}

int glyph_node::force_tprint()
{
  return 0;
}

int glyph_node::is_tag()
{
  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 ligature_node::is_tag()
{
  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 kern_pair_node::is_tag()
{
  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 dbreak_node::is_tag()
{
  return 0;
}

int break_char_node::same(node *nd)
{
  return break_code == ((break_char_node *)nd)->break_code
	 && col == ((break_char_node *)nd)->col
	 && 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 break_char_node::is_tag()
{
  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 line_start_node::is_tag()
{
  return 0;
}

int space_node::same(node *nd)
{
  return n == ((space_node *)nd)->n
	      && set == ((space_node *)nd)->set
	      && col == ((space_node *)nd)->col;
}

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
	 && col == ((word_space_node *)nd)->col;
}

const char *word_space_node::type()
{
  return "word_space_node";
}

int word_space_node::force_tprint()
{
  return 0;
}

int word_space_node::is_tag()
{
  return 0;
}

void unbreakable_space_node::tprint(troff_output_file *out)
{
  out->fill_color(col);
  if (is_html) {
    // we emit the space width as a negative glyph index
    out->flush_tbuf();
    out->do_motion();
    out->put('N');
    out->put(-n.to_units());
    out->put('\n');
  }
  out->right(n);
}

int unbreakable_space_node::same(node *nd)
{
  return n == ((unbreakable_space_node *)nd)->n
	 && set == ((unbreakable_space_node *)nd)->set
	 && col == ((unbreakable_space_node *)nd)->col;
}

const char *unbreakable_space_node::type()
{
  return "unbreakable_space_node";
}

node *unbreakable_space_node::add_self(node *nd, hyphen_list **p)
{
  next = nd;
  hyphen_list *pp = *p;
  *p = (*p)->next;
  delete pp;
  return this;
}

hyphen_list *unbreakable_space_node::get_hyphen_list(hyphen_list *tail, int *)
{
  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_space_node::is_tag()
{
  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;
}

int diverted_copy_file_node::is_tag()
{
  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,
				   int check_only = 0)
{
  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(), &not_found, check_only);
    if (check_only)
      return fm != 0;
    if (!fm) {
      if (not_found)
	warning(WARN_FONT, "can't find font `%1'", external_name.contents());
      (void)font_dictionary.lookup(external_name, &a_char);
      return 0;
    }
    (void)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 (check_only)
    return 1;
  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);
}

int check_font(symbol fam, symbol name)
{
  if (check_style(name))
    name = concat(fam, name);
  return mount_font_no_translate(0, name, name, 1);
}

int check_style(symbol s)
{
  int i = symbol_fontno(s);
  return i < 0 ? 0 : font_table[i]->is_style();
}

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
      (void)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();
	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 nam;
  font_family *fam;
  while (iter.get(&nam, (void **)&fam)) {
    int mapsize = fam->map_size;
    if (n < mapsize)
      fam->map[n] = -1;
    for (int i = 0; i < mapsize; 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;
}

void define_font_special_character()
{
  int n = get_fontno();
  if (n < 0) {
    skip_line();
    return;
  }
  symbol f = font_table[n]->get_name();
  do_define_character(CHAR_FONT_SPECIAL, f.contents());
}

void remove_font_special_character()
{
  int n = get_fontno();
  if (n < 0) {
    skip_line();
    return;
  }
  symbol f = font_table[n]->get_name();
  while (!tok.newline() && !tok.eof()) {
    if (!tok.space() && !tok.tab()) {
      charinfo *s = tok.get_char(1);
      string gl(f.contents());
      gl += ' ';
      gl += s->nm.contents();
      gl += '\0';
      charinfo *ci = get_charinfo(symbol(gl.contents()));
      if (!ci)
	break;
      macro *m = ci->set_macro(0);
      if (m)
	delete m;
    }
    tok.next();
  }
  skip_line();
}

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] != 0;
}

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("bd", bold_font);
  init_request("cs", constant_space);
  init_request("fp", font_position);
  init_request("fschar", define_font_special_character);
  init_request("fspecial", font_special_request);
  init_request("ftr", font_translate);
  init_request("kern", kern_request);
  init_request("lg", ligature);
  init_request("rfschar", remove_font_special_character);
  init_request("shc", set_soft_hyphen_char);
  init_request("special", special_request);
  init_request("sty", style);
  init_request("tkf", track_kern);
  init_request("uf", underline_font);
  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);
}