* Makefile.in (SEP): Replaced with...
[s-roff.git] / src / roff / troff / env.cpp
blobb78ebd5cc38c45c48e5b44e862b80dbb2e2b5b9f
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004
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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
22 #include "troff.h"
23 #include "dictionary.h"
24 #include "hvunits.h"
25 #include "env.h"
26 #include "request.h"
27 #include "node.h"
28 #include "token.h"
29 #include "div.h"
30 #include "reg.h"
31 #include "charinfo.h"
32 #include "macropath.h"
33 #include "input.h"
34 #include <math.h>
36 symbol default_family("T");
38 enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 };
40 enum { HYPHEN_LAST_LINE = 2, HYPHEN_LAST_CHARS = 4, HYPHEN_FIRST_CHARS = 8 };
42 struct env_list {
43 environment *env;
44 env_list *next;
45 env_list(environment *e, env_list *p) : env(e), next(p) {}
48 env_list *env_stack;
49 const int NENVIRONMENTS = 10;
50 environment *env_table[NENVIRONMENTS];
51 dictionary env_dictionary(10);
52 environment *curenv;
53 static int next_line_number = 0;
55 charinfo *field_delimiter_char;
56 charinfo *padding_indicator_char;
58 int translate_space_to_dummy = 0;
60 class pending_output_line {
61 node *nd;
62 int no_fill;
63 vunits vs;
64 vunits post_vs;
65 hunits width;
66 #ifdef WIDOW_CONTROL
67 int last_line; // Is it the last line of the paragraph?
68 #endif /* WIDOW_CONTROL */
69 public:
70 pending_output_line *next;
72 pending_output_line(node *, int, vunits, vunits, hunits,
73 pending_output_line * = 0);
74 ~pending_output_line();
75 int output();
77 #ifdef WIDOW_CONTROL
78 friend void environment::mark_last_line();
79 friend void environment::output(node *, int, vunits, vunits, hunits);
80 #endif /* WIDOW_CONTROL */
83 pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv,
84 hunits w, pending_output_line *p)
85 : nd(n), no_fill(nf), vs(v), post_vs(pv), width(w),
86 #ifdef WIDOW_CONTROL
87 last_line(0),
88 #endif /* WIDOW_CONTROL */
89 next(p)
93 pending_output_line::~pending_output_line()
95 delete_node_list(nd);
98 int pending_output_line::output()
100 if (trap_sprung_flag)
101 return 0;
102 #ifdef WIDOW_CONTROL
103 if (next && next->last_line && !no_fill) {
104 curdiv->need(vs + post_vs + vunits(vresolution));
105 if (trap_sprung_flag) {
106 next->last_line = 0; // Try to avoid infinite loops.
107 return 0;
110 #endif
111 curdiv->output(nd, no_fill, vs, post_vs, width);
112 nd = 0;
113 return 1;
116 void environment::output(node *nd, int no_fill, vunits vs, vunits post_vs,
117 hunits width)
119 #ifdef WIDOW_CONTROL
120 while (pending_lines) {
121 if (widow_control && !pending_lines->no_fill && !pending_lines->next)
122 break;
123 if (!pending_lines->output())
124 break;
125 pending_output_line *tem = pending_lines;
126 pending_lines = pending_lines->next;
127 delete tem;
129 #else /* WIDOW_CONTROL */
130 output_pending_lines();
131 #endif /* WIDOW_CONTROL */
132 if (!trap_sprung_flag && !pending_lines
133 #ifdef WIDOW_CONTROL
134 && (!widow_control || no_fill)
135 #endif /* WIDOW_CONTROL */
137 curdiv->output(nd, no_fill, vs, post_vs, width);
138 emitted_node = 1;
139 } else {
140 pending_output_line **p;
141 for (p = &pending_lines; *p; p = &(*p)->next)
143 *p = new pending_output_line(nd, no_fill, vs, post_vs, width);
147 // a line from .tl goes at the head of the queue
149 void environment::output_title(node *nd, int no_fill, vunits vs,
150 vunits post_vs, hunits width)
152 if (!trap_sprung_flag)
153 curdiv->output(nd, no_fill, vs, post_vs, width);
154 else
155 pending_lines = new pending_output_line(nd, no_fill, vs, post_vs, width,
156 pending_lines);
159 void environment::output_pending_lines()
161 while (pending_lines && pending_lines->output()) {
162 pending_output_line *tem = pending_lines;
163 pending_lines = pending_lines->next;
164 delete tem;
168 #ifdef WIDOW_CONTROL
170 void environment::mark_last_line()
172 if (!widow_control || !pending_lines)
173 return;
174 for (pending_output_line *p = pending_lines; p->next; p = p->next)
176 if (!p->no_fill)
177 p->last_line = 1;
180 void widow_control_request()
182 int n;
183 if (has_arg() && get_integer(&n))
184 curenv->widow_control = n != 0;
185 else
186 curenv->widow_control = 1;
187 skip_line();
190 #endif /* WIDOW_CONTROL */
192 /* font_size functions */
194 size_range *font_size::size_table = 0;
195 int font_size::nranges = 0;
197 extern "C" {
199 int compare_ranges(const void *p1, const void *p2)
201 return ((size_range *)p1)->min - ((size_range *)p2)->min;
206 void font_size::init_size_table(int *sizes)
208 nranges = 0;
209 while (sizes[nranges*2] != 0)
210 nranges++;
211 assert(nranges > 0);
212 size_table = new size_range[nranges];
213 for (int i = 0; i < nranges; i++) {
214 size_table[i].min = sizes[i*2];
215 size_table[i].max = sizes[i*2 + 1];
217 qsort(size_table, nranges, sizeof(size_range), compare_ranges);
220 font_size::font_size(int sp)
222 for (int i = 0; i < nranges; i++) {
223 if (sp < size_table[i].min) {
224 if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
225 p = size_table[i - 1].max;
226 else
227 p = size_table[i].min;
228 return;
230 if (sp <= size_table[i].max) {
231 p = sp;
232 return;
235 p = size_table[nranges - 1].max;
238 int font_size::to_units()
240 return scale(p, units_per_inch, sizescale*72);
243 // we can't do this in a static constructor because various dictionaries
244 // have to get initialized first
246 void init_environments()
248 curenv = env_table[0] = new environment("0");
251 void tab_character()
253 curenv->tab_char = get_optional_char();
254 skip_line();
257 void leader_character()
259 curenv->leader_char = get_optional_char();
260 skip_line();
263 void environment::add_char(charinfo *ci)
265 int s;
266 if (interrupted)
268 // don't allow fields in dummy environments
269 else if (ci == field_delimiter_char && !dummy) {
270 if (current_field)
271 wrap_up_field();
272 else
273 start_field();
275 else if (current_field && ci == padding_indicator_char)
276 add_padding();
277 else if (current_tab) {
278 if (tab_contents == 0)
279 tab_contents = new line_start_node;
280 if (ci != hyphen_indicator_char)
281 tab_contents = tab_contents->add_char(ci, this, &tab_width, &s);
282 else
283 tab_contents = tab_contents->add_discretionary_hyphen();
285 else {
286 if (line == 0)
287 start_line();
288 if (ci != hyphen_indicator_char)
289 line = line->add_char(ci, this, &width_total, &space_total);
290 else
291 line = line->add_discretionary_hyphen();
295 node *environment::make_char_node(charinfo *ci)
297 return make_node(ci, this);
300 void environment::add_node(node *n)
302 if (n == 0)
303 return;
304 if (current_tab || current_field)
305 n->freeze_space();
306 if (interrupted) {
307 delete n;
309 else if (current_tab) {
310 n->next = tab_contents;
311 tab_contents = n;
312 tab_width += n->width();
314 else {
315 if (line == 0) {
316 if (discarding && n->discardable()) {
317 // XXX possibly: input_line_start -= n->width();
318 delete n;
319 return;
321 start_line();
323 width_total += n->width();
324 space_total += n->nspaces();
325 n->next = line;
326 line = n;
331 void environment::add_hyphen_indicator()
333 if (current_tab || interrupted || current_field
334 || hyphen_indicator_char != 0)
335 return;
336 if (line == 0)
337 start_line();
338 line = line->add_discretionary_hyphen();
341 int environment::get_hyphenation_flags()
343 return hyphenation_flags;
346 int environment::get_hyphen_line_max()
348 return hyphen_line_max;
351 int environment::get_hyphen_line_count()
353 return hyphen_line_count;
356 int environment::get_center_lines()
358 return center_lines;
361 int environment::get_right_justify_lines()
363 return right_justify_lines;
366 void environment::add_italic_correction()
368 if (current_tab) {
369 if (tab_contents)
370 tab_contents = tab_contents->add_italic_correction(&tab_width);
372 else if (line)
373 line = line->add_italic_correction(&width_total);
376 void environment::space_newline()
378 assert(!current_tab && !current_field);
379 if (interrupted)
380 return;
381 hunits x = H0;
382 hunits sw = env_space_width(this);
383 hunits ssw = env_sentence_space_width(this);
384 if (!translate_space_to_dummy) {
385 x = sw;
386 if (node_list_ends_sentence(line) == 1)
387 x += ssw;
389 width_list *w = new width_list(sw, ssw);
390 if (node_list_ends_sentence(line) == 1)
391 w->next = new width_list(sw, ssw);
392 if (line != 0 && line->merge_space(x, sw, ssw)) {
393 width_total += x;
394 return;
396 add_node(new word_space_node(x, get_fill_color(), w));
397 possibly_break_line(0, spread_flag);
398 spread_flag = 0;
401 void environment::space()
403 space(env_space_width(this), env_sentence_space_width(this));
406 void environment::space(hunits space_width, hunits sentence_space_width)
408 if (interrupted)
409 return;
410 if (current_field && padding_indicator_char == 0) {
411 add_padding();
412 return;
414 hunits x = translate_space_to_dummy ? H0 : space_width;
415 node *p = current_tab ? tab_contents : line;
416 hunits *tp = current_tab ? &tab_width : &width_total;
417 if (p && p->nspaces() == 1 && p->width() == x
418 && node_list_ends_sentence(p->next) == 1) {
419 hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
420 if (p->merge_space(xx, space_width, sentence_space_width)) {
421 *tp += xx;
422 return;
425 if (p && p->merge_space(x, space_width, sentence_space_width)) {
426 *tp += x;
427 return;
429 add_node(new word_space_node(x,
430 get_fill_color(),
431 new width_list(space_width,
432 sentence_space_width)));
433 possibly_break_line(0, spread_flag);
434 spread_flag = 0;
437 node *do_underline_special(int);
439 void environment::set_font(symbol nm)
441 if (interrupted)
442 return;
443 if (nm == symbol("P") || nm.is_empty()) {
444 if (family->make_definite(prev_fontno) < 0)
445 return;
446 int tem = fontno;
447 fontno = prev_fontno;
448 prev_fontno = tem;
450 else {
451 prev_fontno = fontno;
452 int n = symbol_fontno(nm);
453 if (n < 0) {
454 n = next_available_font_position();
455 if (!mount_font(n, nm))
456 return;
458 if (family->make_definite(n) < 0)
459 return;
460 fontno = n;
462 if (underline_spaces && fontno != prev_fontno) {
463 if (fontno == get_underline_fontno())
464 add_node(do_underline_special(1));
465 if (prev_fontno == get_underline_fontno())
466 add_node(do_underline_special(0));
470 void environment::set_font(int n)
472 if (interrupted)
473 return;
474 if (is_good_fontno(n)) {
475 prev_fontno = fontno;
476 fontno = n;
478 else
479 warning(WARN_FONT, "bad font number");
482 void environment::set_family(symbol fam)
484 if (interrupted)
485 return;
486 if (fam.is_null() || fam.is_empty()) {
487 if (prev_family->make_definite(fontno) < 0)
488 return;
489 font_family *tem = family;
490 family = prev_family;
491 prev_family = tem;
493 else {
494 font_family *f = lookup_family(fam);
495 if (f->make_definite(fontno) < 0)
496 return;
497 prev_family = family;
498 family = f;
502 void environment::set_size(int n)
504 if (interrupted)
505 return;
506 if (n == 0) {
507 font_size temp = prev_size;
508 prev_size = size;
509 size = temp;
510 int temp2 = prev_requested_size;
511 prev_requested_size = requested_size;
512 requested_size = temp2;
514 else {
515 prev_size = size;
516 size = font_size(n);
517 prev_requested_size = requested_size;
518 requested_size = n;
522 void environment::set_char_height(int n)
524 if (interrupted)
525 return;
526 if (n == requested_size || n <= 0)
527 char_height = 0;
528 else
529 char_height = n;
532 void environment::set_char_slant(int n)
534 if (interrupted)
535 return;
536 char_slant = n;
539 color *environment::get_prev_glyph_color()
541 return prev_glyph_color;
544 color *environment::get_glyph_color()
546 return glyph_color;
549 color *environment::get_prev_fill_color()
551 return prev_fill_color;
554 color *environment::get_fill_color()
556 return fill_color;
559 void environment::set_glyph_color(color *c)
561 if (interrupted)
562 return;
563 curenv->prev_glyph_color = curenv->glyph_color;
564 curenv->glyph_color = c;
567 void environment::set_fill_color(color *c)
569 if (interrupted)
570 return;
571 curenv->prev_fill_color = curenv->fill_color;
572 curenv->fill_color = c;
575 environment::environment(symbol nm)
576 : dummy(0),
577 prev_line_length((units_per_inch*13)/2),
578 line_length((units_per_inch*13)/2),
579 prev_title_length((units_per_inch*13)/2),
580 title_length((units_per_inch*13)/2),
581 prev_size(sizescale*10),
582 size(sizescale*10),
583 requested_size(sizescale*10),
584 prev_requested_size(sizescale*10),
585 char_height(0),
586 char_slant(0),
587 space_size(12),
588 sentence_space_size(12),
589 adjust_mode(ADJUST_BOTH),
590 fill(1),
591 interrupted(0),
592 prev_line_interrupted(0),
593 center_lines(0),
594 right_justify_lines(0),
595 prev_vertical_spacing(points_to_units(12)),
596 vertical_spacing(points_to_units(12)),
597 prev_post_vertical_spacing(0),
598 post_vertical_spacing(0),
599 prev_line_spacing(1),
600 line_spacing(1),
601 prev_indent(0),
602 indent(0),
603 temporary_indent(0),
604 have_temporary_indent(0),
605 underline_lines(0),
606 underline_spaces(0),
607 input_trap_count(0),
608 continued_input_trap(0),
609 line(0),
610 prev_text_length(0),
611 width_total(0),
612 space_total(0),
613 input_line_start(0),
614 tabs(units_per_inch/2, TAB_LEFT),
615 line_tabs(0),
616 current_tab(TAB_NONE),
617 leader_node(0),
618 tab_char(0),
619 leader_char(charset_table['.']),
620 current_field(0),
621 discarding(0),
622 spread_flag(0),
623 margin_character_flags(0),
624 margin_character_node(0),
625 margin_character_distance(points_to_units(10)),
626 numbering_nodes(0),
627 number_text_separation(1),
628 line_number_indent(0),
629 line_number_multiple(1),
630 no_number_count(0),
631 hyphenation_flags(1),
632 hyphen_line_count(0),
633 hyphen_line_max(-1),
634 hyphenation_space(H0),
635 hyphenation_margin(H0),
636 composite(0),
637 pending_lines(0),
638 #ifdef WIDOW_CONTROL
639 widow_control(0),
640 #endif /* WIDOW_CONTROL */
641 ignore_next_eol(0),
642 emitted_node(0),
643 glyph_color(&default_color),
644 prev_glyph_color(&default_color),
645 fill_color(&default_color),
646 prev_fill_color(&default_color),
647 name(nm),
648 control_char('.'),
649 no_break_control_char('\''),
650 hyphen_indicator_char(0)
652 prev_family = family = lookup_family(default_family);
653 prev_fontno = fontno = 1;
654 if (!is_good_fontno(1))
655 fatal("font number 1 not a valid font");
656 if (family->make_definite(1) < 0)
657 fatal("invalid default family `%1'", default_family.contents());
658 prev_fontno = fontno;
661 environment::environment(const environment *e)
662 : dummy(1),
663 prev_line_length(e->prev_line_length),
664 line_length(e->line_length),
665 prev_title_length(e->prev_title_length),
666 title_length(e->title_length),
667 prev_size(e->prev_size),
668 size(e->size),
669 requested_size(e->requested_size),
670 prev_requested_size(e->prev_requested_size),
671 char_height(e->char_height),
672 char_slant(e->char_slant),
673 prev_fontno(e->prev_fontno),
674 fontno(e->fontno),
675 prev_family(e->prev_family),
676 family(e->family),
677 space_size(e->space_size),
678 sentence_space_size(e->sentence_space_size),
679 adjust_mode(e->adjust_mode),
680 fill(e->fill),
681 interrupted(0),
682 prev_line_interrupted(0),
683 center_lines(0),
684 right_justify_lines(0),
685 prev_vertical_spacing(e->prev_vertical_spacing),
686 vertical_spacing(e->vertical_spacing),
687 prev_post_vertical_spacing(e->prev_post_vertical_spacing),
688 post_vertical_spacing(e->post_vertical_spacing),
689 prev_line_spacing(e->prev_line_spacing),
690 line_spacing(e->line_spacing),
691 prev_indent(e->prev_indent),
692 indent(e->indent),
693 temporary_indent(0),
694 have_temporary_indent(0),
695 underline_lines(0),
696 underline_spaces(0),
697 input_trap_count(0),
698 continued_input_trap(0),
699 line(0),
700 prev_text_length(e->prev_text_length),
701 width_total(0),
702 space_total(0),
703 input_line_start(0),
704 tabs(e->tabs),
705 line_tabs(e->line_tabs),
706 current_tab(TAB_NONE),
707 leader_node(0),
708 tab_char(e->tab_char),
709 leader_char(e->leader_char),
710 current_field(0),
711 discarding(0),
712 spread_flag(0),
713 margin_character_flags(e->margin_character_flags),
714 margin_character_node(e->margin_character_node),
715 margin_character_distance(e->margin_character_distance),
716 numbering_nodes(0),
717 number_text_separation(e->number_text_separation),
718 line_number_indent(e->line_number_indent),
719 line_number_multiple(e->line_number_multiple),
720 no_number_count(e->no_number_count),
721 hyphenation_flags(e->hyphenation_flags),
722 hyphen_line_count(0),
723 hyphen_line_max(e->hyphen_line_max),
724 hyphenation_space(e->hyphenation_space),
725 hyphenation_margin(e->hyphenation_margin),
726 composite(0),
727 pending_lines(0),
728 #ifdef WIDOW_CONTROL
729 widow_control(e->widow_control),
730 #endif /* WIDOW_CONTROL */
731 ignore_next_eol(0),
732 emitted_node(0),
733 glyph_color(e->glyph_color),
734 prev_glyph_color(e->prev_glyph_color),
735 fill_color(e->fill_color),
736 prev_fill_color(e->prev_fill_color),
737 name(e->name), // so that eg `.if "\n[.ev]"0"' works
738 control_char(e->control_char),
739 no_break_control_char(e->no_break_control_char),
740 hyphen_indicator_char(e->hyphen_indicator_char)
744 void environment::copy(const environment *e)
746 prev_line_length = e->prev_line_length;
747 line_length = e->line_length;
748 prev_title_length = e->prev_title_length;
749 title_length = e->title_length;
750 prev_size = e->prev_size;
751 size = e->size;
752 prev_requested_size = e->prev_requested_size;
753 requested_size = e->requested_size;
754 char_height = e->char_height;
755 char_slant = e->char_slant;
756 space_size = e->space_size;
757 sentence_space_size = e->sentence_space_size;
758 adjust_mode = e->adjust_mode;
759 fill = e->fill;
760 interrupted = 0;
761 prev_line_interrupted = 0;
762 center_lines = 0;
763 right_justify_lines = 0;
764 prev_vertical_spacing = e->prev_vertical_spacing;
765 vertical_spacing = e->vertical_spacing;
766 prev_post_vertical_spacing = e->prev_post_vertical_spacing,
767 post_vertical_spacing = e->post_vertical_spacing,
768 prev_line_spacing = e->prev_line_spacing;
769 line_spacing = e->line_spacing;
770 prev_indent = e->prev_indent;
771 indent = e->indent;
772 have_temporary_indent = 0;
773 temporary_indent = 0;
774 underline_lines = 0;
775 underline_spaces = 0;
776 input_trap_count = 0;
777 continued_input_trap = 0;
778 prev_text_length = e->prev_text_length;
779 width_total = 0;
780 space_total = 0;
781 input_line_start = 0;
782 control_char = e->control_char;
783 no_break_control_char = e->no_break_control_char;
784 hyphen_indicator_char = e->hyphen_indicator_char;
785 spread_flag = 0;
786 line = 0;
787 pending_lines = 0;
788 discarding = 0;
789 tabs = e->tabs;
790 line_tabs = e->line_tabs;
791 current_tab = TAB_NONE;
792 current_field = 0;
793 margin_character_flags = e->margin_character_flags;
794 margin_character_node = e->margin_character_node;
795 margin_character_distance = e->margin_character_distance;
796 numbering_nodes = 0;
797 number_text_separation = e->number_text_separation;
798 line_number_multiple = e->line_number_multiple;
799 line_number_indent = e->line_number_indent;
800 no_number_count = e->no_number_count;
801 tab_char = e->tab_char;
802 leader_char = e->leader_char;
803 hyphenation_flags = e->hyphenation_flags;
804 fontno = e->fontno;
805 prev_fontno = e->prev_fontno;
806 dummy = e->dummy;
807 family = e->family;
808 prev_family = e->prev_family;
809 leader_node = 0;
810 #ifdef WIDOW_CONTROL
811 widow_control = e->widow_control;
812 #endif /* WIDOW_CONTROL */
813 hyphen_line_max = e->hyphen_line_max;
814 hyphen_line_count = 0;
815 hyphenation_space = e->hyphenation_space;
816 hyphenation_margin = e->hyphenation_margin;
817 composite = 0;
818 ignore_next_eol = e->ignore_next_eol;
819 emitted_node = e->emitted_node;
820 glyph_color= e->glyph_color;
821 prev_glyph_color = e->prev_glyph_color;
822 fill_color = e->fill_color;
823 prev_fill_color = e->prev_fill_color;
826 environment::~environment()
828 delete leader_node;
829 delete_node_list(line);
830 delete_node_list(numbering_nodes);
833 hunits environment::get_input_line_position()
835 hunits n;
836 if (line == 0)
837 n = -input_line_start;
838 else
839 n = width_total - input_line_start;
840 if (current_tab)
841 n += tab_width;
842 return n;
845 void environment::set_input_line_position(hunits n)
847 input_line_start = line == 0 ? -n : width_total - n;
848 if (current_tab)
849 input_line_start += tab_width;
852 hunits environment::get_line_length()
854 return line_length;
857 hunits environment::get_saved_line_length()
859 if (line)
860 return target_text_length + saved_indent;
861 else
862 return line_length;
865 vunits environment::get_vertical_spacing()
867 return vertical_spacing;
870 vunits environment::get_post_vertical_spacing()
872 return post_vertical_spacing;
875 int environment::get_line_spacing()
877 return line_spacing;
880 vunits environment::total_post_vertical_spacing()
882 vunits tem(post_vertical_spacing);
883 if (line_spacing > 1)
884 tem += (line_spacing - 1)*vertical_spacing;
885 return tem;
888 int environment::get_bold()
890 return get_bold_fontno(fontno);
893 hunits environment::get_digit_width()
895 return env_digit_width(this);
898 int environment::get_adjust_mode()
900 return adjust_mode;
903 int environment::get_fill()
905 return fill;
908 hunits environment::get_indent()
910 return indent;
913 hunits environment::get_saved_indent()
915 if (line)
916 return saved_indent;
917 else if (have_temporary_indent)
918 return temporary_indent;
919 else
920 return indent;
923 hunits environment::get_temporary_indent()
925 return temporary_indent;
928 hunits environment::get_title_length()
930 return title_length;
933 node *environment::get_prev_char()
935 for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
936 node *last = n->last_char_node();
937 if (last)
938 return last;
940 return 0;
943 hunits environment::get_prev_char_width()
945 node *last = get_prev_char();
946 if (!last)
947 return H0;
948 return last->width();
951 hunits environment::get_prev_char_skew()
953 node *last = get_prev_char();
954 if (!last)
955 return H0;
956 return last->skew();
959 vunits environment::get_prev_char_height()
961 node *last = get_prev_char();
962 if (!last)
963 return V0;
964 vunits min, max;
965 last->vertical_extent(&min, &max);
966 return -min;
969 vunits environment::get_prev_char_depth()
971 node *last = get_prev_char();
972 if (!last)
973 return V0;
974 vunits min, max;
975 last->vertical_extent(&min, &max);
976 return max;
979 hunits environment::get_text_length()
981 hunits n = line == 0 ? H0 : width_total;
982 if (current_tab)
983 n += tab_width;
984 return n;
987 hunits environment::get_prev_text_length()
989 return prev_text_length;
993 static int sb_reg_contents = 0;
994 static int st_reg_contents = 0;
995 static int ct_reg_contents = 0;
996 static int rsb_reg_contents = 0;
997 static int rst_reg_contents = 0;
998 static int skw_reg_contents = 0;
999 static int ssc_reg_contents = 0;
1001 void environment::width_registers()
1003 // this is used to implement \w; it sets the st, sb, ct registers
1004 vunits min = 0, max = 0, cur = 0;
1005 int character_type = 0;
1006 ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
1007 skw_reg_contents = line ? line->skew().to_units() : 0;
1008 line = reverse_node_list(line);
1009 vunits real_min = V0;
1010 vunits real_max = V0;
1011 vunits v1, v2;
1012 for (node *tem = line; tem; tem = tem->next) {
1013 tem->vertical_extent(&v1, &v2);
1014 v1 += cur;
1015 if (v1 < real_min)
1016 real_min = v1;
1017 v2 += cur;
1018 if (v2 > real_max)
1019 real_max = v2;
1020 if ((cur += tem->vertical_width()) < min)
1021 min = cur;
1022 else if (cur > max)
1023 max = cur;
1024 character_type |= tem->character_type();
1026 line = reverse_node_list(line);
1027 st_reg_contents = -min.to_units();
1028 sb_reg_contents = -max.to_units();
1029 rst_reg_contents = -real_min.to_units();
1030 rsb_reg_contents = -real_max.to_units();
1031 ct_reg_contents = character_type;
1034 node *environment::extract_output_line()
1036 if (current_tab)
1037 wrap_up_tab();
1038 node *n = line;
1039 line = 0;
1040 return n;
1043 /* environment related requests */
1045 void environment_switch()
1047 int pop = 0; // 1 means pop, 2 means pop but no error message on underflow
1048 if (curenv->is_dummy())
1049 error("can't switch environments when current environment is dummy");
1050 else if (!has_arg())
1051 pop = 1;
1052 else {
1053 symbol nm;
1054 if (!tok.delimiter()) {
1055 // It looks like a number.
1056 int n;
1057 if (get_integer(&n)) {
1058 if (n >= 0 && n < NENVIRONMENTS) {
1059 env_stack = new env_list(curenv, env_stack);
1060 if (env_table[n] == 0)
1061 env_table[n] = new environment(i_to_a(n));
1062 curenv = env_table[n];
1064 else
1065 nm = i_to_a(n);
1067 else
1068 pop = 2;
1070 else {
1071 nm = get_long_name(1);
1072 if (nm.is_null())
1073 pop = 2;
1075 if (!nm.is_null()) {
1076 environment *e = (environment *)env_dictionary.lookup(nm);
1077 if (!e) {
1078 e = new environment(nm);
1079 (void)env_dictionary.lookup(nm, e);
1081 env_stack = new env_list(curenv, env_stack);
1082 curenv = e;
1085 if (pop) {
1086 if (env_stack == 0) {
1087 if (pop == 1)
1088 error("environment stack underflow");
1090 else {
1091 curenv = env_stack->env;
1092 env_list *tem = env_stack;
1093 env_stack = env_stack->next;
1094 delete tem;
1097 skip_line();
1100 void environment_copy()
1102 symbol nm;
1103 environment *e=0;
1104 tok.skip();
1105 if (!tok.delimiter()) {
1106 // It looks like a number.
1107 int n;
1108 if (get_integer(&n)) {
1109 if (n >= 0 && n < NENVIRONMENTS)
1110 e = env_table[n];
1111 else
1112 nm = i_to_a(n);
1115 else
1116 nm = get_long_name(1);
1117 if (!e && !nm.is_null())
1118 e = (environment *)env_dictionary.lookup(nm);
1119 if (e == 0) {
1120 error("No environment to copy from");
1121 return;
1123 else
1124 curenv->copy(e);
1125 skip_line();
1128 static symbol P_symbol("P");
1130 void font_change()
1132 symbol s = get_name();
1133 int is_number = 1;
1134 if (s.is_null() || s == P_symbol) {
1135 s = P_symbol;
1136 is_number = 0;
1138 else {
1139 for (const char *p = s.contents(); p != 0 && *p != 0; p++)
1140 if (!csdigit(*p)) {
1141 is_number = 0;
1142 break;
1145 if (is_number)
1146 curenv->set_font(atoi(s.contents()));
1147 else
1148 curenv->set_font(s);
1149 skip_line();
1152 void family_change()
1154 symbol s = get_name();
1155 curenv->set_family(s);
1156 skip_line();
1159 void point_size()
1161 int n;
1162 if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) {
1163 if (n <= 0)
1164 n = 1;
1165 curenv->set_size(n);
1166 curenv->add_html_tag(0, ".ps", n);
1168 else
1169 curenv->set_size(0);
1170 skip_line();
1173 void override_sizes()
1175 int n = 16;
1176 int *sizes = new int[n];
1177 int i = 0;
1178 char *buf = read_string();
1179 if (!buf)
1180 return;
1181 char *p = strtok(buf, " \t");
1182 for (;;) {
1183 if (!p)
1184 break;
1185 int lower, upper;
1186 switch (sscanf(p, "%d-%d", &lower, &upper)) {
1187 case 1:
1188 upper = lower;
1189 // fall through
1190 case 2:
1191 if (lower <= upper && lower >= 0)
1192 break;
1193 // fall through
1194 default:
1195 warning(WARN_RANGE, "bad size range `%1'", p);
1196 return;
1198 if (i + 2 > n) {
1199 int *old_sizes = sizes;
1200 sizes = new int[n*2];
1201 memcpy(sizes, old_sizes, n*sizeof(int));
1202 n *= 2;
1203 a_delete old_sizes;
1205 sizes[i++] = lower;
1206 if (lower == 0)
1207 break;
1208 sizes[i++] = upper;
1209 p = strtok(0, " \t");
1211 font_size::init_size_table(sizes);
1214 void space_size()
1216 int n;
1217 if (get_integer(&n)) {
1218 curenv->space_size = n;
1219 if (has_arg() && get_integer(&n))
1220 curenv->sentence_space_size = n;
1221 else
1222 curenv->sentence_space_size = curenv->space_size;
1224 skip_line();
1227 void fill()
1229 while (!tok.newline() && !tok.eof())
1230 tok.next();
1231 if (break_flag)
1232 curenv->do_break();
1233 curenv->fill = 1;
1234 curenv->add_html_tag(1, ".fi");
1235 curenv->add_html_tag(0, ".br");
1236 tok.next();
1239 void no_fill()
1241 while (!tok.newline() && !tok.eof())
1242 tok.next();
1243 if (break_flag)
1244 curenv->do_break();
1245 curenv->fill = 0;
1246 curenv->add_html_tag(1, ".nf");
1247 curenv->add_html_tag(0, ".br");
1248 curenv->add_html_tag(0, ".po", topdiv->get_page_offset().to_units());
1249 tok.next();
1252 void center()
1254 int n;
1255 if (!has_arg() || !get_integer(&n))
1256 n = 1;
1257 else if (n < 0)
1258 n = 0;
1259 while (!tok.newline() && !tok.eof())
1260 tok.next();
1261 if (break_flag)
1262 curenv->do_break();
1263 curenv->right_justify_lines = 0;
1264 curenv->center_lines = n;
1265 curenv->add_html_tag(1, ".ce", n);
1266 tok.next();
1269 void right_justify()
1271 int n;
1272 if (!has_arg() || !get_integer(&n))
1273 n = 1;
1274 else if (n < 0)
1275 n = 0;
1276 while (!tok.newline() && !tok.eof())
1277 tok.next();
1278 if (break_flag)
1279 curenv->do_break();
1280 curenv->center_lines = 0;
1281 curenv->right_justify_lines = n;
1282 curenv->add_html_tag(1, ".rj", n);
1283 tok.next();
1286 void line_length()
1288 hunits temp;
1289 if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) {
1290 if (temp < H0) {
1291 warning(WARN_RANGE, "bad line length %1u", temp.to_units());
1292 temp = H0;
1295 else
1296 temp = curenv->prev_line_length;
1297 curenv->prev_line_length = curenv->line_length;
1298 curenv->line_length = temp;
1299 curenv->add_html_tag(1, ".ll", temp.to_units());
1300 skip_line();
1303 void title_length()
1305 hunits temp;
1306 if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) {
1307 if (temp < H0) {
1308 warning(WARN_RANGE, "bad title length %1u", temp.to_units());
1309 temp = H0;
1312 else
1313 temp = curenv->prev_title_length;
1314 curenv->prev_title_length = curenv->title_length;
1315 curenv->title_length = temp;
1316 skip_line();
1319 void vertical_spacing()
1321 vunits temp;
1322 if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) {
1323 if (temp < V0) {
1324 warning(WARN_RANGE, "vertical spacing must not be negative");
1325 temp = vresolution;
1328 else
1329 temp = curenv->prev_vertical_spacing;
1330 curenv->prev_vertical_spacing = curenv->vertical_spacing;
1331 curenv->vertical_spacing = temp;
1332 skip_line();
1335 void post_vertical_spacing()
1337 vunits temp;
1338 if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) {
1339 if (temp < V0) {
1340 warning(WARN_RANGE,
1341 "post vertical spacing must be greater than or equal to 0");
1342 temp = V0;
1345 else
1346 temp = curenv->prev_post_vertical_spacing;
1347 curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
1348 curenv->post_vertical_spacing = temp;
1349 skip_line();
1352 void line_spacing()
1354 int temp;
1355 if (has_arg() && get_integer(&temp)) {
1356 if (temp < 1) {
1357 warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
1358 temp = 1;
1361 else
1362 temp = curenv->prev_line_spacing;
1363 curenv->prev_line_spacing = curenv->line_spacing;
1364 curenv->line_spacing = temp;
1365 skip_line();
1368 void indent()
1370 hunits temp;
1371 if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) {
1372 if (temp < H0) {
1373 warning(WARN_RANGE, "indent cannot be negative");
1374 temp = H0;
1377 else
1378 temp = curenv->prev_indent;
1379 while (!tok.newline() && !tok.eof())
1380 tok.next();
1381 if (break_flag)
1382 curenv->do_break();
1383 curenv->have_temporary_indent = 0;
1384 curenv->prev_indent = curenv->indent;
1385 curenv->indent = temp;
1386 if (break_flag)
1387 curenv->add_html_tag(1, ".in", temp.to_units());
1388 tok.next();
1391 void temporary_indent()
1393 int err = 0;
1394 hunits temp;
1395 if (!get_hunits(&temp, 'm', curenv->get_indent()))
1396 err = 1;
1397 while (!tok.newline() && !tok.eof())
1398 tok.next();
1399 if (break_flag)
1400 curenv->do_break();
1401 if (temp < H0) {
1402 warning(WARN_RANGE, "total indent cannot be negative");
1403 temp = H0;
1405 if (!err) {
1406 curenv->temporary_indent = temp;
1407 curenv->have_temporary_indent = 1;
1408 curenv->add_html_tag(1, ".ti", temp.to_units());
1410 tok.next();
1413 node *do_underline_special(int underline_spaces)
1415 macro m;
1416 m.append_str("x u ");
1417 m.append(underline_spaces + '0');
1418 return new special_node(m, 1);
1421 void do_underline(int underline_spaces)
1423 int n;
1424 if (!has_arg() || !get_integer(&n))
1425 n = 1;
1426 if (n <= 0) {
1427 if (curenv->underline_lines > 0) {
1428 curenv->prev_fontno = curenv->fontno;
1429 curenv->fontno = curenv->pre_underline_fontno;
1430 if (underline_spaces) {
1431 curenv->underline_spaces = 0;
1432 curenv->add_node(do_underline_special(0));
1435 curenv->underline_lines = 0;
1437 else {
1438 curenv->underline_lines = n;
1439 curenv->pre_underline_fontno = curenv->fontno;
1440 curenv->fontno = get_underline_fontno();
1441 if (underline_spaces) {
1442 curenv->underline_spaces = 1;
1443 curenv->add_node(do_underline_special(1));
1446 skip_line();
1449 void continuous_underline()
1451 do_underline(1);
1454 void underline()
1456 do_underline(0);
1459 void control_char()
1461 curenv->control_char = '.';
1462 if (has_arg()) {
1463 if (tok.ch() == 0)
1464 error("bad control character");
1465 else
1466 curenv->control_char = tok.ch();
1468 skip_line();
1471 void no_break_control_char()
1473 curenv->no_break_control_char = '\'';
1474 if (has_arg()) {
1475 if (tok.ch() == 0)
1476 error("bad control character");
1477 else
1478 curenv->no_break_control_char = tok.ch();
1480 skip_line();
1483 void margin_character()
1485 while (tok.space())
1486 tok.next();
1487 charinfo *ci = tok.get_char();
1488 if (ci) {
1489 // Call tok.next() only after making the node so that
1490 // .mc \s+9\(br\s0 works.
1491 node *nd = curenv->make_char_node(ci);
1492 tok.next();
1493 if (nd) {
1494 delete curenv->margin_character_node;
1495 curenv->margin_character_node = nd;
1496 curenv->margin_character_flags = (MARGIN_CHARACTER_ON
1497 |MARGIN_CHARACTER_NEXT);
1498 hunits d;
1499 if (has_arg() && get_hunits(&d, 'm'))
1500 curenv->margin_character_distance = d;
1503 else {
1504 check_missing_character();
1505 curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON;
1506 if (curenv->margin_character_flags == 0) {
1507 delete curenv->margin_character_node;
1508 curenv->margin_character_node = 0;
1511 skip_line();
1514 void number_lines()
1516 delete_node_list(curenv->numbering_nodes);
1517 curenv->numbering_nodes = 0;
1518 if (has_arg()) {
1519 node *nd = 0;
1520 for (int i = '9'; i >= '0'; i--) {
1521 node *tem = make_node(charset_table[i], curenv);
1522 if (!tem) {
1523 skip_line();
1524 return;
1526 tem->next = nd;
1527 nd = tem;
1529 curenv->numbering_nodes = nd;
1530 curenv->line_number_digit_width = env_digit_width(curenv);
1531 int n;
1532 if (!tok.delimiter()) {
1533 if (get_integer(&n, next_line_number)) {
1534 next_line_number = n;
1535 if (next_line_number < 0) {
1536 warning(WARN_RANGE, "negative line number");
1537 next_line_number = 0;
1541 else
1542 while (!tok.space() && !tok.newline() && !tok.eof())
1543 tok.next();
1544 if (has_arg()) {
1545 if (!tok.delimiter()) {
1546 if (get_integer(&n)) {
1547 if (n <= 0) {
1548 warning(WARN_RANGE, "negative or zero line number multiple");
1550 else
1551 curenv->line_number_multiple = n;
1554 else
1555 while (!tok.space() && !tok.newline() && !tok.eof())
1556 tok.next();
1557 if (has_arg()) {
1558 if (!tok.delimiter()) {
1559 if (get_integer(&n))
1560 curenv->number_text_separation = n;
1562 else
1563 while (!tok.space() && !tok.newline() && !tok.eof())
1564 tok.next();
1565 if (has_arg() && !tok.delimiter() && get_integer(&n))
1566 curenv->line_number_indent = n;
1570 skip_line();
1573 void no_number()
1575 int n;
1576 if (has_arg() && get_integer(&n))
1577 curenv->no_number_count = n > 0 ? n : 0;
1578 else
1579 curenv->no_number_count = 1;
1580 skip_line();
1583 void no_hyphenate()
1585 curenv->hyphenation_flags = 0;
1586 skip_line();
1589 void hyphenate_request()
1591 int n;
1592 if (has_arg() && get_integer(&n))
1593 curenv->hyphenation_flags = n;
1594 else
1595 curenv->hyphenation_flags = 1;
1596 skip_line();
1599 void hyphen_char()
1601 curenv->hyphen_indicator_char = get_optional_char();
1602 skip_line();
1605 void hyphen_line_max_request()
1607 int n;
1608 if (has_arg() && get_integer(&n))
1609 curenv->hyphen_line_max = n;
1610 else
1611 curenv->hyphen_line_max = -1;
1612 skip_line();
1615 void environment::interrupt()
1617 if (!dummy) {
1618 add_node(new transparent_dummy_node);
1619 interrupted = 1;
1623 void environment::newline()
1625 if (underline_lines > 0) {
1626 if (--underline_lines == 0) {
1627 prev_fontno = fontno;
1628 fontno = pre_underline_fontno;
1629 if (underline_spaces) {
1630 underline_spaces = 0;
1631 add_node(do_underline_special(0));
1635 if (current_field)
1636 wrap_up_field();
1637 if (current_tab)
1638 wrap_up_tab();
1639 // strip trailing spaces
1640 while (line != 0 && line->discardable()) {
1641 width_total -= line->width();
1642 space_total -= line->nspaces();
1643 node *tem = line;
1644 line = line->next;
1645 delete tem;
1647 node *to_be_output = 0;
1648 hunits to_be_output_width;
1649 prev_line_interrupted = 0;
1650 if (dummy)
1651 space_newline();
1652 else if (interrupted) {
1653 interrupted = 0;
1654 // see environment::final_break
1655 prev_line_interrupted = exit_started ? 2 : 1;
1657 else if (center_lines > 0) {
1658 --center_lines;
1659 hunits x = target_text_length - width_total;
1660 if (x > H0)
1661 saved_indent += x/2;
1662 to_be_output = line;
1663 if (is_html) {
1664 node *n = make_html_tag("eol.ce");
1665 n->next = to_be_output;
1666 to_be_output = n;
1668 to_be_output_width = width_total;
1669 line = 0;
1671 else if (right_justify_lines > 0) {
1672 --right_justify_lines;
1673 hunits x = target_text_length - width_total;
1674 if (x > H0)
1675 saved_indent += x;
1676 to_be_output = line;
1677 to_be_output_width = width_total;
1678 line = 0;
1680 else if (fill)
1681 space_newline();
1682 else {
1683 to_be_output = line;
1684 to_be_output_width = width_total;
1685 line = 0;
1687 input_line_start = line == 0 ? H0 : width_total;
1688 if (to_be_output) {
1689 if (is_html && !fill) {
1690 if (curdiv == topdiv) {
1691 node *n = make_html_tag("eol");
1693 n->next = to_be_output;
1694 to_be_output = n;
1697 output_line(to_be_output, to_be_output_width);
1698 hyphen_line_count = 0;
1700 if (input_trap_count > 0) {
1701 if (!(continued_input_trap && prev_line_interrupted))
1702 if (--input_trap_count == 0)
1703 spring_trap(input_trap);
1707 void environment::output_line(node *n, hunits width)
1709 prev_text_length = width;
1710 if (margin_character_flags) {
1711 hunits d = line_length + margin_character_distance - saved_indent - width;
1712 if (d > 0) {
1713 n = new hmotion_node(d, get_fill_color(), n);
1714 width += d;
1716 margin_character_flags &= ~MARGIN_CHARACTER_NEXT;
1717 node *tem;
1718 if (!margin_character_flags) {
1719 tem = margin_character_node;
1720 margin_character_node = 0;
1722 else
1723 tem = margin_character_node->copy();
1724 tem->next = n;
1725 n = tem;
1726 width += tem->width();
1728 node *nn = 0;
1729 while (n != 0) {
1730 node *tem = n->next;
1731 n->next = nn;
1732 nn = n;
1733 n = tem;
1735 if (!saved_indent.is_zero())
1736 nn = new hmotion_node(saved_indent, get_fill_color(), nn);
1737 width += saved_indent;
1738 if (no_number_count > 0)
1739 --no_number_count;
1740 else if (numbering_nodes) {
1741 hunits w = (line_number_digit_width
1742 *(3+line_number_indent+number_text_separation));
1743 if (next_line_number % line_number_multiple != 0)
1744 nn = new hmotion_node(w, get_fill_color(), nn);
1745 else {
1746 hunits x = w;
1747 nn = new hmotion_node(number_text_separation * line_number_digit_width,
1748 get_fill_color(), nn);
1749 x -= number_text_separation*line_number_digit_width;
1750 char buf[30];
1751 sprintf(buf, "%3d", next_line_number);
1752 for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
1753 node *gn = numbering_nodes;
1754 for (int count = *p - '0'; count > 0; count--)
1755 gn = gn->next;
1756 gn = gn->copy();
1757 x -= gn->width();
1758 gn->next = nn;
1759 nn = gn;
1761 nn = new hmotion_node(x, get_fill_color(), nn);
1763 width += w;
1764 ++next_line_number;
1766 output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width);
1769 void environment::start_line()
1771 assert(line == 0);
1772 discarding = 0;
1773 line = new line_start_node;
1774 if (have_temporary_indent) {
1775 saved_indent = temporary_indent;
1776 have_temporary_indent = 0;
1778 else
1779 saved_indent = indent;
1780 target_text_length = line_length - saved_indent;
1781 width_total = H0;
1782 space_total = 0;
1785 hunits environment::get_hyphenation_space()
1787 return hyphenation_space;
1790 void hyphenation_space_request()
1792 hunits n;
1793 if (get_hunits(&n, 'm')) {
1794 if (n < H0) {
1795 warning(WARN_RANGE, "hyphenation space cannot be negative");
1796 n = H0;
1798 curenv->hyphenation_space = n;
1800 skip_line();
1803 hunits environment::get_hyphenation_margin()
1805 return hyphenation_margin;
1808 void hyphenation_margin_request()
1810 hunits n;
1811 if (get_hunits(&n, 'm')) {
1812 if (n < H0) {
1813 warning(WARN_RANGE, "hyphenation margin cannot be negative");
1814 n = H0;
1816 curenv->hyphenation_margin = n;
1818 skip_line();
1821 breakpoint *environment::choose_breakpoint()
1823 hunits x = width_total;
1824 int s = space_total;
1825 node *n = line;
1826 breakpoint *best_bp = 0; // the best breakpoint so far
1827 int best_bp_fits = 0;
1828 while (n != 0) {
1829 x -= n->width();
1830 s -= n->nspaces();
1831 breakpoint *bp = n->get_breakpoints(x, s);
1832 while (bp != 0) {
1833 if (bp->width <= target_text_length) {
1834 if (!bp->hyphenated) {
1835 breakpoint *tem = bp->next;
1836 bp->next = 0;
1837 while (tem != 0) {
1838 breakpoint *tem1 = tem;
1839 tem = tem->next;
1840 delete tem1;
1842 if (best_bp_fits
1843 // Decide whether to use the hyphenated breakpoint.
1844 && (hyphen_line_max < 0
1845 // Only choose the hyphenated breakpoint if it would not
1846 // exceed the maximum number of consecutive hyphenated
1847 // lines.
1848 || hyphen_line_count + 1 <= hyphen_line_max)
1849 && !(adjust_mode == ADJUST_BOTH
1850 // Don't choose the hyphenated breakpoint if the line
1851 // can be justified by adding no more than
1852 // hyphenation_space to any word space.
1853 ? (bp->nspaces > 0
1854 && (((target_text_length - bp->width
1855 + (bp->nspaces - 1)*hresolution)/bp->nspaces)
1856 <= hyphenation_space))
1857 // Don't choose the hyphenated breakpoint if the line
1858 // is no more than hyphenation_margin short.
1859 : target_text_length - bp->width <= hyphenation_margin)) {
1860 delete bp;
1861 return best_bp;
1863 if (best_bp)
1864 delete best_bp;
1865 return bp;
1867 else {
1868 if ((adjust_mode == ADJUST_BOTH
1869 ? hyphenation_space == H0
1870 : hyphenation_margin == H0)
1871 && (hyphen_line_max < 0
1872 || hyphen_line_count + 1 <= hyphen_line_max)) {
1873 // No need to consider a non-hyphenated breakpoint.
1874 if (best_bp)
1875 delete best_bp;
1876 breakpoint *tem = bp->next;
1877 bp->next = 0;
1878 while (tem != 0) {
1879 breakpoint *tem1 = tem;
1880 tem = tem->next;
1881 delete tem1;
1883 return bp;
1885 // It fits but it's hyphenated.
1886 if (!best_bp_fits) {
1887 if (best_bp)
1888 delete best_bp;
1889 best_bp = bp;
1890 bp = bp->next;
1891 best_bp_fits = 1;
1893 else {
1894 breakpoint *tem = bp;
1895 bp = bp->next;
1896 delete tem;
1900 else {
1901 if (best_bp)
1902 delete best_bp;
1903 best_bp = bp;
1904 bp = bp->next;
1907 n = n->next;
1909 if (best_bp) {
1910 if (!best_bp_fits)
1911 output_warning(WARN_BREAK, "can't break line");
1912 return best_bp;
1914 return 0;
1917 void environment::hyphenate_line(int start_here)
1919 assert(line != 0);
1920 hyphenation_type prev_type = line->get_hyphenation_type();
1921 node **startp;
1922 if (start_here)
1923 startp = &line;
1924 else
1925 for (startp = &line->next; *startp != 0; startp = &(*startp)->next) {
1926 hyphenation_type this_type = (*startp)->get_hyphenation_type();
1927 if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
1928 break;
1929 prev_type = this_type;
1931 if (*startp == 0)
1932 return;
1933 node *tem = *startp;
1934 do {
1935 tem = tem->next;
1936 } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
1937 int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
1938 node *end = tem;
1939 hyphen_list *sl = 0;
1940 tem = *startp;
1941 node *forward = 0;
1942 int i = 0;
1943 while (tem != end) {
1944 sl = tem->get_hyphen_list(sl, &i);
1945 node *tem1 = tem;
1946 tem = tem->next;
1947 tem1->next = forward;
1948 forward = tem1;
1950 if (!inhibit) {
1951 // this is for characters like hyphen and emdash
1952 int prev_code = 0;
1953 for (hyphen_list *h = sl; h; h = h->next) {
1954 h->breakable = (prev_code != 0
1955 && h->next != 0
1956 && h->next->hyphenation_code != 0);
1957 prev_code = h->hyphenation_code;
1960 if (hyphenation_flags != 0
1961 && !inhibit
1962 // this may not be right if we have extra space on this line
1963 && !((hyphenation_flags & HYPHEN_LAST_LINE)
1964 && (curdiv->distance_to_next_trap()
1965 <= vertical_spacing + total_post_vertical_spacing()))
1966 && i >= 4)
1967 hyphenate(sl, hyphenation_flags);
1968 while (forward != 0) {
1969 node *tem1 = forward;
1970 forward = forward->next;
1971 tem1->next = 0;
1972 tem = tem1->add_self(tem, &sl);
1974 *startp = tem;
1977 static node *node_list_reverse(node *n)
1979 node *res = 0;
1980 while (n) {
1981 node *tem = n;
1982 n = n->next;
1983 tem->next = res;
1984 res = tem;
1986 return res;
1989 static void distribute_space(node *n, int nspaces, hunits desired_space,
1990 int force_reverse = 0)
1992 static int reverse = 0;
1993 if (force_reverse || reverse)
1994 n = node_list_reverse(n);
1995 if (!force_reverse && nspaces > 0 && spread_limit >= 0
1996 && desired_space.to_units() > 0) {
1997 hunits em = curenv->get_size();
1998 double Ems = (double)desired_space.to_units() / nspaces
1999 / (em.is_zero() ? hresolution : em.to_units());
2000 if (Ems > spread_limit)
2001 output_warning(WARN_BREAK, "spreading %1m per space", Ems);
2003 for (node *tem = n; tem; tem = tem->next)
2004 tem->spread_space(&nspaces, &desired_space);
2005 if (force_reverse || reverse)
2006 (void)node_list_reverse(n);
2007 if (!force_reverse)
2008 reverse = !reverse;
2009 assert(desired_space.is_zero() && nspaces == 0);
2012 void environment::possibly_break_line(int start_here, int forced)
2014 if (!fill || current_tab || current_field || dummy)
2015 return;
2016 while (line != 0
2017 && (forced
2018 // When a macro follows a paragraph in fill mode, the
2019 // current line should not be empty.
2020 || (width_total - line->width()) > target_text_length)) {
2021 hyphenate_line(start_here);
2022 breakpoint *bp = choose_breakpoint();
2023 if (bp == 0)
2024 // we'll find one eventually
2025 return;
2026 node *pre, *post;
2027 node **ndp = &line;
2028 while (*ndp != bp->nd)
2029 ndp = &(*ndp)->next;
2030 bp->nd->split(bp->index, &pre, &post);
2031 *ndp = post;
2032 hunits extra_space_width = H0;
2033 switch(adjust_mode) {
2034 case ADJUST_BOTH:
2035 if (bp->nspaces != 0)
2036 extra_space_width = target_text_length - bp->width;
2037 else if (bp->width > 0 && target_text_length > 0
2038 && target_text_length > bp->width)
2039 output_warning(WARN_BREAK, "cannot adjust line");
2040 break;
2041 case ADJUST_CENTER:
2042 saved_indent += (target_text_length - bp->width)/2;
2043 break;
2044 case ADJUST_RIGHT:
2045 saved_indent += target_text_length - bp->width;
2046 break;
2048 distribute_space(pre, bp->nspaces, extra_space_width);
2049 hunits output_width = bp->width + extra_space_width;
2050 input_line_start -= output_width;
2051 if (bp->hyphenated)
2052 hyphen_line_count++;
2053 else
2054 hyphen_line_count = 0;
2055 delete bp;
2056 space_total = 0;
2057 width_total = 0;
2058 node *first_non_discardable = 0;
2059 node *tem;
2060 for (tem = line; tem != 0; tem = tem->next)
2061 if (!tem->discardable())
2062 first_non_discardable = tem;
2063 node *to_be_discarded;
2064 if (first_non_discardable) {
2065 to_be_discarded = first_non_discardable->next;
2066 first_non_discardable->next = 0;
2067 for (tem = line; tem != 0; tem = tem->next) {
2068 width_total += tem->width();
2069 space_total += tem->nspaces();
2071 discarding = 0;
2073 else {
2074 discarding = 1;
2075 to_be_discarded = line;
2076 line = 0;
2078 // Do output_line() here so that line will be 0 iff the
2079 // the environment will be empty.
2080 output_line(pre, output_width);
2081 while (to_be_discarded != 0) {
2082 tem = to_be_discarded;
2083 to_be_discarded = to_be_discarded->next;
2084 input_line_start -= tem->width();
2085 delete tem;
2087 if (line != 0) {
2088 if (have_temporary_indent) {
2089 saved_indent = temporary_indent;
2090 have_temporary_indent = 0;
2092 else
2093 saved_indent = indent;
2094 target_text_length = line_length - saved_indent;
2100 Do the break at the end of input after the end macro (if any).
2102 Unix troff behaves as follows: if the last line is
2104 foo bar\c
2106 it will output foo on the current page, and bar on the next page;
2107 if the last line is
2109 foo\c
2113 foo bar
2115 everything will be output on the current page. This behaviour must be
2116 considered a bug.
2118 The problem is that some macro packages rely on this. For example,
2119 the ATK macros have an end macro that emits \c if it needs to print a
2120 table of contents but doesn't do a 'bp in the end macro; instead the
2121 'bp is done in the bottom of page trap. This works with Unix troff,
2122 provided that the current environment is not empty at the end of the
2123 input file.
2125 The following will make macro packages that do that sort of thing work
2126 even if the current environment is empty at the end of the input file.
2127 If the last input line used \c and this line occurred in the end macro,
2128 then we'll force everything out on the current page, but we'll make
2129 sure that the environment isn't empty so that we won't exit at the
2130 bottom of this page.
2133 void environment::final_break()
2135 if (prev_line_interrupted == 2) {
2136 do_break();
2137 add_node(new transparent_dummy_node);
2139 else
2140 do_break();
2144 * add_html_tag - emits a special html-tag: to help post-grohtml understand
2145 * the key troff commands
2148 void environment::add_html_tag(int force, const char *name)
2150 if (!force && (curdiv != topdiv))
2151 return;
2153 if (is_html) {
2155 * need to emit tag for post-grohtml
2156 * but we check to see whether we can emit specials
2158 if (curdiv == topdiv && topdiv->before_first_page)
2159 topdiv->begin_page();
2160 macro *m = new macro;
2161 m->append_str("html-tag:");
2162 for (const char *p = name; *p; p++)
2163 if (!invalid_input_char((unsigned char)*p))
2164 m->append(*p);
2165 curdiv->output(new special_node(*m), 1, 0, 0, 0);
2166 if (strcmp(name, ".nf") == 0)
2167 curenv->ignore_next_eol = 1;
2172 * add_html_tag - emits a special html-tag: to help post-grohtml understand
2173 * the key troff commands, it appends a string representation
2174 * of i.
2177 void environment::add_html_tag(int force, const char *name, int i)
2179 if (!force && (curdiv != topdiv))
2180 return;
2182 if (is_html) {
2184 * need to emit tag for post-grohtml
2185 * but we check to see whether we can emit specials
2187 if (curdiv == topdiv && topdiv->before_first_page)
2188 topdiv->begin_page();
2189 macro *m = new macro;
2190 m->append_str("html-tag:");
2191 for (const char *p = name; *p; p++)
2192 if (!invalid_input_char((unsigned char)*p))
2193 m->append(*p);
2194 m->append(' ');
2195 m->append_int(i);
2196 node *n = new special_node(*m);
2197 curdiv->output(n, 1, 0, 0, 0);
2202 * add_html_tag_tabs - emits the tab settings for post-grohtml
2205 void environment::add_html_tag_tabs(int force)
2207 if (!force && (curdiv != topdiv))
2208 return;
2210 if (is_html) {
2212 * need to emit tag for post-grohtml
2213 * but we check to see whether we can emit specials
2215 if (curdiv == topdiv && topdiv->before_first_page)
2216 topdiv->begin_page();
2217 macro *m = new macro;
2218 hunits d, l;
2219 enum tab_type t;
2220 m->append_str("html-tag:.ta ");
2221 do {
2222 t = curenv->tabs.distance_to_next_tab(l, &d);
2223 l += d;
2224 switch (t) {
2225 case TAB_LEFT:
2226 m->append_str(" L ");
2227 m->append_int(l.to_units());
2228 break;
2229 case TAB_CENTER:
2230 m->append_str(" C ");
2231 m->append_int(l.to_units());
2232 break;
2233 case TAB_RIGHT:
2234 m->append_str(" R ");
2235 m->append_int(l.to_units());
2236 break;
2237 case TAB_NONE:
2238 break;
2240 } while ((t != TAB_NONE) && (l < get_line_length()));
2241 curdiv->output(new special_node(*m), 1, 0, 0, 0);
2245 node *environment::make_html_tag(const char *name, int i)
2247 if (is_html) {
2249 * need to emit tag for post-grohtml
2250 * but we check to see whether we can emit specials
2252 if (curdiv == topdiv && topdiv->before_first_page)
2253 topdiv->begin_page();
2254 macro *m = new macro;
2255 m->append_str("html-tag:");
2256 for (const char *p = name; *p; p++)
2257 if (!invalid_input_char((unsigned char)*p))
2258 m->append(*p);
2259 m->append(' ');
2260 m->append_int(i);
2261 return new special_node(*m);
2263 return 0;
2266 node *environment::make_html_tag(const char *name)
2268 if (is_html) {
2270 * need to emit tag for post-grohtml
2271 * but we check to see whether we can emit specials
2273 if (curdiv == topdiv && topdiv->before_first_page)
2274 topdiv->begin_page();
2275 macro *m = new macro;
2276 m->append_str("html-tag:");
2277 for (const char *p = name; *p; p++)
2278 if (!invalid_input_char((unsigned char)*p))
2279 m->append(*p);
2280 return new special_node(*m);
2282 return 0;
2285 void environment::do_break(int spread)
2287 if (curdiv == topdiv && topdiv->before_first_page) {
2288 topdiv->begin_page();
2289 return;
2291 if (current_tab)
2292 wrap_up_tab();
2293 if (line) {
2294 // this is so that hyphenation works
2295 line = new space_node(H0, get_fill_color(), line);
2296 space_total++;
2297 possibly_break_line(0, spread);
2299 while (line != 0 && line->discardable()) {
2300 width_total -= line->width();
2301 space_total -= line->nspaces();
2302 node *tem = line;
2303 line = line->next;
2304 delete tem;
2306 discarding = 0;
2307 input_line_start = H0;
2308 if (line != 0) {
2309 if (fill) {
2310 switch (adjust_mode) {
2311 case ADJUST_CENTER:
2312 saved_indent += (target_text_length - width_total)/2;
2313 break;
2314 case ADJUST_RIGHT:
2315 saved_indent += target_text_length - width_total;
2316 break;
2319 node *tem = line;
2320 line = 0;
2321 output_line(tem, width_total);
2322 hyphen_line_count = 0;
2324 prev_line_interrupted = 0;
2325 #ifdef WIDOW_CONTROL
2326 mark_last_line();
2327 output_pending_lines();
2328 #endif /* WIDOW_CONTROL */
2331 int environment::is_empty()
2333 return !current_tab && line == 0 && pending_lines == 0;
2336 void do_break_request(int spread)
2338 while (!tok.newline() && !tok.eof())
2339 tok.next();
2340 if (break_flag) {
2341 curenv->do_break(spread);
2342 curenv->add_html_tag(0, ".br");
2344 tok.next();
2347 void break_request()
2349 do_break_request(0);
2352 void break_spread_request()
2354 do_break_request(1);
2357 void title()
2359 if (curdiv == topdiv && topdiv->before_first_page) {
2360 handle_initial_title();
2361 return;
2363 node *part[3];
2364 hunits part_width[3];
2365 part[0] = part[1] = part[2] = 0;
2366 environment env(curenv);
2367 environment *oldenv = curenv;
2368 curenv = &env;
2369 read_title_parts(part, part_width);
2370 curenv = oldenv;
2371 curenv->size = env.size;
2372 curenv->prev_size = env.prev_size;
2373 curenv->requested_size = env.requested_size;
2374 curenv->prev_requested_size = env.prev_requested_size;
2375 curenv->char_height = env.char_height;
2376 curenv->char_slant = env.char_slant;
2377 curenv->fontno = env.fontno;
2378 curenv->prev_fontno = env.prev_fontno;
2379 curenv->glyph_color = env.glyph_color;
2380 curenv->prev_glyph_color = env.prev_glyph_color;
2381 curenv->fill_color = env.fill_color;
2382 curenv->prev_fill_color = env.prev_fill_color;
2383 node *n = 0;
2384 node *p = part[2];
2385 while (p != 0) {
2386 node *tem = p;
2387 p = p->next;
2388 tem->next = n;
2389 n = tem;
2391 hunits title_length(curenv->title_length);
2392 hunits f = title_length - part_width[1];
2393 hunits f2 = f/2;
2394 n = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(), n);
2395 p = part[1];
2396 while (p != 0) {
2397 node *tem = p;
2398 p = p->next;
2399 tem->next = n;
2400 n = tem;
2402 n = new hmotion_node(f - f2 - part_width[0], curenv->get_fill_color(), n);
2403 p = part[0];
2404 while (p != 0) {
2405 node *tem = p;
2406 p = p->next;
2407 tem->next = n;
2408 n = tem;
2410 curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
2411 curenv->total_post_vertical_spacing(), title_length);
2412 curenv->hyphen_line_count = 0;
2413 tok.next();
2416 void adjust()
2418 curenv->adjust_mode |= 1;
2419 if (has_arg()) {
2420 switch (tok.ch()) {
2421 case 'l':
2422 curenv->adjust_mode = ADJUST_LEFT;
2423 break;
2424 case 'r':
2425 curenv->adjust_mode = ADJUST_RIGHT;
2426 break;
2427 case 'c':
2428 curenv->adjust_mode = ADJUST_CENTER;
2429 break;
2430 case 'b':
2431 case 'n':
2432 curenv->adjust_mode = ADJUST_BOTH;
2433 break;
2434 default:
2435 int n;
2436 if (get_integer(&n)) {
2437 if (n < 0)
2438 warning(WARN_RANGE, "negative adjustment mode");
2439 else if (n > 5) {
2440 curenv->adjust_mode = 5;
2441 warning(WARN_RANGE, "adjustment mode `%1' out of range", n);
2443 else
2444 curenv->adjust_mode = n;
2448 skip_line();
2451 void no_adjust()
2453 curenv->adjust_mode &= ~1;
2454 skip_line();
2457 void do_input_trap(int continued)
2459 curenv->input_trap_count = 0;
2460 if (continued)
2461 curenv->continued_input_trap = 1;
2462 int n;
2463 if (has_arg() && get_integer(&n)) {
2464 if (n <= 0)
2465 warning(WARN_RANGE,
2466 "number of lines for input trap must be greater than zero");
2467 else {
2468 symbol s = get_name(1);
2469 if (!s.is_null()) {
2470 curenv->input_trap_count = n;
2471 curenv->input_trap = s;
2475 skip_line();
2478 void input_trap()
2480 do_input_trap(0);
2483 void input_trap_continued()
2485 do_input_trap(1);
2488 /* tabs */
2490 // must not be R or C or L or a legitimate part of a number expression
2491 const char TAB_REPEAT_CHAR = 'T';
2493 struct tab {
2494 tab *next;
2495 hunits pos;
2496 tab_type type;
2497 tab(hunits, tab_type);
2498 enum { BLOCK = 1024 };
2499 static tab *free_list;
2500 void *operator new(size_t);
2501 void operator delete(void *);
2504 tab *tab::free_list = 0;
2506 void *tab::operator new(size_t n)
2508 assert(n == sizeof(tab));
2509 if (!free_list) {
2510 free_list = (tab *)new char[sizeof(tab)*BLOCK];
2511 for (int i = 0; i < BLOCK - 1; i++)
2512 free_list[i].next = free_list + i + 1;
2513 free_list[BLOCK-1].next = 0;
2515 tab *p = free_list;
2516 free_list = (tab *)(free_list->next);
2517 p->next = 0;
2518 return p;
2521 #ifdef __GNUG__
2522 /* cfront can't cope with this. */
2523 inline
2524 #endif
2525 void tab::operator delete(void *p)
2527 if (p) {
2528 ((tab *)p)->next = free_list;
2529 free_list = (tab *)p;
2533 tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2537 tab_stops::tab_stops(hunits distance, tab_type type)
2538 : initial_list(0)
2540 repeated_list = new tab(distance, type);
2543 tab_stops::~tab_stops()
2545 clear();
2548 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
2550 hunits nextpos;
2552 return distance_to_next_tab(curpos, distance, &nextpos);
2555 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance,
2556 hunits *nextpos)
2558 hunits lastpos = 0;
2559 tab *tem;
2560 for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
2561 lastpos = tem->pos;
2562 if (tem) {
2563 *distance = tem->pos - curpos;
2564 *nextpos = tem->pos;
2565 return tem->type;
2567 if (repeated_list == 0)
2568 return TAB_NONE;
2569 hunits base = lastpos;
2570 for (;;) {
2571 for (tem = repeated_list; tem && tem->pos + base <= curpos; tem = tem->next)
2572 lastpos = tem->pos;
2573 if (tem) {
2574 *distance = tem->pos + base - curpos;
2575 *nextpos = tem->pos + base;
2576 return tem->type;
2578 assert(lastpos > 0);
2579 base += lastpos;
2581 return TAB_NONE;
2584 const char *tab_stops::to_string()
2586 static char *buf = 0;
2587 static int buf_size = 0;
2588 // figure out a maximum on the amount of space we can need
2589 int count = 0;
2590 tab *p;
2591 for (p = initial_list; p; p = p->next)
2592 ++count;
2593 for (p = repeated_list; p; p = p->next)
2594 ++count;
2595 // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
2596 int need = count*12 + 3;
2597 if (buf == 0 || need > buf_size) {
2598 if (buf)
2599 a_delete buf;
2600 buf_size = need;
2601 buf = new char[buf_size];
2603 char *ptr = buf;
2604 for (p = initial_list; p; p = p->next) {
2605 strcpy(ptr, i_to_a(p->pos.to_units()));
2606 ptr = strchr(ptr, '\0');
2607 *ptr++ = 'u';
2608 *ptr = '\0';
2609 switch (p->type) {
2610 case TAB_LEFT:
2611 break;
2612 case TAB_RIGHT:
2613 *ptr++ = 'R';
2614 break;
2615 case TAB_CENTER:
2616 *ptr++ = 'C';
2617 break;
2618 case TAB_NONE:
2619 default:
2620 assert(0);
2623 if (repeated_list)
2624 *ptr++ = TAB_REPEAT_CHAR;
2625 for (p = repeated_list; p; p = p->next) {
2626 strcpy(ptr, i_to_a(p->pos.to_units()));
2627 ptr = strchr(ptr, '\0');
2628 *ptr++ = 'u';
2629 *ptr = '\0';
2630 switch (p->type) {
2631 case TAB_LEFT:
2632 break;
2633 case TAB_RIGHT:
2634 *ptr++ = 'R';
2635 break;
2636 case TAB_CENTER:
2637 *ptr++ = 'C';
2638 break;
2639 case TAB_NONE:
2640 default:
2641 assert(0);
2644 *ptr++ = '\0';
2645 return buf;
2648 tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2652 tab_stops::tab_stops(const tab_stops &ts)
2653 : initial_list(0), repeated_list(0)
2655 tab **p = &initial_list;
2656 tab *t = ts.initial_list;
2657 while (t) {
2658 *p = new tab(t->pos, t->type);
2659 t = t->next;
2660 p = &(*p)->next;
2662 p = &repeated_list;
2663 t = ts.repeated_list;
2664 while (t) {
2665 *p = new tab(t->pos, t->type);
2666 t = t->next;
2667 p = &(*p)->next;
2671 void tab_stops::clear()
2673 while (initial_list) {
2674 tab *tem = initial_list;
2675 initial_list = initial_list->next;
2676 delete tem;
2678 while (repeated_list) {
2679 tab *tem = repeated_list;
2680 repeated_list = repeated_list->next;
2681 delete tem;
2685 void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
2687 tab **p;
2688 for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
2690 *p = new tab(pos, type);
2694 void tab_stops::operator=(const tab_stops &ts)
2696 clear();
2697 tab **p = &initial_list;
2698 tab *t = ts.initial_list;
2699 while (t) {
2700 *p = new tab(t->pos, t->type);
2701 t = t->next;
2702 p = &(*p)->next;
2704 p = &repeated_list;
2705 t = ts.repeated_list;
2706 while (t) {
2707 *p = new tab(t->pos, t->type);
2708 t = t->next;
2709 p = &(*p)->next;
2713 void set_tabs()
2715 hunits pos;
2716 hunits prev_pos = 0;
2717 int first = 1;
2718 int repeated = 0;
2719 tab_stops tabs;
2720 while (has_arg()) {
2721 if (tok.ch() == TAB_REPEAT_CHAR) {
2722 tok.next();
2723 repeated = 1;
2724 prev_pos = 0;
2726 if (!get_hunits(&pos, 'm', prev_pos))
2727 break;
2728 tab_type type = TAB_LEFT;
2729 if (tok.ch() == 'C') {
2730 tok.next();
2731 type = TAB_CENTER;
2733 else if (tok.ch() == 'R') {
2734 tok.next();
2735 type = TAB_RIGHT;
2737 else if (tok.ch() == 'L') {
2738 tok.next();
2740 if (pos <= prev_pos && !first)
2741 warning(WARN_RANGE,
2742 "positions of tab stops must be strictly increasing");
2743 else {
2744 tabs.add_tab(pos, type, repeated);
2745 prev_pos = pos;
2746 first = 0;
2749 curenv->tabs = tabs;
2750 curenv->add_html_tag_tabs(1);
2751 skip_line();
2754 const char *environment::get_tabs()
2756 return tabs.to_string();
2759 tab_type environment::distance_to_next_tab(hunits *distance)
2761 return line_tabs
2762 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
2763 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
2766 tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
2768 return line_tabs
2769 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
2770 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
2771 leftpos);
2774 void field_characters()
2776 field_delimiter_char = get_optional_char();
2777 if (field_delimiter_char)
2778 padding_indicator_char = get_optional_char();
2779 else
2780 padding_indicator_char = 0;
2781 skip_line();
2784 void line_tabs_request()
2786 int n;
2787 if (has_arg() && get_integer(&n))
2788 curenv->line_tabs = n != 0;
2789 else
2790 curenv->line_tabs = 1;
2791 skip_line();
2794 int environment::get_line_tabs()
2796 return line_tabs;
2799 void environment::wrap_up_tab()
2801 if (!current_tab)
2802 return;
2803 if (line == 0)
2804 start_line();
2805 hunits tab_amount;
2806 switch (current_tab) {
2807 case TAB_RIGHT:
2808 tab_amount = tab_distance - tab_width;
2809 line = make_tab_node(tab_amount, line);
2810 break;
2811 case TAB_CENTER:
2812 tab_amount = tab_distance - tab_width/2;
2813 line = make_tab_node(tab_amount, line);
2814 break;
2815 case TAB_NONE:
2816 case TAB_LEFT:
2817 default:
2818 assert(0);
2820 width_total += tab_amount;
2821 width_total += tab_width;
2822 if (current_field) {
2823 if (tab_precedes_field) {
2824 pre_field_width += tab_amount;
2825 tab_precedes_field = 0;
2827 field_distance -= tab_amount;
2828 field_spaces += tab_field_spaces;
2830 if (tab_contents != 0) {
2831 node *tem;
2832 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2834 tem->next = line;
2835 line = tab_contents;
2837 tab_field_spaces = 0;
2838 tab_contents = 0;
2839 tab_width = H0;
2840 tab_distance = H0;
2841 current_tab = TAB_NONE;
2844 node *environment::make_tab_node(hunits d, node *next)
2846 if (leader_node != 0 && d < 0) {
2847 error("motion generated by leader cannot be negative");
2848 delete leader_node;
2849 leader_node = 0;
2851 if (!leader_node)
2852 return new hmotion_node(d, 1, 0, get_fill_color(), next);
2853 node *n = new hline_node(d, leader_node, next);
2854 leader_node = 0;
2855 return n;
2858 void environment::handle_tab(int is_leader)
2860 hunits d;
2861 hunits abs;
2862 if (current_tab)
2863 wrap_up_tab();
2864 charinfo *ci = is_leader ? leader_char : tab_char;
2865 delete leader_node;
2866 leader_node = ci ? make_char_node(ci) : 0;
2867 tab_type t = distance_to_next_tab(&d, &abs);
2868 switch (t) {
2869 case TAB_NONE:
2870 return;
2871 case TAB_LEFT:
2872 add_node(make_tab_node(d));
2873 add_node(make_html_tag("tab L", abs.to_units()));
2874 return;
2875 case TAB_RIGHT:
2876 add_node(make_html_tag("tab R", abs.to_units()));
2877 break;
2878 case TAB_CENTER:
2879 add_node(make_html_tag("tab C", abs.to_units()));
2880 break;
2881 default:
2882 assert(0);
2884 tab_width = 0;
2885 tab_distance = d;
2886 tab_contents = 0;
2887 current_tab = t;
2888 tab_field_spaces = 0;
2891 void environment::start_field()
2893 assert(!current_field);
2894 hunits d;
2895 if (distance_to_next_tab(&d) != TAB_NONE) {
2896 pre_field_width = get_text_length();
2897 field_distance = d;
2898 current_field = 1;
2899 field_spaces = 0;
2900 tab_field_spaces = 0;
2901 for (node *p = line; p; p = p->next)
2902 if (p->nspaces()) {
2903 p->freeze_space();
2904 space_total--;
2906 tab_precedes_field = current_tab != TAB_NONE;
2908 else
2909 error("zero field width");
2912 void environment::wrap_up_field()
2914 if (!current_tab && field_spaces == 0)
2915 add_padding();
2916 hunits padding = field_distance - (get_text_length() - pre_field_width);
2917 if (current_tab && tab_field_spaces != 0) {
2918 hunits tab_padding = scale(padding,
2919 tab_field_spaces,
2920 field_spaces + tab_field_spaces);
2921 padding -= tab_padding;
2922 distribute_space(tab_contents, tab_field_spaces, tab_padding, 1);
2923 tab_field_spaces = 0;
2924 tab_width += tab_padding;
2926 if (field_spaces != 0) {
2927 distribute_space(line, field_spaces, padding, 1);
2928 width_total += padding;
2929 if (current_tab) {
2930 // the start of the tab has been moved to the right by padding, so
2931 tab_distance -= padding;
2932 if (tab_distance <= H0) {
2933 // use the next tab stop instead
2934 current_tab = tabs.distance_to_next_tab(get_input_line_position()
2935 - tab_width,
2936 &tab_distance);
2937 if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
2938 width_total += tab_width;
2939 if (current_tab == TAB_LEFT) {
2940 line = make_tab_node(tab_distance, line);
2941 width_total += tab_distance;
2942 current_tab = TAB_NONE;
2944 if (tab_contents != 0) {
2945 node *tem;
2946 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2948 tem->next = line;
2949 line = tab_contents;
2950 tab_contents = 0;
2952 tab_width = H0;
2953 tab_distance = H0;
2958 current_field = 0;
2961 void environment::add_padding()
2963 if (current_tab) {
2964 tab_contents = new space_node(H0, get_fill_color(), tab_contents);
2965 tab_field_spaces++;
2967 else {
2968 if (line == 0)
2969 start_line();
2970 line = new space_node(H0, get_fill_color(), line);
2971 field_spaces++;
2975 typedef int (environment::*INT_FUNCP)();
2976 typedef vunits (environment::*VUNITS_FUNCP)();
2977 typedef hunits (environment::*HUNITS_FUNCP)();
2978 typedef const char *(environment::*STRING_FUNCP)();
2980 class int_env_reg : public reg {
2981 INT_FUNCP func;
2982 public:
2983 int_env_reg(INT_FUNCP);
2984 const char *get_string();
2985 int get_value(units *val);
2988 class vunits_env_reg : public reg {
2989 VUNITS_FUNCP func;
2990 public:
2991 vunits_env_reg(VUNITS_FUNCP f);
2992 const char *get_string();
2993 int get_value(units *val);
2997 class hunits_env_reg : public reg {
2998 HUNITS_FUNCP func;
2999 public:
3000 hunits_env_reg(HUNITS_FUNCP f);
3001 const char *get_string();
3002 int get_value(units *val);
3005 class string_env_reg : public reg {
3006 STRING_FUNCP func;
3007 public:
3008 string_env_reg(STRING_FUNCP);
3009 const char *get_string();
3012 int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
3016 int int_env_reg::get_value(units *val)
3018 *val = (curenv->*func)();
3019 return 1;
3022 const char *int_env_reg::get_string()
3024 return i_to_a((curenv->*func)());
3027 vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
3031 int vunits_env_reg::get_value(units *val)
3033 *val = (curenv->*func)().to_units();
3034 return 1;
3037 const char *vunits_env_reg::get_string()
3039 return i_to_a((curenv->*func)().to_units());
3042 hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
3046 int hunits_env_reg::get_value(units *val)
3048 *val = (curenv->*func)().to_units();
3049 return 1;
3052 const char *hunits_env_reg::get_string()
3054 return i_to_a((curenv->*func)().to_units());
3057 string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
3061 const char *string_env_reg::get_string()
3063 return (curenv->*func)();
3066 class horizontal_place_reg : public general_reg {
3067 public:
3068 horizontal_place_reg();
3069 int get_value(units *);
3070 void set_value(units);
3073 horizontal_place_reg::horizontal_place_reg()
3077 int horizontal_place_reg::get_value(units *res)
3079 *res = curenv->get_input_line_position().to_units();
3080 return 1;
3083 void horizontal_place_reg::set_value(units n)
3085 curenv->set_input_line_position(hunits(n));
3088 const char *environment::get_font_family_string()
3090 return family->nm.contents();
3093 const char *environment::get_glyph_color_string()
3095 return glyph_color->nm.contents();
3098 const char *environment::get_fill_color_string()
3100 return fill_color->nm.contents();
3103 const char *environment::get_font_name_string()
3105 symbol f = get_font_name(fontno, this);
3106 return f.contents();
3109 const char *environment::get_name_string()
3111 return name.contents();
3114 // Convert a quantity in scaled points to ascii decimal fraction.
3116 const char *sptoa(int sp)
3118 assert(sp > 0);
3119 assert(sizescale > 0);
3120 if (sizescale == 1)
3121 return i_to_a(sp);
3122 if (sp % sizescale == 0)
3123 return i_to_a(sp/sizescale);
3124 // See if 1/sizescale is exactly representable as a decimal fraction,
3125 // ie its only prime factors are 2 and 5.
3126 int n = sizescale;
3127 int power2 = 0;
3128 while ((n & 1) == 0) {
3129 n >>= 1;
3130 power2++;
3132 int power5 = 0;
3133 while ((n % 5) == 0) {
3134 n /= 5;
3135 power5++;
3137 if (n == 1) {
3138 int decimal_point = power5 > power2 ? power5 : power2;
3139 if (decimal_point <= 10) {
3140 int factor = 1;
3141 int t;
3142 for (t = decimal_point - power2; --t >= 0;)
3143 factor *= 2;
3144 for (t = decimal_point - power5; --t >= 0;)
3145 factor *= 5;
3146 if (factor == 1 || sp <= INT_MAX/factor)
3147 return if_to_a(sp*factor, decimal_point);
3150 double s = double(sp)/double(sizescale);
3151 double factor = 10.0;
3152 double val = s;
3153 int decimal_point = 0;
3154 do {
3155 double v = ceil(s*factor);
3156 if (v > INT_MAX)
3157 break;
3158 val = v;
3159 factor *= 10.0;
3160 } while (++decimal_point < 10);
3161 return if_to_a(int(val), decimal_point);
3164 const char *environment::get_point_size_string()
3166 return sptoa(curenv->get_point_size());
3169 const char *environment::get_requested_point_size_string()
3171 return sptoa(curenv->get_requested_point_size());
3174 #define init_int_env_reg(name, func) \
3175 number_reg_dictionary.define(name, new int_env_reg(&environment::func))
3177 #define init_vunits_env_reg(name, func) \
3178 number_reg_dictionary.define(name, new vunits_env_reg(&environment::func))
3180 #define init_hunits_env_reg(name, func) \
3181 number_reg_dictionary.define(name, new hunits_env_reg(&environment::func))
3183 #define init_string_env_reg(name, func) \
3184 number_reg_dictionary.define(name, new string_env_reg(&environment::func))
3186 void init_env_requests()
3188 init_request("ad", adjust);
3189 init_request("br", break_request);
3190 init_request("brp", break_spread_request);
3191 init_request("c2", no_break_control_char);
3192 init_request("cc", control_char);
3193 init_request("ce", center);
3194 init_request("cu", continuous_underline);
3195 init_request("ev", environment_switch);
3196 init_request("evc", environment_copy);
3197 init_request("fam", family_change);
3198 init_request("fc", field_characters);
3199 init_request("fi", fill);
3200 init_request("ft", font_change);
3201 init_request("hc", hyphen_char);
3202 init_request("hlm", hyphen_line_max_request);
3203 init_request("hy", hyphenate_request);
3204 init_request("hym", hyphenation_margin_request);
3205 init_request("hys", hyphenation_space_request);
3206 init_request("in", indent);
3207 init_request("it", input_trap);
3208 init_request("itc", input_trap_continued);
3209 init_request("lc", leader_character);
3210 init_request("linetabs", line_tabs_request);
3211 init_request("ll", line_length);
3212 init_request("ls", line_spacing);
3213 init_request("lt", title_length);
3214 init_request("mc", margin_character);
3215 init_request("na", no_adjust);
3216 init_request("nf", no_fill);
3217 init_request("nh", no_hyphenate);
3218 init_request("nm", number_lines);
3219 init_request("nn", no_number);
3220 init_request("ps", point_size);
3221 init_request("pvs", post_vertical_spacing);
3222 init_request("rj", right_justify);
3223 init_request("sizes", override_sizes);
3224 init_request("ss", space_size);
3225 init_request("ta", set_tabs);
3226 init_request("ti", temporary_indent);
3227 init_request("tc", tab_character);
3228 init_request("tl", title);
3229 init_request("ul", underline);
3230 init_request("vs", vertical_spacing);
3231 #ifdef WIDOW_CONTROL
3232 init_request("wdc", widow_control_request);
3233 #endif /* WIDOW_CONTROL */
3234 init_int_env_reg(".b", get_bold);
3235 init_vunits_env_reg(".cdp", get_prev_char_depth);
3236 init_int_env_reg(".ce", get_center_lines);
3237 init_vunits_env_reg(".cht", get_prev_char_height);
3238 init_hunits_env_reg(".csk", get_prev_char_skew);
3239 init_string_env_reg(".ev", get_name_string);
3240 init_int_env_reg(".f", get_font);
3241 init_string_env_reg(".fam", get_font_family_string);
3242 init_string_env_reg(".fn", get_font_name_string);
3243 init_int_env_reg(".height", get_char_height);
3244 init_int_env_reg(".hlc", get_hyphen_line_count);
3245 init_int_env_reg(".hlm", get_hyphen_line_max);
3246 init_int_env_reg(".hy", get_hyphenation_flags);
3247 init_hunits_env_reg(".hym", get_hyphenation_margin);
3248 init_hunits_env_reg(".hys", get_hyphenation_space);
3249 init_hunits_env_reg(".i", get_indent);
3250 init_hunits_env_reg(".in", get_saved_indent);
3251 init_int_env_reg(".int", get_prev_line_interrupted);
3252 init_int_env_reg(".linetabs", get_line_tabs);
3253 init_hunits_env_reg(".lt", get_title_length);
3254 init_int_env_reg(".j", get_adjust_mode);
3255 init_hunits_env_reg(".k", get_text_length);
3256 init_int_env_reg(".L", get_line_spacing);
3257 init_hunits_env_reg(".l", get_line_length);
3258 init_hunits_env_reg(".ll", get_saved_line_length);
3259 init_string_env_reg(".M", get_fill_color_string);
3260 init_string_env_reg(".m", get_glyph_color_string);
3261 init_hunits_env_reg(".n", get_prev_text_length);
3262 init_int_env_reg(".ps", get_point_size);
3263 init_int_env_reg(".psr", get_requested_point_size);
3264 init_vunits_env_reg(".pvs", get_post_vertical_spacing);
3265 init_int_env_reg(".rj", get_right_justify_lines);
3266 init_string_env_reg(".s", get_point_size_string);
3267 init_int_env_reg(".slant", get_char_slant);
3268 init_int_env_reg(".ss", get_space_size);
3269 init_int_env_reg(".sss", get_sentence_space_size);
3270 init_string_env_reg(".sr", get_requested_point_size_string);
3271 init_string_env_reg(".tabs", get_tabs);
3272 init_int_env_reg(".u", get_fill);
3273 init_vunits_env_reg(".v", get_vertical_spacing);
3274 init_hunits_env_reg(".w", get_prev_char_width);
3275 number_reg_dictionary.define("ct", new variable_reg(&ct_reg_contents));
3276 number_reg_dictionary.define("hp", new horizontal_place_reg);
3277 number_reg_dictionary.define("ln", new variable_reg(&next_line_number));
3278 number_reg_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
3279 number_reg_dictionary.define("rst", new variable_reg(&rst_reg_contents));
3280 number_reg_dictionary.define("sb", new variable_reg(&sb_reg_contents));
3281 number_reg_dictionary.define("skw", new variable_reg(&skw_reg_contents));
3282 number_reg_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
3283 number_reg_dictionary.define("st", new variable_reg(&st_reg_contents));
3286 // Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
3288 struct trie_node;
3290 class trie {
3291 trie_node *tp;
3292 virtual void do_match(int len, void *val) = 0;
3293 virtual void do_delete(void *) = 0;
3294 void delete_trie_node(trie_node *);
3295 public:
3296 trie() : tp(0) {}
3297 virtual ~trie(); // virtual to shut up g++
3298 void insert(const char *, int, void *);
3299 // find calls do_match for each match it finds
3300 void find(const char *pat, int patlen);
3301 void clear();
3304 class hyphen_trie : private trie {
3305 int *h;
3306 void do_match(int i, void *v);
3307 void do_delete(void *v);
3308 void insert_pattern(const char *pat, int patlen, int *num);
3309 void insert_hyphenation(dictionary *ex, const char *pat, int patlen);
3310 int hpf_getc(FILE *f);
3311 public:
3312 hyphen_trie() {}
3313 ~hyphen_trie() {}
3314 void hyphenate(const char *word, int len, int *hyphens);
3315 void read_patterns_file(const char *name, int append, dictionary *ex);
3318 struct hyphenation_language {
3319 symbol name;
3320 dictionary exceptions;
3321 hyphen_trie patterns;
3322 hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
3323 ~hyphenation_language() { }
3326 dictionary language_dictionary(5);
3327 hyphenation_language *current_language = 0;
3329 static void set_hyphenation_language()
3331 symbol nm = get_name(1);
3332 if (!nm.is_null()) {
3333 current_language = (hyphenation_language *)language_dictionary.lookup(nm);
3334 if (!current_language) {
3335 current_language = new hyphenation_language(nm);
3336 (void)language_dictionary.lookup(nm, (void *)current_language);
3339 skip_line();
3342 const int WORD_MAX = 256; // we use unsigned char for offsets in
3343 // hyphenation exceptions
3345 static void hyphen_word()
3347 if (!current_language) {
3348 error("no current hyphenation language");
3349 skip_line();
3350 return;
3352 char buf[WORD_MAX + 1];
3353 unsigned char pos[WORD_MAX + 2];
3354 for (;;) {
3355 tok.skip();
3356 if (tok.newline() || tok.eof())
3357 break;
3358 int i = 0;
3359 int npos = 0;
3360 while (i < WORD_MAX && !tok.space() && !tok.newline() && !tok.eof()) {
3361 charinfo *ci = tok.get_char(1);
3362 if (ci == 0) {
3363 skip_line();
3364 return;
3366 tok.next();
3367 if (ci->get_ascii_code() == '-') {
3368 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3369 pos[npos++] = i;
3371 else {
3372 int c = ci->get_hyphenation_code();
3373 if (c == 0)
3374 break;
3375 buf[i++] = c;
3378 if (i > 0) {
3379 pos[npos] = 0;
3380 buf[i] = 0;
3381 unsigned char *tem = new unsigned char[npos + 1];
3382 memcpy(tem, pos, npos + 1);
3383 tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf),
3384 tem);
3385 if (tem)
3386 a_delete tem;
3389 skip_line();
3392 struct trie_node {
3393 char c;
3394 trie_node *down;
3395 trie_node *right;
3396 void *val;
3397 trie_node(char, trie_node *);
3400 trie_node::trie_node(char ch, trie_node *p)
3401 : c(ch), down(0), right(p), val(0)
3405 trie::~trie()
3407 clear();
3410 void trie::clear()
3412 delete_trie_node(tp);
3413 tp = 0;
3417 void trie::delete_trie_node(trie_node *p)
3419 if (p) {
3420 delete_trie_node(p->down);
3421 delete_trie_node(p->right);
3422 if (p->val)
3423 do_delete(p->val);
3424 delete p;
3428 void trie::insert(const char *pat, int patlen, void *val)
3430 trie_node **p = &tp;
3431 assert(patlen > 0 && pat != 0);
3432 for (;;) {
3433 while (*p != 0 && (*p)->c < pat[0])
3434 p = &((*p)->right);
3435 if (*p == 0 || (*p)->c != pat[0])
3436 *p = new trie_node(pat[0], *p);
3437 if (--patlen == 0) {
3438 (*p)->val = val;
3439 break;
3441 ++pat;
3442 p = &((*p)->down);
3446 void trie::find(const char *pat, int patlen)
3448 trie_node *p = tp;
3449 for (int i = 0; p != 0 && i < patlen; i++) {
3450 while (p != 0 && p->c < pat[i])
3451 p = p->right;
3452 if (p != 0 && p->c == pat[i]) {
3453 if (p->val != 0)
3454 do_match(i+1, p->val);
3455 p = p->down;
3457 else
3458 break;
3462 struct operation {
3463 operation *next;
3464 short distance;
3465 short num;
3466 operation(int, int, operation *);
3469 operation::operation(int i, int j, operation *op)
3470 : next(op), distance(j), num(i)
3474 void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
3476 operation *op = 0;
3477 for (int i = 0; i < patlen+1; i++)
3478 if (num[i] != 0)
3479 op = new operation(num[i], patlen - i, op);
3480 insert(pat, patlen, op);
3483 void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
3484 int patlen)
3486 char buf[WORD_MAX + 1];
3487 unsigned char pos[WORD_MAX + 2];
3488 int i = 0, j = 0;
3489 int npos = 0;
3490 while (j < patlen) {
3491 unsigned char c = pat[j++];
3492 if (c == '-') {
3493 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3494 pos[npos++] = i;
3496 else
3497 buf[i++] = hpf_code_table[c];
3499 if (i > 0) {
3500 pos[npos] = 0;
3501 buf[i] = 0;
3502 unsigned char *tem = new unsigned char[npos + 1];
3503 memcpy(tem, pos, npos + 1);
3504 tem = (unsigned char *)ex->lookup(symbol(buf), tem);
3505 if (tem)
3506 a_delete tem;
3510 void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
3512 int j;
3513 for (j = 0; j < len + 1; j++)
3514 hyphens[j] = 0;
3515 for (j = 0; j < len - 1; j++) {
3516 h = hyphens + j;
3517 find(word + j, len - j);
3521 inline int max(int m, int n)
3523 return m > n ? m : n;
3526 void hyphen_trie::do_match(int i, void *v)
3528 operation *op = (operation *)v;
3529 while (op != 0) {
3530 h[i - op->distance] = max(h[i - op->distance], op->num);
3531 op = op->next;
3535 void hyphen_trie::do_delete(void *v)
3537 operation *op = (operation *)v;
3538 while (op) {
3539 operation *tem = op;
3540 op = tem->next;
3541 delete tem;
3545 /* We use very simple rules to parse TeX's hyphenation patterns.
3547 . `%' starts a comment even if preceded by `\'.
3549 . No support for digraphs and like `\$'.
3551 . `^^xx' (`x' is 0-9 or a-f), and `^^x' (character code of `x' in the
3552 range 0-127) are recognized; other use of `^' causes an error.
3554 . No macro expansion.
3556 . We check for the expression `\patterns{...}' (possibly with
3557 whitespace before and after the braces). Everything between the
3558 braces is taken as hyphenation patterns. Consequently, `{' and `}'
3559 are not allowed in patterns.
3561 . Similarly, `\hyphenation{...}' gives a list of hyphenation
3562 exceptions.
3564 . `\endinput' is recognized also.
3566 . For backwards compatibility, if `\patterns' is missing, the
3567 whole file is treated as a list of hyphenation patterns (only
3568 recognizing `%' as the start of a comment.
3572 int hyphen_trie::hpf_getc(FILE *f)
3574 int c = getc(f);
3575 int c1;
3576 int cc = 0;
3577 if (c != '^')
3578 return c;
3579 c = getc(f);
3580 if (c != '^')
3581 goto fail;
3582 c = getc(f);
3583 c1 = getc(f);
3584 if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
3585 && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
3586 if (c >= '0' && c <= '9')
3587 c -= '0';
3588 else
3589 c = c - 'a' + 10;
3590 if (c1 >= '0' && c1 <= '9')
3591 c1 -= '0';
3592 else
3593 c1 = c1 - 'a' + 10;
3594 cc = c * 16 + c1;
3596 else {
3597 ungetc(c1, f);
3598 if (c >= 0 && c <= 63)
3599 cc = c + 64;
3600 else if (c >= 64 && c <= 127)
3601 cc = c - 64;
3602 else
3603 goto fail;
3605 return cc;
3606 fail:
3607 error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
3608 return c;
3611 void hyphen_trie::read_patterns_file(const char *name, int append,
3612 dictionary *ex)
3614 if (!append)
3615 clear();
3616 char buf[WORD_MAX];
3617 for (int i = 0; i < WORD_MAX; i++)
3618 buf[i] = 0;
3619 int num[WORD_MAX+1];
3620 errno = 0;
3621 char *path = 0;
3622 FILE *fp = mac_path->open_file(name, &path);
3623 if (fp == 0) {
3624 error("can't find hyphenation patterns file `%1'", name);
3625 return;
3627 int c = hpf_getc(fp);
3628 int have_patterns = 0; // we've seen \patterns
3629 int final_pattern = 0; // 1 if we have a trailing closing brace
3630 int have_hyphenation = 0; // we've seen \hyphenation
3631 int final_hyphenation = 0; // 1 if we have a trailing closing brace
3632 int have_keyword = 0; // we've seen either \patterns or \hyphenation
3633 int traditional = 0; // don't handle \patterns
3634 for (;;) {
3635 for (;;) {
3636 if (c == '%') { // skip comments
3637 do {
3638 c = getc(fp);
3639 } while (c != EOF && c != '\n');
3641 if (c == EOF || !csspace(c))
3642 break;
3643 c = hpf_getc(fp);
3645 if (c == EOF) {
3646 if (have_keyword || traditional) // we are done
3647 break;
3648 else { // rescan file in `traditional' mode
3649 rewind(fp);
3650 traditional = 1;
3651 c = hpf_getc(fp);
3652 continue;
3655 int i = 0;
3656 num[0] = 0;
3657 if (!(c == '{' || c == '}')) { // skip braces at line start
3658 do { // scan patterns
3659 if (csdigit(c))
3660 num[i] = c - '0';
3661 else {
3662 buf[i++] = c;
3663 num[i] = 0;
3665 c = hpf_getc(fp);
3666 } while (i < WORD_MAX && c != EOF && !csspace(c)
3667 && c != '%' && c != '{' && c != '}');
3669 if (!traditional) {
3670 if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
3671 while (csspace(c))
3672 c = hpf_getc(fp);
3673 if (c == '{') {
3674 if (have_patterns || have_hyphenation)
3675 error("\\patterns not allowed inside of %1 group",
3676 have_patterns ? "\\patterns" : "\\hyphenation");
3677 else {
3678 have_patterns = 1;
3679 have_keyword = 1;
3681 c = hpf_getc(fp);
3682 continue;
3685 else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
3686 while (csspace(c))
3687 c = hpf_getc(fp);
3688 if (c == '{') {
3689 if (have_patterns || have_hyphenation)
3690 error("\\hyphenation not allowed inside of %1 group",
3691 have_patterns ? "\\patterns" : "\\hyphenation");
3692 else {
3693 have_hyphenation = 1;
3694 have_keyword = 1;
3696 c = hpf_getc(fp);
3697 continue;
3700 else if (strstr(buf, "\\endinput")) {
3701 if (have_patterns || have_hyphenation)
3702 error("found \\endinput inside of %1 group",
3703 have_patterns ? "\\patterns" : "\\hyphenation");
3704 break;
3706 else if (c == '}') {
3707 if (have_patterns) {
3708 have_patterns = 0;
3709 if (i > 0)
3710 final_pattern = 1;
3712 else if (have_hyphenation) {
3713 have_hyphenation = 0;
3714 if (i > 0)
3715 final_hyphenation = 1;
3717 c = hpf_getc(fp);
3719 else if (c == '{') {
3720 if (have_patterns || have_hyphenation)
3721 error("`{' not allowed within %1 group",
3722 have_patterns ? "\\patterns" : "\\hyphenation");
3723 c = hpf_getc(fp); // skipped if not starting \patterns
3724 // or \hyphenation
3727 else {
3728 if (c == '{' || c == '}')
3729 c = hpf_getc(fp);
3731 if (i > 0) {
3732 if (have_patterns || final_pattern || traditional) {
3733 for (int j = 0; j < i; j++)
3734 buf[j] = hpf_code_table[(unsigned char)buf[j]];
3735 insert_pattern(buf, i, num);
3736 final_pattern = 0;
3738 else if (have_hyphenation || final_hyphenation) {
3739 insert_hyphenation(ex, buf, i);
3740 final_hyphenation = 0;
3744 fclose(fp);
3745 a_delete path;
3746 return;
3749 void hyphenate(hyphen_list *h, unsigned flags)
3751 if (!current_language)
3752 return;
3753 while (h) {
3754 while (h && h->hyphenation_code == 0)
3755 h = h->next;
3756 int len = 0;
3757 char hbuf[WORD_MAX+2];
3758 char *buf = hbuf + 1;
3759 hyphen_list *tem;
3760 for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
3761 if (tem->hyphenation_code != 0)
3762 buf[len++] = tem->hyphenation_code;
3763 else
3764 break;
3766 hyphen_list *nexth = tem;
3767 if (len > 2) {
3768 buf[len] = 0;
3769 unsigned char *pos
3770 = (unsigned char *)current_language->exceptions.lookup(buf);
3771 if (pos != 0) {
3772 int j = 0;
3773 int i = 1;
3774 for (tem = h; tem != 0; tem = tem->next, i++)
3775 if (pos[j] == i) {
3776 tem->hyphen = 1;
3777 j++;
3780 else {
3781 hbuf[0] = hbuf[len+1] = '.';
3782 int num[WORD_MAX+3];
3783 current_language->patterns.hyphenate(hbuf, len+2, num);
3784 int i;
3785 num[2] = 0;
3786 if (flags & 8)
3787 num[3] = 0;
3788 if (flags & 4)
3789 --len;
3790 for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
3791 if (num[i] & 1)
3792 tem->hyphen = 1;
3795 h = nexth;
3799 static void do_hyphenation_patterns_file(int append)
3801 symbol name = get_long_name(1);
3802 if (!name.is_null()) {
3803 if (!current_language)
3804 error("no current hyphenation language");
3805 else
3806 current_language->patterns.read_patterns_file(
3807 name.contents(), append,
3808 &current_language->exceptions);
3810 skip_line();
3813 static void hyphenation_patterns_file()
3815 do_hyphenation_patterns_file(0);
3818 static void hyphenation_patterns_file_append()
3820 do_hyphenation_patterns_file(1);
3823 class hyphenation_language_reg : public reg {
3824 public:
3825 const char *get_string();
3828 const char *hyphenation_language_reg::get_string()
3830 return current_language ? current_language->name.contents() : "";
3833 void init_hyphen_requests()
3835 init_request("hw", hyphen_word);
3836 init_request("hla", set_hyphenation_language);
3837 init_request("hpf", hyphenation_patterns_file);
3838 init_request("hpfa", hyphenation_patterns_file_append);
3839 number_reg_dictionary.define(".hla", new hyphenation_language_reg);