2 /* Copyright (C) 1989-2018 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.com)
5 This file is part of groff.
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
21 #include "dictionary.h"
23 #include "stringclass.h"
33 #include "macropath.h"
37 symbol default_family("T");
39 enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 };
42 // Not all combinations are valid; see hyphenate_request() below.
45 HYPHEN_NOT_LAST_LINE = 2,
46 HYPHEN_NOT_LAST_CHARS = 4,
47 HYPHEN_NOT_FIRST_CHARS = 8,
48 HYPHEN_LAST_CHAR = 16,
49 HYPHEN_FIRST_CHAR = 32,
56 env_list(environment *e, env_list *p) : env(e), next(p) {}
60 const int NENVIRONMENTS = 10;
61 environment *env_table[NENVIRONMENTS];
62 dictionary env_dictionary(10);
64 static int next_line_number = 0;
65 extern int suppress_push;
66 extern statem *get_diversion_state();
68 charinfo *field_delimiter_char;
69 charinfo *padding_indicator_char;
71 int translate_space_to_dummy = 0;
73 class pending_output_line {
81 int last_line; // Is it the last line of the paragraph?
82 #endif /* WIDOW_CONTROL */
84 pending_output_line *next;
86 pending_output_line(node *, int, vunits, vunits, hunits, int,
87 pending_output_line * = 0);
88 ~pending_output_line();
92 friend void environment::mark_last_line();
93 friend void environment::output(node *, int, vunits, vunits, hunits, int);
94 #endif /* WIDOW_CONTROL */
97 pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv,
99 pending_output_line *p)
100 : nd(n), no_fill(nf), was_centered(ce), vs(v), post_vs(pv), width(w),
103 #endif /* WIDOW_CONTROL */
108 pending_output_line::~pending_output_line()
110 delete_node_list(nd);
113 int pending_output_line::output()
115 if (trap_sprung_flag)
118 if (next && next->last_line && !no_fill) {
119 curdiv->need(vs + post_vs + vunits(vresolution));
120 if (trap_sprung_flag) {
121 next->last_line = 0; // Try to avoid infinite loops.
126 curenv->construct_format_state(nd, was_centered, !no_fill);
127 curdiv->output(nd, no_fill, vs, post_vs, width);
132 void environment::output(node *nd, int no_fill_flag,
133 vunits vs, vunits post_vs,
134 hunits width, int was_centered)
137 while (pending_lines) {
138 if (widow_control && !pending_lines->no_fill && !pending_lines->next)
140 if (!pending_lines->output())
142 pending_output_line *tem = pending_lines;
143 pending_lines = pending_lines->next;
146 #else /* WIDOW_CONTROL */
147 output_pending_lines();
148 #endif /* WIDOW_CONTROL */
149 if (!trap_sprung_flag && !pending_lines
151 && (!widow_control || no_fill_flag)
152 #endif /* WIDOW_CONTROL */
154 curenv->construct_format_state(nd, was_centered, !no_fill_flag);
155 curdiv->output(nd, no_fill_flag, vs, post_vs, width);
157 pending_output_line **p;
158 for (p = &pending_lines; *p; p = &(*p)->next)
160 *p = new pending_output_line(nd, no_fill_flag, vs, post_vs, width,
165 // a line from .tl goes at the head of the queue
167 void environment::output_title(node *nd, int no_fill_flag,
168 vunits vs, vunits post_vs,
171 if (!trap_sprung_flag)
172 curdiv->output(nd, no_fill_flag, vs, post_vs, width);
174 pending_lines = new pending_output_line(nd, no_fill_flag, vs, post_vs,
175 width, 0, pending_lines);
178 void environment::output_pending_lines()
180 while (pending_lines && pending_lines->output()) {
181 pending_output_line *tem = pending_lines;
182 pending_lines = pending_lines->next;
189 void environment::mark_last_line()
191 if (!widow_control || !pending_lines)
193 pending_output_line *p;
194 for (p = pending_lines; p->next; p = p->next)
200 void widow_control_request()
203 if (has_arg() && get_integer(&n))
204 curenv->widow_control = n != 0;
206 curenv->widow_control = 1;
210 #endif /* WIDOW_CONTROL */
212 /* font_size functions */
214 size_range *font_size::size_table = 0;
215 int font_size::nranges = 0;
219 int compare_ranges(const void *p1, const void *p2)
221 return ((size_range *)p1)->min - ((size_range *)p2)->min;
226 void font_size::init_size_table(int *sizes)
229 while (sizes[nranges*2] != 0)
232 size_table = new size_range[nranges];
233 for (int i = 0; i < nranges; i++) {
234 size_table[i].min = sizes[i*2];
235 size_table[i].max = sizes[i*2 + 1];
237 qsort(size_table, nranges, sizeof(size_range), compare_ranges);
240 font_size::font_size(int sp)
242 for (int i = 0; i < nranges; i++) {
243 if (sp < size_table[i].min) {
244 if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
245 p = size_table[i - 1].max;
247 p = size_table[i].min;
250 if (sp <= size_table[i].max) {
255 p = size_table[nranges - 1].max;
258 int font_size::to_units()
260 return scale(p, units_per_inch, sizescale*72);
263 // we can't do this in a static constructor because various dictionaries
264 // have to get initialized first
266 void init_environments()
268 curenv = env_table[0] = new environment("0");
273 curenv->tab_char = get_optional_char();
277 void leader_character()
279 curenv->leader_char = get_optional_char();
283 void environment::add_char(charinfo *ci)
289 // don't allow fields in dummy environments
290 else if (ci == field_delimiter_char && !dummy) {
296 else if (current_field && ci == padding_indicator_char)
298 else if (current_tab) {
299 if (tab_contents == 0)
300 tab_contents = new line_start_node;
301 if (ci != hyphen_indicator_char)
302 tab_contents = tab_contents->add_char(ci, this, &tab_width, &s, &gc_np);
304 tab_contents = tab_contents->add_discretionary_hyphen();
310 fprintf(stderr, "current line is\n");
311 line->debug_node_list();
313 if (ci != hyphen_indicator_char)
314 line = line->add_char(ci, this, &width_total, &space_total, &gc_np);
316 line = line->add_discretionary_hyphen();
319 fprintf(stderr, "now after we have added character the line is\n");
320 line->debug_node_list();
322 if ((!suppress_push) && gc_np) {
323 if (gc_np && (gc_np->state == 0)) {
324 gc_np->state = construct_state(0);
325 gc_np->push_state = get_diversion_state();
327 else if (line && (line->state == 0)) {
328 line->state = construct_state(0);
329 line->push_state = get_diversion_state();
333 fprintf(stderr, "now we have possibly added the state the line is\n");
334 line->debug_node_list();
338 node *environment::make_char_node(charinfo *ci)
340 return make_node(ci, this);
343 void environment::add_node(node *n)
347 if (!suppress_push) {
348 if (n->is_special && n->state == NULL)
349 n->state = construct_state(0);
350 n->push_state = get_diversion_state();
353 if (current_tab || current_field)
358 else if (current_tab) {
359 n->next = tab_contents;
361 tab_width += n->width();
365 if (discarding && n->discardable()) {
366 // XXX possibly: input_line_start -= n->width();
372 width_total += n->width();
373 space_total += n->nspaces();
376 construct_new_line_state(line);
380 void environment::add_hyphen_indicator()
382 if (current_tab || interrupted || current_field
383 || hyphen_indicator_char != 0)
387 line = line->add_discretionary_hyphen();
390 int environment::get_hyphenation_flags()
392 return hyphenation_flags;
395 int environment::get_hyphen_line_max()
397 return hyphen_line_max;
400 int environment::get_hyphen_line_count()
402 return hyphen_line_count;
405 int environment::get_center_lines()
410 int environment::get_right_justify_lines()
412 return right_justify_lines;
415 void environment::add_italic_correction()
419 tab_contents = tab_contents->add_italic_correction(&tab_width);
422 line = line->add_italic_correction(&width_total);
425 void environment::space_newline()
427 assert(!current_tab && !current_field);
431 hunits sw = env_space_width(this);
432 hunits ssw = env_sentence_space_width(this);
433 if (!translate_space_to_dummy) {
435 if (node_list_ends_sentence(line) == 1)
438 width_list *w = new width_list(sw, ssw);
439 if (node_list_ends_sentence(line) == 1)
440 w->next = new width_list(sw, ssw);
441 if (line != 0 && line->merge_space(x, sw, ssw)) {
445 add_node(new word_space_node(x, get_fill_color(), w));
446 possibly_break_line(0, spread_flag);
450 void environment::space()
452 space(env_space_width(this), env_sentence_space_width(this));
455 void environment::space(hunits space_width, hunits sentence_space_width)
459 if (current_field && padding_indicator_char == 0) {
463 hunits x = translate_space_to_dummy ? H0 : space_width;
464 node *p = current_tab ? tab_contents : line;
465 hunits *tp = current_tab ? &tab_width : &width_total;
466 if (p && p->nspaces() == 1 && p->width() == x
467 && node_list_ends_sentence(p->next) == 1) {
468 hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
469 if (p->merge_space(xx, space_width, sentence_space_width)) {
474 if (p && p->merge_space(x, space_width, sentence_space_width)) {
478 add_node(new word_space_node(x,
480 new width_list(space_width,
481 sentence_space_width)));
482 possibly_break_line(0, spread_flag);
486 node *do_underline_special(int);
488 void environment::set_font(symbol nm)
492 if (nm == symbol("P") || nm.is_empty()) {
493 if (family->make_definite(prev_fontno) < 0)
496 fontno = prev_fontno;
500 prev_fontno = fontno;
501 int n = symbol_fontno(nm);
503 n = next_available_font_position();
504 if (!mount_font(n, nm))
507 if (family->make_definite(n) < 0)
511 if (underline_spaces && fontno != prev_fontno) {
512 if (fontno == get_underline_fontno())
513 add_node(do_underline_special(1));
514 if (prev_fontno == get_underline_fontno())
515 add_node(do_underline_special(0));
519 void environment::set_font(int n)
523 if (is_good_fontno(n)) {
524 prev_fontno = fontno;
528 warning(WARN_FONT, "bad font number");
531 void environment::set_family(symbol fam)
535 if (fam.is_null() || fam.is_empty()) {
536 if (prev_family->make_definite(fontno) < 0)
538 font_family *tem = family;
539 family = prev_family;
543 font_family *f = lookup_family(fam);
544 if (f->make_definite(fontno) < 0)
546 prev_family = family;
551 void environment::set_size(int n)
556 font_size temp = prev_size;
559 int temp2 = prev_requested_size;
560 prev_requested_size = requested_size;
561 requested_size = temp2;
566 prev_requested_size = requested_size;
571 void environment::set_char_height(int n)
575 if (n == requested_size || n <= 0)
581 void environment::set_char_slant(int n)
588 color *environment::get_prev_glyph_color()
590 return prev_glyph_color;
593 color *environment::get_glyph_color()
598 color *environment::get_prev_fill_color()
600 return prev_fill_color;
603 color *environment::get_fill_color()
608 void environment::set_glyph_color(color *c)
612 curenv->prev_glyph_color = curenv->glyph_color;
613 curenv->glyph_color = c;
616 void environment::set_fill_color(color *c)
620 curenv->prev_fill_color = curenv->fill_color;
621 curenv->fill_color = c;
624 environment::environment(symbol nm)
626 prev_line_length((units_per_inch*13)/2),
627 line_length((units_per_inch*13)/2),
628 prev_title_length((units_per_inch*13)/2),
629 title_length((units_per_inch*13)/2),
630 prev_size(sizescale*10),
632 requested_size(sizescale*10),
633 prev_requested_size(sizescale*10),
637 sentence_space_size(12),
638 adjust_mode(ADJUST_BOTH),
641 prev_line_interrupted(0),
643 right_justify_lines(0),
644 prev_vertical_spacing(points_to_units(12)),
645 vertical_spacing(points_to_units(12)),
646 prev_post_vertical_spacing(0),
647 post_vertical_spacing(0),
648 prev_line_spacing(1),
653 have_temporary_indent(0),
657 continued_input_trap(0),
664 current_tab(TAB_NONE),
667 leader_char(charset_table['.']),
671 margin_character_flags(0),
672 margin_character_node(0),
673 margin_character_distance(points_to_units(10)),
675 number_text_separation(1),
676 line_number_indent(0),
677 line_number_multiple(1),
679 hyphenation_flags(1),
680 hyphen_line_count(0),
682 hyphenation_space(H0),
683 hyphenation_margin(H0),
688 #endif /* WIDOW_CONTROL */
689 glyph_color(&default_color),
690 prev_glyph_color(&default_color),
691 fill_color(&default_color),
692 prev_fill_color(&default_color),
695 suppress_next_eol(0),
697 tabs(units_per_inch/2, TAB_LEFT),
700 no_break_control_char('\''),
701 hyphen_indicator_char(0)
703 prev_family = family = lookup_family(default_family);
704 prev_fontno = fontno = 1;
705 if (!is_good_fontno(1))
706 fatal("font number 1 not a valid font");
707 if (family->make_definite(1) < 0)
708 fatal("invalid default family '%1'", default_family.contents());
709 prev_fontno = fontno;
712 environment::environment(const environment *e)
714 prev_line_length(e->prev_line_length),
715 line_length(e->line_length),
716 prev_title_length(e->prev_title_length),
717 title_length(e->title_length),
718 prev_size(e->prev_size),
720 requested_size(e->requested_size),
721 prev_requested_size(e->prev_requested_size),
722 char_height(e->char_height),
723 char_slant(e->char_slant),
724 prev_fontno(e->prev_fontno),
726 prev_family(e->prev_family),
728 space_size(e->space_size),
729 sentence_space_size(e->sentence_space_size),
730 adjust_mode(e->adjust_mode),
733 prev_line_interrupted(0),
735 right_justify_lines(0),
736 prev_vertical_spacing(e->prev_vertical_spacing),
737 vertical_spacing(e->vertical_spacing),
738 prev_post_vertical_spacing(e->prev_post_vertical_spacing),
739 post_vertical_spacing(e->post_vertical_spacing),
740 prev_line_spacing(e->prev_line_spacing),
741 line_spacing(e->line_spacing),
742 prev_indent(e->prev_indent),
745 have_temporary_indent(0),
749 continued_input_trap(0),
751 prev_text_length(e->prev_text_length),
755 line_tabs(e->line_tabs),
756 current_tab(TAB_NONE),
758 tab_char(e->tab_char),
759 leader_char(e->leader_char),
763 margin_character_flags(e->margin_character_flags),
764 margin_character_node(e->margin_character_node),
765 margin_character_distance(e->margin_character_distance),
767 number_text_separation(e->number_text_separation),
768 line_number_indent(e->line_number_indent),
769 line_number_multiple(e->line_number_multiple),
770 no_number_count(e->no_number_count),
771 hyphenation_flags(e->hyphenation_flags),
772 hyphen_line_count(0),
773 hyphen_line_max(e->hyphen_line_max),
774 hyphenation_space(e->hyphenation_space),
775 hyphenation_margin(e->hyphenation_margin),
779 widow_control(e->widow_control),
780 #endif /* WIDOW_CONTROL */
781 glyph_color(e->glyph_color),
782 prev_glyph_color(e->prev_glyph_color),
783 fill_color(e->fill_color),
784 prev_fill_color(e->prev_fill_color),
785 seen_space(e->seen_space),
786 seen_eol(e->seen_eol),
787 suppress_next_eol(e->suppress_next_eol),
788 seen_break(e->seen_break),
790 name(e->name), // so that, e.g., '.if "\n[.ev]"0"' works
791 control_char(e->control_char),
792 no_break_control_char(e->no_break_control_char),
793 hyphen_indicator_char(e->hyphen_indicator_char)
797 void environment::copy(const environment *e)
799 prev_line_length = e->prev_line_length;
800 line_length = e->line_length;
801 prev_title_length = e->prev_title_length;
802 title_length = e->title_length;
803 prev_size = e->prev_size;
805 prev_requested_size = e->prev_requested_size;
806 requested_size = e->requested_size;
807 char_height = e->char_height;
808 char_slant = e->char_slant;
809 space_size = e->space_size;
810 sentence_space_size = e->sentence_space_size;
811 adjust_mode = e->adjust_mode;
814 prev_line_interrupted = 0;
816 right_justify_lines = 0;
817 prev_vertical_spacing = e->prev_vertical_spacing;
818 vertical_spacing = e->vertical_spacing;
819 prev_post_vertical_spacing = e->prev_post_vertical_spacing,
820 post_vertical_spacing = e->post_vertical_spacing,
821 prev_line_spacing = e->prev_line_spacing;
822 line_spacing = e->line_spacing;
823 prev_indent = e->prev_indent;
825 have_temporary_indent = 0;
826 temporary_indent = 0;
828 underline_spaces = 0;
829 input_trap_count = 0;
830 continued_input_trap = 0;
831 prev_text_length = e->prev_text_length;
834 input_line_start = 0;
835 control_char = e->control_char;
836 no_break_control_char = e->no_break_control_char;
837 hyphen_indicator_char = e->hyphen_indicator_char;
843 line_tabs = e->line_tabs;
844 current_tab = TAB_NONE;
846 margin_character_flags = e->margin_character_flags;
847 if (e->margin_character_node)
848 margin_character_node = e->margin_character_node->copy();
849 margin_character_distance = e->margin_character_distance;
851 number_text_separation = e->number_text_separation;
852 line_number_multiple = e->line_number_multiple;
853 line_number_indent = e->line_number_indent;
854 no_number_count = e->no_number_count;
855 tab_char = e->tab_char;
856 leader_char = e->leader_char;
857 hyphenation_flags = e->hyphenation_flags;
859 prev_fontno = e->prev_fontno;
862 prev_family = e->prev_family;
865 widow_control = e->widow_control;
866 #endif /* WIDOW_CONTROL */
867 hyphen_line_max = e->hyphen_line_max;
868 hyphen_line_count = 0;
869 hyphenation_space = e->hyphenation_space;
870 hyphenation_margin = e->hyphenation_margin;
872 glyph_color= e->glyph_color;
873 prev_glyph_color = e->prev_glyph_color;
874 fill_color = e->fill_color;
875 prev_fill_color = e->prev_fill_color;
878 environment::~environment()
881 delete_node_list(line);
882 delete_node_list(numbering_nodes);
885 hunits environment::get_input_line_position()
889 n = -input_line_start;
891 n = width_total - input_line_start;
897 void environment::set_input_line_position(hunits n)
899 input_line_start = line == 0 ? -n : width_total - n;
901 input_line_start += tab_width;
904 hunits environment::get_line_length()
909 hunits environment::get_saved_line_length()
912 return target_text_length + saved_indent;
917 vunits environment::get_vertical_spacing()
919 return vertical_spacing;
922 vunits environment::get_post_vertical_spacing()
924 return post_vertical_spacing;
927 int environment::get_line_spacing()
932 vunits environment::total_post_vertical_spacing()
934 vunits tem(post_vertical_spacing);
935 if (line_spacing > 1)
936 tem += (line_spacing - 1)*vertical_spacing;
940 int environment::get_bold()
942 return get_bold_fontno(fontno);
945 hunits environment::get_digit_width()
947 return env_digit_width(this);
950 int environment::get_adjust_mode()
955 int environment::get_fill()
960 hunits environment::get_indent()
965 hunits environment::get_saved_indent()
969 else if (have_temporary_indent)
970 return temporary_indent;
975 hunits environment::get_temporary_indent()
977 return temporary_indent;
980 hunits environment::get_title_length()
985 node *environment::get_prev_char()
987 for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
988 node *last = n->last_char_node();
995 hunits environment::get_prev_char_width()
997 node *last = get_prev_char();
1000 return last->width();
1003 hunits environment::get_prev_char_skew()
1005 node *last = get_prev_char();
1008 return last->skew();
1011 vunits environment::get_prev_char_height()
1013 node *last = get_prev_char();
1017 last->vertical_extent(&min, &max);
1021 vunits environment::get_prev_char_depth()
1023 node *last = get_prev_char();
1027 last->vertical_extent(&min, &max);
1031 hunits environment::get_text_length()
1033 hunits n = line == 0 ? H0 : width_total;
1039 hunits environment::get_prev_text_length()
1041 return prev_text_length;
1045 static int sb_reg_contents = 0;
1046 static int st_reg_contents = 0;
1047 static int ct_reg_contents = 0;
1048 static int rsb_reg_contents = 0;
1049 static int rst_reg_contents = 0;
1050 static int skw_reg_contents = 0;
1051 static int ssc_reg_contents = 0;
1053 void environment::width_registers()
1055 // this is used to implement \w; it sets the st, sb, ct registers
1056 vunits min = 0, max = 0, cur = 0;
1057 int character_type = 0;
1058 ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
1059 skw_reg_contents = line ? line->skew().to_units() : 0;
1060 line = reverse_node_list(line);
1061 vunits real_min = V0;
1062 vunits real_max = V0;
1064 for (node *tem = line; tem; tem = tem->next) {
1065 tem->vertical_extent(&v1, &v2);
1072 if ((cur += tem->vertical_width()) < min)
1076 character_type |= tem->character_type();
1078 line = reverse_node_list(line);
1079 st_reg_contents = -min.to_units();
1080 sb_reg_contents = -max.to_units();
1081 rst_reg_contents = -real_min.to_units();
1082 rsb_reg_contents = -real_max.to_units();
1083 ct_reg_contents = character_type;
1086 node *environment::extract_output_line()
1095 /* environment related requests */
1097 void environment_switch()
1099 int pop = 0; // 1 means pop, 2 means pop but no error message on underflow
1100 if (curenv->is_dummy())
1101 error("can't switch environments when current environment is dummy");
1102 else if (!has_arg())
1106 if (!tok.delimiter()) {
1107 // It looks like a number.
1109 if (get_integer(&n)) {
1110 if (n >= 0 && n < NENVIRONMENTS) {
1111 env_stack = new env_list(curenv, env_stack);
1112 if (env_table[n] == 0)
1113 env_table[n] = new environment(i_to_a(n));
1114 curenv = env_table[n];
1123 nm = get_long_name(1);
1127 if (!nm.is_null()) {
1128 environment *e = (environment *)env_dictionary.lookup(nm);
1130 e = new environment(nm);
1131 (void)env_dictionary.lookup(nm, e);
1133 env_stack = new env_list(curenv, env_stack);
1138 if (env_stack == 0) {
1140 error("environment stack underflow");
1143 int seen_space = curenv->seen_space;
1144 int seen_eol = curenv->seen_eol;
1145 int suppress_next_eol = curenv->suppress_next_eol;
1146 curenv = env_stack->env;
1147 curenv->seen_space = seen_space;
1148 curenv->seen_eol = seen_eol;
1149 curenv->suppress_next_eol = suppress_next_eol;
1150 env_list *tem = env_stack;
1151 env_stack = env_stack->next;
1158 void environment_copy()
1163 if (!tok.delimiter()) {
1164 // It looks like a number.
1166 if (get_integer(&n)) {
1167 if (n >= 0 && n < NENVIRONMENTS)
1174 nm = get_long_name(1);
1175 if (!e && !nm.is_null())
1176 e = (environment *)env_dictionary.lookup(nm);
1178 error("No environment to copy from");
1186 void fill_color_change()
1188 symbol s = get_name();
1190 curenv->set_fill_color(curenv->get_prev_fill_color());
1196 void glyph_color_change()
1198 symbol s = get_name();
1200 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1206 static symbol P_symbol("P");
1210 symbol s = get_name();
1212 if (s.is_null() || s == P_symbol) {
1217 for (const char *p = s.contents(); p != 0 && *p != 0; p++)
1224 curenv->set_font(atoi(s.contents()));
1226 curenv->set_font(s);
1230 void family_change()
1232 symbol s = get_name();
1233 curenv->set_family(s);
1240 if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) {
1243 curenv->set_size(n);
1246 curenv->set_size(0);
1250 void override_sizes()
1253 int *sizes = new int[n];
1255 char *buf = read_string();
1258 char *p = strtok(buf, " \t");
1263 switch (sscanf(p, "%d-%d", &lower, &upper)) {
1268 if (lower <= upper && lower >= 0)
1272 warning(WARN_RANGE, "bad size range '%1'", p);
1276 int *old_sizes = sizes;
1277 sizes = new int[n*2];
1278 memcpy(sizes, old_sizes, n*sizeof(int));
1286 p = strtok(0, " \t");
1288 font_size::init_size_table(sizes);
1294 if (get_integer(&n)) {
1295 curenv->space_size = n;
1296 if (has_arg() && get_integer(&n))
1297 curenv->sentence_space_size = n;
1299 curenv->sentence_space_size = curenv->space_size;
1306 while (!tok.newline() && !tok.eof())
1316 while (!tok.newline() && !tok.eof())
1321 curenv->suppress_next_eol = 1;
1328 if (!has_arg() || !get_integer(&n))
1332 while (!tok.newline() && !tok.eof())
1336 curenv->right_justify_lines = 0;
1337 curenv->center_lines = n;
1338 curdiv->modified_tag.incl(MTSM_CE);
1342 void right_justify()
1345 if (!has_arg() || !get_integer(&n))
1349 while (!tok.newline() && !tok.eof())
1353 curenv->center_lines = 0;
1354 curenv->right_justify_lines = n;
1355 curdiv->modified_tag.incl(MTSM_RJ);
1362 if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) {
1364 warning(WARN_RANGE, "bad line length %1u", temp.to_units());
1369 temp = curenv->prev_line_length;
1370 curenv->prev_line_length = curenv->line_length;
1371 curenv->line_length = temp;
1372 curdiv->modified_tag.incl(MTSM_LL);
1379 if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) {
1381 warning(WARN_RANGE, "bad title length %1u", temp.to_units());
1386 temp = curenv->prev_title_length;
1387 curenv->prev_title_length = curenv->title_length;
1388 curenv->title_length = temp;
1392 void vertical_spacing()
1395 if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) {
1397 warning(WARN_RANGE, "vertical spacing must not be negative");
1402 temp = curenv->prev_vertical_spacing;
1403 curenv->prev_vertical_spacing = curenv->vertical_spacing;
1404 curenv->vertical_spacing = temp;
1408 void post_vertical_spacing()
1411 if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) {
1414 "post vertical spacing must be greater than or equal to 0");
1419 temp = curenv->prev_post_vertical_spacing;
1420 curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
1421 curenv->post_vertical_spacing = temp;
1428 if (has_arg() && get_integer(&temp)) {
1430 warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
1435 temp = curenv->prev_line_spacing;
1436 curenv->prev_line_spacing = curenv->line_spacing;
1437 curenv->line_spacing = temp;
1444 if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) {
1446 warning(WARN_RANGE, "indent cannot be negative");
1451 temp = curenv->prev_indent;
1452 while (!tok.newline() && !tok.eof())
1456 curenv->have_temporary_indent = 0;
1457 curenv->prev_indent = curenv->indent;
1458 curenv->indent = temp;
1459 curdiv->modified_tag.incl(MTSM_IN);
1463 void temporary_indent()
1467 if (!get_hunits(&temp, 'm', curenv->get_indent()))
1469 while (!tok.newline() && !tok.eof())
1474 warning(WARN_RANGE, "total indent cannot be negative");
1478 curenv->temporary_indent = temp;
1479 curenv->have_temporary_indent = 1;
1480 curdiv->modified_tag.incl(MTSM_TI);
1485 node *do_underline_special(int underline_spaces)
1488 m.append_str("x u ");
1489 m.append(underline_spaces + '0');
1490 return new special_node(m, 1);
1493 void do_underline(int underline_spaces)
1496 if (!has_arg() || !get_integer(&n))
1499 if (curenv->underline_lines > 0) {
1500 curenv->prev_fontno = curenv->fontno;
1501 curenv->fontno = curenv->pre_underline_fontno;
1502 if (underline_spaces) {
1503 curenv->underline_spaces = 0;
1504 curenv->add_node(do_underline_special(0));
1507 curenv->underline_lines = 0;
1510 curenv->underline_lines = n;
1511 curenv->pre_underline_fontno = curenv->fontno;
1512 curenv->fontno = get_underline_fontno();
1513 if (underline_spaces) {
1514 curenv->underline_spaces = 1;
1515 curenv->add_node(do_underline_special(1));
1521 void continuous_underline()
1533 curenv->control_char = '.';
1536 error("bad control character");
1538 curenv->control_char = tok.ch();
1543 void no_break_control_char()
1545 curenv->no_break_control_char = '\'';
1548 error("bad control character");
1550 curenv->no_break_control_char = tok.ch();
1555 void margin_character()
1559 charinfo *ci = tok.get_char();
1561 // Call tok.next() only after making the node so that
1562 // .mc \s+9\(br\s0 works.
1563 node *nd = curenv->make_char_node(ci);
1566 delete curenv->margin_character_node;
1567 curenv->margin_character_node = nd;
1568 curenv->margin_character_flags = MARGIN_CHARACTER_ON
1569 | MARGIN_CHARACTER_NEXT;
1571 if (has_arg() && get_hunits(&d, 'm'))
1572 curenv->margin_character_distance = d;
1576 check_missing_character();
1577 curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON;
1578 if (curenv->margin_character_flags == 0) {
1579 delete curenv->margin_character_node;
1580 curenv->margin_character_node = 0;
1588 delete_node_list(curenv->numbering_nodes);
1589 curenv->numbering_nodes = 0;
1592 for (int i = '9'; i >= '0'; i--) {
1593 node *tem = make_node(charset_table[i], curenv);
1601 curenv->numbering_nodes = nd;
1602 curenv->line_number_digit_width = env_digit_width(curenv);
1604 if (!tok.delimiter()) {
1605 if (get_integer(&n, next_line_number)) {
1606 next_line_number = n;
1607 if (next_line_number < 0) {
1608 warning(WARN_RANGE, "negative line number");
1609 next_line_number = 0;
1614 while (!tok.space() && !tok.newline() && !tok.eof())
1617 if (!tok.delimiter()) {
1618 if (get_integer(&n)) {
1620 warning(WARN_RANGE, "negative or zero line number multiple");
1623 curenv->line_number_multiple = n;
1627 while (!tok.space() && !tok.newline() && !tok.eof())
1630 if (!tok.delimiter()) {
1631 if (get_integer(&n))
1632 curenv->number_text_separation = n;
1635 while (!tok.space() && !tok.newline() && !tok.eof())
1637 if (has_arg() && !tok.delimiter() && get_integer(&n))
1638 curenv->line_number_indent = n;
1648 if (has_arg() && get_integer(&n))
1649 curenv->no_number_count = n > 0 ? n : 0;
1651 curenv->no_number_count = 1;
1657 curenv->hyphenation_flags = 0;
1661 void hyphenate_request()
1664 if (has_arg() && get_integer(&n)) {
1665 if (n < HYPHEN_NONE) {
1666 warning(WARN_RANGE, "negative hyphenation flags ignored: %1", n);
1667 } else if (n > HYPHEN_MAX) {
1668 warning(WARN_RANGE, "unknown hyphenation flags ignored (maximum "
1669 "%1): %2", HYPHEN_MAX, n);
1670 } else if (((n & HYPHEN_DEFAULT) && (n & ~HYPHEN_DEFAULT))
1671 || ((n & HYPHEN_FIRST_CHAR) && (n & HYPHEN_NOT_FIRST_CHARS))
1672 || ((n & HYPHEN_LAST_CHAR) && (n & HYPHEN_NOT_LAST_CHARS)))
1673 warning(WARN_SYNTAX, "contradictory hyphenation flags ignored: "
1676 curenv->hyphenation_flags = n;
1679 curenv->hyphenation_flags = 1;
1685 curenv->hyphen_indicator_char = get_optional_char();
1689 void hyphen_line_max_request()
1692 if (has_arg() && get_integer(&n))
1693 curenv->hyphen_line_max = n;
1695 curenv->hyphen_line_max = -1;
1699 void environment::interrupt()
1702 add_node(new transparent_dummy_node);
1707 void environment::newline()
1709 int was_centered = 0;
1710 if (underline_lines > 0) {
1711 if (--underline_lines == 0) {
1712 prev_fontno = fontno;
1713 fontno = pre_underline_fontno;
1714 if (underline_spaces) {
1715 underline_spaces = 0;
1716 add_node(do_underline_special(0));
1724 // strip trailing spaces
1725 while (line != 0 && line->discardable()) {
1726 width_total -= line->width();
1727 space_total -= line->nspaces();
1732 node *to_be_output = 0;
1733 hunits to_be_output_width;
1734 prev_line_interrupted = 0;
1737 else if (interrupted) {
1739 // see environment::final_break
1740 prev_line_interrupted = exit_started ? 2 : 1;
1742 else if (center_lines > 0) {
1744 hunits x = target_text_length - width_total;
1746 saved_indent += x/2;
1747 to_be_output = line;
1749 to_be_output_width = width_total;
1752 else if (right_justify_lines > 0) {
1753 --right_justify_lines;
1754 hunits x = target_text_length - width_total;
1757 to_be_output = line;
1758 to_be_output_width = width_total;
1764 to_be_output = line;
1765 to_be_output_width = width_total;
1768 input_line_start = line == 0 ? H0 : width_total;
1770 if (is_html && !fill) {
1771 curdiv->modified_tag.incl(MTSM_EOL);
1772 if (suppress_next_eol)
1773 suppress_next_eol = 0;
1778 output_line(to_be_output, to_be_output_width, was_centered);
1779 hyphen_line_count = 0;
1781 if (input_trap_count > 0) {
1782 if (!(continued_input_trap && prev_line_interrupted))
1783 if (--input_trap_count == 0)
1784 spring_trap(input_trap);
1788 void environment::output_line(node *n, hunits width, int was_centered)
1790 prev_text_length = width;
1791 if (margin_character_flags) {
1792 hunits d = line_length + margin_character_distance - saved_indent - width;
1794 n = new hmotion_node(d, get_fill_color(), n);
1797 margin_character_flags &= ~MARGIN_CHARACTER_NEXT;
1799 if (!margin_character_flags) {
1800 tem = margin_character_node;
1801 margin_character_node = 0;
1804 tem = margin_character_node->copy();
1807 width += tem->width();
1811 node *tem = n->next;
1816 if (!saved_indent.is_zero())
1817 nn = new hmotion_node(saved_indent, get_fill_color(), nn);
1818 width += saved_indent;
1819 if (no_number_count > 0)
1821 else if (numbering_nodes) {
1822 hunits w = (line_number_digit_width
1823 *(3+line_number_indent+number_text_separation));
1824 if (next_line_number % line_number_multiple != 0)
1825 nn = new hmotion_node(w, get_fill_color(), nn);
1828 nn = new hmotion_node(number_text_separation * line_number_digit_width,
1829 get_fill_color(), nn);
1830 x -= number_text_separation*line_number_digit_width;
1832 sprintf(buf, "%3d", next_line_number);
1833 for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
1834 node *gn = numbering_nodes;
1835 for (int count = *p - '0'; count > 0; count--)
1842 nn = new hmotion_node(x, get_fill_color(), nn);
1847 output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width,
1851 void environment::start_line()
1855 line = new line_start_node;
1856 if (have_temporary_indent) {
1857 saved_indent = temporary_indent;
1858 have_temporary_indent = 0;
1861 saved_indent = indent;
1862 target_text_length = line_length - saved_indent;
1867 hunits environment::get_hyphenation_space()
1869 return hyphenation_space;
1872 void hyphenation_space_request()
1875 if (get_hunits(&n, 'm')) {
1877 warning(WARN_RANGE, "hyphenation space cannot be negative");
1880 curenv->hyphenation_space = n;
1885 hunits environment::get_hyphenation_margin()
1887 return hyphenation_margin;
1890 void hyphenation_margin_request()
1893 if (get_hunits(&n, 'm')) {
1895 warning(WARN_RANGE, "hyphenation margin cannot be negative");
1898 curenv->hyphenation_margin = n;
1903 breakpoint *environment::choose_breakpoint()
1905 hunits x = width_total;
1906 int s = space_total;
1908 breakpoint *best_bp = 0; // the best breakpoint so far
1909 int best_bp_fits = 0;
1913 breakpoint *bp = n->get_breakpoints(x, s);
1915 if (bp->width <= target_text_length) {
1916 if (!bp->hyphenated) {
1917 breakpoint *tem = bp->next;
1920 breakpoint *tem1 = tem;
1925 // Decide whether to use the hyphenated breakpoint.
1926 && (hyphen_line_max < 0
1927 // Only choose the hyphenated breakpoint if it would not
1928 // exceed the maximum number of consecutive hyphenated
1930 || hyphen_line_count + 1 <= hyphen_line_max)
1931 && !(adjust_mode == ADJUST_BOTH
1932 // Don't choose the hyphenated breakpoint if the line
1933 // can be justified by adding no more than
1934 // hyphenation_space to any word space.
1936 && (((target_text_length - bp->width
1937 + (bp->nspaces - 1)*hresolution)/bp->nspaces)
1938 <= hyphenation_space))
1939 // Don't choose the hyphenated breakpoint if the line
1940 // is no more than hyphenation_margin short.
1941 : target_text_length - bp->width <= hyphenation_margin)) {
1950 if ((adjust_mode == ADJUST_BOTH
1951 ? hyphenation_space == H0
1952 : hyphenation_margin == H0)
1953 && (hyphen_line_max < 0
1954 || hyphen_line_count + 1 <= hyphen_line_max)) {
1955 // No need to consider a non-hyphenated breakpoint.
1958 breakpoint *tem = bp->next;
1961 breakpoint *tem1 = tem;
1967 // It fits but it's hyphenated.
1968 if (!best_bp_fits) {
1976 breakpoint *tem = bp;
1993 output_warning(WARN_BREAK, "can't break line");
1999 void environment::hyphenate_line(int start_here)
2002 hyphenation_type prev_type = line->get_hyphenation_type();
2007 for (startp = &line->next; *startp != 0; startp = &(*startp)->next) {
2008 hyphenation_type this_type = (*startp)->get_hyphenation_type();
2009 if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
2011 prev_type = this_type;
2015 node *tem = *startp;
2018 } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
2019 int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
2021 hyphen_list *sl = 0;
2025 while (tem != end) {
2026 sl = tem->get_hyphen_list(sl, &i);
2029 tem1->next = forward;
2033 // this is for characters like hyphen and emdash
2035 for (hyphen_list *h = sl; h; h = h->next) {
2036 h->breakable = (prev_code != 0
2038 && h->next->hyphenation_code != 0);
2039 prev_code = h->hyphenation_code;
2042 if (hyphenation_flags != 0
2044 // this may not be right if we have extra space on this line
2045 && !((hyphenation_flags & HYPHEN_NOT_LAST_LINE)
2046 && (curdiv->distance_to_next_trap()
2047 <= vertical_spacing + total_post_vertical_spacing()))
2049 - (hyphenation_flags & HYPHEN_FIRST_CHAR ? 1 : 0)
2050 - (hyphenation_flags & HYPHEN_LAST_CHAR ? 1 : 0)
2051 + (hyphenation_flags & HYPHEN_NOT_FIRST_CHARS ? 1 : 0)
2052 + (hyphenation_flags & HYPHEN_NOT_LAST_CHARS ? 1 : 0)))
2053 hyphenate(sl, hyphenation_flags);
2054 while (forward != 0) {
2055 node *tem1 = forward;
2056 forward = forward->next;
2058 tem = tem1->add_self(tem, &sl);
2063 static node *node_list_reverse(node *n)
2075 static void distribute_space(node *n, int nspaces, hunits desired_space,
2076 int force_reverse = 0)
2078 static int reverse = 0;
2079 if (force_reverse || reverse)
2080 n = node_list_reverse(n);
2081 if (!force_reverse && nspaces > 0 && spread_limit >= 0
2082 && desired_space.to_units() > 0) {
2083 hunits em = curenv->get_size();
2084 double Ems = (double)desired_space.to_units() / nspaces
2085 / (em.is_zero() ? hresolution : em.to_units());
2086 if (Ems > spread_limit)
2087 output_warning(WARN_BREAK, "spreading %1m per space", Ems);
2089 for (node *tem = n; tem; tem = tem->next)
2090 tem->spread_space(&nspaces, &desired_space);
2091 if (force_reverse || reverse)
2092 (void)node_list_reverse(n);
2095 assert(desired_space.is_zero() && nspaces == 0);
2098 void environment::possibly_break_line(int start_here, int forced)
2100 int was_centered = center_lines > 0;
2101 if (!fill || current_tab || current_field || dummy)
2105 // When a macro follows a paragraph in fill mode, the
2106 // current line should not be empty.
2107 || (width_total - line->width()) > target_text_length)) {
2108 hyphenate_line(start_here);
2109 breakpoint *bp = choose_breakpoint();
2111 // we'll find one eventually
2115 while (*ndp != bp->nd)
2116 ndp = &(*ndp)->next;
2117 bp->nd->split(bp->index, &pre, &post);
2119 hunits extra_space_width = H0;
2120 switch(adjust_mode) {
2122 if (bp->nspaces != 0)
2123 extra_space_width = target_text_length - bp->width;
2124 else if (bp->width > 0 && target_text_length > 0
2125 && target_text_length > bp->width)
2126 output_warning(WARN_BREAK, "cannot adjust line");
2129 saved_indent += (target_text_length - bp->width)/2;
2133 saved_indent += target_text_length - bp->width;
2136 distribute_space(pre, bp->nspaces, extra_space_width);
2137 hunits output_width = bp->width + extra_space_width;
2138 input_line_start -= output_width;
2140 hyphen_line_count++;
2142 hyphen_line_count = 0;
2146 node *first_non_discardable = 0;
2148 for (tem = line; tem != 0; tem = tem->next)
2149 if (!tem->discardable())
2150 first_non_discardable = tem;
2151 node *to_be_discarded;
2152 if (first_non_discardable) {
2153 to_be_discarded = first_non_discardable->next;
2154 first_non_discardable->next = 0;
2155 for (tem = line; tem != 0; tem = tem->next) {
2156 width_total += tem->width();
2157 space_total += tem->nspaces();
2163 to_be_discarded = line;
2166 // Do output_line() here so that line will be 0 iff the
2167 // the environment will be empty.
2168 output_line(pre, output_width, was_centered);
2169 while (to_be_discarded != 0) {
2170 tem = to_be_discarded;
2171 to_be_discarded = to_be_discarded->next;
2172 input_line_start -= tem->width();
2176 if (have_temporary_indent) {
2177 saved_indent = temporary_indent;
2178 have_temporary_indent = 0;
2181 saved_indent = indent;
2182 target_text_length = line_length - saved_indent;
2188 Do the break at the end of input after the end macro (if any).
2190 Unix troff behaves as follows: if the last line is
2194 it will output foo on the current page, and bar on the next page;
2203 everything will be output on the current page. This behaviour must be
2206 The problem is that some macro packages rely on this. For example,
2207 the ATK macros have an end macro that emits \c if it needs to print a
2208 table of contents but doesn't do a 'bp in the end macro; instead the
2209 'bp is done in the bottom of page trap. This works with Unix troff,
2210 provided that the current environment is not empty at the end of the
2213 The following will make macro packages that do that sort of thing work
2214 even if the current environment is empty at the end of the input file.
2215 If the last input line used \c and this line occurred in the end macro,
2216 then we'll force everything out on the current page, but we'll make
2217 sure that the environment isn't empty so that we won't exit at the
2218 bottom of this page.
2221 void environment::final_break()
2223 if (prev_line_interrupted == 2) {
2225 add_node(new transparent_dummy_node);
2231 node *environment::make_tag(const char *nm, int i)
2235 * need to emit tag for post-grohtml
2236 * but we check to see whether we can emit specials
2238 if (curdiv == topdiv && topdiv->before_first_page)
2239 topdiv->begin_page();
2242 m.append_str("devtag:");
2243 for (const char *p = nm; *p; p++)
2244 if (!invalid_input_char((unsigned char)*p))
2248 return new special_node(m);
2253 void environment::dump_troff_state()
2256 fprintf(stderr, SPACES "register 'in' = %d\n", curenv->indent.to_units());
2257 if (curenv->have_temporary_indent)
2258 fprintf(stderr, SPACES "register 'ti' = %d\n",
2259 curenv->temporary_indent.to_units());
2260 fprintf(stderr, SPACES "centered lines 'ce' = %d\n", curenv->center_lines);
2261 fprintf(stderr, SPACES "register 'll' = %d\n",
2262 curenv->line_length.to_units());
2263 fprintf(stderr, SPACES "fill 'fi=1/nf=0' = %d\n", curenv->fill);
2264 fprintf(stderr, SPACES "page offset 'po' = %d\n",
2265 topdiv->get_page_offset().to_units());
2266 fprintf(stderr, SPACES "seen_break = %d\n", curenv->seen_break);
2267 fprintf(stderr, SPACES "seen_space = %d\n", curenv->seen_space);
2272 statem *environment::construct_state(int only_eol)
2275 statem *s = new statem();
2277 s->add_tag(MTSM_IN, indent);
2278 s->add_tag(MTSM_LL, line_length);
2279 s->add_tag(MTSM_PO, topdiv->get_page_offset().to_units());
2280 s->add_tag(MTSM_RJ, right_justify_lines);
2281 if (have_temporary_indent)
2282 s->add_tag(MTSM_TI, temporary_indent);
2285 s->add_tag(MTSM_BR);
2286 if (seen_space != 0)
2287 s->add_tag(MTSM_SP, seen_space);
2292 s->add_tag(MTSM_EOL);
2293 s->add_tag(MTSM_CE, center_lines);
2302 void environment::construct_format_state(node *n, int was_centered,
2306 // find first glyph node which has a state.
2307 while (n != 0 && n->state == 0)
2309 if (n == 0 || (n->state == 0))
2311 if (seen_space != 0)
2312 n->state->add_tag(MTSM_SP, seen_space);
2313 if (seen_eol && topdiv == curdiv)
2314 n->state->add_tag(MTSM_EOL);
2318 n->state->add_tag(MTSM_CE, center_lines+1);
2320 n->state->add_tag_if_unknown(MTSM_CE, 0);
2321 n->state->add_tag_if_unknown(MTSM_FI, filling);
2324 if (n->state != 0) {
2325 n->state->sub_tag_ce();
2326 n->state->add_tag_if_unknown(MTSM_FI, filling);
2333 void environment::construct_new_line_state(node *n)
2336 // find first glyph node which has a state.
2337 while (n != 0 && n->state == 0)
2339 if (n == 0 || n->state == 0)
2341 if (seen_space != 0)
2342 n->state->add_tag(MTSM_SP, seen_space);
2343 if (seen_eol && topdiv == curdiv)
2344 n->state->add_tag(MTSM_EOL);
2350 extern int global_diverted_space;
2352 void environment::do_break(int do_spread)
2354 int was_centered = 0;
2355 if (curdiv == topdiv && topdiv->before_first_page) {
2356 topdiv->begin_page();
2362 // this is so that hyphenation works
2363 if (line->nspaces() == 0) {
2364 line = new space_node(H0, get_fill_color(), line);
2367 possibly_break_line(0, do_spread);
2369 while (line != 0 && line->discardable()) {
2370 width_total -= line->width();
2371 space_total -= line->nspaces();
2377 input_line_start = H0;
2380 switch (adjust_mode) {
2382 saved_indent += (target_text_length - width_total)/2;
2386 saved_indent += target_text_length - width_total;
2392 output_line(tem, width_total, was_centered);
2393 hyphen_line_count = 0;
2395 prev_line_interrupted = 0;
2396 #ifdef WIDOW_CONTROL
2398 output_pending_lines();
2399 #endif /* WIDOW_CONTROL */
2400 if (!global_diverted_space) {
2401 curdiv->modified_tag.incl(MTSM_BR);
2406 int environment::is_empty()
2408 return !current_tab && line == 0 && pending_lines == 0;
2411 void do_break_request(int spread)
2413 while (!tok.newline() && !tok.eof())
2416 curenv->do_break(spread);
2420 void break_request()
2422 do_break_request(0);
2425 void break_spread_request()
2427 do_break_request(1);
2432 if (curdiv == topdiv && topdiv->before_first_page) {
2433 handle_initial_title();
2437 hunits part_width[3];
2438 part[0] = part[1] = part[2] = 0;
2439 environment env(curenv);
2440 environment *oldenv = curenv;
2442 read_title_parts(part, part_width);
2444 curenv->size = env.size;
2445 curenv->prev_size = env.prev_size;
2446 curenv->requested_size = env.requested_size;
2447 curenv->prev_requested_size = env.prev_requested_size;
2448 curenv->char_height = env.char_height;
2449 curenv->char_slant = env.char_slant;
2450 curenv->fontno = env.fontno;
2451 curenv->prev_fontno = env.prev_fontno;
2452 curenv->glyph_color = env.glyph_color;
2453 curenv->prev_glyph_color = env.prev_glyph_color;
2454 curenv->fill_color = env.fill_color;
2455 curenv->prev_fill_color = env.prev_fill_color;
2464 hunits length_title(curenv->title_length);
2465 hunits f = length_title - part_width[1];
2467 n = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(), n);
2475 n = new hmotion_node(f - f2 - part_width[0], curenv->get_fill_color(), n);
2483 curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
2484 curenv->total_post_vertical_spacing(), length_title);
2485 curenv->hyphen_line_count = 0;
2491 curenv->adjust_mode |= 1;
2495 curenv->adjust_mode = ADJUST_LEFT;
2498 curenv->adjust_mode = ADJUST_RIGHT;
2501 curenv->adjust_mode = ADJUST_CENTER;
2505 curenv->adjust_mode = ADJUST_BOTH;
2509 if (get_integer(&n)) {
2511 warning(WARN_RANGE, "negative adjustment mode");
2513 curenv->adjust_mode = 5;
2514 warning(WARN_RANGE, "adjustment mode '%1' out of range", n);
2517 curenv->adjust_mode = n;
2526 curenv->adjust_mode &= ~1;
2530 void do_input_trap(int continued)
2532 curenv->input_trap_count = 0;
2534 curenv->continued_input_trap = 1;
2536 curenv->continued_input_trap = 0;
2538 if (has_arg() && get_integer(&n)) {
2541 "number of lines for input trap must be greater than zero");
2543 symbol s = get_name(1);
2545 curenv->input_trap_count = n;
2546 curenv->input_trap = s;
2558 void input_trap_continued()
2565 // must not be R or C or L or a legitimate part of a number expression
2566 const char TAB_REPEAT_CHAR = 'T';
2572 tab(hunits, tab_type);
2573 enum { BLOCK = 1024 };
2576 tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2580 tab_stops::tab_stops(hunits distance, tab_type type)
2583 repeated_list = new tab(distance, type);
2586 tab_stops::~tab_stops()
2591 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
2595 return distance_to_next_tab(curpos, distance, &nextpos);
2598 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance,
2603 for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
2606 *distance = tem->pos - curpos;
2607 *nextpos = tem->pos;
2610 if (repeated_list == 0)
2612 hunits base = lastpos;
2614 for (tem = repeated_list; tem && tem->pos + base <= curpos; tem = tem->next)
2617 *distance = tem->pos + base - curpos;
2618 *nextpos = tem->pos + base;
2621 assert(lastpos > 0);
2627 const char *tab_stops::to_string()
2629 static char *buf = 0;
2630 static int buf_size = 0;
2631 // figure out a maximum on the amount of space we can need
2634 for (p = initial_list; p; p = p->next)
2636 for (p = repeated_list; p; p = p->next)
2638 // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
2639 int need = count*12 + 3;
2640 if (buf == 0 || need > buf_size) {
2644 buf = new char[buf_size];
2647 for (p = initial_list; p; p = p->next) {
2648 strcpy(ptr, i_to_a(p->pos.to_units()));
2649 ptr = strchr(ptr, '\0');
2667 *ptr++ = TAB_REPEAT_CHAR;
2668 for (p = repeated_list; p; p = p->next) {
2669 strcpy(ptr, i_to_a(p->pos.to_units()));
2670 ptr = strchr(ptr, '\0');
2691 tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2695 tab_stops::tab_stops(const tab_stops &ts)
2696 : initial_list(0), repeated_list(0)
2698 tab **p = &initial_list;
2699 tab *t = ts.initial_list;
2701 *p = new tab(t->pos, t->type);
2706 t = ts.repeated_list;
2708 *p = new tab(t->pos, t->type);
2714 void tab_stops::clear()
2716 while (initial_list) {
2717 tab *tem = initial_list;
2718 initial_list = initial_list->next;
2721 while (repeated_list) {
2722 tab *tem = repeated_list;
2723 repeated_list = repeated_list->next;
2728 void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
2731 for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
2733 *p = new tab(pos, type);
2737 void tab_stops::operator=(const tab_stops &ts)
2740 tab **p = &initial_list;
2741 tab *t = ts.initial_list;
2743 *p = new tab(t->pos, t->type);
2748 t = ts.repeated_list;
2750 *p = new tab(t->pos, t->type);
2759 hunits prev_pos = 0;
2764 if (tok.ch() == TAB_REPEAT_CHAR) {
2769 if (!get_hunits(&pos, 'm', prev_pos))
2771 tab_type type = TAB_LEFT;
2772 if (tok.ch() == 'C') {
2776 else if (tok.ch() == 'R') {
2780 else if (tok.ch() == 'L') {
2783 if (pos <= prev_pos && !first)
2785 "positions of tab stops must be strictly increasing");
2787 tabs.add_tab(pos, type, repeated);
2792 curenv->tabs = tabs;
2793 curdiv->modified_tag.incl(MTSM_TA);
2797 const char *environment::get_tabs()
2799 return tabs.to_string();
2802 tab_type environment::distance_to_next_tab(hunits *distance)
2805 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
2806 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
2809 tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
2812 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
2813 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
2817 void field_characters()
2819 field_delimiter_char = get_optional_char();
2820 if (field_delimiter_char)
2821 padding_indicator_char = get_optional_char();
2823 padding_indicator_char = 0;
2827 void line_tabs_request()
2830 if (has_arg() && get_integer(&n))
2831 curenv->line_tabs = n != 0;
2833 curenv->line_tabs = 1;
2837 int environment::get_line_tabs()
2842 void environment::wrap_up_tab()
2849 switch (current_tab) {
2851 tab_amount = tab_distance - tab_width;
2852 line = make_tab_node(tab_amount, line);
2855 tab_amount = tab_distance - tab_width/2;
2856 line = make_tab_node(tab_amount, line);
2863 width_total += tab_amount;
2864 width_total += tab_width;
2865 if (current_field) {
2866 if (tab_precedes_field) {
2867 pre_field_width += tab_amount;
2868 tab_precedes_field = 0;
2870 field_distance -= tab_amount;
2871 field_spaces += tab_field_spaces;
2873 if (tab_contents != 0) {
2875 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2878 line = tab_contents;
2880 tab_field_spaces = 0;
2884 current_tab = TAB_NONE;
2887 node *environment::make_tab_node(hunits d, node *next)
2889 if (leader_node != 0 && d < 0) {
2890 error("motion generated by leader cannot be negative");
2895 return new hmotion_node(d, 1, 0, get_fill_color(), next);
2896 node *n = new hline_node(d, leader_node, next);
2901 void environment::handle_tab(int is_leader)
2907 charinfo *ci = is_leader ? leader_char : tab_char;
2909 leader_node = ci ? make_char_node(ci) : 0;
2910 tab_type t = distance_to_next_tab(&d, &absolute);
2915 add_node(make_tag("tab L", absolute.to_units()));
2916 add_node(make_tab_node(d));
2919 add_node(make_tag("tab R", absolute.to_units()));
2922 add_node(make_tag("tab C", absolute.to_units()));
2931 tab_field_spaces = 0;
2934 void environment::start_field()
2936 assert(!current_field);
2938 if (distance_to_next_tab(&d) != TAB_NONE) {
2939 pre_field_width = get_text_length();
2943 tab_field_spaces = 0;
2944 for (node *p = line; p; p = p->next)
2949 tab_precedes_field = current_tab != TAB_NONE;
2952 error("zero field width");
2955 void environment::wrap_up_field()
2957 if (!current_tab && field_spaces == 0)
2959 hunits padding = field_distance - (get_text_length() - pre_field_width);
2960 if (current_tab && tab_field_spaces != 0) {
2961 hunits tab_padding = scale(padding,
2963 field_spaces + tab_field_spaces);
2964 padding -= tab_padding;
2965 distribute_space(tab_contents, tab_field_spaces, tab_padding, 1);
2966 tab_field_spaces = 0;
2967 tab_width += tab_padding;
2969 if (field_spaces != 0) {
2970 distribute_space(line, field_spaces, padding, 1);
2971 width_total += padding;
2973 // the start of the tab has been moved to the right by padding, so
2974 tab_distance -= padding;
2975 if (tab_distance <= H0) {
2976 // use the next tab stop instead
2977 current_tab = tabs.distance_to_next_tab(get_input_line_position()
2980 if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
2981 width_total += tab_width;
2982 if (current_tab == TAB_LEFT) {
2983 line = make_tab_node(tab_distance, line);
2984 width_total += tab_distance;
2985 current_tab = TAB_NONE;
2987 if (tab_contents != 0) {
2989 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2992 line = tab_contents;
3004 void environment::add_padding()
3007 tab_contents = new space_node(H0, get_fill_color(), tab_contents);
3013 line = new space_node(H0, get_fill_color(), line);
3018 typedef int (environment::*INT_FUNCP)();
3019 typedef vunits (environment::*VUNITS_FUNCP)();
3020 typedef hunits (environment::*HUNITS_FUNCP)();
3021 typedef const char *(environment::*STRING_FUNCP)();
3023 class int_env_reg : public reg {
3026 int_env_reg(INT_FUNCP);
3027 const char *get_string();
3028 int get_value(units *val);
3031 class vunits_env_reg : public reg {
3034 vunits_env_reg(VUNITS_FUNCP f);
3035 const char *get_string();
3036 int get_value(units *val);
3040 class hunits_env_reg : public reg {
3043 hunits_env_reg(HUNITS_FUNCP f);
3044 const char *get_string();
3045 int get_value(units *val);
3048 class string_env_reg : public reg {
3051 string_env_reg(STRING_FUNCP);
3052 const char *get_string();
3055 int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
3059 int int_env_reg::get_value(units *val)
3061 *val = (curenv->*func)();
3065 const char *int_env_reg::get_string()
3067 return i_to_a((curenv->*func)());
3070 vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
3074 int vunits_env_reg::get_value(units *val)
3076 *val = (curenv->*func)().to_units();
3080 const char *vunits_env_reg::get_string()
3082 return i_to_a((curenv->*func)().to_units());
3085 hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
3089 int hunits_env_reg::get_value(units *val)
3091 *val = (curenv->*func)().to_units();
3095 const char *hunits_env_reg::get_string()
3097 return i_to_a((curenv->*func)().to_units());
3100 string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
3104 const char *string_env_reg::get_string()
3106 return (curenv->*func)();
3109 class horizontal_place_reg : public general_reg {
3111 horizontal_place_reg();
3112 int get_value(units *);
3113 void set_value(units);
3116 horizontal_place_reg::horizontal_place_reg()
3120 int horizontal_place_reg::get_value(units *res)
3122 *res = curenv->get_input_line_position().to_units();
3126 void horizontal_place_reg::set_value(units n)
3128 curenv->set_input_line_position(hunits(n));
3131 int environment::get_zoom()
3133 return env_get_zoom(this);
3136 const char *environment::get_font_family_string()
3138 return family->nm.contents();
3141 const char *environment::get_glyph_color_string()
3143 return glyph_color->nm.contents();
3146 const char *environment::get_fill_color_string()
3148 return fill_color->nm.contents();
3151 const char *environment::get_font_name_string()
3153 symbol f = get_font_name(fontno, this);
3154 return f.contents();
3157 const char *environment::get_style_name_string()
3159 symbol f = get_style_name(fontno);
3160 return f.contents();
3163 const char *environment::get_name_string()
3165 return name.contents();
3168 // Convert a quantity in scaled points to ascii decimal fraction.
3170 const char *sptoa(int sp)
3173 assert(sizescale > 0);
3176 if (sp % sizescale == 0)
3177 return i_to_a(sp/sizescale);
3178 // See if 1/sizescale is exactly representable as a decimal fraction,
3179 // ie its only prime factors are 2 and 5.
3182 while ((n & 1) == 0) {
3187 while ((n % 5) == 0) {
3192 int decimal_point = power5 > power2 ? power5 : power2;
3193 if (decimal_point <= 10) {
3196 for (t = decimal_point - power2; --t >= 0;)
3198 for (t = decimal_point - power5; --t >= 0;)
3200 if (factor == 1 || sp <= INT_MAX/factor)
3201 return if_to_a(sp*factor, decimal_point);
3204 double s = double(sp)/double(sizescale);
3205 double factor = 10.0;
3207 int decimal_point = 0;
3209 double v = ceil(s*factor);
3214 } while (++decimal_point < 10);
3215 return if_to_a(int(val), decimal_point);
3218 const char *environment::get_point_size_string()
3220 return sptoa(curenv->get_point_size());
3223 const char *environment::get_requested_point_size_string()
3225 return sptoa(curenv->get_requested_point_size());
3228 void environment::print_env()
3230 // at the time of calling .pev, those values are always zero or
3233 // char_height, char_slant,
3235 // current_tab, tab_width, tab_distance
3236 // current_field, field_distance, pre_field_width, field_spaces,
3237 // tab_field_spaces, tab_precedes_field
3240 errprint(" previous line length: %1u\n", prev_line_length.to_units());
3241 errprint(" line length: %1u\n", line_length.to_units());
3242 errprint(" previous title length: %1u\n", prev_title_length.to_units());
3243 errprint(" title length: %1u\n", title_length.to_units());
3244 errprint(" previous size: %1p (%2s)\n",
3245 prev_size.to_points(), prev_size.to_scaled_points());
3246 errprint(" size: %1p (%2s)\n",
3247 size.to_points(), size.to_scaled_points());
3248 errprint(" previous requested size: %1s\n", prev_requested_size);
3249 errprint(" requested size: %1s\n", requested_size);
3250 errprint(" previous font number: %1\n", prev_fontno);
3251 errprint(" font number: %1\n", fontno);
3252 errprint(" previous family: '%1'\n", prev_family->nm.contents());
3253 errprint(" family: '%1'\n", family->nm.contents());
3254 errprint(" space size: %1/36 em\n", space_size);
3255 errprint(" sentence space size: %1/36 em\n", sentence_space_size);
3256 errprint(" previous line interrupted: %1\n",
3257 prev_line_interrupted ? "yes" : "no");
3258 errprint(" fill mode: %1\n", fill ? "on" : "off");
3259 errprint(" adjust mode: %1\n",
3260 adjust_mode == ADJUST_LEFT
3262 : adjust_mode == ADJUST_BOTH
3264 : adjust_mode == ADJUST_CENTER
3268 errprint(" lines to center: %1\n", center_lines);
3269 if (right_justify_lines)
3270 errprint(" lines to right justify: %1\n", right_justify_lines);
3271 errprint(" previous vertical spacing: %1u\n",
3272 prev_vertical_spacing.to_units());
3273 errprint(" vertical spacing: %1u\n", vertical_spacing.to_units());
3274 errprint(" previous post-vertical spacing: %1u\n",
3275 prev_post_vertical_spacing.to_units());
3276 errprint(" post-vertical spacing: %1u\n",
3277 post_vertical_spacing.to_units());
3278 errprint(" previous line spacing: %1\n", prev_line_spacing);
3279 errprint(" line spacing: %1\n", line_spacing);
3280 errprint(" previous indentation: %1u\n", prev_indent.to_units());
3281 errprint(" indentation: %1u\n", indent.to_units());
3282 errprint(" temporary indentation: %1u\n", temporary_indent.to_units());
3283 errprint(" have temporary indentation: %1\n",
3284 have_temporary_indent ? "yes" : "no");
3285 errprint(" currently used indentation: %1u\n", saved_indent.to_units());
3286 errprint(" target text length: %1u\n", target_text_length.to_units());
3287 if (underline_lines) {
3288 errprint(" lines to underline: %1\n", underline_lines);
3289 errprint(" font number before underlining: %1\n", pre_underline_fontno);
3290 errprint(" underline spaces: %1\n", underline_spaces ? "yes" : "no");
3292 if (input_trap.contents()) {
3293 errprint(" input trap macro: '%1'\n", input_trap.contents());
3294 errprint(" input trap line counter: %1\n", input_trap_count);
3295 errprint(" continued input trap: %1\n",
3296 continued_input_trap ? "yes" : "no");
3298 errprint(" previous text length: %1u\n", prev_text_length.to_units());
3299 errprint(" total width: %1u\n", width_total.to_units());
3300 errprint(" total number of spaces: %1\n", space_total);
3301 errprint(" input line start: %1u\n", input_line_start.to_units());
3302 errprint(" line tabs: %1\n", line_tabs ? "yes" : "no");
3303 errprint(" discarding: %1\n", discarding ? "yes" : "no");
3304 errprint(" spread flag set: %1\n", spread_flag ? "yes" : "no"); // \p
3305 if (margin_character_node) {
3306 errprint(" margin character flags: %1\n",
3307 margin_character_flags == MARGIN_CHARACTER_ON
3309 : margin_character_flags == MARGIN_CHARACTER_NEXT
3311 : margin_character_flags == (MARGIN_CHARACTER_ON
3312 | MARGIN_CHARACTER_NEXT)
3315 errprint(" margin character distance: %1u\n",
3316 margin_character_distance.to_units());
3318 if (numbering_nodes) {
3319 errprint(" line number digit width: %1u\n",
3320 line_number_digit_width.to_units());
3321 errprint(" separation between number and text: %1 digit spaces\n",
3322 number_text_separation);
3323 errprint(" line number indentation: %1 digit spaces\n",
3324 line_number_indent);
3325 errprint(" print line numbers every %1line%1\n",
3326 line_number_multiple > 1 ? i_to_a(line_number_multiple) : "",
3327 line_number_multiple > 1 ? "s" : "");
3328 errprint(" lines not to enumerate: %1\n", no_number_count);
3330 string hf = hyphenation_flags ? "on" : "off";
3331 if (hyphenation_flags & HYPHEN_NOT_LAST_LINE)
3332 hf += ", not last line";
3333 if (hyphenation_flags & HYPHEN_LAST_CHAR)
3334 hf += ", last char";
3335 if (hyphenation_flags & HYPHEN_NOT_LAST_CHARS)
3336 hf += ", not last two chars";
3337 if (hyphenation_flags & HYPHEN_FIRST_CHAR)
3338 hf += ", first char";
3339 if (hyphenation_flags & HYPHEN_NOT_FIRST_CHARS)
3340 hf += ", not first two chars";
3342 errprint(" hyphenation_flags: %1\n", hf.contents());
3343 errprint(" number of consecutive hyphenated lines: %1\n",
3345 errprint(" maximum number of consecutive hyphenated lines: %1\n",
3347 errprint(" hyphenation space: %1u\n", hyphenation_space.to_units());
3348 errprint(" hyphenation margin: %1u\n", hyphenation_margin.to_units());
3349 #ifdef WIDOW_CONTROL
3350 errprint(" widow control: %1\n", widow_control ? "yes" : "no");
3351 #endif /* WIDOW_CONTROL */
3356 errprint("Current Environment:\n");
3357 curenv->print_env();
3358 for (int i = 0; i < NENVIRONMENTS; i++) {
3360 errprint("Environment %1:\n", i);
3361 if (env_table[i] != curenv)
3362 env_table[i]->print_env();
3364 errprint(" current\n");
3367 dictionary_iterator iter(env_dictionary);
3370 while (iter.get(&s, (void **)&e)) {
3371 assert(!s.is_null());
3372 errprint("Environment %1:\n", s.contents());
3376 errprint(" current\n");
3382 #define init_int_env_reg(name, func) \
3383 number_reg_dictionary.define(name, new int_env_reg(&environment::func))
3385 #define init_vunits_env_reg(name, func) \
3386 number_reg_dictionary.define(name, new vunits_env_reg(&environment::func))
3388 #define init_hunits_env_reg(name, func) \
3389 number_reg_dictionary.define(name, new hunits_env_reg(&environment::func))
3391 #define init_string_env_reg(name, func) \
3392 number_reg_dictionary.define(name, new string_env_reg(&environment::func))
3394 void init_env_requests()
3396 init_request("ad", adjust);
3397 init_request("br", break_request);
3398 init_request("brp", break_spread_request);
3399 init_request("c2", no_break_control_char);
3400 init_request("cc", control_char);
3401 init_request("ce", center);
3402 init_request("cu", continuous_underline);
3403 init_request("ev", environment_switch);
3404 init_request("evc", environment_copy);
3405 init_request("fam", family_change);
3406 init_request("fc", field_characters);
3407 init_request("fi", fill);
3408 init_request("fcolor", fill_color_change);
3409 init_request("ft", font_change);
3410 init_request("gcolor", glyph_color_change);
3411 init_request("hc", hyphen_char);
3412 init_request("hlm", hyphen_line_max_request);
3413 init_request("hy", hyphenate_request);
3414 init_request("hym", hyphenation_margin_request);
3415 init_request("hys", hyphenation_space_request);
3416 init_request("in", indent);
3417 init_request("it", input_trap);
3418 init_request("itc", input_trap_continued);
3419 init_request("lc", leader_character);
3420 init_request("linetabs", line_tabs_request);
3421 init_request("ll", line_length);
3422 init_request("ls", line_spacing);
3423 init_request("lt", title_length);
3424 init_request("mc", margin_character);
3425 init_request("na", no_adjust);
3426 init_request("nf", no_fill);
3427 init_request("nh", no_hyphenate);
3428 init_request("nm", number_lines);
3429 init_request("nn", no_number);
3430 init_request("pev", print_env);
3431 init_request("ps", point_size);
3432 init_request("pvs", post_vertical_spacing);
3433 init_request("rj", right_justify);
3434 init_request("sizes", override_sizes);
3435 init_request("ss", space_size);
3436 init_request("ta", set_tabs);
3437 init_request("ti", temporary_indent);
3438 init_request("tc", tab_character);
3439 init_request("tl", title);
3440 init_request("ul", underline);
3441 init_request("vs", vertical_spacing);
3442 #ifdef WIDOW_CONTROL
3443 init_request("wdc", widow_control_request);
3444 #endif /* WIDOW_CONTROL */
3445 init_int_env_reg(".b", get_bold);
3446 init_vunits_env_reg(".cdp", get_prev_char_depth);
3447 init_int_env_reg(".ce", get_center_lines);
3448 init_vunits_env_reg(".cht", get_prev_char_height);
3449 init_hunits_env_reg(".csk", get_prev_char_skew);
3450 init_string_env_reg(".ev", get_name_string);
3451 init_int_env_reg(".f", get_font);
3452 init_string_env_reg(".fam", get_font_family_string);
3453 init_string_env_reg(".fn", get_font_name_string);
3454 init_int_env_reg(".height", get_char_height);
3455 init_int_env_reg(".hlc", get_hyphen_line_count);
3456 init_int_env_reg(".hlm", get_hyphen_line_max);
3457 init_int_env_reg(".hy", get_hyphenation_flags);
3458 init_hunits_env_reg(".hym", get_hyphenation_margin);
3459 init_hunits_env_reg(".hys", get_hyphenation_space);
3460 init_hunits_env_reg(".i", get_indent);
3461 init_hunits_env_reg(".in", get_saved_indent);
3462 init_int_env_reg(".int", get_prev_line_interrupted);
3463 init_int_env_reg(".linetabs", get_line_tabs);
3464 init_hunits_env_reg(".lt", get_title_length);
3465 init_int_env_reg(".j", get_adjust_mode);
3466 init_hunits_env_reg(".k", get_text_length);
3467 init_int_env_reg(".L", get_line_spacing);
3468 init_hunits_env_reg(".l", get_line_length);
3469 init_hunits_env_reg(".ll", get_saved_line_length);
3470 init_string_env_reg(".M", get_fill_color_string);
3471 init_string_env_reg(".m", get_glyph_color_string);
3472 init_hunits_env_reg(".n", get_prev_text_length);
3473 init_int_env_reg(".ps", get_point_size);
3474 init_int_env_reg(".psr", get_requested_point_size);
3475 init_vunits_env_reg(".pvs", get_post_vertical_spacing);
3476 init_int_env_reg(".rj", get_right_justify_lines);
3477 init_string_env_reg(".s", get_point_size_string);
3478 init_int_env_reg(".slant", get_char_slant);
3479 init_int_env_reg(".ss", get_space_size);
3480 init_int_env_reg(".sss", get_sentence_space_size);
3481 init_string_env_reg(".sr", get_requested_point_size_string);
3482 init_string_env_reg(".sty", get_style_name_string);
3483 init_string_env_reg(".tabs", get_tabs);
3484 init_int_env_reg(".u", get_fill);
3485 init_vunits_env_reg(".v", get_vertical_spacing);
3486 init_hunits_env_reg(".w", get_prev_char_width);
3487 init_int_env_reg(".zoom", get_zoom);
3488 number_reg_dictionary.define("ct", new variable_reg(&ct_reg_contents));
3489 number_reg_dictionary.define("hp", new horizontal_place_reg);
3490 number_reg_dictionary.define("ln", new variable_reg(&next_line_number));
3491 number_reg_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
3492 number_reg_dictionary.define("rst", new variable_reg(&rst_reg_contents));
3493 number_reg_dictionary.define("sb", new variable_reg(&sb_reg_contents));
3494 number_reg_dictionary.define("skw", new variable_reg(&skw_reg_contents));
3495 number_reg_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
3496 number_reg_dictionary.define("st", new variable_reg(&st_reg_contents));
3499 // Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
3505 virtual void do_match(int len, void *val) = 0;
3506 virtual void do_delete(void *) = 0;
3507 void delete_trie_node(trie_node *);
3510 virtual ~trie(); // virtual to shut up g++
3511 void insert(const char *, int, void *);
3512 // find calls do_match for each match it finds
3513 void find(const char *pat, int patlen);
3517 class hyphen_trie : private trie {
3519 void do_match(int i, void *v);
3520 void do_delete(void *v);
3521 void insert_pattern(const char *pat, int patlen, int *num);
3522 void insert_hyphenation(dictionary *ex, const char *pat, int patlen);
3523 int hpf_getc(FILE *f);
3527 void hyphenate(const char *word, int len, int *hyphens);
3528 void read_patterns_file(const char *name, int append, dictionary *ex);
3531 struct hyphenation_language {
3533 dictionary exceptions;
3534 hyphen_trie patterns;
3535 hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
3536 ~hyphenation_language() { }
3539 dictionary language_dictionary(5);
3540 hyphenation_language *current_language = 0;
3542 static void set_hyphenation_language()
3544 symbol nm = get_name(1);
3545 if (!nm.is_null()) {
3546 current_language = (hyphenation_language *)language_dictionary.lookup(nm);
3547 if (!current_language) {
3548 current_language = new hyphenation_language(nm);
3549 (void)language_dictionary.lookup(nm, (void *)current_language);
3555 const int WORD_MAX = 256; // we use unsigned char for offsets in
3556 // hyphenation exceptions
3558 static void hyphen_word()
3560 if (!current_language) {
3561 error("no current hyphenation language");
3565 char buf[WORD_MAX + 1];
3566 unsigned char pos[WORD_MAX + 2];
3569 if (tok.newline() || tok.eof())
3573 while (i < WORD_MAX && !tok.space() && !tok.newline() && !tok.eof()) {
3574 charinfo *ci = tok.get_char(1);
3580 if (ci->get_ascii_code() == '-') {
3581 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3585 unsigned char c = ci->get_hyphenation_code();
3594 unsigned char *tem = new unsigned char[npos + 1];
3595 memcpy(tem, pos, npos + 1);
3596 tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf),
3610 trie_node(char, trie_node *);
3613 trie_node::trie_node(char ch, trie_node *p)
3614 : c(ch), down(0), right(p), val(0)
3625 delete_trie_node(tp);
3630 void trie::delete_trie_node(trie_node *p)
3633 delete_trie_node(p->down);
3634 delete_trie_node(p->right);
3641 void trie::insert(const char *pat, int patlen, void *val)
3643 trie_node **p = &tp;
3644 assert(patlen > 0 && pat != 0);
3646 while (*p != 0 && (*p)->c < pat[0])
3648 if (*p == 0 || (*p)->c != pat[0])
3649 *p = new trie_node(pat[0], *p);
3650 if (--patlen == 0) {
3659 void trie::find(const char *pat, int patlen)
3662 for (int i = 0; p != 0 && i < patlen; i++) {
3663 while (p != 0 && p->c < pat[i])
3665 if (p != 0 && p->c == pat[i]) {
3667 do_match(i+1, p->val);
3679 operation(int, int, operation *);
3682 operation::operation(int i, int j, operation *op)
3683 : next(op), distance(j), num(i)
3687 void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
3690 for (int i = 0; i < patlen+1; i++)
3692 op = new operation(num[i], patlen - i, op);
3693 insert(pat, patlen, op);
3696 void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
3699 char buf[WORD_MAX + 2];
3700 unsigned char pos[WORD_MAX + 2];
3703 while (j < patlen) {
3704 unsigned char c = pat[j++];
3706 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3712 buf[i++] = hpf_code_table[c];
3717 unsigned char *tem = new unsigned char[npos + 1];
3718 memcpy(tem, pos, npos + 1);
3719 tem = (unsigned char *)ex->lookup(symbol(buf), tem);
3725 void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
3728 for (j = 0; j < len + 1; j++)
3730 for (j = 0; j < len - 1; j++) {
3732 find(word + j, len - j);
3736 inline int max(int m, int n)
3738 return m > n ? m : n;
3741 void hyphen_trie::do_match(int i, void *v)
3743 operation *op = (operation *)v;
3745 h[i - op->distance] = max(h[i - op->distance], op->num);
3750 void hyphen_trie::do_delete(void *v)
3752 operation *op = (operation *)v;
3754 operation *tem = op;
3760 /* We use very simple rules to parse TeX's hyphenation patterns.
3762 . '%' starts a comment even if preceded by '\'.
3764 . No support for digraphs and like '\$'.
3766 . '^^xx' ('x' is 0-9 or a-f), and '^^x' (character code of 'x' in the
3767 range 0-127) are recognized; other use of '^' causes an error.
3769 . No macro expansion.
3771 . We check for the expression '\patterns{...}' (possibly with
3772 whitespace before and after the braces). Everything between the
3773 braces is taken as hyphenation patterns. Consequently, '{' and '}'
3774 are not allowed in patterns.
3776 . Similarly, '\hyphenation{...}' gives a list of hyphenation
3779 . '\endinput' is recognized also.
3781 . For backwards compatibility, if '\patterns' is missing, the
3782 whole file is treated as a list of hyphenation patterns (only
3783 recognizing '%' as the start of a comment.
3787 int hyphen_trie::hpf_getc(FILE *f)
3799 if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
3800 && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
3801 if (c >= '0' && c <= '9')
3805 if (c1 >= '0' && c1 <= '9')
3813 if (c >= 0 && c <= 63)
3815 else if (c >= 64 && c <= 127)
3822 error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
3826 void hyphen_trie::read_patterns_file(const char *name, int append,
3831 char buf[WORD_MAX + 1];
3832 for (int i = 0; i < WORD_MAX + 1; i++)
3834 int num[WORD_MAX + 1];
3837 FILE *fp = mac_path->open_file(name, &path);
3839 error("can't find hyphenation patterns file '%1'", name);
3842 int c = hpf_getc(fp);
3843 int have_patterns = 0; // we've seen \patterns
3844 int final_pattern = 0; // 1 if we have a trailing closing brace
3845 int have_hyphenation = 0; // we've seen \hyphenation
3846 int final_hyphenation = 0; // 1 if we have a trailing closing brace
3847 int have_keyword = 0; // we've seen either \patterns or \hyphenation
3848 int traditional = 0; // don't handle \patterns
3851 if (c == '%') { // skip comments
3854 } while (c != EOF && c != '\n');
3856 if (c == EOF || !csspace(c))
3861 if (have_keyword || traditional) // we are done
3863 else { // rescan file in 'traditional' mode
3872 if (!(c == '{' || c == '}')) { // skip braces at line start
3873 do { // scan patterns
3881 } while (i < WORD_MAX && c != EOF && !csspace(c)
3882 && c != '%' && c != '{' && c != '}');
3885 if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
3889 if (have_patterns || have_hyphenation)
3890 error("\\patterns not allowed inside of %1 group",
3891 have_patterns ? "\\patterns" : "\\hyphenation");
3900 else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
3904 if (have_patterns || have_hyphenation)
3905 error("\\hyphenation not allowed inside of %1 group",
3906 have_patterns ? "\\patterns" : "\\hyphenation");
3908 have_hyphenation = 1;
3915 else if (strstr(buf, "\\endinput")) {
3916 if (have_patterns || have_hyphenation)
3917 error("found \\endinput inside of %1 group",
3918 have_patterns ? "\\patterns" : "\\hyphenation");
3921 else if (c == '}') {
3922 if (have_patterns) {
3927 else if (have_hyphenation) {
3928 have_hyphenation = 0;
3930 final_hyphenation = 1;
3934 else if (c == '{') {
3935 if (have_patterns || have_hyphenation)
3936 error("'{' not allowed within %1 group",
3937 have_patterns ? "\\patterns" : "\\hyphenation");
3938 c = hpf_getc(fp); // skipped if not starting \patterns
3943 if (c == '{' || c == '}')
3947 if (have_patterns || final_pattern || traditional) {
3948 for (int j = 0; j < i; j++)
3949 buf[j] = hpf_code_table[(unsigned char)buf[j]];
3950 insert_pattern(buf, i, num);
3953 else if (have_hyphenation || final_hyphenation) {
3954 // hyphenation exceptions in a pattern file are subject to `.hy'
3955 // restrictions; we mark such entries with a trailing space
3957 insert_hyphenation(ex, buf, i);
3958 final_hyphenation = 0;
3967 void hyphenate(hyphen_list *h, unsigned flags)
3969 if (!current_language)
3972 while (h && h->hyphenation_code == 0)
3975 char hbuf[WORD_MAX + 2];
3976 char *buf = hbuf + 1;
3978 for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
3979 if (tem->hyphenation_code != 0)
3980 buf[len++] = tem->hyphenation_code;
3984 hyphen_list *nexth = tem;
3986 // check `.hw' entries
3989 = (unsigned char *)current_language->exceptions.lookup(buf);
3993 for (tem = h; tem != 0; tem = tem->next, i++)
4000 // check `\hyphenation' entries from pattern files;
4001 // such entries are marked with a trailing space
4004 pos = (unsigned char *)current_language->exceptions.lookup(buf);
4010 if (flags & HYPHEN_FIRST_CHAR)
4017 if (!(flags & HYPHEN_NOT_FIRST_CHARS))
4023 if (!(flags & HYPHEN_LAST_CHAR))
4025 if (flags & HYPHEN_NOT_LAST_CHARS)
4027 for (; i < len && tem; tem = tem->next, i++)
4034 hbuf[0] = hbuf[len + 1] = '.';
4035 int num[WORD_MAX + 3];
4036 current_language->patterns.hyphenate(hbuf, len + 2, num);
4037 // The position of a hyphenation point gets marked with an odd
4040 // hbuf: . h e l p f u l .
4041 // num: 0 0 0 2 4 3 0 0 0 0
4042 if (!(flags & HYPHEN_FIRST_CHAR))
4044 if (flags & HYPHEN_NOT_FIRST_CHARS)
4046 if (flags & HYPHEN_LAST_CHAR)
4048 if (flags & HYPHEN_NOT_LAST_CHARS)
4051 for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
4061 static void do_hyphenation_patterns_file(int append)
4063 symbol name = get_long_name(1);
4064 if (!name.is_null()) {
4065 if (!current_language)
4066 error("no current hyphenation language");
4068 current_language->patterns.read_patterns_file(
4069 name.contents(), append,
4070 ¤t_language->exceptions);
4075 static void hyphenation_patterns_file()
4077 do_hyphenation_patterns_file(0);
4080 static void hyphenation_patterns_file_append()
4082 do_hyphenation_patterns_file(1);
4085 class hyphenation_language_reg : public reg {
4087 const char *get_string();
4090 const char *hyphenation_language_reg::get_string()
4092 return current_language ? current_language->name.contents() : "";
4095 void init_hyphen_requests()
4097 init_request("hw", hyphen_word);
4098 init_request("hla", set_hyphenation_language);
4099 init_request("hpf", hyphenation_patterns_file);
4100 init_request("hpfa", hyphenation_patterns_file_append);
4101 number_reg_dictionary.define(".hla", new hyphenation_language_reg);