* src/libs/libgroff/glyphuni.cpp (glyph_to_unicode_list),
[s-roff.git] / src / roff / troff / env.cpp
blob9a3dc171fb8ab0365fa3a825466b9736e26f8239
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004
3 Free Software Foundation, Inc.
4 Written by James Clark (jjc@jclark.com)
6 This file is part of groff.
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 for more details.
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING. If not, write to the Free Software
20 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
22 #include "troff.h"
23 #include "dictionary.h"
24 #include "hvunits.h"
25 #include "env.h"
26 #include "request.h"
27 #include "node.h"
28 #include "token.h"
29 #include "div.h"
30 #include "reg.h"
31 #include "charinfo.h"
32 #include "macropath.h"
33 #include "input.h"
34 #include <math.h>
36 symbol default_family("T");
38 enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 };
40 enum { HYPHEN_LAST_LINE = 2, HYPHEN_LAST_CHARS = 4, HYPHEN_FIRST_CHARS = 8 };
42 struct env_list {
43 environment *env;
44 env_list *next;
45 env_list(environment *e, env_list *p) : env(e), next(p) {}
48 env_list *env_stack;
49 const int NENVIRONMENTS = 10;
50 environment *env_table[NENVIRONMENTS];
51 dictionary env_dictionary(10);
52 environment *curenv;
53 static int next_line_number = 0;
55 charinfo *field_delimiter_char;
56 charinfo *padding_indicator_char;
58 int translate_space_to_dummy = 0;
60 class pending_output_line {
61 node *nd;
62 int no_fill;
63 vunits vs;
64 vunits post_vs;
65 hunits width;
66 #ifdef WIDOW_CONTROL
67 int last_line; // Is it the last line of the paragraph?
68 #endif /* WIDOW_CONTROL */
69 public:
70 pending_output_line *next;
72 pending_output_line(node *, int, vunits, vunits, hunits,
73 pending_output_line * = 0);
74 ~pending_output_line();
75 int output();
77 #ifdef WIDOW_CONTROL
78 friend void environment::mark_last_line();
79 friend void environment::output(node *, int, vunits, vunits, hunits);
80 #endif /* WIDOW_CONTROL */
83 pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv,
84 hunits w, pending_output_line *p)
85 : nd(n), no_fill(nf), vs(v), post_vs(pv), width(w),
86 #ifdef WIDOW_CONTROL
87 last_line(0),
88 #endif /* WIDOW_CONTROL */
89 next(p)
93 pending_output_line::~pending_output_line()
95 delete_node_list(nd);
98 int pending_output_line::output()
100 if (trap_sprung_flag)
101 return 0;
102 #ifdef WIDOW_CONTROL
103 if (next && next->last_line && !no_fill) {
104 curdiv->need(vs + post_vs + vunits(vresolution));
105 if (trap_sprung_flag) {
106 next->last_line = 0; // Try to avoid infinite loops.
107 return 0;
110 #endif
111 curdiv->output(nd, no_fill, vs, post_vs, width);
112 nd = 0;
113 return 1;
116 void environment::output(node *nd, int no_fill_flag,
117 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_flag)
136 #endif /* WIDOW_CONTROL */
138 curdiv->output(nd, no_fill_flag, 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_flag, 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_flag,
151 vunits vs, vunits post_vs,
152 hunits width)
154 if (!trap_sprung_flag)
155 curdiv->output(nd, no_fill_flag, vs, post_vs, width);
156 else
157 pending_lines = new pending_output_line(nd, no_fill_flag, vs, post_vs,
158 width, pending_lines);
161 void environment::output_pending_lines()
163 while (pending_lines && pending_lines->output()) {
164 pending_output_line *tem = pending_lines;
165 pending_lines = pending_lines->next;
166 delete tem;
170 #ifdef WIDOW_CONTROL
172 void environment::mark_last_line()
174 if (!widow_control || !pending_lines)
175 return;
176 for (pending_output_line *p = pending_lines; p->next; p = p->next)
178 if (!p->no_fill)
179 p->last_line = 1;
182 void widow_control_request()
184 int n;
185 if (has_arg() && get_integer(&n))
186 curenv->widow_control = n != 0;
187 else
188 curenv->widow_control = 1;
189 skip_line();
192 #endif /* WIDOW_CONTROL */
194 /* font_size functions */
196 size_range *font_size::size_table = 0;
197 int font_size::nranges = 0;
199 extern "C" {
201 int compare_ranges(const void *p1, const void *p2)
203 return ((size_range *)p1)->min - ((size_range *)p2)->min;
208 void font_size::init_size_table(int *sizes)
210 nranges = 0;
211 while (sizes[nranges*2] != 0)
212 nranges++;
213 assert(nranges > 0);
214 size_table = new size_range[nranges];
215 for (int i = 0; i < nranges; i++) {
216 size_table[i].min = sizes[i*2];
217 size_table[i].max = sizes[i*2 + 1];
219 qsort(size_table, nranges, sizeof(size_range), compare_ranges);
222 font_size::font_size(int sp)
224 for (int i = 0; i < nranges; i++) {
225 if (sp < size_table[i].min) {
226 if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
227 p = size_table[i - 1].max;
228 else
229 p = size_table[i].min;
230 return;
232 if (sp <= size_table[i].max) {
233 p = sp;
234 return;
237 p = size_table[nranges - 1].max;
240 int font_size::to_units()
242 return scale(p, units_per_inch, sizescale*72);
245 // we can't do this in a static constructor because various dictionaries
246 // have to get initialized first
248 void init_environments()
250 curenv = env_table[0] = new environment("0");
253 void tab_character()
255 curenv->tab_char = get_optional_char();
256 skip_line();
259 void leader_character()
261 curenv->leader_char = get_optional_char();
262 skip_line();
265 void environment::add_char(charinfo *ci)
267 int s;
268 if (interrupted)
270 // don't allow fields in dummy environments
271 else if (ci == field_delimiter_char && !dummy) {
272 if (current_field)
273 wrap_up_field();
274 else
275 start_field();
277 else if (current_field && ci == padding_indicator_char)
278 add_padding();
279 else if (current_tab) {
280 if (tab_contents == 0)
281 tab_contents = new line_start_node;
282 if (ci != hyphen_indicator_char)
283 tab_contents = tab_contents->add_char(ci, this, &tab_width, &s);
284 else
285 tab_contents = tab_contents->add_discretionary_hyphen();
287 else {
288 if (line == 0)
289 start_line();
290 if (ci != hyphen_indicator_char)
291 line = line->add_char(ci, this, &width_total, &space_total);
292 else
293 line = line->add_discretionary_hyphen();
297 node *environment::make_char_node(charinfo *ci)
299 return make_node(ci, this);
302 void environment::add_node(node *n)
304 if (n == 0)
305 return;
306 if (current_tab || current_field)
307 n->freeze_space();
308 if (interrupted) {
309 delete n;
311 else if (current_tab) {
312 n->next = tab_contents;
313 tab_contents = n;
314 tab_width += n->width();
316 else {
317 if (line == 0) {
318 if (discarding && n->discardable()) {
319 // XXX possibly: input_line_start -= n->width();
320 delete n;
321 return;
323 start_line();
325 width_total += n->width();
326 space_total += n->nspaces();
327 n->next = line;
328 line = n;
333 void environment::add_hyphen_indicator()
335 if (current_tab || interrupted || current_field
336 || hyphen_indicator_char != 0)
337 return;
338 if (line == 0)
339 start_line();
340 line = line->add_discretionary_hyphen();
343 int environment::get_hyphenation_flags()
345 return hyphenation_flags;
348 int environment::get_hyphen_line_max()
350 return hyphen_line_max;
353 int environment::get_hyphen_line_count()
355 return hyphen_line_count;
358 int environment::get_center_lines()
360 return center_lines;
363 int environment::get_right_justify_lines()
365 return right_justify_lines;
368 void environment::add_italic_correction()
370 if (current_tab) {
371 if (tab_contents)
372 tab_contents = tab_contents->add_italic_correction(&tab_width);
374 else if (line)
375 line = line->add_italic_correction(&width_total);
378 void environment::space_newline()
380 assert(!current_tab && !current_field);
381 if (interrupted)
382 return;
383 hunits x = H0;
384 hunits sw = env_space_width(this);
385 hunits ssw = env_sentence_space_width(this);
386 if (!translate_space_to_dummy) {
387 x = sw;
388 if (node_list_ends_sentence(line) == 1)
389 x += ssw;
391 width_list *w = new width_list(sw, ssw);
392 if (node_list_ends_sentence(line) == 1)
393 w->next = new width_list(sw, ssw);
394 if (line != 0 && line->merge_space(x, sw, ssw)) {
395 width_total += x;
396 return;
398 add_node(new word_space_node(x, get_fill_color(), w));
399 possibly_break_line(0, spread_flag);
400 spread_flag = 0;
403 void environment::space()
405 space(env_space_width(this), env_sentence_space_width(this));
408 void environment::space(hunits space_width, hunits sentence_space_width)
410 if (interrupted)
411 return;
412 if (current_field && padding_indicator_char == 0) {
413 add_padding();
414 return;
416 hunits x = translate_space_to_dummy ? H0 : space_width;
417 node *p = current_tab ? tab_contents : line;
418 hunits *tp = current_tab ? &tab_width : &width_total;
419 if (p && p->nspaces() == 1 && p->width() == x
420 && node_list_ends_sentence(p->next) == 1) {
421 hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
422 if (p->merge_space(xx, space_width, sentence_space_width)) {
423 *tp += xx;
424 return;
427 if (p && p->merge_space(x, space_width, sentence_space_width)) {
428 *tp += x;
429 return;
431 add_node(new word_space_node(x,
432 get_fill_color(),
433 new width_list(space_width,
434 sentence_space_width)));
435 possibly_break_line(0, spread_flag);
436 spread_flag = 0;
439 node *do_underline_special(int);
441 void environment::set_font(symbol nm)
443 if (interrupted)
444 return;
445 if (nm == symbol("P") || nm.is_empty()) {
446 if (family->make_definite(prev_fontno) < 0)
447 return;
448 int tem = fontno;
449 fontno = prev_fontno;
450 prev_fontno = tem;
452 else {
453 prev_fontno = fontno;
454 int n = symbol_fontno(nm);
455 if (n < 0) {
456 n = next_available_font_position();
457 if (!mount_font(n, nm))
458 return;
460 if (family->make_definite(n) < 0)
461 return;
462 fontno = n;
464 if (underline_spaces && fontno != prev_fontno) {
465 if (fontno == get_underline_fontno())
466 add_node(do_underline_special(1));
467 if (prev_fontno == get_underline_fontno())
468 add_node(do_underline_special(0));
472 void environment::set_font(int n)
474 if (interrupted)
475 return;
476 if (is_good_fontno(n)) {
477 prev_fontno = fontno;
478 fontno = n;
480 else
481 warning(WARN_FONT, "bad font number");
484 void environment::set_family(symbol fam)
486 if (interrupted)
487 return;
488 if (fam.is_null() || fam.is_empty()) {
489 if (prev_family->make_definite(fontno) < 0)
490 return;
491 font_family *tem = family;
492 family = prev_family;
493 prev_family = tem;
495 else {
496 font_family *f = lookup_family(fam);
497 if (f->make_definite(fontno) < 0)
498 return;
499 prev_family = family;
500 family = f;
504 void environment::set_size(int n)
506 if (interrupted)
507 return;
508 if (n == 0) {
509 font_size temp = prev_size;
510 prev_size = size;
511 size = temp;
512 int temp2 = prev_requested_size;
513 prev_requested_size = requested_size;
514 requested_size = temp2;
516 else {
517 prev_size = size;
518 size = font_size(n);
519 prev_requested_size = requested_size;
520 requested_size = n;
524 void environment::set_char_height(int n)
526 if (interrupted)
527 return;
528 if (n == requested_size || n <= 0)
529 char_height = 0;
530 else
531 char_height = n;
534 void environment::set_char_slant(int n)
536 if (interrupted)
537 return;
538 char_slant = n;
541 color *environment::get_prev_glyph_color()
543 return prev_glyph_color;
546 color *environment::get_glyph_color()
548 return glyph_color;
551 color *environment::get_prev_fill_color()
553 return prev_fill_color;
556 color *environment::get_fill_color()
558 return fill_color;
561 void environment::set_glyph_color(color *c)
563 if (interrupted)
564 return;
565 curenv->prev_glyph_color = curenv->glyph_color;
566 curenv->glyph_color = c;
569 void environment::set_fill_color(color *c)
571 if (interrupted)
572 return;
573 curenv->prev_fill_color = curenv->fill_color;
574 curenv->fill_color = c;
577 environment::environment(symbol nm)
578 : dummy(0),
579 prev_line_length((units_per_inch*13)/2),
580 line_length((units_per_inch*13)/2),
581 prev_title_length((units_per_inch*13)/2),
582 title_length((units_per_inch*13)/2),
583 prev_size(sizescale*10),
584 size(sizescale*10),
585 requested_size(sizescale*10),
586 prev_requested_size(sizescale*10),
587 char_height(0),
588 char_slant(0),
589 space_size(12),
590 sentence_space_size(12),
591 adjust_mode(ADJUST_BOTH),
592 fill(1),
593 interrupted(0),
594 prev_line_interrupted(0),
595 center_lines(0),
596 right_justify_lines(0),
597 prev_vertical_spacing(points_to_units(12)),
598 vertical_spacing(points_to_units(12)),
599 prev_post_vertical_spacing(0),
600 post_vertical_spacing(0),
601 prev_line_spacing(1),
602 line_spacing(1),
603 prev_indent(0),
604 indent(0),
605 temporary_indent(0),
606 have_temporary_indent(0),
607 underline_lines(0),
608 underline_spaces(0),
609 input_trap_count(0),
610 continued_input_trap(0),
611 line(0),
612 prev_text_length(0),
613 width_total(0),
614 space_total(0),
615 input_line_start(0),
616 tabs(units_per_inch/2, TAB_LEFT),
617 line_tabs(0),
618 current_tab(TAB_NONE),
619 leader_node(0),
620 tab_char(0),
621 leader_char(charset_table['.']),
622 current_field(0),
623 discarding(0),
624 spread_flag(0),
625 margin_character_flags(0),
626 margin_character_node(0),
627 margin_character_distance(points_to_units(10)),
628 numbering_nodes(0),
629 number_text_separation(1),
630 line_number_indent(0),
631 line_number_multiple(1),
632 no_number_count(0),
633 hyphenation_flags(1),
634 hyphen_line_count(0),
635 hyphen_line_max(-1),
636 hyphenation_space(H0),
637 hyphenation_margin(H0),
638 composite(0),
639 pending_lines(0),
640 #ifdef WIDOW_CONTROL
641 widow_control(0),
642 #endif /* WIDOW_CONTROL */
643 ignore_next_eol(0),
644 emitted_node(0),
645 glyph_color(&default_color),
646 prev_glyph_color(&default_color),
647 fill_color(&default_color),
648 prev_fill_color(&default_color),
649 name(nm),
650 control_char('.'),
651 no_break_control_char('\''),
652 hyphen_indicator_char(0)
654 prev_family = family = lookup_family(default_family);
655 prev_fontno = fontno = 1;
656 if (!is_good_fontno(1))
657 fatal("font number 1 not a valid font");
658 if (family->make_definite(1) < 0)
659 fatal("invalid default family `%1'", default_family.contents());
660 prev_fontno = fontno;
663 environment::environment(const environment *e)
664 : dummy(1),
665 prev_line_length(e->prev_line_length),
666 line_length(e->line_length),
667 prev_title_length(e->prev_title_length),
668 title_length(e->title_length),
669 prev_size(e->prev_size),
670 size(e->size),
671 requested_size(e->requested_size),
672 prev_requested_size(e->prev_requested_size),
673 char_height(e->char_height),
674 char_slant(e->char_slant),
675 prev_fontno(e->prev_fontno),
676 fontno(e->fontno),
677 prev_family(e->prev_family),
678 family(e->family),
679 space_size(e->space_size),
680 sentence_space_size(e->sentence_space_size),
681 adjust_mode(e->adjust_mode),
682 fill(e->fill),
683 interrupted(0),
684 prev_line_interrupted(0),
685 center_lines(0),
686 right_justify_lines(0),
687 prev_vertical_spacing(e->prev_vertical_spacing),
688 vertical_spacing(e->vertical_spacing),
689 prev_post_vertical_spacing(e->prev_post_vertical_spacing),
690 post_vertical_spacing(e->post_vertical_spacing),
691 prev_line_spacing(e->prev_line_spacing),
692 line_spacing(e->line_spacing),
693 prev_indent(e->prev_indent),
694 indent(e->indent),
695 temporary_indent(0),
696 have_temporary_indent(0),
697 underline_lines(0),
698 underline_spaces(0),
699 input_trap_count(0),
700 continued_input_trap(0),
701 line(0),
702 prev_text_length(e->prev_text_length),
703 width_total(0),
704 space_total(0),
705 input_line_start(0),
706 tabs(e->tabs),
707 line_tabs(e->line_tabs),
708 current_tab(TAB_NONE),
709 leader_node(0),
710 tab_char(e->tab_char),
711 leader_char(e->leader_char),
712 current_field(0),
713 discarding(0),
714 spread_flag(0),
715 margin_character_flags(e->margin_character_flags),
716 margin_character_node(e->margin_character_node),
717 margin_character_distance(e->margin_character_distance),
718 numbering_nodes(0),
719 number_text_separation(e->number_text_separation),
720 line_number_indent(e->line_number_indent),
721 line_number_multiple(e->line_number_multiple),
722 no_number_count(e->no_number_count),
723 hyphenation_flags(e->hyphenation_flags),
724 hyphen_line_count(0),
725 hyphen_line_max(e->hyphen_line_max),
726 hyphenation_space(e->hyphenation_space),
727 hyphenation_margin(e->hyphenation_margin),
728 composite(0),
729 pending_lines(0),
730 #ifdef WIDOW_CONTROL
731 widow_control(e->widow_control),
732 #endif /* WIDOW_CONTROL */
733 ignore_next_eol(0),
734 emitted_node(0),
735 glyph_color(e->glyph_color),
736 prev_glyph_color(e->prev_glyph_color),
737 fill_color(e->fill_color),
738 prev_fill_color(e->prev_fill_color),
739 name(e->name), // so that eg `.if "\n[.ev]"0"' works
740 control_char(e->control_char),
741 no_break_control_char(e->no_break_control_char),
742 hyphen_indicator_char(e->hyphen_indicator_char)
746 void environment::copy(const environment *e)
748 prev_line_length = e->prev_line_length;
749 line_length = e->line_length;
750 prev_title_length = e->prev_title_length;
751 title_length = e->title_length;
752 prev_size = e->prev_size;
753 size = e->size;
754 prev_requested_size = e->prev_requested_size;
755 requested_size = e->requested_size;
756 char_height = e->char_height;
757 char_slant = e->char_slant;
758 space_size = e->space_size;
759 sentence_space_size = e->sentence_space_size;
760 adjust_mode = e->adjust_mode;
761 fill = e->fill;
762 interrupted = 0;
763 prev_line_interrupted = 0;
764 center_lines = 0;
765 right_justify_lines = 0;
766 prev_vertical_spacing = e->prev_vertical_spacing;
767 vertical_spacing = e->vertical_spacing;
768 prev_post_vertical_spacing = e->prev_post_vertical_spacing,
769 post_vertical_spacing = e->post_vertical_spacing,
770 prev_line_spacing = e->prev_line_spacing;
771 line_spacing = e->line_spacing;
772 prev_indent = e->prev_indent;
773 indent = e->indent;
774 have_temporary_indent = 0;
775 temporary_indent = 0;
776 underline_lines = 0;
777 underline_spaces = 0;
778 input_trap_count = 0;
779 continued_input_trap = 0;
780 prev_text_length = e->prev_text_length;
781 width_total = 0;
782 space_total = 0;
783 input_line_start = 0;
784 control_char = e->control_char;
785 no_break_control_char = e->no_break_control_char;
786 hyphen_indicator_char = e->hyphen_indicator_char;
787 spread_flag = 0;
788 line = 0;
789 pending_lines = 0;
790 discarding = 0;
791 tabs = e->tabs;
792 line_tabs = e->line_tabs;
793 current_tab = TAB_NONE;
794 current_field = 0;
795 margin_character_flags = e->margin_character_flags;
796 margin_character_node = e->margin_character_node;
797 margin_character_distance = e->margin_character_distance;
798 numbering_nodes = 0;
799 number_text_separation = e->number_text_separation;
800 line_number_multiple = e->line_number_multiple;
801 line_number_indent = e->line_number_indent;
802 no_number_count = e->no_number_count;
803 tab_char = e->tab_char;
804 leader_char = e->leader_char;
805 hyphenation_flags = e->hyphenation_flags;
806 fontno = e->fontno;
807 prev_fontno = e->prev_fontno;
808 dummy = e->dummy;
809 family = e->family;
810 prev_family = e->prev_family;
811 leader_node = 0;
812 #ifdef WIDOW_CONTROL
813 widow_control = e->widow_control;
814 #endif /* WIDOW_CONTROL */
815 hyphen_line_max = e->hyphen_line_max;
816 hyphen_line_count = 0;
817 hyphenation_space = e->hyphenation_space;
818 hyphenation_margin = e->hyphenation_margin;
819 composite = 0;
820 ignore_next_eol = e->ignore_next_eol;
821 emitted_node = e->emitted_node;
822 glyph_color= e->glyph_color;
823 prev_glyph_color = e->prev_glyph_color;
824 fill_color = e->fill_color;
825 prev_fill_color = e->prev_fill_color;
828 environment::~environment()
830 delete leader_node;
831 delete_node_list(line);
832 delete_node_list(numbering_nodes);
835 hunits environment::get_input_line_position()
837 hunits n;
838 if (line == 0)
839 n = -input_line_start;
840 else
841 n = width_total - input_line_start;
842 if (current_tab)
843 n += tab_width;
844 return n;
847 void environment::set_input_line_position(hunits n)
849 input_line_start = line == 0 ? -n : width_total - n;
850 if (current_tab)
851 input_line_start += tab_width;
854 hunits environment::get_line_length()
856 return line_length;
859 hunits environment::get_saved_line_length()
861 if (line)
862 return target_text_length + saved_indent;
863 else
864 return line_length;
867 vunits environment::get_vertical_spacing()
869 return vertical_spacing;
872 vunits environment::get_post_vertical_spacing()
874 return post_vertical_spacing;
877 int environment::get_line_spacing()
879 return line_spacing;
882 vunits environment::total_post_vertical_spacing()
884 vunits tem(post_vertical_spacing);
885 if (line_spacing > 1)
886 tem += (line_spacing - 1)*vertical_spacing;
887 return tem;
890 int environment::get_bold()
892 return get_bold_fontno(fontno);
895 hunits environment::get_digit_width()
897 return env_digit_width(this);
900 int environment::get_adjust_mode()
902 return adjust_mode;
905 int environment::get_fill()
907 return fill;
910 hunits environment::get_indent()
912 return indent;
915 hunits environment::get_saved_indent()
917 if (line)
918 return saved_indent;
919 else if (have_temporary_indent)
920 return temporary_indent;
921 else
922 return indent;
925 hunits environment::get_temporary_indent()
927 return temporary_indent;
930 hunits environment::get_title_length()
932 return title_length;
935 node *environment::get_prev_char()
937 for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
938 node *last = n->last_char_node();
939 if (last)
940 return last;
942 return 0;
945 hunits environment::get_prev_char_width()
947 node *last = get_prev_char();
948 if (!last)
949 return H0;
950 return last->width();
953 hunits environment::get_prev_char_skew()
955 node *last = get_prev_char();
956 if (!last)
957 return H0;
958 return last->skew();
961 vunits environment::get_prev_char_height()
963 node *last = get_prev_char();
964 if (!last)
965 return V0;
966 vunits min, max;
967 last->vertical_extent(&min, &max);
968 return -min;
971 vunits environment::get_prev_char_depth()
973 node *last = get_prev_char();
974 if (!last)
975 return V0;
976 vunits min, max;
977 last->vertical_extent(&min, &max);
978 return max;
981 hunits environment::get_text_length()
983 hunits n = line == 0 ? H0 : width_total;
984 if (current_tab)
985 n += tab_width;
986 return n;
989 hunits environment::get_prev_text_length()
991 return prev_text_length;
995 static int sb_reg_contents = 0;
996 static int st_reg_contents = 0;
997 static int ct_reg_contents = 0;
998 static int rsb_reg_contents = 0;
999 static int rst_reg_contents = 0;
1000 static int skw_reg_contents = 0;
1001 static int ssc_reg_contents = 0;
1003 void environment::width_registers()
1005 // this is used to implement \w; it sets the st, sb, ct registers
1006 vunits min = 0, max = 0, cur = 0;
1007 int character_type = 0;
1008 ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
1009 skw_reg_contents = line ? line->skew().to_units() : 0;
1010 line = reverse_node_list(line);
1011 vunits real_min = V0;
1012 vunits real_max = V0;
1013 vunits v1, v2;
1014 for (node *tem = line; tem; tem = tem->next) {
1015 tem->vertical_extent(&v1, &v2);
1016 v1 += cur;
1017 if (v1 < real_min)
1018 real_min = v1;
1019 v2 += cur;
1020 if (v2 > real_max)
1021 real_max = v2;
1022 if ((cur += tem->vertical_width()) < min)
1023 min = cur;
1024 else if (cur > max)
1025 max = cur;
1026 character_type |= tem->character_type();
1028 line = reverse_node_list(line);
1029 st_reg_contents = -min.to_units();
1030 sb_reg_contents = -max.to_units();
1031 rst_reg_contents = -real_min.to_units();
1032 rsb_reg_contents = -real_max.to_units();
1033 ct_reg_contents = character_type;
1036 node *environment::extract_output_line()
1038 if (current_tab)
1039 wrap_up_tab();
1040 node *n = line;
1041 line = 0;
1042 return n;
1045 /* environment related requests */
1047 void environment_switch()
1049 int pop = 0; // 1 means pop, 2 means pop but no error message on underflow
1050 if (curenv->is_dummy())
1051 error("can't switch environments when current environment is dummy");
1052 else if (!has_arg())
1053 pop = 1;
1054 else {
1055 symbol nm;
1056 if (!tok.delimiter()) {
1057 // It looks like a number.
1058 int n;
1059 if (get_integer(&n)) {
1060 if (n >= 0 && n < NENVIRONMENTS) {
1061 env_stack = new env_list(curenv, env_stack);
1062 if (env_table[n] == 0)
1063 env_table[n] = new environment(i_to_a(n));
1064 curenv = env_table[n];
1066 else
1067 nm = i_to_a(n);
1069 else
1070 pop = 2;
1072 else {
1073 nm = get_long_name(1);
1074 if (nm.is_null())
1075 pop = 2;
1077 if (!nm.is_null()) {
1078 environment *e = (environment *)env_dictionary.lookup(nm);
1079 if (!e) {
1080 e = new environment(nm);
1081 (void)env_dictionary.lookup(nm, e);
1083 env_stack = new env_list(curenv, env_stack);
1084 curenv = e;
1087 if (pop) {
1088 if (env_stack == 0) {
1089 if (pop == 1)
1090 error("environment stack underflow");
1092 else {
1093 curenv = env_stack->env;
1094 env_list *tem = env_stack;
1095 env_stack = env_stack->next;
1096 delete tem;
1099 skip_line();
1102 void environment_copy()
1104 symbol nm;
1105 environment *e=0;
1106 tok.skip();
1107 if (!tok.delimiter()) {
1108 // It looks like a number.
1109 int n;
1110 if (get_integer(&n)) {
1111 if (n >= 0 && n < NENVIRONMENTS)
1112 e = env_table[n];
1113 else
1114 nm = i_to_a(n);
1117 else
1118 nm = get_long_name(1);
1119 if (!e && !nm.is_null())
1120 e = (environment *)env_dictionary.lookup(nm);
1121 if (e == 0) {
1122 error("No environment to copy from");
1123 return;
1125 else
1126 curenv->copy(e);
1127 skip_line();
1130 static symbol P_symbol("P");
1132 void font_change()
1134 symbol s = get_name();
1135 int is_number = 1;
1136 if (s.is_null() || s == P_symbol) {
1137 s = P_symbol;
1138 is_number = 0;
1140 else {
1141 for (const char *p = s.contents(); p != 0 && *p != 0; p++)
1142 if (!csdigit(*p)) {
1143 is_number = 0;
1144 break;
1147 if (is_number)
1148 curenv->set_font(atoi(s.contents()));
1149 else
1150 curenv->set_font(s);
1151 skip_line();
1154 void family_change()
1156 symbol s = get_name();
1157 curenv->set_family(s);
1158 skip_line();
1161 void point_size()
1163 int n;
1164 if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) {
1165 if (n <= 0)
1166 n = 1;
1167 curenv->set_size(n);
1168 curenv->add_html_tag(0, ".ps", n);
1170 else
1171 curenv->set_size(0);
1172 skip_line();
1175 void override_sizes()
1177 int n = 16;
1178 int *sizes = new int[n];
1179 int i = 0;
1180 char *buf = read_string();
1181 if (!buf)
1182 return;
1183 char *p = strtok(buf, " \t");
1184 for (;;) {
1185 if (!p)
1186 break;
1187 int lower, upper;
1188 switch (sscanf(p, "%d-%d", &lower, &upper)) {
1189 case 1:
1190 upper = lower;
1191 // fall through
1192 case 2:
1193 if (lower <= upper && lower >= 0)
1194 break;
1195 // fall through
1196 default:
1197 warning(WARN_RANGE, "bad size range `%1'", p);
1198 return;
1200 if (i + 2 > n) {
1201 int *old_sizes = sizes;
1202 sizes = new int[n*2];
1203 memcpy(sizes, old_sizes, n*sizeof(int));
1204 n *= 2;
1205 a_delete old_sizes;
1207 sizes[i++] = lower;
1208 if (lower == 0)
1209 break;
1210 sizes[i++] = upper;
1211 p = strtok(0, " \t");
1213 font_size::init_size_table(sizes);
1216 void space_size()
1218 int n;
1219 if (get_integer(&n)) {
1220 curenv->space_size = n;
1221 if (has_arg() && get_integer(&n))
1222 curenv->sentence_space_size = n;
1223 else
1224 curenv->sentence_space_size = curenv->space_size;
1226 skip_line();
1229 void fill()
1231 while (!tok.newline() && !tok.eof())
1232 tok.next();
1233 if (break_flag)
1234 curenv->do_break();
1235 curenv->fill = 1;
1236 curenv->add_html_tag(1, ".fi");
1237 curenv->add_html_tag(0, ".br");
1238 tok.next();
1241 void no_fill()
1243 while (!tok.newline() && !tok.eof())
1244 tok.next();
1245 if (break_flag)
1246 curenv->do_break();
1247 curenv->fill = 0;
1248 curenv->add_html_tag(1, ".nf");
1249 curenv->add_html_tag(0, ".br");
1250 curenv->add_html_tag(0, ".po", topdiv->get_page_offset().to_units());
1251 tok.next();
1254 void center()
1256 int n;
1257 if (!has_arg() || !get_integer(&n))
1258 n = 1;
1259 else if (n < 0)
1260 n = 0;
1261 while (!tok.newline() && !tok.eof())
1262 tok.next();
1263 if (break_flag)
1264 curenv->do_break();
1265 curenv->right_justify_lines = 0;
1266 curenv->center_lines = n;
1267 curenv->add_html_tag(1, ".ce", n);
1268 tok.next();
1271 void right_justify()
1273 int n;
1274 if (!has_arg() || !get_integer(&n))
1275 n = 1;
1276 else if (n < 0)
1277 n = 0;
1278 while (!tok.newline() && !tok.eof())
1279 tok.next();
1280 if (break_flag)
1281 curenv->do_break();
1282 curenv->center_lines = 0;
1283 curenv->right_justify_lines = n;
1284 curenv->add_html_tag(1, ".rj", n);
1285 tok.next();
1288 void line_length()
1290 hunits temp;
1291 if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) {
1292 if (temp < H0) {
1293 warning(WARN_RANGE, "bad line length %1u", temp.to_units());
1294 temp = H0;
1297 else
1298 temp = curenv->prev_line_length;
1299 curenv->prev_line_length = curenv->line_length;
1300 curenv->line_length = temp;
1301 curenv->add_html_tag(1, ".ll", temp.to_units());
1302 skip_line();
1305 void title_length()
1307 hunits temp;
1308 if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) {
1309 if (temp < H0) {
1310 warning(WARN_RANGE, "bad title length %1u", temp.to_units());
1311 temp = H0;
1314 else
1315 temp = curenv->prev_title_length;
1316 curenv->prev_title_length = curenv->title_length;
1317 curenv->title_length = temp;
1318 skip_line();
1321 void vertical_spacing()
1323 vunits temp;
1324 if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) {
1325 if (temp < V0) {
1326 warning(WARN_RANGE, "vertical spacing must not be negative");
1327 temp = vresolution;
1330 else
1331 temp = curenv->prev_vertical_spacing;
1332 curenv->prev_vertical_spacing = curenv->vertical_spacing;
1333 curenv->vertical_spacing = temp;
1334 skip_line();
1337 void post_vertical_spacing()
1339 vunits temp;
1340 if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) {
1341 if (temp < V0) {
1342 warning(WARN_RANGE,
1343 "post vertical spacing must be greater than or equal to 0");
1344 temp = V0;
1347 else
1348 temp = curenv->prev_post_vertical_spacing;
1349 curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
1350 curenv->post_vertical_spacing = temp;
1351 skip_line();
1354 void line_spacing()
1356 int temp;
1357 if (has_arg() && get_integer(&temp)) {
1358 if (temp < 1) {
1359 warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
1360 temp = 1;
1363 else
1364 temp = curenv->prev_line_spacing;
1365 curenv->prev_line_spacing = curenv->line_spacing;
1366 curenv->line_spacing = temp;
1367 skip_line();
1370 void indent()
1372 hunits temp;
1373 if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) {
1374 if (temp < H0) {
1375 warning(WARN_RANGE, "indent cannot be negative");
1376 temp = H0;
1379 else
1380 temp = curenv->prev_indent;
1381 while (!tok.newline() && !tok.eof())
1382 tok.next();
1383 if (break_flag)
1384 curenv->do_break();
1385 curenv->have_temporary_indent = 0;
1386 curenv->prev_indent = curenv->indent;
1387 curenv->indent = temp;
1388 if (break_flag)
1389 curenv->add_html_tag(1, ".in", temp.to_units());
1390 tok.next();
1393 void temporary_indent()
1395 int err = 0;
1396 hunits temp;
1397 if (!get_hunits(&temp, 'm', curenv->get_indent()))
1398 err = 1;
1399 while (!tok.newline() && !tok.eof())
1400 tok.next();
1401 if (break_flag)
1402 curenv->do_break();
1403 if (temp < H0) {
1404 warning(WARN_RANGE, "total indent cannot be negative");
1405 temp = H0;
1407 if (!err) {
1408 curenv->temporary_indent = temp;
1409 curenv->have_temporary_indent = 1;
1410 curenv->add_html_tag(1, ".ti", temp.to_units());
1412 tok.next();
1415 node *do_underline_special(int underline_spaces)
1417 macro m;
1418 m.append_str("x u ");
1419 m.append(underline_spaces + '0');
1420 return new special_node(m, 1);
1423 void do_underline(int underline_spaces)
1425 int n;
1426 if (!has_arg() || !get_integer(&n))
1427 n = 1;
1428 if (n <= 0) {
1429 if (curenv->underline_lines > 0) {
1430 curenv->prev_fontno = curenv->fontno;
1431 curenv->fontno = curenv->pre_underline_fontno;
1432 if (underline_spaces) {
1433 curenv->underline_spaces = 0;
1434 curenv->add_node(do_underline_special(0));
1437 curenv->underline_lines = 0;
1439 else {
1440 curenv->underline_lines = n;
1441 curenv->pre_underline_fontno = curenv->fontno;
1442 curenv->fontno = get_underline_fontno();
1443 if (underline_spaces) {
1444 curenv->underline_spaces = 1;
1445 curenv->add_node(do_underline_special(1));
1448 skip_line();
1451 void continuous_underline()
1453 do_underline(1);
1456 void underline()
1458 do_underline(0);
1461 void control_char()
1463 curenv->control_char = '.';
1464 if (has_arg()) {
1465 if (tok.ch() == 0)
1466 error("bad control character");
1467 else
1468 curenv->control_char = tok.ch();
1470 skip_line();
1473 void no_break_control_char()
1475 curenv->no_break_control_char = '\'';
1476 if (has_arg()) {
1477 if (tok.ch() == 0)
1478 error("bad control character");
1479 else
1480 curenv->no_break_control_char = tok.ch();
1482 skip_line();
1485 void margin_character()
1487 while (tok.space())
1488 tok.next();
1489 charinfo *ci = tok.get_char();
1490 if (ci) {
1491 // Call tok.next() only after making the node so that
1492 // .mc \s+9\(br\s0 works.
1493 node *nd = curenv->make_char_node(ci);
1494 tok.next();
1495 if (nd) {
1496 delete curenv->margin_character_node;
1497 curenv->margin_character_node = nd;
1498 curenv->margin_character_flags = (MARGIN_CHARACTER_ON
1499 |MARGIN_CHARACTER_NEXT);
1500 hunits d;
1501 if (has_arg() && get_hunits(&d, 'm'))
1502 curenv->margin_character_distance = d;
1505 else {
1506 check_missing_character();
1507 curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON;
1508 if (curenv->margin_character_flags == 0) {
1509 delete curenv->margin_character_node;
1510 curenv->margin_character_node = 0;
1513 skip_line();
1516 void number_lines()
1518 delete_node_list(curenv->numbering_nodes);
1519 curenv->numbering_nodes = 0;
1520 if (has_arg()) {
1521 node *nd = 0;
1522 for (int i = '9'; i >= '0'; i--) {
1523 node *tem = make_node(charset_table[i], curenv);
1524 if (!tem) {
1525 skip_line();
1526 return;
1528 tem->next = nd;
1529 nd = tem;
1531 curenv->numbering_nodes = nd;
1532 curenv->line_number_digit_width = env_digit_width(curenv);
1533 int n;
1534 if (!tok.delimiter()) {
1535 if (get_integer(&n, next_line_number)) {
1536 next_line_number = n;
1537 if (next_line_number < 0) {
1538 warning(WARN_RANGE, "negative line number");
1539 next_line_number = 0;
1543 else
1544 while (!tok.space() && !tok.newline() && !tok.eof())
1545 tok.next();
1546 if (has_arg()) {
1547 if (!tok.delimiter()) {
1548 if (get_integer(&n)) {
1549 if (n <= 0) {
1550 warning(WARN_RANGE, "negative or zero line number multiple");
1552 else
1553 curenv->line_number_multiple = n;
1556 else
1557 while (!tok.space() && !tok.newline() && !tok.eof())
1558 tok.next();
1559 if (has_arg()) {
1560 if (!tok.delimiter()) {
1561 if (get_integer(&n))
1562 curenv->number_text_separation = n;
1564 else
1565 while (!tok.space() && !tok.newline() && !tok.eof())
1566 tok.next();
1567 if (has_arg() && !tok.delimiter() && get_integer(&n))
1568 curenv->line_number_indent = n;
1572 skip_line();
1575 void no_number()
1577 int n;
1578 if (has_arg() && get_integer(&n))
1579 curenv->no_number_count = n > 0 ? n : 0;
1580 else
1581 curenv->no_number_count = 1;
1582 skip_line();
1585 void no_hyphenate()
1587 curenv->hyphenation_flags = 0;
1588 skip_line();
1591 void hyphenate_request()
1593 int n;
1594 if (has_arg() && get_integer(&n))
1595 curenv->hyphenation_flags = n;
1596 else
1597 curenv->hyphenation_flags = 1;
1598 skip_line();
1601 void hyphen_char()
1603 curenv->hyphen_indicator_char = get_optional_char();
1604 skip_line();
1607 void hyphen_line_max_request()
1609 int n;
1610 if (has_arg() && get_integer(&n))
1611 curenv->hyphen_line_max = n;
1612 else
1613 curenv->hyphen_line_max = -1;
1614 skip_line();
1617 void environment::interrupt()
1619 if (!dummy) {
1620 add_node(new transparent_dummy_node);
1621 interrupted = 1;
1625 void environment::newline()
1627 if (underline_lines > 0) {
1628 if (--underline_lines == 0) {
1629 prev_fontno = fontno;
1630 fontno = pre_underline_fontno;
1631 if (underline_spaces) {
1632 underline_spaces = 0;
1633 add_node(do_underline_special(0));
1637 if (current_field)
1638 wrap_up_field();
1639 if (current_tab)
1640 wrap_up_tab();
1641 // strip trailing spaces
1642 while (line != 0 && line->discardable()) {
1643 width_total -= line->width();
1644 space_total -= line->nspaces();
1645 node *tem = line;
1646 line = line->next;
1647 delete tem;
1649 node *to_be_output = 0;
1650 hunits to_be_output_width;
1651 prev_line_interrupted = 0;
1652 if (dummy)
1653 space_newline();
1654 else if (interrupted) {
1655 interrupted = 0;
1656 // see environment::final_break
1657 prev_line_interrupted = exit_started ? 2 : 1;
1659 else if (center_lines > 0) {
1660 --center_lines;
1661 hunits x = target_text_length - width_total;
1662 if (x > H0)
1663 saved_indent += x/2;
1664 to_be_output = line;
1665 if (is_html) {
1666 node *n = make_html_tag("eol.ce");
1667 n->next = to_be_output;
1668 to_be_output = n;
1670 to_be_output_width = width_total;
1671 line = 0;
1673 else if (right_justify_lines > 0) {
1674 --right_justify_lines;
1675 hunits x = target_text_length - width_total;
1676 if (x > H0)
1677 saved_indent += x;
1678 to_be_output = line;
1679 to_be_output_width = width_total;
1680 line = 0;
1682 else if (fill)
1683 space_newline();
1684 else {
1685 to_be_output = line;
1686 to_be_output_width = width_total;
1687 line = 0;
1689 input_line_start = line == 0 ? H0 : width_total;
1690 if (to_be_output) {
1691 if (is_html && !fill) {
1692 if (curdiv == topdiv) {
1693 node *n = make_html_tag("eol");
1695 n->next = to_be_output;
1696 to_be_output = n;
1699 output_line(to_be_output, to_be_output_width);
1700 hyphen_line_count = 0;
1702 if (input_trap_count > 0) {
1703 if (!(continued_input_trap && prev_line_interrupted))
1704 if (--input_trap_count == 0)
1705 spring_trap(input_trap);
1709 void environment::output_line(node *n, hunits width)
1711 prev_text_length = width;
1712 if (margin_character_flags) {
1713 hunits d = line_length + margin_character_distance - saved_indent - width;
1714 if (d > 0) {
1715 n = new hmotion_node(d, get_fill_color(), n);
1716 width += d;
1718 margin_character_flags &= ~MARGIN_CHARACTER_NEXT;
1719 node *tem;
1720 if (!margin_character_flags) {
1721 tem = margin_character_node;
1722 margin_character_node = 0;
1724 else
1725 tem = margin_character_node->copy();
1726 tem->next = n;
1727 n = tem;
1728 width += tem->width();
1730 node *nn = 0;
1731 while (n != 0) {
1732 node *tem = n->next;
1733 n->next = nn;
1734 nn = n;
1735 n = tem;
1737 if (!saved_indent.is_zero())
1738 nn = new hmotion_node(saved_indent, get_fill_color(), nn);
1739 width += saved_indent;
1740 if (no_number_count > 0)
1741 --no_number_count;
1742 else if (numbering_nodes) {
1743 hunits w = (line_number_digit_width
1744 *(3+line_number_indent+number_text_separation));
1745 if (next_line_number % line_number_multiple != 0)
1746 nn = new hmotion_node(w, get_fill_color(), nn);
1747 else {
1748 hunits x = w;
1749 nn = new hmotion_node(number_text_separation * line_number_digit_width,
1750 get_fill_color(), nn);
1751 x -= number_text_separation*line_number_digit_width;
1752 char buf[30];
1753 sprintf(buf, "%3d", next_line_number);
1754 for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
1755 node *gn = numbering_nodes;
1756 for (int count = *p - '0'; count > 0; count--)
1757 gn = gn->next;
1758 gn = gn->copy();
1759 x -= gn->width();
1760 gn->next = nn;
1761 nn = gn;
1763 nn = new hmotion_node(x, get_fill_color(), nn);
1765 width += w;
1766 ++next_line_number;
1768 output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width);
1771 void environment::start_line()
1773 assert(line == 0);
1774 discarding = 0;
1775 line = new line_start_node;
1776 if (have_temporary_indent) {
1777 saved_indent = temporary_indent;
1778 have_temporary_indent = 0;
1780 else
1781 saved_indent = indent;
1782 target_text_length = line_length - saved_indent;
1783 width_total = H0;
1784 space_total = 0;
1787 hunits environment::get_hyphenation_space()
1789 return hyphenation_space;
1792 void hyphenation_space_request()
1794 hunits n;
1795 if (get_hunits(&n, 'm')) {
1796 if (n < H0) {
1797 warning(WARN_RANGE, "hyphenation space cannot be negative");
1798 n = H0;
1800 curenv->hyphenation_space = n;
1802 skip_line();
1805 hunits environment::get_hyphenation_margin()
1807 return hyphenation_margin;
1810 void hyphenation_margin_request()
1812 hunits n;
1813 if (get_hunits(&n, 'm')) {
1814 if (n < H0) {
1815 warning(WARN_RANGE, "hyphenation margin cannot be negative");
1816 n = H0;
1818 curenv->hyphenation_margin = n;
1820 skip_line();
1823 breakpoint *environment::choose_breakpoint()
1825 hunits x = width_total;
1826 int s = space_total;
1827 node *n = line;
1828 breakpoint *best_bp = 0; // the best breakpoint so far
1829 int best_bp_fits = 0;
1830 while (n != 0) {
1831 x -= n->width();
1832 s -= n->nspaces();
1833 breakpoint *bp = n->get_breakpoints(x, s);
1834 while (bp != 0) {
1835 if (bp->width <= target_text_length) {
1836 if (!bp->hyphenated) {
1837 breakpoint *tem = bp->next;
1838 bp->next = 0;
1839 while (tem != 0) {
1840 breakpoint *tem1 = tem;
1841 tem = tem->next;
1842 delete tem1;
1844 if (best_bp_fits
1845 // Decide whether to use the hyphenated breakpoint.
1846 && (hyphen_line_max < 0
1847 // Only choose the hyphenated breakpoint if it would not
1848 // exceed the maximum number of consecutive hyphenated
1849 // lines.
1850 || hyphen_line_count + 1 <= hyphen_line_max)
1851 && !(adjust_mode == ADJUST_BOTH
1852 // Don't choose the hyphenated breakpoint if the line
1853 // can be justified by adding no more than
1854 // hyphenation_space to any word space.
1855 ? (bp->nspaces > 0
1856 && (((target_text_length - bp->width
1857 + (bp->nspaces - 1)*hresolution)/bp->nspaces)
1858 <= hyphenation_space))
1859 // Don't choose the hyphenated breakpoint if the line
1860 // is no more than hyphenation_margin short.
1861 : target_text_length - bp->width <= hyphenation_margin)) {
1862 delete bp;
1863 return best_bp;
1865 if (best_bp)
1866 delete best_bp;
1867 return bp;
1869 else {
1870 if ((adjust_mode == ADJUST_BOTH
1871 ? hyphenation_space == H0
1872 : hyphenation_margin == H0)
1873 && (hyphen_line_max < 0
1874 || hyphen_line_count + 1 <= hyphen_line_max)) {
1875 // No need to consider a non-hyphenated breakpoint.
1876 if (best_bp)
1877 delete best_bp;
1878 breakpoint *tem = bp->next;
1879 bp->next = 0;
1880 while (tem != 0) {
1881 breakpoint *tem1 = tem;
1882 tem = tem->next;
1883 delete tem1;
1885 return bp;
1887 // It fits but it's hyphenated.
1888 if (!best_bp_fits) {
1889 if (best_bp)
1890 delete best_bp;
1891 best_bp = bp;
1892 bp = bp->next;
1893 best_bp_fits = 1;
1895 else {
1896 breakpoint *tem = bp;
1897 bp = bp->next;
1898 delete tem;
1902 else {
1903 if (best_bp)
1904 delete best_bp;
1905 best_bp = bp;
1906 bp = bp->next;
1909 n = n->next;
1911 if (best_bp) {
1912 if (!best_bp_fits)
1913 output_warning(WARN_BREAK, "can't break line");
1914 return best_bp;
1916 return 0;
1919 void environment::hyphenate_line(int start_here)
1921 assert(line != 0);
1922 hyphenation_type prev_type = line->get_hyphenation_type();
1923 node **startp;
1924 if (start_here)
1925 startp = &line;
1926 else
1927 for (startp = &line->next; *startp != 0; startp = &(*startp)->next) {
1928 hyphenation_type this_type = (*startp)->get_hyphenation_type();
1929 if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
1930 break;
1931 prev_type = this_type;
1933 if (*startp == 0)
1934 return;
1935 node *tem = *startp;
1936 do {
1937 tem = tem->next;
1938 } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
1939 int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
1940 node *end = tem;
1941 hyphen_list *sl = 0;
1942 tem = *startp;
1943 node *forward = 0;
1944 int i = 0;
1945 while (tem != end) {
1946 sl = tem->get_hyphen_list(sl, &i);
1947 node *tem1 = tem;
1948 tem = tem->next;
1949 tem1->next = forward;
1950 forward = tem1;
1952 if (!inhibit) {
1953 // this is for characters like hyphen and emdash
1954 int prev_code = 0;
1955 for (hyphen_list *h = sl; h; h = h->next) {
1956 h->breakable = (prev_code != 0
1957 && h->next != 0
1958 && h->next->hyphenation_code != 0);
1959 prev_code = h->hyphenation_code;
1962 if (hyphenation_flags != 0
1963 && !inhibit
1964 // this may not be right if we have extra space on this line
1965 && !((hyphenation_flags & HYPHEN_LAST_LINE)
1966 && (curdiv->distance_to_next_trap()
1967 <= vertical_spacing + total_post_vertical_spacing()))
1968 && i >= 4)
1969 hyphenate(sl, hyphenation_flags);
1970 while (forward != 0) {
1971 node *tem1 = forward;
1972 forward = forward->next;
1973 tem1->next = 0;
1974 tem = tem1->add_self(tem, &sl);
1976 *startp = tem;
1979 static node *node_list_reverse(node *n)
1981 node *res = 0;
1982 while (n) {
1983 node *tem = n;
1984 n = n->next;
1985 tem->next = res;
1986 res = tem;
1988 return res;
1991 static void distribute_space(node *n, int nspaces, hunits desired_space,
1992 int force_reverse = 0)
1994 static int reverse = 0;
1995 if (force_reverse || reverse)
1996 n = node_list_reverse(n);
1997 if (!force_reverse && nspaces > 0 && spread_limit >= 0
1998 && desired_space.to_units() > 0) {
1999 hunits em = curenv->get_size();
2000 double Ems = (double)desired_space.to_units() / nspaces
2001 / (em.is_zero() ? hresolution : em.to_units());
2002 if (Ems > spread_limit)
2003 output_warning(WARN_BREAK, "spreading %1m per space", Ems);
2005 for (node *tem = n; tem; tem = tem->next)
2006 tem->spread_space(&nspaces, &desired_space);
2007 if (force_reverse || reverse)
2008 (void)node_list_reverse(n);
2009 if (!force_reverse)
2010 reverse = !reverse;
2011 assert(desired_space.is_zero() && nspaces == 0);
2014 void environment::possibly_break_line(int start_here, int forced)
2016 if (!fill || current_tab || current_field || dummy)
2017 return;
2018 while (line != 0
2019 && (forced
2020 // When a macro follows a paragraph in fill mode, the
2021 // current line should not be empty.
2022 || (width_total - line->width()) > target_text_length)) {
2023 hyphenate_line(start_here);
2024 breakpoint *bp = choose_breakpoint();
2025 if (bp == 0)
2026 // we'll find one eventually
2027 return;
2028 node *pre, *post;
2029 node **ndp = &line;
2030 while (*ndp != bp->nd)
2031 ndp = &(*ndp)->next;
2032 bp->nd->split(bp->index, &pre, &post);
2033 *ndp = post;
2034 hunits extra_space_width = H0;
2035 switch(adjust_mode) {
2036 case ADJUST_BOTH:
2037 if (bp->nspaces != 0)
2038 extra_space_width = target_text_length - bp->width;
2039 else if (bp->width > 0 && target_text_length > 0
2040 && target_text_length > bp->width)
2041 output_warning(WARN_BREAK, "cannot adjust line");
2042 break;
2043 case ADJUST_CENTER:
2044 saved_indent += (target_text_length - bp->width)/2;
2045 break;
2046 case ADJUST_RIGHT:
2047 saved_indent += target_text_length - bp->width;
2048 break;
2050 distribute_space(pre, bp->nspaces, extra_space_width);
2051 hunits output_width = bp->width + extra_space_width;
2052 input_line_start -= output_width;
2053 if (bp->hyphenated)
2054 hyphen_line_count++;
2055 else
2056 hyphen_line_count = 0;
2057 delete bp;
2058 space_total = 0;
2059 width_total = 0;
2060 node *first_non_discardable = 0;
2061 node *tem;
2062 for (tem = line; tem != 0; tem = tem->next)
2063 if (!tem->discardable())
2064 first_non_discardable = tem;
2065 node *to_be_discarded;
2066 if (first_non_discardable) {
2067 to_be_discarded = first_non_discardable->next;
2068 first_non_discardable->next = 0;
2069 for (tem = line; tem != 0; tem = tem->next) {
2070 width_total += tem->width();
2071 space_total += tem->nspaces();
2073 discarding = 0;
2075 else {
2076 discarding = 1;
2077 to_be_discarded = line;
2078 line = 0;
2080 // Do output_line() here so that line will be 0 iff the
2081 // the environment will be empty.
2082 output_line(pre, output_width);
2083 while (to_be_discarded != 0) {
2084 tem = to_be_discarded;
2085 to_be_discarded = to_be_discarded->next;
2086 input_line_start -= tem->width();
2087 delete tem;
2089 if (line != 0) {
2090 if (have_temporary_indent) {
2091 saved_indent = temporary_indent;
2092 have_temporary_indent = 0;
2094 else
2095 saved_indent = indent;
2096 target_text_length = line_length - saved_indent;
2102 Do the break at the end of input after the end macro (if any).
2104 Unix troff behaves as follows: if the last line is
2106 foo bar\c
2108 it will output foo on the current page, and bar on the next page;
2109 if the last line is
2111 foo\c
2115 foo bar
2117 everything will be output on the current page. This behaviour must be
2118 considered a bug.
2120 The problem is that some macro packages rely on this. For example,
2121 the ATK macros have an end macro that emits \c if it needs to print a
2122 table of contents but doesn't do a 'bp in the end macro; instead the
2123 'bp is done in the bottom of page trap. This works with Unix troff,
2124 provided that the current environment is not empty at the end of the
2125 input file.
2127 The following will make macro packages that do that sort of thing work
2128 even if the current environment is empty at the end of the input file.
2129 If the last input line used \c and this line occurred in the end macro,
2130 then we'll force everything out on the current page, but we'll make
2131 sure that the environment isn't empty so that we won't exit at the
2132 bottom of this page.
2135 void environment::final_break()
2137 if (prev_line_interrupted == 2) {
2138 do_break();
2139 add_node(new transparent_dummy_node);
2141 else
2142 do_break();
2146 * add_html_tag - emits a special html-tag: to help post-grohtml understand
2147 * the key troff commands
2150 void environment::add_html_tag(int force, const char *nm)
2152 if (!force && (curdiv != topdiv))
2153 return;
2155 if (is_html) {
2157 * need to emit tag for post-grohtml
2158 * but we check to see whether we can emit specials
2160 if (curdiv == topdiv && topdiv->before_first_page)
2161 topdiv->begin_page();
2162 macro *m = new macro;
2163 m->append_str("html-tag:");
2164 for (const char *p = nm; *p; p++)
2165 if (!invalid_input_char((unsigned char)*p))
2166 m->append(*p);
2167 curdiv->output(new special_node(*m), 1, 0, 0, 0);
2168 if (strcmp(nm, ".nf") == 0)
2169 curenv->ignore_next_eol = 1;
2174 * add_html_tag - emits a special html-tag: to help post-grohtml understand
2175 * the key troff commands, it appends a string representation
2176 * of i.
2179 void environment::add_html_tag(int force, const char *nm, int i)
2181 if (!force && (curdiv != topdiv))
2182 return;
2184 if (is_html) {
2186 * need to emit tag for post-grohtml
2187 * but we check to see whether we can emit specials
2189 if (curdiv == topdiv && topdiv->before_first_page)
2190 topdiv->begin_page();
2191 macro *m = new macro;
2192 m->append_str("html-tag:");
2193 for (const char *p = nm; *p; p++)
2194 if (!invalid_input_char((unsigned char)*p))
2195 m->append(*p);
2196 m->append(' ');
2197 m->append_int(i);
2198 node *n = new special_node(*m);
2199 curdiv->output(n, 1, 0, 0, 0);
2204 * add_html_tag_tabs - emits the tab settings for post-grohtml
2207 void environment::add_html_tag_tabs(int force)
2209 if (!force && (curdiv != topdiv))
2210 return;
2212 if (is_html) {
2214 * need to emit tag for post-grohtml
2215 * but we check to see whether we can emit specials
2217 if (curdiv == topdiv && topdiv->before_first_page)
2218 topdiv->begin_page();
2219 macro *m = new macro;
2220 hunits d, l;
2221 enum tab_type t;
2222 m->append_str("html-tag:.ta ");
2223 do {
2224 t = curenv->tabs.distance_to_next_tab(l, &d);
2225 l += d;
2226 switch (t) {
2227 case TAB_LEFT:
2228 m->append_str(" L ");
2229 m->append_int(l.to_units());
2230 break;
2231 case TAB_CENTER:
2232 m->append_str(" C ");
2233 m->append_int(l.to_units());
2234 break;
2235 case TAB_RIGHT:
2236 m->append_str(" R ");
2237 m->append_int(l.to_units());
2238 break;
2239 case TAB_NONE:
2240 break;
2242 } while ((t != TAB_NONE) && (l < get_line_length()));
2243 curdiv->output(new special_node(*m), 1, 0, 0, 0);
2247 node *environment::make_html_tag(const char *nm, int i)
2249 if (is_html) {
2251 * need to emit tag for post-grohtml
2252 * but we check to see whether we can emit specials
2254 if (curdiv == topdiv && topdiv->before_first_page)
2255 topdiv->begin_page();
2256 macro *m = new macro;
2257 m->append_str("html-tag:");
2258 for (const char *p = nm; *p; p++)
2259 if (!invalid_input_char((unsigned char)*p))
2260 m->append(*p);
2261 m->append(' ');
2262 m->append_int(i);
2263 return new special_node(*m);
2265 return 0;
2268 node *environment::make_html_tag(const char *nm)
2270 if (is_html) {
2272 * need to emit tag for post-grohtml
2273 * but we check to see whether we can emit specials
2275 if (curdiv == topdiv && topdiv->before_first_page)
2276 topdiv->begin_page();
2277 macro *m = new macro;
2278 m->append_str("html-tag:");
2279 for (const char *p = nm; *p; p++)
2280 if (!invalid_input_char((unsigned char)*p))
2281 m->append(*p);
2282 return new special_node(*m);
2284 return 0;
2287 void environment::do_break(int do_spread)
2289 if (curdiv == topdiv && topdiv->before_first_page) {
2290 topdiv->begin_page();
2291 return;
2293 if (current_tab)
2294 wrap_up_tab();
2295 if (line) {
2296 // this is so that hyphenation works
2297 line = new space_node(H0, get_fill_color(), line);
2298 space_total++;
2299 possibly_break_line(0, do_spread);
2301 while (line != 0 && line->discardable()) {
2302 width_total -= line->width();
2303 space_total -= line->nspaces();
2304 node *tem = line;
2305 line = line->next;
2306 delete tem;
2308 discarding = 0;
2309 input_line_start = H0;
2310 if (line != 0) {
2311 if (fill) {
2312 switch (adjust_mode) {
2313 case ADJUST_CENTER:
2314 saved_indent += (target_text_length - width_total)/2;
2315 break;
2316 case ADJUST_RIGHT:
2317 saved_indent += target_text_length - width_total;
2318 break;
2321 node *tem = line;
2322 line = 0;
2323 output_line(tem, width_total);
2324 hyphen_line_count = 0;
2326 prev_line_interrupted = 0;
2327 #ifdef WIDOW_CONTROL
2328 mark_last_line();
2329 output_pending_lines();
2330 #endif /* WIDOW_CONTROL */
2333 int environment::is_empty()
2335 return !current_tab && line == 0 && pending_lines == 0;
2338 void do_break_request(int spread)
2340 while (!tok.newline() && !tok.eof())
2341 tok.next();
2342 if (break_flag) {
2343 curenv->do_break(spread);
2344 curenv->add_html_tag(0, ".br");
2346 tok.next();
2349 void break_request()
2351 do_break_request(0);
2354 void break_spread_request()
2356 do_break_request(1);
2359 void title()
2361 if (curdiv == topdiv && topdiv->before_first_page) {
2362 handle_initial_title();
2363 return;
2365 node *part[3];
2366 hunits part_width[3];
2367 part[0] = part[1] = part[2] = 0;
2368 environment env(curenv);
2369 environment *oldenv = curenv;
2370 curenv = &env;
2371 read_title_parts(part, part_width);
2372 curenv = oldenv;
2373 curenv->size = env.size;
2374 curenv->prev_size = env.prev_size;
2375 curenv->requested_size = env.requested_size;
2376 curenv->prev_requested_size = env.prev_requested_size;
2377 curenv->char_height = env.char_height;
2378 curenv->char_slant = env.char_slant;
2379 curenv->fontno = env.fontno;
2380 curenv->prev_fontno = env.prev_fontno;
2381 curenv->glyph_color = env.glyph_color;
2382 curenv->prev_glyph_color = env.prev_glyph_color;
2383 curenv->fill_color = env.fill_color;
2384 curenv->prev_fill_color = env.prev_fill_color;
2385 node *n = 0;
2386 node *p = part[2];
2387 while (p != 0) {
2388 node *tem = p;
2389 p = p->next;
2390 tem->next = n;
2391 n = tem;
2393 hunits length_title(curenv->title_length);
2394 hunits f = length_title - part_width[1];
2395 hunits f2 = f/2;
2396 n = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(), n);
2397 p = part[1];
2398 while (p != 0) {
2399 node *tem = p;
2400 p = p->next;
2401 tem->next = n;
2402 n = tem;
2404 n = new hmotion_node(f - f2 - part_width[0], curenv->get_fill_color(), n);
2405 p = part[0];
2406 while (p != 0) {
2407 node *tem = p;
2408 p = p->next;
2409 tem->next = n;
2410 n = tem;
2412 curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
2413 curenv->total_post_vertical_spacing(), length_title);
2414 curenv->hyphen_line_count = 0;
2415 tok.next();
2418 void adjust()
2420 curenv->adjust_mode |= 1;
2421 if (has_arg()) {
2422 switch (tok.ch()) {
2423 case 'l':
2424 curenv->adjust_mode = ADJUST_LEFT;
2425 break;
2426 case 'r':
2427 curenv->adjust_mode = ADJUST_RIGHT;
2428 break;
2429 case 'c':
2430 curenv->adjust_mode = ADJUST_CENTER;
2431 break;
2432 case 'b':
2433 case 'n':
2434 curenv->adjust_mode = ADJUST_BOTH;
2435 break;
2436 default:
2437 int n;
2438 if (get_integer(&n)) {
2439 if (n < 0)
2440 warning(WARN_RANGE, "negative adjustment mode");
2441 else if (n > 5) {
2442 curenv->adjust_mode = 5;
2443 warning(WARN_RANGE, "adjustment mode `%1' out of range", n);
2445 else
2446 curenv->adjust_mode = n;
2450 skip_line();
2453 void no_adjust()
2455 curenv->adjust_mode &= ~1;
2456 skip_line();
2459 void do_input_trap(int continued)
2461 curenv->input_trap_count = 0;
2462 if (continued)
2463 curenv->continued_input_trap = 1;
2464 int n;
2465 if (has_arg() && get_integer(&n)) {
2466 if (n <= 0)
2467 warning(WARN_RANGE,
2468 "number of lines for input trap must be greater than zero");
2469 else {
2470 symbol s = get_name(1);
2471 if (!s.is_null()) {
2472 curenv->input_trap_count = n;
2473 curenv->input_trap = s;
2477 skip_line();
2480 void input_trap()
2482 do_input_trap(0);
2485 void input_trap_continued()
2487 do_input_trap(1);
2490 /* tabs */
2492 // must not be R or C or L or a legitimate part of a number expression
2493 const char TAB_REPEAT_CHAR = 'T';
2495 struct tab {
2496 tab *next;
2497 hunits pos;
2498 tab_type type;
2499 tab(hunits, tab_type);
2500 enum { BLOCK = 1024 };
2501 static tab *free_list;
2502 void *operator new(size_t);
2503 void operator delete(void *);
2506 tab *tab::free_list = 0;
2508 void *tab::operator new(size_t n)
2510 assert(n == sizeof(tab));
2511 if (!free_list) {
2512 free_list = (tab *)new char[sizeof(tab)*BLOCK];
2513 for (int i = 0; i < BLOCK - 1; i++)
2514 free_list[i].next = free_list + i + 1;
2515 free_list[BLOCK-1].next = 0;
2517 tab *p = free_list;
2518 free_list = (tab *)(free_list->next);
2519 p->next = 0;
2520 return p;
2523 #ifdef __GNUG__
2524 /* cfront can't cope with this. */
2525 inline
2526 #endif
2527 void tab::operator delete(void *p)
2529 if (p) {
2530 ((tab *)p)->next = free_list;
2531 free_list = (tab *)p;
2535 tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2539 tab_stops::tab_stops(hunits distance, tab_type type)
2540 : initial_list(0)
2542 repeated_list = new tab(distance, type);
2545 tab_stops::~tab_stops()
2547 clear();
2550 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
2552 hunits nextpos;
2554 return distance_to_next_tab(curpos, distance, &nextpos);
2557 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance,
2558 hunits *nextpos)
2560 hunits lastpos = 0;
2561 tab *tem;
2562 for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
2563 lastpos = tem->pos;
2564 if (tem) {
2565 *distance = tem->pos - curpos;
2566 *nextpos = tem->pos;
2567 return tem->type;
2569 if (repeated_list == 0)
2570 return TAB_NONE;
2571 hunits base = lastpos;
2572 for (;;) {
2573 for (tem = repeated_list; tem && tem->pos + base <= curpos; tem = tem->next)
2574 lastpos = tem->pos;
2575 if (tem) {
2576 *distance = tem->pos + base - curpos;
2577 *nextpos = tem->pos + base;
2578 return tem->type;
2580 assert(lastpos > 0);
2581 base += lastpos;
2583 return TAB_NONE;
2586 const char *tab_stops::to_string()
2588 static char *buf = 0;
2589 static int buf_size = 0;
2590 // figure out a maximum on the amount of space we can need
2591 int count = 0;
2592 tab *p;
2593 for (p = initial_list; p; p = p->next)
2594 ++count;
2595 for (p = repeated_list; p; p = p->next)
2596 ++count;
2597 // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
2598 int need = count*12 + 3;
2599 if (buf == 0 || need > buf_size) {
2600 if (buf)
2601 a_delete buf;
2602 buf_size = need;
2603 buf = new char[buf_size];
2605 char *ptr = buf;
2606 for (p = initial_list; p; p = p->next) {
2607 strcpy(ptr, i_to_a(p->pos.to_units()));
2608 ptr = strchr(ptr, '\0');
2609 *ptr++ = 'u';
2610 *ptr = '\0';
2611 switch (p->type) {
2612 case TAB_LEFT:
2613 break;
2614 case TAB_RIGHT:
2615 *ptr++ = 'R';
2616 break;
2617 case TAB_CENTER:
2618 *ptr++ = 'C';
2619 break;
2620 case TAB_NONE:
2621 default:
2622 assert(0);
2625 if (repeated_list)
2626 *ptr++ = TAB_REPEAT_CHAR;
2627 for (p = repeated_list; p; p = p->next) {
2628 strcpy(ptr, i_to_a(p->pos.to_units()));
2629 ptr = strchr(ptr, '\0');
2630 *ptr++ = 'u';
2631 *ptr = '\0';
2632 switch (p->type) {
2633 case TAB_LEFT:
2634 break;
2635 case TAB_RIGHT:
2636 *ptr++ = 'R';
2637 break;
2638 case TAB_CENTER:
2639 *ptr++ = 'C';
2640 break;
2641 case TAB_NONE:
2642 default:
2643 assert(0);
2646 *ptr++ = '\0';
2647 return buf;
2650 tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2654 tab_stops::tab_stops(const tab_stops &ts)
2655 : initial_list(0), repeated_list(0)
2657 tab **p = &initial_list;
2658 tab *t = ts.initial_list;
2659 while (t) {
2660 *p = new tab(t->pos, t->type);
2661 t = t->next;
2662 p = &(*p)->next;
2664 p = &repeated_list;
2665 t = ts.repeated_list;
2666 while (t) {
2667 *p = new tab(t->pos, t->type);
2668 t = t->next;
2669 p = &(*p)->next;
2673 void tab_stops::clear()
2675 while (initial_list) {
2676 tab *tem = initial_list;
2677 initial_list = initial_list->next;
2678 delete tem;
2680 while (repeated_list) {
2681 tab *tem = repeated_list;
2682 repeated_list = repeated_list->next;
2683 delete tem;
2687 void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
2689 tab **p;
2690 for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
2692 *p = new tab(pos, type);
2696 void tab_stops::operator=(const tab_stops &ts)
2698 clear();
2699 tab **p = &initial_list;
2700 tab *t = ts.initial_list;
2701 while (t) {
2702 *p = new tab(t->pos, t->type);
2703 t = t->next;
2704 p = &(*p)->next;
2706 p = &repeated_list;
2707 t = ts.repeated_list;
2708 while (t) {
2709 *p = new tab(t->pos, t->type);
2710 t = t->next;
2711 p = &(*p)->next;
2715 void set_tabs()
2717 hunits pos;
2718 hunits prev_pos = 0;
2719 int first = 1;
2720 int repeated = 0;
2721 tab_stops tabs;
2722 while (has_arg()) {
2723 if (tok.ch() == TAB_REPEAT_CHAR) {
2724 tok.next();
2725 repeated = 1;
2726 prev_pos = 0;
2728 if (!get_hunits(&pos, 'm', prev_pos))
2729 break;
2730 tab_type type = TAB_LEFT;
2731 if (tok.ch() == 'C') {
2732 tok.next();
2733 type = TAB_CENTER;
2735 else if (tok.ch() == 'R') {
2736 tok.next();
2737 type = TAB_RIGHT;
2739 else if (tok.ch() == 'L') {
2740 tok.next();
2742 if (pos <= prev_pos && !first)
2743 warning(WARN_RANGE,
2744 "positions of tab stops must be strictly increasing");
2745 else {
2746 tabs.add_tab(pos, type, repeated);
2747 prev_pos = pos;
2748 first = 0;
2751 curenv->tabs = tabs;
2752 curenv->add_html_tag_tabs(1);
2753 skip_line();
2756 const char *environment::get_tabs()
2758 return tabs.to_string();
2761 tab_type environment::distance_to_next_tab(hunits *distance)
2763 return line_tabs
2764 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
2765 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
2768 tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
2770 return line_tabs
2771 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
2772 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
2773 leftpos);
2776 void field_characters()
2778 field_delimiter_char = get_optional_char();
2779 if (field_delimiter_char)
2780 padding_indicator_char = get_optional_char();
2781 else
2782 padding_indicator_char = 0;
2783 skip_line();
2786 void line_tabs_request()
2788 int n;
2789 if (has_arg() && get_integer(&n))
2790 curenv->line_tabs = n != 0;
2791 else
2792 curenv->line_tabs = 1;
2793 skip_line();
2796 int environment::get_line_tabs()
2798 return line_tabs;
2801 void environment::wrap_up_tab()
2803 if (!current_tab)
2804 return;
2805 if (line == 0)
2806 start_line();
2807 hunits tab_amount;
2808 switch (current_tab) {
2809 case TAB_RIGHT:
2810 tab_amount = tab_distance - tab_width;
2811 line = make_tab_node(tab_amount, line);
2812 break;
2813 case TAB_CENTER:
2814 tab_amount = tab_distance - tab_width/2;
2815 line = make_tab_node(tab_amount, line);
2816 break;
2817 case TAB_NONE:
2818 case TAB_LEFT:
2819 default:
2820 assert(0);
2822 width_total += tab_amount;
2823 width_total += tab_width;
2824 if (current_field) {
2825 if (tab_precedes_field) {
2826 pre_field_width += tab_amount;
2827 tab_precedes_field = 0;
2829 field_distance -= tab_amount;
2830 field_spaces += tab_field_spaces;
2832 if (tab_contents != 0) {
2833 node *tem;
2834 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2836 tem->next = line;
2837 line = tab_contents;
2839 tab_field_spaces = 0;
2840 tab_contents = 0;
2841 tab_width = H0;
2842 tab_distance = H0;
2843 current_tab = TAB_NONE;
2846 node *environment::make_tab_node(hunits d, node *next)
2848 if (leader_node != 0 && d < 0) {
2849 error("motion generated by leader cannot be negative");
2850 delete leader_node;
2851 leader_node = 0;
2853 if (!leader_node)
2854 return new hmotion_node(d, 1, 0, get_fill_color(), next);
2855 node *n = new hline_node(d, leader_node, next);
2856 leader_node = 0;
2857 return n;
2860 void environment::handle_tab(int is_leader)
2862 hunits d;
2863 hunits absolute;
2864 if (current_tab)
2865 wrap_up_tab();
2866 charinfo *ci = is_leader ? leader_char : tab_char;
2867 delete leader_node;
2868 leader_node = ci ? make_char_node(ci) : 0;
2869 tab_type t = distance_to_next_tab(&d, &absolute);
2870 switch (t) {
2871 case TAB_NONE:
2872 return;
2873 case TAB_LEFT:
2874 add_node(make_tab_node(d));
2875 add_node(make_html_tag("tab L", absolute.to_units()));
2876 return;
2877 case TAB_RIGHT:
2878 add_node(make_html_tag("tab R", absolute.to_units()));
2879 break;
2880 case TAB_CENTER:
2881 add_node(make_html_tag("tab C", absolute.to_units()));
2882 break;
2883 default:
2884 assert(0);
2886 tab_width = 0;
2887 tab_distance = d;
2888 tab_contents = 0;
2889 current_tab = t;
2890 tab_field_spaces = 0;
2893 void environment::start_field()
2895 assert(!current_field);
2896 hunits d;
2897 if (distance_to_next_tab(&d) != TAB_NONE) {
2898 pre_field_width = get_text_length();
2899 field_distance = d;
2900 current_field = 1;
2901 field_spaces = 0;
2902 tab_field_spaces = 0;
2903 for (node *p = line; p; p = p->next)
2904 if (p->nspaces()) {
2905 p->freeze_space();
2906 space_total--;
2908 tab_precedes_field = current_tab != TAB_NONE;
2910 else
2911 error("zero field width");
2914 void environment::wrap_up_field()
2916 if (!current_tab && field_spaces == 0)
2917 add_padding();
2918 hunits padding = field_distance - (get_text_length() - pre_field_width);
2919 if (current_tab && tab_field_spaces != 0) {
2920 hunits tab_padding = scale(padding,
2921 tab_field_spaces,
2922 field_spaces + tab_field_spaces);
2923 padding -= tab_padding;
2924 distribute_space(tab_contents, tab_field_spaces, tab_padding, 1);
2925 tab_field_spaces = 0;
2926 tab_width += tab_padding;
2928 if (field_spaces != 0) {
2929 distribute_space(line, field_spaces, padding, 1);
2930 width_total += padding;
2931 if (current_tab) {
2932 // the start of the tab has been moved to the right by padding, so
2933 tab_distance -= padding;
2934 if (tab_distance <= H0) {
2935 // use the next tab stop instead
2936 current_tab = tabs.distance_to_next_tab(get_input_line_position()
2937 - tab_width,
2938 &tab_distance);
2939 if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
2940 width_total += tab_width;
2941 if (current_tab == TAB_LEFT) {
2942 line = make_tab_node(tab_distance, line);
2943 width_total += tab_distance;
2944 current_tab = TAB_NONE;
2946 if (tab_contents != 0) {
2947 node *tem;
2948 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2950 tem->next = line;
2951 line = tab_contents;
2952 tab_contents = 0;
2954 tab_width = H0;
2955 tab_distance = H0;
2960 current_field = 0;
2963 void environment::add_padding()
2965 if (current_tab) {
2966 tab_contents = new space_node(H0, get_fill_color(), tab_contents);
2967 tab_field_spaces++;
2969 else {
2970 if (line == 0)
2971 start_line();
2972 line = new space_node(H0, get_fill_color(), line);
2973 field_spaces++;
2977 typedef int (environment::*INT_FUNCP)();
2978 typedef vunits (environment::*VUNITS_FUNCP)();
2979 typedef hunits (environment::*HUNITS_FUNCP)();
2980 typedef const char *(environment::*STRING_FUNCP)();
2982 class int_env_reg : public reg {
2983 INT_FUNCP func;
2984 public:
2985 int_env_reg(INT_FUNCP);
2986 const char *get_string();
2987 int get_value(units *val);
2990 class vunits_env_reg : public reg {
2991 VUNITS_FUNCP func;
2992 public:
2993 vunits_env_reg(VUNITS_FUNCP f);
2994 const char *get_string();
2995 int get_value(units *val);
2999 class hunits_env_reg : public reg {
3000 HUNITS_FUNCP func;
3001 public:
3002 hunits_env_reg(HUNITS_FUNCP f);
3003 const char *get_string();
3004 int get_value(units *val);
3007 class string_env_reg : public reg {
3008 STRING_FUNCP func;
3009 public:
3010 string_env_reg(STRING_FUNCP);
3011 const char *get_string();
3014 int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
3018 int int_env_reg::get_value(units *val)
3020 *val = (curenv->*func)();
3021 return 1;
3024 const char *int_env_reg::get_string()
3026 return i_to_a((curenv->*func)());
3029 vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
3033 int vunits_env_reg::get_value(units *val)
3035 *val = (curenv->*func)().to_units();
3036 return 1;
3039 const char *vunits_env_reg::get_string()
3041 return i_to_a((curenv->*func)().to_units());
3044 hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
3048 int hunits_env_reg::get_value(units *val)
3050 *val = (curenv->*func)().to_units();
3051 return 1;
3054 const char *hunits_env_reg::get_string()
3056 return i_to_a((curenv->*func)().to_units());
3059 string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
3063 const char *string_env_reg::get_string()
3065 return (curenv->*func)();
3068 class horizontal_place_reg : public general_reg {
3069 public:
3070 horizontal_place_reg();
3071 int get_value(units *);
3072 void set_value(units);
3075 horizontal_place_reg::horizontal_place_reg()
3079 int horizontal_place_reg::get_value(units *res)
3081 *res = curenv->get_input_line_position().to_units();
3082 return 1;
3085 void horizontal_place_reg::set_value(units n)
3087 curenv->set_input_line_position(hunits(n));
3090 const char *environment::get_font_family_string()
3092 return family->nm.contents();
3095 const char *environment::get_glyph_color_string()
3097 return glyph_color->nm.contents();
3100 const char *environment::get_fill_color_string()
3102 return fill_color->nm.contents();
3105 const char *environment::get_font_name_string()
3107 symbol f = get_font_name(fontno, this);
3108 return f.contents();
3111 const char *environment::get_name_string()
3113 return name.contents();
3116 // Convert a quantity in scaled points to ascii decimal fraction.
3118 const char *sptoa(int sp)
3120 assert(sp > 0);
3121 assert(sizescale > 0);
3122 if (sizescale == 1)
3123 return i_to_a(sp);
3124 if (sp % sizescale == 0)
3125 return i_to_a(sp/sizescale);
3126 // See if 1/sizescale is exactly representable as a decimal fraction,
3127 // ie its only prime factors are 2 and 5.
3128 int n = sizescale;
3129 int power2 = 0;
3130 while ((n & 1) == 0) {
3131 n >>= 1;
3132 power2++;
3134 int power5 = 0;
3135 while ((n % 5) == 0) {
3136 n /= 5;
3137 power5++;
3139 if (n == 1) {
3140 int decimal_point = power5 > power2 ? power5 : power2;
3141 if (decimal_point <= 10) {
3142 int factor = 1;
3143 int t;
3144 for (t = decimal_point - power2; --t >= 0;)
3145 factor *= 2;
3146 for (t = decimal_point - power5; --t >= 0;)
3147 factor *= 5;
3148 if (factor == 1 || sp <= INT_MAX/factor)
3149 return if_to_a(sp*factor, decimal_point);
3152 double s = double(sp)/double(sizescale);
3153 double factor = 10.0;
3154 double val = s;
3155 int decimal_point = 0;
3156 do {
3157 double v = ceil(s*factor);
3158 if (v > INT_MAX)
3159 break;
3160 val = v;
3161 factor *= 10.0;
3162 } while (++decimal_point < 10);
3163 return if_to_a(int(val), decimal_point);
3166 const char *environment::get_point_size_string()
3168 return sptoa(curenv->get_point_size());
3171 const char *environment::get_requested_point_size_string()
3173 return sptoa(curenv->get_requested_point_size());
3176 #define init_int_env_reg(name, func) \
3177 number_reg_dictionary.define(name, new int_env_reg(&environment::func))
3179 #define init_vunits_env_reg(name, func) \
3180 number_reg_dictionary.define(name, new vunits_env_reg(&environment::func))
3182 #define init_hunits_env_reg(name, func) \
3183 number_reg_dictionary.define(name, new hunits_env_reg(&environment::func))
3185 #define init_string_env_reg(name, func) \
3186 number_reg_dictionary.define(name, new string_env_reg(&environment::func))
3188 void init_env_requests()
3190 init_request("ad", adjust);
3191 init_request("br", break_request);
3192 init_request("brp", break_spread_request);
3193 init_request("c2", no_break_control_char);
3194 init_request("cc", control_char);
3195 init_request("ce", center);
3196 init_request("cu", continuous_underline);
3197 init_request("ev", environment_switch);
3198 init_request("evc", environment_copy);
3199 init_request("fam", family_change);
3200 init_request("fc", field_characters);
3201 init_request("fi", fill);
3202 init_request("ft", font_change);
3203 init_request("hc", hyphen_char);
3204 init_request("hlm", hyphen_line_max_request);
3205 init_request("hy", hyphenate_request);
3206 init_request("hym", hyphenation_margin_request);
3207 init_request("hys", hyphenation_space_request);
3208 init_request("in", indent);
3209 init_request("it", input_trap);
3210 init_request("itc", input_trap_continued);
3211 init_request("lc", leader_character);
3212 init_request("linetabs", line_tabs_request);
3213 init_request("ll", line_length);
3214 init_request("ls", line_spacing);
3215 init_request("lt", title_length);
3216 init_request("mc", margin_character);
3217 init_request("na", no_adjust);
3218 init_request("nf", no_fill);
3219 init_request("nh", no_hyphenate);
3220 init_request("nm", number_lines);
3221 init_request("nn", no_number);
3222 init_request("ps", point_size);
3223 init_request("pvs", post_vertical_spacing);
3224 init_request("rj", right_justify);
3225 init_request("sizes", override_sizes);
3226 init_request("ss", space_size);
3227 init_request("ta", set_tabs);
3228 init_request("ti", temporary_indent);
3229 init_request("tc", tab_character);
3230 init_request("tl", title);
3231 init_request("ul", underline);
3232 init_request("vs", vertical_spacing);
3233 #ifdef WIDOW_CONTROL
3234 init_request("wdc", widow_control_request);
3235 #endif /* WIDOW_CONTROL */
3236 init_int_env_reg(".b", get_bold);
3237 init_vunits_env_reg(".cdp", get_prev_char_depth);
3238 init_int_env_reg(".ce", get_center_lines);
3239 init_vunits_env_reg(".cht", get_prev_char_height);
3240 init_hunits_env_reg(".csk", get_prev_char_skew);
3241 init_string_env_reg(".ev", get_name_string);
3242 init_int_env_reg(".f", get_font);
3243 init_string_env_reg(".fam", get_font_family_string);
3244 init_string_env_reg(".fn", get_font_name_string);
3245 init_int_env_reg(".height", get_char_height);
3246 init_int_env_reg(".hlc", get_hyphen_line_count);
3247 init_int_env_reg(".hlm", get_hyphen_line_max);
3248 init_int_env_reg(".hy", get_hyphenation_flags);
3249 init_hunits_env_reg(".hym", get_hyphenation_margin);
3250 init_hunits_env_reg(".hys", get_hyphenation_space);
3251 init_hunits_env_reg(".i", get_indent);
3252 init_hunits_env_reg(".in", get_saved_indent);
3253 init_int_env_reg(".int", get_prev_line_interrupted);
3254 init_int_env_reg(".linetabs", get_line_tabs);
3255 init_hunits_env_reg(".lt", get_title_length);
3256 init_int_env_reg(".j", get_adjust_mode);
3257 init_hunits_env_reg(".k", get_text_length);
3258 init_int_env_reg(".L", get_line_spacing);
3259 init_hunits_env_reg(".l", get_line_length);
3260 init_hunits_env_reg(".ll", get_saved_line_length);
3261 init_string_env_reg(".M", get_fill_color_string);
3262 init_string_env_reg(".m", get_glyph_color_string);
3263 init_hunits_env_reg(".n", get_prev_text_length);
3264 init_int_env_reg(".ps", get_point_size);
3265 init_int_env_reg(".psr", get_requested_point_size);
3266 init_vunits_env_reg(".pvs", get_post_vertical_spacing);
3267 init_int_env_reg(".rj", get_right_justify_lines);
3268 init_string_env_reg(".s", get_point_size_string);
3269 init_int_env_reg(".slant", get_char_slant);
3270 init_int_env_reg(".ss", get_space_size);
3271 init_int_env_reg(".sss", get_sentence_space_size);
3272 init_string_env_reg(".sr", get_requested_point_size_string);
3273 init_string_env_reg(".tabs", get_tabs);
3274 init_int_env_reg(".u", get_fill);
3275 init_vunits_env_reg(".v", get_vertical_spacing);
3276 init_hunits_env_reg(".w", get_prev_char_width);
3277 number_reg_dictionary.define("ct", new variable_reg(&ct_reg_contents));
3278 number_reg_dictionary.define("hp", new horizontal_place_reg);
3279 number_reg_dictionary.define("ln", new variable_reg(&next_line_number));
3280 number_reg_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
3281 number_reg_dictionary.define("rst", new variable_reg(&rst_reg_contents));
3282 number_reg_dictionary.define("sb", new variable_reg(&sb_reg_contents));
3283 number_reg_dictionary.define("skw", new variable_reg(&skw_reg_contents));
3284 number_reg_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
3285 number_reg_dictionary.define("st", new variable_reg(&st_reg_contents));
3288 // Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
3290 struct trie_node;
3292 class trie {
3293 trie_node *tp;
3294 virtual void do_match(int len, void *val) = 0;
3295 virtual void do_delete(void *) = 0;
3296 void delete_trie_node(trie_node *);
3297 public:
3298 trie() : tp(0) {}
3299 virtual ~trie(); // virtual to shut up g++
3300 void insert(const char *, int, void *);
3301 // find calls do_match for each match it finds
3302 void find(const char *pat, int patlen);
3303 void clear();
3306 class hyphen_trie : private trie {
3307 int *h;
3308 void do_match(int i, void *v);
3309 void do_delete(void *v);
3310 void insert_pattern(const char *pat, int patlen, int *num);
3311 void insert_hyphenation(dictionary *ex, const char *pat, int patlen);
3312 int hpf_getc(FILE *f);
3313 public:
3314 hyphen_trie() {}
3315 ~hyphen_trie() {}
3316 void hyphenate(const char *word, int len, int *hyphens);
3317 void read_patterns_file(const char *name, int append, dictionary *ex);
3320 struct hyphenation_language {
3321 symbol name;
3322 dictionary exceptions;
3323 hyphen_trie patterns;
3324 hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
3325 ~hyphenation_language() { }
3328 dictionary language_dictionary(5);
3329 hyphenation_language *current_language = 0;
3331 static void set_hyphenation_language()
3333 symbol nm = get_name(1);
3334 if (!nm.is_null()) {
3335 current_language = (hyphenation_language *)language_dictionary.lookup(nm);
3336 if (!current_language) {
3337 current_language = new hyphenation_language(nm);
3338 (void)language_dictionary.lookup(nm, (void *)current_language);
3341 skip_line();
3344 const int WORD_MAX = 256; // we use unsigned char for offsets in
3345 // hyphenation exceptions
3347 static void hyphen_word()
3349 if (!current_language) {
3350 error("no current hyphenation language");
3351 skip_line();
3352 return;
3354 char buf[WORD_MAX + 1];
3355 unsigned char pos[WORD_MAX + 2];
3356 for (;;) {
3357 tok.skip();
3358 if (tok.newline() || tok.eof())
3359 break;
3360 int i = 0;
3361 int npos = 0;
3362 while (i < WORD_MAX && !tok.space() && !tok.newline() && !tok.eof()) {
3363 charinfo *ci = tok.get_char(1);
3364 if (ci == 0) {
3365 skip_line();
3366 return;
3368 tok.next();
3369 if (ci->get_ascii_code() == '-') {
3370 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3371 pos[npos++] = i;
3373 else {
3374 int c = ci->get_hyphenation_code();
3375 if (c == 0)
3376 break;
3377 buf[i++] = c;
3380 if (i > 0) {
3381 pos[npos] = 0;
3382 buf[i] = 0;
3383 unsigned char *tem = new unsigned char[npos + 1];
3384 memcpy(tem, pos, npos + 1);
3385 tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf),
3386 tem);
3387 if (tem)
3388 a_delete tem;
3391 skip_line();
3394 struct trie_node {
3395 char c;
3396 trie_node *down;
3397 trie_node *right;
3398 void *val;
3399 trie_node(char, trie_node *);
3402 trie_node::trie_node(char ch, trie_node *p)
3403 : c(ch), down(0), right(p), val(0)
3407 trie::~trie()
3409 clear();
3412 void trie::clear()
3414 delete_trie_node(tp);
3415 tp = 0;
3419 void trie::delete_trie_node(trie_node *p)
3421 if (p) {
3422 delete_trie_node(p->down);
3423 delete_trie_node(p->right);
3424 if (p->val)
3425 do_delete(p->val);
3426 delete p;
3430 void trie::insert(const char *pat, int patlen, void *val)
3432 trie_node **p = &tp;
3433 assert(patlen > 0 && pat != 0);
3434 for (;;) {
3435 while (*p != 0 && (*p)->c < pat[0])
3436 p = &((*p)->right);
3437 if (*p == 0 || (*p)->c != pat[0])
3438 *p = new trie_node(pat[0], *p);
3439 if (--patlen == 0) {
3440 (*p)->val = val;
3441 break;
3443 ++pat;
3444 p = &((*p)->down);
3448 void trie::find(const char *pat, int patlen)
3450 trie_node *p = tp;
3451 for (int i = 0; p != 0 && i < patlen; i++) {
3452 while (p != 0 && p->c < pat[i])
3453 p = p->right;
3454 if (p != 0 && p->c == pat[i]) {
3455 if (p->val != 0)
3456 do_match(i+1, p->val);
3457 p = p->down;
3459 else
3460 break;
3464 struct operation {
3465 operation *next;
3466 short distance;
3467 short num;
3468 operation(int, int, operation *);
3471 operation::operation(int i, int j, operation *op)
3472 : next(op), distance(j), num(i)
3476 void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
3478 operation *op = 0;
3479 for (int i = 0; i < patlen+1; i++)
3480 if (num[i] != 0)
3481 op = new operation(num[i], patlen - i, op);
3482 insert(pat, patlen, op);
3485 void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
3486 int patlen)
3488 char buf[WORD_MAX + 1];
3489 unsigned char pos[WORD_MAX + 2];
3490 int i = 0, j = 0;
3491 int npos = 0;
3492 while (j < patlen) {
3493 unsigned char c = pat[j++];
3494 if (c == '-') {
3495 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3496 pos[npos++] = i;
3498 else
3499 buf[i++] = hpf_code_table[c];
3501 if (i > 0) {
3502 pos[npos] = 0;
3503 buf[i] = 0;
3504 unsigned char *tem = new unsigned char[npos + 1];
3505 memcpy(tem, pos, npos + 1);
3506 tem = (unsigned char *)ex->lookup(symbol(buf), tem);
3507 if (tem)
3508 a_delete tem;
3512 void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
3514 int j;
3515 for (j = 0; j < len + 1; j++)
3516 hyphens[j] = 0;
3517 for (j = 0; j < len - 1; j++) {
3518 h = hyphens + j;
3519 find(word + j, len - j);
3523 inline int max(int m, int n)
3525 return m > n ? m : n;
3528 void hyphen_trie::do_match(int i, void *v)
3530 operation *op = (operation *)v;
3531 while (op != 0) {
3532 h[i - op->distance] = max(h[i - op->distance], op->num);
3533 op = op->next;
3537 void hyphen_trie::do_delete(void *v)
3539 operation *op = (operation *)v;
3540 while (op) {
3541 operation *tem = op;
3542 op = tem->next;
3543 delete tem;
3547 /* We use very simple rules to parse TeX's hyphenation patterns.
3549 . `%' starts a comment even if preceded by `\'.
3551 . No support for digraphs and like `\$'.
3553 . `^^xx' (`x' is 0-9 or a-f), and `^^x' (character code of `x' in the
3554 range 0-127) are recognized; other use of `^' causes an error.
3556 . No macro expansion.
3558 . We check for the expression `\patterns{...}' (possibly with
3559 whitespace before and after the braces). Everything between the
3560 braces is taken as hyphenation patterns. Consequently, `{' and `}'
3561 are not allowed in patterns.
3563 . Similarly, `\hyphenation{...}' gives a list of hyphenation
3564 exceptions.
3566 . `\endinput' is recognized also.
3568 . For backwards compatibility, if `\patterns' is missing, the
3569 whole file is treated as a list of hyphenation patterns (only
3570 recognizing `%' as the start of a comment.
3574 int hyphen_trie::hpf_getc(FILE *f)
3576 int c = getc(f);
3577 int c1;
3578 int cc = 0;
3579 if (c != '^')
3580 return c;
3581 c = getc(f);
3582 if (c != '^')
3583 goto fail;
3584 c = getc(f);
3585 c1 = getc(f);
3586 if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
3587 && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
3588 if (c >= '0' && c <= '9')
3589 c -= '0';
3590 else
3591 c = c - 'a' + 10;
3592 if (c1 >= '0' && c1 <= '9')
3593 c1 -= '0';
3594 else
3595 c1 = c1 - 'a' + 10;
3596 cc = c * 16 + c1;
3598 else {
3599 ungetc(c1, f);
3600 if (c >= 0 && c <= 63)
3601 cc = c + 64;
3602 else if (c >= 64 && c <= 127)
3603 cc = c - 64;
3604 else
3605 goto fail;
3607 return cc;
3608 fail:
3609 error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
3610 return c;
3613 void hyphen_trie::read_patterns_file(const char *name, int append,
3614 dictionary *ex)
3616 if (!append)
3617 clear();
3618 char buf[WORD_MAX];
3619 for (int i = 0; i < WORD_MAX; i++)
3620 buf[i] = 0;
3621 int num[WORD_MAX+1];
3622 errno = 0;
3623 char *path = 0;
3624 FILE *fp = mac_path->open_file(name, &path);
3625 if (fp == 0) {
3626 error("can't find hyphenation patterns file `%1'", name);
3627 return;
3629 int c = hpf_getc(fp);
3630 int have_patterns = 0; // we've seen \patterns
3631 int final_pattern = 0; // 1 if we have a trailing closing brace
3632 int have_hyphenation = 0; // we've seen \hyphenation
3633 int final_hyphenation = 0; // 1 if we have a trailing closing brace
3634 int have_keyword = 0; // we've seen either \patterns or \hyphenation
3635 int traditional = 0; // don't handle \patterns
3636 for (;;) {
3637 for (;;) {
3638 if (c == '%') { // skip comments
3639 do {
3640 c = getc(fp);
3641 } while (c != EOF && c != '\n');
3643 if (c == EOF || !csspace(c))
3644 break;
3645 c = hpf_getc(fp);
3647 if (c == EOF) {
3648 if (have_keyword || traditional) // we are done
3649 break;
3650 else { // rescan file in `traditional' mode
3651 rewind(fp);
3652 traditional = 1;
3653 c = hpf_getc(fp);
3654 continue;
3657 int i = 0;
3658 num[0] = 0;
3659 if (!(c == '{' || c == '}')) { // skip braces at line start
3660 do { // scan patterns
3661 if (csdigit(c))
3662 num[i] = c - '0';
3663 else {
3664 buf[i++] = c;
3665 num[i] = 0;
3667 c = hpf_getc(fp);
3668 } while (i < WORD_MAX && c != EOF && !csspace(c)
3669 && c != '%' && c != '{' && c != '}');
3671 if (!traditional) {
3672 if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
3673 while (csspace(c))
3674 c = hpf_getc(fp);
3675 if (c == '{') {
3676 if (have_patterns || have_hyphenation)
3677 error("\\patterns not allowed inside of %1 group",
3678 have_patterns ? "\\patterns" : "\\hyphenation");
3679 else {
3680 have_patterns = 1;
3681 have_keyword = 1;
3683 c = hpf_getc(fp);
3684 continue;
3687 else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
3688 while (csspace(c))
3689 c = hpf_getc(fp);
3690 if (c == '{') {
3691 if (have_patterns || have_hyphenation)
3692 error("\\hyphenation not allowed inside of %1 group",
3693 have_patterns ? "\\patterns" : "\\hyphenation");
3694 else {
3695 have_hyphenation = 1;
3696 have_keyword = 1;
3698 c = hpf_getc(fp);
3699 continue;
3702 else if (strstr(buf, "\\endinput")) {
3703 if (have_patterns || have_hyphenation)
3704 error("found \\endinput inside of %1 group",
3705 have_patterns ? "\\patterns" : "\\hyphenation");
3706 break;
3708 else if (c == '}') {
3709 if (have_patterns) {
3710 have_patterns = 0;
3711 if (i > 0)
3712 final_pattern = 1;
3714 else if (have_hyphenation) {
3715 have_hyphenation = 0;
3716 if (i > 0)
3717 final_hyphenation = 1;
3719 c = hpf_getc(fp);
3721 else if (c == '{') {
3722 if (have_patterns || have_hyphenation)
3723 error("`{' not allowed within %1 group",
3724 have_patterns ? "\\patterns" : "\\hyphenation");
3725 c = hpf_getc(fp); // skipped if not starting \patterns
3726 // or \hyphenation
3729 else {
3730 if (c == '{' || c == '}')
3731 c = hpf_getc(fp);
3733 if (i > 0) {
3734 if (have_patterns || final_pattern || traditional) {
3735 for (int j = 0; j < i; j++)
3736 buf[j] = hpf_code_table[(unsigned char)buf[j]];
3737 insert_pattern(buf, i, num);
3738 final_pattern = 0;
3740 else if (have_hyphenation || final_hyphenation) {
3741 insert_hyphenation(ex, buf, i);
3742 final_hyphenation = 0;
3746 fclose(fp);
3747 a_delete path;
3748 return;
3751 void hyphenate(hyphen_list *h, unsigned flags)
3753 if (!current_language)
3754 return;
3755 while (h) {
3756 while (h && h->hyphenation_code == 0)
3757 h = h->next;
3758 int len = 0;
3759 char hbuf[WORD_MAX+2];
3760 char *buf = hbuf + 1;
3761 hyphen_list *tem;
3762 for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
3763 if (tem->hyphenation_code != 0)
3764 buf[len++] = tem->hyphenation_code;
3765 else
3766 break;
3768 hyphen_list *nexth = tem;
3769 if (len > 2) {
3770 buf[len] = 0;
3771 unsigned char *pos
3772 = (unsigned char *)current_language->exceptions.lookup(buf);
3773 if (pos != 0) {
3774 int j = 0;
3775 int i = 1;
3776 for (tem = h; tem != 0; tem = tem->next, i++)
3777 if (pos[j] == i) {
3778 tem->hyphen = 1;
3779 j++;
3782 else {
3783 hbuf[0] = hbuf[len+1] = '.';
3784 int num[WORD_MAX+3];
3785 current_language->patterns.hyphenate(hbuf, len+2, num);
3786 int i;
3787 num[2] = 0;
3788 if (flags & 8)
3789 num[3] = 0;
3790 if (flags & 4)
3791 --len;
3792 for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
3793 if (num[i] & 1)
3794 tem->hyphen = 1;
3797 h = nexth;
3801 static void do_hyphenation_patterns_file(int append)
3803 symbol name = get_long_name(1);
3804 if (!name.is_null()) {
3805 if (!current_language)
3806 error("no current hyphenation language");
3807 else
3808 current_language->patterns.read_patterns_file(
3809 name.contents(), append,
3810 &current_language->exceptions);
3812 skip_line();
3815 static void hyphenation_patterns_file()
3817 do_hyphenation_patterns_file(0);
3820 static void hyphenation_patterns_file_append()
3822 do_hyphenation_patterns_file(1);
3825 class hyphenation_language_reg : public reg {
3826 public:
3827 const char *get_string();
3830 const char *hyphenation_language_reg::get_string()
3832 return current_language ? current_language->name.contents() : "";
3835 void init_hyphen_requests()
3837 init_request("hw", hyphen_word);
3838 init_request("hla", set_hyphenation_language);
3839 init_request("hpf", hyphenation_patterns_file);
3840 init_request("hpfa", hyphenation_patterns_file_append);
3841 number_reg_dictionary.define(".hla", new hyphenation_language_reg);