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