env.cpp revision 114402
1// -*- C++ -*-
2/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003
3   Free Software Foundation, Inc.
4     Written by James Clark (jjc@jclark.com)
5
6This file is part of groff.
7
8groff is free software; you can redistribute it and/or modify it under
9the terms of the GNU General Public License as published by the Free
10Software Foundation; either version 2, or (at your option) any later
11version.
12
13groff is distributed in the hope that it will be useful, but WITHOUT ANY
14WARRANTY; without even the implied warranty of MERCHANTABILITY or
15FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16for more details.
17
18You should have received a copy of the GNU General Public License along
19with groff; see the file COPYING.  If not, write to the Free Software
20Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21
22#include "troff.h"
23#include "symbol.h"
24#include "dictionary.h"
25#include "hvunits.h"
26#include "env.h"
27#include "request.h"
28#include "node.h"
29#include "token.h"
30#include "div.h"
31#include "reg.h"
32#include "charinfo.h"
33#include "macropath.h"
34#include "input.h"
35#include <math.h>
36
37symbol default_family("T");
38
39enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 };
40
41enum { HYPHEN_LAST_LINE = 2, HYPHEN_LAST_CHARS = 4, HYPHEN_FIRST_CHARS = 8 };
42
43struct env_list {
44  environment *env;
45  env_list *next;
46  env_list(environment *e, env_list *p) : env(e), next(p) {}
47};
48
49env_list *env_stack;
50const int NENVIRONMENTS = 10;
51environment *env_table[NENVIRONMENTS];
52dictionary env_dictionary(10);
53environment *curenv;
54static int next_line_number = 0;
55
56charinfo *field_delimiter_char;
57charinfo *padding_indicator_char;
58
59int translate_space_to_dummy = 0;
60
61class pending_output_line {
62  node *nd;
63  int no_fill;
64  vunits vs;
65  vunits post_vs;
66  hunits width;
67#ifdef WIDOW_CONTROL
68  int last_line;		// Is it the last line of the paragraph?
69#endif /* WIDOW_CONTROL */
70public:
71  pending_output_line *next;
72
73  pending_output_line(node *, int, vunits, vunits, hunits,
74		      pending_output_line * = 0);
75  ~pending_output_line();
76  int output();
77
78#ifdef WIDOW_CONTROL
79  friend void environment::mark_last_line();
80  friend void environment::output(node *, int, vunits, vunits, hunits);
81#endif /* WIDOW_CONTROL */
82};
83
84pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv,
85					 hunits w, pending_output_line *p)
86: nd(n), no_fill(nf), vs(v), post_vs(pv), width(w),
87#ifdef WIDOW_CONTROL
88  last_line(0),
89#endif /* WIDOW_CONTROL */
90  next(p)
91{
92}
93
94pending_output_line::~pending_output_line()
95{
96  delete_node_list(nd);
97}
98
99int pending_output_line::output()
100{
101  if (trap_sprung_flag)
102    return 0;
103#ifdef WIDOW_CONTROL
104  if (next && next->last_line && !no_fill) {
105    curdiv->need(vs + post_vs + vunits(vresolution));
106    if (trap_sprung_flag) {
107      next->last_line = 0;	// Try to avoid infinite loops.
108      return 0;
109    }
110  }
111#endif
112  curdiv->output(nd, no_fill, vs, post_vs, width);
113  nd = 0;
114  return 1;
115}
116
117void environment::output(node *nd, int no_fill, vunits vs, vunits post_vs,
118			 hunits width)
119{
120#ifdef WIDOW_CONTROL
121  while (pending_lines) {
122    if (widow_control && !pending_lines->no_fill && !pending_lines->next)
123      break;
124    if (!pending_lines->output())
125      break;
126    pending_output_line *tem = pending_lines;
127    pending_lines = pending_lines->next;
128    delete tem;
129  }
130#else /* WIDOW_CONTROL */
131  output_pending_lines();
132#endif /* WIDOW_CONTROL */
133  if (!trap_sprung_flag && !pending_lines
134#ifdef WIDOW_CONTROL
135      && (!widow_control || no_fill)
136#endif /* WIDOW_CONTROL */
137      ) {
138    curdiv->output(nd, no_fill, vs, post_vs, width);
139    emitted_node = 1;
140  } else {
141    pending_output_line **p;
142    for (p = &pending_lines; *p; p = &(*p)->next)
143      ;
144    *p = new pending_output_line(nd, no_fill, vs, post_vs, width);
145  }
146}
147
148// a line from .tl goes at the head of the queue
149
150void environment::output_title(node *nd, int no_fill, vunits vs,
151			       vunits post_vs, hunits width)
152{
153  if (!trap_sprung_flag)
154    curdiv->output(nd, no_fill, vs, post_vs, width);
155  else
156    pending_lines = new pending_output_line(nd, no_fill, vs, post_vs, width,
157					    pending_lines);
158}
159
160void environment::output_pending_lines()
161{
162  while (pending_lines && pending_lines->output()) {
163    pending_output_line *tem = pending_lines;
164    pending_lines = pending_lines->next;
165    delete tem;
166  }
167}
168
169#ifdef WIDOW_CONTROL
170
171void environment::mark_last_line()
172{
173  if (!widow_control || !pending_lines)
174    return;
175  for (pending_output_line *p = pending_lines; p->next; p = p->next)
176    ;
177  if (!p->no_fill)
178    p->last_line = 1;
179}
180
181void widow_control_request()
182{
183  int n;
184  if (has_arg() && get_integer(&n))
185    curenv->widow_control = n != 0;
186  else
187    curenv->widow_control = 1;
188  skip_line();
189}
190
191#endif /* WIDOW_CONTROL */
192
193/* font_size functions */
194
195size_range *font_size::size_table = 0;
196int font_size::nranges = 0;
197
198extern "C" {
199
200int compare_ranges(const void *p1, const void *p2)
201{
202  return ((size_range *)p1)->min - ((size_range *)p2)->min;
203}
204
205}
206
207void font_size::init_size_table(int *sizes)
208{
209  nranges = 0;
210  while (sizes[nranges*2] != 0)
211    nranges++;
212  assert(nranges > 0);
213  size_table = new size_range[nranges];
214  for (int i = 0; i < nranges; i++) {
215    size_table[i].min = sizes[i*2];
216    size_table[i].max = sizes[i*2 + 1];
217  }
218  qsort(size_table, nranges, sizeof(size_range), compare_ranges);
219}
220
221font_size::font_size(int sp)
222{
223  for (int i = 0; i < nranges; i++) {
224    if (sp < size_table[i].min) {
225      if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
226	p = size_table[i - 1].max;
227      else
228	p = size_table[i].min;
229      return;
230    }
231    if (sp <= size_table[i].max) {
232      p = sp;
233      return;
234    }
235  }
236  p = size_table[nranges - 1].max;
237}
238
239int font_size::to_units()
240{
241  return scale(p, units_per_inch, sizescale*72);
242}
243
244// we can't do this in a static constructor because various dictionaries
245// have to get initialized first
246
247void init_environments()
248{
249  curenv = env_table[0] = new environment("0");
250}
251
252void tab_character()
253{
254  curenv->tab_char = get_optional_char();
255  skip_line();
256}
257
258void leader_character()
259{
260  curenv->leader_char = get_optional_char();
261  skip_line();
262}
263
264void environment::add_char(charinfo *ci)
265{
266  int s;
267  if (interrupted)
268    ;
269  // don't allow fields in dummy environments
270  else if (ci == field_delimiter_char && !dummy) {
271    if (current_field)
272      wrap_up_field();
273    else
274      start_field();
275  }
276  else if (current_field && ci == padding_indicator_char)
277    add_padding();
278  else if (current_tab) {
279    if (tab_contents == 0)
280      tab_contents = new line_start_node;
281    if (ci != hyphen_indicator_char)
282      tab_contents = tab_contents->add_char(ci, this, &tab_width, &s);
283    else
284      tab_contents = tab_contents->add_discretionary_hyphen();
285  }
286  else {
287    if (line == 0)
288      start_line();
289    if (ci != hyphen_indicator_char)
290      line = line->add_char(ci, this, &width_total, &space_total);
291    else
292      line = line->add_discretionary_hyphen();
293  }
294}
295
296node *environment::make_char_node(charinfo *ci)
297{
298  return make_node(ci, this);
299}
300
301void environment::add_node(node *n)
302{
303  if (n == 0)
304    return;
305  if (current_tab || current_field)
306    n->freeze_space();
307  if (interrupted) {
308    delete n;
309  }
310  else if (current_tab) {
311    n->next = tab_contents;
312    tab_contents = n;
313    tab_width += n->width();
314  }
315  else {
316    if (line == 0) {
317      if (discarding && n->discardable()) {
318	// XXX possibly: input_line_start -= n->width();
319	delete n;
320	return;
321      }
322      start_line();
323    }
324    width_total += n->width();
325    space_total += n->nspaces();
326    n->next = line;
327    line = n;
328  }
329}
330
331
332void environment::add_hyphen_indicator()
333{
334  if (current_tab || interrupted || current_field
335      || hyphen_indicator_char != 0)
336    return;
337  if (line == 0)
338    start_line();
339  line = line->add_discretionary_hyphen();
340}
341
342int environment::get_hyphenation_flags()
343{
344  return hyphenation_flags;
345}
346
347int environment::get_hyphen_line_max()
348{
349  return hyphen_line_max;
350}
351
352int environment::get_hyphen_line_count()
353{
354  return hyphen_line_count;
355}
356
357int environment::get_center_lines()
358{
359  return center_lines;
360}
361
362int environment::get_right_justify_lines()
363{
364  return right_justify_lines;
365}
366
367void environment::add_italic_correction()
368{
369  if (current_tab) {
370    if (tab_contents)
371      tab_contents = tab_contents->add_italic_correction(&tab_width);
372  }
373  else if (line)
374    line = line->add_italic_correction(&width_total);
375}
376
377void environment::space_newline()
378{
379  assert(!current_tab && !current_field);
380  if (interrupted)
381    return;
382  hunits x = H0;
383  hunits sw = env_space_width(this);
384  hunits ssw = env_sentence_space_width(this);
385  if (!translate_space_to_dummy) {
386    x = sw;
387    if (node_list_ends_sentence(line) == 1)
388      x += ssw;
389  }
390  width_list *w = new width_list(sw, ssw);
391  if (node_list_ends_sentence(line) == 1)
392    w->next = new width_list(sw, ssw);
393  if (line != 0 && line->merge_space(x, sw, ssw)) {
394    width_total += x;
395    return;
396  }
397  add_node(new word_space_node(x, get_fill_color(), w));
398  possibly_break_line(0, spread_flag);
399  spread_flag = 0;
400}
401
402void environment::space()
403{
404  space(env_space_width(this), env_sentence_space_width(this));
405}
406
407void environment::space(hunits space_width, hunits sentence_space_width)
408{
409  if (interrupted)
410    return;
411  if (current_field && padding_indicator_char == 0) {
412    add_padding();
413    return;
414  }
415  hunits x = translate_space_to_dummy ? H0 : space_width;
416  node *p = current_tab ? tab_contents : line;
417  hunits *tp = current_tab ? &tab_width : &width_total;
418  if (p && p->nspaces() == 1 && p->width() == x
419      && node_list_ends_sentence(p->next) == 1) {
420    hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
421    if (p->merge_space(xx, space_width, sentence_space_width)) {
422      *tp += xx;
423      return;
424    }
425  }
426  if (p && p->merge_space(x, space_width, sentence_space_width)) {
427    *tp += x;
428    return;
429  }
430  add_node(new word_space_node(x,
431			       get_fill_color(),
432			       new width_list(space_width,
433					      sentence_space_width)));
434  possibly_break_line(0, spread_flag);
435  spread_flag = 0;
436}
437
438node *do_underline_special(int);
439
440void environment::set_font(symbol nm)
441{
442  if (interrupted)
443    return;
444  if (nm == symbol("P") || nm.is_empty()) {
445    if (family->make_definite(prev_fontno) < 0)
446      return;
447    int tem = fontno;
448    fontno = prev_fontno;
449    prev_fontno = tem;
450  }
451  else {
452    prev_fontno = fontno;
453    int n = symbol_fontno(nm);
454    if (n < 0) {
455      n = next_available_font_position();
456      if (!mount_font(n, nm))
457	return;
458    }
459    if (family->make_definite(n) < 0)
460      return;
461    fontno = n;
462  }
463  if (underline_spaces && fontno != prev_fontno) {
464    if (fontno == get_underline_fontno())
465      add_node(do_underline_special(1));
466    if (prev_fontno == get_underline_fontno())
467      add_node(do_underline_special(0));
468  }
469}
470
471void environment::set_font(int n)
472{
473  if (interrupted)
474    return;
475  if (is_good_fontno(n)) {
476    prev_fontno = fontno;
477    fontno = n;
478  }
479  else
480    warning(WARN_FONT, "bad font number");
481}
482
483void environment::set_family(symbol fam)
484{
485  if (interrupted)
486    return;
487  if (fam.is_null() || fam.is_empty()) {
488    if (prev_family->make_definite(fontno) < 0)
489      return;
490    font_family *tem = family;
491    family = prev_family;
492    prev_family = tem;
493  }
494  else {
495    font_family *f = lookup_family(fam);
496    if (f->make_definite(fontno) < 0)
497      return;
498    prev_family = family;
499    family = f;
500  }
501}
502
503void environment::set_size(int n)
504{
505  if (interrupted)
506    return;
507  if (n == 0) {
508    font_size temp = prev_size;
509    prev_size = size;
510    size = temp;
511    int temp2 = prev_requested_size;
512    prev_requested_size = requested_size;
513    requested_size = temp2;
514  }
515  else {
516    prev_size = size;
517    size = font_size(n);
518    prev_requested_size = requested_size;
519    requested_size = n;
520  }
521}
522
523void environment::set_char_height(int n)
524{
525  if (interrupted)
526    return;
527  if (n == requested_size || n <= 0)
528    char_height = 0;
529  else
530    char_height = n;
531}
532
533void environment::set_char_slant(int n)
534{
535  if (interrupted)
536    return;
537  char_slant = n;
538}
539
540color *environment::get_prev_glyph_color()
541{
542  return prev_glyph_color;
543}
544
545color *environment::get_glyph_color()
546{
547  return glyph_color;
548}
549
550color *environment::get_prev_fill_color()
551{
552  return prev_fill_color;
553}
554
555color *environment::get_fill_color()
556{
557  return fill_color;
558}
559
560void environment::set_glyph_color(color *c)
561{
562  if (interrupted)
563    return;
564  curenv->prev_glyph_color = curenv->glyph_color;
565  curenv->glyph_color = c;
566}
567
568void environment::set_fill_color(color *c)
569{
570  if (interrupted)
571    return;
572  curenv->prev_fill_color = curenv->fill_color;
573  curenv->fill_color = c;
574}
575
576environment::environment(symbol nm)
577: dummy(0),
578  prev_line_length((units_per_inch*13)/2),
579  line_length((units_per_inch*13)/2),
580  prev_title_length((units_per_inch*13)/2),
581  title_length((units_per_inch*13)/2),
582  prev_size(sizescale*10),
583  size(sizescale*10),
584  requested_size(sizescale*10),
585  prev_requested_size(sizescale*10),
586  char_height(0),
587  char_slant(0),
588  space_size(12),
589  sentence_space_size(12),
590  adjust_mode(ADJUST_BOTH),
591  fill(1),
592  interrupted(0),
593  prev_line_interrupted(0),
594  center_lines(0),
595  right_justify_lines(0),
596  prev_vertical_spacing(points_to_units(12)),
597  vertical_spacing(points_to_units(12)),
598  prev_post_vertical_spacing(0),
599  post_vertical_spacing(0),
600  prev_line_spacing(1),
601  line_spacing(1),
602  prev_indent(0),
603  indent(0),
604  temporary_indent(0),
605  have_temporary_indent(0),
606  underline_lines(0),
607  underline_spaces(0),
608  input_trap_count(0),
609  continued_input_trap(0),
610  line(0),
611  prev_text_length(0),
612  width_total(0),
613  space_total(0),
614  input_line_start(0),
615  tabs(units_per_inch/2, TAB_LEFT),
616  line_tabs(0),
617  current_tab(TAB_NONE),
618  leader_node(0),
619  tab_char(0),
620  leader_char(charset_table['.']),
621  current_field(0),
622  discarding(0),
623  spread_flag(0),
624  margin_character_flags(0),
625  margin_character_node(0),
626  margin_character_distance(points_to_units(10)),
627  numbering_nodes(0),
628  number_text_separation(1),
629  line_number_indent(0),
630  line_number_multiple(1),
631  no_number_count(0),
632  hyphenation_flags(1),
633  hyphen_line_count(0),
634  hyphen_line_max(-1),
635  hyphenation_space(H0),
636  hyphenation_margin(H0),
637  composite(0),
638  pending_lines(0),
639#ifdef WIDOW_CONTROL
640  widow_control(0),
641#endif /* WIDOW_CONTROL */
642  ignore_next_eol(0),
643  emitted_node(0),
644  glyph_color(&default_color),
645  prev_glyph_color(&default_color),
646  fill_color(&default_color),
647  prev_fill_color(&default_color),
648  name(nm),
649  control_char('.'),
650  no_break_control_char('\''),
651  hyphen_indicator_char(0)
652{
653  prev_family = family = lookup_family(default_family);
654  prev_fontno = fontno = 1;
655  if (!is_good_fontno(1))
656    fatal("font number 1 not a valid font");
657  if (family->make_definite(1) < 0)
658    fatal("invalid default family `%1'", default_family.contents());
659  prev_fontno = fontno;
660}
661
662environment::environment(const environment *e)
663: dummy(1),
664  prev_line_length(e->prev_line_length),
665  line_length(e->line_length),
666  prev_title_length(e->prev_title_length),
667  title_length(e->title_length),
668  prev_size(e->prev_size),
669  size(e->size),
670  requested_size(e->requested_size),
671  prev_requested_size(e->prev_requested_size),
672  char_height(e->char_height),
673  char_slant(e->char_slant),
674  prev_fontno(e->prev_fontno),
675  fontno(e->fontno),
676  prev_family(e->prev_family),
677  family(e->family),
678  space_size(e->space_size),
679  sentence_space_size(e->sentence_space_size),
680  adjust_mode(e->adjust_mode),
681  fill(e->fill),
682  interrupted(0),
683  prev_line_interrupted(0),
684  center_lines(0),
685  right_justify_lines(0),
686  prev_vertical_spacing(e->prev_vertical_spacing),
687  vertical_spacing(e->vertical_spacing),
688  prev_post_vertical_spacing(e->prev_post_vertical_spacing),
689  post_vertical_spacing(e->post_vertical_spacing),
690  prev_line_spacing(e->prev_line_spacing),
691  line_spacing(e->line_spacing),
692  prev_indent(e->prev_indent),
693  indent(e->indent),
694  temporary_indent(0),
695  have_temporary_indent(0),
696  underline_lines(0),
697  underline_spaces(0),
698  input_trap_count(0),
699  continued_input_trap(0),
700  line(0),
701  prev_text_length(e->prev_text_length),
702  width_total(0),
703  space_total(0),
704  input_line_start(0),
705  tabs(e->tabs),
706  line_tabs(e->line_tabs),
707  current_tab(TAB_NONE),
708  leader_node(0),
709  tab_char(e->tab_char),
710  leader_char(e->leader_char),
711  current_field(0),
712  discarding(0),
713  spread_flag(0),
714  margin_character_flags(e->margin_character_flags),
715  margin_character_node(e->margin_character_node),
716  margin_character_distance(e->margin_character_distance),
717  numbering_nodes(0),
718  number_text_separation(e->number_text_separation),
719  line_number_indent(e->line_number_indent),
720  line_number_multiple(e->line_number_multiple),
721  no_number_count(e->no_number_count),
722  hyphenation_flags(e->hyphenation_flags),
723  hyphen_line_count(0),
724  hyphen_line_max(e->hyphen_line_max),
725  hyphenation_space(e->hyphenation_space),
726  hyphenation_margin(e->hyphenation_margin),
727  composite(0),
728  pending_lines(0),
729#ifdef WIDOW_CONTROL
730  widow_control(e->widow_control),
731#endif /* WIDOW_CONTROL */
732  ignore_next_eol(0),
733  emitted_node(0),
734  glyph_color(e->glyph_color),
735  prev_glyph_color(e->prev_glyph_color),
736  fill_color(e->fill_color),
737  prev_fill_color(e->prev_fill_color),
738  name(e->name),		// so that eg `.if "\n[.ev]"0"' works
739  control_char(e->control_char),
740  no_break_control_char(e->no_break_control_char),
741  hyphen_indicator_char(e->hyphen_indicator_char)
742{
743}
744
745void environment::copy(const environment *e)
746{
747  prev_line_length = e->prev_line_length;
748  line_length = e->line_length;
749  prev_title_length = e->prev_title_length;
750  title_length = e->title_length;
751  prev_size = e->prev_size;
752  size = e->size;
753  prev_requested_size = e->prev_requested_size;
754  requested_size = e->requested_size;
755  char_height = e->char_height;
756  char_slant = e->char_slant;
757  space_size = e->space_size;
758  sentence_space_size = e->sentence_space_size;
759  adjust_mode = e->adjust_mode;
760  fill = e->fill;
761  interrupted = 0;
762  prev_line_interrupted = 0;
763  center_lines = 0;
764  right_justify_lines = 0;
765  prev_vertical_spacing = e->prev_vertical_spacing;
766  vertical_spacing = e->vertical_spacing;
767  prev_post_vertical_spacing = e->prev_post_vertical_spacing,
768  post_vertical_spacing = e->post_vertical_spacing,
769  prev_line_spacing = e->prev_line_spacing;
770  line_spacing = e->line_spacing;
771  prev_indent = e->prev_indent;
772  indent = e->indent;
773  have_temporary_indent = 0;
774  temporary_indent = 0;
775  underline_lines = 0;
776  underline_spaces = 0;
777  input_trap_count = 0;
778  continued_input_trap = 0;
779  prev_text_length = e->prev_text_length;
780  width_total = 0;
781  space_total = 0;
782  input_line_start = 0;
783  control_char = e->control_char;
784  no_break_control_char = e->no_break_control_char;
785  hyphen_indicator_char = e->hyphen_indicator_char;
786  spread_flag = 0;
787  line = 0;
788  pending_lines = 0;
789  discarding = 0;
790  tabs = e->tabs;
791  line_tabs = e->line_tabs;
792  current_tab = TAB_NONE;
793  current_field = 0;
794  margin_character_flags = e->margin_character_flags;
795  margin_character_node = e->margin_character_node;
796  margin_character_distance = e->margin_character_distance;
797  numbering_nodes = 0;
798  number_text_separation = e->number_text_separation;
799  line_number_multiple = e->line_number_multiple;
800  line_number_indent = e->line_number_indent;
801  no_number_count = e->no_number_count;
802  tab_char = e->tab_char;
803  leader_char = e->leader_char;
804  hyphenation_flags = e->hyphenation_flags;
805  fontno = e->fontno;
806  prev_fontno = e->prev_fontno;
807  dummy = e->dummy;
808  family = e->family;
809  prev_family = e->prev_family;
810  leader_node = 0;
811#ifdef WIDOW_CONTROL
812  widow_control = e->widow_control;
813#endif /* WIDOW_CONTROL */
814  hyphen_line_max = e->hyphen_line_max;
815  hyphen_line_count = 0;
816  hyphenation_space = e->hyphenation_space;
817  hyphenation_margin = e->hyphenation_margin;
818  composite = 0;
819  ignore_next_eol = e->ignore_next_eol;
820  emitted_node = e->emitted_node;
821  glyph_color= e->glyph_color;
822  prev_glyph_color = e->prev_glyph_color;
823  fill_color = e->fill_color;
824  prev_fill_color = e->prev_fill_color;
825}
826
827environment::~environment()
828{
829  delete leader_node;
830  delete_node_list(line);
831  delete_node_list(numbering_nodes);
832}
833
834hunits environment::get_input_line_position()
835{
836  hunits n;
837  if (line == 0)
838    n = -input_line_start;
839  else
840    n = width_total - input_line_start;
841  if (current_tab)
842    n += tab_width;
843  return n;
844}
845
846void environment::set_input_line_position(hunits n)
847{
848  input_line_start = line == 0 ? -n : width_total - n;
849  if (current_tab)
850    input_line_start += tab_width;
851}
852
853hunits environment::get_line_length()
854{
855  return line_length;
856}
857
858hunits environment::get_saved_line_length()
859{
860  if (line)
861    return target_text_length + saved_indent;
862  else
863    return line_length;
864}
865
866vunits environment::get_vertical_spacing()
867{
868  return vertical_spacing;
869}
870
871vunits environment::get_post_vertical_spacing()
872{
873  return post_vertical_spacing;
874}
875
876int environment::get_line_spacing()
877{
878  return line_spacing;
879}
880
881vunits environment::total_post_vertical_spacing()
882{
883  vunits tem(post_vertical_spacing);
884  if (line_spacing > 1)
885    tem += (line_spacing - 1)*vertical_spacing;
886  return tem;
887}
888
889int environment::get_bold()
890{
891  return get_bold_fontno(fontno);
892}
893
894hunits environment::get_digit_width()
895{
896  return env_digit_width(this);
897}
898
899int environment::get_adjust_mode()
900{
901  return adjust_mode;
902}
903
904int environment::get_fill()
905{
906  return fill;
907}
908
909hunits environment::get_indent()
910{
911  return indent;
912}
913
914hunits environment::get_saved_indent()
915{
916  if (line)
917    return saved_indent;
918  else if (have_temporary_indent)
919    return temporary_indent;
920  else
921    return indent;
922}
923
924hunits environment::get_temporary_indent()
925{
926  return temporary_indent;
927}
928
929hunits environment::get_title_length()
930{
931  return title_length;
932}
933
934node *environment::get_prev_char()
935{
936  for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
937    node *last = n->last_char_node();
938    if (last)
939      return last;
940  }
941  return 0;
942}
943
944hunits environment::get_prev_char_width()
945{
946  node *last = get_prev_char();
947  if (!last)
948    return H0;
949  return last->width();
950}
951
952hunits environment::get_prev_char_skew()
953{
954  node *last = get_prev_char();
955  if (!last)
956    return H0;
957  return last->skew();
958}
959
960vunits environment::get_prev_char_height()
961{
962  node *last = get_prev_char();
963  if (!last)
964    return V0;
965  vunits min, max;
966  last->vertical_extent(&min, &max);
967  return -min;
968}
969
970vunits environment::get_prev_char_depth()
971{
972  node *last = get_prev_char();
973  if (!last)
974    return V0;
975  vunits min, max;
976  last->vertical_extent(&min, &max);
977  return max;
978}
979
980hunits environment::get_text_length()
981{
982  hunits n = line == 0 ? H0 : width_total;
983  if (current_tab)
984    n += tab_width;
985  return n;
986}
987
988hunits environment::get_prev_text_length()
989{
990  return prev_text_length;
991}
992
993
994static int sb_reg_contents = 0;
995static int st_reg_contents = 0;
996static int ct_reg_contents = 0;
997static int rsb_reg_contents = 0;
998static int rst_reg_contents = 0;
999static int skw_reg_contents = 0;
1000static int ssc_reg_contents = 0;
1001
1002void environment::width_registers()
1003{
1004  // this is used to implement \w; it sets the st, sb, ct registers
1005  vunits min = 0, max = 0, cur = 0;
1006  int character_type = 0;
1007  ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
1008  skw_reg_contents = line ? line->skew().to_units() : 0;
1009  line = reverse_node_list(line);
1010  vunits real_min = V0;
1011  vunits real_max = V0;
1012  vunits v1, v2;
1013  for (node *tem = line; tem; tem = tem->next) {
1014    tem->vertical_extent(&v1, &v2);
1015    v1 += cur;
1016    if (v1 < real_min)
1017      real_min = v1;
1018    v2 += cur;
1019    if (v2 > real_max)
1020      real_max = v2;
1021    if ((cur += tem->vertical_width()) < min)
1022      min = cur;
1023    else if (cur > max)
1024      max = cur;
1025    character_type |= tem->character_type();
1026  }
1027  line = reverse_node_list(line);
1028  st_reg_contents = -min.to_units();
1029  sb_reg_contents = -max.to_units();
1030  rst_reg_contents = -real_min.to_units();
1031  rsb_reg_contents = -real_max.to_units();
1032  ct_reg_contents = character_type;
1033}
1034
1035node *environment::extract_output_line()
1036{
1037  if (current_tab)
1038    wrap_up_tab();
1039  node *n = line;
1040  line = 0;
1041  return n;
1042}
1043
1044/* environment related requests */
1045
1046void environment_switch()
1047{
1048  int pop = 0;	// 1 means pop, 2 means pop but no error message on underflow
1049  if (curenv->is_dummy())
1050    error("can't switch environments when current environment is dummy");
1051  else if (!has_arg())
1052    pop = 1;
1053  else {
1054    symbol nm;
1055    if (!tok.delimiter()) {
1056      // It looks like a number.
1057      int n;
1058      if (get_integer(&n)) {
1059	if (n >= 0 && n < NENVIRONMENTS) {
1060	  env_stack = new env_list(curenv, env_stack);
1061	  if (env_table[n] == 0)
1062	    env_table[n] = new environment(i_to_a(n));
1063	  curenv = env_table[n];
1064	}
1065	else
1066	  nm = i_to_a(n);
1067      }
1068      else
1069	pop = 2;
1070    }
1071    else {
1072      nm = get_long_name(1);
1073      if (nm.is_null())
1074	pop = 2;
1075    }
1076    if (!nm.is_null()) {
1077      environment *e = (environment *)env_dictionary.lookup(nm);
1078      if (!e) {
1079	e = new environment(nm);
1080	(void)env_dictionary.lookup(nm, e);
1081      }
1082      env_stack = new env_list(curenv, env_stack);
1083      curenv = e;
1084    }
1085  }
1086  if (pop) {
1087    if (env_stack == 0) {
1088      if (pop == 1)
1089	error("environment stack underflow");
1090    }
1091    else {
1092      curenv = env_stack->env;
1093      env_list *tem = env_stack;
1094      env_stack = env_stack->next;
1095      delete tem;
1096    }
1097  }
1098  skip_line();
1099}
1100
1101void environment_copy()
1102{
1103  symbol nm;
1104  environment *e=0;
1105  tok.skip();
1106  if (!tok.delimiter()) {
1107    // It looks like a number.
1108    int n;
1109    if (get_integer(&n)) {
1110      if (n >= 0 && n < NENVIRONMENTS)
1111	e = env_table[n];
1112      else
1113	nm = i_to_a(n);
1114    }
1115  }
1116  else
1117    nm = get_long_name(1);
1118  if (!e && !nm.is_null())
1119    e = (environment *)env_dictionary.lookup(nm);
1120  if (e == 0) {
1121    error("No environment to copy from");
1122    return;
1123  }
1124  else
1125    curenv->copy(e);
1126  skip_line();
1127}
1128
1129static symbol P_symbol("P");
1130
1131void font_change()
1132{
1133  symbol s = get_name();
1134  int is_number = 1;
1135  if (s.is_null() || s == P_symbol) {
1136    s = P_symbol;
1137    is_number = 0;
1138  }
1139  else {
1140    for (const char *p = s.contents(); p != 0 && *p != 0; p++)
1141      if (!csdigit(*p)) {
1142	is_number = 0;
1143	break;
1144      }
1145  }
1146  if (is_number)
1147    curenv->set_font(atoi(s.contents()));
1148  else
1149    curenv->set_font(s);
1150  skip_line();
1151}
1152
1153void family_change()
1154{
1155  symbol s = get_name();
1156  curenv->set_family(s);
1157  skip_line();
1158}
1159
1160void point_size()
1161{
1162  int n;
1163  if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) {
1164    if (n <= 0)
1165      n = 1;
1166    curenv->set_size(n);
1167    curenv->add_html_tag(0, ".ps", n);
1168  }
1169  else
1170    curenv->set_size(0);
1171  skip_line();
1172}
1173
1174void override_sizes()
1175{
1176  int n = 16;
1177  int *sizes = new int[n];
1178  int i = 0;
1179  char *buf = read_string();
1180  if (!buf)
1181    return;
1182  char *p = strtok(buf, " \t");
1183  for (;;) {
1184    if (!p)
1185      break;
1186    int lower, upper;
1187    switch (sscanf(p, "%d-%d", &lower, &upper)) {
1188    case 1:
1189      upper = lower;
1190      // fall through
1191    case 2:
1192      if (lower <= upper && lower >= 0)
1193	break;
1194      // fall through
1195    default:
1196      warning(WARN_RANGE, "bad size range `%1'", p);
1197      return;
1198    }
1199    if (i + 2 > n) {
1200      int *old_sizes = sizes;
1201      sizes = new int[n*2];
1202      memcpy(sizes, old_sizes, n*sizeof(int));
1203      n *= 2;
1204      a_delete old_sizes;
1205    }
1206    sizes[i++] = lower;
1207    if (lower == 0)
1208      break;
1209    sizes[i++] = upper;
1210    p = strtok(0, " \t");
1211  }
1212  font_size::init_size_table(sizes);
1213}
1214
1215void space_size()
1216{
1217  int n;
1218  if (get_integer(&n)) {
1219    curenv->space_size = n;
1220    if (has_arg() && get_integer(&n))
1221      curenv->sentence_space_size = n;
1222    else
1223      curenv->sentence_space_size = curenv->space_size;
1224  }
1225  skip_line();
1226}
1227
1228void fill()
1229{
1230  while (!tok.newline() && !tok.eof())
1231    tok.next();
1232  if (break_flag)
1233    curenv->do_break();
1234  curenv->fill = 1;
1235  curenv->add_html_tag(1, ".fi");
1236  curenv->add_html_tag(0, ".br");
1237  tok.next();
1238}
1239
1240void no_fill()
1241{
1242  while (!tok.newline() && !tok.eof())
1243    tok.next();
1244  if (break_flag)
1245    curenv->do_break();
1246  curenv->fill = 0;
1247  curenv->add_html_tag(1, ".nf");
1248  curenv->add_html_tag(0, ".br");
1249  curenv->add_html_tag(0, ".po", topdiv->get_page_offset().to_units());
1250  tok.next();
1251}
1252
1253void center()
1254{
1255  int n;
1256  if (!has_arg() || !get_integer(&n))
1257    n = 1;
1258  else if (n < 0)
1259    n = 0;
1260  while (!tok.newline() && !tok.eof())
1261    tok.next();
1262  if (break_flag)
1263    curenv->do_break();
1264  curenv->right_justify_lines = 0;
1265  curenv->center_lines = n;
1266  curenv->add_html_tag(1, ".ce", n);
1267  tok.next();
1268}
1269
1270void right_justify()
1271{
1272  int n;
1273  if (!has_arg() || !get_integer(&n))
1274    n = 1;
1275  else if (n < 0)
1276    n = 0;
1277  while (!tok.newline() && !tok.eof())
1278    tok.next();
1279  if (break_flag)
1280    curenv->do_break();
1281  curenv->center_lines = 0;
1282  curenv->right_justify_lines = n;
1283  curenv->add_html_tag(1, ".rj", n);
1284  tok.next();
1285}
1286
1287void line_length()
1288{
1289  hunits temp;
1290  if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) {
1291    if (temp < H0) {
1292      warning(WARN_RANGE, "bad line length %1u", temp.to_units());
1293      temp = H0;
1294    }
1295  }
1296  else
1297    temp = curenv->prev_line_length;
1298  curenv->prev_line_length = curenv->line_length;
1299  curenv->line_length = temp;
1300  curenv->add_html_tag(1, ".ll", temp.to_units());
1301  skip_line();
1302}
1303
1304void title_length()
1305{
1306  hunits temp;
1307  if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) {
1308    if (temp < H0) {
1309      warning(WARN_RANGE, "bad title length %1u", temp.to_units());
1310      temp = H0;
1311    }
1312  }
1313  else
1314    temp = curenv->prev_title_length;
1315  curenv->prev_title_length = curenv->title_length;
1316  curenv->title_length = temp;
1317  skip_line();
1318}
1319
1320void vertical_spacing()
1321{
1322  vunits temp;
1323  if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) {
1324    if (temp < V0) {
1325      warning(WARN_RANGE, "vertical spacing must not be negative");
1326      temp = vresolution;
1327    }
1328  }
1329  else
1330    temp = curenv->prev_vertical_spacing;
1331  curenv->prev_vertical_spacing = curenv->vertical_spacing;
1332  curenv->vertical_spacing = temp;
1333  skip_line();
1334}
1335
1336void post_vertical_spacing()
1337{
1338  vunits temp;
1339  if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) {
1340    if (temp < V0) {
1341      warning(WARN_RANGE,
1342	      "post vertical spacing must be greater than or equal to 0");
1343      temp = V0;
1344    }
1345  }
1346  else
1347    temp = curenv->prev_post_vertical_spacing;
1348  curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
1349  curenv->post_vertical_spacing = temp;
1350  skip_line();
1351}
1352
1353void line_spacing()
1354{
1355  int temp;
1356  if (has_arg() && get_integer(&temp)) {
1357    if (temp < 1) {
1358      warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
1359      temp = 1;
1360    }
1361  }
1362  else
1363    temp = curenv->prev_line_spacing;
1364  curenv->prev_line_spacing = curenv->line_spacing;
1365  curenv->line_spacing = temp;
1366  skip_line();
1367}
1368
1369void indent()
1370{
1371  hunits temp;
1372  if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) {
1373    if (temp < H0) {
1374      warning(WARN_RANGE, "indent cannot be negative");
1375      temp = H0;
1376    }
1377  }
1378  else
1379    temp = curenv->prev_indent;
1380  while (!tok.newline() && !tok.eof())
1381    tok.next();
1382  if (break_flag)
1383    curenv->do_break();
1384  curenv->have_temporary_indent = 0;
1385  curenv->prev_indent = curenv->indent;
1386  curenv->indent = temp;
1387  if (break_flag)
1388    curenv->add_html_tag(1, ".in", temp.to_units());
1389  tok.next();
1390}
1391
1392void temporary_indent()
1393{
1394  int err = 0;
1395  hunits temp;
1396  if (!get_hunits(&temp, 'm', curenv->get_indent()))
1397    err = 1;
1398  while (!tok.newline() && !tok.eof())
1399    tok.next();
1400  if (break_flag)
1401    curenv->do_break();
1402  if (temp < H0) {
1403    warning(WARN_RANGE, "total indent cannot be negative");
1404    temp = H0;
1405  }
1406  if (!err) {
1407    curenv->temporary_indent = temp;
1408    curenv->have_temporary_indent = 1;
1409    curenv->add_html_tag(1, ".ti", temp.to_units());
1410  }
1411  tok.next();
1412}
1413
1414node *do_underline_special(int underline_spaces)
1415{
1416  macro m;
1417  m.append_str("x u ");
1418  m.append(underline_spaces + '0');
1419  return new special_node(m, 1);
1420}
1421
1422void do_underline(int underline_spaces)
1423{
1424  int n;
1425  if (!has_arg() || !get_integer(&n))
1426    n = 1;
1427  if (n <= 0) {
1428    if (curenv->underline_lines > 0) {
1429      curenv->prev_fontno = curenv->fontno;
1430      curenv->fontno = curenv->pre_underline_fontno;
1431      if (underline_spaces) {
1432	curenv->underline_spaces = 0;
1433	curenv->add_node(do_underline_special(0));
1434      }
1435    }
1436    curenv->underline_lines = 0;
1437  }
1438  else {
1439    curenv->underline_lines = n;
1440    curenv->pre_underline_fontno = curenv->fontno;
1441    curenv->fontno = get_underline_fontno();
1442    if (underline_spaces) {
1443      curenv->underline_spaces = 1;
1444      curenv->add_node(do_underline_special(1));
1445    }
1446  }
1447  skip_line();
1448}
1449
1450void continuous_underline()
1451{
1452  do_underline(1);
1453}
1454
1455void underline()
1456{
1457  do_underline(0);
1458}
1459
1460void control_char()
1461{
1462  curenv->control_char = '.';
1463  if (has_arg()) {
1464    if (tok.ch() == 0)
1465      error("bad control character");
1466    else
1467      curenv->control_char = tok.ch();
1468  }
1469  skip_line();
1470}
1471
1472void no_break_control_char()
1473{
1474  curenv->no_break_control_char = '\'';
1475  if (has_arg()) {
1476    if (tok.ch() == 0)
1477      error("bad control character");
1478    else
1479      curenv->no_break_control_char = tok.ch();
1480  }
1481  skip_line();
1482}
1483
1484void margin_character()
1485{
1486  while (tok.space())
1487    tok.next();
1488  charinfo *ci = tok.get_char();
1489  if (ci) {
1490    // Call tok.next() only after making the node so that
1491    // .mc \s+9\(br\s0 works.
1492    node *nd = curenv->make_char_node(ci);
1493    tok.next();
1494    if (nd) {
1495      delete curenv->margin_character_node;
1496      curenv->margin_character_node = nd;
1497      curenv->margin_character_flags = (MARGIN_CHARACTER_ON
1498					|MARGIN_CHARACTER_NEXT);
1499      hunits d;
1500      if (has_arg() && get_hunits(&d, 'm'))
1501	curenv->margin_character_distance = d;
1502    }
1503  }
1504  else {
1505    check_missing_character();
1506    curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON;
1507    if (curenv->margin_character_flags == 0) {
1508      delete curenv->margin_character_node;
1509      curenv->margin_character_node = 0;
1510    }
1511  }
1512  skip_line();
1513}
1514
1515void number_lines()
1516{
1517  delete_node_list(curenv->numbering_nodes);
1518  curenv->numbering_nodes = 0;
1519  if (has_arg()) {
1520    node *nd = 0;
1521    for (int i = '9'; i >= '0'; i--) {
1522      node *tem = make_node(charset_table[i], curenv);
1523      if (!tem) {
1524	skip_line();
1525	return;
1526      }
1527      tem->next = nd;
1528      nd = tem;
1529    }
1530    curenv->numbering_nodes = nd;
1531    curenv->line_number_digit_width = env_digit_width(curenv);
1532    int n;
1533    if (!tok.delimiter()) {
1534      if (get_integer(&n, next_line_number)) {
1535	next_line_number = n;
1536	if (next_line_number < 0) {
1537	  warning(WARN_RANGE, "negative line number");
1538	  next_line_number = 0;
1539	}
1540      }
1541    }
1542    else
1543      while (!tok.space() && !tok.newline() && !tok.eof())
1544	tok.next();
1545    if (has_arg()) {
1546      if (!tok.delimiter()) {
1547	if (get_integer(&n)) {
1548	  if (n <= 0) {
1549	    warning(WARN_RANGE, "negative or zero line number multiple");
1550	  }
1551	  else
1552	    curenv->line_number_multiple = n;
1553	}
1554      }
1555      else
1556	while (!tok.space() && !tok.newline() && !tok.eof())
1557	  tok.next();
1558      if (has_arg()) {
1559	if (!tok.delimiter()) {
1560	  if (get_integer(&n))
1561	    curenv->number_text_separation = n;
1562	}
1563	else
1564	  while (!tok.space() && !tok.newline() && !tok.eof())
1565	    tok.next();
1566	if (has_arg() && !tok.delimiter() && get_integer(&n))
1567	  curenv->line_number_indent = n;
1568      }
1569    }
1570  }
1571  skip_line();
1572}
1573
1574void no_number()
1575{
1576  int n;
1577  if (has_arg() && get_integer(&n))
1578    curenv->no_number_count = n > 0 ? n : 0;
1579  else
1580    curenv->no_number_count = 1;
1581  skip_line();
1582}
1583
1584void no_hyphenate()
1585{
1586  curenv->hyphenation_flags = 0;
1587  skip_line();
1588}
1589
1590void hyphenate_request()
1591{
1592  int n;
1593  if (has_arg() && get_integer(&n))
1594    curenv->hyphenation_flags = n;
1595  else
1596    curenv->hyphenation_flags = 1;
1597  skip_line();
1598}
1599
1600void hyphen_char()
1601{
1602  curenv->hyphen_indicator_char = get_optional_char();
1603  skip_line();
1604}
1605
1606void hyphen_line_max_request()
1607{
1608  int n;
1609  if (has_arg() && get_integer(&n))
1610    curenv->hyphen_line_max = n;
1611  else
1612    curenv->hyphen_line_max = -1;
1613  skip_line();
1614}
1615
1616void environment::interrupt()
1617{
1618  if (!dummy) {
1619    add_node(new transparent_dummy_node);
1620    interrupted = 1;
1621  }
1622}
1623
1624void environment::newline()
1625{
1626  if (underline_lines > 0) {
1627    if (--underline_lines == 0) {
1628      prev_fontno = fontno;
1629      fontno = pre_underline_fontno;
1630      if (underline_spaces) {
1631        underline_spaces = 0;
1632        add_node(do_underline_special(0));
1633      }
1634    }
1635  }
1636  if (current_field)
1637    wrap_up_field();
1638  if (current_tab)
1639    wrap_up_tab();
1640  // strip trailing spaces
1641  while (line != 0 && line->discardable()) {
1642    width_total -= line->width();
1643    space_total -= line->nspaces();
1644    node *tem = line;
1645    line = line->next;
1646    delete tem;
1647  }
1648  node *to_be_output = 0;
1649  hunits to_be_output_width;
1650  prev_line_interrupted = 0;
1651  if (dummy)
1652    space_newline();
1653  else if (interrupted) {
1654    interrupted = 0;
1655    // see environment::final_break
1656    prev_line_interrupted = exit_started ? 2 : 1;
1657  }
1658  else if (center_lines > 0) {
1659    --center_lines;
1660    hunits x = target_text_length - width_total;
1661    if (x > H0)
1662      saved_indent += x/2;
1663    to_be_output = line;
1664    if (is_html) {
1665      node *n = make_html_tag("eol.ce");
1666      n->next = to_be_output;
1667      to_be_output = n;
1668    }
1669    to_be_output_width = width_total;
1670    line = 0;
1671  }
1672  else if (right_justify_lines > 0) {
1673    --right_justify_lines;
1674    hunits x = target_text_length - width_total;
1675    if (x > H0)
1676      saved_indent += x;
1677    to_be_output = line;
1678    to_be_output_width = width_total;
1679    line = 0;
1680  }
1681  else if (fill)
1682    space_newline();
1683  else {
1684    to_be_output = line;
1685    to_be_output_width = width_total;
1686    line = 0;
1687  }
1688  input_line_start = line == 0 ? H0 : width_total;
1689  if (to_be_output) {
1690    if (is_html && !fill) {
1691      if (curdiv == topdiv) {
1692	node *n = make_html_tag("eol");
1693
1694	n->next = to_be_output;
1695	to_be_output = n;
1696      }
1697    }
1698    output_line(to_be_output, to_be_output_width);
1699    hyphen_line_count = 0;
1700  }
1701  if (input_trap_count > 0) {
1702    if (!(continued_input_trap && prev_line_interrupted))
1703      if (--input_trap_count == 0)
1704	spring_trap(input_trap);
1705  }
1706}
1707
1708void environment::output_line(node *n, hunits width)
1709{
1710  prev_text_length = width;
1711  if (margin_character_flags) {
1712    hunits d = line_length + margin_character_distance - saved_indent - width;
1713    if (d > 0) {
1714      n = new hmotion_node(d, get_fill_color(), n);
1715      width += d;
1716    }
1717    margin_character_flags &= ~MARGIN_CHARACTER_NEXT;
1718    node *tem;
1719    if (!margin_character_flags) {
1720      tem = margin_character_node;
1721      margin_character_node = 0;
1722    }
1723    else
1724      tem = margin_character_node->copy();
1725    tem->next = n;
1726    n = tem;
1727    width += tem->width();
1728  }
1729  node *nn = 0;
1730  while (n != 0) {
1731    node *tem = n->next;
1732    n->next = nn;
1733    nn = n;
1734    n = tem;
1735  }
1736  if (!saved_indent.is_zero())
1737    nn = new hmotion_node(saved_indent, get_fill_color(), nn);
1738  width += saved_indent;
1739  if (no_number_count > 0)
1740    --no_number_count;
1741  else if (numbering_nodes) {
1742    hunits w = (line_number_digit_width
1743		*(3+line_number_indent+number_text_separation));
1744    if (next_line_number % line_number_multiple != 0)
1745      nn = new hmotion_node(w, get_fill_color(), nn);
1746    else {
1747      hunits x = w;
1748      nn = new hmotion_node(number_text_separation * line_number_digit_width,
1749			    get_fill_color(), nn);
1750      x -= number_text_separation*line_number_digit_width;
1751      char buf[30];
1752      sprintf(buf, "%3d", next_line_number);
1753      for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
1754	node *gn = numbering_nodes;
1755	for (int count = *p - '0'; count > 0; count--)
1756	  gn = gn->next;
1757	gn = gn->copy();
1758	x -= gn->width();
1759	gn->next = nn;
1760	nn = gn;
1761      }
1762      nn = new hmotion_node(x, get_fill_color(), nn);
1763    }
1764    width += w;
1765    ++next_line_number;
1766  }
1767  output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width);
1768}
1769
1770void environment::start_line()
1771{
1772  assert(line == 0);
1773  discarding = 0;
1774  line = new line_start_node;
1775  if (have_temporary_indent) {
1776    saved_indent = temporary_indent;
1777    have_temporary_indent = 0;
1778  }
1779  else
1780    saved_indent = indent;
1781  target_text_length = line_length - saved_indent;
1782  width_total = H0;
1783  space_total = 0;
1784}
1785
1786hunits environment::get_hyphenation_space()
1787{
1788  return hyphenation_space;
1789}
1790
1791void hyphenation_space_request()
1792{
1793  hunits n;
1794  if (get_hunits(&n, 'm')) {
1795    if (n < H0) {
1796      warning(WARN_RANGE, "hyphenation space cannot be negative");
1797      n = H0;
1798    }
1799    curenv->hyphenation_space = n;
1800  }
1801  skip_line();
1802}
1803
1804hunits environment::get_hyphenation_margin()
1805{
1806  return hyphenation_margin;
1807}
1808
1809void hyphenation_margin_request()
1810{
1811  hunits n;
1812  if (get_hunits(&n, 'm')) {
1813    if (n < H0) {
1814      warning(WARN_RANGE, "hyphenation margin cannot be negative");
1815      n = H0;
1816    }
1817    curenv->hyphenation_margin = n;
1818  }
1819  skip_line();
1820}
1821
1822breakpoint *environment::choose_breakpoint()
1823{
1824  hunits x = width_total;
1825  int s = space_total;
1826  node *n = line;
1827  breakpoint *best_bp = 0;	// the best breakpoint so far
1828  int best_bp_fits = 0;
1829  while (n != 0) {
1830    x -= n->width();
1831    s -= n->nspaces();
1832    breakpoint *bp = n->get_breakpoints(x, s);
1833    while (bp != 0) {
1834      if (bp->width <= target_text_length) {
1835	if (!bp->hyphenated) {
1836	  breakpoint *tem = bp->next;
1837	  bp->next = 0;
1838	  while (tem != 0) {
1839	    breakpoint *tem1 = tem;
1840	    tem = tem->next;
1841	    delete tem1;
1842	  }
1843	  if (best_bp_fits
1844	      // Decide whether to use the hyphenated breakpoint.
1845	      && (hyphen_line_max < 0
1846		  // Only choose the hyphenated breakpoint if it would not
1847		  // exceed the maximum number of consecutive hyphenated
1848		  // lines.
1849		  || hyphen_line_count + 1 <= hyphen_line_max)
1850	      && !(adjust_mode == ADJUST_BOTH
1851		   // Don't choose the hyphenated breakpoint if the line
1852		   // can be justified by adding no more than
1853		   // hyphenation_space to any word space.
1854		   ? (bp->nspaces > 0
1855		      && (((target_text_length - bp->width
1856			    + (bp->nspaces - 1)*hresolution)/bp->nspaces)
1857			  <= hyphenation_space))
1858		   // Don't choose the hyphenated breakpoint if the line
1859		   // is no more than hyphenation_margin short.
1860		   : target_text_length - bp->width <= hyphenation_margin)) {
1861	    delete bp;
1862	    return best_bp;
1863	  }
1864	  if (best_bp)
1865	    delete best_bp;
1866	  return bp;
1867	}
1868	else {
1869	  if ((adjust_mode == ADJUST_BOTH
1870	       ? hyphenation_space == H0
1871	       : hyphenation_margin == H0)
1872	      && (hyphen_line_max < 0
1873		  || hyphen_line_count + 1 <= hyphen_line_max)) {
1874	    // No need to consider a non-hyphenated breakpoint.
1875	    if (best_bp)
1876	      delete best_bp;
1877	    breakpoint *tem = bp->next;
1878	    bp->next = 0;
1879	    while (tem != 0) {
1880	      breakpoint *tem1 = tem;
1881	      tem = tem->next;
1882	      delete tem1;
1883	    }
1884	    return bp;
1885	  }
1886	  // It fits but it's hyphenated.
1887	  if (!best_bp_fits) {
1888	    if (best_bp)
1889	      delete best_bp;
1890	    best_bp = bp;
1891	    bp = bp->next;
1892	    best_bp_fits = 1;
1893	  }
1894	  else {
1895	    breakpoint *tem = bp;
1896	    bp = bp->next;
1897	    delete tem;
1898	  }
1899	}
1900      }
1901      else {
1902	if (best_bp)
1903	  delete best_bp;
1904	best_bp = bp;
1905	bp = bp->next;
1906      }
1907    }
1908    n = n->next;
1909  }
1910  if (best_bp) {
1911    if (!best_bp_fits)
1912      output_warning(WARN_BREAK, "can't break line");
1913    return best_bp;
1914  }
1915  return 0;
1916}
1917
1918void environment::hyphenate_line(int start_here)
1919{
1920  assert(line != 0);
1921  hyphenation_type prev_type = line->get_hyphenation_type();
1922  node **startp;
1923  if (start_here)
1924    startp = &line;
1925  else
1926    for (startp = &line->next; *startp != 0; startp = &(*startp)->next) {
1927      hyphenation_type this_type = (*startp)->get_hyphenation_type();
1928      if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
1929	break;
1930      prev_type = this_type;
1931    }
1932  if (*startp == 0)
1933    return;
1934  node *tem = *startp;
1935  do {
1936    tem = tem->next;
1937  } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
1938  int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
1939  node *end = tem;
1940  hyphen_list *sl = 0;
1941  tem = *startp;
1942  node *forward = 0;
1943  int i = 0;
1944  while (tem != end) {
1945    sl = tem->get_hyphen_list(sl, &i);
1946    node *tem1 = tem;
1947    tem = tem->next;
1948    tem1->next = forward;
1949    forward = tem1;
1950  }
1951  if (!inhibit) {
1952    // this is for characters like hyphen and emdash
1953    int prev_code = 0;
1954    for (hyphen_list *h = sl; h; h = h->next) {
1955      h->breakable = (prev_code != 0
1956		      && h->next != 0
1957		      && h->next->hyphenation_code != 0);
1958      prev_code = h->hyphenation_code;
1959    }
1960  }
1961  if (hyphenation_flags != 0
1962      && !inhibit
1963      // this may not be right if we have extra space on this line
1964      && !((hyphenation_flags & HYPHEN_LAST_LINE)
1965	   && (curdiv->distance_to_next_trap()
1966	       <= vertical_spacing + total_post_vertical_spacing()))
1967      && i >= 4)
1968    hyphenate(sl, hyphenation_flags);
1969  while (forward != 0) {
1970    node *tem1 = forward;
1971    forward = forward->next;
1972    tem1->next = 0;
1973    tem = tem1->add_self(tem, &sl);
1974  }
1975  *startp = tem;
1976}
1977
1978static node *node_list_reverse(node *n)
1979{
1980  node *res = 0;
1981  while (n) {
1982    node *tem = n;
1983    n = n->next;
1984    tem->next = res;
1985    res = tem;
1986  }
1987  return res;
1988}
1989
1990static void distribute_space(node *n, int nspaces, hunits desired_space,
1991			     int force_reverse = 0)
1992{
1993  static int reverse = 0;
1994  if (force_reverse || reverse)
1995    n = node_list_reverse(n);
1996  if (!force_reverse && nspaces > 0 && spread_limit >= 0
1997      && desired_space.to_units() > 0) {
1998    hunits em = curenv->get_size();
1999    double Ems = (double)desired_space.to_units() / nspaces
2000		 / (em.is_zero() ? hresolution : em.to_units());
2001    if (Ems > spread_limit)
2002      output_warning(WARN_BREAK, "spreading %1m per space", Ems);
2003  }
2004  for (node *tem = n; tem; tem = tem->next)
2005    tem->spread_space(&nspaces, &desired_space);
2006  if (force_reverse || reverse)
2007    (void)node_list_reverse(n);
2008  if (!force_reverse)
2009    reverse = !reverse;
2010  assert(desired_space.is_zero() && nspaces == 0);
2011}
2012
2013void environment::possibly_break_line(int start_here, int forced)
2014{
2015  if (!fill || current_tab || current_field || dummy)
2016    return;
2017  while (line != 0
2018	 && (forced
2019	     // When a macro follows a paragraph in fill mode, the
2020	     // current line should not be empty.
2021	     || (width_total - line->width()) > target_text_length)) {
2022    hyphenate_line(start_here);
2023    breakpoint *bp = choose_breakpoint();
2024    if (bp == 0)
2025      // we'll find one eventually
2026      return;
2027    node *pre, *post;
2028    node **ndp = &line;
2029    while (*ndp != bp->nd)
2030      ndp = &(*ndp)->next;
2031    bp->nd->split(bp->index, &pre, &post);
2032    *ndp = post;
2033    hunits extra_space_width = H0;
2034    switch(adjust_mode) {
2035    case ADJUST_BOTH:
2036      if (bp->nspaces != 0)
2037	extra_space_width = target_text_length - bp->width;
2038      else if (bp->width > 0 && target_text_length > 0
2039	       && target_text_length > bp->width)
2040	output_warning(WARN_BREAK, "cannot adjust line");
2041      break;
2042    case ADJUST_CENTER:
2043      saved_indent += (target_text_length - bp->width)/2;
2044      break;
2045    case ADJUST_RIGHT:
2046      saved_indent += target_text_length - bp->width;
2047      break;
2048    }
2049    distribute_space(pre, bp->nspaces, extra_space_width);
2050    hunits output_width = bp->width + extra_space_width;
2051    input_line_start -= output_width;
2052    if (bp->hyphenated)
2053      hyphen_line_count++;
2054    else
2055      hyphen_line_count = 0;
2056    delete bp;
2057    space_total = 0;
2058    width_total = 0;
2059    node *first_non_discardable = 0;
2060    node *tem;
2061    for (tem = line; tem != 0; tem = tem->next)
2062      if (!tem->discardable())
2063	first_non_discardable = tem;
2064    node *to_be_discarded;
2065    if (first_non_discardable) {
2066      to_be_discarded = first_non_discardable->next;
2067      first_non_discardable->next = 0;
2068      for (tem = line; tem != 0; tem = tem->next) {
2069	width_total += tem->width();
2070	space_total += tem->nspaces();
2071      }
2072      discarding = 0;
2073    }
2074    else {
2075      discarding = 1;
2076      to_be_discarded = line;
2077      line = 0;
2078    }
2079    // Do output_line() here so that line will be 0 iff the
2080    // the environment will be empty.
2081    output_line(pre, output_width);
2082    while (to_be_discarded != 0) {
2083      tem = to_be_discarded;
2084      to_be_discarded = to_be_discarded->next;
2085      input_line_start -= tem->width();
2086      delete tem;
2087    }
2088    if (line != 0) {
2089      if (have_temporary_indent) {
2090	saved_indent = temporary_indent;
2091	have_temporary_indent = 0;
2092      }
2093      else
2094	saved_indent = indent;
2095      target_text_length = line_length - saved_indent;
2096    }
2097  }
2098}
2099
2100/*
2101Do the break at the end of input after the end macro (if any).
2102
2103Unix troff behaves as follows:  if the last line is
2104
2105foo bar\c
2106
2107it will output foo on the current page, and bar on the next page;
2108if the last line is
2109
2110foo\c
2111
2112or
2113
2114foo bar
2115
2116everything will be output on the current page.  This behaviour must be
2117considered a bug.
2118
2119The problem is that some macro packages rely on this.  For example,
2120the ATK macros have an end macro that emits \c if it needs to print a
2121table of contents but doesn't do a 'bp in the end macro; instead the
2122'bp is done in the bottom of page trap.  This works with Unix troff,
2123provided that the current environment is not empty at the end of the
2124input file.
2125
2126The following will make macro packages that do that sort of thing work
2127even if the current environment is empty at the end of the input file.
2128If the last input line used \c and this line occurred in the end macro,
2129then we'll force everything out on the current page, but we'll make
2130sure that the environment isn't empty so that we won't exit at the
2131bottom of this page.
2132*/
2133
2134void environment::final_break()
2135{
2136  if (prev_line_interrupted == 2) {
2137    do_break();
2138    add_node(new transparent_dummy_node);
2139  }
2140  else
2141    do_break();
2142}
2143
2144/*
2145 *  add_html_tag - emits a special html-tag: to help post-grohtml understand
2146 *                 the key troff commands
2147 */
2148
2149void environment::add_html_tag(int force, const char *name)
2150{
2151  if (!force && (curdiv != topdiv))
2152    return;
2153
2154  if (is_html) {
2155    /*
2156     * need to emit tag for post-grohtml
2157     * but we check to see whether we can emit specials
2158     */
2159    if (curdiv == topdiv && topdiv->before_first_page)
2160      topdiv->begin_page();
2161    macro *m = new macro;
2162    m->append_str("html-tag:");
2163    for (const char *p = name; *p; p++)
2164      if (!invalid_input_char((unsigned char)*p))
2165	m->append(*p);
2166    curdiv->output(new special_node(*m), 1, 0, 0, 0);
2167    if (strcmp(name, ".nf") == 0)
2168      curenv->ignore_next_eol = 1;
2169  }
2170}
2171
2172/*
2173 *  add_html_tag - emits a special html-tag: to help post-grohtml understand
2174 *                 the key troff commands, it appends a string representation
2175 *                 of i.
2176 */
2177
2178void environment::add_html_tag(int force, const char *name, int i)
2179{
2180  if (!force && (curdiv != topdiv))
2181    return;
2182
2183  if (is_html) {
2184    /*
2185     * need to emit tag for post-grohtml
2186     * but we check to see whether we can emit specials
2187     */
2188    if (curdiv == topdiv && topdiv->before_first_page)
2189      topdiv->begin_page();
2190    macro *m = new macro;
2191    m->append_str("html-tag:");
2192    for (const char *p = name; *p; p++)
2193      if (!invalid_input_char((unsigned char)*p))
2194	m->append(*p);
2195    m->append(' ');
2196    m->append_int(i);
2197    node *n = new special_node(*m);
2198    curdiv->output(n, 1, 0, 0, 0);
2199  }
2200}
2201
2202/*
2203 *  add_html_tag_tabs - emits the tab settings for post-grohtml
2204 */
2205
2206void environment::add_html_tag_tabs(int force)
2207{
2208  if (!force && (curdiv != topdiv))
2209    return;
2210
2211  if (is_html) {
2212    /*
2213     * need to emit tag for post-grohtml
2214     * but we check to see whether we can emit specials
2215     */
2216    if (curdiv == topdiv && topdiv->before_first_page)
2217      topdiv->begin_page();
2218    macro *m = new macro;
2219    hunits d, l;
2220    enum tab_type t;
2221    m->append_str("html-tag:.ta ");
2222    do {
2223      t = curenv->tabs.distance_to_next_tab(l, &d);
2224      l += d;
2225      switch (t) {
2226      case TAB_LEFT:
2227	m->append_str(" L ");
2228	m->append_int(l.to_units());
2229	break;
2230      case TAB_CENTER:
2231	m->append_str(" C ");
2232	m->append_int(l.to_units());
2233	break;
2234      case TAB_RIGHT:
2235	m->append_str(" R ");
2236	m->append_int(l.to_units());
2237	break;
2238      case TAB_NONE:
2239	break;
2240      }
2241    } while ((t != TAB_NONE) && (l < get_line_length()));
2242    curdiv->output(new special_node(*m), 1, 0, 0, 0);
2243  }
2244}
2245
2246node *environment::make_html_tag(const char *name, int i)
2247{
2248  if (is_html) {
2249    /*
2250     * need to emit tag for post-grohtml
2251     * but we check to see whether we can emit specials
2252     */
2253    if (curdiv == topdiv && topdiv->before_first_page)
2254      topdiv->begin_page();
2255    macro *m = new macro;
2256    m->append_str("html-tag:");
2257    for (const char *p = name; *p; p++)
2258      if (!invalid_input_char((unsigned char)*p))
2259	m->append(*p);
2260    m->append(' ');
2261    m->append_int(i);
2262    return new special_node(*m);
2263  }
2264  return 0;
2265}
2266
2267node *environment::make_html_tag(const char *name)
2268{
2269  if (is_html) {
2270    /*
2271     * need to emit tag for post-grohtml
2272     * but we check to see whether we can emit specials
2273     */
2274    if (curdiv == topdiv && topdiv->before_first_page)
2275      topdiv->begin_page();
2276    macro *m = new macro;
2277    m->append_str("html-tag:");
2278    for (const char *p = name; *p; p++)
2279      if (!invalid_input_char((unsigned char)*p))
2280	m->append(*p);
2281    return new special_node(*m);
2282  }
2283  return 0;
2284}
2285
2286void environment::do_break(int spread)
2287{
2288  if (curdiv == topdiv && topdiv->before_first_page) {
2289    topdiv->begin_page();
2290    return;
2291  }
2292  if (current_tab)
2293    wrap_up_tab();
2294  if (line) {
2295    // this is so that hyphenation works
2296    line = new space_node(H0, get_fill_color(), line);
2297    space_total++;
2298    possibly_break_line(0, spread);
2299  }
2300  while (line != 0 && line->discardable()) {
2301    width_total -= line->width();
2302    space_total -= line->nspaces();
2303    node *tem = line;
2304    line = line->next;
2305    delete tem;
2306  }
2307  discarding = 0;
2308  input_line_start = H0;
2309  if (line != 0) {
2310    if (fill) {
2311      switch (adjust_mode) {
2312      case ADJUST_CENTER:
2313	saved_indent += (target_text_length - width_total)/2;
2314	break;
2315      case ADJUST_RIGHT:
2316	saved_indent += target_text_length - width_total;
2317	break;
2318      }
2319    }
2320    node *tem = line;
2321    line = 0;
2322    output_line(tem, width_total);
2323    hyphen_line_count = 0;
2324  }
2325  prev_line_interrupted = 0;
2326#ifdef WIDOW_CONTROL
2327  mark_last_line();
2328  output_pending_lines();
2329#endif /* WIDOW_CONTROL */
2330}
2331
2332int environment::is_empty()
2333{
2334  return !current_tab && line == 0 && pending_lines == 0;
2335}
2336
2337void do_break_request(int spread)
2338{
2339  while (!tok.newline() && !tok.eof())
2340    tok.next();
2341  if (break_flag) {
2342    curenv->do_break(spread);
2343    curenv->add_html_tag(0, ".br");
2344  }
2345  tok.next();
2346}
2347
2348void break_request()
2349{
2350  do_break_request(0);
2351}
2352
2353void break_spread_request()
2354{
2355  do_break_request(1);
2356}
2357
2358void title()
2359{
2360  if (curdiv == topdiv && topdiv->before_first_page) {
2361    handle_initial_title();
2362    return;
2363  }
2364  node *part[3];
2365  hunits part_width[3];
2366  part[0] = part[1] = part[2] = 0;
2367  environment env(curenv);
2368  environment *oldenv = curenv;
2369  curenv = &env;
2370  read_title_parts(part, part_width);
2371  curenv = oldenv;
2372  curenv->size = env.size;
2373  curenv->prev_size = env.prev_size;
2374  curenv->requested_size = env.requested_size;
2375  curenv->prev_requested_size = env.prev_requested_size;
2376  curenv->char_height = env.char_height;
2377  curenv->char_slant = env.char_slant;
2378  curenv->fontno = env.fontno;
2379  curenv->prev_fontno = env.prev_fontno;
2380  curenv->glyph_color = env.glyph_color;
2381  curenv->prev_glyph_color = env.prev_glyph_color;
2382  curenv->fill_color = env.fill_color;
2383  curenv->prev_fill_color = env.prev_fill_color;
2384  node *n = 0;
2385  node *p = part[2];
2386  while (p != 0) {
2387    node *tem = p;
2388    p = p->next;
2389    tem->next = n;
2390    n = tem;
2391  }
2392  hunits title_length(curenv->title_length);
2393  hunits f = title_length - part_width[1];
2394  hunits f2 = f/2;
2395  n = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(), n);
2396  p = part[1];
2397  while (p != 0) {
2398    node *tem = p;
2399    p = p->next;
2400    tem->next = n;
2401    n = tem;
2402  }
2403  n = new hmotion_node(f - f2 - part_width[0], curenv->get_fill_color(), n);
2404  p = part[0];
2405  while (p != 0) {
2406    node *tem = p;
2407    p = p->next;
2408    tem->next = n;
2409    n = tem;
2410  }
2411  curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
2412		       curenv->total_post_vertical_spacing(), title_length);
2413  curenv->hyphen_line_count = 0;
2414  tok.next();
2415}
2416
2417void adjust()
2418{
2419  curenv->adjust_mode |= 1;
2420  if (has_arg()) {
2421    switch (tok.ch()) {
2422    case 'l':
2423      curenv->adjust_mode = ADJUST_LEFT;
2424      break;
2425    case 'r':
2426      curenv->adjust_mode = ADJUST_RIGHT;
2427      break;
2428    case 'c':
2429      curenv->adjust_mode = ADJUST_CENTER;
2430      break;
2431    case 'b':
2432    case 'n':
2433      curenv->adjust_mode = ADJUST_BOTH;
2434      break;
2435    default:
2436      int n;
2437      if (get_integer(&n)) {
2438	if (n < 0)
2439	  warning(WARN_RANGE, "negative adjustment mode");
2440	else if (n > 5) {
2441	  curenv->adjust_mode = 5;
2442	  warning(WARN_RANGE, "adjustment mode `%1' out of range", n);
2443	}
2444	else
2445	  curenv->adjust_mode = n;
2446      }
2447    }
2448  }
2449  skip_line();
2450}
2451
2452void no_adjust()
2453{
2454  curenv->adjust_mode &= ~1;
2455  skip_line();
2456}
2457
2458void do_input_trap(int continued)
2459{
2460  curenv->input_trap_count = 0;
2461  if (continued)
2462    curenv->continued_input_trap = 1;
2463  int n;
2464  if (has_arg() && get_integer(&n)) {
2465    if (n <= 0)
2466      warning(WARN_RANGE,
2467	      "number of lines for input trap must be greater than zero");
2468    else {
2469      symbol s = get_name(1);
2470      if (!s.is_null()) {
2471	curenv->input_trap_count = n;
2472	curenv->input_trap = s;
2473      }
2474    }
2475  }
2476  skip_line();
2477}
2478
2479void input_trap()
2480{
2481  do_input_trap(0);
2482}
2483
2484void input_trap_continued()
2485{
2486  do_input_trap(1);
2487}
2488
2489/* tabs */
2490
2491// must not be R or C or L or a legitimate part of a number expression
2492const char TAB_REPEAT_CHAR = 'T';
2493
2494struct tab {
2495  tab *next;
2496  hunits pos;
2497  tab_type type;
2498  tab(hunits, tab_type);
2499  enum { BLOCK = 1024 };
2500  static tab *free_list;
2501  void *operator new(size_t);
2502  void operator delete(void *);
2503};
2504
2505tab *tab::free_list = 0;
2506
2507void *tab::operator new(size_t n)
2508{
2509  assert(n == sizeof(tab));
2510  if (!free_list) {
2511    free_list = (tab *)new char[sizeof(tab)*BLOCK];
2512    for (int i = 0; i < BLOCK - 1; i++)
2513      free_list[i].next = free_list + i + 1;
2514    free_list[BLOCK-1].next = 0;
2515  }
2516  tab *p = free_list;
2517  free_list = (tab *)(free_list->next);
2518  p->next = 0;
2519  return p;
2520}
2521
2522#ifdef __GNUG__
2523/* cfront can't cope with this. */
2524inline
2525#endif
2526void tab::operator delete(void *p)
2527{
2528  if (p) {
2529    ((tab *)p)->next = free_list;
2530    free_list = (tab *)p;
2531  }
2532}
2533
2534tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2535{
2536}
2537
2538tab_stops::tab_stops(hunits distance, tab_type type)
2539: initial_list(0)
2540{
2541  repeated_list = new tab(distance, type);
2542}
2543
2544tab_stops::~tab_stops()
2545{
2546  clear();
2547}
2548
2549tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
2550{
2551  hunits nextpos;
2552
2553  return distance_to_next_tab(curpos, distance, &nextpos);
2554}
2555
2556tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance,
2557					 hunits *nextpos)
2558{
2559  hunits lastpos = 0;
2560  tab *tem;
2561  for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
2562    lastpos = tem->pos;
2563  if (tem) {
2564    *distance = tem->pos - curpos;
2565    *nextpos  = tem->pos;
2566    return tem->type;
2567  }
2568  if (repeated_list == 0)
2569    return TAB_NONE;
2570  hunits base = lastpos;
2571  for (;;) {
2572    for (tem = repeated_list; tem && tem->pos + base <= curpos; tem = tem->next)
2573      lastpos = tem->pos;
2574    if (tem) {
2575      *distance = tem->pos + base - curpos;
2576      *nextpos  = tem->pos + base;
2577      return tem->type;
2578    }
2579    assert(lastpos > 0);
2580    base += lastpos;
2581  }
2582  return TAB_NONE;
2583}
2584
2585const char *tab_stops::to_string()
2586{
2587  static char *buf = 0;
2588  static int buf_size = 0;
2589  // figure out a maximum on the amount of space we can need
2590  int count = 0;
2591  tab *p;
2592  for (p = initial_list; p; p = p->next)
2593    ++count;
2594  for (p = repeated_list; p; p = p->next)
2595    ++count;
2596  // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
2597  int need = count*12 + 3;
2598  if (buf == 0 || need > buf_size) {
2599    if (buf)
2600      a_delete buf;
2601    buf_size = need;
2602    buf = new char[buf_size];
2603  }
2604  char *ptr = buf;
2605  for (p = initial_list; p; p = p->next) {
2606    strcpy(ptr, i_to_a(p->pos.to_units()));
2607    ptr = strchr(ptr, '\0');
2608    *ptr++ = 'u';
2609    *ptr = '\0';
2610    switch (p->type) {
2611    case TAB_LEFT:
2612      break;
2613    case TAB_RIGHT:
2614      *ptr++ = 'R';
2615      break;
2616    case TAB_CENTER:
2617      *ptr++ = 'C';
2618      break;
2619    case TAB_NONE:
2620    default:
2621      assert(0);
2622    }
2623  }
2624  if (repeated_list)
2625    *ptr++ = TAB_REPEAT_CHAR;
2626  for (p = repeated_list; p; p = p->next) {
2627    strcpy(ptr, i_to_a(p->pos.to_units()));
2628    ptr = strchr(ptr, '\0');
2629    *ptr++ = 'u';
2630    *ptr = '\0';
2631    switch (p->type) {
2632    case TAB_LEFT:
2633      break;
2634    case TAB_RIGHT:
2635      *ptr++ = 'R';
2636      break;
2637    case TAB_CENTER:
2638      *ptr++ = 'C';
2639      break;
2640    case TAB_NONE:
2641    default:
2642      assert(0);
2643    }
2644  }
2645  *ptr++ = '\0';
2646  return buf;
2647}
2648
2649tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2650{
2651}
2652
2653tab_stops::tab_stops(const tab_stops &ts)
2654: initial_list(0), repeated_list(0)
2655{
2656  tab **p = &initial_list;
2657  tab *t = ts.initial_list;
2658  while (t) {
2659    *p = new tab(t->pos, t->type);
2660    t = t->next;
2661    p = &(*p)->next;
2662  }
2663  p = &repeated_list;
2664  t = ts.repeated_list;
2665  while (t) {
2666    *p = new tab(t->pos, t->type);
2667    t = t->next;
2668    p = &(*p)->next;
2669  }
2670}
2671
2672void tab_stops::clear()
2673{
2674  while (initial_list) {
2675    tab *tem = initial_list;
2676    initial_list = initial_list->next;
2677    delete tem;
2678  }
2679  while (repeated_list) {
2680    tab *tem = repeated_list;
2681    repeated_list = repeated_list->next;
2682    delete tem;
2683  }
2684}
2685
2686void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
2687{
2688  tab **p;
2689  for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
2690    ;
2691  *p = new tab(pos, type);
2692}
2693
2694
2695void tab_stops::operator=(const tab_stops &ts)
2696{
2697  clear();
2698  tab **p = &initial_list;
2699  tab *t = ts.initial_list;
2700  while (t) {
2701    *p = new tab(t->pos, t->type);
2702    t = t->next;
2703    p = &(*p)->next;
2704  }
2705  p = &repeated_list;
2706  t = ts.repeated_list;
2707  while (t) {
2708    *p = new tab(t->pos, t->type);
2709    t = t->next;
2710    p = &(*p)->next;
2711  }
2712}
2713
2714void set_tabs()
2715{
2716  hunits pos;
2717  hunits prev_pos = 0;
2718  int first = 1;
2719  int repeated = 0;
2720  tab_stops tabs;
2721  while (has_arg()) {
2722    if (tok.ch() == TAB_REPEAT_CHAR) {
2723      tok.next();
2724      repeated = 1;
2725      prev_pos = 0;
2726    }
2727    if (!get_hunits(&pos, 'm', prev_pos))
2728      break;
2729    tab_type type = TAB_LEFT;
2730    if (tok.ch() == 'C') {
2731      tok.next();
2732      type = TAB_CENTER;
2733    }
2734    else if (tok.ch() == 'R') {
2735      tok.next();
2736      type = TAB_RIGHT;
2737    }
2738    else if (tok.ch() == 'L') {
2739      tok.next();
2740    }
2741    if (pos <= prev_pos && !first)
2742      warning(WARN_RANGE,
2743	      "positions of tab stops must be strictly increasing");
2744    else {
2745      tabs.add_tab(pos, type, repeated);
2746      prev_pos = pos;
2747      first = 0;
2748    }
2749  }
2750  curenv->tabs = tabs;
2751  curenv->add_html_tag_tabs(1);
2752  skip_line();
2753}
2754
2755const char *environment::get_tabs()
2756{
2757  return tabs.to_string();
2758}
2759
2760tab_type environment::distance_to_next_tab(hunits *distance)
2761{
2762  return line_tabs
2763    ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
2764    : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
2765}
2766
2767tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
2768{
2769  return line_tabs
2770    ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
2771    : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
2772					leftpos);
2773}
2774
2775void field_characters()
2776{
2777  field_delimiter_char = get_optional_char();
2778  if (field_delimiter_char)
2779    padding_indicator_char = get_optional_char();
2780  else
2781    padding_indicator_char = 0;
2782  skip_line();
2783}
2784
2785void line_tabs_request()
2786{
2787  int n;
2788  if (has_arg() && get_integer(&n))
2789    curenv->line_tabs = n != 0;
2790  else
2791    curenv->line_tabs = 1;
2792  skip_line();
2793}
2794
2795int environment::get_line_tabs()
2796{
2797  return line_tabs;
2798}
2799
2800void environment::wrap_up_tab()
2801{
2802  if (!current_tab)
2803    return;
2804  if (line == 0)
2805    start_line();
2806  hunits tab_amount;
2807  switch (current_tab) {
2808  case TAB_RIGHT:
2809    tab_amount = tab_distance - tab_width;
2810    line = make_tab_node(tab_amount, line);
2811    break;
2812  case TAB_CENTER:
2813    tab_amount = tab_distance - tab_width/2;
2814    line = make_tab_node(tab_amount, line);
2815    break;
2816  case TAB_NONE:
2817  case TAB_LEFT:
2818  default:
2819    assert(0);
2820  }
2821  width_total += tab_amount;
2822  width_total += tab_width;
2823  if (current_field) {
2824    if (tab_precedes_field) {
2825      pre_field_width += tab_amount;
2826      tab_precedes_field = 0;
2827    }
2828    field_distance -= tab_amount;
2829    field_spaces += tab_field_spaces;
2830  }
2831  if (tab_contents != 0) {
2832    node *tem;
2833    for (tem = tab_contents; tem->next != 0; tem = tem->next)
2834      ;
2835    tem->next = line;
2836    line = tab_contents;
2837  }
2838  tab_field_spaces = 0;
2839  tab_contents = 0;
2840  tab_width = H0;
2841  tab_distance = H0;
2842  current_tab = TAB_NONE;
2843}
2844
2845node *environment::make_tab_node(hunits d, node *next)
2846{
2847  if (leader_node != 0 && d < 0) {
2848    error("motion generated by leader cannot be negative");
2849    delete leader_node;
2850    leader_node = 0;
2851  }
2852  if (!leader_node)
2853    return new hmotion_node(d, 1, 0, get_fill_color(), next);
2854  node *n = new hline_node(d, leader_node, next);
2855  leader_node = 0;
2856  return n;
2857}
2858
2859void environment::handle_tab(int is_leader)
2860{
2861  hunits d;
2862  hunits abs;
2863  if (current_tab)
2864    wrap_up_tab();
2865  charinfo *ci = is_leader ? leader_char : tab_char;
2866  delete leader_node;
2867  leader_node = ci ? make_char_node(ci) : 0;
2868  tab_type t = distance_to_next_tab(&d, &abs);
2869  switch (t) {
2870  case TAB_NONE:
2871    return;
2872  case TAB_LEFT:
2873    add_node(make_tab_node(d));
2874    add_node(make_html_tag("tab L", abs.to_units()));
2875    return;
2876  case TAB_RIGHT:
2877    add_node(make_html_tag("tab R", abs.to_units()));
2878    break;
2879  case TAB_CENTER:
2880    add_node(make_html_tag("tab C", abs.to_units()));
2881    break;
2882  default:
2883    assert(0);
2884  }
2885  tab_width = 0;
2886  tab_distance = d;
2887  tab_contents = 0;
2888  current_tab = t;
2889  tab_field_spaces = 0;
2890}
2891
2892void environment::start_field()
2893{
2894  assert(!current_field);
2895  hunits d;
2896  if (distance_to_next_tab(&d) != TAB_NONE) {
2897    pre_field_width = get_text_length();
2898    field_distance = d;
2899    current_field = 1;
2900    field_spaces = 0;
2901    tab_field_spaces = 0;
2902    for (node *p = line; p; p = p->next)
2903      if (p->nspaces()) {
2904	p->freeze_space();
2905	space_total--;
2906      }
2907    tab_precedes_field = current_tab != TAB_NONE;
2908  }
2909  else
2910    error("zero field width");
2911}
2912
2913void environment::wrap_up_field()
2914{
2915  if (!current_tab && field_spaces == 0)
2916    add_padding();
2917  hunits padding = field_distance - (get_text_length() - pre_field_width);
2918  if (current_tab && tab_field_spaces != 0) {
2919    hunits tab_padding = scale(padding,
2920			       tab_field_spaces,
2921			       field_spaces + tab_field_spaces);
2922    padding -= tab_padding;
2923    distribute_space(tab_contents, tab_field_spaces, tab_padding, 1);
2924    tab_field_spaces = 0;
2925    tab_width += tab_padding;
2926  }
2927  if (field_spaces != 0) {
2928    distribute_space(line, field_spaces, padding, 1);
2929    width_total += padding;
2930    if (current_tab) {
2931      // the start of the tab has been moved to the right by padding, so
2932      tab_distance -= padding;
2933      if (tab_distance <= H0) {
2934	// use the next tab stop instead
2935	current_tab = tabs.distance_to_next_tab(get_input_line_position()
2936						- tab_width,
2937						&tab_distance);
2938	if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
2939	  width_total += tab_width;
2940	  if (current_tab == TAB_LEFT) {
2941	    line = make_tab_node(tab_distance, line);
2942	    width_total += tab_distance;
2943	    current_tab = TAB_NONE;
2944	  }
2945	  if (tab_contents != 0) {
2946	    node *tem;
2947	    for (tem = tab_contents; tem->next != 0; tem = tem->next)
2948	      ;
2949	    tem->next = line;
2950	    line = tab_contents;
2951	    tab_contents = 0;
2952	  }
2953	  tab_width = H0;
2954	  tab_distance = H0;
2955	}
2956      }
2957    }
2958  }
2959  current_field = 0;
2960}
2961
2962void environment::add_padding()
2963{
2964  if (current_tab) {
2965    tab_contents = new space_node(H0, get_fill_color(), tab_contents);
2966    tab_field_spaces++;
2967  }
2968  else {
2969    if (line == 0)
2970      start_line();
2971    line = new space_node(H0, get_fill_color(), line);
2972    field_spaces++;
2973  }
2974}
2975
2976typedef int (environment::*INT_FUNCP)();
2977typedef vunits (environment::*VUNITS_FUNCP)();
2978typedef hunits (environment::*HUNITS_FUNCP)();
2979typedef const char *(environment::*STRING_FUNCP)();
2980
2981class int_env_reg : public reg {
2982  INT_FUNCP func;
2983 public:
2984  int_env_reg(INT_FUNCP);
2985  const char *get_string();
2986  int get_value(units *val);
2987};
2988
2989class vunits_env_reg : public reg {
2990  VUNITS_FUNCP func;
2991 public:
2992  vunits_env_reg(VUNITS_FUNCP f);
2993  const char *get_string();
2994  int get_value(units *val);
2995};
2996
2997
2998class hunits_env_reg : public reg {
2999  HUNITS_FUNCP func;
3000 public:
3001  hunits_env_reg(HUNITS_FUNCP f);
3002  const char *get_string();
3003  int get_value(units *val);
3004};
3005
3006class string_env_reg : public reg {
3007  STRING_FUNCP func;
3008public:
3009  string_env_reg(STRING_FUNCP);
3010  const char *get_string();
3011};
3012
3013int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
3014{
3015}
3016
3017int int_env_reg::get_value(units *val)
3018{
3019  *val = (curenv->*func)();
3020  return 1;
3021}
3022
3023const char *int_env_reg::get_string()
3024{
3025  return i_to_a((curenv->*func)());
3026}
3027
3028vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
3029{
3030}
3031
3032int vunits_env_reg::get_value(units *val)
3033{
3034  *val = (curenv->*func)().to_units();
3035  return 1;
3036}
3037
3038const char *vunits_env_reg::get_string()
3039{
3040  return i_to_a((curenv->*func)().to_units());
3041}
3042
3043hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
3044{
3045}
3046
3047int hunits_env_reg::get_value(units *val)
3048{
3049  *val = (curenv->*func)().to_units();
3050  return 1;
3051}
3052
3053const char *hunits_env_reg::get_string()
3054{
3055  return i_to_a((curenv->*func)().to_units());
3056}
3057
3058string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
3059{
3060}
3061
3062const char *string_env_reg::get_string()
3063{
3064  return (curenv->*func)();
3065}
3066
3067class horizontal_place_reg : public general_reg {
3068public:
3069  horizontal_place_reg();
3070  int get_value(units *);
3071  void set_value(units);
3072};
3073
3074horizontal_place_reg::horizontal_place_reg()
3075{
3076}
3077
3078int horizontal_place_reg::get_value(units *res)
3079{
3080  *res = curenv->get_input_line_position().to_units();
3081  return 1;
3082}
3083
3084void horizontal_place_reg::set_value(units n)
3085{
3086  curenv->set_input_line_position(hunits(n));
3087}
3088
3089const char *environment::get_font_family_string()
3090{
3091  return family->nm.contents();
3092}
3093
3094const char *environment::get_font_name_string()
3095{
3096  symbol f = get_font_name(fontno, this);
3097  return f.contents();
3098}
3099
3100const char *environment::get_name_string()
3101{
3102  return name.contents();
3103}
3104
3105// Convert a quantity in scaled points to ascii decimal fraction.
3106
3107const char *sptoa(int sp)
3108{
3109  assert(sp > 0);
3110  assert(sizescale > 0);
3111  if (sizescale == 1)
3112    return i_to_a(sp);
3113  if (sp % sizescale == 0)
3114    return i_to_a(sp/sizescale);
3115  // See if 1/sizescale is exactly representable as a decimal fraction,
3116  // ie its only prime factors are 2 and 5.
3117  int n = sizescale;
3118  int power2 = 0;
3119  while ((n & 1) == 0) {
3120    n >>= 1;
3121    power2++;
3122  }
3123  int power5 = 0;
3124  while ((n % 5) == 0) {
3125    n /= 5;
3126    power5++;
3127  }
3128  if (n == 1) {
3129    int decimal_point = power5 > power2 ? power5 : power2;
3130    if (decimal_point <= 10) {
3131      int factor = 1;
3132      int t;
3133      for (t = decimal_point - power2; --t >= 0;)
3134	factor *= 2;
3135      for (t = decimal_point - power5; --t >= 0;)
3136	factor *= 5;
3137      if (factor == 1 || sp <= INT_MAX/factor)
3138	return if_to_a(sp*factor, decimal_point);
3139    }
3140  }
3141  double s = double(sp)/double(sizescale);
3142  double factor = 10.0;
3143  double val = s;
3144  int decimal_point = 0;
3145  do  {
3146    double v = ceil(s*factor);
3147    if (v > INT_MAX)
3148      break;
3149    val = v;
3150    factor *= 10.0;
3151  } while (++decimal_point < 10);
3152  return if_to_a(int(val), decimal_point);
3153}
3154
3155const char *environment::get_point_size_string()
3156{
3157  return sptoa(curenv->get_point_size());
3158}
3159
3160const char *environment::get_requested_point_size_string()
3161{
3162  return sptoa(curenv->get_requested_point_size());
3163}
3164
3165#define init_int_env_reg(name, func) \
3166  number_reg_dictionary.define(name, new int_env_reg(&environment::func))
3167
3168#define init_vunits_env_reg(name, func) \
3169  number_reg_dictionary.define(name, new vunits_env_reg(&environment::func))
3170
3171#define init_hunits_env_reg(name, func) \
3172  number_reg_dictionary.define(name, new hunits_env_reg(&environment::func))
3173
3174#define init_string_env_reg(name, func) \
3175  number_reg_dictionary.define(name, new string_env_reg(&environment::func))
3176
3177void init_env_requests()
3178{
3179  init_request("ad", adjust);
3180  init_request("br", break_request);
3181  init_request("brp", break_spread_request);
3182  init_request("c2", no_break_control_char);
3183  init_request("cc", control_char);
3184  init_request("ce", center);
3185  init_request("cu", continuous_underline);
3186  init_request("ev", environment_switch);
3187  init_request("evc", environment_copy);
3188  init_request("fam", family_change);
3189  init_request("fc", field_characters);
3190  init_request("fi", fill);
3191  init_request("ft", font_change);
3192  init_request("hc", hyphen_char);
3193  init_request("hlm", hyphen_line_max_request);
3194  init_request("hy", hyphenate_request);
3195  init_request("hym", hyphenation_margin_request);
3196  init_request("hys", hyphenation_space_request);
3197  init_request("in", indent);
3198  init_request("it", input_trap);
3199  init_request("itc", input_trap_continued);
3200  init_request("lc", leader_character);
3201  init_request("linetabs", line_tabs_request);
3202  init_request("ll", line_length);
3203  init_request("ls", line_spacing);
3204  init_request("lt", title_length);
3205  init_request("mc", margin_character);
3206  init_request("na", no_adjust);
3207  init_request("nf", no_fill);
3208  init_request("nh", no_hyphenate);
3209  init_request("nm", number_lines);
3210  init_request("nn", no_number);
3211  init_request("ps", point_size);
3212  init_request("pvs", post_vertical_spacing);
3213  init_request("rj", right_justify);
3214  init_request("sizes", override_sizes);
3215  init_request("ss", space_size);
3216  init_request("ta", set_tabs);
3217  init_request("ti", temporary_indent);
3218  init_request("tc", tab_character);
3219  init_request("tl", title);
3220  init_request("ul", underline);
3221  init_request("vs", vertical_spacing);
3222#ifdef WIDOW_CONTROL
3223  init_request("wdc", widow_control_request);
3224#endif /* WIDOW_CONTROL */
3225  init_int_env_reg(".b", get_bold);
3226  init_vunits_env_reg(".cdp", get_prev_char_depth);
3227  init_int_env_reg(".ce", get_center_lines);
3228  init_vunits_env_reg(".cht", get_prev_char_height);
3229  init_hunits_env_reg(".csk", get_prev_char_skew);
3230  init_string_env_reg(".ev", get_name_string);
3231  init_int_env_reg(".f", get_font);
3232  init_string_env_reg(".fam", get_font_family_string);
3233  init_string_env_reg(".fn", get_font_name_string);
3234  init_int_env_reg(".height", get_char_height);
3235  init_int_env_reg(".hlc", get_hyphen_line_count);
3236  init_int_env_reg(".hlm", get_hyphen_line_max);
3237  init_int_env_reg(".hy", get_hyphenation_flags);
3238  init_hunits_env_reg(".hym", get_hyphenation_margin);
3239  init_hunits_env_reg(".hys", get_hyphenation_space);
3240  init_hunits_env_reg(".i", get_indent);
3241  init_hunits_env_reg(".in", get_saved_indent);
3242  init_int_env_reg(".int", get_prev_line_interrupted);
3243  init_int_env_reg(".linetabs", get_line_tabs);
3244  init_hunits_env_reg(".lt", get_title_length);
3245  init_int_env_reg(".j", get_adjust_mode);
3246  init_hunits_env_reg(".k", get_text_length);
3247  init_int_env_reg(".L", get_line_spacing);
3248  init_hunits_env_reg(".l", get_line_length);
3249  init_hunits_env_reg(".ll", get_saved_line_length);
3250  init_hunits_env_reg(".n", get_prev_text_length);
3251  init_int_env_reg(".ps", get_point_size);
3252  init_int_env_reg(".psr", get_requested_point_size);
3253  init_vunits_env_reg(".pvs", get_post_vertical_spacing);
3254  init_int_env_reg(".rj", get_right_justify_lines);
3255  init_string_env_reg(".s", get_point_size_string);
3256  init_int_env_reg(".slant", get_char_slant);
3257  init_int_env_reg(".ss", get_space_size);
3258  init_int_env_reg(".sss", get_sentence_space_size);
3259  init_string_env_reg(".sr", get_requested_point_size_string);
3260  init_string_env_reg(".tabs", get_tabs);
3261  init_int_env_reg(".u", get_fill);
3262  init_vunits_env_reg(".v", get_vertical_spacing);
3263  init_hunits_env_reg(".w", get_prev_char_width);
3264  number_reg_dictionary.define("ct", new variable_reg(&ct_reg_contents));
3265  number_reg_dictionary.define("hp", new horizontal_place_reg);
3266  number_reg_dictionary.define("ln", new variable_reg(&next_line_number));
3267  number_reg_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
3268  number_reg_dictionary.define("rst", new variable_reg(&rst_reg_contents));
3269  number_reg_dictionary.define("sb", new variable_reg(&sb_reg_contents));
3270  number_reg_dictionary.define("skw", new variable_reg(&skw_reg_contents));
3271  number_reg_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
3272  number_reg_dictionary.define("st", new variable_reg(&st_reg_contents));
3273}
3274
3275// Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
3276
3277struct trie_node;
3278
3279class trie {
3280  trie_node *tp;
3281  virtual void do_match(int len, void *val) = 0;
3282  virtual void do_delete(void *) = 0;
3283  void delete_trie_node(trie_node *);
3284public:
3285  trie() : tp(0) {}
3286  virtual ~trie();		// virtual to shut up g++
3287  void insert(const char *, int, void *);
3288  // find calls do_match for each match it finds
3289  void find(const char *pat, int patlen);
3290  void clear();
3291};
3292
3293class hyphen_trie : private trie {
3294  int *h;
3295  void do_match(int i, void *v);
3296  void do_delete(void *v);
3297  void insert_pattern(const char *pat, int patlen, int *num);
3298  void insert_hyphenation(dictionary *ex, const char *pat, int patlen);
3299  int hpf_getc(FILE *f);
3300public:
3301  hyphen_trie() {}
3302  ~hyphen_trie() {}
3303  void hyphenate(const char *word, int len, int *hyphens);
3304  void read_patterns_file(const char *name, int append, dictionary *ex);
3305};
3306
3307struct hyphenation_language {
3308  symbol name;
3309  dictionary exceptions;
3310  hyphen_trie patterns;
3311  hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
3312  ~hyphenation_language() { }
3313};
3314
3315dictionary language_dictionary(5);
3316hyphenation_language *current_language = 0;
3317
3318static void set_hyphenation_language()
3319{
3320  symbol nm = get_name(1);
3321  if (!nm.is_null()) {
3322    current_language = (hyphenation_language *)language_dictionary.lookup(nm);
3323    if (!current_language) {
3324      current_language = new hyphenation_language(nm);
3325      (void)language_dictionary.lookup(nm, (void *)current_language);
3326    }
3327  }
3328  skip_line();
3329}
3330
3331const int WORD_MAX = 256;	// we use unsigned char for offsets in
3332				// hyphenation exceptions
3333
3334static void hyphen_word()
3335{
3336  if (!current_language) {
3337    error("no current hyphenation language");
3338    skip_line();
3339    return;
3340  }
3341  char buf[WORD_MAX + 1];
3342  unsigned char pos[WORD_MAX + 2];
3343  for (;;) {
3344    tok.skip();
3345    if (tok.newline() || tok.eof())
3346      break;
3347    int i = 0;
3348    int npos = 0;
3349    while (i < WORD_MAX && !tok.space() && !tok.newline() && !tok.eof()) {
3350      charinfo *ci = tok.get_char(1);
3351      if (ci == 0) {
3352	skip_line();
3353	return;
3354      }
3355      tok.next();
3356      if (ci->get_ascii_code() == '-') {
3357	if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3358	  pos[npos++] = i;
3359      }
3360      else {
3361	int c = ci->get_hyphenation_code();
3362	if (c == 0)
3363	  break;
3364	buf[i++] = c;
3365      }
3366    }
3367    if (i > 0) {
3368      pos[npos] = 0;
3369      buf[i] = 0;
3370      unsigned char *tem = new unsigned char[npos + 1];
3371      memcpy(tem, pos, npos + 1);
3372      tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf),
3373								 tem);
3374      if (tem)
3375	a_delete tem;
3376    }
3377  }
3378  skip_line();
3379}
3380
3381struct trie_node {
3382  char c;
3383  trie_node *down;
3384  trie_node *right;
3385  void *val;
3386  trie_node(char, trie_node *);
3387};
3388
3389trie_node::trie_node(char ch, trie_node *p)
3390: c(ch), down(0), right(p), val(0)
3391{
3392}
3393
3394trie::~trie()
3395{
3396  clear();
3397}
3398
3399void trie::clear()
3400{
3401  delete_trie_node(tp);
3402  tp = 0;
3403}
3404
3405
3406void trie::delete_trie_node(trie_node *p)
3407{
3408  if (p) {
3409    delete_trie_node(p->down);
3410    delete_trie_node(p->right);
3411    if (p->val)
3412      do_delete(p->val);
3413    delete p;
3414  }
3415}
3416
3417void trie::insert(const char *pat, int patlen, void *val)
3418{
3419  trie_node **p = &tp;
3420  assert(patlen > 0 && pat != 0);
3421  for (;;) {
3422    while (*p != 0 && (*p)->c < pat[0])
3423      p = &((*p)->right);
3424    if (*p == 0 || (*p)->c != pat[0])
3425      *p = new trie_node(pat[0], *p);
3426    if (--patlen == 0) {
3427      (*p)->val = val;
3428      break;
3429    }
3430    ++pat;
3431    p = &((*p)->down);
3432  }
3433}
3434
3435void trie::find(const char *pat, int patlen)
3436{
3437  trie_node *p = tp;
3438  for (int i = 0; p != 0 && i < patlen; i++) {
3439    while (p != 0 && p->c < pat[i])
3440      p = p->right;
3441    if (p != 0 && p->c == pat[i]) {
3442      if (p->val != 0)
3443	do_match(i+1, p->val);
3444      p = p->down;
3445    }
3446    else
3447      break;
3448  }
3449}
3450
3451struct operation {
3452  operation *next;
3453  short distance;
3454  short num;
3455  operation(int, int, operation *);
3456};
3457
3458operation::operation(int i, int j, operation *op)
3459: next(op), distance(j), num(i)
3460{
3461}
3462
3463void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
3464{
3465  operation *op = 0;
3466  for (int i = 0; i < patlen+1; i++)
3467    if (num[i] != 0)
3468      op = new operation(num[i], patlen - i, op);
3469  insert(pat, patlen, op);
3470}
3471
3472void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
3473				     int patlen)
3474{
3475  char buf[WORD_MAX + 1];
3476  unsigned char pos[WORD_MAX + 2];
3477  int i = 0, j = 0;
3478  int npos = 0;
3479  while (j < patlen) {
3480    unsigned char c = pat[j++];
3481    if (c == '-') {
3482      if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3483	pos[npos++] = i;
3484    }
3485    else
3486      buf[i++] = hpf_code_table[c];
3487  }
3488  if (i > 0) {
3489    pos[npos] = 0;
3490    buf[i] = 0;
3491    unsigned char *tem = new unsigned char[npos + 1];
3492    memcpy(tem, pos, npos + 1);
3493    tem = (unsigned char *)ex->lookup(symbol(buf), tem);
3494    if (tem)
3495      a_delete tem;
3496  }
3497}
3498
3499void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
3500{
3501  int j;
3502  for (j = 0; j < len + 1; j++)
3503    hyphens[j] = 0;
3504  for (j = 0; j < len - 1; j++) {
3505    h = hyphens + j;
3506    find(word + j, len - j);
3507  }
3508}
3509
3510inline int max(int m, int n)
3511{
3512  return m > n ? m : n;
3513}
3514
3515void hyphen_trie::do_match(int i, void *v)
3516{
3517  operation *op = (operation *)v;
3518  while (op != 0) {
3519    h[i - op->distance] = max(h[i - op->distance], op->num);
3520    op = op->next;
3521  }
3522}
3523
3524void hyphen_trie::do_delete(void *v)
3525{
3526  operation *op = (operation *)v;
3527  while (op) {
3528    operation *tem = op;
3529    op = tem->next;
3530    delete tem;
3531  }
3532}
3533
3534/* We use very simple rules to parse TeX's hyphenation patterns.
3535
3536   . `%' starts a comment even if preceded by `\'.
3537
3538   . No support for digraphs and like `\$'.
3539
3540   . `^^xx' (`x' is 0-9 or a-f), and `^^x' (character code of `x' in the
3541     range 0-127) are recognized; other use of `^' causes an error.
3542
3543   . No macro expansion.
3544
3545   . We check for the expression `\patterns{...}' (possibly with
3546     whitespace before and after the braces).  Everything between the
3547     braces is taken as hyphenation patterns.  Consequently, `{' and `}'
3548     are not allowed in patterns.
3549
3550   . Similarly, `\hyphenation{...}' gives a list of hyphenation
3551     exceptions.
3552
3553   . `\endinput' is recognized also.
3554
3555   . For backwards compatibility, if `\patterns' is missing, the
3556     whole file is treated as a list of hyphenation patterns (only
3557     recognizing `%' as the start of a comment.
3558
3559*/
3560
3561int hyphen_trie::hpf_getc(FILE *f)
3562{
3563  int c = getc(f);
3564  int c1;
3565  int cc = 0;
3566  if (c != '^')
3567    return c;
3568  c = getc(f);
3569  if (c != '^')
3570    goto fail;
3571  c = getc(f);
3572  c1 = getc(f);
3573  if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
3574      && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
3575    if (c >= '0' && c <= '9')
3576      c -= '0';
3577    else
3578      c = c - 'a' + 10;
3579    if (c1 >= '0' && c1 <= '9')
3580      c1 -= '0';
3581    else
3582      c1 = c1 - 'a' + 10;
3583    cc = c * 16 + c1;
3584  }
3585  else {
3586    ungetc(c1, f);
3587    if (c >= 0 && c <= 63)
3588      cc = c + 64;
3589    else if (c >= 64 && c <= 127)
3590      cc = c - 64;
3591    else
3592      goto fail;
3593  }
3594  return cc;
3595fail:
3596  error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
3597  return c;
3598}
3599
3600void hyphen_trie::read_patterns_file(const char *name, int append,
3601				     dictionary *ex)
3602{
3603  if (!append)
3604    clear();
3605  char buf[WORD_MAX];
3606  for (int i = 0; i < WORD_MAX; i++)
3607    buf[i] = 0;
3608  int num[WORD_MAX+1];
3609  errno = 0;
3610  char *path = 0;
3611  FILE *fp = mac_path->open_file(name, &path);
3612  if (fp == 0) {
3613    error("can't find hyphenation patterns file `%1'", name);
3614    return;
3615  }
3616  int c = hpf_getc(fp);
3617  int have_patterns = 0;	// we've seen \patterns
3618  int final_pattern = 0;	// 1 if we have a trailing closing brace
3619  int have_hyphenation = 0;	// we've seen \hyphenation
3620  int final_hyphenation = 0;	// 1 if we have a trailing closing brace
3621  int have_keyword = 0;		// we've seen either \patterns or \hyphenation
3622  int traditional = 0;		// don't handle \patterns
3623  for (;;) {
3624    for (;;) {
3625      if (c == '%') {		// skip comments
3626	do {
3627	  c = getc(fp);
3628	} while (c != EOF && c != '\n');
3629      }
3630      if (c == EOF || !csspace(c))
3631	break;
3632      c = hpf_getc(fp);
3633    }
3634    if (c == EOF) {
3635      if (have_keyword || traditional)	// we are done
3636	break;
3637      else {				// rescan file in `traditional' mode
3638	rewind(fp);
3639	traditional = 1;
3640	c = hpf_getc(fp);
3641	continue;
3642      }
3643    }
3644    int i = 0;
3645    num[0] = 0;
3646    if (!(c == '{' || c == '}')) {	// skip braces at line start
3647      do {				// scan patterns
3648	if (csdigit(c))
3649	  num[i] = c - '0';
3650	else {
3651	  buf[i++] = c;
3652	  num[i] = 0;
3653	}
3654	c = hpf_getc(fp);
3655      } while (i < WORD_MAX && c != EOF && !csspace(c)
3656	       && c != '%' && c != '{' && c != '}');
3657    }
3658    if (!traditional) {
3659      if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
3660	while (csspace(c))
3661	  c = hpf_getc(fp);
3662	if (c == '{') {
3663	  if (have_patterns || have_hyphenation)
3664	    error("`{' not allowed inside of \\patterns or \\hyphenation");
3665	  else {
3666	    have_patterns = 1;
3667	    have_keyword = 1;
3668	  }
3669	  c = hpf_getc(fp);
3670	  continue;
3671	}
3672      }
3673      else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
3674	while (csspace(c))
3675	  c = hpf_getc(fp);
3676	if (c == '{') {
3677	  if (have_patterns || have_hyphenation)
3678	    error("`{' not allowed inside of \\patterns or \\hyphenation");
3679	  else {
3680	    have_hyphenation = 1;
3681	    have_keyword = 1;
3682	  }
3683	  c = hpf_getc(fp);
3684	  continue;
3685	}
3686      }
3687      else if (strstr(buf, "\\endinput")) {
3688	if (have_patterns || have_hyphenation)
3689	  error("found \\endinput inside of %1 group",
3690		have_patterns ? "\\patterns" : "\\hyphenation");
3691	break;
3692      }
3693      else if (c == '}') {
3694	if (have_patterns) {
3695	  have_patterns = 0;
3696	  if (i > 0)
3697	    final_pattern = 1;
3698	}
3699	else if (have_hyphenation) {
3700	  have_hyphenation = 0;
3701	  if (i > 0)
3702	    final_hyphenation = 1;
3703	}
3704	c = hpf_getc(fp);
3705      }
3706      else if (c == '{')		// skipped if not starting \patterns
3707	c = hpf_getc(fp);		// or \hyphenation
3708    }
3709    if (i > 0) {
3710      if (have_patterns || final_pattern || traditional) {
3711	for (int j = 0; j < i; j++)
3712	  buf[j] = hpf_code_table[(unsigned char)buf[j]];
3713	insert_pattern(buf, i, num);
3714	final_pattern = 0;
3715      }
3716      else if (have_hyphenation || final_hyphenation) {
3717	insert_hyphenation(ex, buf, i);
3718	final_hyphenation = 0;
3719      }
3720    }
3721  }
3722  fclose(fp);
3723  a_delete path;
3724  return;
3725}
3726
3727void hyphenate(hyphen_list *h, unsigned flags)
3728{
3729  if (!current_language)
3730    return;
3731  while (h) {
3732    while (h && h->hyphenation_code == 0)
3733      h = h->next;
3734    int len = 0;
3735    char hbuf[WORD_MAX+2];
3736    char *buf = hbuf + 1;
3737    hyphen_list *tem;
3738    for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
3739      if (tem->hyphenation_code != 0)
3740	buf[len++] = tem->hyphenation_code;
3741      else
3742	break;
3743    }
3744    hyphen_list *nexth = tem;
3745    if (len > 2) {
3746      buf[len] = 0;
3747      unsigned char *pos
3748	= (unsigned char *)current_language->exceptions.lookup(buf);
3749      if (pos != 0) {
3750	int j = 0;
3751	int i = 1;
3752	for (tem = h; tem != 0; tem = tem->next, i++)
3753	  if (pos[j] == i) {
3754	    tem->hyphen = 1;
3755	    j++;
3756	  }
3757      }
3758      else {
3759	hbuf[0] = hbuf[len+1] = '.';
3760	int num[WORD_MAX+3];
3761	current_language->patterns.hyphenate(hbuf, len+2, num);
3762	int i;
3763	num[2] = 0;
3764	if (flags & 8)
3765	  num[3] = 0;
3766	if (flags & 4)
3767	  --len;
3768	for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
3769	  if (num[i] & 1)
3770	    tem->hyphen = 1;
3771      }
3772    }
3773    h = nexth;
3774  }
3775}
3776
3777static void do_hyphenation_patterns_file(int append)
3778{
3779  symbol name = get_long_name(1);
3780  if (!name.is_null()) {
3781    if (!current_language)
3782      error("no current hyphenation language");
3783    else
3784      current_language->patterns.read_patterns_file(
3785			  name.contents(), append,
3786			  &current_language->exceptions);
3787  }
3788  skip_line();
3789}
3790
3791static void hyphenation_patterns_file()
3792{
3793  do_hyphenation_patterns_file(0);
3794}
3795
3796static void hyphenation_patterns_file_append()
3797{
3798  do_hyphenation_patterns_file(1);
3799}
3800
3801class hyphenation_language_reg : public reg {
3802public:
3803  const char *get_string();
3804};
3805
3806const char *hyphenation_language_reg::get_string()
3807{
3808  return current_language ? current_language->name.contents() : "";
3809}
3810
3811void init_hyphen_requests()
3812{
3813  init_request("hw", hyphen_word);
3814  init_request("hla", set_hyphenation_language);
3815  init_request("hpf", hyphenation_patterns_file);
3816  init_request("hpfa", hyphenation_patterns_file_append);
3817  number_reg_dictionary.define(".hla", new hyphenation_language_reg);
3818}
3819