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