Sync-to-go: update copyright for 2015
[s-roff.git] / src / troff / env.cpp
blobf7a253184c1260e9d8095d85db2b64dada4c74b5
1 /*@
2 * Copyright (c) 2014 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
4 * Copyright (C) 1989 - 1992, 2000 - 2006 Free Software Foundation, Inc.
5 * Written by James Clark (jjc@jclark.com)
7 * This 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 2, or (at your option) any later
10 * version.
12 * This 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
15 * for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with groff; see the file COPYING. If not, write to the Free Software
19 * Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA.
22 #include "config.h"
23 #include "troff-config.h"
25 #include <math.h>
27 #include "file_case.h"
28 #include "font.h"
29 #include "macropath.h"
30 #include "stringclass.h"
32 #include "charinfo.h"
33 #include "dictionary.h"
34 #include "div.h"
35 #include "env.h"
36 #include "hvunits.h"
37 #include "input.h"
38 #include "mtsm.h"
39 #include "node.h"
40 #include "reg.h"
41 #include "request.h"
42 #include "token.h"
43 #include "troff.h"
45 symbol default_family("T");
47 enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 };
49 enum { HYPHEN_LAST_LINE = 2, HYPHEN_LAST_CHARS = 4, HYPHEN_FIRST_CHARS = 8 };
51 class env_list
53 public:
54 environment *env;
55 env_list *next;
57 env_list(environment *e, env_list *p) : env(e), next(p) {}
60 env_list *env_stack;
61 environment *env_table[NENVIRONMENTS];
62 dictionary env_dictionary(NENVIRONMENTS);
63 environment *curenv;
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
75 #ifdef WIDOW_CONTROL
76 friend void environment::mark_last_line();
77 friend void environment::output(node *, int, vunits, vunits, hunits, int);
78 #endif
80 node *nd;
81 int no_fill;
82 int was_centered;
83 vunits vs;
84 vunits post_vs;
85 hunits width;
86 #ifdef WIDOW_CONTROL
87 int last_line; // Is it the last line of the paragraph?
88 #endif
90 public:
91 pending_output_line *next;
93 pending_output_line(node *, int, vunits, vunits, hunits, int,
94 pending_output_line * = 0);
95 ~pending_output_line();
96 int output();
99 pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv,
100 hunits w, int ce,
101 pending_output_line *p)
102 : nd(n), no_fill(nf), was_centered(ce), vs(v), post_vs(pv), width(w),
103 #ifdef WIDOW_CONTROL
104 last_line(0),
105 #endif
106 next(p)
110 pending_output_line::~pending_output_line()
112 delete_node_list(nd);
115 int pending_output_line::output()
117 if (trap_sprung_flag)
118 return 0;
119 #ifdef WIDOW_CONTROL
120 if (next && next->last_line && !no_fill) {
121 curdiv->need(vs + post_vs + vunits(vresolution));
122 if (trap_sprung_flag) {
123 next->last_line = 0; // Try to avoid infinite loops.
124 return 0;
127 #endif
128 curenv->construct_format_state(nd, was_centered, !no_fill);
129 curdiv->output(nd, no_fill, vs, post_vs, width);
130 nd = 0;
131 return 1;
134 void environment::output(node *nd, int no_fill_flag,
135 vunits vs, vunits post_vs,
136 hunits width, int was_centered)
138 #ifdef WIDOW_CONTROL
139 while (pending_lines) {
140 if (widow_control && !pending_lines->no_fill && !pending_lines->next)
141 break;
142 if (!pending_lines->output())
143 break;
144 pending_output_line *tem = pending_lines;
145 pending_lines = pending_lines->next;
146 delete tem;
148 #else
149 output_pending_lines();
150 #endif
151 if (!trap_sprung_flag && !pending_lines
152 #ifdef WIDOW_CONTROL
153 && (!widow_control || no_fill_flag)
154 #endif
156 curenv->construct_format_state(nd, was_centered, !no_fill_flag);
157 curdiv->output(nd, no_fill_flag, vs, post_vs, width);
158 } else {
159 pending_output_line **p;
160 for (p = &pending_lines; *p; p = &(*p)->next)
162 *p = new pending_output_line(nd, no_fill_flag, vs, post_vs, width,
163 was_centered);
167 // a line from .tl goes at the head of the queue
169 void environment::output_title(node *nd, int no_fill_flag,
170 vunits vs, vunits post_vs,
171 hunits width)
173 if (!trap_sprung_flag)
174 curdiv->output(nd, no_fill_flag, vs, post_vs, width);
175 else
176 pending_lines = new pending_output_line(nd, no_fill_flag, vs, post_vs,
177 width, 0, pending_lines);
180 void environment::output_pending_lines()
182 while (pending_lines && pending_lines->output()) {
183 pending_output_line *tem = pending_lines;
184 pending_lines = pending_lines->next;
185 delete tem;
189 #ifdef WIDOW_CONTROL
190 void environment::mark_last_line()
192 if (!widow_control || !pending_lines)
193 return;
194 pending_output_line *p;
195 for (p = pending_lines; p->next; p = p->next)
197 if (!p->no_fill)
198 p->last_line = 1;
201 void widow_control_request()
203 int n;
204 if (has_arg() && get_integer(&n))
205 curenv->widow_control = n != 0;
206 else
207 curenv->widow_control = 1;
208 skip_line();
210 #endif /* WIDOW_CONTROL */
212 /* font_size functions */
214 size_range *font_size::size_table = 0;
215 int font_size::nranges = 0;
217 extern "C" {
218 int compare_ranges(const void *p1, const void *p2)
220 return ((size_range *)p1)->min - ((size_range *)p2)->min;
224 void font_size::init_size_table(int *sizes)
226 nranges = 0;
227 while (sizes[nranges*2] != 0)
228 nranges++;
229 assert(nranges > 0);
230 size_table = new size_range[nranges];
231 for (int i = 0; i < nranges; i++) {
232 size_table[i].min = sizes[i*2];
233 size_table[i].max = sizes[i*2 + 1];
235 qsort(size_table, nranges, sizeof(size_range), compare_ranges);
238 font_size::font_size(int sp)
240 for (int i = 0; i < nranges; i++) {
241 if (sp < size_table[i].min) {
242 if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
243 p = size_table[i - 1].max;
244 else
245 p = size_table[i].min;
246 return;
248 if (sp <= size_table[i].max) {
249 p = sp;
250 return;
253 p = size_table[nranges - 1].max;
256 int font_size::to_units()
258 return scale(p, units_per_inch, sizescale*72);
261 // we can't do this in a static constructor because various dictionaries
262 // have to get initialized first
264 void init_environments()
266 curenv = env_table[0] = new environment("0");
269 void tab_character()
271 curenv->tab_char = get_optional_char();
272 skip_line();
275 void leader_character()
277 curenv->leader_char = get_optional_char();
278 skip_line();
281 void environment::add_char(charinfo *ci)
283 int s;
284 node *gc_np = 0;
285 if (interrupted)
287 // don't allow fields in dummy environments
288 else if (ci == field_delimiter_char && !dummy) {
289 if (current_field)
290 wrap_up_field();
291 else
292 start_field();
294 else if (current_field && ci == padding_indicator_char)
295 add_padding();
296 else if (current_tab) {
297 if (tab_contents == 0)
298 tab_contents = new line_start_node;
299 if (ci != hyphen_indicator_char)
300 tab_contents = tab_contents->add_char(ci, this, &tab_width, &s, &gc_np);
301 else
302 tab_contents = tab_contents->add_discretionary_hyphen();
304 else {
305 if (line == 0)
306 start_line();
307 #if 0
308 fprintf(stderr, "current line is\n");
309 line->debug_node_list();
310 #endif
311 if (ci != hyphen_indicator_char)
312 line = line->add_char(ci, this, &width_total, &space_total, &gc_np);
313 else
314 line = line->add_discretionary_hyphen();
316 #if 0
317 fprintf(stderr, "now after we have added character the line is\n");
318 line->debug_node_list();
319 #endif
320 if ((!suppress_push) && gc_np) {
321 if (gc_np && (gc_np->state == 0)) {
322 gc_np->state = construct_state(0);
323 gc_np->push_state = get_diversion_state();
325 else if (line && (line->state == 0)) {
326 line->state = construct_state(0);
327 line->push_state = get_diversion_state();
330 #if 0
331 fprintf(stderr, "now we have possibly added the state the line is\n");
332 line->debug_node_list();
333 #endif
336 node *environment::make_char_node(charinfo *ci)
338 return make_node(ci, this);
341 void environment::add_node(node *n)
343 if (n == 0)
344 return;
345 if (!suppress_push) {
346 if (n->is_special && n->state == NULL)
347 n->state = construct_state(0);
348 n->push_state = get_diversion_state();
351 if (current_tab || current_field)
352 n->freeze_space();
353 if (interrupted) {
354 delete n;
356 else if (current_tab) {
357 n->next = tab_contents;
358 tab_contents = n;
359 tab_width += n->width();
361 else {
362 if (line == 0) {
363 if (discarding && n->discardable()) {
364 // XXX possibly: input_line_start -= n->width();
365 delete n;
366 return;
368 start_line();
370 width_total += n->width();
371 space_total += n->nspaces();
372 n->next = line;
373 line = n;
374 construct_new_line_state(line);
378 void environment::add_hyphen_indicator()
380 if (current_tab || interrupted || current_field
381 || hyphen_indicator_char != 0)
382 return;
383 if (line == 0)
384 start_line();
385 line = line->add_discretionary_hyphen();
388 int environment::get_hyphenation_flags()
390 return hyphenation_flags;
393 int environment::get_hyphen_line_max()
395 return hyphen_line_max;
398 int environment::get_hyphen_line_count()
400 return hyphen_line_count;
403 int environment::get_center_lines()
405 return center_lines;
408 int environment::get_right_justify_lines()
410 return right_justify_lines;
413 void environment::add_italic_correction()
415 if (current_tab) {
416 if (tab_contents)
417 tab_contents = tab_contents->add_italic_correction(&tab_width);
419 else if (line)
420 line = line->add_italic_correction(&width_total);
423 void environment::space_newline()
425 assert(!current_tab && !current_field);
426 if (interrupted)
427 return;
428 hunits x = H0;
429 hunits sw = env_space_width(this);
430 hunits ssw = env_sentence_space_width(this);
431 if (!translate_space_to_dummy) {
432 x = sw;
433 if (node_list_ends_sentence(line) == 1)
434 x += ssw;
436 width_list *w = new width_list(sw, ssw);
437 if (node_list_ends_sentence(line) == 1)
438 w->next = new width_list(sw, ssw);
439 if (line != 0 && line->merge_space(x, sw, ssw)) {
440 width_total += x;
441 return;
443 add_node(new word_space_node(x, get_fill_color(), w));
444 possibly_break_line(0, spread_flag);
445 spread_flag = 0;
448 void environment::space()
450 space(env_space_width(this), env_sentence_space_width(this));
453 void environment::space(hunits space_width, hunits sentence_space_width)
455 if (interrupted)
456 return;
457 if (current_field && padding_indicator_char == 0) {
458 add_padding();
459 return;
461 hunits x = translate_space_to_dummy ? H0 : space_width;
462 node *p = current_tab ? tab_contents : line;
463 hunits *tp = current_tab ? &tab_width : &width_total;
464 if (p && p->nspaces() == 1 && p->width() == x
465 && node_list_ends_sentence(p->next) == 1) {
466 hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
467 if (p->merge_space(xx, space_width, sentence_space_width)) {
468 *tp += xx;
469 return;
472 if (p && p->merge_space(x, space_width, sentence_space_width)) {
473 *tp += x;
474 return;
476 add_node(new word_space_node(x,
477 get_fill_color(),
478 new width_list(space_width,
479 sentence_space_width)));
480 possibly_break_line(0, spread_flag);
481 spread_flag = 0;
484 node *do_underline_special(int);
486 void environment::set_font(symbol nm)
488 if (interrupted)
489 return;
490 if (nm == symbol("P") || nm.is_empty()) {
491 if (family->make_definite(prev_fontno) < 0)
492 return;
493 int tem = fontno;
494 fontno = prev_fontno;
495 prev_fontno = tem;
497 else {
498 prev_fontno = fontno;
499 int n = symbol_fontno(nm);
500 if (n < 0) {
501 n = next_available_font_position();
502 if (!mount_font(n, nm))
503 return;
505 if (family->make_definite(n) < 0)
506 return;
507 fontno = n;
509 if (underline_spaces && fontno != prev_fontno) {
510 if (fontno == get_underline_fontno())
511 add_node(do_underline_special(1));
512 if (prev_fontno == get_underline_fontno())
513 add_node(do_underline_special(0));
517 void environment::set_font(int n)
519 if (interrupted)
520 return;
521 if (is_good_fontno(n)) {
522 prev_fontno = fontno;
523 fontno = n;
525 else
526 warning(WARN_FONT, "bad font number");
529 void environment::set_family(symbol fam)
531 if (interrupted)
532 return;
533 if (fam.is_null() || fam.is_empty()) {
534 if (prev_family->make_definite(fontno) < 0)
535 return;
536 font_family *tem = family;
537 family = prev_family;
538 prev_family = tem;
540 else {
541 font_family *f = lookup_family(fam);
542 if (f->make_definite(fontno) < 0)
543 return;
544 prev_family = family;
545 family = f;
549 void environment::set_size(int n)
551 if (interrupted)
552 return;
553 if (n == 0) {
554 font_size temp = prev_size;
555 prev_size = size;
556 size = temp;
557 int temp2 = prev_requested_size;
558 prev_requested_size = requested_size;
559 requested_size = temp2;
561 else {
562 prev_size = size;
563 size = font_size(n);
564 prev_requested_size = requested_size;
565 requested_size = n;
569 void environment::set_char_height(int n)
571 if (interrupted)
572 return;
573 if (n == requested_size || n <= 0)
574 char_height = 0;
575 else
576 char_height = n;
579 void environment::set_char_slant(int n)
581 if (interrupted)
582 return;
583 char_slant = n;
586 color *environment::get_prev_glyph_color()
588 return prev_glyph_color;
591 color *environment::get_glyph_color()
593 return glyph_color;
596 color *environment::get_prev_fill_color()
598 return prev_fill_color;
601 color *environment::get_fill_color()
603 return fill_color;
606 void environment::set_glyph_color(color *c)
608 if (interrupted)
609 return;
610 curenv->prev_glyph_color = curenv->glyph_color;
611 curenv->glyph_color = c;
614 void environment::set_fill_color(color *c)
616 if (interrupted)
617 return;
618 curenv->prev_fill_color = curenv->fill_color;
619 curenv->fill_color = c;
622 environment::environment(symbol nm)
623 : dummy(0),
624 prev_line_length((units_per_inch*13)/2),
625 line_length((units_per_inch*13)/2),
626 prev_title_length((units_per_inch*13)/2),
627 title_length((units_per_inch*13)/2),
628 prev_size(sizescale*10),
629 size(sizescale*10),
630 requested_size(sizescale*10),
631 prev_requested_size(sizescale*10),
632 char_height(0),
633 char_slant(0),
634 space_size(12),
635 sentence_space_size(12),
636 adjust_mode(ADJUST_BOTH),
637 fill(1),
638 interrupted(0),
639 prev_line_interrupted(0),
640 center_lines(0),
641 right_justify_lines(0),
642 prev_vertical_spacing(points_to_units(12)),
643 vertical_spacing(points_to_units(12)),
644 prev_post_vertical_spacing(0),
645 post_vertical_spacing(0),
646 prev_line_spacing(1),
647 line_spacing(1),
648 prev_indent(0),
649 indent(0),
650 temporary_indent(0),
651 have_temporary_indent(0),
652 underline_lines(0),
653 underline_spaces(0),
654 input_trap_count(0),
655 continued_input_trap(0),
656 line(0),
657 prev_text_length(0),
658 width_total(0),
659 space_total(0),
660 input_line_start(0),
661 line_tabs(0),
662 current_tab(TAB_NONE),
663 leader_node(0),
664 tab_char(0),
665 leader_char(charset_table['.']),
666 current_field(0),
667 discarding(0),
668 spread_flag(0),
669 margin_character_flags(0),
670 margin_character_node(0),
671 margin_character_distance(points_to_units(10)),
672 numbering_nodes(0),
673 number_text_separation(1),
674 line_number_indent(0),
675 line_number_multiple(1),
676 no_number_count(0),
677 hyphenation_flags(1),
678 hyphen_line_count(0),
679 hyphen_line_max(-1),
680 hyphenation_space(H0),
681 hyphenation_margin(H0),
682 composite(0),
683 pending_lines(0),
684 #ifdef WIDOW_CONTROL
685 widow_control(0),
686 #endif /* WIDOW_CONTROL */
687 glyph_color(&default_color),
688 prev_glyph_color(&default_color),
689 fill_color(&default_color),
690 prev_fill_color(&default_color),
691 seen_space(0),
692 seen_eol(0),
693 suppress_next_eol(0),
694 seen_break(0),
695 tabs(units_per_inch/2, TAB_LEFT),
696 name(nm),
697 control_char('.'),
698 no_break_control_char('\''),
699 hyphen_indicator_char(0)
701 prev_family = family = lookup_family(default_family);
702 prev_fontno = fontno = 1;
703 if (!is_good_fontno(1))
704 fatal("font number 1 not a valid font");
705 if (family->make_definite(1) < 0)
706 fatal("invalid default family `%1'", default_family.contents());
707 prev_fontno = fontno;
710 environment::environment(const environment *e)
711 : dummy(1),
712 prev_line_length(e->prev_line_length),
713 line_length(e->line_length),
714 prev_title_length(e->prev_title_length),
715 title_length(e->title_length),
716 prev_size(e->prev_size),
717 size(e->size),
718 requested_size(e->requested_size),
719 prev_requested_size(e->prev_requested_size),
720 char_height(e->char_height),
721 char_slant(e->char_slant),
722 prev_fontno(e->prev_fontno),
723 fontno(e->fontno),
724 prev_family(e->prev_family),
725 family(e->family),
726 space_size(e->space_size),
727 sentence_space_size(e->sentence_space_size),
728 adjust_mode(e->adjust_mode),
729 fill(e->fill),
730 interrupted(0),
731 prev_line_interrupted(0),
732 center_lines(0),
733 right_justify_lines(0),
734 prev_vertical_spacing(e->prev_vertical_spacing),
735 vertical_spacing(e->vertical_spacing),
736 prev_post_vertical_spacing(e->prev_post_vertical_spacing),
737 post_vertical_spacing(e->post_vertical_spacing),
738 prev_line_spacing(e->prev_line_spacing),
739 line_spacing(e->line_spacing),
740 prev_indent(e->prev_indent),
741 indent(e->indent),
742 temporary_indent(0),
743 have_temporary_indent(0),
744 underline_lines(0),
745 underline_spaces(0),
746 input_trap_count(0),
747 continued_input_trap(0),
748 line(0),
749 prev_text_length(e->prev_text_length),
750 width_total(0),
751 space_total(0),
752 input_line_start(0),
753 line_tabs(e->line_tabs),
754 current_tab(TAB_NONE),
755 leader_node(0),
756 tab_char(e->tab_char),
757 leader_char(e->leader_char),
758 current_field(0),
759 discarding(0),
760 spread_flag(0),
761 margin_character_flags(e->margin_character_flags),
762 margin_character_node(e->margin_character_node),
763 margin_character_distance(e->margin_character_distance),
764 numbering_nodes(0),
765 number_text_separation(e->number_text_separation),
766 line_number_indent(e->line_number_indent),
767 line_number_multiple(e->line_number_multiple),
768 no_number_count(e->no_number_count),
769 hyphenation_flags(e->hyphenation_flags),
770 hyphen_line_count(0),
771 hyphen_line_max(e->hyphen_line_max),
772 hyphenation_space(e->hyphenation_space),
773 hyphenation_margin(e->hyphenation_margin),
774 composite(0),
775 pending_lines(0),
776 #ifdef WIDOW_CONTROL
777 widow_control(e->widow_control),
778 #endif /* WIDOW_CONTROL */
779 glyph_color(e->glyph_color),
780 prev_glyph_color(e->prev_glyph_color),
781 fill_color(e->fill_color),
782 prev_fill_color(e->prev_fill_color),
783 seen_space(e->seen_space),
784 seen_eol(e->seen_eol),
785 suppress_next_eol(e->suppress_next_eol),
786 seen_break(e->seen_break),
787 tabs(e->tabs),
788 name(e->name), // so that eg `.if "\n[.ev]"0"' works
789 control_char(e->control_char),
790 no_break_control_char(e->no_break_control_char),
791 hyphen_indicator_char(e->hyphen_indicator_char)
795 void environment::copy(const environment *e)
797 prev_line_length = e->prev_line_length;
798 line_length = e->line_length;
799 prev_title_length = e->prev_title_length;
800 title_length = e->title_length;
801 prev_size = e->prev_size;
802 size = e->size;
803 prev_requested_size = e->prev_requested_size;
804 requested_size = e->requested_size;
805 char_height = e->char_height;
806 char_slant = e->char_slant;
807 space_size = e->space_size;
808 sentence_space_size = e->sentence_space_size;
809 adjust_mode = e->adjust_mode;
810 fill = e->fill;
811 interrupted = 0;
812 prev_line_interrupted = 0;
813 center_lines = 0;
814 right_justify_lines = 0;
815 prev_vertical_spacing = e->prev_vertical_spacing;
816 vertical_spacing = e->vertical_spacing;
817 prev_post_vertical_spacing = e->prev_post_vertical_spacing,
818 post_vertical_spacing = e->post_vertical_spacing,
819 prev_line_spacing = e->prev_line_spacing;
820 line_spacing = e->line_spacing;
821 prev_indent = e->prev_indent;
822 indent = e->indent;
823 have_temporary_indent = 0;
824 temporary_indent = 0;
825 underline_lines = 0;
826 underline_spaces = 0;
827 input_trap_count = 0;
828 continued_input_trap = 0;
829 prev_text_length = e->prev_text_length;
830 width_total = 0;
831 space_total = 0;
832 input_line_start = 0;
833 control_char = e->control_char;
834 no_break_control_char = e->no_break_control_char;
835 hyphen_indicator_char = e->hyphen_indicator_char;
836 spread_flag = 0;
837 line = 0;
838 pending_lines = 0;
839 discarding = 0;
840 tabs = e->tabs;
841 line_tabs = e->line_tabs;
842 current_tab = TAB_NONE;
843 current_field = 0;
844 margin_character_flags = e->margin_character_flags;
845 if (e->margin_character_node)
846 margin_character_node = e->margin_character_node->copy();
847 margin_character_distance = e->margin_character_distance;
848 numbering_nodes = 0;
849 number_text_separation = e->number_text_separation;
850 line_number_multiple = e->line_number_multiple;
851 line_number_indent = e->line_number_indent;
852 no_number_count = e->no_number_count;
853 tab_char = e->tab_char;
854 leader_char = e->leader_char;
855 hyphenation_flags = e->hyphenation_flags;
856 fontno = e->fontno;
857 prev_fontno = e->prev_fontno;
858 dummy = e->dummy;
859 family = e->family;
860 prev_family = e->prev_family;
861 leader_node = 0;
862 #ifdef WIDOW_CONTROL
863 widow_control = e->widow_control;
864 #endif /* WIDOW_CONTROL */
865 hyphen_line_max = e->hyphen_line_max;
866 hyphen_line_count = 0;
867 hyphenation_space = e->hyphenation_space;
868 hyphenation_margin = e->hyphenation_margin;
869 composite = 0;
870 glyph_color= e->glyph_color;
871 prev_glyph_color = e->prev_glyph_color;
872 fill_color = e->fill_color;
873 prev_fill_color = e->prev_fill_color;
876 environment::~environment()
878 delete leader_node;
879 delete_node_list(line);
880 delete_node_list(numbering_nodes);
883 hunits environment::get_input_line_position()
885 hunits n;
886 if (line == 0)
887 n = -input_line_start;
888 else
889 n = width_total - input_line_start;
890 if (current_tab)
891 n += tab_width;
892 return n;
895 void environment::set_input_line_position(hunits n)
897 input_line_start = line == 0 ? -n : width_total - n;
898 if (current_tab)
899 input_line_start += tab_width;
902 hunits environment::get_line_length()
904 return line_length;
907 hunits environment::get_saved_line_length()
909 if (line)
910 return target_text_length + saved_indent;
911 else
912 return line_length;
915 vunits environment::get_vertical_spacing()
917 return vertical_spacing;
920 vunits environment::get_post_vertical_spacing()
922 return post_vertical_spacing;
925 int environment::get_line_spacing()
927 return line_spacing;
930 vunits environment::total_post_vertical_spacing()
932 vunits tem(post_vertical_spacing);
933 if (line_spacing > 1)
934 tem += (line_spacing - 1)*vertical_spacing;
935 return tem;
938 int environment::get_bold()
940 return get_bold_fontno(fontno);
943 hunits environment::get_digit_width()
945 return env_digit_width(this);
948 int environment::get_adjust_mode()
950 return adjust_mode;
953 int environment::get_fill()
955 return fill;
958 hunits environment::get_indent()
960 return indent;
963 hunits environment::get_saved_indent()
965 if (line)
966 return saved_indent;
967 else if (have_temporary_indent)
968 return temporary_indent;
969 else
970 return indent;
973 hunits environment::get_temporary_indent()
975 return temporary_indent;
978 hunits environment::get_title_length()
980 return title_length;
983 node *environment::get_prev_char()
985 for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
986 node *last = n->last_char_node();
987 if (last)
988 return last;
990 return 0;
993 hunits environment::get_prev_char_width()
995 node *last = get_prev_char();
996 if (!last)
997 return H0;
998 return last->width();
1001 hunits environment::get_prev_char_skew()
1003 node *last = get_prev_char();
1004 if (!last)
1005 return H0;
1006 return last->skew();
1009 vunits environment::get_prev_char_height()
1011 node *last = get_prev_char();
1012 if (!last)
1013 return V0;
1014 vunits min, max;
1015 last->vertical_extent(&min, &max);
1016 return -min;
1019 vunits environment::get_prev_char_depth()
1021 node *last = get_prev_char();
1022 if (!last)
1023 return V0;
1024 vunits min, max;
1025 last->vertical_extent(&min, &max);
1026 return max;
1029 hunits environment::get_text_length()
1031 hunits n = line == 0 ? H0 : width_total;
1032 if (current_tab)
1033 n += tab_width;
1034 return n;
1037 hunits environment::get_prev_text_length()
1039 return prev_text_length;
1043 static int sb_reg_contents = 0;
1044 static int st_reg_contents = 0;
1045 static int ct_reg_contents = 0;
1046 static int rsb_reg_contents = 0;
1047 static int rst_reg_contents = 0;
1048 static int skw_reg_contents = 0;
1049 static int ssc_reg_contents = 0;
1051 void environment::width_registers()
1053 // this is used to implement \w; it sets the st, sb, ct registers
1054 vunits min = 0, max = 0, cur = 0;
1055 int character_type = 0;
1056 ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
1057 skw_reg_contents = line ? line->skew().to_units() : 0;
1058 line = reverse_node_list(line);
1059 vunits real_min = V0;
1060 vunits real_max = V0;
1061 vunits v1, v2;
1062 for (node *tem = line; tem; tem = tem->next) {
1063 tem->vertical_extent(&v1, &v2);
1064 v1 += cur;
1065 if (v1 < real_min)
1066 real_min = v1;
1067 v2 += cur;
1068 if (v2 > real_max)
1069 real_max = v2;
1070 if ((cur += tem->vertical_width()) < min)
1071 min = cur;
1072 else if (cur > max)
1073 max = cur;
1074 character_type |= tem->character_type();
1076 line = reverse_node_list(line);
1077 st_reg_contents = -min.to_units();
1078 sb_reg_contents = -max.to_units();
1079 rst_reg_contents = -real_min.to_units();
1080 rsb_reg_contents = -real_max.to_units();
1081 ct_reg_contents = character_type;
1084 node *environment::extract_output_line()
1086 if (current_tab)
1087 wrap_up_tab();
1088 node *n = line;
1089 line = 0;
1090 return n;
1093 /* environment related requests */
1095 void environment_switch()
1097 int pop = 0; // 1 means pop, 2 means pop but no error message on underflow
1098 if (curenv->is_dummy())
1099 error("can't switch environments when current environment is dummy");
1100 else if (!has_arg())
1101 pop = 1;
1102 else {
1103 symbol nm;
1104 if (!tok.delimiter()) {
1105 // It looks like a number.
1106 int n;
1107 if (get_integer(&n)) {
1108 if (n >= 0 && n < NENVIRONMENTS) {
1109 env_stack = new env_list(curenv, env_stack);
1110 if (env_table[n] == 0)
1111 env_table[n] = new environment(i_to_a(n));
1112 curenv = env_table[n];
1114 else
1115 nm = i_to_a(n);
1117 else
1118 pop = 2;
1120 else {
1121 nm = get_long_name(1);
1122 if (nm.is_null())
1123 pop = 2;
1125 if (!nm.is_null()) {
1126 environment *e = (environment *)env_dictionary.lookup(nm);
1127 if (!e) {
1128 e = new environment(nm);
1129 (void)env_dictionary.lookup(nm, e);
1131 env_stack = new env_list(curenv, env_stack);
1132 curenv = e;
1135 if (pop) {
1136 if (env_stack == 0) {
1137 if (pop == 1)
1138 error("environment stack underflow");
1140 else {
1141 int seen_space = curenv->seen_space;
1142 int seen_eol = curenv->seen_eol;
1143 int suppress_next_eol = curenv->suppress_next_eol;
1144 curenv = env_stack->env;
1145 curenv->seen_space = seen_space;
1146 curenv->seen_eol = seen_eol;
1147 curenv->suppress_next_eol = suppress_next_eol;
1148 env_list *tem = env_stack;
1149 env_stack = env_stack->next;
1150 delete tem;
1153 skip_line();
1156 void environment_copy()
1158 symbol nm;
1159 environment *e=0;
1160 tok.skip();
1161 if (!tok.delimiter()) {
1162 // It looks like a number.
1163 int n;
1164 if (get_integer(&n)) {
1165 if (n >= 0 && n < NENVIRONMENTS)
1166 e = env_table[n];
1167 else
1168 nm = i_to_a(n);
1171 else
1172 nm = get_long_name(1);
1173 if (!e && !nm.is_null())
1174 e = (environment *)env_dictionary.lookup(nm);
1175 if (e == 0) {
1176 error("No environment to copy from");
1177 return;
1179 else
1180 curenv->copy(e);
1181 skip_line();
1184 void fill_color_change()
1186 symbol s = get_name();
1187 if (s.is_null())
1188 curenv->set_fill_color(curenv->get_prev_fill_color());
1189 else
1190 do_fill_color(s);
1191 skip_line();
1194 void glyph_color_change()
1196 symbol s = get_name();
1197 if (s.is_null())
1198 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1199 else
1200 do_glyph_color(s);
1201 skip_line();
1204 static symbol P_symbol("P");
1206 void font_change()
1208 symbol s = get_name();
1209 int is_number = 1;
1210 if (s.is_null() || s == P_symbol) {
1211 s = P_symbol;
1212 is_number = 0;
1214 else {
1215 for (const char *p = s.contents(); p != 0 && *p != 0; p++)
1216 if (!csdigit(*p)) {
1217 is_number = 0;
1218 break;
1221 if (is_number)
1222 curenv->set_font(atoi(s.contents()));
1223 else
1224 curenv->set_font(s);
1225 skip_line();
1228 void family_change()
1230 symbol s = get_name();
1231 curenv->set_family(s);
1232 skip_line();
1235 void point_size()
1237 int n;
1238 if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) {
1239 if (n <= 0)
1240 n = 1;
1241 curenv->set_size(n);
1243 else
1244 curenv->set_size(0);
1245 skip_line();
1248 void override_sizes()
1250 int n = 16;
1251 int *sizes = new int[n];
1252 int i = 0;
1253 char *buf = read_string();
1254 if (!buf)
1255 return;
1256 char *p = strtok(buf, " \t");
1257 for (;;) {
1258 if (!p)
1259 break;
1260 int lower, upper;
1261 switch (sscanf(p, "%d-%d", &lower, &upper)) {
1262 case 1:
1263 upper = lower;
1264 // fall through
1265 case 2:
1266 if (lower <= upper && lower >= 0)
1267 break;
1268 // fall through
1269 default:
1270 warning(WARN_RANGE, "bad size range `%1'", p);
1271 return;
1273 if (i + 2 > n) {
1274 int *old_sizes = sizes;
1275 sizes = new int[n*2];
1276 memcpy(sizes, old_sizes, n*sizeof(int));
1277 n *= 2;
1278 a_delete old_sizes;
1280 sizes[i++] = lower;
1281 if (lower == 0)
1282 break;
1283 sizes[i++] = upper;
1284 p = strtok(0, " \t");
1286 font_size::init_size_table(sizes);
1289 void space_size()
1291 int n;
1292 if (get_integer(&n)) {
1293 curenv->space_size = n;
1294 if (has_arg() && get_integer(&n))
1295 curenv->sentence_space_size = n;
1296 else
1297 curenv->sentence_space_size = curenv->space_size;
1299 skip_line();
1302 void fill()
1304 while (!tok.newline() && !tok.eof())
1305 tok.next();
1306 if (break_flag)
1307 curenv->do_break();
1308 curenv->fill = 1;
1309 tok.next();
1312 void no_fill()
1314 while (!tok.newline() && !tok.eof())
1315 tok.next();
1316 if (break_flag)
1317 curenv->do_break();
1318 curenv->fill = 0;
1319 curenv->suppress_next_eol = 1;
1320 tok.next();
1323 void center()
1325 int n;
1326 if (!has_arg() || !get_integer(&n))
1327 n = 1;
1328 else if (n < 0)
1329 n = 0;
1330 while (!tok.newline() && !tok.eof())
1331 tok.next();
1332 if (break_flag)
1333 curenv->do_break();
1334 curenv->right_justify_lines = 0;
1335 curenv->center_lines = n;
1336 curdiv->modified_tag.incl(MTSM_CE);
1337 tok.next();
1340 void right_justify()
1342 int n;
1343 if (!has_arg() || !get_integer(&n))
1344 n = 1;
1345 else if (n < 0)
1346 n = 0;
1347 while (!tok.newline() && !tok.eof())
1348 tok.next();
1349 if (break_flag)
1350 curenv->do_break();
1351 curenv->center_lines = 0;
1352 curenv->right_justify_lines = n;
1353 curdiv->modified_tag.incl(MTSM_RJ);
1354 tok.next();
1357 void line_length()
1359 hunits temp;
1360 if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) {
1361 if (temp < H0) {
1362 warning(WARN_RANGE, "bad line length %1u", temp.to_units());
1363 temp = H0;
1366 else
1367 temp = curenv->prev_line_length;
1368 curenv->prev_line_length = curenv->line_length;
1369 curenv->line_length = temp;
1370 curdiv->modified_tag.incl(MTSM_LL);
1371 skip_line();
1374 void title_length()
1376 hunits temp;
1377 if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) {
1378 if (temp < H0) {
1379 warning(WARN_RANGE, "bad title length %1u", temp.to_units());
1380 temp = H0;
1383 else
1384 temp = curenv->prev_title_length;
1385 curenv->prev_title_length = curenv->title_length;
1386 curenv->title_length = temp;
1387 skip_line();
1390 void vertical_spacing()
1392 vunits temp;
1393 if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) {
1394 if (temp < V0) {
1395 warning(WARN_RANGE, "vertical spacing must not be negative");
1396 temp = vresolution;
1399 else
1400 temp = curenv->prev_vertical_spacing;
1401 curenv->prev_vertical_spacing = curenv->vertical_spacing;
1402 curenv->vertical_spacing = temp;
1403 skip_line();
1406 void post_vertical_spacing()
1408 vunits temp;
1409 if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) {
1410 if (temp < V0) {
1411 warning(WARN_RANGE,
1412 "post vertical spacing must be greater than or equal to 0");
1413 temp = V0;
1416 else
1417 temp = curenv->prev_post_vertical_spacing;
1418 curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
1419 curenv->post_vertical_spacing = temp;
1420 skip_line();
1423 void line_spacing()
1425 int temp;
1426 if (has_arg() && get_integer(&temp)) {
1427 if (temp < 1) {
1428 warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
1429 temp = 1;
1432 else
1433 temp = curenv->prev_line_spacing;
1434 curenv->prev_line_spacing = curenv->line_spacing;
1435 curenv->line_spacing = temp;
1436 skip_line();
1439 void indent()
1441 hunits temp;
1442 if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) {
1443 if (temp < H0) {
1444 warning(WARN_RANGE, "indent cannot be negative");
1445 temp = H0;
1448 else
1449 temp = curenv->prev_indent;
1450 while (!tok.newline() && !tok.eof())
1451 tok.next();
1452 if (break_flag)
1453 curenv->do_break();
1454 curenv->have_temporary_indent = 0;
1455 curenv->prev_indent = curenv->indent;
1456 curenv->indent = temp;
1457 curdiv->modified_tag.incl(MTSM_IN);
1458 tok.next();
1461 void temporary_indent()
1463 int err = 0;
1464 hunits temp;
1465 if (!get_hunits(&temp, 'm', curenv->get_indent()))
1466 err = 1;
1467 while (!tok.newline() && !tok.eof())
1468 tok.next();
1469 if (break_flag)
1470 curenv->do_break();
1471 if (temp < H0) {
1472 warning(WARN_RANGE, "total indent cannot be negative");
1473 temp = H0;
1475 if (!err) {
1476 curenv->temporary_indent = temp;
1477 curenv->have_temporary_indent = 1;
1478 curdiv->modified_tag.incl(MTSM_TI);
1480 tok.next();
1483 node *do_underline_special(int underline_spaces)
1485 macro m;
1486 m.append_str("x u ");
1487 m.append(underline_spaces + '0');
1488 return new special_node(m, 1);
1491 void do_underline(int underline_spaces)
1493 int n;
1494 if (!has_arg() || !get_integer(&n))
1495 n = 1;
1496 if (n <= 0) {
1497 if (curenv->underline_lines > 0) {
1498 curenv->prev_fontno = curenv->fontno;
1499 curenv->fontno = curenv->pre_underline_fontno;
1500 if (underline_spaces) {
1501 curenv->underline_spaces = 0;
1502 curenv->add_node(do_underline_special(0));
1505 curenv->underline_lines = 0;
1507 else {
1508 curenv->underline_lines = n;
1509 curenv->pre_underline_fontno = curenv->fontno;
1510 curenv->fontno = get_underline_fontno();
1511 if (underline_spaces) {
1512 curenv->underline_spaces = 1;
1513 curenv->add_node(do_underline_special(1));
1516 skip_line();
1519 void continuous_underline()
1521 do_underline(1);
1524 void underline()
1526 do_underline(0);
1529 void control_char()
1531 curenv->control_char = '.';
1532 if (has_arg()) {
1533 if (tok.ch() == 0)
1534 error("bad control character");
1535 else
1536 curenv->control_char = tok.ch();
1538 skip_line();
1541 void no_break_control_char()
1543 curenv->no_break_control_char = '\'';
1544 if (has_arg()) {
1545 if (tok.ch() == 0)
1546 error("bad control character");
1547 else
1548 curenv->no_break_control_char = tok.ch();
1550 skip_line();
1553 void margin_character()
1555 while (tok.space())
1556 tok.next();
1557 charinfo *ci = tok.get_char();
1558 if (ci) {
1559 // Call tok.next() only after making the node so that
1560 // .mc \s+9\(br\s0 works.
1561 node *nd = curenv->make_char_node(ci);
1562 tok.next();
1563 if (nd) {
1564 delete curenv->margin_character_node;
1565 curenv->margin_character_node = nd;
1566 curenv->margin_character_flags = MARGIN_CHARACTER_ON
1567 | MARGIN_CHARACTER_NEXT;
1568 hunits d;
1569 if (has_arg() && get_hunits(&d, 'm'))
1570 curenv->margin_character_distance = d;
1573 else {
1574 check_missing_character();
1575 curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON;
1576 if (curenv->margin_character_flags == 0) {
1577 delete curenv->margin_character_node;
1578 curenv->margin_character_node = 0;
1581 skip_line();
1584 void number_lines()
1586 delete_node_list(curenv->numbering_nodes);
1587 curenv->numbering_nodes = 0;
1588 if (has_arg()) {
1589 node *nd = 0;
1590 for (int i = '9'; i >= '0'; i--) {
1591 node *tem = make_node(charset_table[i], curenv);
1592 if (!tem) {
1593 skip_line();
1594 return;
1596 tem->next = nd;
1597 nd = tem;
1599 curenv->numbering_nodes = nd;
1600 curenv->line_number_digit_width = env_digit_width(curenv);
1601 int n;
1602 if (!tok.delimiter()) {
1603 if (get_integer(&n, next_line_number)) {
1604 next_line_number = n;
1605 if (next_line_number < 0) {
1606 warning(WARN_RANGE, "negative line number");
1607 next_line_number = 0;
1611 else
1612 while (!tok.space() && !tok.newline() && !tok.eof())
1613 tok.next();
1614 if (has_arg()) {
1615 if (!tok.delimiter()) {
1616 if (get_integer(&n)) {
1617 if (n <= 0) {
1618 warning(WARN_RANGE, "negative or zero line number multiple");
1620 else
1621 curenv->line_number_multiple = n;
1624 else
1625 while (!tok.space() && !tok.newline() && !tok.eof())
1626 tok.next();
1627 if (has_arg()) {
1628 if (!tok.delimiter()) {
1629 if (get_integer(&n))
1630 curenv->number_text_separation = n;
1632 else
1633 while (!tok.space() && !tok.newline() && !tok.eof())
1634 tok.next();
1635 if (has_arg() && !tok.delimiter() && get_integer(&n))
1636 curenv->line_number_indent = n;
1640 skip_line();
1643 void no_number()
1645 int n;
1646 if (has_arg() && get_integer(&n))
1647 curenv->no_number_count = n > 0 ? n : 0;
1648 else
1649 curenv->no_number_count = 1;
1650 skip_line();
1653 void no_hyphenate()
1655 curenv->hyphenation_flags = 0;
1656 skip_line();
1659 void hyphenate_request()
1661 int n;
1662 if (has_arg() && get_integer(&n))
1663 curenv->hyphenation_flags = n;
1664 else
1665 curenv->hyphenation_flags = 1;
1666 skip_line();
1669 void hyphen_char()
1671 curenv->hyphen_indicator_char = get_optional_char();
1672 skip_line();
1675 void hyphen_line_max_request()
1677 int n;
1678 if (has_arg() && get_integer(&n))
1679 curenv->hyphen_line_max = n;
1680 else
1681 curenv->hyphen_line_max = -1;
1682 skip_line();
1685 void environment::interrupt()
1687 if (!dummy) {
1688 add_node(new transparent_dummy_node);
1689 interrupted = 1;
1693 void environment::newline()
1695 int was_centered = 0;
1696 if (underline_lines > 0) {
1697 if (--underline_lines == 0) {
1698 prev_fontno = fontno;
1699 fontno = pre_underline_fontno;
1700 if (underline_spaces) {
1701 underline_spaces = 0;
1702 add_node(do_underline_special(0));
1706 if (current_field)
1707 wrap_up_field();
1708 if (current_tab)
1709 wrap_up_tab();
1710 // strip trailing spaces
1711 while (line != 0 && line->discardable()) {
1712 width_total -= line->width();
1713 space_total -= line->nspaces();
1714 node *tem = line;
1715 line = line->next;
1716 delete tem;
1718 node *to_be_output = 0;
1719 hunits to_be_output_width;
1720 prev_line_interrupted = 0;
1721 if (dummy)
1722 space_newline();
1723 else if (interrupted) {
1724 interrupted = 0;
1725 // see environment::final_break
1726 prev_line_interrupted = exit_started ? 2 : 1;
1728 else if (center_lines > 0) {
1729 --center_lines;
1730 hunits x = target_text_length - width_total;
1731 if (x > H0)
1732 saved_indent += x/2;
1733 to_be_output = line;
1734 was_centered = 1;
1735 to_be_output_width = width_total;
1736 line = 0;
1738 else if (right_justify_lines > 0) {
1739 --right_justify_lines;
1740 hunits x = target_text_length - width_total;
1741 if (x > H0)
1742 saved_indent += x;
1743 to_be_output = line;
1744 to_be_output_width = width_total;
1745 line = 0;
1747 else if (fill)
1748 space_newline();
1749 else {
1750 to_be_output = line;
1751 to_be_output_width = width_total;
1752 line = 0;
1754 input_line_start = line == 0 ? H0 : width_total;
1755 if (to_be_output) {
1756 if (is_html && !fill) {
1757 curdiv->modified_tag.incl(MTSM_EOL);
1758 if (suppress_next_eol)
1759 suppress_next_eol = 0;
1760 else
1761 seen_eol = 1;
1764 output_line(to_be_output, to_be_output_width, was_centered);
1765 hyphen_line_count = 0;
1767 if (input_trap_count > 0) {
1768 if (!(continued_input_trap && prev_line_interrupted))
1769 if (--input_trap_count == 0)
1770 spring_trap(input_trap);
1774 void environment::output_line(node *n, hunits width, int was_centered)
1776 prev_text_length = width;
1777 if (margin_character_flags) {
1778 hunits d = line_length + margin_character_distance - saved_indent - width;
1779 if (d > 0) {
1780 n = new hmotion_node(d, get_fill_color(), n);
1781 width += d;
1783 margin_character_flags &= ~MARGIN_CHARACTER_NEXT;
1784 node *tem;
1785 if (!margin_character_flags) {
1786 tem = margin_character_node;
1787 margin_character_node = 0;
1789 else
1790 tem = margin_character_node->copy();
1791 tem->next = n;
1792 n = tem;
1793 width += tem->width();
1795 node *nn = 0;
1796 while (n != 0) {
1797 node *tem = n->next;
1798 n->next = nn;
1799 nn = n;
1800 n = tem;
1802 if (!saved_indent.is_zero())
1803 nn = new hmotion_node(saved_indent, get_fill_color(), nn);
1804 width += saved_indent;
1805 if (no_number_count > 0)
1806 --no_number_count;
1807 else if (numbering_nodes) {
1808 hunits w = (line_number_digit_width
1809 *(3+line_number_indent+number_text_separation));
1810 if (next_line_number % line_number_multiple != 0)
1811 nn = new hmotion_node(w, get_fill_color(), nn);
1812 else {
1813 hunits x = w;
1814 nn = new hmotion_node(number_text_separation * line_number_digit_width,
1815 get_fill_color(), nn);
1816 x -= number_text_separation*line_number_digit_width;
1817 char buf[30];
1818 sprintf(buf, "%3d", next_line_number);
1819 for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
1820 node *gn = numbering_nodes;
1821 for (int count = *p - '0'; count > 0; count--)
1822 gn = gn->next;
1823 gn = gn->copy();
1824 x -= gn->width();
1825 gn->next = nn;
1826 nn = gn;
1828 nn = new hmotion_node(x, get_fill_color(), nn);
1830 width += w;
1831 ++next_line_number;
1833 output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width,
1834 was_centered);
1837 void environment::start_line()
1839 assert(line == 0);
1840 discarding = 0;
1841 line = new line_start_node;
1842 if (have_temporary_indent) {
1843 saved_indent = temporary_indent;
1844 have_temporary_indent = 0;
1846 else
1847 saved_indent = indent;
1848 target_text_length = line_length - saved_indent;
1849 width_total = H0;
1850 space_total = 0;
1853 hunits environment::get_hyphenation_space()
1855 return hyphenation_space;
1858 void hyphenation_space_request()
1860 hunits n;
1861 if (get_hunits(&n, 'm')) {
1862 if (n < H0) {
1863 warning(WARN_RANGE, "hyphenation space cannot be negative");
1864 n = H0;
1866 curenv->hyphenation_space = n;
1868 skip_line();
1871 hunits environment::get_hyphenation_margin()
1873 return hyphenation_margin;
1876 void hyphenation_margin_request()
1878 hunits n;
1879 if (get_hunits(&n, 'm')) {
1880 if (n < H0) {
1881 warning(WARN_RANGE, "hyphenation margin cannot be negative");
1882 n = H0;
1884 curenv->hyphenation_margin = n;
1886 skip_line();
1889 breakpoint *environment::choose_breakpoint()
1891 hunits x = width_total;
1892 int s = space_total;
1893 node *n = line;
1894 breakpoint *best_bp = 0; // the best breakpoint so far
1895 int best_bp_fits = 0;
1896 while (n != 0) {
1897 x -= n->width();
1898 s -= n->nspaces();
1899 breakpoint *bp = n->get_breakpoints(x, s);
1900 while (bp != 0) {
1901 if (bp->width <= target_text_length) {
1902 if (!bp->hyphenated) {
1903 breakpoint *tem = bp->next;
1904 bp->next = 0;
1905 while (tem != 0) {
1906 breakpoint *tem1 = tem;
1907 tem = tem->next;
1908 delete tem1;
1910 if (best_bp_fits
1911 // Decide whether to use the hyphenated breakpoint.
1912 && (hyphen_line_max < 0
1913 // Only choose the hyphenated breakpoint if it would not
1914 // exceed the maximum number of consecutive hyphenated
1915 // lines.
1916 || hyphen_line_count + 1 <= hyphen_line_max)
1917 && !(adjust_mode == ADJUST_BOTH
1918 // Don't choose the hyphenated breakpoint if the line
1919 // can be justified by adding no more than
1920 // hyphenation_space to any word space.
1921 ? (bp->nspaces > 0
1922 && (((target_text_length - bp->width
1923 + (bp->nspaces - 1)*hresolution)/bp->nspaces)
1924 <= hyphenation_space))
1925 // Don't choose the hyphenated breakpoint if the line
1926 // is no more than hyphenation_margin short.
1927 : target_text_length - bp->width <= hyphenation_margin)) {
1928 delete bp;
1929 return best_bp;
1931 if (best_bp)
1932 delete best_bp;
1933 return bp;
1935 else {
1936 if ((adjust_mode == ADJUST_BOTH
1937 ? hyphenation_space == H0
1938 : hyphenation_margin == H0)
1939 && (hyphen_line_max < 0
1940 || hyphen_line_count + 1 <= hyphen_line_max)) {
1941 // No need to consider a non-hyphenated breakpoint.
1942 if (best_bp)
1943 delete best_bp;
1944 breakpoint *tem = bp->next;
1945 bp->next = 0;
1946 while (tem != 0) {
1947 breakpoint *tem1 = tem;
1948 tem = tem->next;
1949 delete tem1;
1951 return bp;
1953 // It fits but it's hyphenated.
1954 if (!best_bp_fits) {
1955 if (best_bp)
1956 delete best_bp;
1957 best_bp = bp;
1958 bp = bp->next;
1959 best_bp_fits = 1;
1961 else {
1962 breakpoint *tem = bp;
1963 bp = bp->next;
1964 delete tem;
1968 else {
1969 if (best_bp)
1970 delete best_bp;
1971 best_bp = bp;
1972 bp = bp->next;
1975 n = n->next;
1977 if (best_bp) {
1978 if (!best_bp_fits)
1979 output_warning(WARN_BREAK, "can't break line");
1980 return best_bp;
1982 return 0;
1985 void environment::hyphenate_line(int start_here)
1987 assert(line != 0);
1988 hyphenation_type prev_type = line->get_hyphenation_type();
1989 node **startp;
1990 if (start_here)
1991 startp = &line;
1992 else
1993 for (startp = &line->next; *startp != 0; startp = &(*startp)->next) {
1994 hyphenation_type this_type = (*startp)->get_hyphenation_type();
1995 if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
1996 break;
1997 prev_type = this_type;
1999 if (*startp == 0)
2000 return;
2001 node *tem = *startp;
2002 do {
2003 tem = tem->next;
2004 } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
2005 int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
2006 node *end = tem;
2007 hyphen_list *sl = 0;
2008 tem = *startp;
2009 node *forward = 0;
2010 int i = 0;
2011 while (tem != end) {
2012 sl = tem->get_hyphen_list(sl, &i);
2013 node *tem1 = tem;
2014 tem = tem->next;
2015 tem1->next = forward;
2016 forward = tem1;
2018 if (!inhibit) {
2019 // this is for characters like hyphen and emdash
2020 int prev_code = 0;
2021 for (hyphen_list *h = sl; h; h = h->next) {
2022 h->breakable = (prev_code != 0
2023 && h->next != 0
2024 && h->next->hyphenation_code != 0);
2025 prev_code = h->hyphenation_code;
2028 if (hyphenation_flags != 0
2029 && !inhibit
2030 // this may not be right if we have extra space on this line
2031 && !((hyphenation_flags & HYPHEN_LAST_LINE)
2032 && (curdiv->distance_to_next_trap()
2033 <= vertical_spacing + total_post_vertical_spacing()))
2034 && i >= 4)
2035 hyphenate(sl, hyphenation_flags);
2036 while (forward != 0) {
2037 node *tem1 = forward;
2038 forward = forward->next;
2039 tem1->next = 0;
2040 tem = tem1->add_self(tem, &sl);
2042 *startp = tem;
2045 static node *node_list_reverse(node *n)
2047 node *res = 0;
2048 while (n) {
2049 node *tem = n;
2050 n = n->next;
2051 tem->next = res;
2052 res = tem;
2054 return res;
2057 static void distribute_space(node *n, int nspaces, hunits desired_space,
2058 int force_reverse = 0)
2060 static int reverse = 0;
2061 if (force_reverse || reverse)
2062 n = node_list_reverse(n);
2063 if (!force_reverse && nspaces > 0 && spread_limit >= 0
2064 && desired_space.to_units() > 0) {
2065 hunits em = curenv->get_size();
2066 double Ems = (double)desired_space.to_units() / nspaces
2067 / (em.is_zero() ? hresolution : em.to_units());
2068 if (Ems > spread_limit)
2069 output_warning(WARN_BREAK, "spreading %1m per space", Ems);
2071 for (node *tem = n; tem; tem = tem->next)
2072 tem->spread_space(&nspaces, &desired_space);
2073 if (force_reverse || reverse)
2074 (void)node_list_reverse(n);
2075 if (!force_reverse)
2076 reverse = !reverse;
2077 assert(desired_space.is_zero() && nspaces == 0);
2080 void environment::possibly_break_line(int start_here, int forced)
2082 int was_centered = center_lines > 0;
2083 if (!fill || current_tab || current_field || dummy)
2084 return;
2085 while (line != 0
2086 && (forced
2087 // When a macro follows a paragraph in fill mode, the
2088 // current line should not be empty.
2089 || (width_total - line->width()) > target_text_length)) {
2090 hyphenate_line(start_here);
2091 breakpoint *bp = choose_breakpoint();
2092 if (bp == 0)
2093 // we'll find one eventually
2094 return;
2095 node *pre, *post;
2096 node **ndp = &line;
2097 while (*ndp != bp->nd)
2098 ndp = &(*ndp)->next;
2099 bp->nd->split(bp->index, &pre, &post);
2100 *ndp = post;
2101 hunits extra_space_width = H0;
2102 switch(adjust_mode) {
2103 case ADJUST_BOTH:
2104 if (bp->nspaces != 0)
2105 extra_space_width = target_text_length - bp->width;
2106 else if (bp->width > 0 && target_text_length > 0
2107 && target_text_length > bp->width)
2108 output_warning(WARN_BREAK, "cannot adjust line");
2109 break;
2110 case ADJUST_CENTER:
2111 saved_indent += (target_text_length - bp->width)/2;
2112 was_centered = 1;
2113 break;
2114 case ADJUST_RIGHT:
2115 saved_indent += target_text_length - bp->width;
2116 break;
2118 distribute_space(pre, bp->nspaces, extra_space_width);
2119 hunits output_width = bp->width + extra_space_width;
2120 input_line_start -= output_width;
2121 if (bp->hyphenated)
2122 hyphen_line_count++;
2123 else
2124 hyphen_line_count = 0;
2125 delete bp;
2126 space_total = 0;
2127 width_total = 0;
2128 node *first_non_discardable = 0;
2129 node *tem;
2130 for (tem = line; tem != 0; tem = tem->next)
2131 if (!tem->discardable())
2132 first_non_discardable = tem;
2133 node *to_be_discarded;
2134 if (first_non_discardable) {
2135 to_be_discarded = first_non_discardable->next;
2136 first_non_discardable->next = 0;
2137 for (tem = line; tem != 0; tem = tem->next) {
2138 width_total += tem->width();
2139 space_total += tem->nspaces();
2141 discarding = 0;
2143 else {
2144 discarding = 1;
2145 to_be_discarded = line;
2146 line = 0;
2148 // Do output_line() here so that line will be 0 iff the
2149 // the environment will be empty.
2150 output_line(pre, output_width, was_centered);
2151 while (to_be_discarded != 0) {
2152 tem = to_be_discarded;
2153 to_be_discarded = to_be_discarded->next;
2154 input_line_start -= tem->width();
2155 delete tem;
2157 if (line != 0) {
2158 if (have_temporary_indent) {
2159 saved_indent = temporary_indent;
2160 have_temporary_indent = 0;
2162 else
2163 saved_indent = indent;
2164 target_text_length = line_length - saved_indent;
2170 Do the break at the end of input after the end macro (if any).
2172 Unix troff behaves as follows: if the last line is
2174 foo bar\c
2176 it will output foo on the current page, and bar on the next page;
2177 if the last line is
2179 foo\c
2183 foo bar
2185 everything will be output on the current page. This behaviour must be
2186 considered a bug.
2188 The problem is that some macro packages rely on this. For example,
2189 the ATK macros have an end macro that emits \c if it needs to print a
2190 table of contents but doesn't do a 'bp in the end macro; instead the
2191 'bp is done in the bottom of page trap. This works with Unix troff,
2192 provided that the current environment is not empty at the end of the
2193 input file.
2195 The following will make macro packages that do that sort of thing work
2196 even if the current environment is empty at the end of the input file.
2197 If the last input line used \c and this line occurred in the end macro,
2198 then we'll force everything out on the current page, but we'll make
2199 sure that the environment isn't empty so that we won't exit at the
2200 bottom of this page.
2203 void environment::final_break()
2205 if (prev_line_interrupted == 2) {
2206 do_break();
2207 add_node(new transparent_dummy_node);
2209 else
2210 do_break();
2213 node *environment::make_tag(const char *nm, int i)
2215 if (is_html) {
2217 * need to emit tag for post-grohtml
2218 * but we check to see whether we can emit specials
2220 if (curdiv == topdiv && topdiv->before_first_page)
2221 topdiv->begin_page();
2222 macro *m = new macro;
2223 m->append_str("devtag:");
2224 for (const char *p = nm; *p; p++)
2225 if (!invalid_input_char((unsigned char)*p))
2226 m->append(*p);
2227 m->append(' ');
2228 m->append_int(i);
2229 return new special_node(*m);
2231 return 0;
2234 void environment::dump_troff_state()
2236 #define SPACES " "
2237 fprintf(stderr, SPACES "register `in' = %d\n", curenv->indent.to_units());
2238 if (curenv->have_temporary_indent)
2239 fprintf(stderr, SPACES "register `ti' = %d\n",
2240 curenv->temporary_indent.to_units());
2241 fprintf(stderr, SPACES "centered lines `ce' = %d\n", curenv->center_lines);
2242 fprintf(stderr, SPACES "register `ll' = %d\n",
2243 curenv->line_length.to_units());
2244 fprintf(stderr, SPACES "fill `fi=1/nf=0' = %d\n", curenv->fill);
2245 fprintf(stderr, SPACES "page offset `po' = %d\n",
2246 topdiv->get_page_offset().to_units());
2247 fprintf(stderr, SPACES "seen_break = %d\n", curenv->seen_break);
2248 fprintf(stderr, SPACES "seen_space = %d\n", curenv->seen_space);
2249 fflush(stderr);
2250 #undef SPACES
2253 statem *environment::construct_state(int only_eol)
2255 if (is_html) {
2256 statem *s = new statem();
2257 if (!only_eol) {
2258 s->add_tag(MTSM_IN, indent);
2259 s->add_tag(MTSM_LL, line_length);
2260 s->add_tag(MTSM_PO, topdiv->get_page_offset().to_units());
2261 s->add_tag(MTSM_RJ, right_justify_lines);
2262 if (have_temporary_indent)
2263 s->add_tag(MTSM_TI, temporary_indent);
2264 s->add_tag_ta();
2265 if (seen_break)
2266 s->add_tag(MTSM_BR);
2267 if (seen_space != 0)
2268 s->add_tag(MTSM_SP, seen_space);
2269 seen_break = 0;
2270 seen_space = 0;
2272 if (seen_eol) {
2273 s->add_tag(MTSM_EOL);
2274 s->add_tag(MTSM_CE, center_lines);
2276 seen_eol = 0;
2277 return s;
2279 else
2280 return NULL;
2283 void environment::construct_format_state(node *n, int was_centered,
2284 int filling)
2286 if (is_html) {
2287 // find first glyph node which has a state.
2288 while (n != 0 && n->state == 0)
2289 n = n->next;
2290 if (n == 0 || (n->state == 0))
2291 return;
2292 if (seen_space != 0)
2293 n->state->add_tag(MTSM_SP, seen_space);
2294 if (seen_eol && topdiv == curdiv)
2295 n->state->add_tag(MTSM_EOL);
2296 seen_space = 0;
2297 seen_eol = 0;
2298 if (was_centered)
2299 n->state->add_tag(MTSM_CE, center_lines+1);
2300 else
2301 n->state->add_tag_if_unknown(MTSM_CE, 0);
2302 n->state->add_tag_if_unknown(MTSM_FI, filling);
2303 n = n->next;
2304 while (n != 0) {
2305 if (n->state != 0) {
2306 n->state->sub_tag_ce();
2307 n->state->add_tag_if_unknown(MTSM_FI, filling);
2309 n = n->next;
2314 void environment::construct_new_line_state(node *n)
2316 if (is_html) {
2317 // find first glyph node which has a state.
2318 while (n != 0 && n->state == 0)
2319 n = n->next;
2320 if (n == 0 || n->state == 0)
2321 return;
2322 if (seen_space != 0)
2323 n->state->add_tag(MTSM_SP, seen_space);
2324 if (seen_eol && topdiv == curdiv)
2325 n->state->add_tag(MTSM_EOL);
2326 seen_space = 0;
2327 seen_eol = 0;
2331 extern int global_diverted_space;
2333 void environment::do_break(int do_spread)
2335 int was_centered = 0;
2336 if (curdiv == topdiv && topdiv->before_first_page) {
2337 topdiv->begin_page();
2338 return;
2340 if (current_tab)
2341 wrap_up_tab();
2342 if (line) {
2343 // this is so that hyphenation works
2344 if (line->nspaces() == 0) {
2345 line = new space_node(H0, get_fill_color(), line);
2346 space_total++;
2348 possibly_break_line(0, do_spread);
2350 while (line != 0 && line->discardable()) {
2351 width_total -= line->width();
2352 space_total -= line->nspaces();
2353 node *tem = line;
2354 line = line->next;
2355 delete tem;
2357 discarding = 0;
2358 input_line_start = H0;
2359 if (line != 0) {
2360 if (fill) {
2361 switch (adjust_mode) {
2362 case ADJUST_CENTER:
2363 saved_indent += (target_text_length - width_total)/2;
2364 was_centered = 1;
2365 break;
2366 case ADJUST_RIGHT:
2367 saved_indent += target_text_length - width_total;
2368 break;
2371 node *tem = line;
2372 line = 0;
2373 output_line(tem, width_total, was_centered);
2374 hyphen_line_count = 0;
2376 prev_line_interrupted = 0;
2377 #ifdef WIDOW_CONTROL
2378 mark_last_line();
2379 output_pending_lines();
2380 #endif /* WIDOW_CONTROL */
2381 if (!global_diverted_space) {
2382 curdiv->modified_tag.incl(MTSM_BR);
2383 seen_break = 1;
2387 int environment::is_empty()
2389 return !current_tab && line == 0 && pending_lines == 0;
2392 void do_break_request(int spread)
2394 while (!tok.newline() && !tok.eof())
2395 tok.next();
2396 if (break_flag)
2397 curenv->do_break(spread);
2398 tok.next();
2401 void break_request()
2403 do_break_request(0);
2406 void break_spread_request()
2408 do_break_request(1);
2411 void title()
2413 if (curdiv == topdiv && topdiv->before_first_page) {
2414 handle_initial_title();
2415 return;
2417 node *part[3];
2418 hunits part_width[3];
2419 part[0] = part[1] = part[2] = 0;
2420 environment env(curenv);
2421 environment *oldenv = curenv;
2422 curenv = &env;
2423 read_title_parts(part, part_width);
2424 curenv = oldenv;
2425 curenv->size = env.size;
2426 curenv->prev_size = env.prev_size;
2427 curenv->requested_size = env.requested_size;
2428 curenv->prev_requested_size = env.prev_requested_size;
2429 curenv->char_height = env.char_height;
2430 curenv->char_slant = env.char_slant;
2431 curenv->fontno = env.fontno;
2432 curenv->prev_fontno = env.prev_fontno;
2433 curenv->glyph_color = env.glyph_color;
2434 curenv->prev_glyph_color = env.prev_glyph_color;
2435 curenv->fill_color = env.fill_color;
2436 curenv->prev_fill_color = env.prev_fill_color;
2437 node *n = 0;
2438 node *p = part[2];
2439 while (p != 0) {
2440 node *tem = p;
2441 p = p->next;
2442 tem->next = n;
2443 n = tem;
2445 hunits length_title(curenv->title_length);
2446 hunits f = length_title - part_width[1];
2447 hunits f2 = f/2;
2448 n = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(), n);
2449 p = part[1];
2450 while (p != 0) {
2451 node *tem = p;
2452 p = p->next;
2453 tem->next = n;
2454 n = tem;
2456 n = new hmotion_node(f - f2 - part_width[0], curenv->get_fill_color(), n);
2457 p = part[0];
2458 while (p != 0) {
2459 node *tem = p;
2460 p = p->next;
2461 tem->next = n;
2462 n = tem;
2464 curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
2465 curenv->total_post_vertical_spacing(), length_title);
2466 curenv->hyphen_line_count = 0;
2467 tok.next();
2470 void adjust()
2472 curenv->adjust_mode |= 1;
2473 if (has_arg()) {
2474 switch (tok.ch()) {
2475 case 'l':
2476 curenv->adjust_mode = ADJUST_LEFT;
2477 break;
2478 case 'r':
2479 curenv->adjust_mode = ADJUST_RIGHT;
2480 break;
2481 case 'c':
2482 curenv->adjust_mode = ADJUST_CENTER;
2483 break;
2484 case 'b':
2485 case 'n':
2486 curenv->adjust_mode = ADJUST_BOTH;
2487 break;
2488 default:
2489 int n;
2490 if (get_integer(&n)) {
2491 if (n < 0)
2492 warning(WARN_RANGE, "negative adjustment mode");
2493 else if (n > 5) {
2494 curenv->adjust_mode = 5;
2495 warning(WARN_RANGE, "adjustment mode `%1' out of range", n);
2497 else
2498 curenv->adjust_mode = n;
2502 skip_line();
2505 void no_adjust()
2507 curenv->adjust_mode &= ~1;
2508 skip_line();
2511 void do_input_trap(int continued)
2513 curenv->input_trap_count = 0;
2514 if (continued)
2515 curenv->continued_input_trap = 1;
2516 int n;
2517 if (has_arg() && get_integer(&n)) {
2518 if (n <= 0)
2519 warning(WARN_RANGE,
2520 "number of lines for input trap must be greater than zero");
2521 else {
2522 symbol s = get_name(1);
2523 if (!s.is_null()) {
2524 curenv->input_trap_count = n;
2525 curenv->input_trap = s;
2529 skip_line();
2532 void input_trap()
2534 do_input_trap(0);
2537 void input_trap_continued()
2539 do_input_trap(1);
2542 /* tabs */
2544 // must not be R or C or L or a legitimate part of a number expression
2545 const char TAB_REPEAT_CHAR = 'T';
2547 struct tab {
2548 tab *next;
2549 hunits pos;
2550 tab_type type;
2551 tab(hunits, tab_type);
2552 enum { BLOCK = 1024 };
2553 static tab *free_list;
2554 void *operator new(size_t);
2555 void operator delete(void *);
2558 tab *tab::free_list = 0;
2560 void *tab::operator new(size_t n)
2562 assert(n == sizeof(tab));
2563 if (!free_list) {
2564 free_list = (tab *)new char[sizeof(tab)*BLOCK];
2565 for (int i = 0; i < BLOCK - 1; i++)
2566 free_list[i].next = free_list + i + 1;
2567 free_list[BLOCK-1].next = 0;
2569 tab *p = free_list;
2570 free_list = (tab *)(free_list->next);
2571 p->next = 0;
2572 return p;
2575 #ifdef __GNUG__
2576 /* cfront can't cope with this. */
2577 inline
2578 #endif
2579 void tab::operator delete(void *p)
2581 if (p) {
2582 ((tab *)p)->next = free_list;
2583 free_list = (tab *)p;
2587 tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2591 tab_stops::tab_stops(hunits distance, tab_type type)
2592 : initial_list(0)
2594 repeated_list = new tab(distance, type);
2597 tab_stops::~tab_stops()
2599 clear();
2602 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
2604 hunits nextpos;
2606 return distance_to_next_tab(curpos, distance, &nextpos);
2609 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance,
2610 hunits *nextpos)
2612 hunits lastpos = 0;
2613 tab *tem;
2614 for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
2615 lastpos = tem->pos;
2616 if (tem) {
2617 *distance = tem->pos - curpos;
2618 *nextpos = tem->pos;
2619 return tem->type;
2621 if (repeated_list == 0)
2622 return TAB_NONE;
2623 hunits base = lastpos;
2624 for (;;) {
2625 for (tem = repeated_list; tem && tem->pos + base <= curpos; tem = tem->next)
2626 lastpos = tem->pos;
2627 if (tem) {
2628 *distance = tem->pos + base - curpos;
2629 *nextpos = tem->pos + base;
2630 return tem->type;
2632 assert(lastpos > 0);
2633 base += lastpos;
2635 return TAB_NONE;
2638 const char *tab_stops::to_string()
2640 static char *buf = 0;
2641 static int buf_size = 0;
2642 // figure out a maximum on the amount of space we can need
2643 int count = 0;
2644 tab *p;
2645 for (p = initial_list; p; p = p->next)
2646 ++count;
2647 for (p = repeated_list; p; p = p->next)
2648 ++count;
2649 // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
2650 int need = count*12 + 3;
2651 if (buf == 0 || need > buf_size) {
2652 if (buf)
2653 a_delete buf;
2654 buf_size = need;
2655 buf = new char[buf_size];
2657 char *ptr = buf;
2658 for (p = initial_list; p; p = p->next) {
2659 strcpy(ptr, i_to_a(p->pos.to_units()));
2660 ptr = strchr(ptr, '\0');
2661 *ptr++ = 'u';
2662 *ptr = '\0';
2663 switch (p->type) {
2664 case TAB_LEFT:
2665 break;
2666 case TAB_RIGHT:
2667 *ptr++ = 'R';
2668 break;
2669 case TAB_CENTER:
2670 *ptr++ = 'C';
2671 break;
2672 case TAB_NONE:
2673 default:
2674 assert(0);
2677 if (repeated_list)
2678 *ptr++ = TAB_REPEAT_CHAR;
2679 for (p = repeated_list; p; p = p->next) {
2680 strcpy(ptr, i_to_a(p->pos.to_units()));
2681 ptr = strchr(ptr, '\0');
2682 *ptr++ = 'u';
2683 *ptr = '\0';
2684 switch (p->type) {
2685 case TAB_LEFT:
2686 break;
2687 case TAB_RIGHT:
2688 *ptr++ = 'R';
2689 break;
2690 case TAB_CENTER:
2691 *ptr++ = 'C';
2692 break;
2693 case TAB_NONE:
2694 default:
2695 assert(0);
2698 *ptr++ = '\0';
2699 return buf;
2702 tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2706 tab_stops::tab_stops(const tab_stops &ts)
2707 : initial_list(0), repeated_list(0)
2709 tab **p = &initial_list;
2710 tab *t = ts.initial_list;
2711 while (t) {
2712 *p = new tab(t->pos, t->type);
2713 t = t->next;
2714 p = &(*p)->next;
2716 p = &repeated_list;
2717 t = ts.repeated_list;
2718 while (t) {
2719 *p = new tab(t->pos, t->type);
2720 t = t->next;
2721 p = &(*p)->next;
2725 void tab_stops::clear()
2727 while (initial_list) {
2728 tab *tem = initial_list;
2729 initial_list = initial_list->next;
2730 delete tem;
2732 while (repeated_list) {
2733 tab *tem = repeated_list;
2734 repeated_list = repeated_list->next;
2735 delete tem;
2739 void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
2741 tab **p;
2742 for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
2744 *p = new tab(pos, type);
2748 void tab_stops::operator=(const tab_stops &ts)
2750 clear();
2751 tab **p = &initial_list;
2752 tab *t = ts.initial_list;
2753 while (t) {
2754 *p = new tab(t->pos, t->type);
2755 t = t->next;
2756 p = &(*p)->next;
2758 p = &repeated_list;
2759 t = ts.repeated_list;
2760 while (t) {
2761 *p = new tab(t->pos, t->type);
2762 t = t->next;
2763 p = &(*p)->next;
2767 void set_tabs()
2769 hunits pos;
2770 hunits prev_pos = 0;
2771 int first = 1;
2772 int repeated = 0;
2773 tab_stops tabs;
2774 while (has_arg()) {
2775 if (tok.ch() == TAB_REPEAT_CHAR) {
2776 tok.next();
2777 repeated = 1;
2778 prev_pos = 0;
2780 if (!get_hunits(&pos, 'm', prev_pos))
2781 break;
2782 tab_type type = TAB_LEFT;
2783 if (tok.ch() == 'C') {
2784 tok.next();
2785 type = TAB_CENTER;
2787 else if (tok.ch() == 'R') {
2788 tok.next();
2789 type = TAB_RIGHT;
2791 else if (tok.ch() == 'L') {
2792 tok.next();
2794 if (pos <= prev_pos && !first)
2795 warning(WARN_RANGE,
2796 "positions of tab stops must be strictly increasing");
2797 else {
2798 tabs.add_tab(pos, type, repeated);
2799 prev_pos = pos;
2800 first = 0;
2803 curenv->tabs = tabs;
2804 curdiv->modified_tag.incl(MTSM_TA);
2805 skip_line();
2808 const char *environment::get_tabs()
2810 return tabs.to_string();
2813 tab_type environment::distance_to_next_tab(hunits *distance)
2815 return line_tabs
2816 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
2817 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
2820 tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
2822 return line_tabs
2823 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
2824 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
2825 leftpos);
2828 void field_characters()
2830 field_delimiter_char = get_optional_char();
2831 if (field_delimiter_char)
2832 padding_indicator_char = get_optional_char();
2833 else
2834 padding_indicator_char = 0;
2835 skip_line();
2838 void line_tabs_request()
2840 int n;
2841 if (has_arg() && get_integer(&n))
2842 curenv->line_tabs = n != 0;
2843 else
2844 curenv->line_tabs = 1;
2845 skip_line();
2848 int environment::get_line_tabs()
2850 return line_tabs;
2853 void environment::wrap_up_tab()
2855 if (!current_tab)
2856 return;
2857 if (line == 0)
2858 start_line();
2859 hunits tab_amount;
2860 switch (current_tab) {
2861 case TAB_RIGHT:
2862 tab_amount = tab_distance - tab_width;
2863 line = make_tab_node(tab_amount, line);
2864 break;
2865 case TAB_CENTER:
2866 tab_amount = tab_distance - tab_width/2;
2867 line = make_tab_node(tab_amount, line);
2868 break;
2869 case TAB_NONE:
2870 case TAB_LEFT:
2871 default:
2872 assert(0);
2874 width_total += tab_amount;
2875 width_total += tab_width;
2876 if (current_field) {
2877 if (tab_precedes_field) {
2878 pre_field_width += tab_amount;
2879 tab_precedes_field = 0;
2881 field_distance -= tab_amount;
2882 field_spaces += tab_field_spaces;
2884 if (tab_contents != 0) {
2885 node *tem;
2886 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2888 tem->next = line;
2889 line = tab_contents;
2891 tab_field_spaces = 0;
2892 tab_contents = 0;
2893 tab_width = H0;
2894 tab_distance = H0;
2895 current_tab = TAB_NONE;
2898 node *environment::make_tab_node(hunits d, node *next)
2900 if (leader_node != 0 && d < 0) {
2901 error("motion generated by leader cannot be negative");
2902 delete leader_node;
2903 leader_node = 0;
2905 if (!leader_node)
2906 return new hmotion_node(d, 1, 0, get_fill_color(), next);
2907 node *n = new hline_node(d, leader_node, next);
2908 leader_node = 0;
2909 return n;
2912 void environment::handle_tab(int is_leader)
2914 hunits d;
2915 hunits absolute;
2916 if (current_tab)
2917 wrap_up_tab();
2918 charinfo *ci = is_leader ? leader_char : tab_char;
2919 delete leader_node;
2920 leader_node = ci ? make_char_node(ci) : 0;
2921 tab_type t = distance_to_next_tab(&d, &absolute);
2922 switch (t) {
2923 case TAB_NONE:
2924 return;
2925 case TAB_LEFT:
2926 add_node(make_tag("tab L", absolute.to_units()));
2927 add_node(make_tab_node(d));
2928 return;
2929 case TAB_RIGHT:
2930 add_node(make_tag("tab R", absolute.to_units()));
2931 break;
2932 case TAB_CENTER:
2933 add_node(make_tag("tab C", absolute.to_units()));
2934 break;
2935 default:
2936 assert(0);
2938 tab_width = 0;
2939 tab_distance = d;
2940 tab_contents = 0;
2941 current_tab = t;
2942 tab_field_spaces = 0;
2945 void environment::start_field()
2947 assert(!current_field);
2948 hunits d;
2949 if (distance_to_next_tab(&d) != TAB_NONE) {
2950 pre_field_width = get_text_length();
2951 field_distance = d;
2952 current_field = 1;
2953 field_spaces = 0;
2954 tab_field_spaces = 0;
2955 for (node *p = line; p; p = p->next)
2956 if (p->nspaces()) {
2957 p->freeze_space();
2958 space_total--;
2960 tab_precedes_field = current_tab != TAB_NONE;
2962 else
2963 error("zero field width");
2966 void environment::wrap_up_field()
2968 if (!current_tab && field_spaces == 0)
2969 add_padding();
2970 hunits padding = field_distance - (get_text_length() - pre_field_width);
2971 if (current_tab && tab_field_spaces != 0) {
2972 hunits tab_padding = scale(padding,
2973 tab_field_spaces,
2974 field_spaces + tab_field_spaces);
2975 padding -= tab_padding;
2976 distribute_space(tab_contents, tab_field_spaces, tab_padding, 1);
2977 tab_field_spaces = 0;
2978 tab_width += tab_padding;
2980 if (field_spaces != 0) {
2981 distribute_space(line, field_spaces, padding, 1);
2982 width_total += padding;
2983 if (current_tab) {
2984 // the start of the tab has been moved to the right by padding, so
2985 tab_distance -= padding;
2986 if (tab_distance <= H0) {
2987 // use the next tab stop instead
2988 current_tab = tabs.distance_to_next_tab(get_input_line_position()
2989 - tab_width,
2990 &tab_distance);
2991 if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
2992 width_total += tab_width;
2993 if (current_tab == TAB_LEFT) {
2994 line = make_tab_node(tab_distance, line);
2995 width_total += tab_distance;
2996 current_tab = TAB_NONE;
2998 if (tab_contents != 0) {
2999 node *tem;
3000 for (tem = tab_contents; tem->next != 0; tem = tem->next)
3002 tem->next = line;
3003 line = tab_contents;
3004 tab_contents = 0;
3006 tab_width = H0;
3007 tab_distance = H0;
3012 current_field = 0;
3015 void environment::add_padding()
3017 if (current_tab) {
3018 tab_contents = new space_node(H0, get_fill_color(), tab_contents);
3019 tab_field_spaces++;
3021 else {
3022 if (line == 0)
3023 start_line();
3024 line = new space_node(H0, get_fill_color(), line);
3025 field_spaces++;
3029 typedef int (environment::*INT_FUNCP)();
3030 typedef vunits (environment::*VUNITS_FUNCP)();
3031 typedef hunits (environment::*HUNITS_FUNCP)();
3032 typedef const char *(environment::*STRING_FUNCP)();
3034 class int_env_reg
3035 : public reg
3037 INT_FUNCP func;
3039 public:
3040 int_env_reg(INT_FUNCP);
3041 const char *get_string();
3042 int get_value(units *val);
3045 class vunits_env_reg
3046 : public reg
3048 VUNITS_FUNCP func;
3050 public:
3051 vunits_env_reg(VUNITS_FUNCP f);
3052 const char *get_string();
3053 int get_value(units *val);
3056 class hunits_env_reg
3057 : public reg
3059 HUNITS_FUNCP func;
3061 public:
3062 hunits_env_reg(HUNITS_FUNCP f);
3063 const char *get_string();
3064 int get_value(units *val);
3067 class string_env_reg
3068 : public reg
3070 STRING_FUNCP func;
3072 public:
3073 string_env_reg(STRING_FUNCP);
3074 const char *get_string();
3077 int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
3081 int int_env_reg::get_value(units *val)
3083 *val = (curenv->*func)();
3084 return 1;
3087 const char *int_env_reg::get_string()
3089 return i_to_a((curenv->*func)());
3092 vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
3096 int vunits_env_reg::get_value(units *val)
3098 *val = (curenv->*func)().to_units();
3099 return 1;
3102 const char *vunits_env_reg::get_string()
3104 return i_to_a((curenv->*func)().to_units());
3107 hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
3111 int hunits_env_reg::get_value(units *val)
3113 *val = (curenv->*func)().to_units();
3114 return 1;
3117 const char *hunits_env_reg::get_string()
3119 return i_to_a((curenv->*func)().to_units());
3122 string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
3126 const char *string_env_reg::get_string()
3128 return (curenv->*func)();
3131 class horizontal_place_reg
3132 : public general_reg
3134 public:
3135 horizontal_place_reg();
3136 int get_value(units *);
3137 void set_value(units);
3140 horizontal_place_reg::horizontal_place_reg()
3144 int horizontal_place_reg::get_value(units *res)
3146 *res = curenv->get_input_line_position().to_units();
3147 return 1;
3150 void horizontal_place_reg::set_value(units n)
3152 curenv->set_input_line_position(hunits(n));
3155 int environment::get_zoom()
3157 return env_get_zoom(this);
3160 const char *environment::get_font_family_string()
3162 return family->nm.contents();
3165 const char *environment::get_glyph_color_string()
3167 return glyph_color->nm.contents();
3170 const char *environment::get_fill_color_string()
3172 return fill_color->nm.contents();
3175 const char *environment::get_font_name_string()
3177 symbol f = get_font_name(fontno, this);
3178 return f.contents();
3181 const char *environment::get_style_name_string()
3183 symbol f = get_style_name(fontno);
3184 return f.contents();
3187 const char *environment::get_name_string()
3189 return name.contents();
3192 // Convert a quantity in scaled points to ascii decimal fraction.
3194 const char *sptoa(int sp)
3196 assert(sp > 0);
3197 assert(sizescale > 0);
3198 if (sizescale == 1)
3199 return i_to_a(sp);
3200 if (sp % sizescale == 0)
3201 return i_to_a(sp/sizescale);
3202 // See if 1/sizescale is exactly representable as a decimal fraction,
3203 // ie its only prime factors are 2 and 5.
3204 int n = sizescale;
3205 int power2 = 0;
3206 while ((n & 1) == 0) {
3207 n >>= 1;
3208 power2++;
3210 int power5 = 0;
3211 while ((n % 5) == 0) {
3212 n /= 5;
3213 power5++;
3215 if (n == 1) {
3216 int decimal_point = power5 > power2 ? power5 : power2;
3217 if (decimal_point <= 10) {
3218 int factor = 1;
3219 int t;
3220 for (t = decimal_point - power2; --t >= 0;)
3221 factor *= 2;
3222 for (t = decimal_point - power5; --t >= 0;)
3223 factor *= 5;
3224 if (factor == 1 || sp <= INT_MAX/factor)
3225 return if_to_a(sp*factor, decimal_point);
3228 double s = double(sp)/double(sizescale);
3229 double factor = 10.0;
3230 double val = s;
3231 int decimal_point = 0;
3232 do {
3233 double v = ceil(s*factor);
3234 if (v > INT_MAX)
3235 break;
3236 val = v;
3237 factor *= 10.0;
3238 } while (++decimal_point < 10);
3239 return if_to_a(int(val), decimal_point);
3242 const char *environment::get_point_size_string()
3244 return sptoa(curenv->get_point_size());
3247 const char *environment::get_requested_point_size_string()
3249 return sptoa(curenv->get_requested_point_size());
3252 void environment::print_env()
3254 // at the time of calling .pev, those values are always zero or
3255 // meaningless:
3257 // char_height, char_slant,
3258 // interrupted
3259 // current_tab, tab_width, tab_distance
3260 // current_field, field_distance, pre_field_width, field_spaces,
3261 // tab_field_spaces, tab_precedes_field
3262 // composite
3264 errprint(" previous line length: %1u\n", prev_line_length.to_units());
3265 errprint(" line length: %1u\n", line_length.to_units());
3266 errprint(" previous title length: %1u\n", prev_title_length.to_units());
3267 errprint(" title length: %1u\n", title_length.to_units());
3268 errprint(" previous size: %1p (%2s)\n",
3269 prev_size.to_points(), prev_size.to_scaled_points());
3270 errprint(" size: %1p (%2s)\n",
3271 size.to_points(), size.to_scaled_points());
3272 errprint(" previous requested size: %1s\n", prev_requested_size);
3273 errprint(" requested size: %1s\n", requested_size);
3274 errprint(" previous font number: %1\n", prev_fontno);
3275 errprint(" font number: %1\n", fontno);
3276 errprint(" previous family: `%1'\n", prev_family->nm.contents());
3277 errprint(" family: `%1'\n", family->nm.contents());
3278 errprint(" space size: %1/36 em\n", space_size);
3279 errprint(" sentence space size: %1/36 em\n", sentence_space_size);
3280 errprint(" previous line interrupted: %1\n",
3281 prev_line_interrupted ? "yes" : "no");
3282 errprint(" fill mode: %1\n", fill ? "on" : "off");
3283 errprint(" adjust mode: %1\n",
3284 adjust_mode == ADJUST_LEFT
3285 ? "left"
3286 : adjust_mode == ADJUST_BOTH
3287 ? "both"
3288 : adjust_mode == ADJUST_CENTER
3289 ? "center"
3290 : "right");
3291 if (center_lines)
3292 errprint(" lines to center: %1\n", center_lines);
3293 if (right_justify_lines)
3294 errprint(" lines to right justify: %1\n", right_justify_lines);
3295 errprint(" previous vertical spacing: %1u\n",
3296 prev_vertical_spacing.to_units());
3297 errprint(" vertical spacing: %1u\n", vertical_spacing.to_units());
3298 errprint(" previous post-vertical spacing: %1u\n",
3299 prev_post_vertical_spacing.to_units());
3300 errprint(" post-vertical spacing: %1u\n",
3301 post_vertical_spacing.to_units());
3302 errprint(" previous line spacing: %1\n", prev_line_spacing);
3303 errprint(" line spacing: %1\n", line_spacing);
3304 errprint(" previous indentation: %1u\n", prev_indent.to_units());
3305 errprint(" indentation: %1u\n", indent.to_units());
3306 errprint(" temporary indentation: %1u\n", temporary_indent.to_units());
3307 errprint(" have temporary indentation: %1\n",
3308 have_temporary_indent ? "yes" : "no");
3309 errprint(" currently used indentation: %1u\n", saved_indent.to_units());
3310 errprint(" target text length: %1u\n", target_text_length.to_units());
3311 if (underline_lines) {
3312 errprint(" lines to underline: %1\n", underline_lines);
3313 errprint(" font number before underlining: %1\n", pre_underline_fontno);
3314 errprint(" underline spaces: %1\n", underline_spaces ? "yes" : "no");
3316 if (input_trap.contents()) {
3317 errprint(" input trap macro: `%1'\n", input_trap.contents());
3318 errprint(" input trap line counter: %1\n", input_trap_count);
3319 errprint(" continued input trap: %1\n",
3320 continued_input_trap ? "yes" : "no");
3322 errprint(" previous text length: %1u\n", prev_text_length.to_units());
3323 errprint(" total width: %1u\n", width_total.to_units());
3324 errprint(" total number of spaces: %1\n", space_total);
3325 errprint(" input line start: %1u\n", input_line_start.to_units());
3326 errprint(" line tabs: %1\n", line_tabs ? "yes" : "no");
3327 errprint(" discarding: %1\n", discarding ? "yes" : "no");
3328 errprint(" spread flag set: %1\n", spread_flag ? "yes" : "no"); // \p
3329 if (margin_character_node) {
3330 errprint(" margin character flags: %1\n",
3331 margin_character_flags == MARGIN_CHARACTER_ON
3332 ? "on"
3333 : margin_character_flags == MARGIN_CHARACTER_NEXT
3334 ? "next"
3335 : margin_character_flags == MARGIN_CHARACTER_ON
3336 | MARGIN_CHARACTER_NEXT
3337 ? "on, next"
3338 : "none");
3339 errprint(" margin character distance: %1u\n",
3340 margin_character_distance.to_units());
3342 if (numbering_nodes) {
3343 errprint(" line number digit width: %1u\n",
3344 line_number_digit_width.to_units());
3345 errprint(" separation between number and text: %1 digit spaces\n",
3346 number_text_separation);
3347 errprint(" line number indentation: %1 digit spaces\n",
3348 line_number_indent);
3349 errprint(" print line numbers every %1line%1\n",
3350 line_number_multiple > 1 ? i_to_a(line_number_multiple) : "",
3351 line_number_multiple > 1 ? "s" : "");
3352 errprint(" lines not to enumerate: %1\n", no_number_count);
3354 string hf = hyphenation_flags ? "on" : "off";
3355 if (hyphenation_flags & HYPHEN_LAST_LINE)
3356 hf += ", not last line";
3357 if (hyphenation_flags & HYPHEN_LAST_CHARS)
3358 hf += ", not last two chars";
3359 if (hyphenation_flags & HYPHEN_FIRST_CHARS)
3360 hf += ", not first two chars";
3361 hf += '\0';
3362 errprint(" hyphenation_flags: %1\n", hf.contents());
3363 errprint(" number of consecutive hyphenated lines: %1\n",
3364 hyphen_line_count);
3365 errprint(" maximum number of consecutive hyphenated lines: %1\n",
3366 hyphen_line_max);
3367 errprint(" hyphenation space: %1u\n", hyphenation_space.to_units());
3368 errprint(" hyphenation margin: %1u\n", hyphenation_margin.to_units());
3369 #ifdef WIDOW_CONTROL
3370 errprint(" widow control: %1\n", widow_control ? "yes" : "no");
3371 #endif
3374 void print_env()
3376 errprint("Current Environment:\n");
3377 curenv->print_env();
3378 for (int i = 0; i < NENVIRONMENTS; i++) {
3379 if (env_table[i]) {
3380 errprint("Environment %1:\n", i);
3381 if (env_table[i] != curenv)
3382 env_table[i]->print_env();
3383 else
3384 errprint(" current\n");
3387 dictionary_iterator iter(env_dictionary);
3388 symbol s;
3389 environment *e;
3390 while (iter.get(&s, (void **)&e)) {
3391 assert(!s.is_null());
3392 errprint("Environment %1:\n", s.contents());
3393 if (e != curenv)
3394 e->print_env();
3395 else
3396 errprint(" current\n");
3398 fflush(stderr);
3399 skip_line();
3402 #define init_int_env_reg(name, func) \
3403 number_reg_dictionary.define(name, new int_env_reg(&environment::func))
3405 #define init_vunits_env_reg(name, func) \
3406 number_reg_dictionary.define(name, new vunits_env_reg(&environment::func))
3408 #define init_hunits_env_reg(name, func) \
3409 number_reg_dictionary.define(name, new hunits_env_reg(&environment::func))
3411 #define init_string_env_reg(name, func) \
3412 number_reg_dictionary.define(name, new string_env_reg(&environment::func))
3414 void init_env_requests()
3416 init_request("ad", adjust);
3417 init_request("br", break_request);
3418 init_request("brp", break_spread_request);
3419 init_request("c2", no_break_control_char);
3420 init_request("cc", control_char);
3421 init_request("ce", center);
3422 init_request("cu", continuous_underline);
3423 init_request("ev", environment_switch);
3424 init_request("evc", environment_copy);
3425 init_request("fam", family_change);
3426 init_request("fc", field_characters);
3427 init_request("fi", fill);
3428 init_request("fcolor", fill_color_change);
3429 init_request("ft", font_change);
3430 init_request("gcolor", glyph_color_change);
3431 init_request("hc", hyphen_char);
3432 init_request("hlm", hyphen_line_max_request);
3433 init_request("hy", hyphenate_request);
3434 init_request("hym", hyphenation_margin_request);
3435 init_request("hys", hyphenation_space_request);
3436 init_request("in", indent);
3437 init_request("it", input_trap);
3438 init_request("itc", input_trap_continued);
3439 init_request("lc", leader_character);
3440 init_request("linetabs", line_tabs_request);
3441 init_request("ll", line_length);
3442 init_request("ls", line_spacing);
3443 init_request("lt", title_length);
3444 init_request("mc", margin_character);
3445 init_request("na", no_adjust);
3446 init_request("nf", no_fill);
3447 init_request("nh", no_hyphenate);
3448 init_request("nm", number_lines);
3449 init_request("nn", no_number);
3450 init_request("pev", print_env);
3451 init_request("ps", point_size);
3452 init_request("pvs", post_vertical_spacing);
3453 init_request("rj", right_justify);
3454 init_request("sizes", override_sizes);
3455 init_request("ss", space_size);
3456 init_request("ta", set_tabs);
3457 init_request("ti", temporary_indent);
3458 init_request("tc", tab_character);
3459 init_request("tl", title);
3460 init_request("ul", underline);
3461 init_request("vs", vertical_spacing);
3462 #ifdef WIDOW_CONTROL
3463 init_request("wdc", widow_control_request);
3464 #endif /* WIDOW_CONTROL */
3465 init_int_env_reg(".b", get_bold);
3466 init_vunits_env_reg(".cdp", get_prev_char_depth);
3467 init_int_env_reg(".ce", get_center_lines);
3468 init_vunits_env_reg(".cht", get_prev_char_height);
3469 init_hunits_env_reg(".csk", get_prev_char_skew);
3470 init_string_env_reg(".ev", get_name_string);
3471 init_int_env_reg(".f", get_font);
3472 init_string_env_reg(".fam", get_font_family_string);
3473 init_string_env_reg(".fn", get_font_name_string);
3474 init_int_env_reg(".height", get_char_height);
3475 init_int_env_reg(".hlc", get_hyphen_line_count);
3476 init_int_env_reg(".hlm", get_hyphen_line_max);
3477 init_int_env_reg(".hy", get_hyphenation_flags);
3478 init_hunits_env_reg(".hym", get_hyphenation_margin);
3479 init_hunits_env_reg(".hys", get_hyphenation_space);
3480 init_hunits_env_reg(".i", get_indent);
3481 init_hunits_env_reg(".in", get_saved_indent);
3482 init_int_env_reg(".int", get_prev_line_interrupted);
3483 init_int_env_reg(".linetabs", get_line_tabs);
3484 init_hunits_env_reg(".lt", get_title_length);
3485 init_int_env_reg(".j", get_adjust_mode);
3486 init_hunits_env_reg(".k", get_text_length);
3487 init_int_env_reg(".L", get_line_spacing);
3488 init_hunits_env_reg(".l", get_line_length);
3489 init_hunits_env_reg(".ll", get_saved_line_length);
3490 init_string_env_reg(".M", get_fill_color_string);
3491 init_string_env_reg(".m", get_glyph_color_string);
3492 init_hunits_env_reg(".n", get_prev_text_length);
3493 init_int_env_reg(".ps", get_point_size);
3494 init_int_env_reg(".psr", get_requested_point_size);
3495 init_vunits_env_reg(".pvs", get_post_vertical_spacing);
3496 init_int_env_reg(".rj", get_right_justify_lines);
3497 init_string_env_reg(".s", get_point_size_string);
3498 init_int_env_reg(".slant", get_char_slant);
3499 init_int_env_reg(".ss", get_space_size);
3500 init_int_env_reg(".sss", get_sentence_space_size);
3501 init_string_env_reg(".sr", get_requested_point_size_string);
3502 init_string_env_reg(".sty", get_style_name_string);
3503 init_string_env_reg(".tabs", get_tabs);
3504 init_int_env_reg(".u", get_fill);
3505 init_vunits_env_reg(".v", get_vertical_spacing);
3506 init_hunits_env_reg(".w", get_prev_char_width);
3507 init_int_env_reg(".zoom", get_zoom);
3508 number_reg_dictionary.define("ct", new variable_reg(&ct_reg_contents));
3509 number_reg_dictionary.define("hp", new horizontal_place_reg);
3510 number_reg_dictionary.define("ln", new variable_reg(&next_line_number));
3511 number_reg_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
3512 number_reg_dictionary.define("rst", new variable_reg(&rst_reg_contents));
3513 number_reg_dictionary.define("sb", new variable_reg(&sb_reg_contents));
3514 number_reg_dictionary.define("skw", new variable_reg(&skw_reg_contents));
3515 number_reg_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
3516 number_reg_dictionary.define("st", new variable_reg(&st_reg_contents));
3519 // Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
3521 struct trie_node;
3523 class trie
3525 trie_node *tp;
3527 virtual void do_match(int len, void *val) = 0;
3528 virtual void do_delete(void *) = 0;
3529 void delete_trie_node(trie_node *);
3531 public:
3532 trie() : tp(0) {}
3533 virtual ~trie(); // virtual to shut up g++
3534 void insert(const char *, int, void *);
3535 // find calls do_match for each match it finds
3536 void find(const char *pat, int patlen);
3537 void clear();
3540 class hyphen_trie
3541 : private trie
3543 int *h;
3545 void do_match(int i, void *v);
3546 void do_delete(void *v);
3547 void insert_pattern(const char *pat, int patlen, int *num);
3548 void insert_hyphenation(dictionary *ex, const char *pat, int patlen);
3549 int hpf_getc(file_case *fcp);
3551 public:
3552 hyphen_trie() {}
3553 ~hyphen_trie() {}
3554 void hyphenate(const char *word, int len, int *hyphens);
3555 void read_patterns_file(const char *name, int append, dictionary *ex);
3558 class hyphenation_language
3560 public:
3561 symbol name;
3562 dictionary exceptions;
3563 hyphen_trie patterns;
3565 hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
3566 ~hyphenation_language() { }
3569 dictionary language_dictionary(5);
3570 hyphenation_language *current_language = 0;
3572 static void set_hyphenation_language()
3574 symbol nm = get_name(1);
3575 if (!nm.is_null()) {
3576 current_language = (hyphenation_language *)language_dictionary.lookup(nm);
3577 if (!current_language) {
3578 current_language = new hyphenation_language(nm);
3579 (void)language_dictionary.lookup(nm, (void *)current_language);
3582 skip_line();
3585 const int WORD_MAX = 256; // we use unsigned char for offsets in
3586 // hyphenation exceptions
3588 static void hyphen_word()
3590 if (!current_language) {
3591 error("no current hyphenation language");
3592 skip_line();
3593 return;
3595 char buf[WORD_MAX + 1];
3596 unsigned char pos[WORD_MAX + 2];
3597 for (;;) {
3598 tok.skip();
3599 if (tok.newline() || tok.eof())
3600 break;
3601 int i = 0;
3602 int npos = 0;
3603 while (i < WORD_MAX && !tok.space() && !tok.newline() && !tok.eof()) {
3604 charinfo *ci = tok.get_char(1);
3605 if (ci == 0) {
3606 skip_line();
3607 return;
3609 tok.next();
3610 if (ci->get_ascii_code() == '-') {
3611 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3612 pos[npos++] = i;
3614 else {
3615 unsigned char c = ci->get_hyphenation_code();
3616 if (c == 0)
3617 break;
3618 buf[i++] = c;
3621 if (i > 0) {
3622 pos[npos] = 0;
3623 buf[i] = 0;
3624 unsigned char *tem = new unsigned char[npos + 1];
3625 memcpy(tem, pos, npos + 1);
3626 tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf),
3627 tem);
3628 if (tem)
3629 a_delete tem;
3632 skip_line();
3635 class trie_node
3637 public:
3638 char c;
3639 trie_node *down;
3640 trie_node *right;
3641 void *val;
3643 trie_node(char, trie_node *);
3646 trie_node::trie_node(char ch, trie_node *p)
3647 : c(ch), down(0), right(p), val(0)
3651 trie::~trie()
3653 clear();
3656 void trie::clear()
3658 delete_trie_node(tp);
3659 tp = 0;
3662 void trie::delete_trie_node(trie_node *p)
3664 if (p) {
3665 delete_trie_node(p->down);
3666 delete_trie_node(p->right);
3667 if (p->val)
3668 do_delete(p->val);
3669 delete p;
3673 void trie::insert(const char *pat, int patlen, void *val)
3675 trie_node **p = &tp;
3676 assert(patlen > 0 && pat != 0);
3677 for (;;) {
3678 while (*p != 0 && (*p)->c < pat[0])
3679 p = &((*p)->right);
3680 if (*p == 0 || (*p)->c != pat[0])
3681 *p = new trie_node(pat[0], *p);
3682 if (--patlen == 0) {
3683 (*p)->val = val;
3684 break;
3686 ++pat;
3687 p = &((*p)->down);
3691 void trie::find(const char *pat, int patlen)
3693 trie_node *p = tp;
3694 for (int i = 0; p != 0 && i < patlen; i++) {
3695 while (p != 0 && p->c < pat[i])
3696 p = p->right;
3697 if (p != 0 && p->c == pat[i]) {
3698 if (p->val != 0)
3699 do_match(i+1, p->val);
3700 p = p->down;
3702 else
3703 break;
3707 class operation
3709 public:
3710 operation *next;
3711 short distance;
3712 short num;
3714 operation(int, int, operation *);
3717 operation::operation(int i, int j, operation *op)
3718 : next(op), distance(j), num(i)
3722 void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
3724 operation *op = 0;
3725 for (int i = 0; i < patlen+1; i++)
3726 if (num[i] != 0)
3727 op = new operation(num[i], patlen - i, op);
3728 insert(pat, patlen, op);
3731 void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
3732 int patlen)
3734 char buf[WORD_MAX + 1];
3735 unsigned char pos[WORD_MAX + 2];
3736 int i = 0, j = 0;
3737 int npos = 0;
3738 while (j < patlen) {
3739 unsigned char c = pat[j++];
3740 if (c == '-') {
3741 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3742 pos[npos++] = i;
3744 else
3745 buf[i++] = hpf_code_table[c];
3747 if (i > 0) {
3748 pos[npos] = 0;
3749 buf[i] = 0;
3750 unsigned char *tem = new unsigned char[npos + 1];
3751 memcpy(tem, pos, npos + 1);
3752 tem = (unsigned char *)ex->lookup(symbol(buf), tem);
3753 if (tem)
3754 a_delete tem;
3758 void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
3760 int j;
3761 for (j = 0; j < len + 1; j++)
3762 hyphens[j] = 0;
3763 for (j = 0; j < len - 1; j++) {
3764 h = hyphens + j;
3765 find(word + j, len - j);
3769 inline int max(int m, int n)
3771 return m > n ? m : n;
3774 void hyphen_trie::do_match(int i, void *v)
3776 operation *op = (operation *)v;
3777 while (op != 0) {
3778 h[i - op->distance] = max(h[i - op->distance], op->num);
3779 op = op->next;
3783 void hyphen_trie::do_delete(void *v)
3785 operation *op = (operation *)v;
3786 while (op) {
3787 operation *tem = op;
3788 op = tem->next;
3789 delete tem;
3793 /* We use very simple rules to parse TeX's hyphenation patterns.
3795 . `%' starts a comment even if preceded by `\'.
3797 . No support for digraphs and like `\$'.
3799 . `^^xx' (`x' is 0-9 or a-f), and `^^x' (character code of `x' in the
3800 range 0-127) are recognized; other use of `^' causes an error.
3802 . No macro expansion.
3804 . We check for the expression `\patterns{...}' (possibly with
3805 whitespace before and after the braces). Everything between the
3806 braces is taken as hyphenation patterns. Consequently, `{' and `}'
3807 are not allowed in patterns.
3809 . Similarly, `\hyphenation{...}' gives a list of hyphenation
3810 exceptions.
3812 . `\endinput' is recognized also.
3814 . For backwards compatibility, if `\patterns' is missing, the
3815 whole file is treated as a list of hyphenation patterns (only
3816 recognizing `%' as the start of a comment.
3820 int hyphen_trie::hpf_getc(file_case *fcp)
3822 int c = fcp->get_c();
3823 int c1;
3824 int cc = 0;
3825 if (c != '^')
3826 return c;
3827 c = fcp->get_c();
3828 if (c != '^')
3829 goto fail;
3830 c = fcp->get_c();
3831 c1 = fcp->get_c();
3832 if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
3833 && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
3834 if (c >= '0' && c <= '9')
3835 c -= '0';
3836 else
3837 c = c - 'a' + 10;
3838 if (c1 >= '0' && c1 <= '9')
3839 c1 -= '0';
3840 else
3841 c1 = c1 - 'a' + 10;
3842 cc = c * 16 + c1;
3844 else {
3845 fcp->unget_c(c1);
3846 if (c >= 0 && c <= 63)
3847 cc = c + 64;
3848 else if (c >= 64 && c <= 127)
3849 cc = c - 64;
3850 else
3851 goto fail;
3853 return cc;
3854 fail:
3855 error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
3856 return c;
3859 void hyphen_trie::read_patterns_file(const char *name, int append,
3860 dictionary *ex)
3862 if (!append)
3863 clear();
3865 char buf[WORD_MAX];
3866 for (int i = 0; i < WORD_MAX; i++)
3867 buf[i] = 0;
3868 int num[WORD_MAX+1];
3869 int have_patterns = 0; // we've seen \patterns
3870 int final_pattern = 0; // 1 if we have a trailing closing brace
3871 int have_hyphenation = 0; // we've seen \hyphenation
3872 int final_hyphenation = 0; // 1 if we have a trailing closing brace
3873 int have_keyword = 0; // we've seen either \patterns or \hyphenation
3874 int traditional = 0; // don't handle \patterns
3875 int c;
3876 file_case *fcp;
3877 if ((fcp = mac_path->open_file(name, fcp->fc_const_path)) == NULL) {
3878 error("can't find hyphenation patterns file `%1'", name);
3879 goto jleave;
3882 for (c = hpf_getc(fcp);;) {
3883 for (;;) {
3884 if (c == '%') { // skip comments
3885 do {
3886 c = fcp->get_c();
3887 } while (c != EOF && c != '\n');
3889 if (c == EOF || !csspace(c))
3890 break;
3891 c = hpf_getc(fcp);
3893 if (c == EOF) {
3894 if (have_keyword || traditional) // we are done
3895 break;
3896 else { // rescan file in `traditional' mode
3897 fcp->rewind();
3898 traditional = 1;
3899 c = hpf_getc(fcp);
3900 continue;
3903 int i = 0;
3904 num[0] = 0;
3905 if (!(c == '{' || c == '}')) { // skip braces at line start
3906 do { // scan patterns
3907 if (csdigit(c))
3908 num[i] = c - '0';
3909 else {
3910 buf[i++] = c;
3911 num[i] = 0;
3913 c = hpf_getc(fcp);
3914 } while (i < WORD_MAX && c != EOF && !csspace(c)
3915 && c != '%' && c != '{' && c != '}');
3917 if (!traditional) {
3918 if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
3919 while (csspace(c))
3920 c = hpf_getc(fcp);
3921 if (c == '{') {
3922 if (have_patterns || have_hyphenation)
3923 error("\\patterns not allowed inside of %1 group",
3924 have_patterns ? "\\patterns" : "\\hyphenation");
3925 else {
3926 have_patterns = 1;
3927 have_keyword = 1;
3929 c = hpf_getc(fcp);
3930 continue;
3933 else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
3934 while (csspace(c))
3935 c = hpf_getc(fcp);
3936 if (c == '{') {
3937 if (have_patterns || have_hyphenation)
3938 error("\\hyphenation not allowed inside of %1 group",
3939 have_patterns ? "\\patterns" : "\\hyphenation");
3940 else {
3941 have_hyphenation = 1;
3942 have_keyword = 1;
3944 c = hpf_getc(fcp);
3945 continue;
3948 else if (strstr(buf, "\\endinput")) {
3949 if (have_patterns || have_hyphenation)
3950 error("found \\endinput inside of %1 group",
3951 have_patterns ? "\\patterns" : "\\hyphenation");
3952 break;
3954 else if (c == '}') {
3955 if (have_patterns) {
3956 have_patterns = 0;
3957 if (i > 0)
3958 final_pattern = 1;
3960 else if (have_hyphenation) {
3961 have_hyphenation = 0;
3962 if (i > 0)
3963 final_hyphenation = 1;
3965 c = hpf_getc(fcp);
3967 else if (c == '{') {
3968 if (have_patterns || have_hyphenation)
3969 error("`{' not allowed within %1 group",
3970 have_patterns ? "\\patterns" : "\\hyphenation");
3971 c = hpf_getc(fcp); // skipped if not starting \patterns
3972 // or \hyphenation
3975 else {
3976 if (c == '{' || c == '}')
3977 c = hpf_getc(fcp);
3979 if (i > 0) {
3980 if (have_patterns || final_pattern || traditional) {
3981 for (int j = 0; j < i; j++)
3982 buf[j] = hpf_code_table[(unsigned char)buf[j]];
3983 insert_pattern(buf, i, num);
3984 final_pattern = 0;
3986 else if (have_hyphenation || final_hyphenation) {
3987 insert_hyphenation(ex, buf, i);
3988 final_hyphenation = 0;
3993 delete fcp;
3994 jleave:
3995 return;
3998 void hyphenate(hyphen_list *h, unsigned flags)
4000 if (!current_language)
4001 return;
4002 while (h) {
4003 while (h && h->hyphenation_code == 0)
4004 h = h->next;
4005 int len = 0;
4006 char hbuf[WORD_MAX + 2];
4007 char *buf = hbuf + 1;
4008 hyphen_list *tem;
4009 for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
4010 if (tem->hyphenation_code != 0)
4011 buf[len++] = tem->hyphenation_code;
4012 else
4013 break;
4015 hyphen_list *nexth = tem;
4016 if (len > 2) {
4017 buf[len] = 0;
4018 unsigned char *pos
4019 = (unsigned char *)current_language->exceptions.lookup(buf);
4020 if (pos != 0) {
4021 int j = 0;
4022 int i = 1;
4023 for (tem = h; tem != 0; tem = tem->next, i++)
4024 if (pos[j] == i) {
4025 tem->hyphen = 1;
4026 j++;
4029 else {
4030 hbuf[0] = hbuf[len + 1] = '.';
4031 int num[WORD_MAX + 3];
4032 current_language->patterns.hyphenate(hbuf, len + 2, num);
4033 int i;
4034 num[2] = 0;
4035 if (flags & HYPHEN_FIRST_CHARS)
4036 num[3] = 0;
4037 if (flags & HYPHEN_LAST_CHARS)
4038 --len;
4039 for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
4040 if (num[i] & 1)
4041 tem->hyphen = 1;
4044 h = nexth;
4048 static void do_hyphenation_patterns_file(int append)
4050 symbol name = get_long_name(1);
4051 if (!name.is_null()) {
4052 if (!current_language)
4053 error("no current hyphenation language");
4054 else
4055 current_language->patterns.read_patterns_file(
4056 name.contents(), append,
4057 &current_language->exceptions);
4059 skip_line();
4062 static void hyphenation_patterns_file()
4064 do_hyphenation_patterns_file(0);
4067 static void hyphenation_patterns_file_append()
4069 do_hyphenation_patterns_file(1);
4072 class hyphenation_language_reg : public reg {
4073 public:
4074 const char *get_string();
4077 const char *hyphenation_language_reg::get_string()
4079 return current_language ? current_language->name.contents() : "";
4082 void init_hyphen_requests()
4084 init_request("hw", hyphen_word);
4085 init_request("hla", set_hyphenation_language);
4086 init_request("hpf", hyphenation_patterns_file);
4087 init_request("hpfa", hyphenation_patterns_file_append);
4088 number_reg_dictionary.define(".hla", new hyphenation_language_reg);
4091 // s-it2-mode