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