Update to groff 1.19.2.
[dragonfly.git] / contrib / groff-1.19 / src / roff / troff / env.cpp
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
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING.  If not, write to the Free Software
20 Foundation, 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
38 symbol default_family("T");
39
40 enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 };
41
42 enum { HYPHEN_LAST_LINE = 2, HYPHEN_LAST_CHARS = 4, HYPHEN_FIRST_CHARS = 8 };
43
44 struct env_list {
45   environment *env;
46   env_list *next;
47   env_list(environment *e, env_list *p) : env(e), next(p) {}
48 };
49
50 env_list *env_stack;
51 const int NENVIRONMENTS = 10;
52 environment *env_table[NENVIRONMENTS];
53 dictionary env_dictionary(10);
54 environment *curenv;
55 static int next_line_number = 0;
56 extern int suppress_push;
57 extern statem *get_diversion_state();
58
59 charinfo *field_delimiter_char;
60 charinfo *padding_indicator_char;
61
62 int translate_space_to_dummy = 0;
63
64 class 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 */
74 public:
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
88 pending_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
99 pending_output_line::~pending_output_line()
100 {
101   delete_node_list(nd);
102 }
103
104 int 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
123 void 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
158 void 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
169 void 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
180 void 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
191 void 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
205 size_range *font_size::size_table = 0;
206 int font_size::nranges = 0;
207
208 extern "C" {
209
210 int compare_ranges(const void *p1, const void *p2)
211 {
212   return ((size_range *)p1)->min - ((size_range *)p2)->min;
213 }
214
215 }
216
217 void 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
231 font_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
249 int 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
257 void init_environments()
258 {
259   curenv = env_table[0] = new environment("0");
260 }
261
262 void tab_character()
263 {
264   curenv->tab_char = get_optional_char();
265   skip_line();
266 }
267
268 void leader_character()
269 {
270   curenv->leader_char = get_optional_char();
271   skip_line();
272 }
273
274 void 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
329 node *environment::make_char_node(charinfo *ci)
330 {
331   return make_node(ci, this);
332 }
333
334 void 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
371 void 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
381 int environment::get_hyphenation_flags()
382 {
383   return hyphenation_flags;
384 }
385
386 int environment::get_hyphen_line_max()
387 {
388   return hyphen_line_max;
389 }
390
391 int environment::get_hyphen_line_count()
392 {
393   return hyphen_line_count;
394 }
395
396 int environment::get_center_lines()
397 {
398   return center_lines;
399 }
400
401 int environment::get_right_justify_lines()
402 {
403   return right_justify_lines;
404 }
405
406 void 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
416 void 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
441 void environment::space()
442 {
443   space(env_space_width(this), env_sentence_space_width(this));
444 }
445
446 void 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
477 node *do_underline_special(int);
478
479 void 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
510 void 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
522 void 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
542 void 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
562 void 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
572 void environment::set_char_slant(int n)
573 {
574   if (interrupted)
575     return;
576   char_slant = n;
577 }
578
579 color *environment::get_prev_glyph_color()
580 {
581   return prev_glyph_color;
582 }
583
584 color *environment::get_glyph_color()
585 {
586   return glyph_color;
587 }
588
589 color *environment::get_prev_fill_color()
590 {
591   return prev_fill_color;
592 }
593
594 color *environment::get_fill_color()
595 {
596   return fill_color;
597 }
598
599 void 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
607 void 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
615 environment::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
703 environment::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
788 void 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
868 environment::~environment()
869 {
870   delete leader_node;
871   delete_node_list(line);
872   delete_node_list(numbering_nodes);
873 }
874
875 hunits 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
887 void 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
894 hunits environment::get_line_length()
895 {
896   return line_length;
897 }
898
899 hunits environment::get_saved_line_length()
900 {
901   if (line)
902     return target_text_length + saved_indent;
903   else
904     return line_length;
905 }
906
907 vunits environment::get_vertical_spacing()
908 {
909   return vertical_spacing;
910 }
911
912 vunits environment::get_post_vertical_spacing()
913 {
914   return post_vertical_spacing;
915 }
916
917 int environment::get_line_spacing()
918 {
919   return line_spacing;
920 }
921
922 vunits 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
930 int environment::get_bold()
931 {
932   return get_bold_fontno(fontno);
933 }
934
935 hunits environment::get_digit_width()
936 {
937   return env_digit_width(this);
938
939
940 int environment::get_adjust_mode()
941 {
942   return adjust_mode;
943 }
944
945 int environment::get_fill()
946 {
947   return fill;
948 }
949
950 hunits environment::get_indent()
951 {
952   return indent;
953 }
954
955 hunits 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
965 hunits environment::get_temporary_indent()
966 {
967   return temporary_indent;
968 }
969
970 hunits environment::get_title_length()
971 {
972   return title_length;
973 }
974
975 node *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
985 hunits environment::get_prev_char_width()
986 {
987   node *last = get_prev_char();
988   if (!last)
989     return H0;
990   return last->width();
991 }
992
993 hunits environment::get_prev_char_skew()
994 {
995   node *last = get_prev_char();
996   if (!last)
997     return H0;
998   return last->skew();
999 }
1000
1001 vunits 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
1011 vunits 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
1021 hunits 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
1029 hunits environment::get_prev_text_length()
1030 {
1031   return prev_text_length;
1032 }
1033
1034
1035 static int sb_reg_contents = 0;
1036 static int st_reg_contents = 0;
1037 static int ct_reg_contents = 0;
1038 static int rsb_reg_contents = 0;
1039 static int rst_reg_contents = 0;
1040 static int skw_reg_contents = 0;
1041 static int ssc_reg_contents = 0;
1042
1043 void 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
1076 node *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
1087 void 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
1148 void 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
1176 void 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
1186 void 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
1196 static symbol P_symbol("P");
1197
1198 void 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
1220 void family_change()
1221 {
1222   symbol s = get_name();
1223   curenv->set_family(s);
1224   skip_line();
1225 }
1226
1227 void 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
1240 void 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
1281 void 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
1294 void 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
1304 void 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
1315 void 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
1332 void 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
1349 void 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
1366 void 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
1382 void 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
1398 void 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
1415 void 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
1431 void 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
1453 void 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
1475 node *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
1483 void 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
1511 void continuous_underline()
1512 {
1513   do_underline(1);
1514 }
1515
1516 void underline()
1517 {
1518   do_underline(0);
1519 }
1520
1521 void 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
1533 void 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
1545 void 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
1576 void 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
1635 void 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
1645 void no_hyphenate()
1646 {
1647   curenv->hyphenation_flags = 0;
1648   skip_line();
1649 }
1650
1651 void 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
1661 void hyphen_char()
1662 {
1663   curenv->hyphen_indicator_char = get_optional_char();
1664   skip_line();
1665 }
1666
1667 void 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
1677 void environment::interrupt()
1678 {
1679   if (!dummy) {
1680     add_node(new transparent_dummy_node);
1681     interrupted = 1;
1682   }
1683 }
1684
1685 void 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
1766 void 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
1829 void 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
1845 hunits environment::get_hyphenation_space()
1846 {
1847   return hyphenation_space;
1848 }
1849
1850 void 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
1863 hunits environment::get_hyphenation_margin()
1864 {
1865   return hyphenation_margin;
1866 }
1867
1868 void 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
1881 breakpoint *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
1977 void 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
2037 static 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
2049 static 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
2072 void 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 /*
2162 Do the break at the end of input after the end macro (if any).
2163
2164 Unix troff behaves as follows:  if the last line is
2165
2166 foo bar\c
2167
2168 it will output foo on the current page, and bar on the next page;
2169 if the last line is
2170
2171 foo\c
2172
2173 or
2174
2175 foo bar
2176
2177 everything will be output on the current page.  This behaviour must be
2178 considered a bug.
2179
2180 The problem is that some macro packages rely on this.  For example,
2181 the ATK macros have an end macro that emits \c if it needs to print a
2182 table 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,
2184 provided that the current environment is not empty at the end of the
2185 input file.
2186
2187 The following will make macro packages that do that sort of thing work
2188 even if the current environment is empty at the end of the input file.
2189 If the last input line used \c and this line occurred in the end macro,
2190 then we'll force everything out on the current page, but we'll make
2191 sure that the environment isn't empty so that we won't exit at the
2192 bottom of this page.
2193 */
2194
2195 void 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
2205 node *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
2226 void 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
2245 statem *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
2275 void 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
2306 void 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
2323 extern int global_diverted_space;
2324
2325 void 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
2377 int environment::is_empty()
2378 {
2379   return !current_tab && line == 0 && pending_lines == 0;
2380 }
2381
2382 void 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
2391 void break_request()
2392 {
2393   do_break_request(0);
2394 }
2395
2396 void break_spread_request()
2397 {
2398   do_break_request(1);
2399 }
2400
2401 void 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
2460 void 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
2495 void no_adjust()
2496 {
2497   curenv->adjust_mode &= ~1;
2498   skip_line();
2499 }
2500
2501 void 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
2522 void input_trap()
2523 {
2524   do_input_trap(0);
2525 }
2526
2527 void 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
2535 const char TAB_REPEAT_CHAR = 'T';
2536
2537 struct 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
2548 tab *tab::free_list = 0;
2549
2550 void *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. */
2567 inline
2568 #endif
2569 void tab::operator delete(void *p)
2570 {
2571   if (p) {
2572     ((tab *)p)->next = free_list;
2573     free_list = (tab *)p;
2574   }
2575 }
2576
2577 tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2578 {
2579 }
2580
2581 tab_stops::tab_stops(hunits distance, tab_type type) 
2582 : initial_list(0)
2583 {
2584   repeated_list = new tab(distance, type);
2585 }
2586
2587 tab_stops::~tab_stops()
2588 {
2589   clear();
2590 }
2591
2592 tab_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
2599 tab_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
2628 const 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
2692 tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2693 {
2694 }
2695
2696 tab_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
2715 void 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
2729 void 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
2738 void 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     
2757 void 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
2798 const char *environment::get_tabs()
2799 {
2800   return tabs.to_string();
2801 }
2802
2803 tab_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
2810 tab_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
2818 void 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
2828 void 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
2838 int environment::get_line_tabs()
2839 {
2840   return line_tabs;
2841 }
2842
2843 void 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
2888 node *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
2902 void 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
2935 void 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
2956 void 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
3005 void 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
3019 typedef int (environment::*INT_FUNCP)();
3020 typedef vunits (environment::*VUNITS_FUNCP)();
3021 typedef hunits (environment::*HUNITS_FUNCP)();
3022 typedef const char *(environment::*STRING_FUNCP)();
3023
3024 class 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
3032 class 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
3041 class 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
3049 class string_env_reg : public reg {
3050   STRING_FUNCP func;
3051 public:
3052   string_env_reg(STRING_FUNCP);
3053   const char *get_string();
3054 };
3055
3056 int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
3057 {
3058 }
3059
3060 int int_env_reg::get_value(units *val)
3061 {
3062   *val = (curenv->*func)();
3063   return 1;
3064 }
3065
3066 const char *int_env_reg::get_string()
3067 {
3068   return i_to_a((curenv->*func)());
3069 }
3070  
3071 vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
3072 {
3073 }
3074
3075 int vunits_env_reg::get_value(units *val)
3076 {
3077   *val = (curenv->*func)().to_units();
3078   return 1;
3079 }
3080
3081 const char *vunits_env_reg::get_string()
3082 {
3083   return i_to_a((curenv->*func)().to_units());
3084 }
3085
3086 hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
3087 {
3088 }
3089
3090 int hunits_env_reg::get_value(units *val)
3091 {
3092   *val = (curenv->*func)().to_units();
3093   return 1;
3094 }
3095
3096 const char *hunits_env_reg::get_string()
3097 {
3098   return i_to_a((curenv->*func)().to_units());
3099 }
3100
3101 string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
3102 {
3103 }
3104
3105 const char *string_env_reg::get_string()
3106 {
3107   return (curenv->*func)();
3108 }
3109
3110 class horizontal_place_reg : public general_reg {
3111 public:
3112   horizontal_place_reg();
3113   int get_value(units *);
3114   void set_value(units);
3115 };
3116
3117 horizontal_place_reg::horizontal_place_reg()
3118 {
3119 }
3120
3121 int horizontal_place_reg::get_value(units *res)
3122 {
3123   *res = curenv->get_input_line_position().to_units();
3124   return 1;
3125 }
3126
3127 void horizontal_place_reg::set_value(units n)
3128 {
3129   curenv->set_input_line_position(hunits(n));
3130 }
3131
3132 const char *environment::get_font_family_string()
3133 {
3134   return family->nm.contents();
3135 }
3136
3137 const char *environment::get_glyph_color_string()
3138 {
3139   return glyph_color->nm.contents();
3140 }
3141
3142 const char *environment::get_fill_color_string()
3143 {
3144   return fill_color->nm.contents();
3145 }
3146
3147 const char *environment::get_font_name_string()
3148 {
3149   symbol f = get_font_name(fontno, this);
3150   return f.contents();
3151 }
3152
3153 const char *environment::get_style_name_string()
3154 {
3155   symbol f = get_style_name(fontno);
3156   return f.contents();
3157 }
3158
3159 const char *environment::get_name_string()
3160 {
3161   return name.contents();
3162 }
3163
3164 // Convert a quantity in scaled points to ascii decimal fraction.
3165
3166 const 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
3214 const char *environment::get_point_size_string()
3215 {
3216   return sptoa(curenv->get_point_size());
3217 }
3218
3219 const 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
3236 void 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
3341 struct trie_node;
3342
3343 class 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 *);
3348 public:
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
3357 class 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);
3364 public:
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
3371 struct 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
3379 dictionary language_dictionary(5);
3380 hyphenation_language *current_language = 0;
3381
3382 static 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
3395 const int WORD_MAX = 256;       // we use unsigned char for offsets in
3396                                 // hyphenation exceptions
3397
3398 static 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
3445 struct trie_node {
3446   char c;
3447   trie_node *down;
3448   trie_node *right;
3449   void *val;
3450   trie_node(char, trie_node *);
3451 };
3452
3453 trie_node::trie_node(char ch, trie_node *p) 
3454 : c(ch), down(0), right(p), val(0)
3455 {
3456 }
3457
3458 trie::~trie()
3459 {
3460   clear();
3461 }
3462
3463 void trie::clear()
3464 {
3465   delete_trie_node(tp);
3466   tp = 0;
3467 }
3468
3469
3470 void 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
3481 void 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
3499 void 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
3515 struct operation {
3516   operation *next;
3517   short distance;
3518   short num;
3519   operation(int, int, operation *);
3520 };
3521
3522 operation::operation(int i, int j, operation *op)
3523 : next(op), distance(j), num(i)
3524 {
3525 }
3526
3527 void 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
3536 void 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
3563 void 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
3574 inline int max(int m, int n)
3575 {
3576   return m > n ? m : n;
3577 }
3578
3579 void 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
3588 void 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
3625 int 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;
3659 fail:
3660   error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
3661   return c;
3662 }
3663
3664 void 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
3802 void 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
3852 static 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
3866 static void hyphenation_patterns_file()
3867 {
3868   do_hyphenation_patterns_file(0);
3869 }
3870
3871 static void hyphenation_patterns_file_append()
3872 {
3873   do_hyphenation_patterns_file(1);
3874 }
3875
3876 class hyphenation_language_reg : public reg {
3877 public:
3878   const char *get_string();
3879 };
3880
3881 const char *hyphenation_language_reg::get_string()
3882 {
3883   return current_language ? current_language->name.contents() : "";
3884 }
3885
3886 void 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 }