groff before CVS: release 1.06
[s-roff.git] / troff / env.cc
blobe44baad9feacac8b13a800de8052783e04d84e0e
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.com)
5 This file is part of groff.
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 for more details.
17 You should have received a copy of the GNU General Public License along
18 with groff; see the file COPYING. If not, write to the Free Software
19 Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
21 #include "troff.h"
22 #include "symbol.h"
23 #include "dictionary.h"
24 #include "hvunits.h"
25 #include "env.h"
26 #include "request.h"
27 #include "node.h"
28 #include "token.h"
29 #include "div.h"
30 #include "reg.h"
31 #include "charinfo.h"
32 #include <math.h>
34 symbol default_family("T");
36 enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 };
38 enum { HYPHEN_LAST_LINE = 2, HYPHEN_LAST_CHARS = 4, HYPHEN_FIRST_CHARS = 8 };
40 struct env_list {
41 environment *env;
42 env_list *next;
43 env_list(environment *e, env_list *p) : env(e), next(p) {}
46 env_list *env_stack;
47 const int NENVIRONMENTS = 10;
48 environment *env_table[NENVIRONMENTS];
49 dictionary env_dictionary(10);
50 environment *curenv;
51 static int next_line_number = 0;
53 charinfo *field_delimiter_char;
54 charinfo *padding_indicator_char;
56 int translate_space_to_dummy = 0;
58 class pending_output_line {
59 node *nd;
60 int no_fill;
61 vunits vs;
62 int ls;
63 hunits width;
64 #ifdef WIDOW_CONTROL
65 int last_line; // Is it the last line of the paragraph?
66 #endif /* WIDOW_CONTROL */
67 public:
68 pending_output_line *next;
70 pending_output_line(node *, int, vunits, int, hunits,
71 pending_output_line * = 0);
72 ~pending_output_line();
73 int output();
75 #ifdef WIDOW_CONTROL
76 friend void environment::mark_last_line();
77 friend void environment::output(node *, int, vunits, int, hunits);
78 #endif /* WIDOW_CONTROL */
81 pending_output_line::pending_output_line(node *n, int nf, vunits v, int l,
82 hunits w, pending_output_line *p)
83 : nd(n), no_fill(nf), vs(v), ls(l), width(w),
84 #ifdef WIDOW_CONTROL
85 last_line(0),
86 #endif /* WIDOW_CONTROL */
87 next(p)
91 pending_output_line::~pending_output_line()
93 delete_node_list(nd);
96 int pending_output_line::output()
98 if (trap_sprung_flag)
99 return 0;
100 #ifdef WIDOW_CONTROL
101 if (next && next->last_line && !no_fill) {
102 curdiv->need(vs*ls + vunits(vresolution));
103 if (trap_sprung_flag) {
104 next->last_line = 0; // Try to avoid infinite loops.
105 return 0;
108 #endif
109 curdiv->output(nd, no_fill, vs, ls, width);
110 nd = 0;
111 return 1;
114 void environment::output(node *nd, int no_fill, vunits vs, int ls,
115 hunits width)
117 #ifdef WIDOW_CONTROL
118 while (pending_lines) {
119 if (widow_control && !pending_lines->no_fill && !pending_lines->next)
120 break;
121 if (!pending_lines->output())
122 break;
123 pending_output_line *tem = pending_lines;
124 pending_lines = pending_lines->next;
125 delete tem;
127 #else /* WIDOW_CONTROL */
128 output_pending_lines();
129 #endif /* WIDOW_CONTROL */
130 if (!trap_sprung_flag && !pending_lines
131 #ifdef WIDOW_CONTROL
132 && (!widow_control || no_fill)
133 #endif /* WIDOW_CONTROL */
135 curdiv->output(nd, no_fill, vs, ls, width);
136 else {
137 for (pending_output_line **p = &pending_lines; *p; p = &(*p)->next)
139 *p = new pending_output_line(nd, no_fill, vs, ls, width);
143 // a line from .tl goes at the head of the queue
145 void environment::output_title(node *nd, int no_fill, vunits vs, int ls,
146 hunits width)
148 if (!trap_sprung_flag)
149 curdiv->output(nd, no_fill, vs, ls, width);
150 else
151 pending_lines = new pending_output_line(nd, no_fill, vs, ls, width,
152 pending_lines);
155 void environment::output_pending_lines()
157 while (pending_lines && pending_lines->output()) {
158 pending_output_line *tem = pending_lines;
159 pending_lines = pending_lines->next;
160 delete tem;
164 #ifdef WIDOW_CONTROL
166 void environment::mark_last_line()
168 if (!widow_control || !pending_lines)
169 return;
170 for (pending_output_line *p = pending_lines; p->next; p = p->next)
172 if (!p->no_fill)
173 p->last_line = 1;
176 void widow_control_request()
178 if (has_arg()) {
179 int n;
180 if (get_integer(&n))
181 curenv->widow_control = n != 0;
183 else
184 curenv->widow_control = 1;
185 skip_line();
188 #endif /* WIDOW_CONTROL */
190 /* font_size functions */
192 size_range *font_size::size_table = 0;
193 int font_size::nranges = 0;
195 extern "C" {
197 static int compare_ranges(const void *p1, const void *p2)
199 return ((size_range *)p1)->min - ((size_range *)p2)->min;
204 void font_size::init_size_table(int *sizes)
206 nranges = 0;
207 while (sizes[nranges*2] != 0)
208 nranges++;
209 assert(nranges > 0);
210 size_table = new size_range[nranges];
211 for (int i = 0; i < nranges; i++) {
212 size_table[i].min = sizes[i*2];
213 size_table[i].max = sizes[i*2 + 1];
215 qsort(size_table, nranges, sizeof(size_range), compare_ranges);
218 font_size::font_size(int sp)
220 for (int i = 0; i < nranges; i++) {
221 if (sp < size_table[i].min) {
222 if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
223 p = size_table[i - 1].max;
224 else
225 p = size_table[i].min;
226 return;
228 if (sp <= size_table[i].max) {
229 p = sp;
230 return;
233 p = size_table[nranges - 1].max;
236 int font_size::to_units()
238 return scale(p, units_per_inch, sizescale*72);
241 // we can't do this in a static constructor because various dictionaries
242 // have to get initialized first
244 void init_environments()
246 curenv = env_table[0] = new environment("0");
249 void tab_character()
251 curenv->tab_char = get_optional_char();
252 skip_line();
255 void leader_character()
257 curenv->leader_char = get_optional_char();
258 skip_line();
261 void environment::add_char(charinfo *ci)
263 if (interrupted)
265 // don't allow fields in dummy environments
266 else if (ci == field_delimiter_char && !dummy) {
267 if (current_field)
268 wrap_up_field();
269 else
270 start_field();
272 else if (current_field && ci == padding_indicator_char)
273 add_padding();
274 else if (current_tab) {
275 if (tab_contents == 0)
276 tab_contents = new line_start_node;
277 if (ci != hyphen_indicator_char)
278 tab_contents = tab_contents->add_char(ci, this, &tab_width);
279 else
280 tab_contents = tab_contents->add_discretionary_hyphen();
282 else {
283 if (line == 0)
284 start_line();
285 if (ci != hyphen_indicator_char)
286 line = line->add_char(ci, this, &width_total);
287 else
288 line = line->add_discretionary_hyphen();
292 node *environment::make_char_node(charinfo *ci)
294 return make_node(ci, this);
297 void environment::add_node(node *n)
299 assert(n != 0);
300 if (current_tab || current_field)
301 n->freeze_space();
302 if (interrupted) {
303 delete n;
305 else if (current_tab) {
306 n->next = tab_contents;
307 tab_contents = n;
308 tab_width += n->width();
310 else {
311 if (line == 0) {
312 if (discarding && n->discardable()) {
313 // XXX possibly: input_line_start -= n->width();
314 delete n;
315 return;
317 start_line();
319 width_total += n->width();
320 space_total += n->nspaces();
321 n->next = line;
322 line = n;
327 void environment::add_hyphen_indicator()
329 if (current_tab || interrupted || current_field
330 || hyphen_indicator_char != 0)
331 return;
332 if (line == 0)
333 start_line();
334 line = line->add_discretionary_hyphen();
337 int environment::get_hyphenation_flags()
339 return hyphenation_flags;
342 int environment::get_hyphen_line_max()
344 return hyphen_line_max;
347 int environment::get_hyphen_line_count()
349 return hyphen_line_count;
352 int environment::get_center_lines()
354 return center_lines;
357 int environment::get_right_justify_lines()
359 return right_justify_lines;
362 void environment::add_italic_correction()
364 if (current_tab) {
365 if (tab_contents)
366 tab_contents = tab_contents->add_italic_correction(&tab_width);
368 else if (line)
369 line = line->add_italic_correction(&width_total);
372 void environment::space_newline()
374 assert(!current_tab && !current_field);
375 if (interrupted)
376 return;
377 hunits x = H0;
378 if (!translate_space_to_dummy) {
379 x = env_space_width(this);
380 if (node_list_ends_sentence(line) == 1)
381 x += env_sentence_space_width(this);
383 if (line != 0 && line->merge_space(x)) {
384 width_total += x;
385 return;
387 add_node(new word_space_node(x));
388 possibly_break_line(spread_flag);
389 spread_flag = 0;
392 void environment::space()
394 if (interrupted)
395 return;
396 if (current_field && padding_indicator_char == 0) {
397 add_padding();
398 return;
400 hunits x = translate_space_to_dummy ? H0 : env_space_width(this);
401 node *p = current_tab ? tab_contents : line;
402 hunits *tp = current_tab ? &tab_width : &width_total;
403 if (p && p->nspaces() == 1 && p->width() == x
404 && node_list_ends_sentence(p->next) == 1) {
405 hunits xx = translate_space_to_dummy ? H0 : env_sentence_space_width(this);
406 if (p->merge_space(xx)) {
407 *tp += xx;
408 return;
411 if (p && p->merge_space(x)) {
412 *tp += x;
413 return;
415 add_node(new word_space_node(x));
416 possibly_break_line(spread_flag);
417 spread_flag = 0;
420 void environment::set_font(symbol nm)
422 if (interrupted)
423 return;
424 if (nm == symbol("P")) {
425 if (family->make_definite(prev_fontno) < 0)
426 return;
427 int tem = fontno;
428 fontno = prev_fontno;
429 prev_fontno = tem;
431 else {
432 int n = symbol_fontno(nm);
433 if (n < 0) {
434 n = next_available_font_position();
435 if (!mount_font(n, nm))
436 return;
438 if (family->make_definite(n) < 0)
439 return;
440 prev_fontno = fontno;
441 fontno = n;
445 void environment::set_font(int n)
447 if (interrupted)
448 return;
449 if (is_good_fontno(n)) {
450 prev_fontno = fontno;
451 fontno = n;
453 else
454 error("bad font number");
457 void environment::set_family(symbol fam)
459 if (fam.is_null()) {
460 if (prev_family->make_definite(fontno) < 0)
461 return;
462 font_family *tem = family;
463 family = prev_family;
464 prev_family = tem;
466 else {
467 font_family *f = lookup_family(fam);
468 if (f->make_definite(fontno) < 0)
469 return;
470 prev_family = family;
471 family = f;
475 void environment::set_size(int n)
477 if (interrupted)
478 return;
479 if (n == 0) {
480 font_size temp = prev_size;
481 prev_size = size;
482 size = temp;
483 int temp2 = prev_requested_size;
484 prev_requested_size = requested_size;
485 requested_size = temp2;
487 else {
488 prev_size = size;
489 size = font_size(n);
490 prev_requested_size = requested_size;
491 requested_size = n;
495 void environment::set_char_height(int n)
497 if (interrupted)
498 return;
499 if (n == requested_size || n <= 0)
500 char_height = 0;
501 else
502 char_height = n;
505 void environment::set_char_slant(int n)
507 if (interrupted)
508 return;
509 char_slant = n;
512 environment::environment(symbol nm)
513 : name(nm),
514 prev_line_length((units_per_inch*13)/2),
515 line_length((units_per_inch*13)/2),
516 prev_title_length((units_per_inch*13)/2),
517 title_length((units_per_inch*13)/2),
518 prev_size(sizescale*10),
519 size(sizescale*10),
520 requested_size(sizescale*10),
521 prev_requested_size(sizescale*10),
522 char_height(0),
523 char_slant(0),
524 space_size(12),
525 sentence_space_size(12),
526 adjust_mode(ADJUST_BOTH),
527 fill(1),
528 interrupted(0),
529 prev_line_interrupted(0),
530 center_lines(0),
531 right_justify_lines(0),
532 prev_vertical_spacing(points_to_units(12)),
533 vertical_spacing(points_to_units(12)),
534 prev_line_spacing(1),
535 line_spacing(1),
536 prev_indent(0),
537 indent(0),
538 have_temporary_indent(0),
539 temporary_indent(0),
540 underline_lines(0),
541 input_trap_count(0),
542 prev_text_length(0),
543 width_total(0),
544 space_total(0),
545 input_line_start(0),
546 control_char('.'),
547 no_break_control_char('\''),
548 hyphen_indicator_char(0),
549 spread_flag(0),
550 line(0),
551 pending_lines(0),
552 discarding(0),
553 tabs(units_per_inch/2, TAB_LEFT),
554 current_tab(TAB_NONE),
555 current_field(0),
556 margin_character_node(0),
557 margin_character_distance(points_to_units(10)),
558 numbering_nodes(0),
559 number_text_separation(1),
560 line_number_multiple(1),
561 line_number_indent(0),
562 no_number_count(0),
563 tab_char(0),
564 leader_char(charset_table['.']),
565 hyphenation_flags(1),
566 dummy(0),
567 leader_node(0),
568 #ifdef WIDOW_CONTROL
569 widow_control(0),
570 #endif /* WIDOW_CONTROL */
571 hyphen_line_count(0),
572 hyphen_line_max(-1),
573 hyphenation_space(H0),
574 hyphenation_margin(H0),
575 composite(0)
577 prev_family = family = lookup_family(default_family);
578 prev_fontno = fontno = 1;
579 if (!is_good_fontno(1))
580 fatal("font number 1 not a valid font");
581 if (family->make_definite(1) < 0)
582 fatal("invalid default family `%1'", default_family.contents());
583 prev_fontno = fontno;
586 environment::environment(const environment *e)
587 : name(e->name), // so that eg `.if "\n[.ev]"0"' works
588 prev_line_length(e->prev_line_length),
589 line_length(e->line_length),
590 prev_title_length(e->prev_title_length),
591 title_length(e->title_length),
592 prev_size(e->prev_size),
593 size(e->size),
594 prev_requested_size(e->prev_requested_size),
595 requested_size(e->requested_size),
596 char_height(e->char_height),
597 char_slant(e->char_slant),
598 space_size(e->space_size),
599 sentence_space_size(e->sentence_space_size),
600 adjust_mode(e->adjust_mode),
601 fill(e->fill),
602 interrupted(0),
603 prev_line_interrupted(0),
604 center_lines(0),
605 right_justify_lines(0),
606 prev_vertical_spacing(e->prev_vertical_spacing),
607 vertical_spacing(e->vertical_spacing),
608 prev_line_spacing(e->prev_line_spacing),
609 line_spacing(e->line_spacing),
610 prev_indent(e->prev_indent),
611 indent(e->indent),
612 have_temporary_indent(0),
613 temporary_indent(0),
614 underline_lines(0),
615 input_trap_count(0),
616 prev_text_length(e->prev_text_length),
617 width_total(0),
618 space_total(0),
619 input_line_start(0),
620 control_char(e->control_char),
621 no_break_control_char(e->no_break_control_char),
622 hyphen_indicator_char(e->hyphen_indicator_char),
623 spread_flag(0),
624 line(0),
625 pending_lines(0),
626 discarding(0),
627 tabs(e->tabs),
628 current_tab(TAB_NONE),
629 current_field(0),
630 margin_character_node(e->margin_character_node),
631 margin_character_distance(e->margin_character_distance),
632 numbering_nodes(0),
633 number_text_separation(e->number_text_separation),
634 line_number_multiple(e->line_number_multiple),
635 line_number_indent(e->line_number_indent),
636 no_number_count(e->no_number_count),
637 tab_char(e->tab_char),
638 leader_char(e->leader_char),
639 hyphenation_flags(e->hyphenation_flags),
640 fontno(e->fontno),
641 prev_fontno(e->prev_fontno),
642 dummy(1),
643 family(e->family),
644 prev_family(e->prev_family),
645 leader_node(0),
646 #ifdef WIDOW_CONTROL
647 widow_control(e->widow_control),
648 #endif /* WIDOW_CONTROL */
649 hyphen_line_max(e->hyphen_line_max),
650 hyphen_line_count(0),
651 hyphenation_space(e->hyphenation_space),
652 hyphenation_margin(e->hyphenation_margin),
653 composite(0)
657 environment::~environment()
659 delete leader_node;
660 delete_node_list(line);
661 delete_node_list(numbering_nodes);
664 hunits environment::get_input_line_position()
666 hunits n;
667 if (line == 0)
668 n = -input_line_start;
669 else
670 n = width_total - input_line_start;
671 if (current_tab)
672 n += tab_width;
673 return n;
676 void environment::set_input_line_position(hunits n)
678 input_line_start = line == 0 ? -n : width_total - n;
679 if (current_tab)
680 input_line_start += tab_width;
683 hunits environment::get_line_length()
685 return line_length;
688 hunits environment::get_saved_line_length()
690 if (line)
691 return target_text_length + saved_indent;
692 else
693 return line_length;
696 vunits environment::get_vertical_spacing()
698 return vertical_spacing;
701 int environment::get_line_spacing()
703 return line_spacing;
706 int environment::get_bold()
708 return get_bold_fontno(fontno);
711 hunits environment::get_digit_width()
713 return env_digit_width(this);
716 int environment::get_adjust_mode()
718 return adjust_mode;
721 int environment::get_fill()
723 return fill;
726 hunits environment::get_indent()
728 return indent;
731 hunits environment::get_saved_indent()
733 if (line)
734 return saved_indent;
735 else if (have_temporary_indent)
736 return temporary_indent;
737 else
738 return indent;
741 hunits environment::get_temporary_indent()
743 return temporary_indent;
746 hunits environment::get_title_length()
748 return title_length;
751 node *environment::get_prev_char()
753 for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
754 node *last = n->last_char_node();
755 if (last)
756 return last;
758 return 0;
761 hunits environment::get_prev_char_width()
763 node *last = get_prev_char();
764 if (!last)
765 return H0;
766 return last->width();
769 hunits environment::get_prev_char_skew()
771 node *last = get_prev_char();
772 if (!last)
773 return H0;
774 return last->skew();
777 vunits environment::get_prev_char_height()
779 node *last = get_prev_char();
780 if (!last)
781 return V0;
782 vunits min, max;
783 last->vertical_extent(&min, &max);
784 return -min;
787 vunits environment::get_prev_char_depth()
789 node *last = get_prev_char();
790 if (!last)
791 return V0;
792 vunits min, max;
793 last->vertical_extent(&min, &max);
794 return max;
797 hunits environment::get_text_length()
799 hunits n = line == 0 ? H0 : width_total;
800 if (current_tab)
801 n += tab_width;
802 return n;
805 hunits environment::get_prev_text_length()
807 return prev_text_length;
811 static int sb_reg_contents = 0;
812 static int st_reg_contents = 0;
813 static int ct_reg_contents = 0;
814 static int rsb_reg_contents = 0;
815 static int rst_reg_contents = 0;
816 static int skw_reg_contents = 0;
817 static int ssc_reg_contents = 0;
819 void environment::width_registers()
821 // this is used to implement \w; it sets the st, sb, ct registers
822 vunits min = 0, max = 0, cur = 0;
823 int character_type = 0;
824 ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
825 skw_reg_contents = line ? line->skew().to_units() : 0;
826 line = reverse_node_list(line);
827 vunits real_min = V0;
828 vunits real_max = V0;
829 vunits v1, v2;
830 for (node *tem = line; tem; tem = tem->next) {
831 tem->vertical_extent(&v1, &v2);
832 v1 += cur;
833 if (v1 < real_min)
834 real_min = v1;
835 v2 += cur;
836 if (v2 > real_max)
837 real_max = v2;
838 if ((cur += tem->vertical_width()) < min)
839 min = cur;
840 else if (cur > max)
841 max = cur;
842 character_type |= tem->character_type();
844 line = reverse_node_list(line);
845 st_reg_contents = -min.to_units();
846 sb_reg_contents = -max.to_units();
847 rst_reg_contents = -real_min.to_units();
848 rsb_reg_contents = -real_max.to_units();
849 ct_reg_contents = character_type;
852 node *environment::extract_output_line()
854 if (current_tab)
855 wrap_up_tab();
856 node *n = line;
857 line = 0;
858 return n;
861 /* environment related requests */
863 void environment_switch()
865 int pop = 0; // 1 means pop, 2 means pop but no error message on underflow
866 if (curenv->is_dummy())
867 error("can't switch environments when current environment is dummy");
868 else if (!has_arg())
869 pop = 1;
870 else {
871 symbol nm;
872 if (!tok.delimiter()) {
873 // It looks like a number.
874 int n;
875 if (get_integer(&n)) {
876 if (n >= 0 && n < NENVIRONMENTS) {
877 env_stack = new env_list(curenv, env_stack);
878 if (env_table[n] == 0)
879 env_table[n] = new environment(itoa(n));
880 curenv = env_table[n];
882 else
883 nm = itoa(n);
885 else
886 pop = 2;
888 else {
889 nm = get_long_name(1);
890 if (nm.is_null())
891 pop = 2;
893 if (!nm.is_null()) {
894 environment *e = (environment *)env_dictionary.lookup(nm);
895 if (!e) {
896 e = new environment(nm);
897 (void)env_dictionary.lookup(nm, e);
899 env_stack = new env_list(curenv, env_stack);
900 curenv = e;
903 if (pop) {
904 if (env_stack == 0) {
905 if (pop == 1)
906 error("environment stack underflow");
908 else {
909 curenv = env_stack->env;
910 env_list *tem = env_stack;
911 env_stack = env_stack->next;
912 delete tem;
915 skip_line();
919 static symbol P_symbol("P");
921 void font_change()
923 symbol s = get_name();
924 int is_number = 1;
925 if (s.is_null() || s == P_symbol) {
926 s = P_symbol;
927 is_number = 0;
929 else {
930 for (const char *p = s.contents(); p != 0 && *p != 0; p++)
931 if (!csdigit(*p)) {
932 is_number = 0;
933 break;
936 if (is_number)
937 curenv->set_font(atoi(s.contents()));
938 else
939 curenv->set_font(s);
940 skip_line();
943 void family_change()
945 symbol s = get_name(1);
946 if (!s.is_null())
947 curenv->set_family(s);
948 skip_line();
951 #if 0
952 void point_size()
954 int n;
955 if (has_arg()) {
956 if (get_number(&n, 0, curenv->get_requested_point_size()/sizescale)) {
957 n *= sizescale;
958 if (n <= 0)
959 n = 1;
960 curenv->set_size(n);
963 else
964 curenv->set_size(0);
965 skip_line();
967 #endif
969 void point_size()
971 int n;
972 if (has_arg()) {
973 if (get_number(&n, 'z', curenv->get_requested_point_size())) {
974 if (n <= 0)
975 n = 1;
976 curenv->set_size(n);
979 else
980 curenv->set_size(0);
981 skip_line();
984 void space_size()
986 int n;
987 if (get_integer(&n)) {
988 curenv->space_size = n;
989 if (has_arg()) {
990 if (get_integer(&n))
991 curenv->sentence_space_size = n;
993 else
994 curenv->sentence_space_size = curenv->space_size;
996 skip_line();
999 void fill()
1001 while (!tok.newline() && !tok.eof())
1002 tok.next();
1003 if (break_flag)
1004 curenv->do_break();
1005 curenv->fill = 1;
1006 tok.next();
1009 void no_fill()
1011 while (!tok.newline() && !tok.eof())
1012 tok.next();
1013 if (break_flag)
1014 curenv->do_break();
1015 curenv->fill = 0;
1016 tok.next();
1019 void center()
1021 int n;
1022 if (!has_arg() || !get_integer(&n))
1023 n = 1;
1024 while (!tok.newline() && !tok.eof())
1025 tok.next();
1026 if (break_flag)
1027 curenv->do_break();
1028 curenv->right_justify_lines = 0;
1029 curenv->center_lines = n;
1030 tok.next();
1033 void right_justify()
1035 int n;
1036 if (!has_arg() || !get_integer(&n))
1037 n = 1;
1038 while (!tok.newline() && !tok.eof())
1039 tok.next();
1040 if (break_flag)
1041 curenv->do_break();
1042 curenv->center_lines = 0;
1043 curenv->right_justify_lines = n;
1044 tok.next();
1047 void line_length()
1049 hunits temp;
1050 if (!has_arg()) {
1051 hunits temp = curenv->line_length;
1052 curenv->line_length = curenv->prev_line_length;
1053 curenv->prev_line_length = temp;
1055 else if (get_hunits(&temp, 'm', curenv->line_length)) {
1056 if (temp < H0) {
1057 warning(WARN_RANGE, "bad line length %1u", temp.to_units());
1058 temp = H0;
1060 curenv->prev_line_length = curenv->line_length;
1061 curenv->line_length = temp;
1063 skip_line();
1066 void title_length()
1068 hunits temp;
1069 if (!has_arg()) {
1070 hunits temp = curenv->title_length;
1071 curenv->title_length = curenv->prev_title_length;
1072 curenv->prev_title_length = temp;
1074 else if (get_hunits(&temp, 'm', curenv->title_length)) {
1075 if (temp < H0) {
1076 warning(WARN_RANGE, "bad title length %1u", temp.to_units());
1077 temp = H0;
1079 curenv->prev_title_length = curenv->title_length;
1080 curenv->title_length = temp;
1082 skip_line();
1085 void vertical_spacing()
1087 if (!has_arg()) {
1088 vunits temp = curenv->vertical_spacing;
1089 curenv->vertical_spacing = curenv->prev_vertical_spacing;
1090 curenv->prev_vertical_spacing = temp;
1092 else {
1093 vunits temp;
1094 if (get_vunits(&temp, 'p', curenv->vertical_spacing)) {
1095 if (temp <= V0) {
1096 warning(WARN_RANGE, "vertical spacing must be greater than 0");
1097 temp = vresolution;
1099 curenv->prev_vertical_spacing = curenv->vertical_spacing;
1100 curenv->vertical_spacing = temp;
1103 skip_line();
1106 void line_spacing()
1108 int temp;
1109 if (!has_arg()) {
1110 temp = curenv->line_spacing;
1111 curenv->line_spacing = curenv->prev_line_spacing;
1112 curenv->prev_line_spacing = temp;
1114 else if (get_integer(&temp)) {
1115 if (temp < 1) {
1116 warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
1117 temp = 1;
1119 curenv->prev_line_spacing = curenv->line_spacing;
1120 curenv->line_spacing = temp;
1122 skip_line();
1125 void indent()
1127 hunits temp;
1128 int err = 0;
1129 if (!has_arg())
1130 temp = curenv->prev_indent;
1131 else if (!get_hunits(&temp, 'm', curenv->indent))
1132 err = 1;
1133 while (!tok.newline() && !tok.eof())
1134 tok.next();
1135 if (break_flag)
1136 curenv->do_break();
1137 if (temp < H0) {
1138 warning(WARN_RANGE, "indent cannot be negative");
1139 temp = H0;
1141 if (!err) {
1142 curenv->have_temporary_indent = 0;
1143 curenv->prev_indent = curenv->indent;
1144 curenv->indent = temp;
1146 tok.next();
1149 void temporary_indent()
1151 int err = 0;
1152 hunits temp;
1153 if (!get_hunits(&temp, 'm', curenv->get_indent()))
1154 err = 1;
1155 while (!tok.newline() && !tok.eof())
1156 tok.next();
1157 if (break_flag)
1158 curenv->do_break();
1159 if (temp < H0) {
1160 warning(WARN_RANGE, "total indent cannot be negative");
1161 temp = H0;
1163 if (!err) {
1164 curenv->temporary_indent = temp;
1165 curenv->have_temporary_indent = 1;
1167 tok.next();
1170 void underline()
1172 int n = 0;
1173 if (!has_arg())
1174 n = 1;
1175 else if (!get_integer(&n))
1176 n = 0;
1177 if (n <= 0) {
1178 if (curenv->underline_lines > 0) {
1179 curenv->prev_fontno = curenv->fontno;
1180 curenv->fontno = curenv->pre_underline_fontno;
1182 curenv->underline_lines = 0;
1184 else {
1185 curenv->underline_lines = n;
1186 curenv->pre_underline_fontno = curenv->fontno;
1187 curenv->fontno = get_underline_fontno();
1189 skip_line();
1193 void control_char()
1195 if (!has_arg())
1196 curenv->control_char = '.';
1197 else if (tok.ch() == 0)
1198 error("bad control character");
1199 else
1200 curenv->control_char = tok.ch();
1201 skip_line();
1204 void no_break_control_char()
1206 if (!has_arg())
1207 curenv->no_break_control_char = '\'';
1208 else if (tok.ch() == 0)
1209 error("bad control character");
1210 else
1211 curenv->no_break_control_char = tok.ch();
1212 skip_line();
1216 void margin_character()
1218 if (curenv->margin_character_node) {
1219 delete curenv->margin_character_node;
1220 curenv->margin_character_node = 0;
1222 charinfo *ci = get_optional_char();
1223 if (ci) {
1224 curenv->margin_character_node = curenv->make_char_node(ci);
1225 hunits d;
1226 if (curenv->margin_character_node && has_arg() && get_hunits(&d, 'm'))
1227 curenv->margin_character_distance = d;
1229 skip_line();
1232 void number_lines()
1234 delete_node_list(curenv->numbering_nodes);
1235 curenv->numbering_nodes = 0;
1236 int n;
1237 if (has_arg() && get_integer(&n, next_line_number)) {
1238 next_line_number = n;
1239 if (next_line_number < 0) {
1240 warning(WARN_RANGE, "negative line number");
1241 next_line_number = 0;
1243 node *nd = 0;
1244 for (int i = '9'; i >= '0'; i--) {
1245 node *tem = make_node(charset_table[i], curenv);
1246 if (!tem) {
1247 skip_line();
1248 return;
1250 tem->next = nd;
1251 nd = tem;
1253 curenv->numbering_nodes = nd;
1254 curenv->line_number_digit_width = env_digit_width(curenv);
1255 if (has_arg()) {
1256 if (!tok.delimiter()) {
1257 if (get_integer(&n)) {
1258 if (n <= 0) {
1259 warning(WARN_RANGE, "negative or zero line number multiple");
1261 else
1262 curenv->line_number_multiple = n;
1265 else
1266 while (!tok.space() && !tok.newline() && !tok.eof())
1267 tok.next();
1268 if (has_arg()) {
1269 if (!tok.delimiter()) {
1270 if (get_integer(&n))
1271 curenv->number_text_separation = n;
1273 else
1274 while (!tok.space() && !tok.newline() && !tok.eof())
1275 tok.next();
1276 if (has_arg() && !tok.delimiter() && get_integer(&n))
1277 curenv->line_number_indent = n;
1281 skip_line();
1284 void no_number()
1286 if (has_arg()) {
1287 int n;
1288 if (get_integer(&n))
1289 curenv->no_number_count = n > 0 ? n : 0;
1291 else
1292 curenv->no_number_count = 1;
1293 skip_line();
1296 void no_hyphenate()
1298 curenv->hyphenation_flags = 0;
1299 skip_line();
1302 void hyphenate_request()
1304 int n;
1305 if (has_arg()) {
1306 if (get_integer(&n))
1307 curenv->hyphenation_flags = n;
1309 else
1310 curenv->hyphenation_flags = 1;
1311 skip_line();
1314 void hyphen_char()
1316 curenv->hyphen_indicator_char = get_optional_char();
1317 skip_line();
1320 void hyphen_line_max_request()
1322 if (has_arg()) {
1323 int n;
1324 if (get_integer(&n))
1325 curenv->hyphen_line_max = n;
1327 else
1328 curenv->hyphen_line_max = -1;
1329 skip_line();
1332 void environment::interrupt()
1334 if (!dummy) {
1335 add_node(new transparent_dummy_node);
1336 interrupted = 1;
1340 void environment::newline()
1342 if (underline_lines > 0) {
1343 if (--underline_lines == 0) {
1344 prev_fontno = fontno;
1345 fontno = pre_underline_fontno;
1348 if (current_field)
1349 wrap_up_field();
1350 if (current_tab)
1351 wrap_up_tab();
1352 // strip trailing spaces
1353 while (line != 0 && line->discardable()) {
1354 width_total -= line->width();
1355 space_total -= line->nspaces();
1356 node *tem = line;
1357 line = line->next;
1358 delete tem;
1360 node *to_be_output = 0;
1361 hunits to_be_output_width;
1362 prev_line_interrupted = 0;
1363 if (dummy)
1364 space_newline();
1365 else if (interrupted) {
1366 interrupted = 0;
1367 // see environment::final_break
1368 prev_line_interrupted = exit_started ? 2 : 1;
1370 else if (center_lines > 0) {
1371 --center_lines;
1372 hunits x = target_text_length - width_total;
1373 if (x > H0)
1374 saved_indent += x/2;
1375 to_be_output = line;
1376 to_be_output_width = width_total;
1377 line = 0;
1379 else if (right_justify_lines > 0) {
1380 --right_justify_lines;
1381 hunits x = target_text_length - width_total;
1382 if (x > H0)
1383 saved_indent += x;
1384 to_be_output = line;
1385 to_be_output_width = width_total;
1386 line = 0;
1388 else if (fill)
1389 space_newline();
1390 else {
1391 to_be_output = line;
1392 to_be_output_width = width_total;
1393 line = 0;
1395 input_line_start = line == 0 ? H0 : width_total;
1396 if (to_be_output) {
1397 output_line(to_be_output, to_be_output_width);
1398 hyphen_line_count = 0;
1400 if (input_trap_count > 0) {
1401 if (--input_trap_count == 0)
1402 spring_trap(input_trap);
1406 void environment::output_line(node *n, hunits width)
1408 prev_text_length = width;
1409 if (margin_character_node) {
1410 hunits d = line_length + margin_character_distance - saved_indent - width;
1411 if (d > 0) {
1412 n = new hmotion_node(d, n);
1413 width += d;
1415 node *tem = margin_character_node->copy();
1416 tem->next = n;
1417 n = tem;
1418 width += tem->width();
1420 node *nn = 0;
1421 while (n != 0) {
1422 node *tem = n->next;
1423 n->next = nn;
1424 nn = n;
1425 n = tem;
1427 if (!saved_indent.is_zero())
1428 nn = new hmotion_node(saved_indent, nn);
1429 width += saved_indent;
1430 if (no_number_count > 0)
1431 --no_number_count;
1432 else if (numbering_nodes) {
1433 hunits w = (line_number_digit_width
1434 *(3+line_number_indent+number_text_separation));
1435 if (next_line_number % line_number_multiple != 0)
1436 nn = new hmotion_node(w, nn);
1437 else {
1438 hunits x = w;
1439 nn = new hmotion_node(number_text_separation*line_number_digit_width,
1440 nn);
1441 x -= number_text_separation*line_number_digit_width;
1442 char buf[30];
1443 sprintf(buf, "%3d", next_line_number);
1444 for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
1445 node *gn = numbering_nodes;
1446 for (int count = *p - '0'; count > 0; count--)
1447 gn = gn->next;
1448 gn = gn->copy();
1449 x -= gn->width();
1450 gn->next = nn;
1451 nn = gn;
1453 nn = new hmotion_node(x, nn);
1455 width += w;
1456 ++next_line_number;
1458 output(nn, !fill, vertical_spacing, line_spacing, width);
1461 void environment::start_line()
1463 assert(line == 0);
1464 discarding = 0;
1465 line = new line_start_node;
1466 if (have_temporary_indent) {
1467 saved_indent = temporary_indent;
1468 have_temporary_indent = 0;
1470 else
1471 saved_indent = indent;
1472 target_text_length = line_length - saved_indent;
1473 width_total = H0;
1474 space_total = 0;
1477 hunits environment::get_hyphenation_space()
1479 return hyphenation_space;
1482 void hyphenation_space_request()
1484 hunits n;
1485 if (get_hunits(&n, 'm')) {
1486 if (n < H0) {
1487 warning(WARN_RANGE, "hyphenation space cannot be negative");
1488 n = H0;
1490 curenv->hyphenation_space = n;
1492 skip_line();
1495 hunits environment::get_hyphenation_margin()
1497 return hyphenation_margin;
1500 void hyphenation_margin_request()
1502 hunits n;
1503 if (get_hunits(&n, 'm')) {
1504 if (n < H0) {
1505 warning(WARN_RANGE, "hyphenation margin cannot be negative");
1506 n = H0;
1508 curenv->hyphenation_margin = n;
1510 skip_line();
1513 breakpoint *environment::choose_breakpoint()
1515 hunits x = width_total;
1516 int s = space_total;
1517 node *n = line;
1518 breakpoint *best_bp = 0; // the best breakpoint so far
1519 int best_bp_fits = 0;
1520 while (n != 0) {
1521 x -= n->width();
1522 s -= n->nspaces();
1523 breakpoint *bp = n->get_breakpoints(x, s);
1524 while (bp != 0) {
1525 if (bp->width <= target_text_length) {
1526 if (!bp->hyphenated) {
1527 breakpoint *tem = bp->next;
1528 bp->next = 0;
1529 while (tem != 0) {
1530 breakpoint *tem1 = tem;
1531 tem = tem->next;
1532 delete tem1;
1534 if (best_bp_fits
1535 // Decide whether to use the hyphenated breakpoint.
1536 && (hyphen_line_max < 0
1537 // Only choose the hyphenated breakpoint if it would not
1538 // exceed the maximum number of consecutive hyphenated
1539 // lines.
1540 || hyphen_line_count + 1 <= hyphen_line_max)
1541 && !(adjust_mode == ADJUST_BOTH
1542 // Don't choose the hyphenated breakpoint if the line
1543 // can be justified by adding no more than
1544 // hyphenation_space to any word space.
1545 ? (bp->nspaces > 0
1546 && (((target_text_length - bp->width
1547 + (bp->nspaces - 1)*hresolution)/bp->nspaces)
1548 <= hyphenation_space))
1549 // Don't choose the hyphenated breakpoint if the line
1550 // is no more than hyphenation_margin short.
1551 : target_text_length - bp->width <= hyphenation_margin)) {
1552 delete bp;
1553 return best_bp;
1555 if (best_bp)
1556 delete best_bp;
1557 return bp;
1559 else {
1560 if ((adjust_mode == ADJUST_BOTH
1561 ? hyphenation_space == H0
1562 : hyphenation_margin == H0)
1563 && (hyphen_line_max < 0
1564 || hyphen_line_count + 1 <= hyphen_line_max)) {
1565 // No need to consider a non-hyphenated breakpoint.
1566 if (best_bp)
1567 delete best_bp;
1568 return bp;
1570 // It fits but it's hyphenated.
1571 if (!best_bp_fits) {
1572 if (best_bp)
1573 delete best_bp;
1574 best_bp = bp;
1575 bp = bp->next;
1576 best_bp_fits = 1;
1578 else {
1579 breakpoint *tem = bp;
1580 bp = bp->next;
1581 delete tem;
1585 else {
1586 if (best_bp)
1587 delete best_bp;
1588 best_bp = bp;
1589 bp = bp->next;
1592 n = n->next;
1594 if (best_bp) {
1595 if (!best_bp_fits)
1596 warning(WARN_BREAK, "can't break line");
1597 return best_bp;
1599 return 0;
1602 void environment::hyphenate_line()
1604 if (line == 0)
1605 return;
1606 hyphenation_type prev_type = line->get_hyphenation_type();
1607 for (node **startp = &line->next; *startp != 0; startp = &(*startp)->next) {
1608 hyphenation_type this_type = (*startp)->get_hyphenation_type();
1609 if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
1610 break;
1611 prev_type = this_type;
1613 if (*startp == 0)
1614 return;
1615 node *tem = *startp;
1616 int i = 0;
1617 do {
1618 ++i;
1619 tem = tem->next;
1620 } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
1621 int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
1622 node *end = tem;
1623 hyphen_list *sl = 0;
1624 tem = *startp;
1625 node *forward = 0;
1626 while (tem != end) {
1627 sl = tem->get_hyphen_list(sl);
1628 node *tem1 = tem;
1629 tem = tem->next;
1630 tem1->next = forward;
1631 forward = tem1;
1633 if (!inhibit) {
1634 // this is for characters like hyphen and emdash
1635 int prev_code = 0;
1636 for (hyphen_list *h = sl; h; h = h->next) {
1637 h->breakable = (prev_code != 0
1638 && h->next != 0
1639 && h->next->hyphenation_code != 0);
1640 prev_code = h->hyphenation_code;
1643 if (hyphenation_flags != 0
1644 && !inhibit
1645 // this may not be right if we have extra space on this line
1646 && !((hyphenation_flags & HYPHEN_LAST_LINE)
1647 && curdiv->distance_to_next_trap() <= line_spacing*vertical_spacing)
1648 && i >= 4)
1649 hyphenate(sl, hyphenation_flags);
1650 while (forward != 0) {
1651 node *tem1 = forward;
1652 forward = forward->next;
1653 tem1->next = 0;
1654 tem = tem1->add_self(tem, &sl);
1656 *startp = tem;
1659 static node *node_list_reverse(node *n)
1661 node *res = 0;
1662 while (n) {
1663 node *tem = n;
1664 n = n->next;
1665 tem->next = res;
1666 res = tem;
1668 return res;
1671 static void distribute_space(node *n, int nspaces, hunits desired_space,
1672 int force_reverse = 0)
1674 static int reverse = 0;
1675 if (force_reverse || reverse)
1676 n = node_list_reverse(n);
1677 for (node *tem = n; tem; tem = tem->next)
1678 tem->spread_space(&nspaces, &desired_space);
1679 if (force_reverse || reverse)
1680 (void)node_list_reverse(n);
1681 if (!force_reverse)
1682 reverse = !reverse;
1683 assert(desired_space.is_zero() && nspaces == 0);
1686 void environment::possibly_break_line(int forced)
1688 if (!fill || current_tab || current_field || dummy)
1689 return;
1690 while (line != 0 && (forced || width_total > target_text_length)) {
1691 hyphenate_line();
1692 breakpoint *bp = choose_breakpoint();
1693 if (bp == 0)
1694 // we'll find one eventually
1695 return;
1696 node *pre, *post;
1697 node **ndp = &line;
1698 while (*ndp != bp->nd)
1699 ndp = &(*ndp)->next;
1700 bp->nd->split(bp->index, &pre, &post);
1701 *ndp = post;
1702 hunits extra_space_width = H0;
1703 switch(adjust_mode) {
1704 case ADJUST_BOTH:
1705 if (bp->nspaces != 0)
1706 extra_space_width = target_text_length - bp->width;
1707 break;
1708 case ADJUST_CENTER:
1709 saved_indent += (target_text_length - bp->width)/2;
1710 break;
1711 case ADJUST_RIGHT:
1712 saved_indent += target_text_length - bp->width;
1713 break;
1715 distribute_space(pre, bp->nspaces, extra_space_width);
1716 hunits output_width = bp->width + extra_space_width;
1717 input_line_start -= output_width;
1718 if (bp->hyphenated)
1719 hyphen_line_count++;
1720 else
1721 hyphen_line_count = 0;
1722 delete bp;
1723 space_total = 0;
1724 width_total = 0;
1725 node *first_non_discardable = 0;
1726 for (node *tem = line; tem != 0; tem = tem->next)
1727 if (!tem->discardable())
1728 first_non_discardable = tem;
1729 node *to_be_discarded;
1730 if (first_non_discardable) {
1731 to_be_discarded = first_non_discardable->next;
1732 first_non_discardable->next = 0;
1733 for (tem = line; tem != 0; tem = tem->next) {
1734 width_total += tem->width();
1735 space_total += tem->nspaces();
1737 discarding = 0;
1739 else {
1740 discarding = 1;
1741 to_be_discarded = line;
1742 line = 0;
1744 // Do output_line() here so that line will be 0 iff the
1745 // the environment will be empty.
1746 output_line(pre, output_width);
1747 while (to_be_discarded != 0) {
1748 tem = to_be_discarded;
1749 to_be_discarded = to_be_discarded->next;
1750 input_line_start -= tem->width();
1751 delete tem;
1753 if (line != 0) {
1754 if (have_temporary_indent) {
1755 saved_indent = temporary_indent;
1756 have_temporary_indent = 0;
1758 else
1759 saved_indent = indent;
1760 target_text_length = line_length - saved_indent;
1766 Do the break at the end of input after the end macro (if any).
1768 Unix troff behaves as follows: if the last line is
1770 foo bar\c
1772 it will output foo on the current page, and bar on the next page;
1773 if the last line is
1775 foo\c
1779 foo bar
1781 everything will be output on the current page. This behaviour must be
1782 considered a bug.
1784 The problem is that some macro packages rely on this. For example,
1785 the ATK macros have an end macro that emits \c if it needs to print a
1786 table of contents but doesn't do a 'bp in the end macro; instead the
1787 'bp is done in the bottom of page trap. This works with Unix troff,
1788 provided that the current environment is not empty at the end of the
1789 input file.
1791 The following will make macro packages that do that sort of thing work
1792 even if the current environment is empty at the end of the input file.
1793 If the last input line used \c and this line occurred in the end macro,
1794 then we'll force everything out on the current page, but we'll make
1795 sure that the environment isn't empty so that we won't exit at the
1796 bottom of this page.
1799 void environment::final_break()
1801 if (prev_line_interrupted == 2) {
1802 do_break();
1803 add_node(new transparent_dummy_node);
1805 else
1806 do_break();
1809 void environment::do_break()
1811 if (curdiv == topdiv && !topdiv->first_page_begun) {
1812 topdiv->begin_page();
1813 return;
1815 if (current_tab)
1816 wrap_up_tab();
1817 if (line) {
1818 line = new space_node(H0, line); // this is so that hyphenation works
1819 space_total++;
1820 possibly_break_line();
1822 while (line != 0 && line->discardable()) {
1823 width_total -= line->width();
1824 space_total -= line->nspaces();
1825 node *tem = line;
1826 line = line->next;
1827 delete tem;
1829 discarding = 0;
1830 input_line_start = H0;
1831 if (line != 0) {
1832 if (fill) {
1833 switch (adjust_mode) {
1834 case ADJUST_CENTER:
1835 saved_indent += (target_text_length - width_total)/2;
1836 break;
1837 case ADJUST_RIGHT:
1838 saved_indent += target_text_length - width_total;
1839 break;
1842 node *tem = line;
1843 line = 0;
1844 output_line(tem, width_total);
1845 hyphen_line_count = 0;
1847 prev_line_interrupted = 0;
1848 #ifdef WIDOW_CONTROL
1849 mark_last_line();
1850 output_pending_lines();
1851 #endif /* WIDOW_CONTROL */
1854 int environment::is_empty()
1856 return !current_tab && line == 0 && pending_lines == 0;
1859 void break_request()
1861 while (!tok.newline() && !tok.eof())
1862 tok.next();
1863 if (break_flag)
1864 curenv->do_break();
1865 tok.next();
1868 void title()
1870 if (curdiv == topdiv && !topdiv->first_page_begun) {
1871 handle_initial_title();
1872 return;
1874 node *part[3];
1875 hunits part_width[3];
1876 part[0] = part[1] = part[2] = 0;
1877 environment env(curenv);
1878 environment *oldenv = curenv;
1879 curenv = &env;
1880 read_title_parts(part, part_width);
1881 curenv = oldenv;
1882 curenv->size = env.size;
1883 curenv->prev_size = env.prev_size;
1884 curenv->requested_size = env.requested_size;
1885 curenv->prev_requested_size = env.prev_requested_size;
1886 curenv->char_height = env.char_height;
1887 curenv->char_slant = env.char_slant;
1888 curenv->fontno = env.fontno;
1889 curenv->prev_fontno = env.prev_fontno;
1890 node *n = 0;
1891 node *p = part[2];
1892 while (p != 0) {
1893 node *tem = p;
1894 p = p->next;
1895 tem->next = n;
1896 n = tem;
1898 hunits title_length(curenv->title_length);
1899 hunits f = title_length - part_width[1];
1900 hunits f2 = f/2;
1901 n = new hmotion_node(f2 - part_width[2], n);
1902 p = part[1];
1903 while (p != 0) {
1904 node *tem = p;
1905 p = p->next;
1906 tem->next = n;
1907 n = tem;
1909 n = new hmotion_node(f - f2 - part_width[0], n);
1910 p = part[0];
1911 while (p != 0) {
1912 node *tem = p;
1913 p = p->next;
1914 tem->next = n;
1915 n = tem;
1917 curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
1918 curenv->line_spacing, title_length);
1919 curenv->hyphen_line_count = 0;
1920 tok.next();
1923 void adjust()
1925 if (!has_arg())
1926 curenv->adjust_mode |= 1;
1927 else
1928 switch (tok.ch()) {
1929 case 'l':
1930 curenv->adjust_mode = ADJUST_LEFT;
1931 break;
1932 case 'r':
1933 curenv->adjust_mode = ADJUST_RIGHT;
1934 break;
1935 case 'c':
1936 curenv->adjust_mode = ADJUST_CENTER;
1937 break;
1938 case 'b':
1939 case 'n':
1940 curenv->adjust_mode = ADJUST_BOTH;
1941 break;
1942 default:
1943 int n;
1944 if (get_integer(&n)) {
1945 if (0 <= n && n <= 5)
1946 curenv->adjust_mode = n;
1947 else
1948 warning(WARN_RANGE, "adjustment mode `%1' out of range", n);
1951 skip_line();
1954 void no_adjust()
1956 curenv->adjust_mode &= ~1;
1957 skip_line();
1960 void input_trap()
1962 int n;
1963 if (!has_arg())
1964 curenv->input_trap_count = 0;
1965 else if (get_integer(&n)) {
1966 if (n <= 0)
1967 error("number of lines for input trap must be greater than zero");
1968 else {
1969 symbol s = get_name(1);
1970 if (!s.is_null()) {
1971 curenv->input_trap_count = n;
1972 curenv->input_trap = s;
1976 skip_line();
1979 /* tabs */
1981 // must not be R or C or L or a legitimate part of a number expression
1982 const char TAB_REPEAT_CHAR = 'T';
1984 struct tab {
1985 tab *next;
1986 hunits pos;
1987 tab_type type;
1988 tab(hunits, tab_type);
1989 enum { BLOCK = 1024 };
1990 static tab *free_list;
1991 void *operator new(size_t);
1992 void operator delete(void *);
1995 tab *tab::free_list = 0;
1997 void *tab::operator new(size_t n)
1999 assert(n == sizeof(tab));
2000 if (!free_list) {
2001 free_list = (tab *)new char[sizeof(tab)*BLOCK];
2002 for (int i = 0; i < BLOCK - 1; i++)
2003 free_list[i].next = free_list + i + 1;
2004 free_list[BLOCK-1].next = 0;
2006 tab *p = free_list;
2007 free_list = (tab *)(free_list->next);
2008 p->next = 0;
2009 return p;
2012 #ifdef __GNUG__
2013 /* cfront can't cope with this. */
2014 inline
2015 #endif
2016 void tab::operator delete(void *p)
2018 if (p) {
2019 ((tab *)p)->next = free_list;
2020 free_list = (tab *)p;
2024 tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2028 tab_stops::tab_stops(hunits distance, tab_type type)
2029 : initial_list(0)
2031 repeated_list = new tab(distance, type);
2034 tab_stops::~tab_stops()
2036 clear();
2039 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
2041 hunits lastpos = 0;
2042 for (tab *tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
2043 lastpos = tem->pos;
2044 if (tem) {
2045 *distance = tem->pos - curpos;
2046 return tem->type;
2048 if (repeated_list == 0)
2049 return TAB_NONE;
2050 hunits base = lastpos;
2051 for (;;) {
2052 for (tem = repeated_list; tem && tem->pos + base <= curpos; tem = tem->next)
2053 lastpos = tem->pos;
2054 if (tem) {
2055 *distance = tem->pos + base - curpos;
2056 return tem->type;
2058 assert(lastpos > 0);
2059 base += lastpos;
2061 return TAB_NONE;
2064 const char *tab_stops::to_string()
2066 static char *buf = 0;
2067 static int buf_size = 0;
2068 // figure out a maximum on the amount of space we can need
2069 int count = 0;
2070 for (tab *p = initial_list; p; p = p->next)
2071 ++count;
2072 for (p = repeated_list; p; p = p->next)
2073 ++count;
2074 // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
2075 int need = count*12 + 3;
2076 if (buf == 0 || need > buf_size) {
2077 if (buf)
2078 a_delete buf;
2079 buf_size = need;
2080 buf = new char[buf_size];
2082 char *ptr = buf;
2083 for (p = initial_list; p; p = p->next) {
2084 strcpy(ptr, itoa(p->pos.to_units()));
2085 ptr = strchr(ptr, '\0');
2086 *ptr++ = 'u';
2087 *ptr = '\0';
2088 switch (p->type) {
2089 case TAB_LEFT:
2090 break;
2091 case TAB_RIGHT:
2092 *ptr++ = 'R';
2093 break;
2094 case TAB_CENTER:
2095 *ptr++ = 'C';
2096 break;
2097 case TAB_NONE:
2098 default:
2099 assert(0);
2102 if (repeated_list)
2103 *ptr++ = TAB_REPEAT_CHAR;
2104 for (p = repeated_list; p; p = p->next) {
2105 strcpy(ptr, itoa(p->pos.to_units()));
2106 ptr = strchr(ptr, '\0');
2107 *ptr++ = 'u';
2108 *ptr = '\0';
2109 switch (p->type) {
2110 case TAB_LEFT:
2111 break;
2112 case TAB_RIGHT:
2113 *ptr++ = 'R';
2114 break;
2115 case TAB_CENTER:
2116 *ptr++ = 'C';
2117 break;
2118 case TAB_NONE:
2119 default:
2120 assert(0);
2123 *ptr++ = '\0';
2124 return buf;
2127 tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2131 tab_stops::tab_stops(const tab_stops &ts)
2132 : initial_list(0), repeated_list(0)
2134 tab **p = &initial_list;
2135 tab *t = ts.initial_list;
2136 while (t) {
2137 *p = new tab(t->pos, t->type);
2138 t = t->next;
2139 p = &(*p)->next;
2141 p = &repeated_list;
2142 t = ts.repeated_list;
2143 while (t) {
2144 *p = new tab(t->pos, t->type);
2145 t = t->next;
2146 p = &(*p)->next;
2150 void tab_stops::clear()
2152 while (initial_list) {
2153 tab *tem = initial_list;
2154 initial_list = initial_list->next;
2155 delete tem;
2157 while (repeated_list) {
2158 tab *tem = repeated_list;
2159 repeated_list = repeated_list->next;
2160 delete tem;
2164 void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
2166 for (tab **p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
2168 *p = new tab(pos, type);
2172 void tab_stops::operator=(const tab_stops &ts)
2174 clear();
2175 tab **p = &initial_list;
2176 tab *t = ts.initial_list;
2177 while (t) {
2178 *p = new tab(t->pos, t->type);
2179 t = t->next;
2180 p = &(*p)->next;
2182 p = &repeated_list;
2183 t = ts.repeated_list;
2184 while (t) {
2185 *p = new tab(t->pos, t->type);
2186 t = t->next;
2187 p = &(*p)->next;
2191 void set_tabs()
2193 hunits pos;
2194 hunits prev_pos = 0;
2195 int first = 1;
2196 int repeated = 0;
2197 tab_stops tabs;
2198 while (has_arg()) {
2199 if (tok.ch() == TAB_REPEAT_CHAR) {
2200 tok.next();
2201 repeated = 1;
2202 prev_pos = 0;
2204 if (!get_hunits(&pos, 'm', prev_pos))
2205 break;
2206 tab_type type = TAB_LEFT;
2207 if (tok.ch() == 'C') {
2208 tok.next();
2209 type = TAB_CENTER;
2211 else if (tok.ch() == 'R') {
2212 tok.next();
2213 type = TAB_RIGHT;
2215 else if (tok.ch() == 'L') {
2216 tok.next();
2218 if (pos <= prev_pos && !first)
2219 warning(WARN_RANGE,
2220 "positions of tab stops must be strictly increasing");
2221 else {
2222 tabs.add_tab(pos, type, repeated);
2223 prev_pos = pos;
2224 first = 0;
2227 curenv->tabs = tabs;
2228 skip_line();
2231 const char *environment::get_tabs()
2233 return tabs.to_string();
2236 #if 0
2237 tab_stops saved_tabs;
2239 void tabs_save()
2241 saved_tabs = curenv->tabs;
2242 skip_line();
2245 void tabs_restore()
2247 curenv->tabs = saved_tabs;
2248 skip_line();
2250 #endif
2252 tab_type environment::distance_to_next_tab(hunits *distance)
2254 return curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
2257 void field_characters()
2259 field_delimiter_char = get_optional_char();
2260 if (field_delimiter_char)
2261 padding_indicator_char = get_optional_char();
2262 else
2263 padding_indicator_char = 0;
2264 skip_line();
2267 void environment::wrap_up_tab()
2269 if (!current_tab)
2270 return;
2271 if (line == 0)
2272 start_line();
2273 hunits tab_amount;
2274 switch (current_tab) {
2275 case TAB_RIGHT:
2276 tab_amount = tab_distance - tab_width;
2277 line = make_tab_node(tab_amount, line);
2278 break;
2279 case TAB_CENTER:
2280 tab_amount = tab_distance - tab_width/2;
2281 line = make_tab_node(tab_amount, line);
2282 break;
2283 case TAB_NONE:
2284 case TAB_LEFT:
2285 default:
2286 assert(0);
2288 width_total += tab_amount;
2289 width_total += tab_width;
2290 if (current_field) {
2291 if (tab_precedes_field) {
2292 pre_field_width += tab_amount;
2293 tab_precedes_field = 0;
2295 field_distance -= tab_amount;
2296 field_spaces += tab_field_spaces;
2298 if (tab_contents != 0) {
2299 for (node *tem = tab_contents; tem->next != 0; tem = tem->next)
2301 tem->next = line;
2302 line = tab_contents;
2304 tab_field_spaces = 0;
2305 tab_contents = 0;
2306 tab_width = H0;
2307 tab_distance = H0;
2308 current_tab = TAB_NONE;
2311 node *environment::make_tab_node(hunits d, node *next)
2313 if (leader_node != 0 && d < 0) {
2314 error("motion generated by leader cannot be negative");
2315 delete leader_node;
2316 leader_node = 0;
2318 if (!leader_node)
2319 return new hmotion_node(d, next);
2320 node *n = new hline_node(d, leader_node, next);
2321 leader_node = 0;
2322 return n;
2325 void environment::handle_tab(int is_leader)
2327 hunits d;
2328 if (current_tab)
2329 wrap_up_tab();
2330 charinfo *ci = is_leader ? leader_char : tab_char;
2331 delete leader_node;
2332 leader_node = ci ? make_char_node(ci) : 0;
2333 tab_type t = distance_to_next_tab(&d);
2334 switch (t) {
2335 case TAB_NONE:
2336 return;
2337 case TAB_LEFT:
2338 add_node(make_tab_node(d));
2339 return;
2340 case TAB_RIGHT:
2341 case TAB_CENTER:
2342 tab_width = 0;
2343 tab_distance = d;
2344 tab_contents = 0;
2345 current_tab = t;
2346 tab_field_spaces = 0;
2347 return;
2348 default:
2349 assert(0);
2353 void environment::start_field()
2355 assert(!current_field);
2356 hunits d;
2357 if (distance_to_next_tab(&d) != TAB_NONE) {
2358 pre_field_width = get_text_length();
2359 field_distance = d;
2360 current_field = 1;
2361 field_spaces = 0;
2362 tab_field_spaces = 0;
2363 for (node *p = line; p; p = p->next)
2364 if (p->nspaces()) {
2365 p->freeze_space();
2366 space_total--;
2368 tab_precedes_field = current_tab != TAB_NONE;
2370 else
2371 error("zero field width");
2374 void environment::wrap_up_field()
2376 if (!current_tab && field_spaces == 0)
2377 add_padding();
2378 hunits padding = field_distance - (get_text_length() - pre_field_width);
2379 if (current_tab && tab_field_spaces != 0) {
2380 hunits tab_padding = scale(padding,
2381 tab_field_spaces,
2382 field_spaces + tab_field_spaces);
2383 padding -= tab_padding;
2384 distribute_space(tab_contents, tab_field_spaces, tab_padding, 1);
2385 tab_field_spaces = 0;
2386 tab_width += tab_padding;
2388 if (field_spaces != 0) {
2389 distribute_space(line, field_spaces, padding, 1);
2390 width_total += padding;
2391 if (current_tab) {
2392 // the start of the tab has been moved to the right by padding, so
2393 tab_distance -= padding;
2394 if (tab_distance <= H0) {
2395 // use the next tab stop instead
2396 current_tab = tabs.distance_to_next_tab(get_input_line_position()
2397 - tab_width,
2398 &tab_distance);
2399 if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
2400 width_total += tab_width;
2401 if (current_tab == TAB_LEFT) {
2402 line = make_tab_node(tab_distance, line);
2403 width_total += tab_distance;
2404 current_tab = TAB_NONE;
2406 if (tab_contents != 0) {
2407 for (node *tem = tab_contents; tem->next != 0; tem = tem->next)
2409 tem->next = line;
2410 line = tab_contents;
2411 tab_contents = 0;
2413 tab_width = H0;
2414 tab_distance = H0;
2419 current_field = 0;
2422 void environment::add_padding()
2424 if (current_tab) {
2425 tab_contents = new space_node(H0, tab_contents);
2426 tab_field_spaces++;
2428 else {
2429 if (line == 0)
2430 start_line();
2431 line = new space_node(H0, line);
2432 field_spaces++;
2436 typedef int (environment::*INT_FUNCP)();
2437 typedef vunits (environment::*VUNITS_FUNCP)();
2438 typedef hunits (environment::*HUNITS_FUNCP)();
2439 typedef const char *(environment::*STRING_FUNCP)();
2441 class int_env_reg : public reg {
2442 INT_FUNCP func;
2443 public:
2444 int_env_reg(INT_FUNCP);
2445 const char *get_string();
2446 int get_value(units *val);
2449 class vunits_env_reg : public reg {
2450 VUNITS_FUNCP func;
2451 public:
2452 vunits_env_reg(VUNITS_FUNCP f);
2453 const char *get_string();
2454 int get_value(units *val);
2458 class hunits_env_reg : public reg {
2459 HUNITS_FUNCP func;
2460 public:
2461 hunits_env_reg(HUNITS_FUNCP f);
2462 const char *get_string();
2463 int get_value(units *val);
2466 class string_env_reg : public reg {
2467 STRING_FUNCP func;
2468 public:
2469 string_env_reg(STRING_FUNCP);
2470 const char *get_string();
2473 int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
2477 int int_env_reg::get_value(units *val)
2479 *val = (curenv->*func)();
2480 return 1;
2483 const char *int_env_reg::get_string()
2485 return itoa((curenv->*func)());
2488 vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
2492 int vunits_env_reg::get_value(units *val)
2494 *val = (curenv->*func)().to_units();
2495 return 1;
2498 const char *vunits_env_reg::get_string()
2500 return itoa((curenv->*func)().to_units());
2503 hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
2507 int hunits_env_reg::get_value(units *val)
2509 *val = (curenv->*func)().to_units();
2510 return 1;
2513 const char *hunits_env_reg::get_string()
2515 return itoa((curenv->*func)().to_units());
2518 string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
2522 const char *string_env_reg::get_string()
2524 return (curenv->*func)();
2527 class horizontal_place_reg : public general_reg {
2528 public:
2529 horizontal_place_reg();
2530 int get_value(units *);
2531 void set_value(units);
2534 horizontal_place_reg::horizontal_place_reg()
2538 int horizontal_place_reg::get_value(units *res)
2540 *res = curenv->get_input_line_position().to_units();
2541 return 1;
2544 void horizontal_place_reg::set_value(units n)
2546 curenv->set_input_line_position(hunits(n));
2549 const char *environment::get_font_family_string()
2551 return family->nm.contents();
2554 const char *environment::get_name_string()
2556 return name.contents();
2559 // Convert a quantity in scaled points to ascii decimal fraction.
2561 const char *sptoa(int sp)
2563 assert(sp > 0);
2564 assert(sizescale > 0);
2565 if (sizescale == 1)
2566 return itoa(sp);
2567 if (sp % sizescale == 0)
2568 return itoa(sp/sizescale);
2569 // See if 1/sizescale is exactly representable as a decimal fraction,
2570 // ie its only prime factors are 2 and 5.
2571 int n = sizescale;
2572 int power2 = 0;
2573 while ((n & 1) == 0) {
2574 n >>= 1;
2575 power2++;
2577 int power5 = 0;
2578 while ((n % 5) == 0) {
2579 n /= 5;
2580 power5++;
2582 if (n == 1) {
2583 int decimal_point = power5 > power2 ? power5 : power2;
2584 if (decimal_point <= 10) {
2585 int factor = 1;
2586 int t;
2587 for (t = decimal_point - power2; --t >= 0;)
2588 factor *= 2;
2589 for (t = decimal_point - power5; --t >= 0;)
2590 factor *= 5;
2591 if (factor == 1 || sp <= INT_MAX/factor)
2592 return iftoa(sp*factor, decimal_point);
2595 double s = double(sp)/double(sizescale);
2596 double factor = 10.0;
2597 double val = s;
2598 int decimal_point = 0;
2599 do {
2600 double v = ceil(s*factor);
2601 if (v > INT_MAX)
2602 break;
2603 val = v;
2604 factor *= 10.0;
2605 } while (++decimal_point < 10);
2606 return iftoa(int(val), decimal_point);
2609 const char *environment::get_point_size_string()
2611 return sptoa(curenv->get_point_size());
2614 const char *environment::get_requested_point_size_string()
2616 return sptoa(curenv->get_requested_point_size());
2619 #define init_int_env_reg(name, func) \
2620 number_reg_dictionary.define(name, new int_env_reg(&environment::func))
2622 #define init_vunits_env_reg(name, func) \
2623 number_reg_dictionary.define(name, new vunits_env_reg(&environment::func))
2625 #define init_hunits_env_reg(name, func) \
2626 number_reg_dictionary.define(name, new hunits_env_reg(&environment::func))
2628 #define init_string_env_reg(name, func) \
2629 number_reg_dictionary.define(name, new string_env_reg(&environment::func))
2631 void init_env_requests()
2633 init_request("it", input_trap);
2634 init_request("ad", adjust);
2635 init_request("na", no_adjust);
2636 init_request("ev", environment_switch);
2637 init_request("lt", title_length);
2638 init_request("ps", point_size);
2639 init_request("ft", font_change);
2640 init_request("fam", family_change);
2641 init_request("ss", space_size);
2642 init_request("fi", fill);
2643 init_request("nf", no_fill);
2644 init_request("ce", center);
2645 init_request("rj", right_justify);
2646 init_request("vs", vertical_spacing);
2647 init_request("ls", line_spacing);
2648 init_request("ll", line_length);
2649 init_request("in", indent);
2650 init_request("ti", temporary_indent);
2651 init_request("ul", underline);
2652 init_request("cu", underline);
2653 init_request("cc", control_char);
2654 init_request("c2", no_break_control_char);
2655 init_request("br", break_request);
2656 init_request("tl", title);
2657 init_request("ta", set_tabs);
2658 init_request("fc", field_characters);
2659 init_request("mc", margin_character);
2660 init_request("nn", no_number);
2661 init_request("nm", number_lines);
2662 init_request("tc", tab_character);
2663 init_request("lc", leader_character);
2664 init_request("hy", hyphenate_request);
2665 init_request("hc", hyphen_char);
2666 init_request("nh", no_hyphenate);
2667 init_request("hlm", hyphen_line_max_request);
2668 #ifdef WIDOW_CONTROL
2669 init_request("wdc", widow_control_request);
2670 #endif /* WIDOW_CONTROL */
2671 #if 0
2672 init_request("tas", tabs_save);
2673 init_request("tar", tabs_restore);
2674 #endif
2675 init_request("hys", hyphenation_space_request);
2676 init_request("hym", hyphenation_margin_request);
2677 init_int_env_reg(".f", get_font);
2678 init_int_env_reg(".b", get_bold);
2679 init_hunits_env_reg(".i", get_indent);
2680 init_hunits_env_reg(".in", get_saved_indent);
2681 init_int_env_reg(".j", get_adjust_mode);
2682 init_hunits_env_reg(".k", get_text_length);
2683 init_hunits_env_reg(".l", get_line_length);
2684 init_hunits_env_reg(".ll", get_saved_line_length);
2685 init_int_env_reg(".L", get_line_spacing);
2686 init_hunits_env_reg(".n", get_prev_text_length);
2687 init_string_env_reg(".s", get_point_size_string);
2688 init_string_env_reg(".sr", get_requested_point_size_string);
2689 init_int_env_reg(".ps", get_point_size);
2690 init_int_env_reg(".psr", get_requested_point_size);
2691 init_int_env_reg(".u", get_fill);
2692 init_vunits_env_reg(".v", get_vertical_spacing);
2693 init_hunits_env_reg(".w", get_prev_char_width);
2694 init_int_env_reg(".ss", get_space_size);
2695 init_int_env_reg(".sss", get_sentence_space_size);
2696 init_string_env_reg(".fam", get_font_family_string);
2697 init_string_env_reg(".ev", get_name_string);
2698 init_int_env_reg(".hy", get_hyphenation_flags);
2699 init_int_env_reg(".hlm", get_hyphen_line_max);
2700 init_int_env_reg(".hlc", get_hyphen_line_count);
2701 init_hunits_env_reg(".lt", get_title_length);
2702 init_string_env_reg(".tabs", get_tabs);
2703 init_hunits_env_reg(".csk", get_prev_char_skew);
2704 init_vunits_env_reg(".cht", get_prev_char_height);
2705 init_vunits_env_reg(".cdp", get_prev_char_depth);
2706 init_int_env_reg(".ce", get_center_lines);
2707 init_int_env_reg(".rj", get_right_justify_lines);
2708 init_hunits_env_reg(".hys", get_hyphenation_space);
2709 init_hunits_env_reg(".hym", get_hyphenation_margin);
2710 number_reg_dictionary.define("ln", new variable_reg(&next_line_number));
2711 number_reg_dictionary.define("ct", new variable_reg(&ct_reg_contents));
2712 number_reg_dictionary.define("sb", new variable_reg(&sb_reg_contents));
2713 number_reg_dictionary.define("st", new variable_reg(&st_reg_contents));
2714 number_reg_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
2715 number_reg_dictionary.define("rst", new variable_reg(&rst_reg_contents));
2716 number_reg_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
2717 number_reg_dictionary.define("skw", new variable_reg(&skw_reg_contents));
2718 number_reg_dictionary.define("hp", new horizontal_place_reg);
2721 // Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
2723 const int WORD_MAX = 1024;
2725 dictionary exception_dictionary(501);
2727 static void hyphen_word()
2729 char buf[WORD_MAX + 1];
2730 unsigned char pos[WORD_MAX + 2];
2731 for (;;) {
2732 tok.skip();
2733 if (tok.newline() || tok.eof())
2734 break;
2735 int i = 0;
2736 int npos = 0;
2737 while (i < WORD_MAX && !tok.space() && !tok.newline() && !tok.eof()) {
2738 charinfo *ci = tok.get_char(1);
2739 if (ci == 0) {
2740 skip_line();
2741 return;
2743 tok.next();
2744 if (ci->get_ascii_code() == '-') {
2745 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
2746 pos[npos++] = i;
2748 else {
2749 int c = ci->get_hyphenation_code();
2750 if (c == 0)
2751 break;
2752 buf[i++] = c;
2755 if (i > 0) {
2756 pos[npos] = 0;
2757 buf[i] = 0;
2758 unsigned char *tem = new unsigned char[npos + 1];
2759 memcpy(tem, pos, npos+1);
2760 tem = (unsigned char *)exception_dictionary.lookup(symbol(buf), tem);
2761 if (tem)
2762 a_delete tem;
2765 skip_line();
2768 struct trie_node;
2770 class trie {
2771 trie_node *tp;
2772 virtual void do_match(int len, void *val) = 0;
2773 public:
2774 trie() : tp(0) {}
2775 void insert(const char *, int, void *);
2776 // find calls do_match for each match it finds
2777 void find(const char *pat, int patlen);
2778 void clear();
2781 struct trie_node {
2782 char c;
2783 trie_node *down;
2784 trie_node *right;
2785 void *val;
2786 trie_node(char, trie_node *);
2787 ~trie_node();
2790 trie_node::trie_node(char ch, trie_node *p)
2791 : c(ch), right(p), down(0), val(0)
2795 trie_node::~trie_node()
2797 if (down)
2798 delete down;
2799 if (right)
2800 delete right;
2801 if (val)
2802 delete val;
2805 void trie::clear()
2807 if (tp) {
2808 delete tp;
2809 tp = 0;
2813 void trie::insert(const char *pat, int patlen, void *val)
2815 trie_node **p = &tp;
2816 assert(patlen > 0 && pat != 0);
2817 for (;;) {
2818 while (*p != 0 && (*p)->c < pat[0])
2819 p = &((*p)->right);
2820 if (*p == 0 || (*p)->c != pat[0])
2821 *p = new trie_node(pat[0], *p);
2822 if (--patlen == 0) {
2823 (*p)->val = val;
2824 break;
2826 ++pat;
2827 p = &((*p)->down);
2831 void trie::find(const char *pat, int patlen)
2833 trie_node *p = tp;
2834 for (int i = 0; p != 0 && i < patlen; i++) {
2835 while (p != 0 && p->c < pat[i])
2836 p = p->right;
2837 if (p != 0 && p->c == pat[i]) {
2838 if (p->val != 0)
2839 do_match(i+1, p->val);
2840 p = p->down;
2842 else
2843 break;
2847 class hyphen_trie : private trie {
2848 int *h;
2849 void do_match(int i, void *v);
2850 void insert_pattern(const char *pat, int patlen, int *num);
2851 public:
2852 hyphen_trie() {}
2853 void hyphenate(const char *word, int len, int *hyphens);
2854 void read_patterns_file(const char *name);
2857 struct operation {
2858 operation *next;
2859 short distance;
2860 short num;
2861 operation(int, int, operation *);
2864 operation::operation(int i, int j, operation *op)
2865 : num(i), distance(j), next(op)
2869 void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
2871 operation *op = 0;
2872 for (int i = 0; i < patlen+1; i++)
2873 if (num[i] != 0)
2874 op = new operation(num[i], patlen - i, op);
2875 insert(pat, patlen, op);
2878 void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
2880 for (int j = 0; j < len+1; j++)
2881 hyphens[j] = 0;
2882 for (j = 0; j < len - 1; j++) {
2883 h = hyphens + j;
2884 find(word + j, len - j);
2888 inline int max(int m, int n)
2890 return m > n ? m : n;
2893 void hyphen_trie::do_match(int i, void *v)
2895 operation *op = (operation *)v;
2896 while (op != 0) {
2897 h[i - op->distance] = max(h[i - op->distance], op->num);
2898 op = op->next;
2903 void hyphen_trie::read_patterns_file(const char *name)
2905 clear();
2906 char buf[WORD_MAX];
2907 int num[WORD_MAX+1];
2908 errno = 0;
2909 FILE *fp = fopen(name, "r");
2910 if (fp == 0)
2911 fatal("can't open hyphenation patterns file `%1': %2",
2912 name, strerror(errno));
2913 int c = getc(fp);
2914 for (;;) {
2915 while (c != EOF && csspace(c))
2916 c = getc(fp);
2917 if (c == EOF)
2918 break;
2919 int i = 0;
2920 num[0] = 0;
2921 do {
2922 if (csdigit(c))
2923 num[i] = c - '0';
2924 else {
2925 buf[i++] = c;
2926 num[i] = 0;
2928 c = getc(fp);
2929 } while (i < WORD_MAX && c != EOF && !csspace(c));
2930 insert_pattern(buf, i, num);
2932 fclose(fp);
2935 hyphen_trie ht;
2937 void hyphenate(hyphen_list *h, unsigned flags)
2939 while (h && h->hyphenation_code == 0)
2940 h = h->next;
2941 int len = 0;
2942 char hbuf[WORD_MAX+2];
2943 char *buf = hbuf + 1;
2944 for (hyphen_list *tem = h; tem && len < WORD_MAX; tem = tem->next) {
2945 if (tem->hyphenation_code != 0)
2946 buf[len++] = tem->hyphenation_code;
2947 else
2948 break;
2950 if (len > 2) {
2951 buf[len] = 0;
2952 unsigned char *pos = (unsigned char *)exception_dictionary.lookup(buf);
2953 if (pos != 0) {
2954 int j = 0;
2955 int i = 1;
2956 for (tem = h; tem != 0; tem = tem->next, i++)
2957 if (pos[j] == i) {
2958 tem->hyphen = 1;
2959 j++;
2962 else {
2963 hbuf[0] = hbuf[len+1] = '.';
2964 int num[WORD_MAX+3];
2965 ht.hyphenate(hbuf, len+2, num);
2966 int i;
2967 num[2] = 0;
2968 if (flags & 8)
2969 num[3] = 0;
2970 if (flags & 4)
2971 --len;
2972 for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
2973 if (num[i] & 1)
2974 tem->hyphen = 1;
2979 void read_hyphen_file(const char *name)
2981 ht.read_patterns_file(name);
2984 void init_hyphen_requests()
2986 init_request("hw", hyphen_word);