* src/roff/troff/env.cpp (hyphen_trie::read_patterns_file): Fix loop
[s-roff.git] / src / roff / troff / env.cpp
blob3c7ff02e880110a37a2fdb1b7300277dfc7a1cd0
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003
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 "symbol.h"
24 #include "dictionary.h"
25 #include "hvunits.h"
26 #include "env.h"
27 #include "request.h"
28 #include "node.h"
29 #include "token.h"
30 #include "div.h"
31 #include "reg.h"
32 #include "charinfo.h"
33 #include "macropath.h"
34 #include "input.h"
35 #include <math.h>
37 symbol default_family("T");
39 enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 };
41 enum { HYPHEN_LAST_LINE = 2, HYPHEN_LAST_CHARS = 4, HYPHEN_FIRST_CHARS = 8 };
43 struct env_list {
44 environment *env;
45 env_list *next;
46 env_list(environment *e, env_list *p) : env(e), next(p) {}
49 env_list *env_stack;
50 const int NENVIRONMENTS = 10;
51 environment *env_table[NENVIRONMENTS];
52 dictionary env_dictionary(10);
53 environment *curenv;
54 static int next_line_number = 0;
56 charinfo *field_delimiter_char;
57 charinfo *padding_indicator_char;
59 int translate_space_to_dummy = 0;
61 class pending_output_line {
62 node *nd;
63 int no_fill;
64 vunits vs;
65 vunits post_vs;
66 hunits width;
67 #ifdef WIDOW_CONTROL
68 int last_line; // Is it the last line of the paragraph?
69 #endif /* WIDOW_CONTROL */
70 public:
71 pending_output_line *next;
73 pending_output_line(node *, int, vunits, vunits, hunits,
74 pending_output_line * = 0);
75 ~pending_output_line();
76 int output();
78 #ifdef WIDOW_CONTROL
79 friend void environment::mark_last_line();
80 friend void environment::output(node *, int, vunits, vunits, hunits);
81 #endif /* WIDOW_CONTROL */
84 pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv,
85 hunits w, pending_output_line *p)
86 : nd(n), no_fill(nf), vs(v), post_vs(pv), width(w),
87 #ifdef WIDOW_CONTROL
88 last_line(0),
89 #endif /* WIDOW_CONTROL */
90 next(p)
94 pending_output_line::~pending_output_line()
96 delete_node_list(nd);
99 int pending_output_line::output()
101 if (trap_sprung_flag)
102 return 0;
103 #ifdef WIDOW_CONTROL
104 if (next && next->last_line && !no_fill) {
105 curdiv->need(vs + post_vs + vunits(vresolution));
106 if (trap_sprung_flag) {
107 next->last_line = 0; // Try to avoid infinite loops.
108 return 0;
111 #endif
112 curdiv->output(nd, no_fill, vs, post_vs, width);
113 nd = 0;
114 return 1;
117 void environment::output(node *nd, int no_fill, vunits vs, vunits post_vs,
118 hunits width)
120 #ifdef WIDOW_CONTROL
121 while (pending_lines) {
122 if (widow_control && !pending_lines->no_fill && !pending_lines->next)
123 break;
124 if (!pending_lines->output())
125 break;
126 pending_output_line *tem = pending_lines;
127 pending_lines = pending_lines->next;
128 delete tem;
130 #else /* WIDOW_CONTROL */
131 output_pending_lines();
132 #endif /* WIDOW_CONTROL */
133 if (!trap_sprung_flag && !pending_lines
134 #ifdef WIDOW_CONTROL
135 && (!widow_control || no_fill)
136 #endif /* WIDOW_CONTROL */
138 curdiv->output(nd, no_fill, vs, post_vs, width);
139 emitted_node = 1;
140 } else {
141 pending_output_line **p;
142 for (p = &pending_lines; *p; p = &(*p)->next)
144 *p = new pending_output_line(nd, no_fill, vs, post_vs, width);
148 // a line from .tl goes at the head of the queue
150 void environment::output_title(node *nd, int no_fill, vunits vs,
151 vunits post_vs, hunits width)
153 if (!trap_sprung_flag)
154 curdiv->output(nd, no_fill, vs, post_vs, width);
155 else
156 pending_lines = new pending_output_line(nd, no_fill, vs, post_vs, width,
157 pending_lines);
160 void environment::output_pending_lines()
162 while (pending_lines && pending_lines->output()) {
163 pending_output_line *tem = pending_lines;
164 pending_lines = pending_lines->next;
165 delete tem;
169 #ifdef WIDOW_CONTROL
171 void environment::mark_last_line()
173 if (!widow_control || !pending_lines)
174 return;
175 for (pending_output_line *p = pending_lines; p->next; p = p->next)
177 if (!p->no_fill)
178 p->last_line = 1;
181 void widow_control_request()
183 int n;
184 if (has_arg() && get_integer(&n))
185 curenv->widow_control = n != 0;
186 else
187 curenv->widow_control = 1;
188 skip_line();
191 #endif /* WIDOW_CONTROL */
193 /* font_size functions */
195 size_range *font_size::size_table = 0;
196 int font_size::nranges = 0;
198 extern "C" {
200 int compare_ranges(const void *p1, const void *p2)
202 return ((size_range *)p1)->min - ((size_range *)p2)->min;
207 void font_size::init_size_table(int *sizes)
209 nranges = 0;
210 while (sizes[nranges*2] != 0)
211 nranges++;
212 assert(nranges > 0);
213 size_table = new size_range[nranges];
214 for (int i = 0; i < nranges; i++) {
215 size_table[i].min = sizes[i*2];
216 size_table[i].max = sizes[i*2 + 1];
218 qsort(size_table, nranges, sizeof(size_range), compare_ranges);
221 font_size::font_size(int sp)
223 for (int i = 0; i < nranges; i++) {
224 if (sp < size_table[i].min) {
225 if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
226 p = size_table[i - 1].max;
227 else
228 p = size_table[i].min;
229 return;
231 if (sp <= size_table[i].max) {
232 p = sp;
233 return;
236 p = size_table[nranges - 1].max;
239 int font_size::to_units()
241 return scale(p, units_per_inch, sizescale*72);
244 // we can't do this in a static constructor because various dictionaries
245 // have to get initialized first
247 void init_environments()
249 curenv = env_table[0] = new environment("0");
252 void tab_character()
254 curenv->tab_char = get_optional_char();
255 skip_line();
258 void leader_character()
260 curenv->leader_char = get_optional_char();
261 skip_line();
264 void environment::add_char(charinfo *ci)
266 int s;
267 if (interrupted)
269 // don't allow fields in dummy environments
270 else if (ci == field_delimiter_char && !dummy) {
271 if (current_field)
272 wrap_up_field();
273 else
274 start_field();
276 else if (current_field && ci == padding_indicator_char)
277 add_padding();
278 else if (current_tab) {
279 if (tab_contents == 0)
280 tab_contents = new line_start_node;
281 if (ci != hyphen_indicator_char)
282 tab_contents = tab_contents->add_char(ci, this, &tab_width, &s);
283 else
284 tab_contents = tab_contents->add_discretionary_hyphen();
286 else {
287 if (line == 0)
288 start_line();
289 if (ci != hyphen_indicator_char)
290 line = line->add_char(ci, this, &width_total, &space_total);
291 else
292 line = line->add_discretionary_hyphen();
296 node *environment::make_char_node(charinfo *ci)
298 return make_node(ci, this);
301 void environment::add_node(node *n)
303 if (n == 0)
304 return;
305 if (current_tab || current_field)
306 n->freeze_space();
307 if (interrupted) {
308 delete n;
310 else if (current_tab) {
311 n->next = tab_contents;
312 tab_contents = n;
313 tab_width += n->width();
315 else {
316 if (line == 0) {
317 if (discarding && n->discardable()) {
318 // XXX possibly: input_line_start -= n->width();
319 delete n;
320 return;
322 start_line();
324 width_total += n->width();
325 space_total += n->nspaces();
326 n->next = line;
327 line = n;
332 void environment::add_hyphen_indicator()
334 if (current_tab || interrupted || current_field
335 || hyphen_indicator_char != 0)
336 return;
337 if (line == 0)
338 start_line();
339 line = line->add_discretionary_hyphen();
342 int environment::get_hyphenation_flags()
344 return hyphenation_flags;
347 int environment::get_hyphen_line_max()
349 return hyphen_line_max;
352 int environment::get_hyphen_line_count()
354 return hyphen_line_count;
357 int environment::get_center_lines()
359 return center_lines;
362 int environment::get_right_justify_lines()
364 return right_justify_lines;
367 void environment::add_italic_correction()
369 if (current_tab) {
370 if (tab_contents)
371 tab_contents = tab_contents->add_italic_correction(&tab_width);
373 else if (line)
374 line = line->add_italic_correction(&width_total);
377 void environment::space_newline()
379 assert(!current_tab && !current_field);
380 if (interrupted)
381 return;
382 hunits x = H0;
383 hunits sw = env_space_width(this);
384 hunits ssw = env_sentence_space_width(this);
385 if (!translate_space_to_dummy) {
386 x = sw;
387 if (node_list_ends_sentence(line) == 1)
388 x += ssw;
390 width_list *w = new width_list(sw, ssw);
391 if (node_list_ends_sentence(line) == 1)
392 w->next = new width_list(sw, ssw);
393 if (line != 0 && line->merge_space(x, sw, ssw)) {
394 width_total += x;
395 return;
397 add_node(new word_space_node(x, get_fill_color(), w));
398 possibly_break_line(0, spread_flag);
399 spread_flag = 0;
402 void environment::space()
404 space(env_space_width(this), env_sentence_space_width(this));
407 void environment::space(hunits space_width, hunits sentence_space_width)
409 if (interrupted)
410 return;
411 if (current_field && padding_indicator_char == 0) {
412 add_padding();
413 return;
415 hunits x = translate_space_to_dummy ? H0 : space_width;
416 node *p = current_tab ? tab_contents : line;
417 hunits *tp = current_tab ? &tab_width : &width_total;
418 if (p && p->nspaces() == 1 && p->width() == x
419 && node_list_ends_sentence(p->next) == 1) {
420 hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
421 if (p->merge_space(xx, space_width, sentence_space_width)) {
422 *tp += xx;
423 return;
426 if (p && p->merge_space(x, space_width, sentence_space_width)) {
427 *tp += x;
428 return;
430 add_node(new word_space_node(x,
431 get_fill_color(),
432 new width_list(space_width,
433 sentence_space_width)));
434 possibly_break_line(0, spread_flag);
435 spread_flag = 0;
438 node *do_underline_special(int);
440 void environment::set_font(symbol nm)
442 if (interrupted)
443 return;
444 if (nm == symbol("P") || nm.is_empty()) {
445 if (family->make_definite(prev_fontno) < 0)
446 return;
447 int tem = fontno;
448 fontno = prev_fontno;
449 prev_fontno = tem;
451 else {
452 prev_fontno = fontno;
453 int n = symbol_fontno(nm);
454 if (n < 0) {
455 n = next_available_font_position();
456 if (!mount_font(n, nm))
457 return;
459 if (family->make_definite(n) < 0)
460 return;
461 fontno = n;
463 if (underline_spaces && fontno != prev_fontno) {
464 if (fontno == get_underline_fontno())
465 add_node(do_underline_special(1));
466 if (prev_fontno == get_underline_fontno())
467 add_node(do_underline_special(0));
471 void environment::set_font(int n)
473 if (interrupted)
474 return;
475 if (is_good_fontno(n)) {
476 prev_fontno = fontno;
477 fontno = n;
479 else
480 warning(WARN_FONT, "bad font number");
483 void environment::set_family(symbol fam)
485 if (interrupted)
486 return;
487 if (fam.is_null() || fam.is_empty()) {
488 if (prev_family->make_definite(fontno) < 0)
489 return;
490 font_family *tem = family;
491 family = prev_family;
492 prev_family = tem;
494 else {
495 font_family *f = lookup_family(fam);
496 if (f->make_definite(fontno) < 0)
497 return;
498 prev_family = family;
499 family = f;
503 void environment::set_size(int n)
505 if (interrupted)
506 return;
507 if (n == 0) {
508 font_size temp = prev_size;
509 prev_size = size;
510 size = temp;
511 int temp2 = prev_requested_size;
512 prev_requested_size = requested_size;
513 requested_size = temp2;
515 else {
516 prev_size = size;
517 size = font_size(n);
518 prev_requested_size = requested_size;
519 requested_size = n;
523 void environment::set_char_height(int n)
525 if (interrupted)
526 return;
527 if (n == requested_size || n <= 0)
528 char_height = 0;
529 else
530 char_height = n;
533 void environment::set_char_slant(int n)
535 if (interrupted)
536 return;
537 char_slant = n;
540 color *environment::get_prev_glyph_color()
542 return prev_glyph_color;
545 color *environment::get_glyph_color()
547 return glyph_color;
550 color *environment::get_prev_fill_color()
552 return prev_fill_color;
555 color *environment::get_fill_color()
557 return fill_color;
560 void environment::set_glyph_color(color *c)
562 if (interrupted)
563 return;
564 curenv->prev_glyph_color = curenv->glyph_color;
565 curenv->glyph_color = c;
568 void environment::set_fill_color(color *c)
570 if (interrupted)
571 return;
572 curenv->prev_fill_color = curenv->fill_color;
573 curenv->fill_color = c;
576 environment::environment(symbol nm)
577 : dummy(0),
578 prev_line_length((units_per_inch*13)/2),
579 line_length((units_per_inch*13)/2),
580 prev_title_length((units_per_inch*13)/2),
581 title_length((units_per_inch*13)/2),
582 prev_size(sizescale*10),
583 size(sizescale*10),
584 requested_size(sizescale*10),
585 prev_requested_size(sizescale*10),
586 char_height(0),
587 char_slant(0),
588 space_size(12),
589 sentence_space_size(12),
590 adjust_mode(ADJUST_BOTH),
591 fill(1),
592 interrupted(0),
593 prev_line_interrupted(0),
594 center_lines(0),
595 right_justify_lines(0),
596 prev_vertical_spacing(points_to_units(12)),
597 vertical_spacing(points_to_units(12)),
598 prev_post_vertical_spacing(0),
599 post_vertical_spacing(0),
600 prev_line_spacing(1),
601 line_spacing(1),
602 prev_indent(0),
603 indent(0),
604 temporary_indent(0),
605 have_temporary_indent(0),
606 underline_lines(0),
607 underline_spaces(0),
608 input_trap_count(0),
609 continued_input_trap(0),
610 line(0),
611 prev_text_length(0),
612 width_total(0),
613 space_total(0),
614 input_line_start(0),
615 tabs(units_per_inch/2, TAB_LEFT),
616 line_tabs(0),
617 current_tab(TAB_NONE),
618 leader_node(0),
619 tab_char(0),
620 leader_char(charset_table['.']),
621 current_field(0),
622 discarding(0),
623 spread_flag(0),
624 margin_character_flags(0),
625 margin_character_node(0),
626 margin_character_distance(points_to_units(10)),
627 numbering_nodes(0),
628 number_text_separation(1),
629 line_number_indent(0),
630 line_number_multiple(1),
631 no_number_count(0),
632 hyphenation_flags(1),
633 hyphen_line_count(0),
634 hyphen_line_max(-1),
635 hyphenation_space(H0),
636 hyphenation_margin(H0),
637 composite(0),
638 pending_lines(0),
639 #ifdef WIDOW_CONTROL
640 widow_control(0),
641 #endif /* WIDOW_CONTROL */
642 ignore_next_eol(0),
643 emitted_node(0),
644 glyph_color(&default_color),
645 prev_glyph_color(&default_color),
646 fill_color(&default_color),
647 prev_fill_color(&default_color),
648 name(nm),
649 control_char('.'),
650 no_break_control_char('\''),
651 hyphen_indicator_char(0)
653 prev_family = family = lookup_family(default_family);
654 prev_fontno = fontno = 1;
655 if (!is_good_fontno(1))
656 fatal("font number 1 not a valid font");
657 if (family->make_definite(1) < 0)
658 fatal("invalid default family `%1'", default_family.contents());
659 prev_fontno = fontno;
662 environment::environment(const environment *e)
663 : dummy(1),
664 prev_line_length(e->prev_line_length),
665 line_length(e->line_length),
666 prev_title_length(e->prev_title_length),
667 title_length(e->title_length),
668 prev_size(e->prev_size),
669 size(e->size),
670 requested_size(e->requested_size),
671 prev_requested_size(e->prev_requested_size),
672 char_height(e->char_height),
673 char_slant(e->char_slant),
674 prev_fontno(e->prev_fontno),
675 fontno(e->fontno),
676 prev_family(e->prev_family),
677 family(e->family),
678 space_size(e->space_size),
679 sentence_space_size(e->sentence_space_size),
680 adjust_mode(e->adjust_mode),
681 fill(e->fill),
682 interrupted(0),
683 prev_line_interrupted(0),
684 center_lines(0),
685 right_justify_lines(0),
686 prev_vertical_spacing(e->prev_vertical_spacing),
687 vertical_spacing(e->vertical_spacing),
688 prev_post_vertical_spacing(e->prev_post_vertical_spacing),
689 post_vertical_spacing(e->post_vertical_spacing),
690 prev_line_spacing(e->prev_line_spacing),
691 line_spacing(e->line_spacing),
692 prev_indent(e->prev_indent),
693 indent(e->indent),
694 temporary_indent(0),
695 have_temporary_indent(0),
696 underline_lines(0),
697 underline_spaces(0),
698 input_trap_count(0),
699 continued_input_trap(0),
700 line(0),
701 prev_text_length(e->prev_text_length),
702 width_total(0),
703 space_total(0),
704 input_line_start(0),
705 tabs(e->tabs),
706 line_tabs(e->line_tabs),
707 current_tab(TAB_NONE),
708 leader_node(0),
709 tab_char(e->tab_char),
710 leader_char(e->leader_char),
711 current_field(0),
712 discarding(0),
713 spread_flag(0),
714 margin_character_flags(e->margin_character_flags),
715 margin_character_node(e->margin_character_node),
716 margin_character_distance(e->margin_character_distance),
717 numbering_nodes(0),
718 number_text_separation(e->number_text_separation),
719 line_number_indent(e->line_number_indent),
720 line_number_multiple(e->line_number_multiple),
721 no_number_count(e->no_number_count),
722 hyphenation_flags(e->hyphenation_flags),
723 hyphen_line_count(0),
724 hyphen_line_max(e->hyphen_line_max),
725 hyphenation_space(e->hyphenation_space),
726 hyphenation_margin(e->hyphenation_margin),
727 composite(0),
728 pending_lines(0),
729 #ifdef WIDOW_CONTROL
730 widow_control(e->widow_control),
731 #endif /* WIDOW_CONTROL */
732 ignore_next_eol(0),
733 emitted_node(0),
734 glyph_color(e->glyph_color),
735 prev_glyph_color(e->prev_glyph_color),
736 fill_color(e->fill_color),
737 prev_fill_color(e->prev_fill_color),
738 name(e->name), // so that eg `.if "\n[.ev]"0"' works
739 control_char(e->control_char),
740 no_break_control_char(e->no_break_control_char),
741 hyphen_indicator_char(e->hyphen_indicator_char)
745 void environment::copy(const environment *e)
747 prev_line_length = e->prev_line_length;
748 line_length = e->line_length;
749 prev_title_length = e->prev_title_length;
750 title_length = e->title_length;
751 prev_size = e->prev_size;
752 size = e->size;
753 prev_requested_size = e->prev_requested_size;
754 requested_size = e->requested_size;
755 char_height = e->char_height;
756 char_slant = e->char_slant;
757 space_size = e->space_size;
758 sentence_space_size = e->sentence_space_size;
759 adjust_mode = e->adjust_mode;
760 fill = e->fill;
761 interrupted = 0;
762 prev_line_interrupted = 0;
763 center_lines = 0;
764 right_justify_lines = 0;
765 prev_vertical_spacing = e->prev_vertical_spacing;
766 vertical_spacing = e->vertical_spacing;
767 prev_post_vertical_spacing = e->prev_post_vertical_spacing,
768 post_vertical_spacing = e->post_vertical_spacing,
769 prev_line_spacing = e->prev_line_spacing;
770 line_spacing = e->line_spacing;
771 prev_indent = e->prev_indent;
772 indent = e->indent;
773 have_temporary_indent = 0;
774 temporary_indent = 0;
775 underline_lines = 0;
776 underline_spaces = 0;
777 input_trap_count = 0;
778 continued_input_trap = 0;
779 prev_text_length = e->prev_text_length;
780 width_total = 0;
781 space_total = 0;
782 input_line_start = 0;
783 control_char = e->control_char;
784 no_break_control_char = e->no_break_control_char;
785 hyphen_indicator_char = e->hyphen_indicator_char;
786 spread_flag = 0;
787 line = 0;
788 pending_lines = 0;
789 discarding = 0;
790 tabs = e->tabs;
791 line_tabs = e->line_tabs;
792 current_tab = TAB_NONE;
793 current_field = 0;
794 margin_character_flags = e->margin_character_flags;
795 margin_character_node = e->margin_character_node;
796 margin_character_distance = e->margin_character_distance;
797 numbering_nodes = 0;
798 number_text_separation = e->number_text_separation;
799 line_number_multiple = e->line_number_multiple;
800 line_number_indent = e->line_number_indent;
801 no_number_count = e->no_number_count;
802 tab_char = e->tab_char;
803 leader_char = e->leader_char;
804 hyphenation_flags = e->hyphenation_flags;
805 fontno = e->fontno;
806 prev_fontno = e->prev_fontno;
807 dummy = e->dummy;
808 family = e->family;
809 prev_family = e->prev_family;
810 leader_node = 0;
811 #ifdef WIDOW_CONTROL
812 widow_control = e->widow_control;
813 #endif /* WIDOW_CONTROL */
814 hyphen_line_max = e->hyphen_line_max;
815 hyphen_line_count = 0;
816 hyphenation_space = e->hyphenation_space;
817 hyphenation_margin = e->hyphenation_margin;
818 composite = 0;
819 ignore_next_eol = e->ignore_next_eol;
820 emitted_node = e->emitted_node;
821 glyph_color= e->glyph_color;
822 prev_glyph_color = e->prev_glyph_color;
823 fill_color = e->fill_color;
824 prev_fill_color = e->prev_fill_color;
827 environment::~environment()
829 delete leader_node;
830 delete_node_list(line);
831 delete_node_list(numbering_nodes);
834 hunits environment::get_input_line_position()
836 hunits n;
837 if (line == 0)
838 n = -input_line_start;
839 else
840 n = width_total - input_line_start;
841 if (current_tab)
842 n += tab_width;
843 return n;
846 void environment::set_input_line_position(hunits n)
848 input_line_start = line == 0 ? -n : width_total - n;
849 if (current_tab)
850 input_line_start += tab_width;
853 hunits environment::get_line_length()
855 return line_length;
858 hunits environment::get_saved_line_length()
860 if (line)
861 return target_text_length + saved_indent;
862 else
863 return line_length;
866 vunits environment::get_vertical_spacing()
868 return vertical_spacing;
871 vunits environment::get_post_vertical_spacing()
873 return post_vertical_spacing;
876 int environment::get_line_spacing()
878 return line_spacing;
881 vunits environment::total_post_vertical_spacing()
883 vunits tem(post_vertical_spacing);
884 if (line_spacing > 1)
885 tem += (line_spacing - 1)*vertical_spacing;
886 return tem;
889 int environment::get_bold()
891 return get_bold_fontno(fontno);
894 hunits environment::get_digit_width()
896 return env_digit_width(this);
899 int environment::get_adjust_mode()
901 return adjust_mode;
904 int environment::get_fill()
906 return fill;
909 hunits environment::get_indent()
911 return indent;
914 hunits environment::get_saved_indent()
916 if (line)
917 return saved_indent;
918 else if (have_temporary_indent)
919 return temporary_indent;
920 else
921 return indent;
924 hunits environment::get_temporary_indent()
926 return temporary_indent;
929 hunits environment::get_title_length()
931 return title_length;
934 node *environment::get_prev_char()
936 for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
937 node *last = n->last_char_node();
938 if (last)
939 return last;
941 return 0;
944 hunits environment::get_prev_char_width()
946 node *last = get_prev_char();
947 if (!last)
948 return H0;
949 return last->width();
952 hunits environment::get_prev_char_skew()
954 node *last = get_prev_char();
955 if (!last)
956 return H0;
957 return last->skew();
960 vunits environment::get_prev_char_height()
962 node *last = get_prev_char();
963 if (!last)
964 return V0;
965 vunits min, max;
966 last->vertical_extent(&min, &max);
967 return -min;
970 vunits environment::get_prev_char_depth()
972 node *last = get_prev_char();
973 if (!last)
974 return V0;
975 vunits min, max;
976 last->vertical_extent(&min, &max);
977 return max;
980 hunits environment::get_text_length()
982 hunits n = line == 0 ? H0 : width_total;
983 if (current_tab)
984 n += tab_width;
985 return n;
988 hunits environment::get_prev_text_length()
990 return prev_text_length;
994 static int sb_reg_contents = 0;
995 static int st_reg_contents = 0;
996 static int ct_reg_contents = 0;
997 static int rsb_reg_contents = 0;
998 static int rst_reg_contents = 0;
999 static int skw_reg_contents = 0;
1000 static int ssc_reg_contents = 0;
1002 void environment::width_registers()
1004 // this is used to implement \w; it sets the st, sb, ct registers
1005 vunits min = 0, max = 0, cur = 0;
1006 int character_type = 0;
1007 ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
1008 skw_reg_contents = line ? line->skew().to_units() : 0;
1009 line = reverse_node_list(line);
1010 vunits real_min = V0;
1011 vunits real_max = V0;
1012 vunits v1, v2;
1013 for (node *tem = line; tem; tem = tem->next) {
1014 tem->vertical_extent(&v1, &v2);
1015 v1 += cur;
1016 if (v1 < real_min)
1017 real_min = v1;
1018 v2 += cur;
1019 if (v2 > real_max)
1020 real_max = v2;
1021 if ((cur += tem->vertical_width()) < min)
1022 min = cur;
1023 else if (cur > max)
1024 max = cur;
1025 character_type |= tem->character_type();
1027 line = reverse_node_list(line);
1028 st_reg_contents = -min.to_units();
1029 sb_reg_contents = -max.to_units();
1030 rst_reg_contents = -real_min.to_units();
1031 rsb_reg_contents = -real_max.to_units();
1032 ct_reg_contents = character_type;
1035 node *environment::extract_output_line()
1037 if (current_tab)
1038 wrap_up_tab();
1039 node *n = line;
1040 line = 0;
1041 return n;
1044 /* environment related requests */
1046 void environment_switch()
1048 int pop = 0; // 1 means pop, 2 means pop but no error message on underflow
1049 if (curenv->is_dummy())
1050 error("can't switch environments when current environment is dummy");
1051 else if (!has_arg())
1052 pop = 1;
1053 else {
1054 symbol nm;
1055 if (!tok.delimiter()) {
1056 // It looks like a number.
1057 int n;
1058 if (get_integer(&n)) {
1059 if (n >= 0 && n < NENVIRONMENTS) {
1060 env_stack = new env_list(curenv, env_stack);
1061 if (env_table[n] == 0)
1062 env_table[n] = new environment(i_to_a(n));
1063 curenv = env_table[n];
1065 else
1066 nm = i_to_a(n);
1068 else
1069 pop = 2;
1071 else {
1072 nm = get_long_name(1);
1073 if (nm.is_null())
1074 pop = 2;
1076 if (!nm.is_null()) {
1077 environment *e = (environment *)env_dictionary.lookup(nm);
1078 if (!e) {
1079 e = new environment(nm);
1080 (void)env_dictionary.lookup(nm, e);
1082 env_stack = new env_list(curenv, env_stack);
1083 curenv = e;
1086 if (pop) {
1087 if (env_stack == 0) {
1088 if (pop == 1)
1089 error("environment stack underflow");
1091 else {
1092 curenv = env_stack->env;
1093 env_list *tem = env_stack;
1094 env_stack = env_stack->next;
1095 delete tem;
1098 skip_line();
1101 void environment_copy()
1103 symbol nm;
1104 environment *e=0;
1105 tok.skip();
1106 if (!tok.delimiter()) {
1107 // It looks like a number.
1108 int n;
1109 if (get_integer(&n)) {
1110 if (n >= 0 && n < NENVIRONMENTS)
1111 e = env_table[n];
1112 else
1113 nm = i_to_a(n);
1116 else
1117 nm = get_long_name(1);
1118 if (!e && !nm.is_null())
1119 e = (environment *)env_dictionary.lookup(nm);
1120 if (e == 0) {
1121 error("No environment to copy from");
1122 return;
1124 else
1125 curenv->copy(e);
1126 skip_line();
1129 static symbol P_symbol("P");
1131 void font_change()
1133 symbol s = get_name();
1134 int is_number = 1;
1135 if (s.is_null() || s == P_symbol) {
1136 s = P_symbol;
1137 is_number = 0;
1139 else {
1140 for (const char *p = s.contents(); p != 0 && *p != 0; p++)
1141 if (!csdigit(*p)) {
1142 is_number = 0;
1143 break;
1146 if (is_number)
1147 curenv->set_font(atoi(s.contents()));
1148 else
1149 curenv->set_font(s);
1150 skip_line();
1153 void family_change()
1155 symbol s = get_name();
1156 curenv->set_family(s);
1157 skip_line();
1160 void point_size()
1162 int n;
1163 if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) {
1164 if (n <= 0)
1165 n = 1;
1166 curenv->set_size(n);
1167 curenv->add_html_tag(0, ".ps", n);
1169 else
1170 curenv->set_size(0);
1171 skip_line();
1174 void override_sizes()
1176 int n = 16;
1177 int *sizes = new int[n];
1178 int i = 0;
1179 char *buf = read_string();
1180 if (!buf)
1181 return;
1182 char *p = strtok(buf, " \t");
1183 for (;;) {
1184 if (!p)
1185 break;
1186 int lower, upper;
1187 switch (sscanf(p, "%d-%d", &lower, &upper)) {
1188 case 1:
1189 upper = lower;
1190 // fall through
1191 case 2:
1192 if (lower <= upper && lower >= 0)
1193 break;
1194 // fall through
1195 default:
1196 warning(WARN_RANGE, "bad size range `%1'", p);
1197 return;
1199 if (i + 2 > n) {
1200 int *old_sizes = sizes;
1201 sizes = new int[n*2];
1202 memcpy(sizes, old_sizes, n*sizeof(int));
1203 n *= 2;
1204 a_delete old_sizes;
1206 sizes[i++] = lower;
1207 if (lower == 0)
1208 break;
1209 sizes[i++] = upper;
1210 p = strtok(0, " \t");
1212 font_size::init_size_table(sizes);
1215 void space_size()
1217 int n;
1218 if (get_integer(&n)) {
1219 curenv->space_size = n;
1220 if (has_arg() && get_integer(&n))
1221 curenv->sentence_space_size = n;
1222 else
1223 curenv->sentence_space_size = curenv->space_size;
1225 skip_line();
1228 void fill()
1230 while (!tok.newline() && !tok.eof())
1231 tok.next();
1232 if (break_flag)
1233 curenv->do_break();
1234 curenv->fill = 1;
1235 curenv->add_html_tag(1, ".fi");
1236 curenv->add_html_tag(0, ".br");
1237 tok.next();
1240 void no_fill()
1242 while (!tok.newline() && !tok.eof())
1243 tok.next();
1244 if (break_flag)
1245 curenv->do_break();
1246 curenv->fill = 0;
1247 curenv->add_html_tag(1, ".nf");
1248 curenv->add_html_tag(0, ".br");
1249 curenv->add_html_tag(0, ".po", topdiv->get_page_offset().to_units());
1250 tok.next();
1253 void center()
1255 int n;
1256 if (!has_arg() || !get_integer(&n))
1257 n = 1;
1258 else if (n < 0)
1259 n = 0;
1260 while (!tok.newline() && !tok.eof())
1261 tok.next();
1262 if (break_flag)
1263 curenv->do_break();
1264 curenv->right_justify_lines = 0;
1265 curenv->center_lines = n;
1266 curenv->add_html_tag(1, ".ce", n);
1267 tok.next();
1270 void right_justify()
1272 int n;
1273 if (!has_arg() || !get_integer(&n))
1274 n = 1;
1275 else if (n < 0)
1276 n = 0;
1277 while (!tok.newline() && !tok.eof())
1278 tok.next();
1279 if (break_flag)
1280 curenv->do_break();
1281 curenv->center_lines = 0;
1282 curenv->right_justify_lines = n;
1283 curenv->add_html_tag(1, ".rj", n);
1284 tok.next();
1287 void line_length()
1289 hunits temp;
1290 if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) {
1291 if (temp < H0) {
1292 warning(WARN_RANGE, "bad line length %1u", temp.to_units());
1293 temp = H0;
1296 else
1297 temp = curenv->prev_line_length;
1298 curenv->prev_line_length = curenv->line_length;
1299 curenv->line_length = temp;
1300 curenv->add_html_tag(1, ".ll", temp.to_units());
1301 skip_line();
1304 void title_length()
1306 hunits temp;
1307 if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) {
1308 if (temp < H0) {
1309 warning(WARN_RANGE, "bad title length %1u", temp.to_units());
1310 temp = H0;
1313 else
1314 temp = curenv->prev_title_length;
1315 curenv->prev_title_length = curenv->title_length;
1316 curenv->title_length = temp;
1317 skip_line();
1320 void vertical_spacing()
1322 vunits temp;
1323 if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) {
1324 if (temp < V0) {
1325 warning(WARN_RANGE, "vertical spacing must not be negative");
1326 temp = vresolution;
1329 else
1330 temp = curenv->prev_vertical_spacing;
1331 curenv->prev_vertical_spacing = curenv->vertical_spacing;
1332 curenv->vertical_spacing = temp;
1333 skip_line();
1336 void post_vertical_spacing()
1338 vunits temp;
1339 if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) {
1340 if (temp < V0) {
1341 warning(WARN_RANGE,
1342 "post vertical spacing must be greater than or equal to 0");
1343 temp = V0;
1346 else
1347 temp = curenv->prev_post_vertical_spacing;
1348 curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
1349 curenv->post_vertical_spacing = temp;
1350 skip_line();
1353 void line_spacing()
1355 int temp;
1356 if (has_arg() && get_integer(&temp)) {
1357 if (temp < 1) {
1358 warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
1359 temp = 1;
1362 else
1363 temp = curenv->prev_line_spacing;
1364 curenv->prev_line_spacing = curenv->line_spacing;
1365 curenv->line_spacing = temp;
1366 skip_line();
1369 void indent()
1371 hunits temp;
1372 if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) {
1373 if (temp < H0) {
1374 warning(WARN_RANGE, "indent cannot be negative");
1375 temp = H0;
1378 else
1379 temp = curenv->prev_indent;
1380 while (!tok.newline() && !tok.eof())
1381 tok.next();
1382 if (break_flag)
1383 curenv->do_break();
1384 curenv->have_temporary_indent = 0;
1385 curenv->prev_indent = curenv->indent;
1386 curenv->indent = temp;
1387 if (break_flag)
1388 curenv->add_html_tag(1, ".in", temp.to_units());
1389 tok.next();
1392 void temporary_indent()
1394 int err = 0;
1395 hunits temp;
1396 if (!get_hunits(&temp, 'm', curenv->get_indent()))
1397 err = 1;
1398 while (!tok.newline() && !tok.eof())
1399 tok.next();
1400 if (break_flag)
1401 curenv->do_break();
1402 if (temp < H0) {
1403 warning(WARN_RANGE, "total indent cannot be negative");
1404 temp = H0;
1406 if (!err) {
1407 curenv->temporary_indent = temp;
1408 curenv->have_temporary_indent = 1;
1409 curenv->add_html_tag(1, ".ti", temp.to_units());
1411 tok.next();
1414 node *do_underline_special(int underline_spaces)
1416 macro m;
1417 m.append_str("x u ");
1418 m.append(underline_spaces + '0');
1419 return new special_node(m, 1);
1422 void do_underline(int underline_spaces)
1424 int n;
1425 if (!has_arg() || !get_integer(&n))
1426 n = 1;
1427 if (n <= 0) {
1428 if (curenv->underline_lines > 0) {
1429 curenv->prev_fontno = curenv->fontno;
1430 curenv->fontno = curenv->pre_underline_fontno;
1431 if (underline_spaces) {
1432 curenv->underline_spaces = 0;
1433 curenv->add_node(do_underline_special(0));
1436 curenv->underline_lines = 0;
1438 else {
1439 curenv->underline_lines = n;
1440 curenv->pre_underline_fontno = curenv->fontno;
1441 curenv->fontno = get_underline_fontno();
1442 if (underline_spaces) {
1443 curenv->underline_spaces = 1;
1444 curenv->add_node(do_underline_special(1));
1447 skip_line();
1450 void continuous_underline()
1452 do_underline(1);
1455 void underline()
1457 do_underline(0);
1460 void control_char()
1462 curenv->control_char = '.';
1463 if (has_arg()) {
1464 if (tok.ch() == 0)
1465 error("bad control character");
1466 else
1467 curenv->control_char = tok.ch();
1469 skip_line();
1472 void no_break_control_char()
1474 curenv->no_break_control_char = '\'';
1475 if (has_arg()) {
1476 if (tok.ch() == 0)
1477 error("bad control character");
1478 else
1479 curenv->no_break_control_char = tok.ch();
1481 skip_line();
1484 void margin_character()
1486 while (tok.space())
1487 tok.next();
1488 charinfo *ci = tok.get_char();
1489 if (ci) {
1490 // Call tok.next() only after making the node so that
1491 // .mc \s+9\(br\s0 works.
1492 node *nd = curenv->make_char_node(ci);
1493 tok.next();
1494 if (nd) {
1495 delete curenv->margin_character_node;
1496 curenv->margin_character_node = nd;
1497 curenv->margin_character_flags = (MARGIN_CHARACTER_ON
1498 |MARGIN_CHARACTER_NEXT);
1499 hunits d;
1500 if (has_arg() && get_hunits(&d, 'm'))
1501 curenv->margin_character_distance = d;
1504 else {
1505 check_missing_character();
1506 curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON;
1507 if (curenv->margin_character_flags == 0) {
1508 delete curenv->margin_character_node;
1509 curenv->margin_character_node = 0;
1512 skip_line();
1515 void number_lines()
1517 delete_node_list(curenv->numbering_nodes);
1518 curenv->numbering_nodes = 0;
1519 if (has_arg()) {
1520 node *nd = 0;
1521 for (int i = '9'; i >= '0'; i--) {
1522 node *tem = make_node(charset_table[i], curenv);
1523 if (!tem) {
1524 skip_line();
1525 return;
1527 tem->next = nd;
1528 nd = tem;
1530 curenv->numbering_nodes = nd;
1531 curenv->line_number_digit_width = env_digit_width(curenv);
1532 int n;
1533 if (!tok.delimiter()) {
1534 if (get_integer(&n, next_line_number)) {
1535 next_line_number = n;
1536 if (next_line_number < 0) {
1537 warning(WARN_RANGE, "negative line number");
1538 next_line_number = 0;
1542 else
1543 while (!tok.space() && !tok.newline() && !tok.eof())
1544 tok.next();
1545 if (has_arg()) {
1546 if (!tok.delimiter()) {
1547 if (get_integer(&n)) {
1548 if (n <= 0) {
1549 warning(WARN_RANGE, "negative or zero line number multiple");
1551 else
1552 curenv->line_number_multiple = n;
1555 else
1556 while (!tok.space() && !tok.newline() && !tok.eof())
1557 tok.next();
1558 if (has_arg()) {
1559 if (!tok.delimiter()) {
1560 if (get_integer(&n))
1561 curenv->number_text_separation = n;
1563 else
1564 while (!tok.space() && !tok.newline() && !tok.eof())
1565 tok.next();
1566 if (has_arg() && !tok.delimiter() && get_integer(&n))
1567 curenv->line_number_indent = n;
1571 skip_line();
1574 void no_number()
1576 int n;
1577 if (has_arg() && get_integer(&n))
1578 curenv->no_number_count = n > 0 ? n : 0;
1579 else
1580 curenv->no_number_count = 1;
1581 skip_line();
1584 void no_hyphenate()
1586 curenv->hyphenation_flags = 0;
1587 skip_line();
1590 void hyphenate_request()
1592 int n;
1593 if (has_arg() && get_integer(&n))
1594 curenv->hyphenation_flags = n;
1595 else
1596 curenv->hyphenation_flags = 1;
1597 skip_line();
1600 void hyphen_char()
1602 curenv->hyphen_indicator_char = get_optional_char();
1603 skip_line();
1606 void hyphen_line_max_request()
1608 int n;
1609 if (has_arg() && get_integer(&n))
1610 curenv->hyphen_line_max = n;
1611 else
1612 curenv->hyphen_line_max = -1;
1613 skip_line();
1616 void environment::interrupt()
1618 if (!dummy) {
1619 add_node(new transparent_dummy_node);
1620 interrupted = 1;
1624 void environment::newline()
1626 if (underline_lines > 0) {
1627 if (--underline_lines == 0) {
1628 prev_fontno = fontno;
1629 fontno = pre_underline_fontno;
1630 if (underline_spaces) {
1631 underline_spaces = 0;
1632 add_node(do_underline_special(0));
1636 if (current_field)
1637 wrap_up_field();
1638 if (current_tab)
1639 wrap_up_tab();
1640 // strip trailing spaces
1641 while (line != 0 && line->discardable()) {
1642 width_total -= line->width();
1643 space_total -= line->nspaces();
1644 node *tem = line;
1645 line = line->next;
1646 delete tem;
1648 node *to_be_output = 0;
1649 hunits to_be_output_width;
1650 prev_line_interrupted = 0;
1651 if (dummy)
1652 space_newline();
1653 else if (interrupted) {
1654 interrupted = 0;
1655 // see environment::final_break
1656 prev_line_interrupted = exit_started ? 2 : 1;
1658 else if (center_lines > 0) {
1659 --center_lines;
1660 hunits x = target_text_length - width_total;
1661 if (x > H0)
1662 saved_indent += x/2;
1663 to_be_output = line;
1664 if (is_html) {
1665 node *n = make_html_tag("eol.ce");
1666 n->next = to_be_output;
1667 to_be_output = n;
1669 to_be_output_width = width_total;
1670 line = 0;
1672 else if (right_justify_lines > 0) {
1673 --right_justify_lines;
1674 hunits x = target_text_length - width_total;
1675 if (x > H0)
1676 saved_indent += x;
1677 to_be_output = line;
1678 to_be_output_width = width_total;
1679 line = 0;
1681 else if (fill)
1682 space_newline();
1683 else {
1684 to_be_output = line;
1685 to_be_output_width = width_total;
1686 line = 0;
1688 input_line_start = line == 0 ? H0 : width_total;
1689 if (to_be_output) {
1690 if (is_html && !fill) {
1691 if (curdiv == topdiv) {
1692 node *n = make_html_tag("eol");
1694 n->next = to_be_output;
1695 to_be_output = n;
1698 output_line(to_be_output, to_be_output_width);
1699 hyphen_line_count = 0;
1701 if (input_trap_count > 0) {
1702 if (!(continued_input_trap && prev_line_interrupted))
1703 if (--input_trap_count == 0)
1704 spring_trap(input_trap);
1708 void environment::output_line(node *n, hunits width)
1710 prev_text_length = width;
1711 if (margin_character_flags) {
1712 hunits d = line_length + margin_character_distance - saved_indent - width;
1713 if (d > 0) {
1714 n = new hmotion_node(d, get_fill_color(), n);
1715 width += d;
1717 margin_character_flags &= ~MARGIN_CHARACTER_NEXT;
1718 node *tem;
1719 if (!margin_character_flags) {
1720 tem = margin_character_node;
1721 margin_character_node = 0;
1723 else
1724 tem = margin_character_node->copy();
1725 tem->next = n;
1726 n = tem;
1727 width += tem->width();
1729 node *nn = 0;
1730 while (n != 0) {
1731 node *tem = n->next;
1732 n->next = nn;
1733 nn = n;
1734 n = tem;
1736 if (!saved_indent.is_zero())
1737 nn = new hmotion_node(saved_indent, get_fill_color(), nn);
1738 width += saved_indent;
1739 if (no_number_count > 0)
1740 --no_number_count;
1741 else if (numbering_nodes) {
1742 hunits w = (line_number_digit_width
1743 *(3+line_number_indent+number_text_separation));
1744 if (next_line_number % line_number_multiple != 0)
1745 nn = new hmotion_node(w, get_fill_color(), nn);
1746 else {
1747 hunits x = w;
1748 nn = new hmotion_node(number_text_separation * line_number_digit_width,
1749 get_fill_color(), nn);
1750 x -= number_text_separation*line_number_digit_width;
1751 char buf[30];
1752 sprintf(buf, "%3d", next_line_number);
1753 for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
1754 node *gn = numbering_nodes;
1755 for (int count = *p - '0'; count > 0; count--)
1756 gn = gn->next;
1757 gn = gn->copy();
1758 x -= gn->width();
1759 gn->next = nn;
1760 nn = gn;
1762 nn = new hmotion_node(x, get_fill_color(), nn);
1764 width += w;
1765 ++next_line_number;
1767 output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width);
1770 void environment::start_line()
1772 assert(line == 0);
1773 discarding = 0;
1774 line = new line_start_node;
1775 if (have_temporary_indent) {
1776 saved_indent = temporary_indent;
1777 have_temporary_indent = 0;
1779 else
1780 saved_indent = indent;
1781 target_text_length = line_length - saved_indent;
1782 width_total = H0;
1783 space_total = 0;
1786 hunits environment::get_hyphenation_space()
1788 return hyphenation_space;
1791 void hyphenation_space_request()
1793 hunits n;
1794 if (get_hunits(&n, 'm')) {
1795 if (n < H0) {
1796 warning(WARN_RANGE, "hyphenation space cannot be negative");
1797 n = H0;
1799 curenv->hyphenation_space = n;
1801 skip_line();
1804 hunits environment::get_hyphenation_margin()
1806 return hyphenation_margin;
1809 void hyphenation_margin_request()
1811 hunits n;
1812 if (get_hunits(&n, 'm')) {
1813 if (n < H0) {
1814 warning(WARN_RANGE, "hyphenation margin cannot be negative");
1815 n = H0;
1817 curenv->hyphenation_margin = n;
1819 skip_line();
1822 breakpoint *environment::choose_breakpoint()
1824 hunits x = width_total;
1825 int s = space_total;
1826 node *n = line;
1827 breakpoint *best_bp = 0; // the best breakpoint so far
1828 int best_bp_fits = 0;
1829 while (n != 0) {
1830 x -= n->width();
1831 s -= n->nspaces();
1832 breakpoint *bp = n->get_breakpoints(x, s);
1833 while (bp != 0) {
1834 if (bp->width <= target_text_length) {
1835 if (!bp->hyphenated) {
1836 breakpoint *tem = bp->next;
1837 bp->next = 0;
1838 while (tem != 0) {
1839 breakpoint *tem1 = tem;
1840 tem = tem->next;
1841 delete tem1;
1843 if (best_bp_fits
1844 // Decide whether to use the hyphenated breakpoint.
1845 && (hyphen_line_max < 0
1846 // Only choose the hyphenated breakpoint if it would not
1847 // exceed the maximum number of consecutive hyphenated
1848 // lines.
1849 || hyphen_line_count + 1 <= hyphen_line_max)
1850 && !(adjust_mode == ADJUST_BOTH
1851 // Don't choose the hyphenated breakpoint if the line
1852 // can be justified by adding no more than
1853 // hyphenation_space to any word space.
1854 ? (bp->nspaces > 0
1855 && (((target_text_length - bp->width
1856 + (bp->nspaces - 1)*hresolution)/bp->nspaces)
1857 <= hyphenation_space))
1858 // Don't choose the hyphenated breakpoint if the line
1859 // is no more than hyphenation_margin short.
1860 : target_text_length - bp->width <= hyphenation_margin)) {
1861 delete bp;
1862 return best_bp;
1864 if (best_bp)
1865 delete best_bp;
1866 return bp;
1868 else {
1869 if ((adjust_mode == ADJUST_BOTH
1870 ? hyphenation_space == H0
1871 : hyphenation_margin == H0)
1872 && (hyphen_line_max < 0
1873 || hyphen_line_count + 1 <= hyphen_line_max)) {
1874 // No need to consider a non-hyphenated breakpoint.
1875 if (best_bp)
1876 delete best_bp;
1877 breakpoint *tem = bp->next;
1878 bp->next = 0;
1879 while (tem != 0) {
1880 breakpoint *tem1 = tem;
1881 tem = tem->next;
1882 delete tem1;
1884 return bp;
1886 // It fits but it's hyphenated.
1887 if (!best_bp_fits) {
1888 if (best_bp)
1889 delete best_bp;
1890 best_bp = bp;
1891 bp = bp->next;
1892 best_bp_fits = 1;
1894 else {
1895 breakpoint *tem = bp;
1896 bp = bp->next;
1897 delete tem;
1901 else {
1902 if (best_bp)
1903 delete best_bp;
1904 best_bp = bp;
1905 bp = bp->next;
1908 n = n->next;
1910 if (best_bp) {
1911 if (!best_bp_fits)
1912 output_warning(WARN_BREAK, "can't break line");
1913 return best_bp;
1915 return 0;
1918 void environment::hyphenate_line(int start_here)
1920 assert(line != 0);
1921 hyphenation_type prev_type = line->get_hyphenation_type();
1922 node **startp;
1923 if (start_here)
1924 startp = &line;
1925 else
1926 for (startp = &line->next; *startp != 0; startp = &(*startp)->next) {
1927 hyphenation_type this_type = (*startp)->get_hyphenation_type();
1928 if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
1929 break;
1930 prev_type = this_type;
1932 if (*startp == 0)
1933 return;
1934 node *tem = *startp;
1935 do {
1936 tem = tem->next;
1937 } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
1938 int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
1939 node *end = tem;
1940 hyphen_list *sl = 0;
1941 tem = *startp;
1942 node *forward = 0;
1943 int i = 0;
1944 while (tem != end) {
1945 sl = tem->get_hyphen_list(sl, &i);
1946 node *tem1 = tem;
1947 tem = tem->next;
1948 tem1->next = forward;
1949 forward = tem1;
1951 if (!inhibit) {
1952 // this is for characters like hyphen and emdash
1953 int prev_code = 0;
1954 for (hyphen_list *h = sl; h; h = h->next) {
1955 h->breakable = (prev_code != 0
1956 && h->next != 0
1957 && h->next->hyphenation_code != 0);
1958 prev_code = h->hyphenation_code;
1961 if (hyphenation_flags != 0
1962 && !inhibit
1963 // this may not be right if we have extra space on this line
1964 && !((hyphenation_flags & HYPHEN_LAST_LINE)
1965 && (curdiv->distance_to_next_trap()
1966 <= vertical_spacing + total_post_vertical_spacing()))
1967 && i >= 4)
1968 hyphenate(sl, hyphenation_flags);
1969 while (forward != 0) {
1970 node *tem1 = forward;
1971 forward = forward->next;
1972 tem1->next = 0;
1973 tem = tem1->add_self(tem, &sl);
1975 *startp = tem;
1978 static node *node_list_reverse(node *n)
1980 node *res = 0;
1981 while (n) {
1982 node *tem = n;
1983 n = n->next;
1984 tem->next = res;
1985 res = tem;
1987 return res;
1990 static void distribute_space(node *n, int nspaces, hunits desired_space,
1991 int force_reverse = 0)
1993 static int reverse = 0;
1994 if (force_reverse || reverse)
1995 n = node_list_reverse(n);
1996 if (!force_reverse && nspaces > 0 && spread_limit >= 0
1997 && desired_space.to_units() > 0) {
1998 hunits em = curenv->get_size();
1999 double Ems = (double)desired_space.to_units() / nspaces
2000 / (em.is_zero() ? hresolution : em.to_units());
2001 if (Ems > spread_limit)
2002 output_warning(WARN_BREAK, "spreading %1m per space", Ems);
2004 for (node *tem = n; tem; tem = tem->next)
2005 tem->spread_space(&nspaces, &desired_space);
2006 if (force_reverse || reverse)
2007 (void)node_list_reverse(n);
2008 if (!force_reverse)
2009 reverse = !reverse;
2010 assert(desired_space.is_zero() && nspaces == 0);
2013 void environment::possibly_break_line(int start_here, int forced)
2015 if (!fill || current_tab || current_field || dummy)
2016 return;
2017 while (line != 0
2018 && (forced
2019 // When a macro follows a paragraph in fill mode, the
2020 // current line should not be empty.
2021 || (width_total - line->width()) > target_text_length)) {
2022 hyphenate_line(start_here);
2023 breakpoint *bp = choose_breakpoint();
2024 if (bp == 0)
2025 // we'll find one eventually
2026 return;
2027 node *pre, *post;
2028 node **ndp = &line;
2029 while (*ndp != bp->nd)
2030 ndp = &(*ndp)->next;
2031 bp->nd->split(bp->index, &pre, &post);
2032 *ndp = post;
2033 hunits extra_space_width = H0;
2034 switch(adjust_mode) {
2035 case ADJUST_BOTH:
2036 if (bp->nspaces != 0)
2037 extra_space_width = target_text_length - bp->width;
2038 else if (bp->width > 0 && target_text_length > 0
2039 && target_text_length > bp->width)
2040 output_warning(WARN_BREAK, "cannot adjust line");
2041 break;
2042 case ADJUST_CENTER:
2043 saved_indent += (target_text_length - bp->width)/2;
2044 break;
2045 case ADJUST_RIGHT:
2046 saved_indent += target_text_length - bp->width;
2047 break;
2049 distribute_space(pre, bp->nspaces, extra_space_width);
2050 hunits output_width = bp->width + extra_space_width;
2051 input_line_start -= output_width;
2052 if (bp->hyphenated)
2053 hyphen_line_count++;
2054 else
2055 hyphen_line_count = 0;
2056 delete bp;
2057 space_total = 0;
2058 width_total = 0;
2059 node *first_non_discardable = 0;
2060 node *tem;
2061 for (tem = line; tem != 0; tem = tem->next)
2062 if (!tem->discardable())
2063 first_non_discardable = tem;
2064 node *to_be_discarded;
2065 if (first_non_discardable) {
2066 to_be_discarded = first_non_discardable->next;
2067 first_non_discardable->next = 0;
2068 for (tem = line; tem != 0; tem = tem->next) {
2069 width_total += tem->width();
2070 space_total += tem->nspaces();
2072 discarding = 0;
2074 else {
2075 discarding = 1;
2076 to_be_discarded = line;
2077 line = 0;
2079 // Do output_line() here so that line will be 0 iff the
2080 // the environment will be empty.
2081 output_line(pre, output_width);
2082 while (to_be_discarded != 0) {
2083 tem = to_be_discarded;
2084 to_be_discarded = to_be_discarded->next;
2085 input_line_start -= tem->width();
2086 delete tem;
2088 if (line != 0) {
2089 if (have_temporary_indent) {
2090 saved_indent = temporary_indent;
2091 have_temporary_indent = 0;
2093 else
2094 saved_indent = indent;
2095 target_text_length = line_length - saved_indent;
2101 Do the break at the end of input after the end macro (if any).
2103 Unix troff behaves as follows: if the last line is
2105 foo bar\c
2107 it will output foo on the current page, and bar on the next page;
2108 if the last line is
2110 foo\c
2114 foo bar
2116 everything will be output on the current page. This behaviour must be
2117 considered a bug.
2119 The problem is that some macro packages rely on this. For example,
2120 the ATK macros have an end macro that emits \c if it needs to print a
2121 table of contents but doesn't do a 'bp in the end macro; instead the
2122 'bp is done in the bottom of page trap. This works with Unix troff,
2123 provided that the current environment is not empty at the end of the
2124 input file.
2126 The following will make macro packages that do that sort of thing work
2127 even if the current environment is empty at the end of the input file.
2128 If the last input line used \c and this line occurred in the end macro,
2129 then we'll force everything out on the current page, but we'll make
2130 sure that the environment isn't empty so that we won't exit at the
2131 bottom of this page.
2134 void environment::final_break()
2136 if (prev_line_interrupted == 2) {
2137 do_break();
2138 add_node(new transparent_dummy_node);
2140 else
2141 do_break();
2145 * add_html_tag - emits a special html-tag: to help post-grohtml understand
2146 * the key troff commands
2149 void environment::add_html_tag(int force, const char *name)
2151 if (!force && (curdiv != topdiv))
2152 return;
2154 if (is_html) {
2156 * need to emit tag for post-grohtml
2157 * but we check to see whether we can emit specials
2159 if (curdiv == topdiv && topdiv->before_first_page)
2160 topdiv->begin_page();
2161 macro *m = new macro;
2162 m->append_str("html-tag:");
2163 for (const char *p = name; *p; p++)
2164 if (!invalid_input_char((unsigned char)*p))
2165 m->append(*p);
2166 curdiv->output(new special_node(*m), 1, 0, 0, 0);
2167 if (strcmp(name, ".nf") == 0)
2168 curenv->ignore_next_eol = 1;
2173 * add_html_tag - emits a special html-tag: to help post-grohtml understand
2174 * the key troff commands, it appends a string representation
2175 * of i.
2178 void environment::add_html_tag(int force, const char *name, int i)
2180 if (!force && (curdiv != topdiv))
2181 return;
2183 if (is_html) {
2185 * need to emit tag for post-grohtml
2186 * but we check to see whether we can emit specials
2188 if (curdiv == topdiv && topdiv->before_first_page)
2189 topdiv->begin_page();
2190 macro *m = new macro;
2191 m->append_str("html-tag:");
2192 for (const char *p = name; *p; p++)
2193 if (!invalid_input_char((unsigned char)*p))
2194 m->append(*p);
2195 m->append(' ');
2196 m->append_int(i);
2197 node *n = new special_node(*m);
2198 curdiv->output(n, 1, 0, 0, 0);
2203 * add_html_tag_tabs - emits the tab settings for post-grohtml
2206 void environment::add_html_tag_tabs(int force)
2208 if (!force && (curdiv != topdiv))
2209 return;
2211 if (is_html) {
2213 * need to emit tag for post-grohtml
2214 * but we check to see whether we can emit specials
2216 if (curdiv == topdiv && topdiv->before_first_page)
2217 topdiv->begin_page();
2218 macro *m = new macro;
2219 hunits d, l;
2220 enum tab_type t;
2221 m->append_str("html-tag:.ta ");
2222 do {
2223 t = curenv->tabs.distance_to_next_tab(l, &d);
2224 l += d;
2225 switch (t) {
2226 case TAB_LEFT:
2227 m->append_str(" L ");
2228 m->append_int(l.to_units());
2229 break;
2230 case TAB_CENTER:
2231 m->append_str(" C ");
2232 m->append_int(l.to_units());
2233 break;
2234 case TAB_RIGHT:
2235 m->append_str(" R ");
2236 m->append_int(l.to_units());
2237 break;
2238 case TAB_NONE:
2239 break;
2241 } while ((t != TAB_NONE) && (l < get_line_length()));
2242 curdiv->output(new special_node(*m), 1, 0, 0, 0);
2246 node *environment::make_html_tag(const char *name, int i)
2248 if (is_html) {
2250 * need to emit tag for post-grohtml
2251 * but we check to see whether we can emit specials
2253 if (curdiv == topdiv && topdiv->before_first_page)
2254 topdiv->begin_page();
2255 macro *m = new macro;
2256 m->append_str("html-tag:");
2257 for (const char *p = name; *p; p++)
2258 if (!invalid_input_char((unsigned char)*p))
2259 m->append(*p);
2260 m->append(' ');
2261 m->append_int(i);
2262 return new special_node(*m);
2264 return 0;
2267 node *environment::make_html_tag(const char *name)
2269 if (is_html) {
2271 * need to emit tag for post-grohtml
2272 * but we check to see whether we can emit specials
2274 if (curdiv == topdiv && topdiv->before_first_page)
2275 topdiv->begin_page();
2276 macro *m = new macro;
2277 m->append_str("html-tag:");
2278 for (const char *p = name; *p; p++)
2279 if (!invalid_input_char((unsigned char)*p))
2280 m->append(*p);
2281 return new special_node(*m);
2283 return 0;
2286 void environment::do_break(int spread)
2288 if (curdiv == topdiv && topdiv->before_first_page) {
2289 topdiv->begin_page();
2290 return;
2292 if (current_tab)
2293 wrap_up_tab();
2294 if (line) {
2295 // this is so that hyphenation works
2296 line = new space_node(H0, get_fill_color(), line);
2297 space_total++;
2298 possibly_break_line(0, spread);
2300 while (line != 0 && line->discardable()) {
2301 width_total -= line->width();
2302 space_total -= line->nspaces();
2303 node *tem = line;
2304 line = line->next;
2305 delete tem;
2307 discarding = 0;
2308 input_line_start = H0;
2309 if (line != 0) {
2310 if (fill) {
2311 switch (adjust_mode) {
2312 case ADJUST_CENTER:
2313 saved_indent += (target_text_length - width_total)/2;
2314 break;
2315 case ADJUST_RIGHT:
2316 saved_indent += target_text_length - width_total;
2317 break;
2320 node *tem = line;
2321 line = 0;
2322 output_line(tem, width_total);
2323 hyphen_line_count = 0;
2325 prev_line_interrupted = 0;
2326 #ifdef WIDOW_CONTROL
2327 mark_last_line();
2328 output_pending_lines();
2329 #endif /* WIDOW_CONTROL */
2332 int environment::is_empty()
2334 return !current_tab && line == 0 && pending_lines == 0;
2337 void do_break_request(int spread)
2339 while (!tok.newline() && !tok.eof())
2340 tok.next();
2341 if (break_flag) {
2342 curenv->do_break(spread);
2343 curenv->add_html_tag(0, ".br");
2345 tok.next();
2348 void break_request()
2350 do_break_request(0);
2353 void break_spread_request()
2355 do_break_request(1);
2358 void title()
2360 if (curdiv == topdiv && topdiv->before_first_page) {
2361 handle_initial_title();
2362 return;
2364 node *part[3];
2365 hunits part_width[3];
2366 part[0] = part[1] = part[2] = 0;
2367 environment env(curenv);
2368 environment *oldenv = curenv;
2369 curenv = &env;
2370 read_title_parts(part, part_width);
2371 curenv = oldenv;
2372 curenv->size = env.size;
2373 curenv->prev_size = env.prev_size;
2374 curenv->requested_size = env.requested_size;
2375 curenv->prev_requested_size = env.prev_requested_size;
2376 curenv->char_height = env.char_height;
2377 curenv->char_slant = env.char_slant;
2378 curenv->fontno = env.fontno;
2379 curenv->prev_fontno = env.prev_fontno;
2380 curenv->glyph_color = env.glyph_color;
2381 curenv->prev_glyph_color = env.prev_glyph_color;
2382 curenv->fill_color = env.fill_color;
2383 curenv->prev_fill_color = env.prev_fill_color;
2384 node *n = 0;
2385 node *p = part[2];
2386 while (p != 0) {
2387 node *tem = p;
2388 p = p->next;
2389 tem->next = n;
2390 n = tem;
2392 hunits title_length(curenv->title_length);
2393 hunits f = title_length - part_width[1];
2394 hunits f2 = f/2;
2395 n = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(), n);
2396 p = part[1];
2397 while (p != 0) {
2398 node *tem = p;
2399 p = p->next;
2400 tem->next = n;
2401 n = tem;
2403 n = new hmotion_node(f - f2 - part_width[0], curenv->get_fill_color(), n);
2404 p = part[0];
2405 while (p != 0) {
2406 node *tem = p;
2407 p = p->next;
2408 tem->next = n;
2409 n = tem;
2411 curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
2412 curenv->total_post_vertical_spacing(), title_length);
2413 curenv->hyphen_line_count = 0;
2414 tok.next();
2417 void adjust()
2419 curenv->adjust_mode |= 1;
2420 if (has_arg()) {
2421 switch (tok.ch()) {
2422 case 'l':
2423 curenv->adjust_mode = ADJUST_LEFT;
2424 break;
2425 case 'r':
2426 curenv->adjust_mode = ADJUST_RIGHT;
2427 break;
2428 case 'c':
2429 curenv->adjust_mode = ADJUST_CENTER;
2430 break;
2431 case 'b':
2432 case 'n':
2433 curenv->adjust_mode = ADJUST_BOTH;
2434 break;
2435 default:
2436 int n;
2437 if (get_integer(&n)) {
2438 if (n < 0)
2439 warning(WARN_RANGE, "negative adjustment mode");
2440 else if (n > 5) {
2441 curenv->adjust_mode = 5;
2442 warning(WARN_RANGE, "adjustment mode `%1' out of range", n);
2444 else
2445 curenv->adjust_mode = n;
2449 skip_line();
2452 void no_adjust()
2454 curenv->adjust_mode &= ~1;
2455 skip_line();
2458 void do_input_trap(int continued)
2460 curenv->input_trap_count = 0;
2461 if (continued)
2462 curenv->continued_input_trap = 1;
2463 int n;
2464 if (has_arg() && get_integer(&n)) {
2465 if (n <= 0)
2466 warning(WARN_RANGE,
2467 "number of lines for input trap must be greater than zero");
2468 else {
2469 symbol s = get_name(1);
2470 if (!s.is_null()) {
2471 curenv->input_trap_count = n;
2472 curenv->input_trap = s;
2476 skip_line();
2479 void input_trap()
2481 do_input_trap(0);
2484 void input_trap_continued()
2486 do_input_trap(1);
2489 /* tabs */
2491 // must not be R or C or L or a legitimate part of a number expression
2492 const char TAB_REPEAT_CHAR = 'T';
2494 struct tab {
2495 tab *next;
2496 hunits pos;
2497 tab_type type;
2498 tab(hunits, tab_type);
2499 enum { BLOCK = 1024 };
2500 static tab *free_list;
2501 void *operator new(size_t);
2502 void operator delete(void *);
2505 tab *tab::free_list = 0;
2507 void *tab::operator new(size_t n)
2509 assert(n == sizeof(tab));
2510 if (!free_list) {
2511 free_list = (tab *)new char[sizeof(tab)*BLOCK];
2512 for (int i = 0; i < BLOCK - 1; i++)
2513 free_list[i].next = free_list + i + 1;
2514 free_list[BLOCK-1].next = 0;
2516 tab *p = free_list;
2517 free_list = (tab *)(free_list->next);
2518 p->next = 0;
2519 return p;
2522 #ifdef __GNUG__
2523 /* cfront can't cope with this. */
2524 inline
2525 #endif
2526 void tab::operator delete(void *p)
2528 if (p) {
2529 ((tab *)p)->next = free_list;
2530 free_list = (tab *)p;
2534 tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2538 tab_stops::tab_stops(hunits distance, tab_type type)
2539 : initial_list(0)
2541 repeated_list = new tab(distance, type);
2544 tab_stops::~tab_stops()
2546 clear();
2549 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
2551 hunits nextpos;
2553 return distance_to_next_tab(curpos, distance, &nextpos);
2556 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance,
2557 hunits *nextpos)
2559 hunits lastpos = 0;
2560 tab *tem;
2561 for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
2562 lastpos = tem->pos;
2563 if (tem) {
2564 *distance = tem->pos - curpos;
2565 *nextpos = tem->pos;
2566 return tem->type;
2568 if (repeated_list == 0)
2569 return TAB_NONE;
2570 hunits base = lastpos;
2571 for (;;) {
2572 for (tem = repeated_list; tem && tem->pos + base <= curpos; tem = tem->next)
2573 lastpos = tem->pos;
2574 if (tem) {
2575 *distance = tem->pos + base - curpos;
2576 *nextpos = tem->pos + base;
2577 return tem->type;
2579 assert(lastpos > 0);
2580 base += lastpos;
2582 return TAB_NONE;
2585 const char *tab_stops::to_string()
2587 static char *buf = 0;
2588 static int buf_size = 0;
2589 // figure out a maximum on the amount of space we can need
2590 int count = 0;
2591 tab *p;
2592 for (p = initial_list; p; p = p->next)
2593 ++count;
2594 for (p = repeated_list; p; p = p->next)
2595 ++count;
2596 // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
2597 int need = count*12 + 3;
2598 if (buf == 0 || need > buf_size) {
2599 if (buf)
2600 a_delete buf;
2601 buf_size = need;
2602 buf = new char[buf_size];
2604 char *ptr = buf;
2605 for (p = initial_list; p; p = p->next) {
2606 strcpy(ptr, i_to_a(p->pos.to_units()));
2607 ptr = strchr(ptr, '\0');
2608 *ptr++ = 'u';
2609 *ptr = '\0';
2610 switch (p->type) {
2611 case TAB_LEFT:
2612 break;
2613 case TAB_RIGHT:
2614 *ptr++ = 'R';
2615 break;
2616 case TAB_CENTER:
2617 *ptr++ = 'C';
2618 break;
2619 case TAB_NONE:
2620 default:
2621 assert(0);
2624 if (repeated_list)
2625 *ptr++ = TAB_REPEAT_CHAR;
2626 for (p = repeated_list; p; p = p->next) {
2627 strcpy(ptr, i_to_a(p->pos.to_units()));
2628 ptr = strchr(ptr, '\0');
2629 *ptr++ = 'u';
2630 *ptr = '\0';
2631 switch (p->type) {
2632 case TAB_LEFT:
2633 break;
2634 case TAB_RIGHT:
2635 *ptr++ = 'R';
2636 break;
2637 case TAB_CENTER:
2638 *ptr++ = 'C';
2639 break;
2640 case TAB_NONE:
2641 default:
2642 assert(0);
2645 *ptr++ = '\0';
2646 return buf;
2649 tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2653 tab_stops::tab_stops(const tab_stops &ts)
2654 : initial_list(0), repeated_list(0)
2656 tab **p = &initial_list;
2657 tab *t = ts.initial_list;
2658 while (t) {
2659 *p = new tab(t->pos, t->type);
2660 t = t->next;
2661 p = &(*p)->next;
2663 p = &repeated_list;
2664 t = ts.repeated_list;
2665 while (t) {
2666 *p = new tab(t->pos, t->type);
2667 t = t->next;
2668 p = &(*p)->next;
2672 void tab_stops::clear()
2674 while (initial_list) {
2675 tab *tem = initial_list;
2676 initial_list = initial_list->next;
2677 delete tem;
2679 while (repeated_list) {
2680 tab *tem = repeated_list;
2681 repeated_list = repeated_list->next;
2682 delete tem;
2686 void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
2688 tab **p;
2689 for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
2691 *p = new tab(pos, type);
2695 void tab_stops::operator=(const tab_stops &ts)
2697 clear();
2698 tab **p = &initial_list;
2699 tab *t = ts.initial_list;
2700 while (t) {
2701 *p = new tab(t->pos, t->type);
2702 t = t->next;
2703 p = &(*p)->next;
2705 p = &repeated_list;
2706 t = ts.repeated_list;
2707 while (t) {
2708 *p = new tab(t->pos, t->type);
2709 t = t->next;
2710 p = &(*p)->next;
2714 void set_tabs()
2716 hunits pos;
2717 hunits prev_pos = 0;
2718 int first = 1;
2719 int repeated = 0;
2720 tab_stops tabs;
2721 while (has_arg()) {
2722 if (tok.ch() == TAB_REPEAT_CHAR) {
2723 tok.next();
2724 repeated = 1;
2725 prev_pos = 0;
2727 if (!get_hunits(&pos, 'm', prev_pos))
2728 break;
2729 tab_type type = TAB_LEFT;
2730 if (tok.ch() == 'C') {
2731 tok.next();
2732 type = TAB_CENTER;
2734 else if (tok.ch() == 'R') {
2735 tok.next();
2736 type = TAB_RIGHT;
2738 else if (tok.ch() == 'L') {
2739 tok.next();
2741 if (pos <= prev_pos && !first)
2742 warning(WARN_RANGE,
2743 "positions of tab stops must be strictly increasing");
2744 else {
2745 tabs.add_tab(pos, type, repeated);
2746 prev_pos = pos;
2747 first = 0;
2750 curenv->tabs = tabs;
2751 curenv->add_html_tag_tabs(1);
2752 skip_line();
2755 const char *environment::get_tabs()
2757 return tabs.to_string();
2760 tab_type environment::distance_to_next_tab(hunits *distance)
2762 return line_tabs
2763 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
2764 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
2767 tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
2769 return line_tabs
2770 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
2771 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
2772 leftpos);
2775 void field_characters()
2777 field_delimiter_char = get_optional_char();
2778 if (field_delimiter_char)
2779 padding_indicator_char = get_optional_char();
2780 else
2781 padding_indicator_char = 0;
2782 skip_line();
2785 void line_tabs_request()
2787 int n;
2788 if (has_arg() && get_integer(&n))
2789 curenv->line_tabs = n != 0;
2790 else
2791 curenv->line_tabs = 1;
2792 skip_line();
2795 int environment::get_line_tabs()
2797 return line_tabs;
2800 void environment::wrap_up_tab()
2802 if (!current_tab)
2803 return;
2804 if (line == 0)
2805 start_line();
2806 hunits tab_amount;
2807 switch (current_tab) {
2808 case TAB_RIGHT:
2809 tab_amount = tab_distance - tab_width;
2810 line = make_tab_node(tab_amount, line);
2811 break;
2812 case TAB_CENTER:
2813 tab_amount = tab_distance - tab_width/2;
2814 line = make_tab_node(tab_amount, line);
2815 break;
2816 case TAB_NONE:
2817 case TAB_LEFT:
2818 default:
2819 assert(0);
2821 width_total += tab_amount;
2822 width_total += tab_width;
2823 if (current_field) {
2824 if (tab_precedes_field) {
2825 pre_field_width += tab_amount;
2826 tab_precedes_field = 0;
2828 field_distance -= tab_amount;
2829 field_spaces += tab_field_spaces;
2831 if (tab_contents != 0) {
2832 node *tem;
2833 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2835 tem->next = line;
2836 line = tab_contents;
2838 tab_field_spaces = 0;
2839 tab_contents = 0;
2840 tab_width = H0;
2841 tab_distance = H0;
2842 current_tab = TAB_NONE;
2845 node *environment::make_tab_node(hunits d, node *next)
2847 if (leader_node != 0 && d < 0) {
2848 error("motion generated by leader cannot be negative");
2849 delete leader_node;
2850 leader_node = 0;
2852 if (!leader_node)
2853 return new hmotion_node(d, 1, 0, get_fill_color(), next);
2854 node *n = new hline_node(d, leader_node, next);
2855 leader_node = 0;
2856 return n;
2859 void environment::handle_tab(int is_leader)
2861 hunits d;
2862 hunits abs;
2863 if (current_tab)
2864 wrap_up_tab();
2865 charinfo *ci = is_leader ? leader_char : tab_char;
2866 delete leader_node;
2867 leader_node = ci ? make_char_node(ci) : 0;
2868 tab_type t = distance_to_next_tab(&d, &abs);
2869 switch (t) {
2870 case TAB_NONE:
2871 return;
2872 case TAB_LEFT:
2873 add_node(make_tab_node(d));
2874 add_node(make_html_tag("tab L", abs.to_units()));
2875 return;
2876 case TAB_RIGHT:
2877 add_node(make_html_tag("tab R", abs.to_units()));
2878 break;
2879 case TAB_CENTER:
2880 add_node(make_html_tag("tab C", abs.to_units()));
2881 break;
2882 default:
2883 assert(0);
2885 tab_width = 0;
2886 tab_distance = d;
2887 tab_contents = 0;
2888 current_tab = t;
2889 tab_field_spaces = 0;
2892 void environment::start_field()
2894 assert(!current_field);
2895 hunits d;
2896 if (distance_to_next_tab(&d) != TAB_NONE) {
2897 pre_field_width = get_text_length();
2898 field_distance = d;
2899 current_field = 1;
2900 field_spaces = 0;
2901 tab_field_spaces = 0;
2902 for (node *p = line; p; p = p->next)
2903 if (p->nspaces()) {
2904 p->freeze_space();
2905 space_total--;
2907 tab_precedes_field = current_tab != TAB_NONE;
2909 else
2910 error("zero field width");
2913 void environment::wrap_up_field()
2915 if (!current_tab && field_spaces == 0)
2916 add_padding();
2917 hunits padding = field_distance - (get_text_length() - pre_field_width);
2918 if (current_tab && tab_field_spaces != 0) {
2919 hunits tab_padding = scale(padding,
2920 tab_field_spaces,
2921 field_spaces + tab_field_spaces);
2922 padding -= tab_padding;
2923 distribute_space(tab_contents, tab_field_spaces, tab_padding, 1);
2924 tab_field_spaces = 0;
2925 tab_width += tab_padding;
2927 if (field_spaces != 0) {
2928 distribute_space(line, field_spaces, padding, 1);
2929 width_total += padding;
2930 if (current_tab) {
2931 // the start of the tab has been moved to the right by padding, so
2932 tab_distance -= padding;
2933 if (tab_distance <= H0) {
2934 // use the next tab stop instead
2935 current_tab = tabs.distance_to_next_tab(get_input_line_position()
2936 - tab_width,
2937 &tab_distance);
2938 if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
2939 width_total += tab_width;
2940 if (current_tab == TAB_LEFT) {
2941 line = make_tab_node(tab_distance, line);
2942 width_total += tab_distance;
2943 current_tab = TAB_NONE;
2945 if (tab_contents != 0) {
2946 node *tem;
2947 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2949 tem->next = line;
2950 line = tab_contents;
2951 tab_contents = 0;
2953 tab_width = H0;
2954 tab_distance = H0;
2959 current_field = 0;
2962 void environment::add_padding()
2964 if (current_tab) {
2965 tab_contents = new space_node(H0, get_fill_color(), tab_contents);
2966 tab_field_spaces++;
2968 else {
2969 if (line == 0)
2970 start_line();
2971 line = new space_node(H0, get_fill_color(), line);
2972 field_spaces++;
2976 typedef int (environment::*INT_FUNCP)();
2977 typedef vunits (environment::*VUNITS_FUNCP)();
2978 typedef hunits (environment::*HUNITS_FUNCP)();
2979 typedef const char *(environment::*STRING_FUNCP)();
2981 class int_env_reg : public reg {
2982 INT_FUNCP func;
2983 public:
2984 int_env_reg(INT_FUNCP);
2985 const char *get_string();
2986 int get_value(units *val);
2989 class vunits_env_reg : public reg {
2990 VUNITS_FUNCP func;
2991 public:
2992 vunits_env_reg(VUNITS_FUNCP f);
2993 const char *get_string();
2994 int get_value(units *val);
2998 class hunits_env_reg : public reg {
2999 HUNITS_FUNCP func;
3000 public:
3001 hunits_env_reg(HUNITS_FUNCP f);
3002 const char *get_string();
3003 int get_value(units *val);
3006 class string_env_reg : public reg {
3007 STRING_FUNCP func;
3008 public:
3009 string_env_reg(STRING_FUNCP);
3010 const char *get_string();
3013 int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
3017 int int_env_reg::get_value(units *val)
3019 *val = (curenv->*func)();
3020 return 1;
3023 const char *int_env_reg::get_string()
3025 return i_to_a((curenv->*func)());
3028 vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
3032 int vunits_env_reg::get_value(units *val)
3034 *val = (curenv->*func)().to_units();
3035 return 1;
3038 const char *vunits_env_reg::get_string()
3040 return i_to_a((curenv->*func)().to_units());
3043 hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
3047 int hunits_env_reg::get_value(units *val)
3049 *val = (curenv->*func)().to_units();
3050 return 1;
3053 const char *hunits_env_reg::get_string()
3055 return i_to_a((curenv->*func)().to_units());
3058 string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
3062 const char *string_env_reg::get_string()
3064 return (curenv->*func)();
3067 class horizontal_place_reg : public general_reg {
3068 public:
3069 horizontal_place_reg();
3070 int get_value(units *);
3071 void set_value(units);
3074 horizontal_place_reg::horizontal_place_reg()
3078 int horizontal_place_reg::get_value(units *res)
3080 *res = curenv->get_input_line_position().to_units();
3081 return 1;
3084 void horizontal_place_reg::set_value(units n)
3086 curenv->set_input_line_position(hunits(n));
3089 const char *environment::get_font_family_string()
3091 return family->nm.contents();
3094 const char *environment::get_font_name_string()
3096 symbol f = get_font_name(fontno, this);
3097 return f.contents();
3100 const char *environment::get_name_string()
3102 return name.contents();
3105 // Convert a quantity in scaled points to ascii decimal fraction.
3107 const char *sptoa(int sp)
3109 assert(sp > 0);
3110 assert(sizescale > 0);
3111 if (sizescale == 1)
3112 return i_to_a(sp);
3113 if (sp % sizescale == 0)
3114 return i_to_a(sp/sizescale);
3115 // See if 1/sizescale is exactly representable as a decimal fraction,
3116 // ie its only prime factors are 2 and 5.
3117 int n = sizescale;
3118 int power2 = 0;
3119 while ((n & 1) == 0) {
3120 n >>= 1;
3121 power2++;
3123 int power5 = 0;
3124 while ((n % 5) == 0) {
3125 n /= 5;
3126 power5++;
3128 if (n == 1) {
3129 int decimal_point = power5 > power2 ? power5 : power2;
3130 if (decimal_point <= 10) {
3131 int factor = 1;
3132 int t;
3133 for (t = decimal_point - power2; --t >= 0;)
3134 factor *= 2;
3135 for (t = decimal_point - power5; --t >= 0;)
3136 factor *= 5;
3137 if (factor == 1 || sp <= INT_MAX/factor)
3138 return if_to_a(sp*factor, decimal_point);
3141 double s = double(sp)/double(sizescale);
3142 double factor = 10.0;
3143 double val = s;
3144 int decimal_point = 0;
3145 do {
3146 double v = ceil(s*factor);
3147 if (v > INT_MAX)
3148 break;
3149 val = v;
3150 factor *= 10.0;
3151 } while (++decimal_point < 10);
3152 return if_to_a(int(val), decimal_point);
3155 const char *environment::get_point_size_string()
3157 return sptoa(curenv->get_point_size());
3160 const char *environment::get_requested_point_size_string()
3162 return sptoa(curenv->get_requested_point_size());
3165 #define init_int_env_reg(name, func) \
3166 number_reg_dictionary.define(name, new int_env_reg(&environment::func))
3168 #define init_vunits_env_reg(name, func) \
3169 number_reg_dictionary.define(name, new vunits_env_reg(&environment::func))
3171 #define init_hunits_env_reg(name, func) \
3172 number_reg_dictionary.define(name, new hunits_env_reg(&environment::func))
3174 #define init_string_env_reg(name, func) \
3175 number_reg_dictionary.define(name, new string_env_reg(&environment::func))
3177 void init_env_requests()
3179 init_request("ad", adjust);
3180 init_request("br", break_request);
3181 init_request("brp", break_spread_request);
3182 init_request("c2", no_break_control_char);
3183 init_request("cc", control_char);
3184 init_request("ce", center);
3185 init_request("cu", continuous_underline);
3186 init_request("ev", environment_switch);
3187 init_request("evc", environment_copy);
3188 init_request("fam", family_change);
3189 init_request("fc", field_characters);
3190 init_request("fi", fill);
3191 init_request("ft", font_change);
3192 init_request("hc", hyphen_char);
3193 init_request("hlm", hyphen_line_max_request);
3194 init_request("hy", hyphenate_request);
3195 init_request("hym", hyphenation_margin_request);
3196 init_request("hys", hyphenation_space_request);
3197 init_request("in", indent);
3198 init_request("it", input_trap);
3199 init_request("itc", input_trap_continued);
3200 init_request("lc", leader_character);
3201 init_request("linetabs", line_tabs_request);
3202 init_request("ll", line_length);
3203 init_request("ls", line_spacing);
3204 init_request("lt", title_length);
3205 init_request("mc", margin_character);
3206 init_request("na", no_adjust);
3207 init_request("nf", no_fill);
3208 init_request("nh", no_hyphenate);
3209 init_request("nm", number_lines);
3210 init_request("nn", no_number);
3211 init_request("ps", point_size);
3212 init_request("pvs", post_vertical_spacing);
3213 init_request("rj", right_justify);
3214 init_request("sizes", override_sizes);
3215 init_request("ss", space_size);
3216 init_request("ta", set_tabs);
3217 init_request("ti", temporary_indent);
3218 init_request("tc", tab_character);
3219 init_request("tl", title);
3220 init_request("ul", underline);
3221 init_request("vs", vertical_spacing);
3222 #ifdef WIDOW_CONTROL
3223 init_request("wdc", widow_control_request);
3224 #endif /* WIDOW_CONTROL */
3225 init_int_env_reg(".b", get_bold);
3226 init_vunits_env_reg(".cdp", get_prev_char_depth);
3227 init_int_env_reg(".ce", get_center_lines);
3228 init_vunits_env_reg(".cht", get_prev_char_height);
3229 init_hunits_env_reg(".csk", get_prev_char_skew);
3230 init_string_env_reg(".ev", get_name_string);
3231 init_int_env_reg(".f", get_font);
3232 init_string_env_reg(".fam", get_font_family_string);
3233 init_string_env_reg(".fn", get_font_name_string);
3234 init_int_env_reg(".height", get_char_height);
3235 init_int_env_reg(".hlc", get_hyphen_line_count);
3236 init_int_env_reg(".hlm", get_hyphen_line_max);
3237 init_int_env_reg(".hy", get_hyphenation_flags);
3238 init_hunits_env_reg(".hym", get_hyphenation_margin);
3239 init_hunits_env_reg(".hys", get_hyphenation_space);
3240 init_hunits_env_reg(".i", get_indent);
3241 init_hunits_env_reg(".in", get_saved_indent);
3242 init_int_env_reg(".int", get_prev_line_interrupted);
3243 init_int_env_reg(".linetabs", get_line_tabs);
3244 init_hunits_env_reg(".lt", get_title_length);
3245 init_int_env_reg(".j", get_adjust_mode);
3246 init_hunits_env_reg(".k", get_text_length);
3247 init_int_env_reg(".L", get_line_spacing);
3248 init_hunits_env_reg(".l", get_line_length);
3249 init_hunits_env_reg(".ll", get_saved_line_length);
3250 init_hunits_env_reg(".n", get_prev_text_length);
3251 init_int_env_reg(".ps", get_point_size);
3252 init_int_env_reg(".psr", get_requested_point_size);
3253 init_vunits_env_reg(".pvs", get_post_vertical_spacing);
3254 init_int_env_reg(".rj", get_right_justify_lines);
3255 init_string_env_reg(".s", get_point_size_string);
3256 init_int_env_reg(".slant", get_char_slant);
3257 init_int_env_reg(".ss", get_space_size);
3258 init_int_env_reg(".sss", get_sentence_space_size);
3259 init_string_env_reg(".sr", get_requested_point_size_string);
3260 init_string_env_reg(".tabs", get_tabs);
3261 init_int_env_reg(".u", get_fill);
3262 init_vunits_env_reg(".v", get_vertical_spacing);
3263 init_hunits_env_reg(".w", get_prev_char_width);
3264 number_reg_dictionary.define("ct", new variable_reg(&ct_reg_contents));
3265 number_reg_dictionary.define("hp", new horizontal_place_reg);
3266 number_reg_dictionary.define("ln", new variable_reg(&next_line_number));
3267 number_reg_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
3268 number_reg_dictionary.define("rst", new variable_reg(&rst_reg_contents));
3269 number_reg_dictionary.define("sb", new variable_reg(&sb_reg_contents));
3270 number_reg_dictionary.define("skw", new variable_reg(&skw_reg_contents));
3271 number_reg_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
3272 number_reg_dictionary.define("st", new variable_reg(&st_reg_contents));
3275 // Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
3277 struct trie_node;
3279 class trie {
3280 trie_node *tp;
3281 virtual void do_match(int len, void *val) = 0;
3282 virtual void do_delete(void *) = 0;
3283 void delete_trie_node(trie_node *);
3284 public:
3285 trie() : tp(0) {}
3286 virtual ~trie(); // virtual to shut up g++
3287 void insert(const char *, int, void *);
3288 // find calls do_match for each match it finds
3289 void find(const char *pat, int patlen);
3290 void clear();
3293 class hyphen_trie : private trie {
3294 int *h;
3295 void do_match(int i, void *v);
3296 void do_delete(void *v);
3297 void insert_pattern(const char *pat, int patlen, int *num);
3298 void insert_hyphenation(dictionary *ex, const char *pat, int patlen);
3299 int hpf_getc(FILE *f);
3300 public:
3301 hyphen_trie() {}
3302 ~hyphen_trie() {}
3303 void hyphenate(const char *word, int len, int *hyphens);
3304 void read_patterns_file(const char *name, int append, dictionary *ex);
3307 struct hyphenation_language {
3308 symbol name;
3309 dictionary exceptions;
3310 hyphen_trie patterns;
3311 hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
3312 ~hyphenation_language() { }
3315 dictionary language_dictionary(5);
3316 hyphenation_language *current_language = 0;
3318 static void set_hyphenation_language()
3320 symbol nm = get_name(1);
3321 if (!nm.is_null()) {
3322 current_language = (hyphenation_language *)language_dictionary.lookup(nm);
3323 if (!current_language) {
3324 current_language = new hyphenation_language(nm);
3325 (void)language_dictionary.lookup(nm, (void *)current_language);
3328 skip_line();
3331 const int WORD_MAX = 256; // we use unsigned char for offsets in
3332 // hyphenation exceptions
3334 static void hyphen_word()
3336 if (!current_language) {
3337 error("no current hyphenation language");
3338 skip_line();
3339 return;
3341 char buf[WORD_MAX + 1];
3342 unsigned char pos[WORD_MAX + 2];
3343 for (;;) {
3344 tok.skip();
3345 if (tok.newline() || tok.eof())
3346 break;
3347 int i = 0;
3348 int npos = 0;
3349 while (i < WORD_MAX && !tok.space() && !tok.newline() && !tok.eof()) {
3350 charinfo *ci = tok.get_char(1);
3351 if (ci == 0) {
3352 skip_line();
3353 return;
3355 tok.next();
3356 if (ci->get_ascii_code() == '-') {
3357 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3358 pos[npos++] = i;
3360 else {
3361 int c = ci->get_hyphenation_code();
3362 if (c == 0)
3363 break;
3364 buf[i++] = c;
3367 if (i > 0) {
3368 pos[npos] = 0;
3369 buf[i] = 0;
3370 unsigned char *tem = new unsigned char[npos + 1];
3371 memcpy(tem, pos, npos + 1);
3372 tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf),
3373 tem);
3374 if (tem)
3375 a_delete tem;
3378 skip_line();
3381 struct trie_node {
3382 char c;
3383 trie_node *down;
3384 trie_node *right;
3385 void *val;
3386 trie_node(char, trie_node *);
3389 trie_node::trie_node(char ch, trie_node *p)
3390 : c(ch), down(0), right(p), val(0)
3394 trie::~trie()
3396 clear();
3399 void trie::clear()
3401 delete_trie_node(tp);
3402 tp = 0;
3406 void trie::delete_trie_node(trie_node *p)
3408 if (p) {
3409 delete_trie_node(p->down);
3410 delete_trie_node(p->right);
3411 if (p->val)
3412 do_delete(p->val);
3413 delete p;
3417 void trie::insert(const char *pat, int patlen, void *val)
3419 trie_node **p = &tp;
3420 assert(patlen > 0 && pat != 0);
3421 for (;;) {
3422 while (*p != 0 && (*p)->c < pat[0])
3423 p = &((*p)->right);
3424 if (*p == 0 || (*p)->c != pat[0])
3425 *p = new trie_node(pat[0], *p);
3426 if (--patlen == 0) {
3427 (*p)->val = val;
3428 break;
3430 ++pat;
3431 p = &((*p)->down);
3435 void trie::find(const char *pat, int patlen)
3437 trie_node *p = tp;
3438 for (int i = 0; p != 0 && i < patlen; i++) {
3439 while (p != 0 && p->c < pat[i])
3440 p = p->right;
3441 if (p != 0 && p->c == pat[i]) {
3442 if (p->val != 0)
3443 do_match(i+1, p->val);
3444 p = p->down;
3446 else
3447 break;
3451 struct operation {
3452 operation *next;
3453 short distance;
3454 short num;
3455 operation(int, int, operation *);
3458 operation::operation(int i, int j, operation *op)
3459 : next(op), distance(j), num(i)
3463 void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
3465 operation *op = 0;
3466 for (int i = 0; i < patlen+1; i++)
3467 if (num[i] != 0)
3468 op = new operation(num[i], patlen - i, op);
3469 insert(pat, patlen, op);
3472 void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
3473 int patlen)
3475 char buf[WORD_MAX + 1];
3476 unsigned char pos[WORD_MAX + 2];
3477 int i = 0, j = 0;
3478 int npos = 0;
3479 while (j < patlen) {
3480 unsigned char c = pat[j++];
3481 if (c == '-') {
3482 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3483 pos[npos++] = i;
3485 else
3486 buf[i++] = hpf_code_table[c];
3488 if (i > 0) {
3489 pos[npos] = 0;
3490 buf[i] = 0;
3491 unsigned char *tem = new unsigned char[npos + 1];
3492 memcpy(tem, pos, npos + 1);
3493 tem = (unsigned char *)ex->lookup(symbol(buf), tem);
3494 if (tem)
3495 a_delete tem;
3499 void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
3501 int j;
3502 for (j = 0; j < len + 1; j++)
3503 hyphens[j] = 0;
3504 for (j = 0; j < len - 1; j++) {
3505 h = hyphens + j;
3506 find(word + j, len - j);
3510 inline int max(int m, int n)
3512 return m > n ? m : n;
3515 void hyphen_trie::do_match(int i, void *v)
3517 operation *op = (operation *)v;
3518 while (op != 0) {
3519 h[i - op->distance] = max(h[i - op->distance], op->num);
3520 op = op->next;
3524 void hyphen_trie::do_delete(void *v)
3526 operation *op = (operation *)v;
3527 while (op) {
3528 operation *tem = op;
3529 op = tem->next;
3530 delete tem;
3534 /* We use very simple rules to parse TeX's hyphenation patterns.
3536 . `%' starts a comment even if preceded by `\'.
3538 . No support for digraphs and like `\$'.
3540 . `^^xx' (`x' is 0-9 or a-f), and `^^x' (character code of `x' in the
3541 range 0-127) are recognized; other use of `^' causes an error.
3543 . No macro expansion.
3545 . We check for the expression `\patterns{...}' (possibly with
3546 whitespace before and after the braces). Everything between the
3547 braces is taken as hyphenation patterns. Consequently, `{' and `}'
3548 are not allowed in patterns.
3550 . Similarly, `\hyphenation{...}' gives a list of hyphenation
3551 exceptions.
3553 . `\endinput' is recognized also.
3555 . For backwards compatibility, if `\patterns' is missing, the
3556 whole file is treated as a list of hyphenation patterns (only
3557 recognizing `%' as the start of a comment.
3561 int hyphen_trie::hpf_getc(FILE *f)
3563 int c = getc(f);
3564 int c1;
3565 int cc = 0;
3566 if (c != '^')
3567 return c;
3568 c = getc(f);
3569 if (c != '^')
3570 goto fail;
3571 c = getc(f);
3572 c1 = getc(f);
3573 if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
3574 && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
3575 if (c >= '0' && c <= '9')
3576 c -= '0';
3577 else
3578 c = c - 'a' + 10;
3579 if (c1 >= '0' && c1 <= '9')
3580 c1 -= '0';
3581 else
3582 c1 = c1 - 'a' + 10;
3583 cc = c * 16 + c1;
3585 else {
3586 ungetc(c1, f);
3587 if (c >= 0 && c <= 63)
3588 cc = c + 64;
3589 else if (c >= 64 && c <= 127)
3590 cc = c - 64;
3591 else
3592 goto fail;
3594 return cc;
3595 fail:
3596 error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
3597 return c;
3600 void hyphen_trie::read_patterns_file(const char *name, int append,
3601 dictionary *ex)
3603 if (!append)
3604 clear();
3605 char buf[WORD_MAX];
3606 for (int i = 0; i < WORD_MAX; i++)
3607 buf[i] = 0;
3608 int num[WORD_MAX+1];
3609 errno = 0;
3610 char *path = 0;
3611 FILE *fp = mac_path->open_file(name, &path);
3612 if (fp == 0) {
3613 error("can't find hyphenation patterns file `%1'", name);
3614 return;
3616 int c = hpf_getc(fp);
3617 int have_patterns = 0; // we've seen \patterns
3618 int final_pattern = 0; // 1 if we have a trailing closing brace
3619 int have_hyphenation = 0; // we've seen \hyphenation
3620 int final_hyphenation = 0; // 1 if we have a trailing closing brace
3621 int have_keyword = 0; // we've seen either \patterns or \hyphenation
3622 int traditional = 0; // don't handle \patterns
3623 for (;;) {
3624 for (;;) {
3625 if (c == '%') { // skip comments
3626 do {
3627 c = getc(fp);
3628 } while (c != EOF && c != '\n');
3630 if (c == EOF || !csspace(c))
3631 break;
3632 c = hpf_getc(fp);
3634 if (c == EOF) {
3635 if (have_keyword || traditional) // we are done
3636 break;
3637 else { // rescan file in `traditional' mode
3638 rewind(fp);
3639 traditional = 1;
3640 c = hpf_getc(fp);
3641 continue;
3644 int i = 0;
3645 num[0] = 0;
3646 if (!(c == '{' || c == '}')) { // skip braces at line start
3647 do { // scan patterns
3648 if (csdigit(c))
3649 num[i] = c - '0';
3650 else {
3651 buf[i++] = c;
3652 num[i] = 0;
3654 c = hpf_getc(fp);
3655 } while (i < WORD_MAX && c != EOF && !csspace(c)
3656 && c != '%' && c != '{' && c != '}');
3658 if (!traditional) {
3659 if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
3660 while (csspace(c))
3661 c = hpf_getc(fp);
3662 if (c == '{') {
3663 if (have_patterns || have_hyphenation)
3664 error("\\patterns not allowed inside of %1 group",
3665 have_patterns ? "\\patterns" : "\\hyphenation");
3666 else {
3667 have_patterns = 1;
3668 have_keyword = 1;
3670 c = hpf_getc(fp);
3671 continue;
3674 else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
3675 while (csspace(c))
3676 c = hpf_getc(fp);
3677 if (c == '{') {
3678 if (have_patterns || have_hyphenation)
3679 error("\\hyphenation not allowed inside of %1 group",
3680 have_patterns ? "\\patterns" : "\\hyphenation");
3681 else {
3682 have_hyphenation = 1;
3683 have_keyword = 1;
3685 c = hpf_getc(fp);
3686 continue;
3689 else if (strstr(buf, "\\endinput")) {
3690 if (have_patterns || have_hyphenation)
3691 error("found \\endinput inside of %1 group",
3692 have_patterns ? "\\patterns" : "\\hyphenation");
3693 break;
3695 else if (c == '}') {
3696 if (have_patterns) {
3697 have_patterns = 0;
3698 if (i > 0)
3699 final_pattern = 1;
3701 else if (have_hyphenation) {
3702 have_hyphenation = 0;
3703 if (i > 0)
3704 final_hyphenation = 1;
3706 c = hpf_getc(fp);
3708 else if (c == '{') {
3709 if (have_patterns || have_hyphenation)
3710 error("`{' not allowed within %1 group",
3711 have_patterns ? "\\patterns" : "\\hyphenation");
3712 c = hpf_getc(fp); // skipped if not starting \patterns
3713 // or \hyphenation
3716 else {
3717 if (c == '{' || c == '}')
3718 c = hpf_getc(fp);
3720 if (i > 0) {
3721 if (have_patterns || final_pattern || traditional) {
3722 for (int j = 0; j < i; j++)
3723 buf[j] = hpf_code_table[(unsigned char)buf[j]];
3724 insert_pattern(buf, i, num);
3725 final_pattern = 0;
3727 else if (have_hyphenation || final_hyphenation) {
3728 insert_hyphenation(ex, buf, i);
3729 final_hyphenation = 0;
3733 fclose(fp);
3734 a_delete path;
3735 return;
3738 void hyphenate(hyphen_list *h, unsigned flags)
3740 if (!current_language)
3741 return;
3742 while (h) {
3743 while (h && h->hyphenation_code == 0)
3744 h = h->next;
3745 int len = 0;
3746 char hbuf[WORD_MAX+2];
3747 char *buf = hbuf + 1;
3748 hyphen_list *tem;
3749 for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
3750 if (tem->hyphenation_code != 0)
3751 buf[len++] = tem->hyphenation_code;
3752 else
3753 break;
3755 hyphen_list *nexth = tem;
3756 if (len > 2) {
3757 buf[len] = 0;
3758 unsigned char *pos
3759 = (unsigned char *)current_language->exceptions.lookup(buf);
3760 if (pos != 0) {
3761 int j = 0;
3762 int i = 1;
3763 for (tem = h; tem != 0; tem = tem->next, i++)
3764 if (pos[j] == i) {
3765 tem->hyphen = 1;
3766 j++;
3769 else {
3770 hbuf[0] = hbuf[len+1] = '.';
3771 int num[WORD_MAX+3];
3772 current_language->patterns.hyphenate(hbuf, len+2, num);
3773 int i;
3774 num[2] = 0;
3775 if (flags & 8)
3776 num[3] = 0;
3777 if (flags & 4)
3778 --len;
3779 for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
3780 if (num[i] & 1)
3781 tem->hyphen = 1;
3784 h = nexth;
3788 static void do_hyphenation_patterns_file(int append)
3790 symbol name = get_long_name(1);
3791 if (!name.is_null()) {
3792 if (!current_language)
3793 error("no current hyphenation language");
3794 else
3795 current_language->patterns.read_patterns_file(
3796 name.contents(), append,
3797 &current_language->exceptions);
3799 skip_line();
3802 static void hyphenation_patterns_file()
3804 do_hyphenation_patterns_file(0);
3807 static void hyphenation_patterns_file_append()
3809 do_hyphenation_patterns_file(1);
3812 class hyphenation_language_reg : public reg {
3813 public:
3814 const char *get_string();
3817 const char *hyphenation_language_reg::get_string()
3819 return current_language ? current_language->name.contents() : "";
3822 void init_hyphen_requests()
3824 init_request("hw", hyphen_word);
3825 init_request("hla", set_hyphenation_language);
3826 init_request("hpf", hyphenation_patterns_file);
3827 init_request("hpfa", hyphenation_patterns_file_append);
3828 number_reg_dictionary.define(".hla", new hyphenation_language_reg);