Adapt src/roff (src/roff/groff)
[s-roff.git] / src / roff / troff / env.cpp
blob5e93625c606ec33de058f4713012d667a16da756
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005,
3 2006
4 Free Software Foundation, Inc.
5 Written by James Clark (jjc@jclark.com)
7 This file is part of groff.
9 groff is free software; you can redistribute it and/or modify it under
10 the terms of the GNU General Public License as published by the Free
11 Software Foundation; either version 2, or (at your option) any later
12 version.
14 groff is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 for more details.
19 You should have received a copy of the GNU General Public License along
20 with groff; see the file COPYING. If not, write to the Free Software
21 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
23 #include "troff.h"
24 #include "dictionary.h"
25 #include "hvunits.h"
26 #include "stringclass.h"
27 #include "mtsm.h"
28 #include "env.h"
29 #include "file_case.h"
30 #include "request.h"
31 #include "node.h"
32 #include "token.h"
33 #include "div.h"
34 #include "reg.h"
35 #include "font.h"
36 #include "charinfo.h"
37 #include "macropath.h"
38 #include "input.h"
39 #include <math.h>
41 symbol default_family("T");
43 enum { ADJUST_LEFT = 0, ADJUST_BOTH = 1, ADJUST_CENTER = 3, ADJUST_RIGHT = 5 };
45 enum { HYPHEN_LAST_LINE = 2, HYPHEN_LAST_CHARS = 4, HYPHEN_FIRST_CHARS = 8 };
47 struct env_list {
48 environment *env;
49 env_list *next;
50 env_list(environment *e, env_list *p) : env(e), next(p) {}
53 env_list *env_stack;
54 const int NENVIRONMENTS = 10;
55 environment *env_table[NENVIRONMENTS];
56 dictionary env_dictionary(10);
57 environment *curenv;
58 static int next_line_number = 0;
59 extern int suppress_push;
60 extern statem *get_diversion_state();
62 charinfo *field_delimiter_char;
63 charinfo *padding_indicator_char;
65 int translate_space_to_dummy = 0;
67 class pending_output_line {
68 node *nd;
69 int no_fill;
70 int was_centered;
71 vunits vs;
72 vunits post_vs;
73 hunits width;
74 #ifdef WIDOW_CONTROL
75 int last_line; // Is it the last line of the paragraph?
76 #endif /* WIDOW_CONTROL */
77 public:
78 pending_output_line *next;
80 pending_output_line(node *, int, vunits, vunits, hunits, int,
81 pending_output_line * = 0);
82 ~pending_output_line();
83 int output();
85 #ifdef WIDOW_CONTROL
86 friend void environment::mark_last_line();
87 friend void environment::output(node *, int, vunits, vunits, hunits, int);
88 #endif /* WIDOW_CONTROL */
91 pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv,
92 hunits w, int ce,
93 pending_output_line *p)
94 : nd(n), no_fill(nf), was_centered(ce), vs(v), post_vs(pv), width(w),
95 #ifdef WIDOW_CONTROL
96 last_line(0),
97 #endif /* WIDOW_CONTROL */
98 next(p)
102 pending_output_line::~pending_output_line()
104 delete_node_list(nd);
107 int pending_output_line::output()
109 if (trap_sprung_flag)
110 return 0;
111 #ifdef WIDOW_CONTROL
112 if (next && next->last_line && !no_fill) {
113 curdiv->need(vs + post_vs + vunits(vresolution));
114 if (trap_sprung_flag) {
115 next->last_line = 0; // Try to avoid infinite loops.
116 return 0;
119 #endif
120 curenv->construct_format_state(nd, was_centered, !no_fill);
121 curdiv->output(nd, no_fill, vs, post_vs, width);
122 nd = 0;
123 return 1;
126 void environment::output(node *nd, int no_fill_flag,
127 vunits vs, vunits post_vs,
128 hunits width, int was_centered)
130 #ifdef WIDOW_CONTROL
131 while (pending_lines) {
132 if (widow_control && !pending_lines->no_fill && !pending_lines->next)
133 break;
134 if (!pending_lines->output())
135 break;
136 pending_output_line *tem = pending_lines;
137 pending_lines = pending_lines->next;
138 delete tem;
140 #else /* WIDOW_CONTROL */
141 output_pending_lines();
142 #endif /* WIDOW_CONTROL */
143 if (!trap_sprung_flag && !pending_lines
144 #ifdef WIDOW_CONTROL
145 && (!widow_control || no_fill_flag)
146 #endif /* WIDOW_CONTROL */
148 curenv->construct_format_state(nd, was_centered, !no_fill_flag);
149 curdiv->output(nd, no_fill_flag, vs, post_vs, width);
150 } else {
151 pending_output_line **p;
152 for (p = &pending_lines; *p; p = &(*p)->next)
154 *p = new pending_output_line(nd, no_fill_flag, vs, post_vs, width,
155 was_centered);
159 // a line from .tl goes at the head of the queue
161 void environment::output_title(node *nd, int no_fill_flag,
162 vunits vs, vunits post_vs,
163 hunits width)
165 if (!trap_sprung_flag)
166 curdiv->output(nd, no_fill_flag, vs, post_vs, width);
167 else
168 pending_lines = new pending_output_line(nd, no_fill_flag, vs, post_vs,
169 width, 0, pending_lines);
172 void environment::output_pending_lines()
174 while (pending_lines && pending_lines->output()) {
175 pending_output_line *tem = pending_lines;
176 pending_lines = pending_lines->next;
177 delete tem;
181 #ifdef WIDOW_CONTROL
183 void environment::mark_last_line()
185 if (!widow_control || !pending_lines)
186 return;
187 pending_output_line *p;
188 for (p = pending_lines; p->next; p = p->next)
190 if (!p->no_fill)
191 p->last_line = 1;
194 void widow_control_request()
196 int n;
197 if (has_arg() && get_integer(&n))
198 curenv->widow_control = n != 0;
199 else
200 curenv->widow_control = 1;
201 skip_line();
204 #endif /* WIDOW_CONTROL */
206 /* font_size functions */
208 size_range *font_size::size_table = 0;
209 int font_size::nranges = 0;
211 extern "C" {
213 int compare_ranges(const void *p1, const void *p2)
215 return ((size_range *)p1)->min - ((size_range *)p2)->min;
220 void font_size::init_size_table(int *sizes)
222 nranges = 0;
223 while (sizes[nranges*2] != 0)
224 nranges++;
225 assert(nranges > 0);
226 size_table = new size_range[nranges];
227 for (int i = 0; i < nranges; i++) {
228 size_table[i].min = sizes[i*2];
229 size_table[i].max = sizes[i*2 + 1];
231 qsort(size_table, nranges, sizeof(size_range), compare_ranges);
234 font_size::font_size(int sp)
236 for (int i = 0; i < nranges; i++) {
237 if (sp < size_table[i].min) {
238 if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
239 p = size_table[i - 1].max;
240 else
241 p = size_table[i].min;
242 return;
244 if (sp <= size_table[i].max) {
245 p = sp;
246 return;
249 p = size_table[nranges - 1].max;
252 int font_size::to_units()
254 return scale(p, units_per_inch, sizescale*72);
257 // we can't do this in a static constructor because various dictionaries
258 // have to get initialized first
260 void init_environments()
262 curenv = env_table[0] = new environment("0");
265 void tab_character()
267 curenv->tab_char = get_optional_char();
268 skip_line();
271 void leader_character()
273 curenv->leader_char = get_optional_char();
274 skip_line();
277 void environment::add_char(charinfo *ci)
279 int s;
280 node *gc_np = 0;
281 if (interrupted)
283 // don't allow fields in dummy environments
284 else if (ci == field_delimiter_char && !dummy) {
285 if (current_field)
286 wrap_up_field();
287 else
288 start_field();
290 else if (current_field && ci == padding_indicator_char)
291 add_padding();
292 else if (current_tab) {
293 if (tab_contents == 0)
294 tab_contents = new line_start_node;
295 if (ci != hyphen_indicator_char)
296 tab_contents = tab_contents->add_char(ci, this, &tab_width, &s, &gc_np);
297 else
298 tab_contents = tab_contents->add_discretionary_hyphen();
300 else {
301 if (line == 0)
302 start_line();
303 #if 0
304 fprintf(stderr, "current line is\n");
305 line->debug_node_list();
306 #endif
307 if (ci != hyphen_indicator_char)
308 line = line->add_char(ci, this, &width_total, &space_total, &gc_np);
309 else
310 line = line->add_discretionary_hyphen();
312 #if 0
313 fprintf(stderr, "now after we have added character the line is\n");
314 line->debug_node_list();
315 #endif
316 if ((!suppress_push) && gc_np) {
317 if (gc_np && (gc_np->state == 0)) {
318 gc_np->state = construct_state(0);
319 gc_np->push_state = get_diversion_state();
321 else if (line && (line->state == 0)) {
322 line->state = construct_state(0);
323 line->push_state = get_diversion_state();
326 #if 0
327 fprintf(stderr, "now we have possibly added the state the line is\n");
328 line->debug_node_list();
329 #endif
332 node *environment::make_char_node(charinfo *ci)
334 return make_node(ci, this);
337 void environment::add_node(node *n)
339 if (n == 0)
340 return;
341 if (!suppress_push) {
342 if (n->is_special && n->state == NULL)
343 n->state = construct_state(0);
344 n->push_state = get_diversion_state();
347 if (current_tab || current_field)
348 n->freeze_space();
349 if (interrupted) {
350 delete n;
352 else if (current_tab) {
353 n->next = tab_contents;
354 tab_contents = n;
355 tab_width += n->width();
357 else {
358 if (line == 0) {
359 if (discarding && n->discardable()) {
360 // XXX possibly: input_line_start -= n->width();
361 delete n;
362 return;
364 start_line();
366 width_total += n->width();
367 space_total += n->nspaces();
368 n->next = line;
369 line = n;
370 construct_new_line_state(line);
374 void environment::add_hyphen_indicator()
376 if (current_tab || interrupted || current_field
377 || hyphen_indicator_char != 0)
378 return;
379 if (line == 0)
380 start_line();
381 line = line->add_discretionary_hyphen();
384 int environment::get_hyphenation_flags()
386 return hyphenation_flags;
389 int environment::get_hyphen_line_max()
391 return hyphen_line_max;
394 int environment::get_hyphen_line_count()
396 return hyphen_line_count;
399 int environment::get_center_lines()
401 return center_lines;
404 int environment::get_right_justify_lines()
406 return right_justify_lines;
409 void environment::add_italic_correction()
411 if (current_tab) {
412 if (tab_contents)
413 tab_contents = tab_contents->add_italic_correction(&tab_width);
415 else if (line)
416 line = line->add_italic_correction(&width_total);
419 void environment::space_newline()
421 assert(!current_tab && !current_field);
422 if (interrupted)
423 return;
424 hunits x = H0;
425 hunits sw = env_space_width(this);
426 hunits ssw = env_sentence_space_width(this);
427 if (!translate_space_to_dummy) {
428 x = sw;
429 if (node_list_ends_sentence(line) == 1)
430 x += ssw;
432 width_list *w = new width_list(sw, ssw);
433 if (node_list_ends_sentence(line) == 1)
434 w->next = new width_list(sw, ssw);
435 if (line != 0 && line->merge_space(x, sw, ssw)) {
436 width_total += x;
437 return;
439 add_node(new word_space_node(x, get_fill_color(), w));
440 possibly_break_line(0, spread_flag);
441 spread_flag = 0;
444 void environment::space()
446 space(env_space_width(this), env_sentence_space_width(this));
449 void environment::space(hunits space_width, hunits sentence_space_width)
451 if (interrupted)
452 return;
453 if (current_field && padding_indicator_char == 0) {
454 add_padding();
455 return;
457 hunits x = translate_space_to_dummy ? H0 : space_width;
458 node *p = current_tab ? tab_contents : line;
459 hunits *tp = current_tab ? &tab_width : &width_total;
460 if (p && p->nspaces() == 1 && p->width() == x
461 && node_list_ends_sentence(p->next) == 1) {
462 hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
463 if (p->merge_space(xx, space_width, sentence_space_width)) {
464 *tp += xx;
465 return;
468 if (p && p->merge_space(x, space_width, sentence_space_width)) {
469 *tp += x;
470 return;
472 add_node(new word_space_node(x,
473 get_fill_color(),
474 new width_list(space_width,
475 sentence_space_width)));
476 possibly_break_line(0, spread_flag);
477 spread_flag = 0;
480 node *do_underline_special(int);
482 void environment::set_font(symbol nm)
484 if (interrupted)
485 return;
486 if (nm == symbol("P") || nm.is_empty()) {
487 if (family->make_definite(prev_fontno) < 0)
488 return;
489 int tem = fontno;
490 fontno = prev_fontno;
491 prev_fontno = tem;
493 else {
494 prev_fontno = fontno;
495 int n = symbol_fontno(nm);
496 if (n < 0) {
497 n = next_available_font_position();
498 if (!mount_font(n, nm))
499 return;
501 if (family->make_definite(n) < 0)
502 return;
503 fontno = n;
505 if (underline_spaces && fontno != prev_fontno) {
506 if (fontno == get_underline_fontno())
507 add_node(do_underline_special(1));
508 if (prev_fontno == get_underline_fontno())
509 add_node(do_underline_special(0));
513 void environment::set_font(int n)
515 if (interrupted)
516 return;
517 if (is_good_fontno(n)) {
518 prev_fontno = fontno;
519 fontno = n;
521 else
522 warning(WARN_FONT, "bad font number");
525 void environment::set_family(symbol fam)
527 if (interrupted)
528 return;
529 if (fam.is_null() || fam.is_empty()) {
530 if (prev_family->make_definite(fontno) < 0)
531 return;
532 font_family *tem = family;
533 family = prev_family;
534 prev_family = tem;
536 else {
537 font_family *f = lookup_family(fam);
538 if (f->make_definite(fontno) < 0)
539 return;
540 prev_family = family;
541 family = f;
545 void environment::set_size(int n)
547 if (interrupted)
548 return;
549 if (n == 0) {
550 font_size temp = prev_size;
551 prev_size = size;
552 size = temp;
553 int temp2 = prev_requested_size;
554 prev_requested_size = requested_size;
555 requested_size = temp2;
557 else {
558 prev_size = size;
559 size = font_size(n);
560 prev_requested_size = requested_size;
561 requested_size = n;
565 void environment::set_char_height(int n)
567 if (interrupted)
568 return;
569 if (n == requested_size || n <= 0)
570 char_height = 0;
571 else
572 char_height = n;
575 void environment::set_char_slant(int n)
577 if (interrupted)
578 return;
579 char_slant = n;
582 color *environment::get_prev_glyph_color()
584 return prev_glyph_color;
587 color *environment::get_glyph_color()
589 return glyph_color;
592 color *environment::get_prev_fill_color()
594 return prev_fill_color;
597 color *environment::get_fill_color()
599 return fill_color;
602 void environment::set_glyph_color(color *c)
604 if (interrupted)
605 return;
606 curenv->prev_glyph_color = curenv->glyph_color;
607 curenv->glyph_color = c;
610 void environment::set_fill_color(color *c)
612 if (interrupted)
613 return;
614 curenv->prev_fill_color = curenv->fill_color;
615 curenv->fill_color = c;
618 environment::environment(symbol nm)
619 : dummy(0),
620 prev_line_length((units_per_inch*13)/2),
621 line_length((units_per_inch*13)/2),
622 prev_title_length((units_per_inch*13)/2),
623 title_length((units_per_inch*13)/2),
624 prev_size(sizescale*10),
625 size(sizescale*10),
626 requested_size(sizescale*10),
627 prev_requested_size(sizescale*10),
628 char_height(0),
629 char_slant(0),
630 space_size(12),
631 sentence_space_size(12),
632 adjust_mode(ADJUST_BOTH),
633 fill(1),
634 interrupted(0),
635 prev_line_interrupted(0),
636 center_lines(0),
637 right_justify_lines(0),
638 prev_vertical_spacing(points_to_units(12)),
639 vertical_spacing(points_to_units(12)),
640 prev_post_vertical_spacing(0),
641 post_vertical_spacing(0),
642 prev_line_spacing(1),
643 line_spacing(1),
644 prev_indent(0),
645 indent(0),
646 temporary_indent(0),
647 have_temporary_indent(0),
648 underline_lines(0),
649 underline_spaces(0),
650 input_trap_count(0),
651 continued_input_trap(0),
652 line(0),
653 prev_text_length(0),
654 width_total(0),
655 space_total(0),
656 input_line_start(0),
657 line_tabs(0),
658 current_tab(TAB_NONE),
659 leader_node(0),
660 tab_char(0),
661 leader_char(charset_table['.']),
662 current_field(0),
663 discarding(0),
664 spread_flag(0),
665 margin_character_flags(0),
666 margin_character_node(0),
667 margin_character_distance(points_to_units(10)),
668 numbering_nodes(0),
669 number_text_separation(1),
670 line_number_indent(0),
671 line_number_multiple(1),
672 no_number_count(0),
673 hyphenation_flags(1),
674 hyphen_line_count(0),
675 hyphen_line_max(-1),
676 hyphenation_space(H0),
677 hyphenation_margin(H0),
678 composite(0),
679 pending_lines(0),
680 #ifdef WIDOW_CONTROL
681 widow_control(0),
682 #endif /* WIDOW_CONTROL */
683 glyph_color(&default_color),
684 prev_glyph_color(&default_color),
685 fill_color(&default_color),
686 prev_fill_color(&default_color),
687 seen_space(0),
688 seen_eol(0),
689 suppress_next_eol(0),
690 seen_break(0),
691 tabs(units_per_inch/2, TAB_LEFT),
692 name(nm),
693 control_char('.'),
694 no_break_control_char('\''),
695 hyphen_indicator_char(0)
697 prev_family = family = lookup_family(default_family);
698 prev_fontno = fontno = 1;
699 if (!is_good_fontno(1))
700 fatal("font number 1 not a valid font");
701 if (family->make_definite(1) < 0)
702 fatal("invalid default family `%1'", default_family.contents());
703 prev_fontno = fontno;
706 environment::environment(const environment *e)
707 : dummy(1),
708 prev_line_length(e->prev_line_length),
709 line_length(e->line_length),
710 prev_title_length(e->prev_title_length),
711 title_length(e->title_length),
712 prev_size(e->prev_size),
713 size(e->size),
714 requested_size(e->requested_size),
715 prev_requested_size(e->prev_requested_size),
716 char_height(e->char_height),
717 char_slant(e->char_slant),
718 prev_fontno(e->prev_fontno),
719 fontno(e->fontno),
720 prev_family(e->prev_family),
721 family(e->family),
722 space_size(e->space_size),
723 sentence_space_size(e->sentence_space_size),
724 adjust_mode(e->adjust_mode),
725 fill(e->fill),
726 interrupted(0),
727 prev_line_interrupted(0),
728 center_lines(0),
729 right_justify_lines(0),
730 prev_vertical_spacing(e->prev_vertical_spacing),
731 vertical_spacing(e->vertical_spacing),
732 prev_post_vertical_spacing(e->prev_post_vertical_spacing),
733 post_vertical_spacing(e->post_vertical_spacing),
734 prev_line_spacing(e->prev_line_spacing),
735 line_spacing(e->line_spacing),
736 prev_indent(e->prev_indent),
737 indent(e->indent),
738 temporary_indent(0),
739 have_temporary_indent(0),
740 underline_lines(0),
741 underline_spaces(0),
742 input_trap_count(0),
743 continued_input_trap(0),
744 line(0),
745 prev_text_length(e->prev_text_length),
746 width_total(0),
747 space_total(0),
748 input_line_start(0),
749 line_tabs(e->line_tabs),
750 current_tab(TAB_NONE),
751 leader_node(0),
752 tab_char(e->tab_char),
753 leader_char(e->leader_char),
754 current_field(0),
755 discarding(0),
756 spread_flag(0),
757 margin_character_flags(e->margin_character_flags),
758 margin_character_node(e->margin_character_node),
759 margin_character_distance(e->margin_character_distance),
760 numbering_nodes(0),
761 number_text_separation(e->number_text_separation),
762 line_number_indent(e->line_number_indent),
763 line_number_multiple(e->line_number_multiple),
764 no_number_count(e->no_number_count),
765 hyphenation_flags(e->hyphenation_flags),
766 hyphen_line_count(0),
767 hyphen_line_max(e->hyphen_line_max),
768 hyphenation_space(e->hyphenation_space),
769 hyphenation_margin(e->hyphenation_margin),
770 composite(0),
771 pending_lines(0),
772 #ifdef WIDOW_CONTROL
773 widow_control(e->widow_control),
774 #endif /* WIDOW_CONTROL */
775 glyph_color(e->glyph_color),
776 prev_glyph_color(e->prev_glyph_color),
777 fill_color(e->fill_color),
778 prev_fill_color(e->prev_fill_color),
779 seen_space(e->seen_space),
780 seen_eol(e->seen_eol),
781 suppress_next_eol(e->suppress_next_eol),
782 seen_break(e->seen_break),
783 tabs(e->tabs),
784 name(e->name), // so that eg `.if "\n[.ev]"0"' works
785 control_char(e->control_char),
786 no_break_control_char(e->no_break_control_char),
787 hyphen_indicator_char(e->hyphen_indicator_char)
791 void environment::copy(const environment *e)
793 prev_line_length = e->prev_line_length;
794 line_length = e->line_length;
795 prev_title_length = e->prev_title_length;
796 title_length = e->title_length;
797 prev_size = e->prev_size;
798 size = e->size;
799 prev_requested_size = e->prev_requested_size;
800 requested_size = e->requested_size;
801 char_height = e->char_height;
802 char_slant = e->char_slant;
803 space_size = e->space_size;
804 sentence_space_size = e->sentence_space_size;
805 adjust_mode = e->adjust_mode;
806 fill = e->fill;
807 interrupted = 0;
808 prev_line_interrupted = 0;
809 center_lines = 0;
810 right_justify_lines = 0;
811 prev_vertical_spacing = e->prev_vertical_spacing;
812 vertical_spacing = e->vertical_spacing;
813 prev_post_vertical_spacing = e->prev_post_vertical_spacing,
814 post_vertical_spacing = e->post_vertical_spacing,
815 prev_line_spacing = e->prev_line_spacing;
816 line_spacing = e->line_spacing;
817 prev_indent = e->prev_indent;
818 indent = e->indent;
819 have_temporary_indent = 0;
820 temporary_indent = 0;
821 underline_lines = 0;
822 underline_spaces = 0;
823 input_trap_count = 0;
824 continued_input_trap = 0;
825 prev_text_length = e->prev_text_length;
826 width_total = 0;
827 space_total = 0;
828 input_line_start = 0;
829 control_char = e->control_char;
830 no_break_control_char = e->no_break_control_char;
831 hyphen_indicator_char = e->hyphen_indicator_char;
832 spread_flag = 0;
833 line = 0;
834 pending_lines = 0;
835 discarding = 0;
836 tabs = e->tabs;
837 line_tabs = e->line_tabs;
838 current_tab = TAB_NONE;
839 current_field = 0;
840 margin_character_flags = e->margin_character_flags;
841 if (e->margin_character_node)
842 margin_character_node = e->margin_character_node->copy();
843 margin_character_distance = e->margin_character_distance;
844 numbering_nodes = 0;
845 number_text_separation = e->number_text_separation;
846 line_number_multiple = e->line_number_multiple;
847 line_number_indent = e->line_number_indent;
848 no_number_count = e->no_number_count;
849 tab_char = e->tab_char;
850 leader_char = e->leader_char;
851 hyphenation_flags = e->hyphenation_flags;
852 fontno = e->fontno;
853 prev_fontno = e->prev_fontno;
854 dummy = e->dummy;
855 family = e->family;
856 prev_family = e->prev_family;
857 leader_node = 0;
858 #ifdef WIDOW_CONTROL
859 widow_control = e->widow_control;
860 #endif /* WIDOW_CONTROL */
861 hyphen_line_max = e->hyphen_line_max;
862 hyphen_line_count = 0;
863 hyphenation_space = e->hyphenation_space;
864 hyphenation_margin = e->hyphenation_margin;
865 composite = 0;
866 glyph_color= e->glyph_color;
867 prev_glyph_color = e->prev_glyph_color;
868 fill_color = e->fill_color;
869 prev_fill_color = e->prev_fill_color;
872 environment::~environment()
874 delete leader_node;
875 delete_node_list(line);
876 delete_node_list(numbering_nodes);
879 hunits environment::get_input_line_position()
881 hunits n;
882 if (line == 0)
883 n = -input_line_start;
884 else
885 n = width_total - input_line_start;
886 if (current_tab)
887 n += tab_width;
888 return n;
891 void environment::set_input_line_position(hunits n)
893 input_line_start = line == 0 ? -n : width_total - n;
894 if (current_tab)
895 input_line_start += tab_width;
898 hunits environment::get_line_length()
900 return line_length;
903 hunits environment::get_saved_line_length()
905 if (line)
906 return target_text_length + saved_indent;
907 else
908 return line_length;
911 vunits environment::get_vertical_spacing()
913 return vertical_spacing;
916 vunits environment::get_post_vertical_spacing()
918 return post_vertical_spacing;
921 int environment::get_line_spacing()
923 return line_spacing;
926 vunits environment::total_post_vertical_spacing()
928 vunits tem(post_vertical_spacing);
929 if (line_spacing > 1)
930 tem += (line_spacing - 1)*vertical_spacing;
931 return tem;
934 int environment::get_bold()
936 return get_bold_fontno(fontno);
939 hunits environment::get_digit_width()
941 return env_digit_width(this);
944 int environment::get_adjust_mode()
946 return adjust_mode;
949 int environment::get_fill()
951 return fill;
954 hunits environment::get_indent()
956 return indent;
959 hunits environment::get_saved_indent()
961 if (line)
962 return saved_indent;
963 else if (have_temporary_indent)
964 return temporary_indent;
965 else
966 return indent;
969 hunits environment::get_temporary_indent()
971 return temporary_indent;
974 hunits environment::get_title_length()
976 return title_length;
979 node *environment::get_prev_char()
981 for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
982 node *last = n->last_char_node();
983 if (last)
984 return last;
986 return 0;
989 hunits environment::get_prev_char_width()
991 node *last = get_prev_char();
992 if (!last)
993 return H0;
994 return last->width();
997 hunits environment::get_prev_char_skew()
999 node *last = get_prev_char();
1000 if (!last)
1001 return H0;
1002 return last->skew();
1005 vunits environment::get_prev_char_height()
1007 node *last = get_prev_char();
1008 if (!last)
1009 return V0;
1010 vunits min, max;
1011 last->vertical_extent(&min, &max);
1012 return -min;
1015 vunits environment::get_prev_char_depth()
1017 node *last = get_prev_char();
1018 if (!last)
1019 return V0;
1020 vunits min, max;
1021 last->vertical_extent(&min, &max);
1022 return max;
1025 hunits environment::get_text_length()
1027 hunits n = line == 0 ? H0 : width_total;
1028 if (current_tab)
1029 n += tab_width;
1030 return n;
1033 hunits environment::get_prev_text_length()
1035 return prev_text_length;
1039 static int sb_reg_contents = 0;
1040 static int st_reg_contents = 0;
1041 static int ct_reg_contents = 0;
1042 static int rsb_reg_contents = 0;
1043 static int rst_reg_contents = 0;
1044 static int skw_reg_contents = 0;
1045 static int ssc_reg_contents = 0;
1047 void environment::width_registers()
1049 // this is used to implement \w; it sets the st, sb, ct registers
1050 vunits min = 0, max = 0, cur = 0;
1051 int character_type = 0;
1052 ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
1053 skw_reg_contents = line ? line->skew().to_units() : 0;
1054 line = reverse_node_list(line);
1055 vunits real_min = V0;
1056 vunits real_max = V0;
1057 vunits v1, v2;
1058 for (node *tem = line; tem; tem = tem->next) {
1059 tem->vertical_extent(&v1, &v2);
1060 v1 += cur;
1061 if (v1 < real_min)
1062 real_min = v1;
1063 v2 += cur;
1064 if (v2 > real_max)
1065 real_max = v2;
1066 if ((cur += tem->vertical_width()) < min)
1067 min = cur;
1068 else if (cur > max)
1069 max = cur;
1070 character_type |= tem->character_type();
1072 line = reverse_node_list(line);
1073 st_reg_contents = -min.to_units();
1074 sb_reg_contents = -max.to_units();
1075 rst_reg_contents = -real_min.to_units();
1076 rsb_reg_contents = -real_max.to_units();
1077 ct_reg_contents = character_type;
1080 node *environment::extract_output_line()
1082 if (current_tab)
1083 wrap_up_tab();
1084 node *n = line;
1085 line = 0;
1086 return n;
1089 /* environment related requests */
1091 void environment_switch()
1093 int pop = 0; // 1 means pop, 2 means pop but no error message on underflow
1094 if (curenv->is_dummy())
1095 error("can't switch environments when current environment is dummy");
1096 else if (!has_arg())
1097 pop = 1;
1098 else {
1099 symbol nm;
1100 if (!tok.delimiter()) {
1101 // It looks like a number.
1102 int n;
1103 if (get_integer(&n)) {
1104 if (n >= 0 && n < NENVIRONMENTS) {
1105 env_stack = new env_list(curenv, env_stack);
1106 if (env_table[n] == 0)
1107 env_table[n] = new environment(i_to_a(n));
1108 curenv = env_table[n];
1110 else
1111 nm = i_to_a(n);
1113 else
1114 pop = 2;
1116 else {
1117 nm = get_long_name(1);
1118 if (nm.is_null())
1119 pop = 2;
1121 if (!nm.is_null()) {
1122 environment *e = (environment *)env_dictionary.lookup(nm);
1123 if (!e) {
1124 e = new environment(nm);
1125 (void)env_dictionary.lookup(nm, e);
1127 env_stack = new env_list(curenv, env_stack);
1128 curenv = e;
1131 if (pop) {
1132 if (env_stack == 0) {
1133 if (pop == 1)
1134 error("environment stack underflow");
1136 else {
1137 int seen_space = curenv->seen_space;
1138 int seen_eol = curenv->seen_eol;
1139 int suppress_next_eol = curenv->suppress_next_eol;
1140 curenv = env_stack->env;
1141 curenv->seen_space = seen_space;
1142 curenv->seen_eol = seen_eol;
1143 curenv->suppress_next_eol = suppress_next_eol;
1144 env_list *tem = env_stack;
1145 env_stack = env_stack->next;
1146 delete tem;
1149 skip_line();
1152 void environment_copy()
1154 symbol nm;
1155 environment *e=0;
1156 tok.skip();
1157 if (!tok.delimiter()) {
1158 // It looks like a number.
1159 int n;
1160 if (get_integer(&n)) {
1161 if (n >= 0 && n < NENVIRONMENTS)
1162 e = env_table[n];
1163 else
1164 nm = i_to_a(n);
1167 else
1168 nm = get_long_name(1);
1169 if (!e && !nm.is_null())
1170 e = (environment *)env_dictionary.lookup(nm);
1171 if (e == 0) {
1172 error("No environment to copy from");
1173 return;
1175 else
1176 curenv->copy(e);
1177 skip_line();
1180 void fill_color_change()
1182 symbol s = get_name();
1183 if (s.is_null())
1184 curenv->set_fill_color(curenv->get_prev_fill_color());
1185 else
1186 do_fill_color(s);
1187 skip_line();
1190 void glyph_color_change()
1192 symbol s = get_name();
1193 if (s.is_null())
1194 curenv->set_glyph_color(curenv->get_prev_glyph_color());
1195 else
1196 do_glyph_color(s);
1197 skip_line();
1200 static symbol P_symbol("P");
1202 void font_change()
1204 symbol s = get_name();
1205 int is_number = 1;
1206 if (s.is_null() || s == P_symbol) {
1207 s = P_symbol;
1208 is_number = 0;
1210 else {
1211 for (const char *p = s.contents(); p != 0 && *p != 0; p++)
1212 if (!csdigit(*p)) {
1213 is_number = 0;
1214 break;
1217 if (is_number)
1218 curenv->set_font(atoi(s.contents()));
1219 else
1220 curenv->set_font(s);
1221 skip_line();
1224 void family_change()
1226 symbol s = get_name();
1227 curenv->set_family(s);
1228 skip_line();
1231 void point_size()
1233 int n;
1234 if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) {
1235 if (n <= 0)
1236 n = 1;
1237 curenv->set_size(n);
1239 else
1240 curenv->set_size(0);
1241 skip_line();
1244 void override_sizes()
1246 int n = 16;
1247 int *sizes = new int[n];
1248 int i = 0;
1249 char *buf = read_string();
1250 if (!buf)
1251 return;
1252 char *p = strtok(buf, " \t");
1253 for (;;) {
1254 if (!p)
1255 break;
1256 int lower, upper;
1257 switch (sscanf(p, "%d-%d", &lower, &upper)) {
1258 case 1:
1259 upper = lower;
1260 // fall through
1261 case 2:
1262 if (lower <= upper && lower >= 0)
1263 break;
1264 // fall through
1265 default:
1266 warning(WARN_RANGE, "bad size range `%1'", p);
1267 return;
1269 if (i + 2 > n) {
1270 int *old_sizes = sizes;
1271 sizes = new int[n*2];
1272 memcpy(sizes, old_sizes, n*sizeof(int));
1273 n *= 2;
1274 a_delete old_sizes;
1276 sizes[i++] = lower;
1277 if (lower == 0)
1278 break;
1279 sizes[i++] = upper;
1280 p = strtok(0, " \t");
1282 font_size::init_size_table(sizes);
1285 void space_size()
1287 int n;
1288 if (get_integer(&n)) {
1289 curenv->space_size = n;
1290 if (has_arg() && get_integer(&n))
1291 curenv->sentence_space_size = n;
1292 else
1293 curenv->sentence_space_size = curenv->space_size;
1295 skip_line();
1298 void fill()
1300 while (!tok.newline() && !tok.eof())
1301 tok.next();
1302 if (break_flag)
1303 curenv->do_break();
1304 curenv->fill = 1;
1305 tok.next();
1308 void no_fill()
1310 while (!tok.newline() && !tok.eof())
1311 tok.next();
1312 if (break_flag)
1313 curenv->do_break();
1314 curenv->fill = 0;
1315 curenv->suppress_next_eol = 1;
1316 tok.next();
1319 void center()
1321 int n;
1322 if (!has_arg() || !get_integer(&n))
1323 n = 1;
1324 else if (n < 0)
1325 n = 0;
1326 while (!tok.newline() && !tok.eof())
1327 tok.next();
1328 if (break_flag)
1329 curenv->do_break();
1330 curenv->right_justify_lines = 0;
1331 curenv->center_lines = n;
1332 curdiv->modified_tag.incl(MTSM_CE);
1333 tok.next();
1336 void right_justify()
1338 int n;
1339 if (!has_arg() || !get_integer(&n))
1340 n = 1;
1341 else if (n < 0)
1342 n = 0;
1343 while (!tok.newline() && !tok.eof())
1344 tok.next();
1345 if (break_flag)
1346 curenv->do_break();
1347 curenv->center_lines = 0;
1348 curenv->right_justify_lines = n;
1349 curdiv->modified_tag.incl(MTSM_RJ);
1350 tok.next();
1353 void line_length()
1355 hunits temp;
1356 if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) {
1357 if (temp < H0) {
1358 warning(WARN_RANGE, "bad line length %1u", temp.to_units());
1359 temp = H0;
1362 else
1363 temp = curenv->prev_line_length;
1364 curenv->prev_line_length = curenv->line_length;
1365 curenv->line_length = temp;
1366 curdiv->modified_tag.incl(MTSM_LL);
1367 skip_line();
1370 void title_length()
1372 hunits temp;
1373 if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) {
1374 if (temp < H0) {
1375 warning(WARN_RANGE, "bad title length %1u", temp.to_units());
1376 temp = H0;
1379 else
1380 temp = curenv->prev_title_length;
1381 curenv->prev_title_length = curenv->title_length;
1382 curenv->title_length = temp;
1383 skip_line();
1386 void vertical_spacing()
1388 vunits temp;
1389 if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) {
1390 if (temp < V0) {
1391 warning(WARN_RANGE, "vertical spacing must not be negative");
1392 temp = vresolution;
1395 else
1396 temp = curenv->prev_vertical_spacing;
1397 curenv->prev_vertical_spacing = curenv->vertical_spacing;
1398 curenv->vertical_spacing = temp;
1399 skip_line();
1402 void post_vertical_spacing()
1404 vunits temp;
1405 if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) {
1406 if (temp < V0) {
1407 warning(WARN_RANGE,
1408 "post vertical spacing must be greater than or equal to 0");
1409 temp = V0;
1412 else
1413 temp = curenv->prev_post_vertical_spacing;
1414 curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
1415 curenv->post_vertical_spacing = temp;
1416 skip_line();
1419 void line_spacing()
1421 int temp;
1422 if (has_arg() && get_integer(&temp)) {
1423 if (temp < 1) {
1424 warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
1425 temp = 1;
1428 else
1429 temp = curenv->prev_line_spacing;
1430 curenv->prev_line_spacing = curenv->line_spacing;
1431 curenv->line_spacing = temp;
1432 skip_line();
1435 void indent()
1437 hunits temp;
1438 if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) {
1439 if (temp < H0) {
1440 warning(WARN_RANGE, "indent cannot be negative");
1441 temp = H0;
1444 else
1445 temp = curenv->prev_indent;
1446 while (!tok.newline() && !tok.eof())
1447 tok.next();
1448 if (break_flag)
1449 curenv->do_break();
1450 curenv->have_temporary_indent = 0;
1451 curenv->prev_indent = curenv->indent;
1452 curenv->indent = temp;
1453 curdiv->modified_tag.incl(MTSM_IN);
1454 tok.next();
1457 void temporary_indent()
1459 int err = 0;
1460 hunits temp;
1461 if (!get_hunits(&temp, 'm', curenv->get_indent()))
1462 err = 1;
1463 while (!tok.newline() && !tok.eof())
1464 tok.next();
1465 if (break_flag)
1466 curenv->do_break();
1467 if (temp < H0) {
1468 warning(WARN_RANGE, "total indent cannot be negative");
1469 temp = H0;
1471 if (!err) {
1472 curenv->temporary_indent = temp;
1473 curenv->have_temporary_indent = 1;
1474 curdiv->modified_tag.incl(MTSM_TI);
1476 tok.next();
1479 node *do_underline_special(int underline_spaces)
1481 macro m;
1482 m.append_str("x u ");
1483 m.append(underline_spaces + '0');
1484 return new special_node(m, 1);
1487 void do_underline(int underline_spaces)
1489 int n;
1490 if (!has_arg() || !get_integer(&n))
1491 n = 1;
1492 if (n <= 0) {
1493 if (curenv->underline_lines > 0) {
1494 curenv->prev_fontno = curenv->fontno;
1495 curenv->fontno = curenv->pre_underline_fontno;
1496 if (underline_spaces) {
1497 curenv->underline_spaces = 0;
1498 curenv->add_node(do_underline_special(0));
1501 curenv->underline_lines = 0;
1503 else {
1504 curenv->underline_lines = n;
1505 curenv->pre_underline_fontno = curenv->fontno;
1506 curenv->fontno = get_underline_fontno();
1507 if (underline_spaces) {
1508 curenv->underline_spaces = 1;
1509 curenv->add_node(do_underline_special(1));
1512 skip_line();
1515 void continuous_underline()
1517 do_underline(1);
1520 void underline()
1522 do_underline(0);
1525 void control_char()
1527 curenv->control_char = '.';
1528 if (has_arg()) {
1529 if (tok.ch() == 0)
1530 error("bad control character");
1531 else
1532 curenv->control_char = tok.ch();
1534 skip_line();
1537 void no_break_control_char()
1539 curenv->no_break_control_char = '\'';
1540 if (has_arg()) {
1541 if (tok.ch() == 0)
1542 error("bad control character");
1543 else
1544 curenv->no_break_control_char = tok.ch();
1546 skip_line();
1549 void margin_character()
1551 while (tok.space())
1552 tok.next();
1553 charinfo *ci = tok.get_char();
1554 if (ci) {
1555 // Call tok.next() only after making the node so that
1556 // .mc \s+9\(br\s0 works.
1557 node *nd = curenv->make_char_node(ci);
1558 tok.next();
1559 if (nd) {
1560 delete curenv->margin_character_node;
1561 curenv->margin_character_node = nd;
1562 curenv->margin_character_flags = MARGIN_CHARACTER_ON
1563 | MARGIN_CHARACTER_NEXT;
1564 hunits d;
1565 if (has_arg() && get_hunits(&d, 'm'))
1566 curenv->margin_character_distance = d;
1569 else {
1570 check_missing_character();
1571 curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON;
1572 if (curenv->margin_character_flags == 0) {
1573 delete curenv->margin_character_node;
1574 curenv->margin_character_node = 0;
1577 skip_line();
1580 void number_lines()
1582 delete_node_list(curenv->numbering_nodes);
1583 curenv->numbering_nodes = 0;
1584 if (has_arg()) {
1585 node *nd = 0;
1586 for (int i = '9'; i >= '0'; i--) {
1587 node *tem = make_node(charset_table[i], curenv);
1588 if (!tem) {
1589 skip_line();
1590 return;
1592 tem->next = nd;
1593 nd = tem;
1595 curenv->numbering_nodes = nd;
1596 curenv->line_number_digit_width = env_digit_width(curenv);
1597 int n;
1598 if (!tok.delimiter()) {
1599 if (get_integer(&n, next_line_number)) {
1600 next_line_number = n;
1601 if (next_line_number < 0) {
1602 warning(WARN_RANGE, "negative line number");
1603 next_line_number = 0;
1607 else
1608 while (!tok.space() && !tok.newline() && !tok.eof())
1609 tok.next();
1610 if (has_arg()) {
1611 if (!tok.delimiter()) {
1612 if (get_integer(&n)) {
1613 if (n <= 0) {
1614 warning(WARN_RANGE, "negative or zero line number multiple");
1616 else
1617 curenv->line_number_multiple = n;
1620 else
1621 while (!tok.space() && !tok.newline() && !tok.eof())
1622 tok.next();
1623 if (has_arg()) {
1624 if (!tok.delimiter()) {
1625 if (get_integer(&n))
1626 curenv->number_text_separation = n;
1628 else
1629 while (!tok.space() && !tok.newline() && !tok.eof())
1630 tok.next();
1631 if (has_arg() && !tok.delimiter() && get_integer(&n))
1632 curenv->line_number_indent = n;
1636 skip_line();
1639 void no_number()
1641 int n;
1642 if (has_arg() && get_integer(&n))
1643 curenv->no_number_count = n > 0 ? n : 0;
1644 else
1645 curenv->no_number_count = 1;
1646 skip_line();
1649 void no_hyphenate()
1651 curenv->hyphenation_flags = 0;
1652 skip_line();
1655 void hyphenate_request()
1657 int n;
1658 if (has_arg() && get_integer(&n))
1659 curenv->hyphenation_flags = n;
1660 else
1661 curenv->hyphenation_flags = 1;
1662 skip_line();
1665 void hyphen_char()
1667 curenv->hyphen_indicator_char = get_optional_char();
1668 skip_line();
1671 void hyphen_line_max_request()
1673 int n;
1674 if (has_arg() && get_integer(&n))
1675 curenv->hyphen_line_max = n;
1676 else
1677 curenv->hyphen_line_max = -1;
1678 skip_line();
1681 void environment::interrupt()
1683 if (!dummy) {
1684 add_node(new transparent_dummy_node);
1685 interrupted = 1;
1689 void environment::newline()
1691 int was_centered = 0;
1692 if (underline_lines > 0) {
1693 if (--underline_lines == 0) {
1694 prev_fontno = fontno;
1695 fontno = pre_underline_fontno;
1696 if (underline_spaces) {
1697 underline_spaces = 0;
1698 add_node(do_underline_special(0));
1702 if (current_field)
1703 wrap_up_field();
1704 if (current_tab)
1705 wrap_up_tab();
1706 // strip trailing spaces
1707 while (line != 0 && line->discardable()) {
1708 width_total -= line->width();
1709 space_total -= line->nspaces();
1710 node *tem = line;
1711 line = line->next;
1712 delete tem;
1714 node *to_be_output = 0;
1715 hunits to_be_output_width;
1716 prev_line_interrupted = 0;
1717 if (dummy)
1718 space_newline();
1719 else if (interrupted) {
1720 interrupted = 0;
1721 // see environment::final_break
1722 prev_line_interrupted = exit_started ? 2 : 1;
1724 else if (center_lines > 0) {
1725 --center_lines;
1726 hunits x = target_text_length - width_total;
1727 if (x > H0)
1728 saved_indent += x/2;
1729 to_be_output = line;
1730 was_centered = 1;
1731 to_be_output_width = width_total;
1732 line = 0;
1734 else if (right_justify_lines > 0) {
1735 --right_justify_lines;
1736 hunits x = target_text_length - width_total;
1737 if (x > H0)
1738 saved_indent += x;
1739 to_be_output = line;
1740 to_be_output_width = width_total;
1741 line = 0;
1743 else if (fill)
1744 space_newline();
1745 else {
1746 to_be_output = line;
1747 to_be_output_width = width_total;
1748 line = 0;
1750 input_line_start = line == 0 ? H0 : width_total;
1751 if (to_be_output) {
1752 if (is_html && !fill) {
1753 curdiv->modified_tag.incl(MTSM_EOL);
1754 if (suppress_next_eol)
1755 suppress_next_eol = 0;
1756 else
1757 seen_eol = 1;
1760 output_line(to_be_output, to_be_output_width, was_centered);
1761 hyphen_line_count = 0;
1763 if (input_trap_count > 0) {
1764 if (!(continued_input_trap && prev_line_interrupted))
1765 if (--input_trap_count == 0)
1766 spring_trap(input_trap);
1770 void environment::output_line(node *n, hunits width, int was_centered)
1772 prev_text_length = width;
1773 if (margin_character_flags) {
1774 hunits d = line_length + margin_character_distance - saved_indent - width;
1775 if (d > 0) {
1776 n = new hmotion_node(d, get_fill_color(), n);
1777 width += d;
1779 margin_character_flags &= ~MARGIN_CHARACTER_NEXT;
1780 node *tem;
1781 if (!margin_character_flags) {
1782 tem = margin_character_node;
1783 margin_character_node = 0;
1785 else
1786 tem = margin_character_node->copy();
1787 tem->next = n;
1788 n = tem;
1789 width += tem->width();
1791 node *nn = 0;
1792 while (n != 0) {
1793 node *tem = n->next;
1794 n->next = nn;
1795 nn = n;
1796 n = tem;
1798 if (!saved_indent.is_zero())
1799 nn = new hmotion_node(saved_indent, get_fill_color(), nn);
1800 width += saved_indent;
1801 if (no_number_count > 0)
1802 --no_number_count;
1803 else if (numbering_nodes) {
1804 hunits w = (line_number_digit_width
1805 *(3+line_number_indent+number_text_separation));
1806 if (next_line_number % line_number_multiple != 0)
1807 nn = new hmotion_node(w, get_fill_color(), nn);
1808 else {
1809 hunits x = w;
1810 nn = new hmotion_node(number_text_separation * line_number_digit_width,
1811 get_fill_color(), nn);
1812 x -= number_text_separation*line_number_digit_width;
1813 char buf[30];
1814 sprintf(buf, "%3d", next_line_number);
1815 for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
1816 node *gn = numbering_nodes;
1817 for (int count = *p - '0'; count > 0; count--)
1818 gn = gn->next;
1819 gn = gn->copy();
1820 x -= gn->width();
1821 gn->next = nn;
1822 nn = gn;
1824 nn = new hmotion_node(x, get_fill_color(), nn);
1826 width += w;
1827 ++next_line_number;
1829 output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width,
1830 was_centered);
1833 void environment::start_line()
1835 assert(line == 0);
1836 discarding = 0;
1837 line = new line_start_node;
1838 if (have_temporary_indent) {
1839 saved_indent = temporary_indent;
1840 have_temporary_indent = 0;
1842 else
1843 saved_indent = indent;
1844 target_text_length = line_length - saved_indent;
1845 width_total = H0;
1846 space_total = 0;
1849 hunits environment::get_hyphenation_space()
1851 return hyphenation_space;
1854 void hyphenation_space_request()
1856 hunits n;
1857 if (get_hunits(&n, 'm')) {
1858 if (n < H0) {
1859 warning(WARN_RANGE, "hyphenation space cannot be negative");
1860 n = H0;
1862 curenv->hyphenation_space = n;
1864 skip_line();
1867 hunits environment::get_hyphenation_margin()
1869 return hyphenation_margin;
1872 void hyphenation_margin_request()
1874 hunits n;
1875 if (get_hunits(&n, 'm')) {
1876 if (n < H0) {
1877 warning(WARN_RANGE, "hyphenation margin cannot be negative");
1878 n = H0;
1880 curenv->hyphenation_margin = n;
1882 skip_line();
1885 breakpoint *environment::choose_breakpoint()
1887 hunits x = width_total;
1888 int s = space_total;
1889 node *n = line;
1890 breakpoint *best_bp = 0; // the best breakpoint so far
1891 int best_bp_fits = 0;
1892 while (n != 0) {
1893 x -= n->width();
1894 s -= n->nspaces();
1895 breakpoint *bp = n->get_breakpoints(x, s);
1896 while (bp != 0) {
1897 if (bp->width <= target_text_length) {
1898 if (!bp->hyphenated) {
1899 breakpoint *tem = bp->next;
1900 bp->next = 0;
1901 while (tem != 0) {
1902 breakpoint *tem1 = tem;
1903 tem = tem->next;
1904 delete tem1;
1906 if (best_bp_fits
1907 // Decide whether to use the hyphenated breakpoint.
1908 && (hyphen_line_max < 0
1909 // Only choose the hyphenated breakpoint if it would not
1910 // exceed the maximum number of consecutive hyphenated
1911 // lines.
1912 || hyphen_line_count + 1 <= hyphen_line_max)
1913 && !(adjust_mode == ADJUST_BOTH
1914 // Don't choose the hyphenated breakpoint if the line
1915 // can be justified by adding no more than
1916 // hyphenation_space to any word space.
1917 ? (bp->nspaces > 0
1918 && (((target_text_length - bp->width
1919 + (bp->nspaces - 1)*hresolution)/bp->nspaces)
1920 <= hyphenation_space))
1921 // Don't choose the hyphenated breakpoint if the line
1922 // is no more than hyphenation_margin short.
1923 : target_text_length - bp->width <= hyphenation_margin)) {
1924 delete bp;
1925 return best_bp;
1927 if (best_bp)
1928 delete best_bp;
1929 return bp;
1931 else {
1932 if ((adjust_mode == ADJUST_BOTH
1933 ? hyphenation_space == H0
1934 : hyphenation_margin == H0)
1935 && (hyphen_line_max < 0
1936 || hyphen_line_count + 1 <= hyphen_line_max)) {
1937 // No need to consider a non-hyphenated breakpoint.
1938 if (best_bp)
1939 delete best_bp;
1940 breakpoint *tem = bp->next;
1941 bp->next = 0;
1942 while (tem != 0) {
1943 breakpoint *tem1 = tem;
1944 tem = tem->next;
1945 delete tem1;
1947 return bp;
1949 // It fits but it's hyphenated.
1950 if (!best_bp_fits) {
1951 if (best_bp)
1952 delete best_bp;
1953 best_bp = bp;
1954 bp = bp->next;
1955 best_bp_fits = 1;
1957 else {
1958 breakpoint *tem = bp;
1959 bp = bp->next;
1960 delete tem;
1964 else {
1965 if (best_bp)
1966 delete best_bp;
1967 best_bp = bp;
1968 bp = bp->next;
1971 n = n->next;
1973 if (best_bp) {
1974 if (!best_bp_fits)
1975 output_warning(WARN_BREAK, "can't break line");
1976 return best_bp;
1978 return 0;
1981 void environment::hyphenate_line(int start_here)
1983 assert(line != 0);
1984 hyphenation_type prev_type = line->get_hyphenation_type();
1985 node **startp;
1986 if (start_here)
1987 startp = &line;
1988 else
1989 for (startp = &line->next; *startp != 0; startp = &(*startp)->next) {
1990 hyphenation_type this_type = (*startp)->get_hyphenation_type();
1991 if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
1992 break;
1993 prev_type = this_type;
1995 if (*startp == 0)
1996 return;
1997 node *tem = *startp;
1998 do {
1999 tem = tem->next;
2000 } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
2001 int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
2002 node *end = tem;
2003 hyphen_list *sl = 0;
2004 tem = *startp;
2005 node *forward = 0;
2006 int i = 0;
2007 while (tem != end) {
2008 sl = tem->get_hyphen_list(sl, &i);
2009 node *tem1 = tem;
2010 tem = tem->next;
2011 tem1->next = forward;
2012 forward = tem1;
2014 if (!inhibit) {
2015 // this is for characters like hyphen and emdash
2016 int prev_code = 0;
2017 for (hyphen_list *h = sl; h; h = h->next) {
2018 h->breakable = (prev_code != 0
2019 && h->next != 0
2020 && h->next->hyphenation_code != 0);
2021 prev_code = h->hyphenation_code;
2024 if (hyphenation_flags != 0
2025 && !inhibit
2026 // this may not be right if we have extra space on this line
2027 && !((hyphenation_flags & HYPHEN_LAST_LINE)
2028 && (curdiv->distance_to_next_trap()
2029 <= vertical_spacing + total_post_vertical_spacing()))
2030 && i >= 4)
2031 hyphenate(sl, hyphenation_flags);
2032 while (forward != 0) {
2033 node *tem1 = forward;
2034 forward = forward->next;
2035 tem1->next = 0;
2036 tem = tem1->add_self(tem, &sl);
2038 *startp = tem;
2041 static node *node_list_reverse(node *n)
2043 node *res = 0;
2044 while (n) {
2045 node *tem = n;
2046 n = n->next;
2047 tem->next = res;
2048 res = tem;
2050 return res;
2053 static void distribute_space(node *n, int nspaces, hunits desired_space,
2054 int force_reverse = 0)
2056 static int reverse = 0;
2057 if (force_reverse || reverse)
2058 n = node_list_reverse(n);
2059 if (!force_reverse && nspaces > 0 && spread_limit >= 0
2060 && desired_space.to_units() > 0) {
2061 hunits em = curenv->get_size();
2062 double Ems = (double)desired_space.to_units() / nspaces
2063 / (em.is_zero() ? hresolution : em.to_units());
2064 if (Ems > spread_limit)
2065 output_warning(WARN_BREAK, "spreading %1m per space", Ems);
2067 for (node *tem = n; tem; tem = tem->next)
2068 tem->spread_space(&nspaces, &desired_space);
2069 if (force_reverse || reverse)
2070 (void)node_list_reverse(n);
2071 if (!force_reverse)
2072 reverse = !reverse;
2073 assert(desired_space.is_zero() && nspaces == 0);
2076 void environment::possibly_break_line(int start_here, int forced)
2078 int was_centered = center_lines > 0;
2079 if (!fill || current_tab || current_field || dummy)
2080 return;
2081 while (line != 0
2082 && (forced
2083 // When a macro follows a paragraph in fill mode, the
2084 // current line should not be empty.
2085 || (width_total - line->width()) > target_text_length)) {
2086 hyphenate_line(start_here);
2087 breakpoint *bp = choose_breakpoint();
2088 if (bp == 0)
2089 // we'll find one eventually
2090 return;
2091 node *pre, *post;
2092 node **ndp = &line;
2093 while (*ndp != bp->nd)
2094 ndp = &(*ndp)->next;
2095 bp->nd->split(bp->index, &pre, &post);
2096 *ndp = post;
2097 hunits extra_space_width = H0;
2098 switch(adjust_mode) {
2099 case ADJUST_BOTH:
2100 if (bp->nspaces != 0)
2101 extra_space_width = target_text_length - bp->width;
2102 else if (bp->width > 0 && target_text_length > 0
2103 && target_text_length > bp->width)
2104 output_warning(WARN_BREAK, "cannot adjust line");
2105 break;
2106 case ADJUST_CENTER:
2107 saved_indent += (target_text_length - bp->width)/2;
2108 was_centered = 1;
2109 break;
2110 case ADJUST_RIGHT:
2111 saved_indent += target_text_length - bp->width;
2112 break;
2114 distribute_space(pre, bp->nspaces, extra_space_width);
2115 hunits output_width = bp->width + extra_space_width;
2116 input_line_start -= output_width;
2117 if (bp->hyphenated)
2118 hyphen_line_count++;
2119 else
2120 hyphen_line_count = 0;
2121 delete bp;
2122 space_total = 0;
2123 width_total = 0;
2124 node *first_non_discardable = 0;
2125 node *tem;
2126 for (tem = line; tem != 0; tem = tem->next)
2127 if (!tem->discardable())
2128 first_non_discardable = tem;
2129 node *to_be_discarded;
2130 if (first_non_discardable) {
2131 to_be_discarded = first_non_discardable->next;
2132 first_non_discardable->next = 0;
2133 for (tem = line; tem != 0; tem = tem->next) {
2134 width_total += tem->width();
2135 space_total += tem->nspaces();
2137 discarding = 0;
2139 else {
2140 discarding = 1;
2141 to_be_discarded = line;
2142 line = 0;
2144 // Do output_line() here so that line will be 0 iff the
2145 // the environment will be empty.
2146 output_line(pre, output_width, was_centered);
2147 while (to_be_discarded != 0) {
2148 tem = to_be_discarded;
2149 to_be_discarded = to_be_discarded->next;
2150 input_line_start -= tem->width();
2151 delete tem;
2153 if (line != 0) {
2154 if (have_temporary_indent) {
2155 saved_indent = temporary_indent;
2156 have_temporary_indent = 0;
2158 else
2159 saved_indent = indent;
2160 target_text_length = line_length - saved_indent;
2166 Do the break at the end of input after the end macro (if any).
2168 Unix troff behaves as follows: if the last line is
2170 foo bar\c
2172 it will output foo on the current page, and bar on the next page;
2173 if the last line is
2175 foo\c
2179 foo bar
2181 everything will be output on the current page. This behaviour must be
2182 considered a bug.
2184 The problem is that some macro packages rely on this. For example,
2185 the ATK macros have an end macro that emits \c if it needs to print a
2186 table of contents but doesn't do a 'bp in the end macro; instead the
2187 'bp is done in the bottom of page trap. This works with Unix troff,
2188 provided that the current environment is not empty at the end of the
2189 input file.
2191 The following will make macro packages that do that sort of thing work
2192 even if the current environment is empty at the end of the input file.
2193 If the last input line used \c and this line occurred in the end macro,
2194 then we'll force everything out on the current page, but we'll make
2195 sure that the environment isn't empty so that we won't exit at the
2196 bottom of this page.
2199 void environment::final_break()
2201 if (prev_line_interrupted == 2) {
2202 do_break();
2203 add_node(new transparent_dummy_node);
2205 else
2206 do_break();
2209 node *environment::make_tag(const char *nm, int i)
2211 if (is_html) {
2213 * need to emit tag for post-grohtml
2214 * but we check to see whether we can emit specials
2216 if (curdiv == topdiv && topdiv->before_first_page)
2217 topdiv->begin_page();
2218 macro *m = new macro;
2219 m->append_str("devtag:");
2220 for (const char *p = nm; *p; p++)
2221 if (!invalid_input_char((unsigned char)*p))
2222 m->append(*p);
2223 m->append(' ');
2224 m->append_int(i);
2225 return new special_node(*m);
2227 return 0;
2230 void environment::dump_troff_state()
2232 #define SPACES " "
2233 fprintf(stderr, SPACES "register `in' = %d\n", curenv->indent.to_units());
2234 if (curenv->have_temporary_indent)
2235 fprintf(stderr, SPACES "register `ti' = %d\n",
2236 curenv->temporary_indent.to_units());
2237 fprintf(stderr, SPACES "centered lines `ce' = %d\n", curenv->center_lines);
2238 fprintf(stderr, SPACES "register `ll' = %d\n",
2239 curenv->line_length.to_units());
2240 fprintf(stderr, SPACES "fill `fi=1/nf=0' = %d\n", curenv->fill);
2241 fprintf(stderr, SPACES "page offset `po' = %d\n",
2242 topdiv->get_page_offset().to_units());
2243 fprintf(stderr, SPACES "seen_break = %d\n", curenv->seen_break);
2244 fprintf(stderr, SPACES "seen_space = %d\n", curenv->seen_space);
2245 fflush(stderr);
2246 #undef SPACES
2249 statem *environment::construct_state(int only_eol)
2251 if (is_html) {
2252 statem *s = new statem();
2253 if (!only_eol) {
2254 s->add_tag(MTSM_IN, indent);
2255 s->add_tag(MTSM_LL, line_length);
2256 s->add_tag(MTSM_PO, topdiv->get_page_offset().to_units());
2257 s->add_tag(MTSM_RJ, right_justify_lines);
2258 if (have_temporary_indent)
2259 s->add_tag(MTSM_TI, temporary_indent);
2260 s->add_tag_ta();
2261 if (seen_break)
2262 s->add_tag(MTSM_BR);
2263 if (seen_space != 0)
2264 s->add_tag(MTSM_SP, seen_space);
2265 seen_break = 0;
2266 seen_space = 0;
2268 if (seen_eol) {
2269 s->add_tag(MTSM_EOL);
2270 s->add_tag(MTSM_CE, center_lines);
2272 seen_eol = 0;
2273 return s;
2275 else
2276 return NULL;
2279 void environment::construct_format_state(node *n, int was_centered,
2280 int filling)
2282 if (is_html) {
2283 // find first glyph node which has a state.
2284 while (n != 0 && n->state == 0)
2285 n = n->next;
2286 if (n == 0 || (n->state == 0))
2287 return;
2288 if (seen_space != 0)
2289 n->state->add_tag(MTSM_SP, seen_space);
2290 if (seen_eol && topdiv == curdiv)
2291 n->state->add_tag(MTSM_EOL);
2292 seen_space = 0;
2293 seen_eol = 0;
2294 if (was_centered)
2295 n->state->add_tag(MTSM_CE, center_lines+1);
2296 else
2297 n->state->add_tag_if_unknown(MTSM_CE, 0);
2298 n->state->add_tag_if_unknown(MTSM_FI, filling);
2299 n = n->next;
2300 while (n != 0) {
2301 if (n->state != 0) {
2302 n->state->sub_tag_ce();
2303 n->state->add_tag_if_unknown(MTSM_FI, filling);
2305 n = n->next;
2310 void environment::construct_new_line_state(node *n)
2312 if (is_html) {
2313 // find first glyph node which has a state.
2314 while (n != 0 && n->state == 0)
2315 n = n->next;
2316 if (n == 0 || n->state == 0)
2317 return;
2318 if (seen_space != 0)
2319 n->state->add_tag(MTSM_SP, seen_space);
2320 if (seen_eol && topdiv == curdiv)
2321 n->state->add_tag(MTSM_EOL);
2322 seen_space = 0;
2323 seen_eol = 0;
2327 extern int global_diverted_space;
2329 void environment::do_break(int do_spread)
2331 int was_centered = 0;
2332 if (curdiv == topdiv && topdiv->before_first_page) {
2333 topdiv->begin_page();
2334 return;
2336 if (current_tab)
2337 wrap_up_tab();
2338 if (line) {
2339 // this is so that hyphenation works
2340 if (line->nspaces() == 0) {
2341 line = new space_node(H0, get_fill_color(), line);
2342 space_total++;
2344 possibly_break_line(0, do_spread);
2346 while (line != 0 && line->discardable()) {
2347 width_total -= line->width();
2348 space_total -= line->nspaces();
2349 node *tem = line;
2350 line = line->next;
2351 delete tem;
2353 discarding = 0;
2354 input_line_start = H0;
2355 if (line != 0) {
2356 if (fill) {
2357 switch (adjust_mode) {
2358 case ADJUST_CENTER:
2359 saved_indent += (target_text_length - width_total)/2;
2360 was_centered = 1;
2361 break;
2362 case ADJUST_RIGHT:
2363 saved_indent += target_text_length - width_total;
2364 break;
2367 node *tem = line;
2368 line = 0;
2369 output_line(tem, width_total, was_centered);
2370 hyphen_line_count = 0;
2372 prev_line_interrupted = 0;
2373 #ifdef WIDOW_CONTROL
2374 mark_last_line();
2375 output_pending_lines();
2376 #endif /* WIDOW_CONTROL */
2377 if (!global_diverted_space) {
2378 curdiv->modified_tag.incl(MTSM_BR);
2379 seen_break = 1;
2383 int environment::is_empty()
2385 return !current_tab && line == 0 && pending_lines == 0;
2388 void do_break_request(int spread)
2390 while (!tok.newline() && !tok.eof())
2391 tok.next();
2392 if (break_flag)
2393 curenv->do_break(spread);
2394 tok.next();
2397 void break_request()
2399 do_break_request(0);
2402 void break_spread_request()
2404 do_break_request(1);
2407 void title()
2409 if (curdiv == topdiv && topdiv->before_first_page) {
2410 handle_initial_title();
2411 return;
2413 node *part[3];
2414 hunits part_width[3];
2415 part[0] = part[1] = part[2] = 0;
2416 environment env(curenv);
2417 environment *oldenv = curenv;
2418 curenv = &env;
2419 read_title_parts(part, part_width);
2420 curenv = oldenv;
2421 curenv->size = env.size;
2422 curenv->prev_size = env.prev_size;
2423 curenv->requested_size = env.requested_size;
2424 curenv->prev_requested_size = env.prev_requested_size;
2425 curenv->char_height = env.char_height;
2426 curenv->char_slant = env.char_slant;
2427 curenv->fontno = env.fontno;
2428 curenv->prev_fontno = env.prev_fontno;
2429 curenv->glyph_color = env.glyph_color;
2430 curenv->prev_glyph_color = env.prev_glyph_color;
2431 curenv->fill_color = env.fill_color;
2432 curenv->prev_fill_color = env.prev_fill_color;
2433 node *n = 0;
2434 node *p = part[2];
2435 while (p != 0) {
2436 node *tem = p;
2437 p = p->next;
2438 tem->next = n;
2439 n = tem;
2441 hunits length_title(curenv->title_length);
2442 hunits f = length_title - part_width[1];
2443 hunits f2 = f/2;
2444 n = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(), n);
2445 p = part[1];
2446 while (p != 0) {
2447 node *tem = p;
2448 p = p->next;
2449 tem->next = n;
2450 n = tem;
2452 n = new hmotion_node(f - f2 - part_width[0], curenv->get_fill_color(), n);
2453 p = part[0];
2454 while (p != 0) {
2455 node *tem = p;
2456 p = p->next;
2457 tem->next = n;
2458 n = tem;
2460 curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
2461 curenv->total_post_vertical_spacing(), length_title);
2462 curenv->hyphen_line_count = 0;
2463 tok.next();
2466 void adjust()
2468 curenv->adjust_mode |= 1;
2469 if (has_arg()) {
2470 switch (tok.ch()) {
2471 case 'l':
2472 curenv->adjust_mode = ADJUST_LEFT;
2473 break;
2474 case 'r':
2475 curenv->adjust_mode = ADJUST_RIGHT;
2476 break;
2477 case 'c':
2478 curenv->adjust_mode = ADJUST_CENTER;
2479 break;
2480 case 'b':
2481 case 'n':
2482 curenv->adjust_mode = ADJUST_BOTH;
2483 break;
2484 default:
2485 int n;
2486 if (get_integer(&n)) {
2487 if (n < 0)
2488 warning(WARN_RANGE, "negative adjustment mode");
2489 else if (n > 5) {
2490 curenv->adjust_mode = 5;
2491 warning(WARN_RANGE, "adjustment mode `%1' out of range", n);
2493 else
2494 curenv->adjust_mode = n;
2498 skip_line();
2501 void no_adjust()
2503 curenv->adjust_mode &= ~1;
2504 skip_line();
2507 void do_input_trap(int continued)
2509 curenv->input_trap_count = 0;
2510 if (continued)
2511 curenv->continued_input_trap = 1;
2512 int n;
2513 if (has_arg() && get_integer(&n)) {
2514 if (n <= 0)
2515 warning(WARN_RANGE,
2516 "number of lines for input trap must be greater than zero");
2517 else {
2518 symbol s = get_name(1);
2519 if (!s.is_null()) {
2520 curenv->input_trap_count = n;
2521 curenv->input_trap = s;
2525 skip_line();
2528 void input_trap()
2530 do_input_trap(0);
2533 void input_trap_continued()
2535 do_input_trap(1);
2538 /* tabs */
2540 // must not be R or C or L or a legitimate part of a number expression
2541 const char TAB_REPEAT_CHAR = 'T';
2543 struct tab {
2544 tab *next;
2545 hunits pos;
2546 tab_type type;
2547 tab(hunits, tab_type);
2548 enum { BLOCK = 1024 };
2549 static tab *free_list;
2550 void *operator new(size_t);
2551 void operator delete(void *);
2554 tab *tab::free_list = 0;
2556 void *tab::operator new(size_t n)
2558 assert(n == sizeof(tab));
2559 if (!free_list) {
2560 free_list = (tab *)new char[sizeof(tab)*BLOCK];
2561 for (int i = 0; i < BLOCK - 1; i++)
2562 free_list[i].next = free_list + i + 1;
2563 free_list[BLOCK-1].next = 0;
2565 tab *p = free_list;
2566 free_list = (tab *)(free_list->next);
2567 p->next = 0;
2568 return p;
2571 #ifdef __GNUG__
2572 /* cfront can't cope with this. */
2573 inline
2574 #endif
2575 void tab::operator delete(void *p)
2577 if (p) {
2578 ((tab *)p)->next = free_list;
2579 free_list = (tab *)p;
2583 tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2587 tab_stops::tab_stops(hunits distance, tab_type type)
2588 : initial_list(0)
2590 repeated_list = new tab(distance, type);
2593 tab_stops::~tab_stops()
2595 clear();
2598 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
2600 hunits nextpos;
2602 return distance_to_next_tab(curpos, distance, &nextpos);
2605 tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance,
2606 hunits *nextpos)
2608 hunits lastpos = 0;
2609 tab *tem;
2610 for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
2611 lastpos = tem->pos;
2612 if (tem) {
2613 *distance = tem->pos - curpos;
2614 *nextpos = tem->pos;
2615 return tem->type;
2617 if (repeated_list == 0)
2618 return TAB_NONE;
2619 hunits base = lastpos;
2620 for (;;) {
2621 for (tem = repeated_list; tem && tem->pos + base <= curpos; tem = tem->next)
2622 lastpos = tem->pos;
2623 if (tem) {
2624 *distance = tem->pos + base - curpos;
2625 *nextpos = tem->pos + base;
2626 return tem->type;
2628 assert(lastpos > 0);
2629 base += lastpos;
2631 return TAB_NONE;
2634 const char *tab_stops::to_string()
2636 static char *buf = 0;
2637 static int buf_size = 0;
2638 // figure out a maximum on the amount of space we can need
2639 int count = 0;
2640 tab *p;
2641 for (p = initial_list; p; p = p->next)
2642 ++count;
2643 for (p = repeated_list; p; p = p->next)
2644 ++count;
2645 // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
2646 int need = count*12 + 3;
2647 if (buf == 0 || need > buf_size) {
2648 if (buf)
2649 a_delete buf;
2650 buf_size = need;
2651 buf = new char[buf_size];
2653 char *ptr = buf;
2654 for (p = initial_list; p; p = p->next) {
2655 strcpy(ptr, i_to_a(p->pos.to_units()));
2656 ptr = strchr(ptr, '\0');
2657 *ptr++ = 'u';
2658 *ptr = '\0';
2659 switch (p->type) {
2660 case TAB_LEFT:
2661 break;
2662 case TAB_RIGHT:
2663 *ptr++ = 'R';
2664 break;
2665 case TAB_CENTER:
2666 *ptr++ = 'C';
2667 break;
2668 case TAB_NONE:
2669 default:
2670 assert(0);
2673 if (repeated_list)
2674 *ptr++ = TAB_REPEAT_CHAR;
2675 for (p = repeated_list; p; p = p->next) {
2676 strcpy(ptr, i_to_a(p->pos.to_units()));
2677 ptr = strchr(ptr, '\0');
2678 *ptr++ = 'u';
2679 *ptr = '\0';
2680 switch (p->type) {
2681 case TAB_LEFT:
2682 break;
2683 case TAB_RIGHT:
2684 *ptr++ = 'R';
2685 break;
2686 case TAB_CENTER:
2687 *ptr++ = 'C';
2688 break;
2689 case TAB_NONE:
2690 default:
2691 assert(0);
2694 *ptr++ = '\0';
2695 return buf;
2698 tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2702 tab_stops::tab_stops(const tab_stops &ts)
2703 : initial_list(0), repeated_list(0)
2705 tab **p = &initial_list;
2706 tab *t = ts.initial_list;
2707 while (t) {
2708 *p = new tab(t->pos, t->type);
2709 t = t->next;
2710 p = &(*p)->next;
2712 p = &repeated_list;
2713 t = ts.repeated_list;
2714 while (t) {
2715 *p = new tab(t->pos, t->type);
2716 t = t->next;
2717 p = &(*p)->next;
2721 void tab_stops::clear()
2723 while (initial_list) {
2724 tab *tem = initial_list;
2725 initial_list = initial_list->next;
2726 delete tem;
2728 while (repeated_list) {
2729 tab *tem = repeated_list;
2730 repeated_list = repeated_list->next;
2731 delete tem;
2735 void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
2737 tab **p;
2738 for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
2740 *p = new tab(pos, type);
2744 void tab_stops::operator=(const tab_stops &ts)
2746 clear();
2747 tab **p = &initial_list;
2748 tab *t = ts.initial_list;
2749 while (t) {
2750 *p = new tab(t->pos, t->type);
2751 t = t->next;
2752 p = &(*p)->next;
2754 p = &repeated_list;
2755 t = ts.repeated_list;
2756 while (t) {
2757 *p = new tab(t->pos, t->type);
2758 t = t->next;
2759 p = &(*p)->next;
2763 void set_tabs()
2765 hunits pos;
2766 hunits prev_pos = 0;
2767 int first = 1;
2768 int repeated = 0;
2769 tab_stops tabs;
2770 while (has_arg()) {
2771 if (tok.ch() == TAB_REPEAT_CHAR) {
2772 tok.next();
2773 repeated = 1;
2774 prev_pos = 0;
2776 if (!get_hunits(&pos, 'm', prev_pos))
2777 break;
2778 tab_type type = TAB_LEFT;
2779 if (tok.ch() == 'C') {
2780 tok.next();
2781 type = TAB_CENTER;
2783 else if (tok.ch() == 'R') {
2784 tok.next();
2785 type = TAB_RIGHT;
2787 else if (tok.ch() == 'L') {
2788 tok.next();
2790 if (pos <= prev_pos && !first)
2791 warning(WARN_RANGE,
2792 "positions of tab stops must be strictly increasing");
2793 else {
2794 tabs.add_tab(pos, type, repeated);
2795 prev_pos = pos;
2796 first = 0;
2799 curenv->tabs = tabs;
2800 curdiv->modified_tag.incl(MTSM_TA);
2801 skip_line();
2804 const char *environment::get_tabs()
2806 return tabs.to_string();
2809 tab_type environment::distance_to_next_tab(hunits *distance)
2811 return line_tabs
2812 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
2813 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
2816 tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
2818 return line_tabs
2819 ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
2820 : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
2821 leftpos);
2824 void field_characters()
2826 field_delimiter_char = get_optional_char();
2827 if (field_delimiter_char)
2828 padding_indicator_char = get_optional_char();
2829 else
2830 padding_indicator_char = 0;
2831 skip_line();
2834 void line_tabs_request()
2836 int n;
2837 if (has_arg() && get_integer(&n))
2838 curenv->line_tabs = n != 0;
2839 else
2840 curenv->line_tabs = 1;
2841 skip_line();
2844 int environment::get_line_tabs()
2846 return line_tabs;
2849 void environment::wrap_up_tab()
2851 if (!current_tab)
2852 return;
2853 if (line == 0)
2854 start_line();
2855 hunits tab_amount;
2856 switch (current_tab) {
2857 case TAB_RIGHT:
2858 tab_amount = tab_distance - tab_width;
2859 line = make_tab_node(tab_amount, line);
2860 break;
2861 case TAB_CENTER:
2862 tab_amount = tab_distance - tab_width/2;
2863 line = make_tab_node(tab_amount, line);
2864 break;
2865 case TAB_NONE:
2866 case TAB_LEFT:
2867 default:
2868 assert(0);
2870 width_total += tab_amount;
2871 width_total += tab_width;
2872 if (current_field) {
2873 if (tab_precedes_field) {
2874 pre_field_width += tab_amount;
2875 tab_precedes_field = 0;
2877 field_distance -= tab_amount;
2878 field_spaces += tab_field_spaces;
2880 if (tab_contents != 0) {
2881 node *tem;
2882 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2884 tem->next = line;
2885 line = tab_contents;
2887 tab_field_spaces = 0;
2888 tab_contents = 0;
2889 tab_width = H0;
2890 tab_distance = H0;
2891 current_tab = TAB_NONE;
2894 node *environment::make_tab_node(hunits d, node *next)
2896 if (leader_node != 0 && d < 0) {
2897 error("motion generated by leader cannot be negative");
2898 delete leader_node;
2899 leader_node = 0;
2901 if (!leader_node)
2902 return new hmotion_node(d, 1, 0, get_fill_color(), next);
2903 node *n = new hline_node(d, leader_node, next);
2904 leader_node = 0;
2905 return n;
2908 void environment::handle_tab(int is_leader)
2910 hunits d;
2911 hunits absolute;
2912 if (current_tab)
2913 wrap_up_tab();
2914 charinfo *ci = is_leader ? leader_char : tab_char;
2915 delete leader_node;
2916 leader_node = ci ? make_char_node(ci) : 0;
2917 tab_type t = distance_to_next_tab(&d, &absolute);
2918 switch (t) {
2919 case TAB_NONE:
2920 return;
2921 case TAB_LEFT:
2922 add_node(make_tag("tab L", absolute.to_units()));
2923 add_node(make_tab_node(d));
2924 return;
2925 case TAB_RIGHT:
2926 add_node(make_tag("tab R", absolute.to_units()));
2927 break;
2928 case TAB_CENTER:
2929 add_node(make_tag("tab C", absolute.to_units()));
2930 break;
2931 default:
2932 assert(0);
2934 tab_width = 0;
2935 tab_distance = d;
2936 tab_contents = 0;
2937 current_tab = t;
2938 tab_field_spaces = 0;
2941 void environment::start_field()
2943 assert(!current_field);
2944 hunits d;
2945 if (distance_to_next_tab(&d) != TAB_NONE) {
2946 pre_field_width = get_text_length();
2947 field_distance = d;
2948 current_field = 1;
2949 field_spaces = 0;
2950 tab_field_spaces = 0;
2951 for (node *p = line; p; p = p->next)
2952 if (p->nspaces()) {
2953 p->freeze_space();
2954 space_total--;
2956 tab_precedes_field = current_tab != TAB_NONE;
2958 else
2959 error("zero field width");
2962 void environment::wrap_up_field()
2964 if (!current_tab && field_spaces == 0)
2965 add_padding();
2966 hunits padding = field_distance - (get_text_length() - pre_field_width);
2967 if (current_tab && tab_field_spaces != 0) {
2968 hunits tab_padding = scale(padding,
2969 tab_field_spaces,
2970 field_spaces + tab_field_spaces);
2971 padding -= tab_padding;
2972 distribute_space(tab_contents, tab_field_spaces, tab_padding, 1);
2973 tab_field_spaces = 0;
2974 tab_width += tab_padding;
2976 if (field_spaces != 0) {
2977 distribute_space(line, field_spaces, padding, 1);
2978 width_total += padding;
2979 if (current_tab) {
2980 // the start of the tab has been moved to the right by padding, so
2981 tab_distance -= padding;
2982 if (tab_distance <= H0) {
2983 // use the next tab stop instead
2984 current_tab = tabs.distance_to_next_tab(get_input_line_position()
2985 - tab_width,
2986 &tab_distance);
2987 if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
2988 width_total += tab_width;
2989 if (current_tab == TAB_LEFT) {
2990 line = make_tab_node(tab_distance, line);
2991 width_total += tab_distance;
2992 current_tab = TAB_NONE;
2994 if (tab_contents != 0) {
2995 node *tem;
2996 for (tem = tab_contents; tem->next != 0; tem = tem->next)
2998 tem->next = line;
2999 line = tab_contents;
3000 tab_contents = 0;
3002 tab_width = H0;
3003 tab_distance = H0;
3008 current_field = 0;
3011 void environment::add_padding()
3013 if (current_tab) {
3014 tab_contents = new space_node(H0, get_fill_color(), tab_contents);
3015 tab_field_spaces++;
3017 else {
3018 if (line == 0)
3019 start_line();
3020 line = new space_node(H0, get_fill_color(), line);
3021 field_spaces++;
3025 typedef int (environment::*INT_FUNCP)();
3026 typedef vunits (environment::*VUNITS_FUNCP)();
3027 typedef hunits (environment::*HUNITS_FUNCP)();
3028 typedef const char *(environment::*STRING_FUNCP)();
3030 class int_env_reg : public reg {
3031 INT_FUNCP func;
3032 public:
3033 int_env_reg(INT_FUNCP);
3034 const char *get_string();
3035 int get_value(units *val);
3038 class vunits_env_reg : public reg {
3039 VUNITS_FUNCP func;
3040 public:
3041 vunits_env_reg(VUNITS_FUNCP f);
3042 const char *get_string();
3043 int get_value(units *val);
3047 class hunits_env_reg : public reg {
3048 HUNITS_FUNCP func;
3049 public:
3050 hunits_env_reg(HUNITS_FUNCP f);
3051 const char *get_string();
3052 int get_value(units *val);
3055 class string_env_reg : public reg {
3056 STRING_FUNCP func;
3057 public:
3058 string_env_reg(STRING_FUNCP);
3059 const char *get_string();
3062 int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
3066 int int_env_reg::get_value(units *val)
3068 *val = (curenv->*func)();
3069 return 1;
3072 const char *int_env_reg::get_string()
3074 return i_to_a((curenv->*func)());
3077 vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
3081 int vunits_env_reg::get_value(units *val)
3083 *val = (curenv->*func)().to_units();
3084 return 1;
3087 const char *vunits_env_reg::get_string()
3089 return i_to_a((curenv->*func)().to_units());
3092 hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
3096 int hunits_env_reg::get_value(units *val)
3098 *val = (curenv->*func)().to_units();
3099 return 1;
3102 const char *hunits_env_reg::get_string()
3104 return i_to_a((curenv->*func)().to_units());
3107 string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
3111 const char *string_env_reg::get_string()
3113 return (curenv->*func)();
3116 class horizontal_place_reg : public general_reg {
3117 public:
3118 horizontal_place_reg();
3119 int get_value(units *);
3120 void set_value(units);
3123 horizontal_place_reg::horizontal_place_reg()
3127 int horizontal_place_reg::get_value(units *res)
3129 *res = curenv->get_input_line_position().to_units();
3130 return 1;
3133 void horizontal_place_reg::set_value(units n)
3135 curenv->set_input_line_position(hunits(n));
3138 int environment::get_zoom()
3140 return env_get_zoom(this);
3143 const char *environment::get_font_family_string()
3145 return family->nm.contents();
3148 const char *environment::get_glyph_color_string()
3150 return glyph_color->nm.contents();
3153 const char *environment::get_fill_color_string()
3155 return fill_color->nm.contents();
3158 const char *environment::get_font_name_string()
3160 symbol f = get_font_name(fontno, this);
3161 return f.contents();
3164 const char *environment::get_style_name_string()
3166 symbol f = get_style_name(fontno);
3167 return f.contents();
3170 const char *environment::get_name_string()
3172 return name.contents();
3175 // Convert a quantity in scaled points to ascii decimal fraction.
3177 const char *sptoa(int sp)
3179 assert(sp > 0);
3180 assert(sizescale > 0);
3181 if (sizescale == 1)
3182 return i_to_a(sp);
3183 if (sp % sizescale == 0)
3184 return i_to_a(sp/sizescale);
3185 // See if 1/sizescale is exactly representable as a decimal fraction,
3186 // ie its only prime factors are 2 and 5.
3187 int n = sizescale;
3188 int power2 = 0;
3189 while ((n & 1) == 0) {
3190 n >>= 1;
3191 power2++;
3193 int power5 = 0;
3194 while ((n % 5) == 0) {
3195 n /= 5;
3196 power5++;
3198 if (n == 1) {
3199 int decimal_point = power5 > power2 ? power5 : power2;
3200 if (decimal_point <= 10) {
3201 int factor = 1;
3202 int t;
3203 for (t = decimal_point - power2; --t >= 0;)
3204 factor *= 2;
3205 for (t = decimal_point - power5; --t >= 0;)
3206 factor *= 5;
3207 if (factor == 1 || sp <= INT_MAX/factor)
3208 return if_to_a(sp*factor, decimal_point);
3211 double s = double(sp)/double(sizescale);
3212 double factor = 10.0;
3213 double val = s;
3214 int decimal_point = 0;
3215 do {
3216 double v = ceil(s*factor);
3217 if (v > INT_MAX)
3218 break;
3219 val = v;
3220 factor *= 10.0;
3221 } while (++decimal_point < 10);
3222 return if_to_a(int(val), decimal_point);
3225 const char *environment::get_point_size_string()
3227 return sptoa(curenv->get_point_size());
3230 const char *environment::get_requested_point_size_string()
3232 return sptoa(curenv->get_requested_point_size());
3235 void environment::print_env()
3237 // at the time of calling .pev, those values are always zero or
3238 // meaningless:
3240 // char_height, char_slant,
3241 // interrupted
3242 // current_tab, tab_width, tab_distance
3243 // current_field, field_distance, pre_field_width, field_spaces,
3244 // tab_field_spaces, tab_precedes_field
3245 // composite
3247 errprint(" previous line length: %1u\n", prev_line_length.to_units());
3248 errprint(" line length: %1u\n", line_length.to_units());
3249 errprint(" previous title length: %1u\n", prev_title_length.to_units());
3250 errprint(" title length: %1u\n", title_length.to_units());
3251 errprint(" previous size: %1p (%2s)\n",
3252 prev_size.to_points(), prev_size.to_scaled_points());
3253 errprint(" size: %1p (%2s)\n",
3254 size.to_points(), size.to_scaled_points());
3255 errprint(" previous requested size: %1s\n", prev_requested_size);
3256 errprint(" requested size: %1s\n", requested_size);
3257 errprint(" previous font number: %1\n", prev_fontno);
3258 errprint(" font number: %1\n", fontno);
3259 errprint(" previous family: `%1'\n", prev_family->nm.contents());
3260 errprint(" family: `%1'\n", family->nm.contents());
3261 errprint(" space size: %1/36 em\n", space_size);
3262 errprint(" sentence space size: %1/36 em\n", sentence_space_size);
3263 errprint(" previous line interrupted: %1\n",
3264 prev_line_interrupted ? "yes" : "no");
3265 errprint(" fill mode: %1\n", fill ? "on" : "off");
3266 errprint(" adjust mode: %1\n",
3267 adjust_mode == ADJUST_LEFT
3268 ? "left"
3269 : adjust_mode == ADJUST_BOTH
3270 ? "both"
3271 : adjust_mode == ADJUST_CENTER
3272 ? "center"
3273 : "right");
3274 if (center_lines)
3275 errprint(" lines to center: %1\n", center_lines);
3276 if (right_justify_lines)
3277 errprint(" lines to right justify: %1\n", right_justify_lines);
3278 errprint(" previous vertical spacing: %1u\n",
3279 prev_vertical_spacing.to_units());
3280 errprint(" vertical spacing: %1u\n", vertical_spacing.to_units());
3281 errprint(" previous post-vertical spacing: %1u\n",
3282 prev_post_vertical_spacing.to_units());
3283 errprint(" post-vertical spacing: %1u\n",
3284 post_vertical_spacing.to_units());
3285 errprint(" previous line spacing: %1\n", prev_line_spacing);
3286 errprint(" line spacing: %1\n", line_spacing);
3287 errprint(" previous indentation: %1u\n", prev_indent.to_units());
3288 errprint(" indentation: %1u\n", indent.to_units());
3289 errprint(" temporary indentation: %1u\n", temporary_indent.to_units());
3290 errprint(" have temporary indentation: %1\n",
3291 have_temporary_indent ? "yes" : "no");
3292 errprint(" currently used indentation: %1u\n", saved_indent.to_units());
3293 errprint(" target text length: %1u\n", target_text_length.to_units());
3294 if (underline_lines) {
3295 errprint(" lines to underline: %1\n", underline_lines);
3296 errprint(" font number before underlining: %1\n", pre_underline_fontno);
3297 errprint(" underline spaces: %1\n", underline_spaces ? "yes" : "no");
3299 if (input_trap.contents()) {
3300 errprint(" input trap macro: `%1'\n", input_trap.contents());
3301 errprint(" input trap line counter: %1\n", input_trap_count);
3302 errprint(" continued input trap: %1\n",
3303 continued_input_trap ? "yes" : "no");
3305 errprint(" previous text length: %1u\n", prev_text_length.to_units());
3306 errprint(" total width: %1u\n", width_total.to_units());
3307 errprint(" total number of spaces: %1\n", space_total);
3308 errprint(" input line start: %1u\n", input_line_start.to_units());
3309 errprint(" line tabs: %1\n", line_tabs ? "yes" : "no");
3310 errprint(" discarding: %1\n", discarding ? "yes" : "no");
3311 errprint(" spread flag set: %1\n", spread_flag ? "yes" : "no"); // \p
3312 if (margin_character_node) {
3313 errprint(" margin character flags: %1\n",
3314 margin_character_flags == MARGIN_CHARACTER_ON
3315 ? "on"
3316 : margin_character_flags == MARGIN_CHARACTER_NEXT
3317 ? "next"
3318 : margin_character_flags == MARGIN_CHARACTER_ON
3319 | MARGIN_CHARACTER_NEXT
3320 ? "on, next"
3321 : "none");
3322 errprint(" margin character distance: %1u\n",
3323 margin_character_distance.to_units());
3325 if (numbering_nodes) {
3326 errprint(" line number digit width: %1u\n",
3327 line_number_digit_width.to_units());
3328 errprint(" separation between number and text: %1 digit spaces\n",
3329 number_text_separation);
3330 errprint(" line number indentation: %1 digit spaces\n",
3331 line_number_indent);
3332 errprint(" print line numbers every %1line%1\n",
3333 line_number_multiple > 1 ? i_to_a(line_number_multiple) : "",
3334 line_number_multiple > 1 ? "s" : "");
3335 errprint(" lines not to enumerate: %1\n", no_number_count);
3337 string hf = hyphenation_flags ? "on" : "off";
3338 if (hyphenation_flags & HYPHEN_LAST_LINE)
3339 hf += ", not last line";
3340 if (hyphenation_flags & HYPHEN_LAST_CHARS)
3341 hf += ", not last two chars";
3342 if (hyphenation_flags & HYPHEN_FIRST_CHARS)
3343 hf += ", not first two chars";
3344 hf += '\0';
3345 errprint(" hyphenation_flags: %1\n", hf.contents());
3346 errprint(" number of consecutive hyphenated lines: %1\n",
3347 hyphen_line_count);
3348 errprint(" maximum number of consecutive hyphenated lines: %1\n",
3349 hyphen_line_max);
3350 errprint(" hyphenation space: %1u\n", hyphenation_space.to_units());
3351 errprint(" hyphenation margin: %1u\n", hyphenation_margin.to_units());
3352 #ifdef WIDOW_CONTROL
3353 errprint(" widow control: %1\n", widow_control ? "yes" : "no");
3354 #endif /* WIDOW_CONTROL */
3357 void print_env()
3359 errprint("Current Environment:\n");
3360 curenv->print_env();
3361 for (int i = 0; i < NENVIRONMENTS; i++) {
3362 if (env_table[i]) {
3363 errprint("Environment %1:\n", i);
3364 if (env_table[i] != curenv)
3365 env_table[i]->print_env();
3366 else
3367 errprint(" current\n");
3370 dictionary_iterator iter(env_dictionary);
3371 symbol s;
3372 environment *e;
3373 while (iter.get(&s, (void **)&e)) {
3374 assert(!s.is_null());
3375 errprint("Environment %1:\n", s.contents());
3376 if (e != curenv)
3377 e->print_env();
3378 else
3379 errprint(" current\n");
3381 fflush(stderr);
3382 skip_line();
3385 #define init_int_env_reg(name, func) \
3386 number_reg_dictionary.define(name, new int_env_reg(&environment::func))
3388 #define init_vunits_env_reg(name, func) \
3389 number_reg_dictionary.define(name, new vunits_env_reg(&environment::func))
3391 #define init_hunits_env_reg(name, func) \
3392 number_reg_dictionary.define(name, new hunits_env_reg(&environment::func))
3394 #define init_string_env_reg(name, func) \
3395 number_reg_dictionary.define(name, new string_env_reg(&environment::func))
3397 void init_env_requests()
3399 init_request("ad", adjust);
3400 init_request("br", break_request);
3401 init_request("brp", break_spread_request);
3402 init_request("c2", no_break_control_char);
3403 init_request("cc", control_char);
3404 init_request("ce", center);
3405 init_request("cu", continuous_underline);
3406 init_request("ev", environment_switch);
3407 init_request("evc", environment_copy);
3408 init_request("fam", family_change);
3409 init_request("fc", field_characters);
3410 init_request("fi", fill);
3411 init_request("fcolor", fill_color_change);
3412 init_request("ft", font_change);
3413 init_request("gcolor", glyph_color_change);
3414 init_request("hc", hyphen_char);
3415 init_request("hlm", hyphen_line_max_request);
3416 init_request("hy", hyphenate_request);
3417 init_request("hym", hyphenation_margin_request);
3418 init_request("hys", hyphenation_space_request);
3419 init_request("in", indent);
3420 init_request("it", input_trap);
3421 init_request("itc", input_trap_continued);
3422 init_request("lc", leader_character);
3423 init_request("linetabs", line_tabs_request);
3424 init_request("ll", line_length);
3425 init_request("ls", line_spacing);
3426 init_request("lt", title_length);
3427 init_request("mc", margin_character);
3428 init_request("na", no_adjust);
3429 init_request("nf", no_fill);
3430 init_request("nh", no_hyphenate);
3431 init_request("nm", number_lines);
3432 init_request("nn", no_number);
3433 init_request("pev", print_env);
3434 init_request("ps", point_size);
3435 init_request("pvs", post_vertical_spacing);
3436 init_request("rj", right_justify);
3437 init_request("sizes", override_sizes);
3438 init_request("ss", space_size);
3439 init_request("ta", set_tabs);
3440 init_request("ti", temporary_indent);
3441 init_request("tc", tab_character);
3442 init_request("tl", title);
3443 init_request("ul", underline);
3444 init_request("vs", vertical_spacing);
3445 #ifdef WIDOW_CONTROL
3446 init_request("wdc", widow_control_request);
3447 #endif /* WIDOW_CONTROL */
3448 init_int_env_reg(".b", get_bold);
3449 init_vunits_env_reg(".cdp", get_prev_char_depth);
3450 init_int_env_reg(".ce", get_center_lines);
3451 init_vunits_env_reg(".cht", get_prev_char_height);
3452 init_hunits_env_reg(".csk", get_prev_char_skew);
3453 init_string_env_reg(".ev", get_name_string);
3454 init_int_env_reg(".f", get_font);
3455 init_string_env_reg(".fam", get_font_family_string);
3456 init_string_env_reg(".fn", get_font_name_string);
3457 init_int_env_reg(".height", get_char_height);
3458 init_int_env_reg(".hlc", get_hyphen_line_count);
3459 init_int_env_reg(".hlm", get_hyphen_line_max);
3460 init_int_env_reg(".hy", get_hyphenation_flags);
3461 init_hunits_env_reg(".hym", get_hyphenation_margin);
3462 init_hunits_env_reg(".hys", get_hyphenation_space);
3463 init_hunits_env_reg(".i", get_indent);
3464 init_hunits_env_reg(".in", get_saved_indent);
3465 init_int_env_reg(".int", get_prev_line_interrupted);
3466 init_int_env_reg(".linetabs", get_line_tabs);
3467 init_hunits_env_reg(".lt", get_title_length);
3468 init_int_env_reg(".j", get_adjust_mode);
3469 init_hunits_env_reg(".k", get_text_length);
3470 init_int_env_reg(".L", get_line_spacing);
3471 init_hunits_env_reg(".l", get_line_length);
3472 init_hunits_env_reg(".ll", get_saved_line_length);
3473 init_string_env_reg(".M", get_fill_color_string);
3474 init_string_env_reg(".m", get_glyph_color_string);
3475 init_hunits_env_reg(".n", get_prev_text_length);
3476 init_int_env_reg(".ps", get_point_size);
3477 init_int_env_reg(".psr", get_requested_point_size);
3478 init_vunits_env_reg(".pvs", get_post_vertical_spacing);
3479 init_int_env_reg(".rj", get_right_justify_lines);
3480 init_string_env_reg(".s", get_point_size_string);
3481 init_int_env_reg(".slant", get_char_slant);
3482 init_int_env_reg(".ss", get_space_size);
3483 init_int_env_reg(".sss", get_sentence_space_size);
3484 init_string_env_reg(".sr", get_requested_point_size_string);
3485 init_string_env_reg(".sty", get_style_name_string);
3486 init_string_env_reg(".tabs", get_tabs);
3487 init_int_env_reg(".u", get_fill);
3488 init_vunits_env_reg(".v", get_vertical_spacing);
3489 init_hunits_env_reg(".w", get_prev_char_width);
3490 init_int_env_reg(".zoom", get_zoom);
3491 number_reg_dictionary.define("ct", new variable_reg(&ct_reg_contents));
3492 number_reg_dictionary.define("hp", new horizontal_place_reg);
3493 number_reg_dictionary.define("ln", new variable_reg(&next_line_number));
3494 number_reg_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
3495 number_reg_dictionary.define("rst", new variable_reg(&rst_reg_contents));
3496 number_reg_dictionary.define("sb", new variable_reg(&sb_reg_contents));
3497 number_reg_dictionary.define("skw", new variable_reg(&skw_reg_contents));
3498 number_reg_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
3499 number_reg_dictionary.define("st", new variable_reg(&st_reg_contents));
3502 // Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
3504 struct trie_node;
3506 class trie {
3507 trie_node *tp;
3508 virtual void do_match(int len, void *val) = 0;
3509 virtual void do_delete(void *) = 0;
3510 void delete_trie_node(trie_node *);
3511 public:
3512 trie() : tp(0) {}
3513 virtual ~trie(); // virtual to shut up g++
3514 void insert(const char *, int, void *);
3515 // find calls do_match for each match it finds
3516 void find(const char *pat, int patlen);
3517 void clear();
3520 class hyphen_trie : private trie {
3521 int *h;
3522 void do_match(int i, void *v);
3523 void do_delete(void *v);
3524 void insert_pattern(const char *pat, int patlen, int *num);
3525 void insert_hyphenation(dictionary *ex, const char *pat, int patlen);
3526 int hpf_getc(file_case *fcp);
3527 public:
3528 hyphen_trie() {}
3529 ~hyphen_trie() {}
3530 void hyphenate(const char *word, int len, int *hyphens);
3531 void read_patterns_file(const char *name, int append, dictionary *ex);
3534 struct hyphenation_language {
3535 symbol name;
3536 dictionary exceptions;
3537 hyphen_trie patterns;
3538 hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
3539 ~hyphenation_language() { }
3542 dictionary language_dictionary(5);
3543 hyphenation_language *current_language = 0;
3545 static void set_hyphenation_language()
3547 symbol nm = get_name(1);
3548 if (!nm.is_null()) {
3549 current_language = (hyphenation_language *)language_dictionary.lookup(nm);
3550 if (!current_language) {
3551 current_language = new hyphenation_language(nm);
3552 (void)language_dictionary.lookup(nm, (void *)current_language);
3555 skip_line();
3558 const int WORD_MAX = 256; // we use unsigned char for offsets in
3559 // hyphenation exceptions
3561 static void hyphen_word()
3563 if (!current_language) {
3564 error("no current hyphenation language");
3565 skip_line();
3566 return;
3568 char buf[WORD_MAX + 1];
3569 unsigned char pos[WORD_MAX + 2];
3570 for (;;) {
3571 tok.skip();
3572 if (tok.newline() || tok.eof())
3573 break;
3574 int i = 0;
3575 int npos = 0;
3576 while (i < WORD_MAX && !tok.space() && !tok.newline() && !tok.eof()) {
3577 charinfo *ci = tok.get_char(1);
3578 if (ci == 0) {
3579 skip_line();
3580 return;
3582 tok.next();
3583 if (ci->get_ascii_code() == '-') {
3584 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3585 pos[npos++] = i;
3587 else {
3588 unsigned char c = ci->get_hyphenation_code();
3589 if (c == 0)
3590 break;
3591 buf[i++] = c;
3594 if (i > 0) {
3595 pos[npos] = 0;
3596 buf[i] = 0;
3597 unsigned char *tem = new unsigned char[npos + 1];
3598 memcpy(tem, pos, npos + 1);
3599 tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf),
3600 tem);
3601 if (tem)
3602 a_delete tem;
3605 skip_line();
3608 struct trie_node {
3609 char c;
3610 trie_node *down;
3611 trie_node *right;
3612 void *val;
3613 trie_node(char, trie_node *);
3616 trie_node::trie_node(char ch, trie_node *p)
3617 : c(ch), down(0), right(p), val(0)
3621 trie::~trie()
3623 clear();
3626 void trie::clear()
3628 delete_trie_node(tp);
3629 tp = 0;
3633 void trie::delete_trie_node(trie_node *p)
3635 if (p) {
3636 delete_trie_node(p->down);
3637 delete_trie_node(p->right);
3638 if (p->val)
3639 do_delete(p->val);
3640 delete p;
3644 void trie::insert(const char *pat, int patlen, void *val)
3646 trie_node **p = &tp;
3647 assert(patlen > 0 && pat != 0);
3648 for (;;) {
3649 while (*p != 0 && (*p)->c < pat[0])
3650 p = &((*p)->right);
3651 if (*p == 0 || (*p)->c != pat[0])
3652 *p = new trie_node(pat[0], *p);
3653 if (--patlen == 0) {
3654 (*p)->val = val;
3655 break;
3657 ++pat;
3658 p = &((*p)->down);
3662 void trie::find(const char *pat, int patlen)
3664 trie_node *p = tp;
3665 for (int i = 0; p != 0 && i < patlen; i++) {
3666 while (p != 0 && p->c < pat[i])
3667 p = p->right;
3668 if (p != 0 && p->c == pat[i]) {
3669 if (p->val != 0)
3670 do_match(i+1, p->val);
3671 p = p->down;
3673 else
3674 break;
3678 struct operation {
3679 operation *next;
3680 short distance;
3681 short num;
3682 operation(int, int, operation *);
3685 operation::operation(int i, int j, operation *op)
3686 : next(op), distance(j), num(i)
3690 void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
3692 operation *op = 0;
3693 for (int i = 0; i < patlen+1; i++)
3694 if (num[i] != 0)
3695 op = new operation(num[i], patlen - i, op);
3696 insert(pat, patlen, op);
3699 void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
3700 int patlen)
3702 char buf[WORD_MAX + 1];
3703 unsigned char pos[WORD_MAX + 2];
3704 int i = 0, j = 0;
3705 int npos = 0;
3706 while (j < patlen) {
3707 unsigned char c = pat[j++];
3708 if (c == '-') {
3709 if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3710 pos[npos++] = i;
3712 else
3713 buf[i++] = hpf_code_table[c];
3715 if (i > 0) {
3716 pos[npos] = 0;
3717 buf[i] = 0;
3718 unsigned char *tem = new unsigned char[npos + 1];
3719 memcpy(tem, pos, npos + 1);
3720 tem = (unsigned char *)ex->lookup(symbol(buf), tem);
3721 if (tem)
3722 a_delete tem;
3726 void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
3728 int j;
3729 for (j = 0; j < len + 1; j++)
3730 hyphens[j] = 0;
3731 for (j = 0; j < len - 1; j++) {
3732 h = hyphens + j;
3733 find(word + j, len - j);
3737 inline int max(int m, int n)
3739 return m > n ? m : n;
3742 void hyphen_trie::do_match(int i, void *v)
3744 operation *op = (operation *)v;
3745 while (op != 0) {
3746 h[i - op->distance] = max(h[i - op->distance], op->num);
3747 op = op->next;
3751 void hyphen_trie::do_delete(void *v)
3753 operation *op = (operation *)v;
3754 while (op) {
3755 operation *tem = op;
3756 op = tem->next;
3757 delete tem;
3761 /* We use very simple rules to parse TeX's hyphenation patterns.
3763 . `%' starts a comment even if preceded by `\'.
3765 . No support for digraphs and like `\$'.
3767 . `^^xx' (`x' is 0-9 or a-f), and `^^x' (character code of `x' in the
3768 range 0-127) are recognized; other use of `^' causes an error.
3770 . No macro expansion.
3772 . We check for the expression `\patterns{...}' (possibly with
3773 whitespace before and after the braces). Everything between the
3774 braces is taken as hyphenation patterns. Consequently, `{' and `}'
3775 are not allowed in patterns.
3777 . Similarly, `\hyphenation{...}' gives a list of hyphenation
3778 exceptions.
3780 . `\endinput' is recognized also.
3782 . For backwards compatibility, if `\patterns' is missing, the
3783 whole file is treated as a list of hyphenation patterns (only
3784 recognizing `%' as the start of a comment.
3788 int hyphen_trie::hpf_getc(file_case *fcp)
3790 int c = fcp->get_c();
3791 int c1;
3792 int cc = 0;
3793 if (c != '^')
3794 return c;
3795 c = fcp->get_c();
3796 if (c != '^')
3797 goto fail;
3798 c = fcp->get_c();
3799 c1 = fcp->get_c();
3800 if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
3801 && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
3802 if (c >= '0' && c <= '9')
3803 c -= '0';
3804 else
3805 c = c - 'a' + 10;
3806 if (c1 >= '0' && c1 <= '9')
3807 c1 -= '0';
3808 else
3809 c1 = c1 - 'a' + 10;
3810 cc = c * 16 + c1;
3812 else {
3813 fcp->unget_c(c1);
3814 if (c >= 0 && c <= 63)
3815 cc = c + 64;
3816 else if (c >= 64 && c <= 127)
3817 cc = c - 64;
3818 else
3819 goto fail;
3821 return cc;
3822 fail:
3823 error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
3824 return c;
3827 void hyphen_trie::read_patterns_file(const char *name, int append,
3828 dictionary *ex)
3830 if (!append)
3831 clear();
3833 char buf[WORD_MAX];
3834 for (int i = 0; i < WORD_MAX; i++)
3835 buf[i] = 0;
3836 int num[WORD_MAX+1];
3837 int have_patterns = 0; // we've seen \patterns
3838 int final_pattern = 0; // 1 if we have a trailing closing brace
3839 int have_hyphenation = 0; // we've seen \hyphenation
3840 int final_hyphenation = 0; // 1 if we have a trailing closing brace
3841 int have_keyword = 0; // we've seen either \patterns or \hyphenation
3842 int traditional = 0; // don't handle \patterns
3843 int c;
3844 file_case *fcp;
3845 if ((fcp = mac_path->open_file(name, fcp->fc_const_path)) == NULL) {
3846 error("can't find hyphenation patterns file `%1'", name);
3847 goto jleave;
3850 for (c = hpf_getc(fcp);;) {
3851 for (;;) {
3852 if (c == '%') { // skip comments
3853 do {
3854 c = fcp->get_c();
3855 } while (c != EOF && c != '\n');
3857 if (c == EOF || !csspace(c))
3858 break;
3859 c = hpf_getc(fcp);
3861 if (c == EOF) {
3862 if (have_keyword || traditional) // we are done
3863 break;
3864 else { // rescan file in `traditional' mode
3865 fcp->rewind();
3866 traditional = 1;
3867 c = hpf_getc(fcp);
3868 continue;
3871 int i = 0;
3872 num[0] = 0;
3873 if (!(c == '{' || c == '}')) { // skip braces at line start
3874 do { // scan patterns
3875 if (csdigit(c))
3876 num[i] = c - '0';
3877 else {
3878 buf[i++] = c;
3879 num[i] = 0;
3881 c = hpf_getc(fcp);
3882 } while (i < WORD_MAX && c != EOF && !csspace(c)
3883 && c != '%' && c != '{' && c != '}');
3885 if (!traditional) {
3886 if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
3887 while (csspace(c))
3888 c = hpf_getc(fcp);
3889 if (c == '{') {
3890 if (have_patterns || have_hyphenation)
3891 error("\\patterns not allowed inside of %1 group",
3892 have_patterns ? "\\patterns" : "\\hyphenation");
3893 else {
3894 have_patterns = 1;
3895 have_keyword = 1;
3897 c = hpf_getc(fcp);
3898 continue;
3901 else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
3902 while (csspace(c))
3903 c = hpf_getc(fcp);
3904 if (c == '{') {
3905 if (have_patterns || have_hyphenation)
3906 error("\\hyphenation not allowed inside of %1 group",
3907 have_patterns ? "\\patterns" : "\\hyphenation");
3908 else {
3909 have_hyphenation = 1;
3910 have_keyword = 1;
3912 c = hpf_getc(fcp);
3913 continue;
3916 else if (strstr(buf, "\\endinput")) {
3917 if (have_patterns || have_hyphenation)
3918 error("found \\endinput inside of %1 group",
3919 have_patterns ? "\\patterns" : "\\hyphenation");
3920 break;
3922 else if (c == '}') {
3923 if (have_patterns) {
3924 have_patterns = 0;
3925 if (i > 0)
3926 final_pattern = 1;
3928 else if (have_hyphenation) {
3929 have_hyphenation = 0;
3930 if (i > 0)
3931 final_hyphenation = 1;
3933 c = hpf_getc(fcp);
3935 else if (c == '{') {
3936 if (have_patterns || have_hyphenation)
3937 error("`{' not allowed within %1 group",
3938 have_patterns ? "\\patterns" : "\\hyphenation");
3939 c = hpf_getc(fcp); // skipped if not starting \patterns
3940 // or \hyphenation
3943 else {
3944 if (c == '{' || c == '}')
3945 c = hpf_getc(fcp);
3947 if (i > 0) {
3948 if (have_patterns || final_pattern || traditional) {
3949 for (int j = 0; j < i; j++)
3950 buf[j] = hpf_code_table[(unsigned char)buf[j]];
3951 insert_pattern(buf, i, num);
3952 final_pattern = 0;
3954 else if (have_hyphenation || final_hyphenation) {
3955 insert_hyphenation(ex, buf, i);
3956 final_hyphenation = 0;
3961 delete fcp;
3962 jleave:
3963 return;
3966 void hyphenate(hyphen_list *h, unsigned flags)
3968 if (!current_language)
3969 return;
3970 while (h) {
3971 while (h && h->hyphenation_code == 0)
3972 h = h->next;
3973 int len = 0;
3974 char hbuf[WORD_MAX + 2];
3975 char *buf = hbuf + 1;
3976 hyphen_list *tem;
3977 for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
3978 if (tem->hyphenation_code != 0)
3979 buf[len++] = tem->hyphenation_code;
3980 else
3981 break;
3983 hyphen_list *nexth = tem;
3984 if (len > 2) {
3985 buf[len] = 0;
3986 unsigned char *pos
3987 = (unsigned char *)current_language->exceptions.lookup(buf);
3988 if (pos != 0) {
3989 int j = 0;
3990 int i = 1;
3991 for (tem = h; tem != 0; tem = tem->next, i++)
3992 if (pos[j] == i) {
3993 tem->hyphen = 1;
3994 j++;
3997 else {
3998 hbuf[0] = hbuf[len + 1] = '.';
3999 int num[WORD_MAX + 3];
4000 current_language->patterns.hyphenate(hbuf, len + 2, num);
4001 int i;
4002 num[2] = 0;
4003 if (flags & HYPHEN_FIRST_CHARS)
4004 num[3] = 0;
4005 if (flags & HYPHEN_LAST_CHARS)
4006 --len;
4007 for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
4008 if (num[i] & 1)
4009 tem->hyphen = 1;
4012 h = nexth;
4016 static void do_hyphenation_patterns_file(int append)
4018 symbol name = get_long_name(1);
4019 if (!name.is_null()) {
4020 if (!current_language)
4021 error("no current hyphenation language");
4022 else
4023 current_language->patterns.read_patterns_file(
4024 name.contents(), append,
4025 &current_language->exceptions);
4027 skip_line();
4030 static void hyphenation_patterns_file()
4032 do_hyphenation_patterns_file(0);
4035 static void hyphenation_patterns_file_append()
4037 do_hyphenation_patterns_file(1);
4040 class hyphenation_language_reg : public reg {
4041 public:
4042 const char *get_string();
4045 const char *hyphenation_language_reg::get_string()
4047 return current_language ? current_language->name.contents() : "";
4050 void init_hyphen_requests()
4052 init_request("hw", hyphen_word);
4053 init_request("hla", set_hyphenation_language);
4054 init_request("hpf", hyphenation_patterns_file);
4055 init_request("hpfa", hyphenation_patterns_file_append);
4056 number_reg_dictionary.define(".hla", new hyphenation_language_reg);