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