* All affected files: Update postal address of FSF.
[s-roff.git] / src / roff / troff / env.cpp
blob9de9a46aa507fcbe4f4671e38966389fa5799b2e
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005
3 Free Software Foundation, Inc.
4 Written by James Clark (jjc@jclark.com)
6 This file is part of groff.
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.
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.
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, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
22 #include "troff.h"
23 #include "dictionary.h"
24 #include "hvunits.h"
25 #include "stringclass.h"
26 #include "mtsm.h"
27 #include "env.h"
28 #include "request.h"
29 #include "node.h"
30 #include "token.h"
31 #include "div.h"
32 #include "reg.h"
33 #include "charinfo.h"
34 #include "macropath.h"
35 #include "input.h"
36 #include <math.h>
38 symbol default_family("T");
40 enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 };
42 enum { HYPHEN_LAST_LINE = 2, HYPHEN_LAST_CHARS = 4, HYPHEN_FIRST_CHARS = 8 };
44 struct env_list {
45 environment *env;
46 env_list *next;
47 env_list(environment *e, env_list *p) : env(e), next(p) {}
50 env_list *env_stack;
51 const int NENVIRONMENTS = 10;
52 environment *env_table[NENVIRONMENTS];
53 dictionary env_dictionary(10);
54 environment *curenv;
55 static int next_line_number = 0;
56 extern int suppress_push;
57 extern statem *get_diversion_state();
59 charinfo *field_delimiter_char;
60 charinfo *padding_indicator_char;
62 int translate_space_to_dummy = 0;
64 class pending_output_line {
65 node *nd;
66 int no_fill;
67 int was_centered;
68 vunits vs;
69 vunits post_vs;
70 hunits width;
71 #ifdef WIDOW_CONTROL
72 int last_line; // Is it the last line of the paragraph?
73 #endif /* WIDOW_CONTROL */
74 public:
75 pending_output_line *next;
77 pending_output_line(node *, int, vunits, vunits, hunits, int,
78 pending_output_line * = 0);
79 ~pending_output_line();
80 int output();
82 #ifdef WIDOW_CONTROL
83 friend void environment::mark_last_line();
84 friend void environment::output(node *, int, vunits, vunits, hunits, int);
85 #endif /* WIDOW_CONTROL */
88 pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv,
89 hunits w, int ce,
90 pending_output_line *p)
91 : nd(n), no_fill(nf), was_centered(ce), vs(v), post_vs(pv), width(w),
92 #ifdef WIDOW_CONTROL
93 last_line(0),
94 #endif /* WIDOW_CONTROL */
95 next(p)
99 pending_output_line::~pending_output_line()
101 delete_node_list(nd);
104 int pending_output_line::output()
106 if (trap_sprung_flag)
107 return 0;
108 #ifdef WIDOW_CONTROL
109 if (next && next->last_line && !no_fill) {
110 curdiv->need(vs + post_vs + vunits(vresolution));
111 if (trap_sprung_flag) {
112 next->last_line = 0; // Try to avoid infinite loops.
113 return 0;
116 #endif
117 curenv->construct_format_state(nd, was_centered, !no_fill);
118 curdiv->output(nd, no_fill, vs, post_vs, width);
119 nd = 0;
120 return 1;
123 void environment::output(node *nd, int no_fill_flag,
124 vunits vs, vunits post_vs,
125 hunits width, int was_centered)
127 #ifdef WIDOW_CONTROL
128 while (pending_lines) {
129 if (widow_control && !pending_lines->no_fill && !pending_lines->next)
130 break;
131 if (!pending_lines->output())
132 break;
133 pending_output_line *tem = pending_lines;
134 pending_lines = pending_lines->next;
135 delete tem;
137 #else /* WIDOW_CONTROL */
138 output_pending_lines();
139 #endif /* WIDOW_CONTROL */
140 if (!trap_sprung_flag && !pending_lines
141 #ifdef WIDOW_CONTROL
142 && (!widow_control || no_fill_flag)
143 #endif /* WIDOW_CONTROL */
145 curenv->construct_format_state(nd, was_centered, !no_fill_flag);
146 curdiv->output(nd, no_fill_flag, vs, post_vs, width);
147 } else {
148 pending_output_line **p;
149 for (p = &pending_lines; *p; p = &(*p)->next)
151 *p = new pending_output_line(nd, no_fill_flag, vs, post_vs, width,
152 was_centered);
156 // a line from .tl goes at the head of the queue
158 void environment::output_title(node *nd, int no_fill_flag,
159 vunits vs, vunits post_vs,
160 hunits width)
162 if (!trap_sprung_flag)
163 curdiv->output(nd, no_fill_flag, vs, post_vs, width);
164 else
165 pending_lines = new pending_output_line(nd, no_fill_flag, vs, post_vs,
166 width, 0, pending_lines);
169 void environment::output_pending_lines()
171 while (pending_lines && pending_lines->output()) {
172 pending_output_line *tem = pending_lines;
173 pending_lines = pending_lines->next;
174 delete tem;
178 #ifdef WIDOW_CONTROL
180 void environment::mark_last_line()
182 if (!widow_control || !pending_lines)
183 return;
184 pending_output_line *p;
185 for (p = pending_lines; p->next; p = p->next)
187 if (!p->no_fill)
188 p->last_line = 1;
191 void widow_control_request()
193 int n;
194 if (has_arg() && get_integer(&n))
195 curenv->widow_control = n != 0;
196 else
197 curenv->widow_control = 1;
198 skip_line();
201 #endif /* WIDOW_CONTROL */
203 /* font_size functions */
205 size_range *font_size::size_table = 0;
206 int font_size::nranges = 0;
208 extern "C" {
210 int compare_ranges(const void *p1, const void *p2)
212 return ((size_range *)p1)->min - ((size_range *)p2)->min;
217 void font_size::init_size_table(int *sizes)
219 nranges = 0;
220 while (sizes[nranges*2] != 0)
221 nranges++;
222 assert(nranges > 0);
223 size_table = new size_range[nranges];
224 for (int i = 0; i < nranges; i++) {
225 size_table[i].min = sizes[i*2];
226 size_table[i].max = sizes[i*2 + 1];
228 qsort(size_table, nranges, sizeof(size_range), compare_ranges);
231 font_size::font_size(int sp)
233 for (int i = 0; i < nranges; i++) {
234 if (sp < size_table[i].min) {
235 if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
236 p = size_table[i - 1].max;
237 else
238 p = size_table[i].min;
239 return;
241 if (sp <= size_table[i].max) {
242 p = sp;
243 return;
246 p = size_table[nranges - 1].max;
249 int font_size::to_units()
251 return scale(p, units_per_inch, sizescale*72);
254 // we can't do this in a static constructor because various dictionaries
255 // have to get initialized first
257 void init_environments()
259 curenv = env_table[0] = new environment("0");
262 void tab_character()
264 curenv->tab_char = get_optional_char();
265 skip_line();
268 void leader_character()
270 curenv->leader_char = get_optional_char();
271 skip_line();
274 void environment::add_char(charinfo *ci)
276 int s;
277 node *gc_np = 0;
278 if (interrupted)
280 // don't allow fields in dummy environments
281 else if (ci == field_delimiter_char && !dummy) {
282 if (current_field)
283 wrap_up_field();
284 else
285 start_field();
287 else if (current_field && ci == padding_indicator_char)
288 add_padding();
289 else if (current_tab) {
290 if (tab_contents == 0)
291 tab_contents = new line_start_node;
292 if (ci != hyphen_indicator_char)
293 tab_contents = tab_contents->add_char(ci, this, &tab_width, &s, &gc_np);
294 else
295 tab_contents = tab_contents->add_discretionary_hyphen();
297 else {
298 if (line == 0)
299 start_line();
300 #if 0
301 fprintf(stderr, "current line is\n");
302 line->debug_node_list();
303 #endif
304 if (ci != hyphen_indicator_char)
305 line = line->add_char(ci, this, &width_total, &space_total, &gc_np);
306 else
307 line = line->add_discretionary_hyphen();
309 #if 0
310 fprintf(stderr, "now after we have added character the line is\n");
311 line->debug_node_list();
312 #endif
313 if ((!suppress_push) && gc_np) {
314 if (gc_np && (gc_np->state == 0)) {
315 gc_np->state = construct_state(0);
316 gc_np->push_state = get_diversion_state();
318 else if (line && (line->state == 0)) {
319 line->state = construct_state(0);
320 line->push_state = get_diversion_state();
323 #if 0
324 fprintf(stderr, "now we have possibly added the state the line is\n");
325 line->debug_node_list();
326 #endif
329 node *environment::make_char_node(charinfo *ci)
331 return make_node(ci, this);
334 void environment::add_node(node *n)
336 if (n == 0)
337 return;
338 if (!suppress_push) {
339 if (n->is_special && n->state == NULL)
340 n->state = construct_state(0);
341 n->push_state = get_diversion_state();
344 if (current_tab || current_field)
345 n->freeze_space();
346 if (interrupted) {
347 delete n;
349 else if (current_tab) {
350 n->next = tab_contents;
351 tab_contents = n;
352 tab_width += n->width();
354 else {
355 if (line == 0) {
356 if (discarding && n->discardable()) {
357 // XXX possibly: input_line_start -= n->width();
358 delete n;
359 return;
361 start_line();
363 width_total += n->width();
364 space_total += n->nspaces();
365 n->next = line;
366 line = n;
367 construct_new_line_state(line);
371 void environment::add_hyphen_indicator()
373 if (current_tab || interrupted || current_field
374 || hyphen_indicator_char != 0)
375 return;
376 if (line == 0)
377 start_line();
378 line = line->add_discretionary_hyphen();
381 int environment::get_hyphenation_flags()
383 return hyphenation_flags;
386 int environment::get_hyphen_line_max()
388 return hyphen_line_max;
391 int environment::get_hyphen_line_count()
393 return hyphen_line_count;
396 int environment::get_center_lines()
398 return center_lines;
401 int environment::get_right_justify_lines()
403 return right_justify_lines;
406 void environment::add_italic_correction()
408 if (current_tab) {
409 if (tab_contents)
410 tab_contents = tab_contents->add_italic_correction(&tab_width);
412 else if (line)
413 line = line->add_italic_correction(&width_total);
416 void environment::space_newline()
418 assert(!current_tab && !current_field);
419 if (interrupted)
420 return;
421 hunits x = H0;
422 hunits sw = env_space_width(this);
423 hunits ssw = env_sentence_space_width(this);
424 if (!translate_space_to_dummy) {
425 x = sw;
426 if (node_list_ends_sentence(line) == 1)
427 x += ssw;
429 width_list *w = new width_list(sw, ssw);
430 if (node_list_ends_sentence(line) == 1)
431 w->next = new width_list(sw, ssw);
432 if (line != 0 && line->merge_space(x, sw, ssw)) {
433 width_total += x;
434 return;
436 add_node(new word_space_node(x, get_fill_color(), w));
437 possibly_break_line(0, spread_flag);
438 spread_flag = 0;
441 void environment::space()
443 space(env_space_width(this), env_sentence_space_width(this));
446 void environment::space(hunits space_width, hunits sentence_space_width)
448 if (interrupted)
449 return;
450 if (current_field && padding_indicator_char == 0) {
451 add_padding();
452 return;
454 hunits x = translate_space_to_dummy ? H0 : space_width;
455 node *p = current_tab ? tab_contents : line;
456 hunits *tp = current_tab ? &tab_width : &width_total;
457 if (p && p->nspaces() == 1 && p->width() == x
458 && node_list_ends_sentence(p->next) == 1) {
459 hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
460 if (p->merge_space(xx, space_width, sentence_space_width)) {
461 *tp += xx;
462 return;
465 if (p && p->merge_space(x, space_width, sentence_space_width)) {
466 *tp += x;
467 return;
469 add_node(new word_space_node(x,
470 get_fill_color(),
471 new width_list(space_width,
472 sentence_space_width)));
473 possibly_break_line(0, spread_flag);
474 spread_flag = 0;
477 node *do_underline_special(int);
479 void environment::set_font(symbol nm)
481 if (interrupted)
482 return;
483 if (nm == symbol("P") || nm.is_empty()) {
484 if (family->make_definite(prev_fontno) < 0)
485 return;
486 int tem = fontno;
487 fontno = prev_fontno;
488 prev_fontno = tem;
490 else {
491 prev_fontno = fontno;
492 int n = symbol_fontno(nm);
493 if (n < 0) {
494 n = next_available_font_position();
495 if (!mount_font(n, nm))
496 return;
498 if (family->make_definite(n) < 0)
499 return;
500 fontno = n;
502 if (underline_spaces && fontno != prev_fontno) {
503 if (fontno == get_underline_fontno())
504 add_node(do_underline_special(1));
505 if (prev_fontno == get_underline_fontno())
506 add_node(do_underline_special(0));
510 void environment::set_font(int n)
512 if (interrupted)
513 return;
514 if (is_good_fontno(n)) {
515 prev_fontno = fontno;
516 fontno = n;
518 else
519 warning(WARN_FONT, "bad font number");
522 void environment::set_family(symbol fam)
524 if (interrupted)
525 return;
526 if (fam.is_null() || fam.is_empty()) {
527 if (prev_family->make_definite(fontno) < 0)
528 return;
529 font_family *tem = family;
530 family = prev_family;
531 prev_family = tem;
533 else {
534 font_family *f = lookup_family(fam);
535 if (f->make_definite(fontno) < 0)
536 return;
537 prev_family = family;
538 family = f;
542 void environment::set_size(int n)
544 if (interrupted)
545 return;
546 if (n == 0) {
547 font_size temp = prev_size;
548 prev_size = size;
549 size = temp;
550 int temp2 = prev_requested_size;
551 prev_requested_size = requested_size;
552 requested_size = temp2;
554 else {
555 prev_size = size;
556 size = font_size(n);
557 prev_requested_size = requested_size;
558 requested_size = n;
562 void environment::set_char_height(int n)
564 if (interrupted)
565 return;
566 if (n == requested_size || n <= 0)
567 char_height = 0;
568 else
569 char_height = n;
572 void environment::set_char_slant(int n)
574 if (interrupted)
575 return;
576 char_slant = n;
579 color *environment::get_prev_glyph_color()
581 return prev_glyph_color;
584 color *environment::get_glyph_color()
586 return glyph_color;
589 color *environment::get_prev_fill_color()
591 return prev_fill_color;
594 color *environment::get_fill_color()
596 return fill_color;
599 void environment::set_glyph_color(color *c)
601 if (interrupted)
602 return;
603 curenv->prev_glyph_color = curenv->glyph_color;
604 curenv->glyph_color = c;
607 void environment::set_fill_color(color *c)
609 if (interrupted)
610 return;
611 curenv->prev_fill_color = curenv->fill_color;
612 curenv->fill_color = c;
615 environment::environment(symbol nm)
616 : dummy(0),
617 prev_line_length((units_per_inch*13)/2),
618 line_length((units_per_inch*13)/2),
619 prev_title_length((units_per_inch*13)/2),
620 title_length((units_per_inch*13)/2),
621 prev_size(sizescale*10),
622 size(sizescale*10),
623 requested_size(sizescale*10),
624 prev_requested_size(sizescale*10),
625 char_height(0),
626 char_slant(0),
627 space_size(12),
628 sentence_space_size(12),
629 adjust_mode(ADJUST_BOTH),
630 fill(1),
631 interrupted(0),
632 prev_line_interrupted(0),
633 center_lines(0),
634 right_justify_lines(0),
635 prev_vertical_spacing(points_to_units(12)),
636 vertical_spacing(points_to_units(12)),
637 prev_post_vertical_spacing(0),
638 post_vertical_spacing(0),
639 prev_line_spacing(1),
640 line_spacing(1),
641 prev_indent(0),
642 indent(0),
643 temporary_indent(0),
644 have_temporary_indent(0),
645 underline_lines(0),
646 underline_spaces(0),
647 input_trap_count(0),
648 continued_input_trap(0),
649 line(0),
650 prev_text_length(0),
651 width_total(0),
652 space_total(0),
653 input_line_start(0),
654 line_tabs(0),
655 current_tab(TAB_NONE),
656 leader_node(0),
657 tab_char(0),
658 leader_char(charset_table['.']),
659 current_field(0),
660 discarding(0),
661 spread_flag(0),
662 margin_character_flags(0),
663 margin_character_node(0),
664 margin_character_distance(points_to_units(10)),
665 numbering_nodes(0),
666 number_text_separation(1),
667 line_number_indent(0),
668 line_number_multiple(1),
669 no_number_count(0),
670 hyphenation_flags(1),
671 hyphen_line_count(0),
672 hyphen_line_max(-1),
673 hyphenation_space(H0),
674 hyphenation_margin(H0),
675 composite(0),
676 pending_lines(0),
677 #ifdef WIDOW_CONTROL
678 widow_control(0),
679 #endif /* WIDOW_CONTROL */
680 glyph_color(&default_color),
681 prev_glyph_color(&default_color),
682 fill_color(&default_color),
683 prev_fill_color(&default_color),
684 seen_space(0),
685 seen_eol(0),
686 suppress_next_eol(0),
687 seen_break(0),
688 tabs(units_per_inch/2, TAB_LEFT),
689 name(nm),
690 control_char('.'),
691 no_break_control_char('\''),
692 hyphen_indicator_char(0)
694 prev_family = family = lookup_family(default_family);
695 prev_fontno = fontno = 1;
696 if (!is_good_fontno(1))
697 fatal("font number 1 not a valid font");
698 if (family->make_definite(1) < 0)
699 fatal("invalid default family `%1'", default_family.contents());
700 prev_fontno = fontno;
703 environment::environment(const environment *e)
704 : dummy(1),
705 prev_line_length(e->prev_line_length),
706 line_length(e->line_length),
707 prev_title_length(e->prev_title_length),
708 title_length(e->title_length),
709 prev_size(e->prev_size),
710 size(e->size),
711 requested_size(e->requested_size),
712 prev_requested_size(e->prev_requested_size),
713 char_height(e->char_height),
714 char_slant(e->char_slant),
715 prev_fontno(e->prev_fontno),
716 fontno(e->fontno),
717 prev_family(e->prev_family),
718 family(e->family),
719 space_size(e->space_size),
720 sentence_space_size(e->sentence_space_size),
721 adjust_mode(e->adjust_mode),
722 fill(e->fill),
723 interrupted(0),
724 prev_line_interrupted(0),
725 center_lines(0),
726 right_justify_lines(0),
727 prev_vertical_spacing(e->prev_vertical_spacing),
728 vertical_spacing(e->vertical_spacing),
729 prev_post_vertical_spacing(e->prev_post_vertical_spacing),
730 post_vertical_spacing(e->post_vertical_spacing),
731 prev_line_spacing(e->prev_line_spacing),
732 line_spacing(e->line_spacing),
733 prev_indent(e->prev_indent),
734 indent(e->indent),
735 temporary_indent(0),
736 have_temporary_indent(0),
737 underline_lines(0),
738 underline_spaces(0),
739 input_trap_count(0),
740 continued_input_trap(0),
741 line(0),
742 prev_text_length(e->prev_text_length),
743 width_total(0),
744 space_total(0),
745 input_line_start(0),
746 line_tabs(e->line_tabs),
747 current_tab(TAB_NONE),
748 leader_node(0),
749 tab_char(e->tab_char),
750 leader_char(e->leader_char),
751 current_field(0),
752 discarding(0),
753 spread_flag(0),
754 margin_character_flags(e->margin_character_flags),
755 margin_character_node(e->margin_character_node),
756 margin_character_distance(e->margin_character_distance),
757 numbering_nodes(0),
758 number_text_separation(e->number_text_separation),
759 line_number_indent(e->line_number_indent),
760 line_number_multiple(e->line_number_multiple),
761 no_number_count(e->no_number_count),
762 hyphenation_flags(e->hyphenation_flags),
763 hyphen_line_count(0),
764 hyphen_line_max(e->hyphen_line_max),
765 hyphenation_space(e->hyphenation_space),
766 hyphenation_margin(e->hyphenation_margin),
767 composite(0),
768 pending_lines(0),
769 #ifdef WIDOW_CONTROL
770 widow_control(e->widow_control),
771 #endif /* WIDOW_CONTROL */
772 glyph_color(e->glyph_color),
773 prev_glyph_color(e->prev_glyph_color),
774 fill_color(e->fill_color),
775 prev_fill_color(e->prev_fill_color),
776 seen_space(e->seen_space),
777 seen_eol(e->seen_eol),
778 suppress_next_eol(e->suppress_next_eol),
779 seen_break(e->seen_break),
780 tabs(e->tabs),
781 name(e->name), // so that eg `.if "\n[.ev]"0"' works
782 control_char(e->control_char),
783 no_break_control_char(e->no_break_control_char),
784 hyphen_indicator_char(e->hyphen_indicator_char)
788 void environment::copy(const environment *e)
790 prev_line_length = e->prev_line_length;
791 line_length = e->line_length;
792 prev_title_length = e->prev_title_length;
793 title_length = e->title_length;
794 prev_size = e->prev_size;
795 size = e->size;
796 prev_requested_size = e->prev_requested_size;
797 requested_size = e->requested_size;
798 char_height = e->char_height;
799 char_slant = e->char_slant;
800 space_size = e->space_size;
801 sentence_space_size = e->sentence_space_size;
802 adjust_mode = e->adjust_mode;
803 fill = e->fill;
804 interrupted = 0;
805 prev_line_interrupted = 0;
806 center_lines = 0;
807 right_justify_lines = 0;
808 prev_vertical_spacing = e->prev_vertical_spacing;
809 vertical_spacing = e->vertical_spacing;
810 prev_post_vertical_spacing = e->prev_post_vertical_spacing,
811 post_vertical_spacing = e->post_vertical_spacing,
812 prev_line_spacing = e->prev_line_spacing;
813 line_spacing = e->line_spacing;
814 prev_indent = e->prev_indent;
815 indent = e->indent;
816 have_temporary_indent = 0;
817 temporary_indent = 0;
818 underline_lines = 0;
819 underline_spaces = 0;
820 input_trap_count = 0;
821 continued_input_trap = 0;
822 prev_text_length = e->prev_text_length;
823 width_total = 0;
824 space_total = 0;
825 input_line_start = 0;
826 control_char = e->control_char;
827 no_break_control_char = e->no_break_control_char;
828 hyphen_indicator_char = e->hyphen_indicator_char;
829 spread_flag = 0;
830 line = 0;
831 pending_lines = 0;
832 discarding = 0;
833 tabs = e->tabs;
834 line_tabs = e->line_tabs;
835 current_tab = TAB_NONE;
836 current_field = 0;
837 margin_character_flags = e->margin_character_flags;
838 margin_character_node = e->margin_character_node;
839 margin_character_distance = e->margin_character_distance;
840 numbering_nodes = 0;
841 number_text_separation = e->number_text_separation;
842 line_number_multiple = e->line_number_multiple;
843 line_number_indent = e->line_number_indent;
844 no_number_count = e->no_number_count;
845 tab_char = e->tab_char;
846 leader_char = e->leader_char;
847 hyphenation_flags = e->hyphenation_flags;
848 fontno = e->fontno;
849 prev_fontno = e->prev_fontno;
850 dummy = e->dummy;
851 family = e->family;
852 prev_family = e->prev_family;
853 leader_node = 0;
854 #ifdef WIDOW_CONTROL
855 widow_control = e->widow_control;
856 #endif /* WIDOW_CONTROL */
857 hyphen_line_max = e->hyphen_line_max;
858 hyphen_line_count = 0;
859 hyphenation_space = e->hyphenation_space;
860 hyphenation_margin = e->hyphenation_margin;
861 composite = 0;
862 glyph_color= e->glyph_color;
863 prev_glyph_color = e->prev_glyph_color;
864 fill_color = e->fill_color;
865 prev_fill_color = e->prev_fill_color;
868 environment::~environment()
870 delete leader_node;
871 delete_node_list(line);
872 delete_node_list(numbering_nodes);
875 hunits environment::get_input_line_position()
877 hunits n;
878 if (line == 0)
879 n = -input_line_start;
880 else
881 n = width_total - input_line_start;
882 if (current_tab)
883 n += tab_width;
884 return n;
887 void environment::set_input_line_position(hunits n)
889 input_line_start = line == 0 ? -n : width_total - n;
890 if (current_tab)
891 input_line_start += tab_width;
894 hunits environment::get_line_length()
896 return line_length;
899 hunits environment::get_saved_line_length()
901 if (line)
902 return target_text_length + saved_indent;
903 else
904 return line_length;
907 vunits environment::get_vertical_spacing()
909 return vertical_spacing;
912 vunits environment::get_post_vertical_spacing()
914 return post_vertical_spacing;
917 int environment::get_line_spacing()
919 return line_spacing;
922 vunits environment::total_post_vertical_spacing()
924 vunits tem(post_vertical_spacing);
925 if (line_spacing > 1)
926 tem += (line_spacing - 1)*vertical_spacing;
927 return tem;
930 int environment::get_bold()
932 return get_bold_fontno(fontno);
935 hunits environment::get_digit_width()
937 return env_digit_width(this);
940 int environment::get_adjust_mode()
942 return adjust_mode;
945 int environment::get_fill()
947 return fill;
950 hunits environment::get_indent()
952 return indent;
955 hunits environment::get_saved_indent()
957 if (line)
958 return saved_indent;
959 else if (have_temporary_indent)
960 return temporary_indent;
961 else
962 return indent;
965 hunits environment::get_temporary_indent()
967 return temporary_indent;
970 hunits environment::get_title_length()
972 return title_length;
975 node *environment::get_prev_char()
977 for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
978 node *last = n->last_char_node();
979 if (last)
980 return last;
982 return 0;
985 hunits environment::get_prev_char_width()
987 node *last = get_prev_char();
988 if (!last)
989 return H0;
990 return last->width();
993 hunits environment::get_prev_char_skew()
995 node *last = get_prev_char();
996 if (!last)
997 return H0;
998 return last->skew();
1001 vunits environment::get_prev_char_height()
1003 node *last = get_prev_char();
1004 if (!last)
1005 return V0;
1006 vunits min, max;
1007 last->vertical_extent(&min, &max);
1008 return -min;
1011 vunits environment::get_prev_char_depth()
1013 node *last = get_prev_char();
1014 if (!last)
1015 return V0;
1016 vunits min, max;
1017 last->vertical_extent(&min, &max);
1018 return max;
1021 hunits environment::get_text_length()
1023 hunits n = line == 0 ? H0 : width_total;
1024 if (current_tab)
1025 n += tab_width;
1026 return n;
1029 hunits environment::get_prev_text_length()
1031 return prev_text_length;
1035 static int sb_reg_contents = 0;
1036 static int st_reg_contents = 0;
1037 static int ct_reg_contents = 0;
1038 static int rsb_reg_contents = 0;
1039 static int rst_reg_contents = 0;
1040 static int skw_reg_contents = 0;
1041 static int ssc_reg_contents = 0;
1043 void environment::width_registers()
1045 // this is used to implement \w; it sets the st, sb, ct registers
1046 vunits min = 0, max = 0, cur = 0;
1047 int character_type = 0;
1048 ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
1049 skw_reg_contents = line ? line->skew().to_units() : 0;
1050 line = reverse_node_list(line);
1051 vunits real_min = V0;
1052 vunits real_max = V0;
1053 vunits v1, v2;
1054 for (node *tem = line; tem; tem = tem->next) {
1055 tem->vertical_extent(&v1, &v2);
1056 v1 += cur;
1057 if (v1 < real_min)
1058 real_min = v1;
1059 v2 += cur;
1060 if (v2 > real_max)
1061 real_max = v2;
1062 if ((cur += tem->vertical_width()) < min)
1063 min = cur;
1064 else if (cur > max)
1065 max = cur;
1066 character_type |= tem->character_type();
1068 line = reverse_node_list(line);
1069 st_reg_contents = -min.to_units();
1070 sb_reg_contents = -max.to_units();
1071 rst_reg_contents = -real_min.to_units();
1072 rsb_reg_contents = -real_max.to_units();
1073 ct_reg_contents = character_type;
1076 node *environment::extract_output_line()
1078 if (current_tab)
1079 wrap_up_tab();
1080 node *n = line;
1081 line = 0;
1082 return n;
1085 /* environment related requests */
1087 void environment_switch()
1089 int pop = 0; // 1 means pop, 2 means pop but no error message on underflow
1090 if (curenv->is_dummy())
1091 error("can't switch environments when current environment is dummy");
1092 else if (!has_arg())
1093 pop = 1;
1094 else {
1095 symbol nm;
1096 if (!tok.delimiter()) {
1097 // It looks like a number.
1098 int n;
1099 if (get_integer(&n)) {
1100 if (n >= 0 && n < NENVIRONMENTS) {
1101 env_stack = new env_list(curenv, env_stack);
1102 if (env_table[n] == 0)
1103 env_table[n] = new environment(i_to_a(n));
1104 curenv = env_table[n];
1106 else
1107 nm = i_to_a(n);
1109 else
1110 pop = 2;
1112 else {
1113 nm = get_long_name(1);
1114 if (nm.is_null())
1115 pop = 2;
1117 if (!nm.is_null()) {
1118 environment *e = (environment *)env_dictionary.lookup(nm);
1119 if (!e) {
1120 e = new environment(nm);
1121 (void)env_dictionary.lookup(nm, e);
1123 env_stack = new env_list(curenv, env_stack);
1124 curenv = e;
1127 if (pop) {
1128 if (env_stack == 0) {
1129 if (pop == 1)
1130 error("environment stack underflow");
1132 else {
1133 int seen_space = curenv->seen_space;
1134 int seen_eol = curenv->seen_eol;
1135 int suppress_next_eol = curenv->suppress_next_eol;
1136 curenv = env_stack->env;
1137 curenv->seen_space = seen_space;
1138 curenv->seen_eol = seen_eol;
1139 curenv->suppress_next_eol = suppress_next_eol;
1140 env_list *tem = env_stack;
1141 env_stack = env_stack->next;
1142 delete tem;
1145 skip_line();
1148 void environment_copy()
1150 symbol nm;
1151 environment *e=0;
1152 tok.skip();
1153 if (!tok.delimiter()) {
1154 // It looks like a number.
1155 int n;
1156 if (get_integer(&n)) {
1157 if (n >= 0 && n < NENVIRONMENTS)
1158 e = env_table[n];
1159 else
1160 nm = i_to_a(n);
1163 else
1164 nm = get_long_name(1);
1165 if (!e && !nm.is_null())
1166 e = (environment *)env_dictionary.lookup(nm);
1167 if (e == 0) {
1168 error("No environment to copy from");
1169 return;
1171 else
1172 curenv->copy(e);
1173 skip_line();
1176 void fill_color_change()
1178 symbol s = get_name();
1179 if (s.is_null())
1180 curenv->set_fill_color(curenv->get_prev_fill_color());
1181 else
1182 do_fill_color(s);
1183 skip_line();
1186 void glyph_color_change()
1188 symbol s = get_name();
1189 if (s.is_null())
1190 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1191 else
1192 do_glyph_color(s);
1193 skip_line();
1196 static symbol P_symbol("P");
1198 void font_change()
1200 symbol s = get_name();
1201 int is_number = 1;
1202 if (s.is_null() || s == P_symbol) {
1203 s = P_symbol;
1204 is_number = 0;
1206 else {
1207 for (const char *p = s.contents(); p != 0 && *p != 0; p++)
1208 if (!csdigit(*p)) {
1209 is_number = 0;
1210 break;
1213 if (is_number)
1214 curenv->set_font(atoi(s.contents()));
1215 else
1216 curenv->set_font(s);
1217 skip_line();
1220 void family_change()
1222 symbol s = get_name();
1223 curenv->set_family(s);
1224 skip_line();
1227 void point_size()
1229 int n;
1230 if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) {
1231 if (n <= 0)
1232 n = 1;
1233 curenv->set_size(n);
1235 else
1236 curenv->set_size(0);
1237 skip_line();
1240 void override_sizes()
1242 int n = 16;
1243 int *sizes = new int[n];
1244 int i = 0;
1245 char *buf = read_string();
1246 if (!buf)
1247 return;
1248 char *p = strtok(buf, " \t");
1249 for (;;) {
1250 if (!p)
1251 break;
1252 int lower, upper;
1253 switch (sscanf(p, "%d-%d", &lower, &upper)) {
1254 case 1:
1255 upper = lower;
1256 // fall through
1257 case 2:
1258 if (lower <= upper && lower >= 0)
1259 break;
1260 // fall through
1261 default:
1262 warning(WARN_RANGE, "bad size range `%1'", p);
1263 return;
1265 if (i + 2 > n) {
1266 int *old_sizes = sizes;
1267 sizes = new int[n*2];
1268 memcpy(sizes, old_sizes, n*sizeof(int));
1269 n *= 2;
1270 a_delete old_sizes;
1272 sizes[i++] = lower;
1273 if (lower == 0)
1274 break;
1275 sizes[i++] = upper;
1276 p = strtok(0, " \t");
1278 font_size::init_size_table(sizes);
1281 void space_size()
1283 int n;
1284 if (get_integer(&n)) {
1285 curenv->space_size = n;
1286 if (has_arg() && get_integer(&n))
1287 curenv->sentence_space_size = n;
1288 else
1289 curenv->sentence_space_size = curenv->space_size;
1291 skip_line();
1294 void fill()
1296 while (!tok.newline() && !tok.eof())
1297 tok.next();
1298 if (break_flag)
1299 curenv->do_break();
1300 curenv->fill = 1;
1301 tok.next();
1304 void no_fill()
1306 while (!tok.newline() && !tok.eof())
1307 tok.next();
1308 if (break_flag)
1309 curenv->do_break();
1310 curenv->fill = 0;
1311 curenv->suppress_next_eol = 1;
1312 tok.next();
1315 void center()
1317 int n;
1318 if (!has_arg() || !get_integer(&n))
1319 n = 1;
1320 else if (n < 0)
1321 n = 0;
1322 while (!tok.newline() && !tok.eof())
1323 tok.next();
1324 if (break_flag)
1325 curenv->do_break();
1326 curenv->right_justify_lines = 0;
1327 curenv->center_lines = n;
1328 curdiv->modified_tag.incl(MTSM_CE);
1329 tok.next();
1332 void right_justify()
1334 int n;
1335 if (!has_arg() || !get_integer(&n))
1336 n = 1;
1337 else if (n < 0)
1338 n = 0;
1339 while (!tok.newline() && !tok.eof())
1340 tok.next();
1341 if (break_flag)
1342 curenv->do_break();
1343 curenv->center_lines = 0;
1344 curenv->right_justify_lines = n;
1345 curdiv->modified_tag.incl(MTSM_RJ);
1346 tok.next();
1349 void line_length()
1351 hunits temp;
1352 if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) {
1353 if (temp < H0) {
1354 warning(WARN_RANGE, "bad line length %1u", temp.to_units());
1355 temp = H0;
1358 else
1359 temp = curenv->prev_line_length;
1360 curenv->prev_line_length = curenv->line_length;
1361 curenv->line_length = temp;
1362 curdiv->modified_tag.incl(MTSM_LL);
1363 skip_line();
1366 void title_length()
1368 hunits temp;
1369 if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) {
1370 if (temp < H0) {
1371 warning(WARN_RANGE, "bad title length %1u", temp.to_units());
1372 temp = H0;
1375 else
1376 temp = curenv->prev_title_length;
1377 curenv->prev_title_length = curenv->title_length;
1378 curenv->title_length = temp;
1379 skip_line();
1382 void vertical_spacing()
1384 vunits temp;
1385 if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) {
1386 if (temp < V0) {
1387 warning(WARN_RANGE, "vertical spacing must not be negative");
1388 temp = vresolution;
1391 else
1392 temp = curenv->prev_vertical_spacing;
1393 curenv->prev_vertical_spacing = curenv->vertical_spacing;
1394 curenv->vertical_spacing = temp;
1395 skip_line();
1398 void post_vertical_spacing()
1400 vunits temp;
1401 if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) {
1402 if (temp < V0) {
1403 warning(WARN_RANGE,
1404 "post vertical spacing must be greater than or equal to 0");
1405 temp = V0;
1408 else
1409 temp = curenv->prev_post_vertical_spacing;
1410 curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
1411 curenv->post_vertical_spacing = temp;
1412 skip_line();
1415 void line_spacing()
1417 int temp;
1418 if (has_arg() && get_integer(&temp)) {
1419 if (temp < 1) {
1420 warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
1421 temp = 1;
1424 else
1425 temp = curenv->prev_line_spacing;
1426 curenv->prev_line_spacing = curenv->line_spacing;
1427 curenv->line_spacing = temp;
1428 skip_line();
1431 void indent()
1433 hunits temp;
1434 if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) {
1435 if (temp < H0) {
1436 warning(WARN_RANGE, "indent cannot be negative");
1437 temp = H0;
1440 else
1441 temp = curenv->prev_indent;
1442 while (!tok.newline() && !tok.eof())
1443 tok.next();
1444 if (break_flag)
1445 curenv->do_break();
1446 curenv->have_temporary_indent = 0;
1447 curenv->prev_indent = curenv->indent;
1448 curenv->indent = temp;
1449 curdiv->modified_tag.incl(MTSM_IN);
1450 tok.next();
1453 void temporary_indent()
1455 int err = 0;
1456 hunits temp;
1457 if (!get_hunits(&temp, 'm', curenv->get_indent()))
1458 err = 1;
1459 while (!tok.newline() && !tok.eof())
1460 tok.next();
1461 if (break_flag)
1462 curenv->do_break();
1463 if (temp < H0) {
1464 warning(WARN_RANGE, "total indent cannot be negative");
1465 temp = H0;
1467 if (!err) {
1468 curenv->temporary_indent = temp;
1469 curenv->have_temporary_indent = 1;
1470 curdiv->modified_tag.incl(MTSM_TI);
1472 tok.next();
1475 node *do_underline_special(int underline_spaces)
1477 macro m;
1478 m.append_str("x u ");
1479 m.append(underline_spaces + '0');
1480 return new special_node(m, 1);
1483 void do_underline(int underline_spaces)
1485 int n;
1486 if (!has_arg() || !get_integer(&n))
1487 n = 1;
1488 if (n <= 0) {
1489 if (curenv->underline_lines > 0) {
1490 curenv->prev_fontno = curenv->fontno;
1491 curenv->fontno = curenv->pre_underline_fontno;
1492 if (underline_spaces) {
1493 curenv->underline_spaces = 0;
1494 curenv->add_node(do_underline_special(0));
1497 curenv->underline_lines = 0;
1499 else {
1500 curenv->underline_lines = n;
1501 curenv->pre_underline_fontno = curenv->fontno;
1502 curenv->fontno = get_underline_fontno();
1503 if (underline_spaces) {
1504 curenv->underline_spaces = 1;
1505 curenv->add_node(do_underline_special(1));
1508 skip_line();
1511 void continuous_underline()
1513 do_underline(1);
1516 void underline()
1518 do_underline(0);
1521 void control_char()
1523 curenv->control_char = '.';
1524 if (has_arg()) {
1525 if (tok.ch() == 0)
1526 error("bad control character");
1527 else
1528 curenv->control_char = tok.ch();
1530 skip_line();
1533 void no_break_control_char()
1535 curenv->no_break_control_char = '\'';
1536 if (has_arg()) {
1537 if (tok.ch() == 0)
1538 error("bad control character");
1539 else
1540 curenv->no_break_control_char = tok.ch();
1542 skip_line();
1545 void margin_character()
1547 while (tok.space())
1548 tok.next();
1549 charinfo *ci = tok.get_char();
1550 if (ci) {
1551 // Call tok.next() only after making the node so that
1552 // .mc \s+9\(br\s0 works.
1553 node *nd = curenv->make_char_node(ci);
1554 tok.next();
1555 if (nd) {
1556 delete curenv->margin_character_node;
1557 curenv->margin_character_node = nd;
1558 curenv->margin_character_flags = (MARGIN_CHARACTER_ON
1559 |MARGIN_CHARACTER_NEXT);
1560 hunits d;
1561 if (has_arg() && get_hunits(&d, 'm'))
1562 curenv->margin_character_distance = d;
1565 else {
1566 check_missing_character();
1567 curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON;
1568 if (curenv->margin_character_flags == 0) {
1569 delete curenv->margin_character_node;
1570 curenv->margin_character_node = 0;
1573 skip_line();
1576 void number_lines()
1578 delete_node_list(curenv->numbering_nodes);
1579 curenv->numbering_nodes = 0;
1580 if (has_arg()) {
1581 node *nd = 0;
1582 for (int i = '9'; i >= '0'; i--) {
1583 node *tem = make_node(charset_table[i], curenv);
1584 if (!tem) {
1585 skip_line();
1586 return;
1588 tem->next = nd;
1589 nd = tem;
1591 curenv->numbering_nodes = nd;
1592 curenv->line_number_digit_width = env_digit_width(curenv);
1593 int n;
1594 if (!tok.delimiter()) {
1595 if (get_integer(&n, next_line_number)) {
1596 next_line_number = n;
1597 if (next_line_number < 0) {
1598 warning(WARN_RANGE, "negative line number");
1599 next_line_number = 0;
1603 else
1604 while (!tok.space() && !tok.newline() && !tok.eof())
1605 tok.next();
1606 if (has_arg()) {
1607 if (!tok.delimiter()) {
1608 if (get_integer(&n)) {
1609 if (n <= 0) {
1610 warning(WARN_RANGE, "negative or zero line number multiple");
1612 else
1613 curenv->line_number_multiple = n;
1616 else
1617 while (!tok.space() && !tok.newline() && !tok.eof())
1618 tok.next();
1619 if (has_arg()) {
1620 if (!tok.delimiter()) {
1621 if (get_integer(&n))
1622 curenv->number_text_separation = n;
1624 else
1625 while (!tok.space() && !tok.newline() && !tok.eof())
1626 tok.next();
1627 if (has_arg() && !tok.delimiter() && get_integer(&n))
1628 curenv->line_number_indent = n;
1632 skip_line();
1635 void no_number()
1637 int n;
1638 if (has_arg() && get_integer(&n))
1639 curenv->no_number_count = n > 0 ? n : 0;
1640 else
1641 curenv->no_number_count = 1;
1642 skip_line();
1645 void no_hyphenate()
1647 curenv->hyphenation_flags = 0;
1648 skip_line();
1651 void hyphenate_request()
1653 int n;
1654 if (has_arg() && get_integer(&n))
1655 curenv->hyphenation_flags = n;
1656 else
1657 curenv->hyphenation_flags = 1;
1658 skip_line();
1661 void hyphen_char()
1663 curenv->hyphen_indicator_char = get_optional_char();
1664 skip_line();
1667 void hyphen_line_max_request()
1669 int n;
1670 if (has_arg() && get_integer(&n))
1671 curenv->hyphen_line_max = n;
1672 else
1673 curenv->hyphen_line_max = -1;
1674 skip_line();
1677 void environment::interrupt()
1679 if (!dummy) {
1680 add_node(new transparent_dummy_node);
1681 interrupted = 1;
1685 void environment::newline()
1687 int was_centered = 0;
1688 if (underline_lines > 0) {
1689 if (--underline_lines == 0) {
1690 prev_fontno = fontno;
1691 fontno = pre_underline_fontno;
1692 if (underline_spaces) {
1693 underline_spaces = 0;
1694 add_node(do_underline_special(0));
1698 if (current_field)
1699 wrap_up_field();
1700 if (current_tab)
1701 wrap_up_tab();
1702 // strip trailing spaces
1703 while (line != 0 && line->discardable()) {
1704 width_total -= line->width();
1705 space_total -= line->nspaces();
1706 node *tem = line;
1707 line = line->next;
1708 delete tem;
1710 node *to_be_output = 0;
1711 hunits to_be_output_width;
1712 prev_line_interrupted = 0;
1713 if (dummy)
1714 space_newline();
1715 else if (interrupted) {
1716 interrupted = 0;
1717 // see environment::final_break
1718 prev_line_interrupted = exit_started ? 2 : 1;
1720 else if (center_lines > 0) {
1721 --center_lines;
1722 hunits x = target_text_length - width_total;
1723 if (x > H0)
1724 saved_indent += x/2;
1725 to_be_output = line;
1726 was_centered = 1;
1727 to_be_output_width = width_total;
1728 line = 0;
1730 else if (right_justify_lines > 0) {
1731 --right_justify_lines;
1732 hunits x = target_text_length - width_total;
1733 if (x > H0)
1734 saved_indent += x;
1735 to_be_output = line;
1736 to_be_output_width = width_total;
1737 line = 0;
1739 else if (fill)
1740 space_newline();
1741 else {
1742 to_be_output = line;
1743 to_be_output_width = width_total;
1744 line = 0;
1746 input_line_start = line == 0 ? H0 : width_total;
1747 if (to_be_output) {
1748 if (is_html && !fill) {
1749 curdiv->modified_tag.incl(MTSM_EOL);
1750 if (suppress_next_eol)
1751 suppress_next_eol = 0;
1752 else
1753 seen_eol = 1;
1756 output_line(to_be_output, to_be_output_width, was_centered);
1757 hyphen_line_count = 0;
1759 if (input_trap_count > 0) {
1760 if (!(continued_input_trap && prev_line_interrupted))
1761 if (--input_trap_count == 0)
1762 spring_trap(input_trap);
1766 void environment::output_line(node *n, hunits width, int was_centered)
1768 prev_text_length = width;
1769 if (margin_character_flags) {
1770 hunits d = line_length + margin_character_distance - saved_indent - width;
1771 if (d > 0) {
1772 n = new hmotion_node(d, get_fill_color(), n);
1773 width += d;
1775 margin_character_flags &= ~MARGIN_CHARACTER_NEXT;
1776 node *tem;
1777 if (!margin_character_flags) {
1778 tem = margin_character_node;
1779 margin_character_node = 0;
1781 else
1782 tem = margin_character_node->copy();
1783 tem->next = n;
1784 n = tem;
1785 width += tem->width();
1787 node *nn = 0;
1788 while (n != 0) {
1789 node *tem = n->next;
1790 n->next = nn;
1791 nn = n;
1792 n = tem;
1794 if (!saved_indent.is_zero())
1795 nn = new hmotion_node(saved_indent, get_fill_color(), nn);
1796 width += saved_indent;
1797 if (no_number_count > 0)
1798 --no_number_count;
1799 else if (numbering_nodes) {
1800 hunits w = (line_number_digit_width
1801 *(3+line_number_indent+number_text_separation));
1802 if (next_line_number % line_number_multiple != 0)
1803 nn = new hmotion_node(w, get_fill_color(), nn);
1804 else {
1805 hunits x = w;
1806 nn = new hmotion_node(number_text_separation * line_number_digit_width,
1807 get_fill_color(), nn);
1808 x -= number_text_separation*line_number_digit_width;
1809 char buf[30];
1810 sprintf(buf, "%3d", next_line_number);
1811 for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
1812 node *gn = numbering_nodes;
1813 for (int count = *p - '0'; count > 0; count--)
1814 gn = gn->next;
1815 gn = gn->copy();
1816 x -= gn->width();
1817 gn->next = nn;
1818 nn = gn;
1820 nn = new hmotion_node(x, get_fill_color(), nn);
1822 width += w;
1823 ++next_line_number;
1825 output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width,
1826 was_centered);
1829 void environment::start_line()
1831 assert(line == 0);
1832 discarding = 0;
1833 line = new line_start_node;
1834 if (have_temporary_indent) {
1835 saved_indent = temporary_indent;
1836 have_temporary_indent = 0;
1838 else
1839 saved_indent = indent;
1840 target_text_length = line_length - saved_indent;
1841 width_total = H0;
1842 space_total = 0;
1845 hunits environment::get_hyphenation_space()
1847 return hyphenation_space;
1850 void hyphenation_space_request()
1852 hunits n;
1853 if (get_hunits(&n, 'm')) {
1854 if (n < H0) {
1855 warning(WARN_RANGE, "hyphenation space cannot be negative");
1856 n = H0;
1858 curenv->hyphenation_space = n;
1860 skip_line();
1863 hunits environment::get_hyphenation_margin()
1865 return hyphenation_margin;
1868 void hyphenation_margin_request()
1870 hunits n;
1871 if (get_hunits(&n, 'm')) {
1872 if (n < H0) {
1873 warning(WARN_RANGE, "hyphenation margin cannot be negative");
1874 n = H0;
1876 curenv->hyphenation_margin = n;
1878 skip_line();
1881 breakpoint *environment::choose_breakpoint()
1883 hunits x = width_total;
1884 int s = space_total;
1885 node *n = line;
1886 breakpoint *best_bp = 0; // the best breakpoint so far
1887 int best_bp_fits = 0;
1888 while (n != 0) {
1889 x -= n->width();
1890 s -= n->nspaces();
1891 breakpoint *bp = n->get_breakpoints(x, s);
1892 while (bp != 0) {
1893 if (bp->width <= target_text_length) {
1894 if (!bp->hyphenated) {
1895 breakpoint *tem = bp->next;
1896 bp->next = 0;
1897 while (tem != 0) {
1898 breakpoint *tem1 = tem;
1899 tem = tem->next;
1900 delete tem1;
1902 if (best_bp_fits
1903 // Decide whether to use the hyphenated breakpoint.
1904 && (hyphen_line_max < 0
1905 // Only choose the hyphenated breakpoint if it would not
1906 // exceed the maximum number of consecutive hyphenated
1907 // lines.
1908 || hyphen_line_count + 1 <= hyphen_line_max)
1909 && !(adjust_mode == ADJUST_BOTH
1910 // Don't choose the hyphenated breakpoint if the line
1911 // can be justified by adding no more than
1912 // hyphenation_space to any word space.
1913 ? (bp->nspaces > 0
1914 && (((target_text_length - bp->width
1915 + (bp->nspaces - 1)*hresolution)/bp->nspaces)
1916 <= hyphenation_space))
1917 // Don't choose the hyphenated breakpoint if the line
1918 // is no more than hyphenation_margin short.
1919 : target_text_length - bp->width <= hyphenation_margin)) {
1920 delete bp;
1921 return best_bp;
1923 if (best_bp)
1924 delete best_bp;
1925 return bp;
1927 else {
1928 if ((adjust_mode == ADJUST_BOTH
1929 ? hyphenation_space == H0
1930 : hyphenation_margin == H0)
1931 && (hyphen_line_max < 0
1932 || hyphen_line_count + 1 <= hyphen_line_max)) {
1933 // No need to consider a non-hyphenated breakpoint.
1934 if (best_bp)
1935 delete best_bp;
1936 breakpoint *tem = bp->next;
1937 bp->next = 0;
1938 while (tem != 0) {
1939 breakpoint *tem1 = tem;
1940 tem = tem->next;
1941 delete tem1;
1943 return bp;
1945 // It fits but it's hyphenated.
1946 if (!best_bp_fits) {
1947 if (best_bp)
1948 delete best_bp;
1949 best_bp = bp;
1950 bp = bp->next;
1951 best_bp_fits = 1;
1953 else {
1954 breakpoint *tem = bp;
1955 bp = bp->next;
1956 delete tem;
1960 else {
1961 if (best_bp)
1962 delete best_bp;
1963 best_bp = bp;
1964 bp = bp->next;
1967 n = n->next;
1969 if (best_bp) {
1970 if (!best_bp_fits)
1971 output_warning(WARN_BREAK, "can't break line");
1972 return best_bp;
1974 return 0;
1977 void environment::hyphenate_line(int start_here)
1979 assert(line != 0);
1980 hyphenation_type prev_type = line->get_hyphenation_type();
1981 node **startp;
1982 if (start_here)
1983 startp = &line;
1984 else
1985 for (startp = &line->next; *startp != 0; startp = &(*startp)->next) {
1986 hyphenation_type this_type = (*startp)->get_hyphenation_type();
1987 if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
1988 break;
1989 prev_type = this_type;
1991 if (*startp == 0)
1992 return;
1993 node *tem = *startp;
1994 do {
1995 tem = tem->next;
1996 } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
1997 int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
1998 node *end = tem;
1999 hyphen_list *sl = 0;
2000 tem = *startp;
2001 node *forward = 0;
2002 int i = 0;
2003 while (tem != end) {
2004 sl = tem->get_hyphen_list(sl, &i);
2005 node *tem1 = tem;
2006 tem = tem->next;
2007 tem1->next = forward;
2008 forward = tem1;
2010 if (!inhibit) {
2011 // this is for characters like hyphen and emdash
2012 int prev_code = 0;
2013 for (hyphen_list *h = sl; h; h = h->next) {
2014 h->breakable = (prev_code != 0
2015 && h->next != 0
2016 && h->next->hyphenation_code != 0);
2017 prev_code = h->hyphenation_code;
2020 if (hyphenation_flags != 0
2021 && !inhibit
2022 // this may not be right if we have extra space on this line
2023 && !((hyphenation_flags & HYPHEN_LAST_LINE)
2024 && (curdiv->distance_to_next_trap()
2025 <= vertical_spacing + total_post_vertical_spacing()))
2026 && i >= 4)
2027 hyphenate(sl, hyphenation_flags);
2028 while (forward != 0) {
2029 node *tem1 = forward;
2030 forward = forward->next;
2031 tem1->next = 0;
2032 tem = tem1->add_self(tem, &sl);
2034 *startp = tem;
2037 static node *node_list_reverse(node *n)
2039 node *res = 0;
2040 while (n) {
2041 node *tem = n;
2042 n = n->next;
2043 tem->next = res;
2044 res = tem;
2046 return res;
2049 static void distribute_space(node *n, int nspaces, hunits desired_space,
2050 int force_reverse = 0)
2052 static int reverse = 0;
2053 if (force_reverse || reverse)
2054 n = node_list_reverse(n);
2055 if (!force_reverse && nspaces > 0 && spread_limit >= 0
2056 && desired_space.to_units() > 0) {
2057 hunits em = curenv->get_size();
2058 double Ems = (double)desired_space.to_units() / nspaces
2059 / (em.is_zero() ? hresolution : em.to_units());
2060 if (Ems > spread_limit)
2061 output_warning(WARN_BREAK, "spreading %1m per space", Ems);
2063 for (node *tem = n; tem; tem = tem->next)
2064 tem->spread_space(&nspaces, &desired_space);
2065 if (force_reverse || reverse)
2066 (void)node_list_reverse(n);
2067 if (!force_reverse)
2068 reverse = !reverse;
2069 assert(desired_space.is_zero() && nspaces == 0);
2072 void environment::possibly_break_line(int start_here, int forced)
2074 int was_centered = center_lines > 0;
2075 if (!fill || current_tab || current_field || dummy)
2076 return;
2077 while (line != 0
2078 && (forced
2079 // When a macro follows a paragraph in fill mode, the
2080 // current line should not be empty.
2081 || (width_total - line->width()) > target_text_length)) {
2082 hyphenate_line(start_here);
2083 breakpoint *bp = choose_breakpoint();
2084 if (bp == 0)
2085 // we'll find one eventually
2086 return;
2087 node *pre, *post;
2088 node **ndp = &line;
2089 while (*ndp != bp->nd)
2090 ndp = &(*ndp)->next;
2091 bp->nd->split(bp->index, &pre, &post);
2092 *ndp = post;
2093 hunits extra_space_width = H0;
2094 switch(adjust_mode) {
2095 case ADJUST_BOTH:
2096 if (bp->nspaces != 0)
2097 extra_space_width = target_text_length - bp->width;
2098 else if (bp->width > 0 && target_text_length > 0
2099 && target_text_length > bp->width)
2100 output_warning(WARN_BREAK, "cannot adjust line");
2101 break;
2102 case ADJUST_CENTER:
2103 saved_indent += (target_text_length - bp->width)/2;
2104 was_centered = 1;
2105 break;
2106 case ADJUST_RIGHT:
2107 saved_indent += target_text_length - bp->width;
2108 break;
2110 distribute_space(pre, bp->nspaces, extra_space_width);
2111 hunits output_width = bp->width + extra_space_width;
2112 input_line_start -= output_width;
2113 if (bp->hyphenated)
2114 hyphen_line_count++;
2115 else
2116 hyphen_line_count = 0;
2117 delete bp;
2118 space_total = 0;
2119 width_total = 0;
2120 node *first_non_discardable = 0;
2121 node *tem;
2122 for (tem = line; tem != 0; tem = tem->next)
2123 if (!tem->discardable())
2124 first_non_discardable = tem;
2125 node *to_be_discarded;
2126 if (first_non_discardable) {
2127 to_be_discarded = first_non_discardable->next;
2128 first_non_discardable->next = 0;
2129 for (tem = line; tem != 0; tem = tem->next) {
2130 width_total += tem->width();
2131 space_total += tem->nspaces();
2133 discarding = 0;
2135 else {
2136 discarding = 1;
2137 to_be_discarded = line;
2138 line = 0;
2140 // Do output_line() here so that line will be 0 iff the
2141 // the environment will be empty.
2142 output_line(pre, output_width, was_centered);
2143 while (to_be_discarded != 0) {
2144 tem = to_be_discarded;
2145 to_be_discarded = to_be_discarded->next;
2146 input_line_start -= tem->width();
2147 delete tem;
2149 if (line != 0) {
2150 if (have_temporary_indent) {
2151 saved_indent = temporary_indent;
2152 have_temporary_indent = 0;
2154 else
2155 saved_indent = indent;
2156 target_text_length = line_length - saved_indent;
2162 Do the break at the end of input after the end macro (if any).
2164 Unix troff behaves as follows: if the last line is
2166 foo bar\c
2168 it will output foo on the current page, and bar on the next page;
2169 if the last line is
2171 foo\c
2175 foo bar
2177 everything will be output on the current page. This behaviour must be
2178 considered a bug.
2180 The problem is that some macro packages rely on this. For example,
2181 the ATK macros have an end macro that emits \c if it needs to print a
2182 table of contents but doesn't do a 'bp in the end macro; instead the
2183 'bp is done in the bottom of page trap. This works with Unix troff,
2184 provided that the current environment is not empty at the end of the
2185 input file.
2187 The following will make macro packages that do that sort of thing work
2188 even if the current environment is empty at the end of the input file.
2189 If the last input line used \c and this line occurred in the end macro,
2190 then we'll force everything out on the current page, but we'll make
2191 sure that the environment isn't empty so that we won't exit at the
2192 bottom of this page.
2195 void environment::final_break()
2197 if (prev_line_interrupted == 2) {
2198 do_break();
2199 add_node(new transparent_dummy_node);
2201 else
2202 do_break();
2205 node *environment::make_tag(const char *nm, int i)
2207 if (is_html) {
2209 * need to emit tag for post-grohtml
2210 * but we check to see whether we can emit specials
2212 if (curdiv == topdiv && topdiv->before_first_page)
2213 topdiv->begin_page();
2214 macro *m = new macro;
2215 m->append_str("devtag:");
2216 for (const char *p = nm; *p; p++)
2217 if (!invalid_input_char((unsigned char)*p))
2218 m->append(*p);
2219 m->append(' ');
2220 m->append_int(i);
2221 return new special_node(*m);
2223 return 0;
2226 void environment::dump_troff_state()
2228 #define SPACES " "
2229 fprintf(stderr, SPACES "register `in' = %d\n", curenv->indent.to_units());
2230 if (curenv->have_temporary_indent)
2231 fprintf(stderr, SPACES "register `ti' = %d\n",
2232 curenv->temporary_indent.to_units());
2233 fprintf(stderr, SPACES "centered lines `ce' = %d\n", curenv->center_lines);
2234 fprintf(stderr, SPACES "register `ll' = %d\n",
2235 curenv->line_length.to_units());
2236 fprintf(stderr, SPACES "fill `fi=1/nf=0' = %d\n", curenv->fill);
2237 fprintf(stderr, SPACES "page offset `po' = %d\n",
2238 topdiv->get_page_offset().to_units());
2239 fprintf(stderr, SPACES "seen_break = %d\n", curenv->seen_break);
2240 fprintf(stderr, SPACES "seen_space = %d\n", curenv->seen_space);
2241 fflush(stderr);
2242 #undef SPACES
2245 statem *environment::construct_state(int only_eol)
2247 if (is_html) {
2248 statem *s = new statem();
2249 if (!only_eol) {
2250 s->add_tag(MTSM_IN, indent);
2251 s->add_tag(MTSM_LL, line_length);
2252 s->add_tag(MTSM_PO, topdiv->get_page_offset().to_units());
2253 s->add_tag(MTSM_RJ, right_justify_lines);
2254 if (have_temporary_indent)
2255 s->add_tag(MTSM_TI, temporary_indent);
2256 s->add_tag_ta();
2257 if (seen_break)
2258 s->add_tag(MTSM_BR);
2259 if (seen_space != 0)
2260 s->add_tag(MTSM_SP, seen_space);
2261 seen_break = 0;
2262 seen_space = 0;
2264 if (seen_eol) {
2265 s->add_tag(MTSM_EOL);
2266 s->add_tag(MTSM_CE, center_lines);
2268 seen_eol = 0;
2269 return s;
2271 else
2272 return NULL;
2275 void environment::construct_format_state(node *n, int was_centered,
2276 int filling)
2278 if (is_html) {
2279 // find first glyph node which has a state.
2280 while (n != 0 && n->state == 0)
2281 n = n->next;
2282 if (n == 0 || (n->state == 0))
2283 return;
2284 if (seen_space != 0)
2285 n->state->add_tag(MTSM_SP, seen_space);
2286 if (seen_eol && topdiv == curdiv)
2287 n->state->add_tag(MTSM_EOL);
2288 seen_space = 0;
2289 seen_eol = 0;
2290 if (was_centered)
2291 n->state->add_tag(MTSM_CE, center_lines+1);
2292 else
2293 n->state->add_tag_if_unknown(MTSM_CE, 0);
2294 n->state->add_tag_if_unknown(MTSM_FI, filling);
2295 n = n->next;
2296 while (n != 0) {
2297 if (n->state != 0) {
2298 n->state->sub_tag_ce();
2299 n->state->add_tag_if_unknown(MTSM_FI, filling);
2301 n = n->next;
2306 void environment::construct_new_line_state(node *n)
2308 if (is_html) {
2309 // find first glyph node which has a state.
2310 while (n != 0 && n->state == 0)
2311 n = n->next;
2312 if (n == 0 || n->state == 0)
2313 return;
2314 if (seen_space != 0)
2315 n->state->add_tag(MTSM_SP, seen_space);
2316 if (seen_eol && topdiv == curdiv)
2317 n->state->add_tag(MTSM_EOL);
2318 seen_space = 0;
2319 seen_eol = 0;
2323 extern int global_diverted_space;
2325 void environment::do_break(int do_spread)
2327 int was_centered = 0;
2328 if (curdiv == topdiv && topdiv->before_first_page) {
2329 topdiv->begin_page();
2330 return;
2332 if (current_tab)
2333 wrap_up_tab();
2334 if (line) {
2335 // this is so that hyphenation works
2336 line = new space_node(H0, get_fill_color(), line);
2337 space_total++;
2338 possibly_break_line(0, do_spread);
2340 while (line != 0 && line->discardable()) {
2341 width_total -= line->width();
2342 space_total -= line->nspaces();
2343 node *tem = line;
2344 line = line->next;
2345 delete tem;
2347 discarding = 0;
2348 input_line_start = H0;
2349 if (line != 0) {
2350 if (fill) {
2351 switch (adjust_mode) {
2352 case ADJUST_CENTER:
2353 saved_indent += (target_text_length - width_total)/2;
2354 was_centered = 1;
2355 break;
2356 case ADJUST_RIGHT:
2357 saved_indent += target_text_length - width_total;
2358 break;
2361 node *tem = line;
2362 line = 0;
2363 output_line(tem, width_total, was_centered);
2364 hyphen_line_count = 0;
2366 prev_line_interrupted = 0;
2367 #ifdef WIDOW_CONTROL
2368 mark_last_line();
2369 output_pending_lines();
2370 #endif /* WIDOW_CONTROL */
2371 if (!global_diverted_space) {
2372 curdiv->modified_tag.incl(MTSM_BR);
2373 seen_break = 1;
2377 int environment::is_empty()
2379 return !current_tab && line == 0 && pending_lines == 0;
2382 void do_break_request(int spread)
2384 while (!tok.newline() && !tok.eof())
2385 tok.next();
2386 if (break_flag)
2387 curenv->do_break(spread);
2388 tok.next();
2391 void break_request()
2393 do_break_request(0);
2396 void break_spread_request()
2398 do_break_request(1);
2401 void title()
2403 if (curdiv == topdiv && topdiv->before_first_page) {
2404 handle_initial_title();
2405 return;
2407 node *part[3];
2408 hunits part_width[3];
2409 part[0] = part[1] = part[2] = 0;
2410 environment env(curenv);
2411 environment *oldenv = curenv;
2412 curenv = &env;
2413 read_title_parts(part, part_width);
2414 curenv = oldenv;
2415 curenv->size = env.size;
2416 curenv->prev_size = env.prev_size;
2417 curenv->requested_size = env.requested_size;
2418 curenv->prev_requested_size = env.prev_requested_size;
2419 curenv->char_height = env.char_height;
2420 curenv->char_slant = env.char_slant;
2421 curenv->fontno = env.fontno;
2422 curenv->prev_fontno = env.prev_fontno;
2423 curenv->glyph_color = env.glyph_color;
2424 curenv->prev_glyph_color = env.prev_glyph_color;
2425 curenv->fill_color = env.fill_color;
2426 curenv->prev_fill_color = env.prev_fill_color;
2427 node *n = 0;
2428 node *p = part[2];
2429 while (p != 0) {
2430 node *tem = p;
2431 p = p->next;
2432 tem->next = n;
2433 n = tem;
2435 hunits length_title(curenv->title_length);
2436 hunits f = length_title - part_width[1];
2437 hunits f2 = f/2;
2438 n = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(), n);
2439 p = part[1];
2440 while (p != 0) {
2441 node *tem = p;
2442 p = p->next;
2443 tem->next = n;
2444 n = tem;
2446 n = new hmotion_node(f - f2 - part_width[0], curenv->get_fill_color(), n);
2447 p = part[0];
2448 while (p != 0) {
2449 node *tem = p;
2450 p = p->next;
2451 tem->next = n;
2452 n = tem;
2454 curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
2455 curenv->total_post_vertical_spacing(), length_title);
2456 curenv->hyphen_line_count = 0;
2457 tok.next();
2460 void adjust()
2462 curenv->adjust_mode |= 1;
2463 if (has_arg()) {
2464 switch (tok.ch()) {
2465 case 'l':
2466 curenv->adjust_mode = ADJUST_LEFT;
2467 break;
2468 case 'r':
2469 curenv->adjust_mode = ADJUST_RIGHT;
2470 break;
2471 case 'c':
2472 curenv->adjust_mode = ADJUST_CENTER;
2473 break;
2474 case 'b':
2475 case 'n':
2476 curenv->adjust_mode = ADJUST_BOTH;
2477 break;
2478 default:
2479 int n;
2480 if (get_integer(&n)) {
2481 if (n < 0)
2482 warning(WARN_RANGE, "negative adjustment mode");
2483 else if (n > 5) {
2484 curenv->adjust_mode = 5;
2485 warning(WARN_RANGE, "adjustment mode `%1' out of range", n);
2487 else
2488 curenv->adjust_mode = n;
2492 skip_line();
2495 void no_adjust()
2497 curenv->adjust_mode &= ~1;
2498 skip_line();
2501 void do_input_trap(int continued)
2503 curenv->input_trap_count = 0;
2504 if (continued)
2505 curenv->continued_input_trap = 1;
2506 int n;
2507 if (has_arg() && get_integer(&n)) {
2508 if (n <= 0)
2509 warning(WARN_RANGE,
2510 "number of lines for input trap must be greater than zero");
2511 else {
2512 symbol s = get_name(1);
2513 if (!s.is_null()) {
2514 curenv->input_trap_count = n;
2515 curenv->input_trap = s;
2519 skip_line();
2522 void input_trap()
2524 do_input_trap(0);
2527 void input_trap_continued()
2529 do_input_trap(1);
2532 /* tabs */
2534 // must not be R or C or L or a legitimate part of a number expression
2535 const char TAB_REPEAT_CHAR = 'T';
2537 struct tab {
2538 tab *next;
2539 hunits pos;
2540 tab_type type;
2541 tab(hunits, tab_type);
2542 enum { BLOCK = 1024 };
2543 static tab *free_list;
2544 void *operator new(size_t);
2545 void operator delete(void *);
2548 tab *tab::free_list = 0;
2550 void *tab::operator new(size_t n)
2552 assert(n == sizeof(tab));
2553 if (!free_list) {
2554 free_list = (tab *)new char[sizeof(tab)*BLOCK];
2555 for (int i = 0; i < BLOCK - 1; i++)
2556 free_list[i].next = free_list + i + 1;
2557 free_list[BLOCK-1].next = 0;
2559 tab *p = free_list;
2560 free_list = (tab *)(free_list->next);
2561 p->next = 0;
2562 return p;
2565 #ifdef __GNUG__
2566 /* cfront can't cope with this. */
2567 inline
2568 #endif
2569 void tab::operator delete(void *p)
2571 if (p) {
2572 ((tab *)p)->next = free_list;
2573 free_list = (tab *)p;
2577 tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2581 tab_stops::tab_stops(hunits distance, tab_type type)
2582 : initial_list(0)
2584 repeated_list = new tab(distance, type);
2587 tab_stops::~tab_stops()
2589 clear();
2592 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
2594 hunits nextpos;
2596 return distance_to_next_tab(curpos, distance, &nextpos);
2599 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance,
2600 hunits *nextpos)
2602 hunits lastpos = 0;
2603 tab *tem;
2604 for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
2605 lastpos = tem->pos;
2606 if (tem) {
2607 *distance = tem->pos - curpos;
2608 *nextpos = tem->pos;
2609 return tem->type;
2611 if (repeated_list == 0)
2612 return TAB_NONE;
2613 hunits base = lastpos;
2614 for (;;) {
2615 for (tem = repeated_list; tem && tem->pos + base <= curpos; tem = tem->next)
2616 lastpos = tem->pos;
2617 if (tem) {
2618 *distance = tem->pos + base - curpos;
2619 *nextpos = tem->pos + base;
2620 return tem->type;
2622 assert(lastpos > 0);
2623 base += lastpos;
2625 return TAB_NONE;
2628 const char *tab_stops::to_string()
2630 static char *buf = 0;
2631 static int buf_size = 0;
2632 // figure out a maximum on the amount of space we can need
2633 int count = 0;
2634 tab *p;
2635 for (p = initial_list; p; p = p->next)
2636 ++count;
2637 for (p = repeated_list; p; p = p->next)
2638 ++count;
2639 // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
2640 int need = count*12 + 3;
2641 if (buf == 0 || need > buf_size) {
2642 if (buf)
2643 a_delete buf;
2644 buf_size = need;
2645 buf = new char[buf_size];
2647 char *ptr = buf;
2648 for (p = initial_list; p; p = p->next) {
2649 strcpy(ptr, i_to_a(p->pos.to_units()));
2650 ptr = strchr(ptr, '\0');
2651 *ptr++ = 'u';
2652 *ptr = '\0';
2653 switch (p->type) {
2654 case TAB_LEFT:
2655 break;
2656 case TAB_RIGHT:
2657 *ptr++ = 'R';
2658 break;
2659 case TAB_CENTER:
2660 *ptr++ = 'C';
2661 break;
2662 case TAB_NONE:
2663 default:
2664 assert(0);
2667 if (repeated_list)
2668 *ptr++ = TAB_REPEAT_CHAR;
2669 for (p = repeated_list; p; p = p->next) {
2670 strcpy(ptr, i_to_a(p->pos.to_units()));
2671 ptr = strchr(ptr, '\0');
2672 *ptr++ = 'u';
2673 *ptr = '\0';
2674 switch (p->type) {
2675 case TAB_LEFT:
2676 break;
2677 case TAB_RIGHT:
2678 *ptr++ = 'R';
2679 break;
2680 case TAB_CENTER:
2681 *ptr++ = 'C';
2682 break;
2683 case TAB_NONE:
2684 default:
2685 assert(0);
2688 *ptr++ = '\0';
2689 return buf;
2692 tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2696 tab_stops::tab_stops(const tab_stops &ts)
2697 : initial_list(0), repeated_list(0)
2699 tab **p = &initial_list;
2700 tab *t = ts.initial_list;
2701 while (t) {
2702 *p = new tab(t->pos, t->type);
2703 t = t->next;
2704 p = &(*p)->next;
2706 p = &repeated_list;
2707 t = ts.repeated_list;
2708 while (t) {
2709 *p = new tab(t->pos, t->type);
2710 t = t->next;
2711 p = &(*p)->next;
2715 void tab_stops::clear()
2717 while (initial_list) {
2718 tab *tem = initial_list;
2719 initial_list = initial_list->next;
2720 delete tem;
2722 while (repeated_list) {
2723 tab *tem = repeated_list;
2724 repeated_list = repeated_list->next;
2725 delete tem;
2729 void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
2731 tab **p;
2732 for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
2734 *p = new tab(pos, type);
2738 void tab_stops::operator=(const tab_stops &ts)
2740 clear();
2741 tab **p = &initial_list;
2742 tab *t = ts.initial_list;
2743 while (t) {
2744 *p = new tab(t->pos, t->type);
2745 t = t->next;
2746 p = &(*p)->next;
2748 p = &repeated_list;
2749 t = ts.repeated_list;
2750 while (t) {
2751 *p = new tab(t->pos, t->type);
2752 t = t->next;
2753 p = &(*p)->next;
2757 void set_tabs()
2759 hunits pos;
2760 hunits prev_pos = 0;
2761 int first = 1;
2762 int repeated = 0;
2763 tab_stops tabs;
2764 while (has_arg()) {
2765 if (tok.ch() == TAB_REPEAT_CHAR) {
2766 tok.next();
2767 repeated = 1;
2768 prev_pos = 0;
2770 if (!get_hunits(&pos, 'm', prev_pos))
2771 break;
2772 tab_type type = TAB_LEFT;
2773 if (tok.ch() == 'C') {
2774 tok.next();
2775 type = TAB_CENTER;
2777 else if (tok.ch() == 'R') {
2778 tok.next();
2779 type = TAB_RIGHT;
2781 else if (tok.ch() == 'L') {
2782 tok.next();
2784 if (pos <= prev_pos && !first)
2785 warning(WARN_RANGE,
2786 "positions of tab stops must be strictly increasing");
2787 else {
2788 tabs.add_tab(pos, type, repeated);
2789 prev_pos = pos;
2790 first = 0;
2793 curenv->tabs = tabs;
2794 curdiv->modified_tag.incl(MTSM_TA);
2795 skip_line();
2798 const char *environment::get_tabs()
2800 return tabs.to_string();
2803 tab_type environment::distance_to_next_tab(hunits *distance)
2805 return line_tabs
2806 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
2807 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
2810 tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
2812 return line_tabs
2813 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
2814 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
2815 leftpos);
2818 void field_characters()
2820 field_delimiter_char = get_optional_char();
2821 if (field_delimiter_char)
2822 padding_indicator_char = get_optional_char();
2823 else
2824 padding_indicator_char = 0;
2825 skip_line();
2828 void line_tabs_request()
2830 int n;
2831 if (has_arg() && get_integer(&n))
2832 curenv->line_tabs = n != 0;
2833 else
2834 curenv->line_tabs = 1;
2835 skip_line();
2838 int environment::get_line_tabs()
2840 return line_tabs;
2843 void environment::wrap_up_tab()
2845 if (!current_tab)
2846 return;
2847 if (line == 0)
2848 start_line();
2849 hunits tab_amount;
2850 switch (current_tab) {
2851 case TAB_RIGHT:
2852 tab_amount = tab_distance - tab_width;
2853 line = make_tab_node(tab_amount, line);
2854 break;
2855 case TAB_CENTER:
2856 tab_amount = tab_distance - tab_width/2;
2857 line = make_tab_node(tab_amount, line);
2858 break;
2859 case TAB_NONE:
2860 case TAB_LEFT:
2861 default:
2862 assert(0);
2864 width_total += tab_amount;
2865 width_total += tab_width;
2866 if (current_field) {
2867 if (tab_precedes_field) {
2868 pre_field_width += tab_amount;
2869 tab_precedes_field = 0;
2871 field_distance -= tab_amount;
2872 field_spaces += tab_field_spaces;
2874 if (tab_contents != 0) {
2875 node *tem;
2876 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2878 tem->next = line;
2879 line = tab_contents;
2881 tab_field_spaces = 0;
2882 tab_contents = 0;
2883 tab_width = H0;
2884 tab_distance = H0;
2885 current_tab = TAB_NONE;
2888 node *environment::make_tab_node(hunits d, node *next)
2890 if (leader_node != 0 && d < 0) {
2891 error("motion generated by leader cannot be negative");
2892 delete leader_node;
2893 leader_node = 0;
2895 if (!leader_node)
2896 return new hmotion_node(d, 1, 0, get_fill_color(), next);
2897 node *n = new hline_node(d, leader_node, next);
2898 leader_node = 0;
2899 return n;
2902 void environment::handle_tab(int is_leader)
2904 hunits d;
2905 hunits absolute;
2906 if (current_tab)
2907 wrap_up_tab();
2908 charinfo *ci = is_leader ? leader_char : tab_char;
2909 delete leader_node;
2910 leader_node = ci ? make_char_node(ci) : 0;
2911 tab_type t = distance_to_next_tab(&d, &absolute);
2912 switch (t) {
2913 case TAB_NONE:
2914 return;
2915 case TAB_LEFT:
2916 add_node(make_tag("tab L", absolute.to_units()));
2917 add_node(make_tab_node(d));
2918 return;
2919 case TAB_RIGHT:
2920 add_node(make_tag("tab R", absolute.to_units()));
2921 break;
2922 case TAB_CENTER:
2923 add_node(make_tag("tab C", absolute.to_units()));
2924 break;
2925 default:
2926 assert(0);
2928 tab_width = 0;
2929 tab_distance = d;
2930 tab_contents = 0;
2931 current_tab = t;
2932 tab_field_spaces = 0;
2935 void environment::start_field()
2937 assert(!current_field);
2938 hunits d;
2939 if (distance_to_next_tab(&d) != TAB_NONE) {
2940 pre_field_width = get_text_length();
2941 field_distance = d;
2942 current_field = 1;
2943 field_spaces = 0;
2944 tab_field_spaces = 0;
2945 for (node *p = line; p; p = p->next)
2946 if (p->nspaces()) {
2947 p->freeze_space();
2948 space_total--;
2950 tab_precedes_field = current_tab != TAB_NONE;
2952 else
2953 error("zero field width");
2956 void environment::wrap_up_field()
2958 if (!current_tab && field_spaces == 0)
2959 add_padding();
2960 hunits padding = field_distance - (get_text_length() - pre_field_width);
2961 if (current_tab && tab_field_spaces != 0) {
2962 hunits tab_padding = scale(padding,
2963 tab_field_spaces,
2964 field_spaces + tab_field_spaces);
2965 padding -= tab_padding;
2966 distribute_space(tab_contents, tab_field_spaces, tab_padding, 1);
2967 tab_field_spaces = 0;
2968 tab_width += tab_padding;
2970 if (field_spaces != 0) {
2971 distribute_space(line, field_spaces, padding, 1);
2972 width_total += padding;
2973 if (current_tab) {
2974 // the start of the tab has been moved to the right by padding, so
2975 tab_distance -= padding;
2976 if (tab_distance <= H0) {
2977 // use the next tab stop instead
2978 current_tab = tabs.distance_to_next_tab(get_input_line_position()
2979 - tab_width,
2980 &tab_distance);
2981 if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
2982 width_total += tab_width;
2983 if (current_tab == TAB_LEFT) {
2984 line = make_tab_node(tab_distance, line);
2985 width_total += tab_distance;
2986 current_tab = TAB_NONE;
2988 if (tab_contents != 0) {
2989 node *tem;
2990 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2992 tem->next = line;
2993 line = tab_contents;
2994 tab_contents = 0;
2996 tab_width = H0;
2997 tab_distance = H0;
3002 current_field = 0;
3005 void environment::add_padding()
3007 if (current_tab) {
3008 tab_contents = new space_node(H0, get_fill_color(), tab_contents);
3009 tab_field_spaces++;
3011 else {
3012 if (line == 0)
3013 start_line();
3014 line = new space_node(H0, get_fill_color(), line);
3015 field_spaces++;
3019 typedef int (environment::*INT_FUNCP)();
3020 typedef vunits (environment::*VUNITS_FUNCP)();
3021 typedef hunits (environment::*HUNITS_FUNCP)();
3022 typedef const char *(environment::*STRING_FUNCP)();
3024 class int_env_reg : public reg {
3025 INT_FUNCP func;
3026 public:
3027 int_env_reg(INT_FUNCP);
3028 const char *get_string();
3029 int get_value(units *val);
3032 class vunits_env_reg : public reg {
3033 VUNITS_FUNCP func;
3034 public:
3035 vunits_env_reg(VUNITS_FUNCP f);
3036 const char *get_string();
3037 int get_value(units *val);
3041 class hunits_env_reg : public reg {
3042 HUNITS_FUNCP func;
3043 public:
3044 hunits_env_reg(HUNITS_FUNCP f);
3045 const char *get_string();
3046 int get_value(units *val);
3049 class string_env_reg : public reg {
3050 STRING_FUNCP func;
3051 public:
3052 string_env_reg(STRING_FUNCP);
3053 const char *get_string();
3056 int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
3060 int int_env_reg::get_value(units *val)
3062 *val = (curenv->*func)();
3063 return 1;
3066 const char *int_env_reg::get_string()
3068 return i_to_a((curenv->*func)());
3071 vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
3075 int vunits_env_reg::get_value(units *val)
3077 *val = (curenv->*func)().to_units();
3078 return 1;
3081 const char *vunits_env_reg::get_string()
3083 return i_to_a((curenv->*func)().to_units());
3086 hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
3090 int hunits_env_reg::get_value(units *val)
3092 *val = (curenv->*func)().to_units();
3093 return 1;
3096 const char *hunits_env_reg::get_string()
3098 return i_to_a((curenv->*func)().to_units());
3101 string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
3105 const char *string_env_reg::get_string()
3107 return (curenv->*func)();
3110 class horizontal_place_reg : public general_reg {
3111 public:
3112 horizontal_place_reg();
3113 int get_value(units *);
3114 void set_value(units);
3117 horizontal_place_reg::horizontal_place_reg()
3121 int horizontal_place_reg::get_value(units *res)
3123 *res = curenv->get_input_line_position().to_units();
3124 return 1;
3127 void horizontal_place_reg::set_value(units n)
3129 curenv->set_input_line_position(hunits(n));
3132 const char *environment::get_font_family_string()
3134 return family->nm.contents();
3137 const char *environment::get_glyph_color_string()
3139 return glyph_color->nm.contents();
3142 const char *environment::get_fill_color_string()
3144 return fill_color->nm.contents();
3147 const char *environment::get_font_name_string()
3149 symbol f = get_font_name(fontno, this);
3150 return f.contents();
3153 const char *environment::get_style_name_string()
3155 symbol f = get_style_name(fontno);
3156 return f.contents();
3159 const char *environment::get_name_string()
3161 return name.contents();
3164 // Convert a quantity in scaled points to ascii decimal fraction.
3166 const char *sptoa(int sp)
3168 assert(sp > 0);
3169 assert(sizescale > 0);
3170 if (sizescale == 1)
3171 return i_to_a(sp);
3172 if (sp % sizescale == 0)
3173 return i_to_a(sp/sizescale);
3174 // See if 1/sizescale is exactly representable as a decimal fraction,
3175 // ie its only prime factors are 2 and 5.
3176 int n = sizescale;
3177 int power2 = 0;
3178 while ((n & 1) == 0) {
3179 n >>= 1;
3180 power2++;
3182 int power5 = 0;
3183 while ((n % 5) == 0) {
3184 n /= 5;
3185 power5++;
3187 if (n == 1) {
3188 int decimal_point = power5 > power2 ? power5 : power2;
3189 if (decimal_point <= 10) {
3190 int factor = 1;
3191 int t;
3192 for (t = decimal_point - power2; --t >= 0;)
3193 factor *= 2;
3194 for (t = decimal_point - power5; --t >= 0;)
3195 factor *= 5;
3196 if (factor == 1 || sp <= INT_MAX/factor)
3197 return if_to_a(sp*factor, decimal_point);
3200 double s = double(sp)/double(sizescale);
3201 double factor = 10.0;
3202 double val = s;
3203 int decimal_point = 0;
3204 do {
3205 double v = ceil(s*factor);
3206 if (v > INT_MAX)
3207 break;
3208 val = v;
3209 factor *= 10.0;
3210 } while (++decimal_point < 10);
3211 return if_to_a(int(val), decimal_point);
3214 const char *environment::get_point_size_string()
3216 return sptoa(curenv->get_point_size());
3219 const char *environment::get_requested_point_size_string()
3221 return sptoa(curenv->get_requested_point_size());
3224 #define init_int_env_reg(name, func) \
3225 number_reg_dictionary.define(name, new int_env_reg(&environment::func))
3227 #define init_vunits_env_reg(name, func) \
3228 number_reg_dictionary.define(name, new vunits_env_reg(&environment::func))
3230 #define init_hunits_env_reg(name, func) \
3231 number_reg_dictionary.define(name, new hunits_env_reg(&environment::func))
3233 #define init_string_env_reg(name, func) \
3234 number_reg_dictionary.define(name, new string_env_reg(&environment::func))
3236 void init_env_requests()
3238 init_request("ad", adjust);
3239 init_request("br", break_request);
3240 init_request("brp", break_spread_request);
3241 init_request("c2", no_break_control_char);
3242 init_request("cc", control_char);
3243 init_request("ce", center);
3244 init_request("cu", continuous_underline);
3245 init_request("ev", environment_switch);
3246 init_request("evc", environment_copy);
3247 init_request("fam", family_change);
3248 init_request("fc", field_characters);
3249 init_request("fi", fill);
3250 init_request("fcolor", fill_color_change);
3251 init_request("ft", font_change);
3252 init_request("gcolor", glyph_color_change);
3253 init_request("hc", hyphen_char);
3254 init_request("hlm", hyphen_line_max_request);
3255 init_request("hy", hyphenate_request);
3256 init_request("hym", hyphenation_margin_request);
3257 init_request("hys", hyphenation_space_request);
3258 init_request("in", indent);
3259 init_request("it", input_trap);
3260 init_request("itc", input_trap_continued);
3261 init_request("lc", leader_character);
3262 init_request("linetabs", line_tabs_request);
3263 init_request("ll", line_length);
3264 init_request("ls", line_spacing);
3265 init_request("lt", title_length);
3266 init_request("mc", margin_character);
3267 init_request("na", no_adjust);
3268 init_request("nf", no_fill);
3269 init_request("nh", no_hyphenate);
3270 init_request("nm", number_lines);
3271 init_request("nn", no_number);
3272 init_request("ps", point_size);
3273 init_request("pvs", post_vertical_spacing);
3274 init_request("rj", right_justify);
3275 init_request("sizes", override_sizes);
3276 init_request("ss", space_size);
3277 init_request("ta", set_tabs);
3278 init_request("ti", temporary_indent);
3279 init_request("tc", tab_character);
3280 init_request("tl", title);
3281 init_request("ul", underline);
3282 init_request("vs", vertical_spacing);
3283 #ifdef WIDOW_CONTROL
3284 init_request("wdc", widow_control_request);
3285 #endif /* WIDOW_CONTROL */
3286 init_int_env_reg(".b", get_bold);
3287 init_vunits_env_reg(".cdp", get_prev_char_depth);
3288 init_int_env_reg(".ce", get_center_lines);
3289 init_vunits_env_reg(".cht", get_prev_char_height);
3290 init_hunits_env_reg(".csk", get_prev_char_skew);
3291 init_string_env_reg(".ev", get_name_string);
3292 init_int_env_reg(".f", get_font);
3293 init_string_env_reg(".fam", get_font_family_string);
3294 init_string_env_reg(".fn", get_font_name_string);
3295 init_int_env_reg(".height", get_char_height);
3296 init_int_env_reg(".hlc", get_hyphen_line_count);
3297 init_int_env_reg(".hlm", get_hyphen_line_max);
3298 init_int_env_reg(".hy", get_hyphenation_flags);
3299 init_hunits_env_reg(".hym", get_hyphenation_margin);
3300 init_hunits_env_reg(".hys", get_hyphenation_space);
3301 init_hunits_env_reg(".i", get_indent);
3302 init_hunits_env_reg(".in", get_saved_indent);
3303 init_int_env_reg(".int", get_prev_line_interrupted);
3304 init_int_env_reg(".linetabs", get_line_tabs);
3305 init_hunits_env_reg(".lt", get_title_length);
3306 init_int_env_reg(".j", get_adjust_mode);
3307 init_hunits_env_reg(".k", get_text_length);
3308 init_int_env_reg(".L", get_line_spacing);
3309 init_hunits_env_reg(".l", get_line_length);
3310 init_hunits_env_reg(".ll", get_saved_line_length);
3311 init_string_env_reg(".M", get_fill_color_string);
3312 init_string_env_reg(".m", get_glyph_color_string);
3313 init_hunits_env_reg(".n", get_prev_text_length);
3314 init_int_env_reg(".ps", get_point_size);
3315 init_int_env_reg(".psr", get_requested_point_size);
3316 init_vunits_env_reg(".pvs", get_post_vertical_spacing);
3317 init_int_env_reg(".rj", get_right_justify_lines);
3318 init_string_env_reg(".s", get_point_size_string);
3319 init_int_env_reg(".slant", get_char_slant);
3320 init_int_env_reg(".ss", get_space_size);
3321 init_int_env_reg(".sss", get_sentence_space_size);
3322 init_string_env_reg(".sr", get_requested_point_size_string);
3323 init_string_env_reg(".sty", get_style_name_string);
3324 init_string_env_reg(".tabs", get_tabs);
3325 init_int_env_reg(".u", get_fill);
3326 init_vunits_env_reg(".v", get_vertical_spacing);
3327 init_hunits_env_reg(".w", get_prev_char_width);
3328 number_reg_dictionary.define("ct", new variable_reg(&ct_reg_contents));
3329 number_reg_dictionary.define("hp", new horizontal_place_reg);
3330 number_reg_dictionary.define("ln", new variable_reg(&next_line_number));
3331 number_reg_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
3332 number_reg_dictionary.define("rst", new variable_reg(&rst_reg_contents));
3333 number_reg_dictionary.define("sb", new variable_reg(&sb_reg_contents));
3334 number_reg_dictionary.define("skw", new variable_reg(&skw_reg_contents));
3335 number_reg_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
3336 number_reg_dictionary.define("st", new variable_reg(&st_reg_contents));
3339 // Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
3341 struct trie_node;
3343 class trie {
3344 trie_node *tp;
3345 virtual void do_match(int len, void *val) = 0;
3346 virtual void do_delete(void *) = 0;
3347 void delete_trie_node(trie_node *);
3348 public:
3349 trie() : tp(0) {}
3350 virtual ~trie(); // virtual to shut up g++
3351 void insert(const char *, int, void *);
3352 // find calls do_match for each match it finds
3353 void find(const char *pat, int patlen);
3354 void clear();
3357 class hyphen_trie : private trie {
3358 int *h;
3359 void do_match(int i, void *v);
3360 void do_delete(void *v);
3361 void insert_pattern(const char *pat, int patlen, int *num);
3362 void insert_hyphenation(dictionary *ex, const char *pat, int patlen);
3363 int hpf_getc(FILE *f);
3364 public:
3365 hyphen_trie() {}
3366 ~hyphen_trie() {}
3367 void hyphenate(const char *word, int len, int *hyphens);
3368 void read_patterns_file(const char *name, int append, dictionary *ex);
3371 struct hyphenation_language {
3372 symbol name;
3373 dictionary exceptions;
3374 hyphen_trie patterns;
3375 hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
3376 ~hyphenation_language() { }
3379 dictionary language_dictionary(5);
3380 hyphenation_language *current_language = 0;
3382 static void set_hyphenation_language()
3384 symbol nm = get_name(1);
3385 if (!nm.is_null()) {
3386 current_language = (hyphenation_language *)language_dictionary.lookup(nm);
3387 if (!current_language) {
3388 current_language = new hyphenation_language(nm);
3389 (void)language_dictionary.lookup(nm, (void *)current_language);
3392 skip_line();
3395 const int WORD_MAX = 256; // we use unsigned char for offsets in
3396 // hyphenation exceptions
3398 static void hyphen_word()
3400 if (!current_language) {
3401 error("no current hyphenation language");
3402 skip_line();
3403 return;
3405 char buf[WORD_MAX + 1];
3406 unsigned char pos[WORD_MAX + 2];
3407 for (;;) {
3408 tok.skip();
3409 if (tok.newline() || tok.eof())
3410 break;
3411 int i = 0;
3412 int npos = 0;
3413 while (i < WORD_MAX && !tok.space() && !tok.newline() && !tok.eof()) {
3414 charinfo *ci = tok.get_char(1);
3415 if (ci == 0) {
3416 skip_line();
3417 return;
3419 tok.next();
3420 if (ci->get_ascii_code() == '-') {
3421 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3422 pos[npos++] = i;
3424 else {
3425 unsigned char c = ci->get_hyphenation_code();
3426 if (c == 0)
3427 break;
3428 buf[i++] = c;
3431 if (i > 0) {
3432 pos[npos] = 0;
3433 buf[i] = 0;
3434 unsigned char *tem = new unsigned char[npos + 1];
3435 memcpy(tem, pos, npos + 1);
3436 tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf),
3437 tem);
3438 if (tem)
3439 a_delete tem;
3442 skip_line();
3445 struct trie_node {
3446 char c;
3447 trie_node *down;
3448 trie_node *right;
3449 void *val;
3450 trie_node(char, trie_node *);
3453 trie_node::trie_node(char ch, trie_node *p)
3454 : c(ch), down(0), right(p), val(0)
3458 trie::~trie()
3460 clear();
3463 void trie::clear()
3465 delete_trie_node(tp);
3466 tp = 0;
3470 void trie::delete_trie_node(trie_node *p)
3472 if (p) {
3473 delete_trie_node(p->down);
3474 delete_trie_node(p->right);
3475 if (p->val)
3476 do_delete(p->val);
3477 delete p;
3481 void trie::insert(const char *pat, int patlen, void *val)
3483 trie_node **p = &tp;
3484 assert(patlen > 0 && pat != 0);
3485 for (;;) {
3486 while (*p != 0 && (*p)->c < pat[0])
3487 p = &((*p)->right);
3488 if (*p == 0 || (*p)->c != pat[0])
3489 *p = new trie_node(pat[0], *p);
3490 if (--patlen == 0) {
3491 (*p)->val = val;
3492 break;
3494 ++pat;
3495 p = &((*p)->down);
3499 void trie::find(const char *pat, int patlen)
3501 trie_node *p = tp;
3502 for (int i = 0; p != 0 && i < patlen; i++) {
3503 while (p != 0 && p->c < pat[i])
3504 p = p->right;
3505 if (p != 0 && p->c == pat[i]) {
3506 if (p->val != 0)
3507 do_match(i+1, p->val);
3508 p = p->down;
3510 else
3511 break;
3515 struct operation {
3516 operation *next;
3517 short distance;
3518 short num;
3519 operation(int, int, operation *);
3522 operation::operation(int i, int j, operation *op)
3523 : next(op), distance(j), num(i)
3527 void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
3529 operation *op = 0;
3530 for (int i = 0; i < patlen+1; i++)
3531 if (num[i] != 0)
3532 op = new operation(num[i], patlen - i, op);
3533 insert(pat, patlen, op);
3536 void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
3537 int patlen)
3539 char buf[WORD_MAX + 1];
3540 unsigned char pos[WORD_MAX + 2];
3541 int i = 0, j = 0;
3542 int npos = 0;
3543 while (j < patlen) {
3544 unsigned char c = pat[j++];
3545 if (c == '-') {
3546 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3547 pos[npos++] = i;
3549 else
3550 buf[i++] = hpf_code_table[c];
3552 if (i > 0) {
3553 pos[npos] = 0;
3554 buf[i] = 0;
3555 unsigned char *tem = new unsigned char[npos + 1];
3556 memcpy(tem, pos, npos + 1);
3557 tem = (unsigned char *)ex->lookup(symbol(buf), tem);
3558 if (tem)
3559 a_delete tem;
3563 void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
3565 int j;
3566 for (j = 0; j < len + 1; j++)
3567 hyphens[j] = 0;
3568 for (j = 0; j < len - 1; j++) {
3569 h = hyphens + j;
3570 find(word + j, len - j);
3574 inline int max(int m, int n)
3576 return m > n ? m : n;
3579 void hyphen_trie::do_match(int i, void *v)
3581 operation *op = (operation *)v;
3582 while (op != 0) {
3583 h[i - op->distance] = max(h[i - op->distance], op->num);
3584 op = op->next;
3588 void hyphen_trie::do_delete(void *v)
3590 operation *op = (operation *)v;
3591 while (op) {
3592 operation *tem = op;
3593 op = tem->next;
3594 delete tem;
3598 /* We use very simple rules to parse TeX's hyphenation patterns.
3600 . `%' starts a comment even if preceded by `\'.
3602 . No support for digraphs and like `\$'.
3604 . `^^xx' (`x' is 0-9 or a-f), and `^^x' (character code of `x' in the
3605 range 0-127) are recognized; other use of `^' causes an error.
3607 . No macro expansion.
3609 . We check for the expression `\patterns{...}' (possibly with
3610 whitespace before and after the braces). Everything between the
3611 braces is taken as hyphenation patterns. Consequently, `{' and `}'
3612 are not allowed in patterns.
3614 . Similarly, `\hyphenation{...}' gives a list of hyphenation
3615 exceptions.
3617 . `\endinput' is recognized also.
3619 . For backwards compatibility, if `\patterns' is missing, the
3620 whole file is treated as a list of hyphenation patterns (only
3621 recognizing `%' as the start of a comment.
3625 int hyphen_trie::hpf_getc(FILE *f)
3627 int c = getc(f);
3628 int c1;
3629 int cc = 0;
3630 if (c != '^')
3631 return c;
3632 c = getc(f);
3633 if (c != '^')
3634 goto fail;
3635 c = getc(f);
3636 c1 = getc(f);
3637 if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
3638 && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
3639 if (c >= '0' && c <= '9')
3640 c -= '0';
3641 else
3642 c = c - 'a' + 10;
3643 if (c1 >= '0' && c1 <= '9')
3644 c1 -= '0';
3645 else
3646 c1 = c1 - 'a' + 10;
3647 cc = c * 16 + c1;
3649 else {
3650 ungetc(c1, f);
3651 if (c >= 0 && c <= 63)
3652 cc = c + 64;
3653 else if (c >= 64 && c <= 127)
3654 cc = c - 64;
3655 else
3656 goto fail;
3658 return cc;
3659 fail:
3660 error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
3661 return c;
3664 void hyphen_trie::read_patterns_file(const char *name, int append,
3665 dictionary *ex)
3667 if (!append)
3668 clear();
3669 char buf[WORD_MAX];
3670 for (int i = 0; i < WORD_MAX; i++)
3671 buf[i] = 0;
3672 int num[WORD_MAX+1];
3673 errno = 0;
3674 char *path = 0;
3675 FILE *fp = mac_path->open_file(name, &path);
3676 if (fp == 0) {
3677 error("can't find hyphenation patterns file `%1'", name);
3678 return;
3680 int c = hpf_getc(fp);
3681 int have_patterns = 0; // we've seen \patterns
3682 int final_pattern = 0; // 1 if we have a trailing closing brace
3683 int have_hyphenation = 0; // we've seen \hyphenation
3684 int final_hyphenation = 0; // 1 if we have a trailing closing brace
3685 int have_keyword = 0; // we've seen either \patterns or \hyphenation
3686 int traditional = 0; // don't handle \patterns
3687 for (;;) {
3688 for (;;) {
3689 if (c == '%') { // skip comments
3690 do {
3691 c = getc(fp);
3692 } while (c != EOF && c != '\n');
3694 if (c == EOF || !csspace(c))
3695 break;
3696 c = hpf_getc(fp);
3698 if (c == EOF) {
3699 if (have_keyword || traditional) // we are done
3700 break;
3701 else { // rescan file in `traditional' mode
3702 rewind(fp);
3703 traditional = 1;
3704 c = hpf_getc(fp);
3705 continue;
3708 int i = 0;
3709 num[0] = 0;
3710 if (!(c == '{' || c == '}')) { // skip braces at line start
3711 do { // scan patterns
3712 if (csdigit(c))
3713 num[i] = c - '0';
3714 else {
3715 buf[i++] = c;
3716 num[i] = 0;
3718 c = hpf_getc(fp);
3719 } while (i < WORD_MAX && c != EOF && !csspace(c)
3720 && c != '%' && c != '{' && c != '}');
3722 if (!traditional) {
3723 if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
3724 while (csspace(c))
3725 c = hpf_getc(fp);
3726 if (c == '{') {
3727 if (have_patterns || have_hyphenation)
3728 error("\\patterns not allowed inside of %1 group",
3729 have_patterns ? "\\patterns" : "\\hyphenation");
3730 else {
3731 have_patterns = 1;
3732 have_keyword = 1;
3734 c = hpf_getc(fp);
3735 continue;
3738 else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
3739 while (csspace(c))
3740 c = hpf_getc(fp);
3741 if (c == '{') {
3742 if (have_patterns || have_hyphenation)
3743 error("\\hyphenation not allowed inside of %1 group",
3744 have_patterns ? "\\patterns" : "\\hyphenation");
3745 else {
3746 have_hyphenation = 1;
3747 have_keyword = 1;
3749 c = hpf_getc(fp);
3750 continue;
3753 else if (strstr(buf, "\\endinput")) {
3754 if (have_patterns || have_hyphenation)
3755 error("found \\endinput inside of %1 group",
3756 have_patterns ? "\\patterns" : "\\hyphenation");
3757 break;
3759 else if (c == '}') {
3760 if (have_patterns) {
3761 have_patterns = 0;
3762 if (i > 0)
3763 final_pattern = 1;
3765 else if (have_hyphenation) {
3766 have_hyphenation = 0;
3767 if (i > 0)
3768 final_hyphenation = 1;
3770 c = hpf_getc(fp);
3772 else if (c == '{') {
3773 if (have_patterns || have_hyphenation)
3774 error("`{' not allowed within %1 group",
3775 have_patterns ? "\\patterns" : "\\hyphenation");
3776 c = hpf_getc(fp); // skipped if not starting \patterns
3777 // or \hyphenation
3780 else {
3781 if (c == '{' || c == '}')
3782 c = hpf_getc(fp);
3784 if (i > 0) {
3785 if (have_patterns || final_pattern || traditional) {
3786 for (int j = 0; j < i; j++)
3787 buf[j] = hpf_code_table[(unsigned char)buf[j]];
3788 insert_pattern(buf, i, num);
3789 final_pattern = 0;
3791 else if (have_hyphenation || final_hyphenation) {
3792 insert_hyphenation(ex, buf, i);
3793 final_hyphenation = 0;
3797 fclose(fp);
3798 a_delete path;
3799 return;
3802 void hyphenate(hyphen_list *h, unsigned flags)
3804 if (!current_language)
3805 return;
3806 while (h) {
3807 while (h && h->hyphenation_code == 0)
3808 h = h->next;
3809 int len = 0;
3810 char hbuf[WORD_MAX+2];
3811 char *buf = hbuf + 1;
3812 hyphen_list *tem;
3813 for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
3814 if (tem->hyphenation_code != 0)
3815 buf[len++] = tem->hyphenation_code;
3816 else
3817 break;
3819 hyphen_list *nexth = tem;
3820 if (len > 2) {
3821 buf[len] = 0;
3822 unsigned char *pos
3823 = (unsigned char *)current_language->exceptions.lookup(buf);
3824 if (pos != 0) {
3825 int j = 0;
3826 int i = 1;
3827 for (tem = h; tem != 0; tem = tem->next, i++)
3828 if (pos[j] == i) {
3829 tem->hyphen = 1;
3830 j++;
3833 else {
3834 hbuf[0] = hbuf[len+1] = '.';
3835 int num[WORD_MAX+3];
3836 current_language->patterns.hyphenate(hbuf, len+2, num);
3837 int i;
3838 num[2] = 0;
3839 if (flags & 8)
3840 num[3] = 0;
3841 if (flags & 4)
3842 --len;
3843 for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
3844 if (num[i] & 1)
3845 tem->hyphen = 1;
3848 h = nexth;
3852 static void do_hyphenation_patterns_file(int append)
3854 symbol name = get_long_name(1);
3855 if (!name.is_null()) {
3856 if (!current_language)
3857 error("no current hyphenation language");
3858 else
3859 current_language->patterns.read_patterns_file(
3860 name.contents(), append,
3861 &current_language->exceptions);
3863 skip_line();
3866 static void hyphenation_patterns_file()
3868 do_hyphenation_patterns_file(0);
3871 static void hyphenation_patterns_file_append()
3873 do_hyphenation_patterns_file(1);
3876 class hyphenation_language_reg : public reg {
3877 public:
3878 const char *get_string();
3881 const char *hyphenation_language_reg::get_string()
3883 return current_language ? current_language->name.contents() : "";
3886 void init_hyphen_requests()
3888 init_request("hw", hyphen_word);
3889 init_request("hla", set_hyphenation_language);
3890 init_request("hpf", hyphenation_patterns_file);
3891 init_request("hpfa", hyphenation_patterns_file_append);
3892 number_reg_dictionary.define(".hla", new hyphenation_language_reg);