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