Line data Source code
1 : /* Copyright 1989-2025 Free Software Foundation, Inc.
2 : 2020-2025 G. Branden Robinson
3 :
4 : Written by James Clark (jjc@jclark.com)
5 :
6 : This file is part of groff, the GNU roff typesetting system.
7 :
8 : groff is free software; you can redistribute it and/or modify it under
9 : the terms of the GNU General Public License as published by the Free
10 : Software Foundation, either version 3 of the License, or
11 : (at your option) any later version.
12 :
13 : groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 : WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 : FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 : for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program. If not, see <http://www.gnu.org/licenses/>. */
20 :
21 : #ifdef HAVE_CONFIG_H
22 : #include <config.h>
23 : #endif
24 :
25 : #include <errno.h> // errno
26 : #include <math.h> // ceil(), fabs()
27 :
28 : #include <vector>
29 : #include <algorithm> // find()
30 :
31 : #include "troff.h"
32 : #include "dictionary.h"
33 : #include "hvunits.h"
34 : #include "stringclass.h"
35 : #include "mtsm.h"
36 : #include "env.h"
37 : #include "request.h"
38 : #include "node.h"
39 : #include "token.h"
40 : #include "div.h"
41 : #include "reg.h"
42 : #include "font.h"
43 : #include "charinfo.h"
44 : #include "macropath.h"
45 : #include "input.h"
46 :
47 : symbol default_family("T");
48 :
49 : // C++11: Use `enum : char`.
50 : // TODO: Can we move this into a (possibly struct/class-implicit)
51 : // namespace?
52 : enum { ADJUST_LEFT = 0,
53 : ADJUST_BOTH = 1,
54 : ADJUST_CENTER = 3,
55 : ADJUST_RIGHT = 5,
56 : ADJUST_MAX = 5
57 : };
58 :
59 : // C++11: Use `enum : unsigned char`.
60 : // TODO: Can we move this into a (possibly struct/class-implicit)
61 : // namespace?
62 : enum {
63 : // Not all combinations are valid; see hyphenate_request() below.
64 : HYPHEN_NONE = 0,
65 : HYPHEN_DEFAULT = 1,
66 : HYPHEN_NOT_LAST_LINE = 2,
67 : HYPHEN_NOT_LAST_CHARS = 4,
68 : HYPHEN_NOT_FIRST_CHARS = 8,
69 : HYPHEN_LAST_CHAR = 16,
70 : HYPHEN_FIRST_CHAR = 32,
71 : HYPHEN_MAX = 63
72 : };
73 :
74 : struct env_list_node {
75 : environment *env;
76 : env_list_node *next;
77 18938 : env_list_node(environment *e, env_list_node *p) : env(e), next(p) {}
78 : };
79 :
80 : env_list_node *env_stack;
81 : dictionary env_dictionary(10);
82 : environment *curenv;
83 : static int next_line_number = 0;
84 : extern statem *get_diversion_state();
85 :
86 : charinfo *field_delimiter_char;
87 : charinfo *padding_indicator_char;
88 :
89 : bool translate_space_to_dummy = false;
90 :
91 : // forward declaration
92 : static void hyphenate(hyphen_list *h, unsigned int flags);
93 :
94 : class pending_output_line {
95 : node *nd;
96 : bool suppress_filling;
97 : bool was_centered;
98 : vunits vs;
99 : vunits post_vs;
100 : hunits width;
101 : #ifdef WIDOW_CONTROL
102 : bool is_last_line; // Is it the last line of the paragraph?
103 : #endif /* WIDOW_CONTROL */
104 : public:
105 : pending_output_line *next;
106 :
107 : pending_output_line(node *, bool, vunits, vunits, hunits, bool,
108 : pending_output_line * = 0 /* nullptr */);
109 : ~pending_output_line();
110 : bool output();
111 :
112 : #ifdef WIDOW_CONTROL
113 : friend void environment::mark_last_line();
114 : friend void environment::output(node *, bool, vunits, vunits, hunits,
115 : bool);
116 : #endif /* WIDOW_CONTROL */
117 : };
118 :
119 0 : pending_output_line::pending_output_line(node *nod, bool nf, vunits v,
120 : vunits pv, hunits w, bool ce,
121 0 : pending_output_line *p)
122 : : nd(nod), suppress_filling(nf), was_centered(ce), vs(v), post_vs(pv),
123 : width(w),
124 : #ifdef WIDOW_CONTROL
125 : is_last_line(false),
126 : #endif /* WIDOW_CONTROL */
127 0 : next(p)
128 : {
129 0 : }
130 :
131 0 : pending_output_line::~pending_output_line()
132 : {
133 0 : delete_node_list(nd);
134 0 : }
135 :
136 0 : bool pending_output_line::output()
137 : {
138 0 : if (was_trap_sprung)
139 0 : return false;
140 : #ifdef WIDOW_CONTROL
141 : if (next && next->is_last_line && !suppress_filling) {
142 : curdiv->need(vs + post_vs + vunits(vresolution));
143 : if (was_trap_sprung) {
144 : next->is_last_line = false; // Try to avoid infinite loops.
145 : return false;
146 : }
147 : }
148 : #endif
149 0 : curenv->construct_format_state(nd, was_centered, !suppress_filling);
150 0 : curdiv->output(nd, suppress_filling, vs, post_vs, width);
151 0 : nd = 0 /* nullptr */;
152 0 : return true;
153 : }
154 :
155 527376 : void environment::output(node *nd, bool suppress_filling,
156 : vunits vs, vunits post_vs,
157 : hunits width, bool was_centered)
158 : {
159 : #ifdef WIDOW_CONTROL
160 : while (pending_lines != 0 /* nullptr */) {
161 : if (want_widow_control && !pending_lines->suppress_filling
162 : && !pending_lines->next)
163 : break;
164 : if (!pending_lines->output())
165 : break;
166 : pending_output_line *tem = pending_lines;
167 : pending_lines = pending_lines->next;
168 : delete tem;
169 : }
170 : #else /* WIDOW_CONTROL */
171 527376 : output_pending_lines();
172 : #endif /* WIDOW_CONTROL */
173 527376 : if (!was_trap_sprung && !pending_lines
174 : #ifdef WIDOW_CONTROL
175 : && (!want_widow_control || suppress_filling)
176 : #endif /* WIDOW_CONTROL */
177 : ) {
178 527376 : curenv->construct_format_state(nd, was_centered, !suppress_filling);
179 527376 : curdiv->output(nd, suppress_filling, vs, post_vs, width);
180 : }
181 : else {
182 : pending_output_line **p;
183 0 : for (p = &pending_lines; *p; p = &(*p)->next)
184 : ;
185 0 : *p = new pending_output_line(nd, suppress_filling, vs, post_vs,
186 0 : width, was_centered);
187 : }
188 527361 : }
189 :
190 : // a line from .tl goes at the head of the queue
191 :
192 3298 : void environment::output_title(node *nd, bool suppress_filling,
193 : vunits vs, vunits post_vs,
194 : hunits width)
195 : {
196 3298 : if (!was_trap_sprung)
197 3298 : curdiv->output(nd, suppress_filling, vs, post_vs, width);
198 : else
199 0 : pending_lines = new pending_output_line(nd, suppress_filling, vs,
200 : post_vs, width, 0,
201 0 : pending_lines);
202 3298 : }
203 :
204 566955 : void environment::output_pending_lines()
205 : {
206 566955 : while ((pending_lines != 0 /* nullptr */) && pending_lines->output())
207 : {
208 0 : pending_output_line *tem = pending_lines;
209 0 : pending_lines = pending_lines->next;
210 0 : delete tem;
211 : }
212 566955 : }
213 :
214 : #ifdef WIDOW_CONTROL
215 :
216 : void environment::mark_last_line()
217 : {
218 : if (!want_widow_control || !pending_lines)
219 : return;
220 : pending_output_line *p;
221 : for (p = pending_lines; p->next; p = p->next)
222 : ;
223 : if (!p->suppress_filling)
224 : p->is_last_line = true;
225 : }
226 :
227 : void widow_control_request()
228 : {
229 : int n;
230 : if (has_arg() && read_integer(&n))
231 : curenv->want_widow_control = (n > 0);
232 : else
233 : curenv->want_widow_control = true;
234 : skip_line();
235 : }
236 :
237 : #endif /* WIDOW_CONTROL */
238 :
239 : /* font_size functions */
240 :
241 : size_range *font_size::size_list = 0 /* nullptr */;
242 : int font_size::nranges = 0;
243 :
244 : extern "C" {
245 :
246 281 : int compare_ranges(const void *p1, const void *p2)
247 : {
248 281 : return ( static_cast<size_range *>(const_cast<void *>(p1))->min
249 281 : - static_cast<size_range *>(const_cast<void *>(p2))->min);
250 : }
251 :
252 : }
253 :
254 1419 : void font_size::init_size_list(int *sizes)
255 : {
256 1419 : nranges = 0;
257 3039 : while (sizes[nranges * 2] != 0)
258 1620 : nranges++;
259 1419 : assert(nranges > 0);
260 1419 : size_list = new size_range[nranges];
261 3039 : for (int i = 0; i < nranges; i++) {
262 1620 : size_list[i].min = sizes[i * 2];
263 1620 : size_list[i].max = sizes[i * 2 + 1];
264 : }
265 1419 : qsort(size_list, nranges, sizeof(size_range), compare_ranges);
266 1419 : }
267 :
268 2 : void font_size::dump_size_list()
269 : {
270 : int lo, hi;
271 2 : errprint(" valid type size list for selected font: ");
272 2 : if (nranges == 0)
273 0 : errprint(" empty!");
274 : else {
275 2 : bool need_comma = false;
276 5 : for (int i = 0; i < nranges; i++) {
277 3 : lo = size_list[i].min;
278 3 : hi = size_list[i].max;
279 3 : if (need_comma)
280 1 : errprint(", ");
281 3 : if (lo == hi)
282 1 : errprint("%1s", lo);
283 : else
284 2 : errprint("%1s-%2s", lo, hi);
285 3 : need_comma = true;
286 : }
287 : }
288 2 : errprint("\n");
289 2 : fflush(stderr);
290 2 : }
291 :
292 106533 : font_size::font_size(int sp)
293 : {
294 106616 : for (int i = 0; i < nranges; i++) {
295 106613 : if (sp < size_list[i].min) {
296 17406 : if (i > 0 && size_list[i].min - sp >= sp - size_list[i - 1].max)
297 0 : p = size_list[i - 1].max;
298 : else
299 17406 : p = size_list[i].min;
300 17406 : return;
301 : }
302 89207 : if (sp <= size_list[i].max) {
303 89124 : p = sp;
304 89124 : return;
305 : }
306 : }
307 3 : p = size_list[nranges - 1].max;
308 : }
309 :
310 190485 : int font_size::to_units()
311 : {
312 190485 : return scale(p, units_per_inch, sizescale*72);
313 : }
314 :
315 : // we can't do this in a static constructor because various dictionaries
316 : // have to get initialized first
317 : static symbol default_environment_name("0");
318 :
319 1418 : void init_environments()
320 : {
321 1418 : curenv = new environment(default_environment_name);
322 1418 : (void) env_dictionary.lookup(default_environment_name, curenv);
323 1418 : }
324 :
325 : // Set tab character, used to fill out the remainder of a tab stop where
326 : // a tab (TAB, U+0009) occurs in the input. If a null pointer,
327 : // horizontal motion "fills" the tab stop.
328 0 : void tab_character_request()
329 : {
330 0 : curenv->tab_char = read_character();
331 0 : skip_line();
332 0 : }
333 :
334 : // Set leader character, used to fill out the remainder of a tab stop
335 : // where a leader (SOH, U+0001) occurs in the input. If a null pointer,
336 : // horizontal motion "fills" the tab stop. Used when the behavior of a
337 : // null pointer tab character is also desired on the same output line
338 : // (or more generally).
339 247 : void leader_character_request()
340 : {
341 247 : curenv->leader_char = read_character();
342 247 : skip_line();
343 247 : }
344 :
345 12902599 : void environment::add_char(charinfo *ci)
346 : {
347 12902599 : assert(!was_line_interrupted);
348 12902599 : node *gc_np = 0 /* nullptr */;
349 12902599 : if (was_line_interrupted)
350 : ;
351 : // don't allow fields in dummy environments
352 12902599 : else if (ci == field_delimiter_char && !is_dummy_env) {
353 1962 : if (has_current_field)
354 981 : wrap_up_field();
355 : else
356 981 : start_field();
357 : }
358 12900637 : else if (has_current_field && ci == padding_indicator_char)
359 1640 : add_padding();
360 12898997 : else if (current_tab != TAB_NONE) {
361 9 : if (tab_contents == 0 /* nullptr */)
362 2 : tab_contents = new line_start_node;
363 9 : if (ci != hyphen_indicator_char) {
364 : int s;
365 9 : tab_contents = tab_contents->add_char(ci, this, &tab_width, &s,
366 9 : &gc_np);
367 : }
368 : else
369 0 : tab_contents = tab_contents->add_discretionary_hyphen();
370 : }
371 : else {
372 12898988 : if (line == 0 /* nullptr */)
373 1548559 : start_line();
374 : #if 0
375 : fprintf(stderr, "current line is\n");
376 : dump_node_list_in_reverse(line);
377 : #endif
378 12898988 : if (ci != hyphen_indicator_char)
379 12898988 : line = line->add_char(ci, this, &width_total, &space_total, &gc_np);
380 : else
381 0 : line = line->add_discretionary_hyphen();
382 : }
383 : #if 0
384 : fprintf(stderr, "now after we have added character the line is\n");
385 : dump_node_list_in_reverse(line);
386 : #endif
387 12902599 : if ((!suppress_push) && gc_np) {
388 3927653 : if (gc_np && (gc_np->state == 0 /* nullptr */)) {
389 3927653 : gc_np->state = construct_state(false);
390 3927653 : gc_np->push_state = get_diversion_state();
391 : }
392 0 : else if (line && (line->state == 0 /* nullptr */)) {
393 0 : line->state = construct_state(false);
394 0 : line->push_state = get_diversion_state();
395 : }
396 : }
397 : #if 0
398 : fprintf(stderr, "now we have possibly added the state the line is\n");
399 : dump_node_list_in_reverse(line);
400 : #endif
401 12902599 : }
402 :
403 797 : node *environment::make_char_node(charinfo *ci)
404 : {
405 797 : return make_node(ci, this);
406 : }
407 :
408 10495030 : void environment::add_node(node *nd)
409 : {
410 10495030 : assert(nd != 0 /* nullptr */);
411 10495030 : if (nd == 0 /* nullptr */)
412 0 : return;
413 10495030 : if (!suppress_push) {
414 9916570 : if (nd->is_special && nd->state == 0 /* nullptr */)
415 221112 : nd->state = construct_state(false);
416 9916570 : nd->push_state = get_diversion_state();
417 : }
418 :
419 10495030 : if ((current_tab != TAB_NONE) || has_current_field)
420 2024 : nd->freeze_space();
421 10495030 : if (was_line_interrupted) {
422 53 : delete nd;
423 : }
424 10494977 : else if (current_tab != TAB_NONE) {
425 1560 : nd->next = tab_contents;
426 1560 : tab_contents = nd;
427 1560 : tab_width += nd->width();
428 : }
429 : else {
430 10493417 : if (line == 0 /* nullptr */) {
431 964930 : if (is_discarding && nd->discardable()) {
432 : // XXX possibly: input_line_start -= nd->width();
433 0 : delete nd;
434 0 : return;
435 : }
436 964930 : start_line();
437 : }
438 10493417 : width_total += nd->width();
439 10493417 : space_total += nd->nspaces();
440 10493417 : nd->next = line;
441 10493417 : line = nd;
442 10493417 : construct_new_line_state(line);
443 : }
444 : }
445 :
446 82840 : void environment::add_hyphen_indicator()
447 : {
448 82840 : if ((current_tab != TAB_NONE)
449 82840 : || was_line_interrupted
450 82840 : || has_current_field
451 82819 : || (hyphen_indicator_char != 0 /* nullptr */))
452 21 : return;
453 82819 : if (line == 0 /* nullptr */)
454 60879 : start_line();
455 82819 : line = line->add_discretionary_hyphen();
456 : }
457 :
458 1413 : unsigned int environment::get_hyphenation_mode()
459 : {
460 1413 : return hyphenation_mode;
461 : }
462 :
463 0 : unsigned int environment::get_hyphenation_mode_default()
464 : {
465 0 : return hyphenation_mode_default;
466 : }
467 :
468 472 : int environment::get_hyphen_line_max()
469 : {
470 472 : return hyphen_line_max;
471 : }
472 :
473 0 : int environment::get_hyphen_line_count()
474 : {
475 0 : return hyphen_line_count;
476 : }
477 :
478 840 : int environment::get_centered_line_count()
479 : {
480 840 : return centered_line_count;
481 : }
482 :
483 3 : int environment::get_right_aligned_line_count()
484 : {
485 3 : return right_aligned_line_count;
486 : }
487 :
488 477 : int environment::get_no_number_count()
489 : {
490 477 : return no_number_count;
491 : }
492 :
493 0 : int environment::get_input_trap_line_count()
494 : {
495 0 : return input_trap_count;
496 : }
497 :
498 0 : int environment::get_input_trap_respects_continuation()
499 : {
500 0 : return continued_input_trap;
501 : }
502 :
503 0 : const char *environment::get_input_trap_macro()
504 : {
505 0 : return input_trap.contents();
506 : }
507 :
508 33376 : void environment::add_italic_correction()
509 : {
510 33376 : if (current_tab != TAB_NONE) {
511 0 : if (tab_contents != 0 /* nullptr */)
512 0 : tab_contents = tab_contents->add_italic_correction(&tab_width);
513 : }
514 33376 : else if (line)
515 33376 : line = line->add_italic_correction(&width_total);
516 33376 : }
517 :
518 207266 : void environment::space_newline()
519 : {
520 207266 : assert(!was_line_interrupted);
521 207266 : assert((current_tab == TAB_NONE) && !has_current_field);
522 207266 : if (was_line_interrupted)
523 0 : return;
524 207266 : hunits x = H0;
525 207266 : hunits sw = env_space_width(this);
526 207266 : hunits ssw = env_sentence_space_width(this);
527 207266 : if (!translate_space_to_dummy) {
528 207266 : x = sw;
529 207266 : if (node_list_ends_sentence(line) == 1)
530 40029 : x += ssw;
531 : }
532 207266 : width_list *w = new width_list(sw, ssw);
533 207266 : if (node_list_ends_sentence(line) == 1)
534 40029 : w->next = new width_list(sw, ssw);
535 207266 : if (line != 0 /* nullptr */ && line->did_space_merge(x, sw, ssw)) {
536 0 : width_total += x;
537 0 : return;
538 : }
539 207266 : add_node(new word_space_node(x, get_fill_color(), w));
540 207266 : possibly_break_line(false /* must break here */, is_spreading);
541 207266 : is_spreading = false;
542 : }
543 :
544 581037 : void environment::space()
545 : {
546 581037 : space(env_space_width(this), env_sentence_space_width(this));
547 581037 : }
548 :
549 584623 : void environment::space(hunits space_width, hunits sentence_space_width)
550 : {
551 584623 : if (was_line_interrupted)
552 6322 : return;
553 584623 : if (has_current_field && padding_indicator_char == 0 /* nullptr */) {
554 0 : add_padding();
555 0 : return;
556 : }
557 584623 : hunits x = translate_space_to_dummy ? H0 : space_width;
558 584623 : node *p = (current_tab != TAB_NONE) ? tab_contents : line;
559 584623 : hunits *tp = (current_tab != TAB_NONE) ? &tab_width : &width_total;
560 541214 : if (p && p->nspaces() == 1 && p->width() == x
561 1125837 : && node_list_ends_sentence(p->next) == 1) {
562 1524 : hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
563 1524 : if (p->did_space_merge(xx, space_width, sentence_space_width)) {
564 1524 : *tp += xx;
565 1524 : return;
566 : }
567 : }
568 583099 : if (p && p->did_space_merge(x, space_width, sentence_space_width)) {
569 4798 : *tp += x;
570 4798 : return;
571 : }
572 578301 : add_node(new word_space_node(x,
573 578301 : get_fill_color(),
574 0 : new width_list(space_width,
575 578301 : sentence_space_width)));
576 578301 : possibly_break_line(false /* must break here */, is_spreading);
577 578301 : is_spreading = false;
578 : }
579 :
580 0 : static node *configure_space_underlining(bool b)
581 : {
582 0 : macro m;
583 0 : m.append_str("x u ");
584 0 : m.append(b ? '1' : '0');
585 0 : return new device_extension_node(m, 1);
586 : }
587 :
588 : // TODO: Kill this off in groff 1.24.0 release + 2 years. See input.cpp.
589 : std::vector<symbol> deprecated_font_identifiers;
590 : extern bool is_device_ps_or_pdf; // See input.cpp.
591 :
592 180757 : static void warn_if_font_name_deprecated(symbol nm)
593 : {
594 180757 : const char *name = nm.contents();
595 : std::vector<symbol>::iterator it
596 : = find(deprecated_font_identifiers.begin(),
597 180757 : deprecated_font_identifiers.end(), name);
598 180757 : if (it != deprecated_font_identifiers.end()) {
599 0 : warning(WARN_FONT, "font name '%1' is deprecated", name);
600 : // Warn only once for each name.
601 0 : deprecated_font_identifiers.erase(it);
602 : }
603 180757 : }
604 :
605 252556 : bool environment::set_font(symbol nm)
606 : {
607 252556 : if (was_line_interrupted)
608 0 : return true; // "no operation" is successful
609 : // TODO: Kill this off in groff 1.24.0 release + 2 years.
610 252556 : if (is_device_ps_or_pdf)
611 180757 : warn_if_font_name_deprecated(nm);
612 252556 : if (nm == symbol("P") || nm.is_empty()) {
613 16010 : if (family->resolve(prev_fontno) == FONT_NOT_MOUNTED)
614 0 : return false;
615 16010 : int tem = fontno;
616 16010 : fontno = prev_fontno;
617 16010 : prev_fontno = tem;
618 : }
619 : else {
620 236546 : prev_fontno = fontno;
621 236546 : int n = symbol_fontno(nm);
622 236546 : if (n < 0) {
623 29 : n = next_available_font_position();
624 29 : if (!mount_font(n, nm))
625 1 : return false;
626 : }
627 236545 : if (family->resolve(n) == FONT_NOT_MOUNTED)
628 0 : return false;
629 236545 : fontno = n;
630 : }
631 252555 : if (underline_spaces && fontno != prev_fontno) {
632 0 : if (fontno == get_underline_fontno())
633 0 : add_node(configure_space_underlining(true));
634 0 : if (prev_fontno == get_underline_fontno())
635 0 : add_node(configure_space_underlining(false));
636 : }
637 252555 : return true;
638 : }
639 :
640 67965 : bool environment::set_font(int n)
641 : {
642 67965 : if (was_line_interrupted)
643 0 : return false;
644 67965 : if (is_valid_font_mounting_position(n)) {
645 67964 : prev_fontno = fontno;
646 67964 : fontno = n;
647 : }
648 : else {
649 1 : warning(WARN_FONT, "no font mounted at position %1", n);
650 1 : return false;
651 : }
652 67964 : return true;
653 : }
654 :
655 45686 : void environment::set_family(symbol fam)
656 : {
657 45686 : if (was_line_interrupted)
658 376 : return;
659 45310 : if (fam.is_null() || fam.is_empty()) {
660 604 : int previous_mounting_position = prev_family->resolve(fontno);
661 604 : assert(previous_mounting_position >= 0);
662 604 : if (previous_mounting_position == FONT_NOT_MOUNTED)
663 0 : return;
664 604 : font_family *tem = family;
665 604 : family = prev_family;
666 604 : prev_family = tem;
667 : }
668 : else {
669 44706 : font_family *f = lookup_family(fam);
670 : // If the family isn't already in the dictionary, looking it up will
671 : // create an entry for it. That doesn't mean that it will be
672 : // resolvable to a real font when combined with a style name.
673 44706 : assert((f != 0 /* nullptr */) &&
674 : (0 != "font family dictionary lookup"));
675 44706 : if (0 /* nullptr */ == f)
676 0 : return;
677 44706 : if (f->resolve(fontno) == FONT_NOT_MOUNTED) {
678 0 : error("no font family named '%1' exists", fam.contents());
679 0 : return;
680 : }
681 44706 : prev_family = family;
682 44706 : family = f;
683 : }
684 : }
685 :
686 85776 : void environment::set_size(int n)
687 : {
688 85776 : if (was_line_interrupted)
689 0 : return;
690 85776 : if (0 == n) {
691 1824 : font_size temp = prev_size;
692 1824 : prev_size = size;
693 1824 : size = temp;
694 1824 : int temp2 = prev_requested_size;
695 1824 : prev_requested_size = requested_size;
696 1824 : requested_size = temp2;
697 : }
698 : else {
699 83952 : prev_size = size;
700 83952 : size = font_size(n);
701 83952 : prev_requested_size = requested_size;
702 83952 : requested_size = n;
703 : }
704 : }
705 :
706 615 : void environment::set_char_height(int n)
707 : {
708 615 : if (was_line_interrupted)
709 0 : return;
710 615 : if (n == requested_size || n <= 0)
711 282 : char_height = 0;
712 : else
713 333 : char_height = n;
714 : }
715 :
716 30 : void environment::set_char_slant(int n)
717 : {
718 30 : if (was_line_interrupted)
719 0 : return;
720 30 : char_slant = n;
721 : }
722 :
723 9408 : color *environment::get_prev_stroke_color()
724 : {
725 9408 : return prev_stroke_color;
726 : }
727 :
728 13170637 : color *environment::get_stroke_color()
729 : {
730 13170637 : return stroke_color;
731 : }
732 :
733 32487 : color *environment::get_prev_fill_color()
734 : {
735 32487 : return prev_fill_color;
736 : }
737 :
738 14763909 : color *environment::get_fill_color()
739 : {
740 14763909 : return fill_color;
741 : }
742 :
743 120055 : void environment::set_stroke_color(color *c)
744 : {
745 120055 : if (was_line_interrupted)
746 0 : return;
747 120055 : curenv->prev_stroke_color = curenv->stroke_color;
748 120055 : curenv->stroke_color = c;
749 : }
750 :
751 65328 : void environment::set_fill_color(color *c)
752 : {
753 65328 : if (was_line_interrupted)
754 0 : return;
755 65328 : curenv->prev_fill_color = curenv->fill_color;
756 65328 : curenv->fill_color = c;
757 : }
758 :
759 2620 : environment::environment(symbol nm)
760 : : is_dummy_env(false),
761 2620 : prev_line_length((units_per_inch*13)/2),
762 2620 : line_length((units_per_inch*13)/2),
763 2620 : prev_title_length((units_per_inch*13)/2),
764 2620 : title_length((units_per_inch*13)/2),
765 : prev_size(sizescale*10),
766 : size(sizescale*10),
767 2620 : requested_size(sizescale*10),
768 2620 : prev_requested_size(sizescale*10),
769 : char_height(0),
770 : char_slant(0),
771 : space_size(12),
772 : sentence_space_size(12),
773 : adjust_mode(ADJUST_BOTH),
774 : is_filling(true),
775 : was_line_interrupted(false),
776 : was_previous_line_interrupted(0),
777 : centered_line_count(0),
778 : right_aligned_line_count(0),
779 : prev_vertical_spacing(points_to_units(12)),
780 : vertical_spacing(points_to_units(12)),
781 : prev_post_vertical_spacing(0),
782 : post_vertical_spacing(0),
783 : prev_line_spacing(1),
784 : line_spacing(1),
785 : prev_indent(0),
786 : indent(0),
787 : temporary_indent(0),
788 : have_temporary_indent(false),
789 : underlined_line_count(0),
790 : underline_spaces(false),
791 : input_trap_count(-1),
792 : continued_input_trap(false),
793 : line(0 /* nullptr */),
794 : prev_text_length(0),
795 : width_total(0),
796 : space_total(0),
797 : input_line_start(0),
798 : using_line_tabs(false),
799 : current_tab(TAB_NONE),
800 : leader_node(0 /* nullptr */),
801 : tab_char(0 /* nullptr */),
802 2620 : leader_char(charset_table['.']),
803 : has_current_field(false),
804 : is_discarding(false),
805 : is_spreading(false),
806 : margin_character_flags(0U),
807 : margin_character_node(0 /* nullptr */),
808 : margin_character_distance(points_to_units(10)),
809 : numbering_nodes(0 /* nullptr */),
810 : number_text_separation(1),
811 : line_number_indent(0),
812 : line_number_multiple(1),
813 : no_number_count(0),
814 : hyphenation_mode(1),
815 : hyphenation_mode_default(1),
816 : hyphen_line_count(0),
817 : hyphen_line_max(-1),
818 : hyphenation_space(H0),
819 : hyphenation_margin(H0),
820 : composite(false),
821 : pending_lines(0 /* nullptr */),
822 : #ifdef WIDOW_CONTROL
823 : want_widow_control(false),
824 : #endif /* WIDOW_CONTROL */
825 : stroke_color(&default_color),
826 : prev_stroke_color(&default_color),
827 : fill_color(&default_color),
828 : prev_fill_color(&default_color),
829 : control_character((unsigned char)('.')),
830 : no_break_control_character((unsigned char)('\'')),
831 : seen_space(false),
832 : seen_eol(false),
833 : suppress_next_eol(false),
834 : seen_break(false),
835 : tabs((units_per_inch / 2), TAB_LEFT),
836 : name(nm),
837 2620 : hyphen_indicator_char(0)
838 : {
839 2620 : prev_family = family = lookup_family(default_family);
840 2620 : prev_fontno = fontno = 1;
841 2620 : if (!is_valid_font_mounting_position(1))
842 0 : fatal("font mounted at position 1 is not valid");
843 2620 : if (family->resolve(1) == FONT_NOT_MOUNTED)
844 0 : fatal("invalid default font family '%1'",
845 0 : default_family.contents());
846 2620 : prev_fontno = fontno;
847 2620 : }
848 :
849 2839993 : environment::environment(const environment *e)
850 : : is_dummy_env(true),
851 : prev_line_length(e->prev_line_length),
852 : line_length(e->line_length),
853 : prev_title_length(e->prev_title_length),
854 : title_length(e->title_length),
855 : prev_size(e->prev_size),
856 : size(e->size),
857 2839993 : requested_size(e->requested_size),
858 2839993 : prev_requested_size(e->prev_requested_size),
859 2839993 : char_height(e->char_height),
860 2839993 : char_slant(e->char_slant),
861 2839993 : prev_fontno(e->prev_fontno),
862 2839993 : fontno(e->fontno),
863 2839993 : prev_family(e->prev_family),
864 2839993 : family(e->family),
865 2839993 : space_size(e->space_size),
866 2839993 : sentence_space_size(e->sentence_space_size),
867 2839993 : adjust_mode(e->adjust_mode),
868 2839993 : is_filling(e->is_filling),
869 : was_line_interrupted(false),
870 : was_previous_line_interrupted(0),
871 : centered_line_count(0),
872 : right_aligned_line_count(0),
873 : prev_vertical_spacing(e->prev_vertical_spacing),
874 : vertical_spacing(e->vertical_spacing),
875 : prev_post_vertical_spacing(e->prev_post_vertical_spacing),
876 : post_vertical_spacing(e->post_vertical_spacing),
877 2839993 : prev_line_spacing(e->prev_line_spacing),
878 2839993 : line_spacing(e->line_spacing),
879 : prev_indent(e->prev_indent),
880 : indent(e->indent),
881 : temporary_indent(0),
882 : have_temporary_indent(false),
883 : underlined_line_count(0),
884 : underline_spaces(false),
885 : input_trap_count(-1),
886 : continued_input_trap(false),
887 : line(0 /* nullptr */),
888 : prev_text_length(e->prev_text_length),
889 : width_total(0),
890 : space_total(0),
891 : input_line_start(0),
892 2839993 : using_line_tabs(e->using_line_tabs),
893 : current_tab(TAB_NONE),
894 : leader_node(0 /* nullptr */),
895 2839993 : tab_char(e->tab_char),
896 2839993 : leader_char(e->leader_char),
897 : has_current_field(false),
898 : is_discarding(false),
899 : is_spreading(false),
900 2839993 : margin_character_flags(e->margin_character_flags),
901 2839993 : margin_character_node(e->margin_character_node),
902 : margin_character_distance(e->margin_character_distance),
903 : numbering_nodes(0 /* nullptr */),
904 2839993 : number_text_separation(e->number_text_separation),
905 2839993 : line_number_indent(e->line_number_indent),
906 2839993 : line_number_multiple(e->line_number_multiple),
907 2839993 : no_number_count(e->no_number_count),
908 2839993 : hyphenation_mode(e->hyphenation_mode),
909 2839993 : hyphenation_mode_default(e->hyphenation_mode_default),
910 : hyphen_line_count(0),
911 2839993 : hyphen_line_max(e->hyphen_line_max),
912 : hyphenation_space(e->hyphenation_space),
913 : hyphenation_margin(e->hyphenation_margin),
914 : composite(false),
915 : pending_lines(0 /* nullptr */),
916 : #ifdef WIDOW_CONTROL
917 : want_widow_control(e->want_widow_control),
918 : #endif /* WIDOW_CONTROL */
919 2839993 : stroke_color(e->stroke_color),
920 2839993 : prev_stroke_color(e->prev_stroke_color),
921 2839993 : fill_color(e->fill_color),
922 2839993 : prev_fill_color(e->prev_fill_color),
923 2839993 : control_character(e->control_character),
924 2839993 : no_break_control_character(e->no_break_control_character),
925 2839993 : seen_space(e->seen_space),
926 2839993 : seen_eol(e->seen_eol),
927 2839993 : suppress_next_eol(e->suppress_next_eol),
928 2839993 : seen_break(e->seen_break),
929 2839993 : tabs(e->tabs),
930 : name(e->name), // so that, e.g., '.if "\n[.ev]"0"' works
931 2839993 : hyphen_indicator_char(e->hyphen_indicator_char)
932 : {
933 2839993 : }
934 :
935 12619 : void environment::copy(const environment *e)
936 : {
937 12619 : prev_line_length = e->prev_line_length;
938 12619 : line_length = e->line_length;
939 12619 : prev_title_length = e->prev_title_length;
940 12619 : title_length = e->title_length;
941 12619 : prev_size = e->prev_size;
942 12619 : size = e->size;
943 12619 : prev_requested_size = e->prev_requested_size;
944 12619 : requested_size = e->requested_size;
945 12619 : char_height = e->char_height;
946 12619 : char_slant = e->char_slant;
947 12619 : space_size = e->space_size;
948 12619 : sentence_space_size = e->sentence_space_size;
949 12619 : adjust_mode = e->adjust_mode;
950 12619 : is_filling = e->is_filling;
951 12619 : was_line_interrupted = false;
952 12619 : was_previous_line_interrupted = 0;
953 12619 : centered_line_count = 0;
954 12619 : right_aligned_line_count = 0;
955 12619 : prev_vertical_spacing = e->prev_vertical_spacing;
956 12619 : vertical_spacing = e->vertical_spacing;
957 12619 : prev_post_vertical_spacing = e->prev_post_vertical_spacing,
958 12619 : post_vertical_spacing = e->post_vertical_spacing,
959 12619 : prev_line_spacing = e->prev_line_spacing;
960 12619 : line_spacing = e->line_spacing;
961 12619 : prev_indent = e->prev_indent;
962 12619 : indent = e->indent;
963 12619 : have_temporary_indent = false;
964 12619 : temporary_indent = 0;
965 12619 : underlined_line_count = 0;
966 12619 : underline_spaces = false;
967 12619 : input_trap_count = -1;
968 12619 : continued_input_trap = false;
969 12619 : prev_text_length = e->prev_text_length;
970 12619 : width_total = 0;
971 12619 : space_total = 0;
972 12619 : input_line_start = 0;
973 12619 : control_character = e->control_character;
974 12619 : no_break_control_character = e->no_break_control_character;
975 12619 : hyphen_indicator_char = e->hyphen_indicator_char;
976 12619 : is_spreading = false;
977 12619 : line = 0 /* nullptr */;
978 12619 : pending_lines = 0 /* nullptr */;
979 12619 : is_discarding = false;
980 12619 : tabs = e->tabs;
981 12619 : using_line_tabs = e->using_line_tabs;
982 12619 : current_tab = TAB_NONE;
983 12619 : has_current_field = false;
984 12619 : margin_character_flags = e->margin_character_flags;
985 12619 : if (e->margin_character_node)
986 0 : margin_character_node = e->margin_character_node->copy();
987 12619 : margin_character_distance = e->margin_character_distance;
988 12619 : numbering_nodes = 0 /* nullptr */;
989 12619 : number_text_separation = e->number_text_separation;
990 12619 : line_number_multiple = e->line_number_multiple;
991 12619 : line_number_indent = e->line_number_indent;
992 12619 : no_number_count = e->no_number_count;
993 12619 : tab_char = e->tab_char;
994 12619 : leader_char = e->leader_char;
995 12619 : hyphenation_mode = e->hyphenation_mode;
996 12619 : hyphenation_mode_default = e->hyphenation_mode_default;
997 12619 : fontno = e->fontno;
998 12619 : prev_fontno = e->prev_fontno;
999 12619 : is_dummy_env = e->is_dummy_env;
1000 12619 : family = e->family;
1001 12619 : prev_family = e->prev_family;
1002 12619 : leader_node = 0 /* nullptr */;
1003 : #ifdef WIDOW_CONTROL
1004 : want_widow_control = e->want_widow_control;
1005 : #endif /* WIDOW_CONTROL */
1006 12619 : hyphen_line_max = e->hyphen_line_max;
1007 12619 : hyphen_line_count = 0;
1008 12619 : hyphenation_space = e->hyphenation_space;
1009 12619 : hyphenation_margin = e->hyphenation_margin;
1010 12619 : composite = false;
1011 12619 : stroke_color= e->stroke_color;
1012 12619 : prev_stroke_color = e->prev_stroke_color;
1013 12619 : fill_color = e->fill_color;
1014 12619 : prev_fill_color = e->prev_fill_color;
1015 12619 : }
1016 :
1017 2839993 : environment::~environment()
1018 : {
1019 2839993 : delete leader_node;
1020 2839993 : delete_node_list(line);
1021 2839993 : delete_node_list(numbering_nodes);
1022 2839993 : }
1023 :
1024 38707956 : unsigned char environment::get_control_character()
1025 : {
1026 38707956 : return control_character;
1027 : }
1028 :
1029 0 : bool environment::set_control_character(unsigned char c)
1030 : {
1031 0 : if (c == no_break_control_character)
1032 0 : return false;
1033 0 : control_character = c;
1034 0 : return true;
1035 : }
1036 :
1037 150026 : unsigned char environment::get_no_break_control_character()
1038 : {
1039 150026 : return no_break_control_character;
1040 : }
1041 :
1042 3 : bool environment::set_no_break_control_character(unsigned char c)
1043 : {
1044 3 : if (c == control_character)
1045 0 : return false;
1046 3 : no_break_control_character = c;
1047 3 : return true;
1048 : }
1049 :
1050 53268 : hunits environment::get_input_line_position()
1051 : {
1052 53268 : hunits n;
1053 53268 : if (line == 0 /* nullptr */)
1054 2251 : n = -input_line_start;
1055 : else
1056 51017 : n = width_total - input_line_start;
1057 53268 : if (current_tab != TAB_NONE)
1058 0 : n += tab_width;
1059 53268 : return n;
1060 : }
1061 :
1062 0 : void environment::set_input_line_position(hunits n)
1063 : {
1064 0 : input_line_start = line == 0 /* nullptr */ ? -n : width_total - n;
1065 0 : if (current_tab != TAB_NONE)
1066 0 : input_line_start += tab_width;
1067 0 : }
1068 :
1069 2005647 : hunits environment::get_line_length()
1070 : {
1071 2005647 : return line_length;
1072 : }
1073 :
1074 20 : hunits environment::get_saved_line_length()
1075 : {
1076 20 : if (line)
1077 0 : return target_text_length + saved_indent;
1078 : else
1079 20 : return line_length;
1080 : }
1081 :
1082 838768 : vunits environment::get_vertical_spacing()
1083 : {
1084 838768 : return vertical_spacing;
1085 : }
1086 :
1087 0 : vunits environment::get_post_vertical_spacing()
1088 : {
1089 0 : return post_vertical_spacing;
1090 : }
1091 :
1092 1187 : int environment::get_line_spacing()
1093 : {
1094 1187 : return line_spacing;
1095 : }
1096 :
1097 558250 : vunits environment::total_post_vertical_spacing()
1098 : {
1099 558250 : vunits tem(post_vertical_spacing);
1100 558250 : if (line_spacing > 1)
1101 4 : tem += (line_spacing - 1)*vertical_spacing;
1102 558250 : return tem;
1103 : }
1104 :
1105 1 : hunits environment::get_emboldening_offset()
1106 : {
1107 1 : return env_font_emboldening_offset(this, fontno);
1108 : }
1109 :
1110 1214 : hunits environment::get_digit_width()
1111 : {
1112 1214 : return env_digit_width(this);
1113 : }
1114 :
1115 10850 : unsigned int environment::get_adjust_mode()
1116 : {
1117 10850 : return adjust_mode;
1118 : }
1119 :
1120 528691 : int environment::get_fill()
1121 : {
1122 528691 : return is_filling;
1123 : }
1124 :
1125 12716 : hunits environment::get_indent()
1126 : {
1127 12716 : return indent;
1128 : }
1129 :
1130 8 : hunits environment::get_saved_indent()
1131 : {
1132 8 : if (line)
1133 0 : return saved_indent;
1134 8 : else if (have_temporary_indent)
1135 0 : return temporary_indent;
1136 : else
1137 8 : return indent;
1138 : }
1139 :
1140 0 : hunits environment::get_temporary_indent()
1141 : {
1142 0 : return temporary_indent;
1143 : }
1144 :
1145 914 : hunits environment::get_title_length()
1146 : {
1147 914 : return title_length;
1148 : }
1149 :
1150 3401 : node *environment::get_prev_char()
1151 : {
1152 3401 : for (node *nd = (current_tab != TAB_NONE) ? tab_contents : line;
1153 3409 : nd != 0 /* nullptr */;
1154 8 : nd = nd->next) {
1155 3409 : node *last = nd->last_char_node();
1156 3409 : if (last)
1157 3401 : return last;
1158 : }
1159 0 : return 0 /* nullptr */;
1160 : }
1161 :
1162 8 : hunits environment::get_prev_char_width()
1163 : {
1164 8 : node *last = get_prev_char();
1165 8 : if (!last)
1166 0 : return H0;
1167 8 : return last->width();
1168 : }
1169 :
1170 0 : hunits environment::get_prev_char_skew()
1171 : {
1172 0 : node *last = get_prev_char();
1173 0 : if (!last)
1174 0 : return H0;
1175 0 : return last->skew();
1176 : }
1177 :
1178 2289 : vunits environment::get_prev_char_height()
1179 : {
1180 2289 : node *last = get_prev_char();
1181 2289 : if (!last)
1182 0 : return V0;
1183 2289 : vunits min, max;
1184 2289 : last->vertical_extent(&min, &max);
1185 2289 : return -min;
1186 : }
1187 :
1188 1104 : vunits environment::get_prev_char_depth()
1189 : {
1190 1104 : node *last = get_prev_char();
1191 1104 : if (!last)
1192 0 : return V0;
1193 1104 : vunits min, max;
1194 1104 : last->vertical_extent(&min, &max);
1195 1104 : return max;
1196 : }
1197 :
1198 2759 : hunits environment::get_text_length()
1199 : {
1200 2759 : hunits n = line == 0 /* nullptr */ ? H0 : width_total;
1201 2759 : if (current_tab != TAB_NONE)
1202 0 : n += tab_width;
1203 2759 : return n;
1204 : }
1205 :
1206 12 : hunits environment::get_prev_text_length()
1207 : {
1208 12 : return prev_text_length;
1209 : }
1210 :
1211 :
1212 : static int sb_reg_contents = 0;
1213 : static int st_reg_contents = 0;
1214 : static int ct_reg_contents = 0;
1215 : static int rsb_reg_contents = 0;
1216 : static int rst_reg_contents = 0;
1217 : static int skw_reg_contents = 0;
1218 : static int ssc_reg_contents = 0;
1219 :
1220 26009 : void environment::width_registers()
1221 : {
1222 : // this is used to implement \w; it sets the st, sb, ct registers
1223 26009 : vunits min = 0, max = 0, cur = 0;
1224 26009 : int character_type = 0;
1225 26009 : ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
1226 26009 : skw_reg_contents = line ? line->skew().to_units() : 0;
1227 26009 : line = reverse_node_list(line);
1228 26009 : vunits real_min = V0;
1229 26009 : vunits real_max = V0;
1230 26009 : vunits v1, v2;
1231 262662 : for (node *tem = line; tem != 0 /* nullptr */; tem = tem->next) {
1232 236653 : tem->vertical_extent(&v1, &v2);
1233 236653 : v1 += cur;
1234 236653 : if (v1 < real_min)
1235 26890 : real_min = v1;
1236 236653 : v2 += cur;
1237 236653 : if (v2 > real_max)
1238 20018 : real_max = v2;
1239 236653 : if ((cur += tem->vertical_width()) < min)
1240 22 : min = cur;
1241 236631 : else if (cur > max)
1242 3 : max = cur;
1243 236653 : character_type |= tem->character_type();
1244 : }
1245 26009 : line = reverse_node_list(line);
1246 26009 : st_reg_contents = -min.to_units();
1247 26009 : sb_reg_contents = -max.to_units();
1248 26009 : rst_reg_contents = -real_min.to_units();
1249 26009 : rsb_reg_contents = -real_max.to_units();
1250 26009 : ct_reg_contents = character_type;
1251 26009 : }
1252 :
1253 2820016 : node *environment::extract_output_line()
1254 : {
1255 2820016 : if (current_tab != TAB_NONE)
1256 0 : wrap_up_tab();
1257 2820016 : node *nd = line;
1258 2820016 : line = 0 /* nullptr */;
1259 2820016 : return nd;
1260 : }
1261 :
1262 297 : static void select_fill_color_request()
1263 : {
1264 297 : symbol s = read_identifier();
1265 297 : if (s.is_null())
1266 66 : curenv->set_fill_color(curenv->get_prev_fill_color());
1267 : else
1268 231 : do_fill_color(s);
1269 297 : skip_line();
1270 297 : }
1271 :
1272 43091 : static void select_stroke_color_request()
1273 : {
1274 43091 : symbol s = read_identifier();
1275 43091 : if (s.is_null())
1276 9192 : curenv->set_stroke_color(curenv->get_prev_stroke_color());
1277 : else
1278 33899 : do_stroke_color(s);
1279 43091 : skip_line();
1280 43091 : }
1281 :
1282 : static symbol P_symbol("P");
1283 :
1284 : // Select font with name or mounting position `s`.
1285 320521 : void select_font(symbol s)
1286 : {
1287 320521 : bool is_number = true;
1288 320521 : if (s.is_null() || s.is_empty())
1289 9951 : s = P_symbol;
1290 320521 : if (s == P_symbol)
1291 16010 : is_number = false;
1292 : else {
1293 304511 : const char *p = s.contents();
1294 304511 : assert(*p != 0 /* nullptr */);
1295 304511 : if ((csdigit(*p)) || ('-' == *p))
1296 67965 : p++;
1297 304511 : for (; p != 0 /* nullptr */ && *p != '\0'; p++)
1298 236546 : if (!csdigit(*p)) {
1299 236546 : is_number = false;
1300 236546 : break;
1301 : }
1302 : }
1303 : // environment::set_font warns if an unused mounting position is
1304 : // requested. We must warn here if a bogus font name is selected.
1305 320521 : if (is_number) {
1306 67965 : errno = 0;
1307 67965 : long val = strtol(s.contents(), NULL, 10);
1308 67965 : if ((ERANGE == errno) || (val > INT_MAX) || (val < 0))
1309 0 : warning(WARN_RANGE, "font mounting position must be in range"
1310 0 : " 0..%1, got %2", INT_MAX, s.contents());
1311 : else
1312 67965 : (void) curenv->set_font(int(val));
1313 : }
1314 : else {
1315 252556 : if (s == "DESC")
1316 0 : error("'%1' is not a valid font name", s.contents());
1317 252556 : else if (!curenv->set_font(s))
1318 1 : warning(WARN_FONT, "cannot select font '%1'", s.contents());
1319 : }
1320 320521 : }
1321 :
1322 193232 : static void select_font_request()
1323 : {
1324 193232 : select_font(read_identifier());
1325 193232 : skip_line();
1326 193232 : }
1327 :
1328 46292 : void family_change()
1329 : {
1330 46292 : if (in_nroff_mode) {
1331 3058 : skip_line();
1332 3058 : return;
1333 : }
1334 43234 : symbol s = read_identifier();
1335 43234 : curenv->set_family(s);
1336 43234 : skip_line();
1337 : }
1338 :
1339 101684 : void point_size()
1340 : {
1341 101684 : if (in_nroff_mode) {
1342 21880 : skip_line();
1343 21880 : return;
1344 : }
1345 : int n;
1346 79804 : if (has_arg()
1347 79804 : && read_measurement(&n, 'z', curenv->get_requested_point_size()))
1348 : {
1349 79774 : if (n <= 0)
1350 0 : n = 1;
1351 79774 : curenv->set_size(n);
1352 : }
1353 : else
1354 30 : curenv->set_size(0);
1355 79804 : skip_line();
1356 : }
1357 :
1358 1 : static void override_available_type_sizes_request()
1359 : {
1360 1 : if (!has_arg(true /* peek */)) {
1361 0 : warning(WARN_MISSING, "available font sizes override request"
1362 : " expects at least one argument");
1363 0 : skip_line();
1364 0 : return;
1365 : }
1366 1 : if (in_nroff_mode) {
1367 0 : skip_line();
1368 0 : return;
1369 : }
1370 1 : int n = 16;
1371 1 : int *sizes = new int[n]; // C++03: new int[n]();
1372 1 : (void) memset(sizes, 0, (n * sizeof(int)));
1373 1 : int i = 0;
1374 1 : char *buf = read_rest_of_line_as_argument();
1375 1 : if (0 /* nullptr */ == buf)
1376 0 : return;
1377 1 : char *p = strtok(buf, " \t");
1378 : for (;;) {
1379 3 : if (0 /* nullptr */ == p)
1380 1 : break;
1381 : int lower, upper;
1382 2 : switch (sscanf(p, "%d-%d", &lower, &upper)) {
1383 1 : case 1:
1384 1 : upper = lower;
1385 : // fall through
1386 2 : case 2:
1387 2 : if ((lower <= upper) && (lower >= 0))
1388 2 : break;
1389 : // fall through
1390 : default:
1391 0 : warning(WARN_RANGE, "invalid size range '%1'", p);
1392 0 : return;
1393 : }
1394 2 : if ((i + 2) > n) {
1395 0 : int *old_sizes = sizes;
1396 0 : sizes = new int[n * 2]; // C++03: new int[n * 2]();
1397 0 : (void) memset(sizes, 0, (n * 2 * sizeof(int)));
1398 0 : memcpy(sizes, old_sizes, (n * sizeof(int)));
1399 0 : n *= 2;
1400 0 : delete[] old_sizes;
1401 : }
1402 2 : sizes[i++] = lower;
1403 2 : if (0 == lower)
1404 0 : break;
1405 2 : sizes[i++] = upper;
1406 2 : p = strtok(0 /* nullptr */, " \t");
1407 2 : }
1408 1 : font_size::init_size_list(sizes);
1409 1 : tok.next();
1410 : }
1411 :
1412 4659 : void space_size()
1413 : {
1414 4659 : if (!has_arg()) {
1415 0 : warning(WARN_MISSING, "space size configuration request expects"
1416 : " at least one argument");
1417 0 : skip_line();
1418 0 : return;
1419 : }
1420 : int n;
1421 4659 : if (read_integer(&n)) {
1422 4659 : if (n < 0)
1423 1 : warning(WARN_RANGE, "ignoring negative word space size: '%1'", n);
1424 : else
1425 4658 : curenv->space_size = n;
1426 4659 : if (has_arg() && read_integer(&n))
1427 2745 : if (n < 0)
1428 1 : warning(WARN_RANGE, "ignoring negative sentence space size: "
1429 2 : "'%1'", n);
1430 : else
1431 2744 : curenv->sentence_space_size = n;
1432 : else
1433 1914 : curenv->sentence_space_size = curenv->space_size;
1434 : }
1435 4659 : skip_line();
1436 : }
1437 :
1438 13270 : void fill()
1439 : {
1440 13270 : while (!tok.is_newline() && !tok.is_eof())
1441 34 : tok.next();
1442 13236 : if (was_invoked_with_regular_control_character)
1443 11888 : curenv->do_break();
1444 13236 : curenv->is_filling = true;
1445 13236 : tok.next();
1446 13236 : }
1447 :
1448 20182 : void no_fill()
1449 : {
1450 20182 : while (!tok.is_newline() && !tok.is_eof())
1451 0 : tok.next();
1452 20182 : if (was_invoked_with_regular_control_character)
1453 20060 : curenv->do_break();
1454 20182 : curenv->is_filling = false;
1455 20182 : curenv->suppress_next_eol = true;
1456 20182 : tok.next();
1457 20182 : }
1458 :
1459 9024 : void center()
1460 : {
1461 : int n;
1462 9024 : if (!has_arg() || !read_integer(&n))
1463 38 : n = 1;
1464 8986 : else if (n < 0)
1465 0 : n = 0;
1466 9024 : while (!tok.is_newline() && !tok.is_eof())
1467 0 : tok.next();
1468 9024 : if (was_invoked_with_regular_control_character)
1469 7179 : curenv->do_break();
1470 9024 : curenv->right_aligned_line_count = 0;
1471 9024 : curenv->centered_line_count = n;
1472 9024 : curdiv->modified_tag.incl(MTSM_CE);
1473 9024 : tok.next();
1474 9024 : }
1475 :
1476 4507 : void right_justify()
1477 : {
1478 : int n;
1479 4507 : if (!has_arg() || !read_integer(&n))
1480 0 : n = 1;
1481 4507 : else if (n < 0)
1482 0 : n = 0;
1483 4507 : while (!tok.is_newline() && !tok.is_eof())
1484 0 : tok.next();
1485 4507 : if (was_invoked_with_regular_control_character)
1486 4507 : curenv->do_break();
1487 4507 : curenv->centered_line_count = 0;
1488 4507 : curenv->right_aligned_line_count = n;
1489 4507 : curdiv->modified_tag.incl(MTSM_RJ);
1490 4507 : tok.next();
1491 4507 : }
1492 :
1493 66876 : void line_length()
1494 : {
1495 66876 : hunits temp;
1496 66876 : if (has_arg() && read_hunits(&temp, 'm', curenv->line_length)) {
1497 57299 : if (temp < hresolution) {
1498 1 : warning(WARN_RANGE, "setting computed line length %1u to device"
1499 : " horizontal motion quantum",
1500 1 : temp.to_units());
1501 1 : temp = hresolution;
1502 : }
1503 : }
1504 : else
1505 9577 : temp = curenv->prev_line_length;
1506 66876 : curenv->prev_line_length = curenv->line_length;
1507 66876 : curenv->line_length = temp;
1508 66876 : curdiv->modified_tag.incl(MTSM_LL);
1509 66876 : skip_line();
1510 66876 : }
1511 :
1512 5472 : void title_length()
1513 : {
1514 5472 : hunits temp;
1515 5472 : if (has_arg() && read_hunits(&temp, 'm', curenv->title_length)) {
1516 5472 : if (temp < hresolution) {
1517 0 : warning(WARN_RANGE, "setting computed title length %1u to device"
1518 : " horizontal motion quantum",
1519 0 : temp.to_units());
1520 0 : temp = hresolution;
1521 : }
1522 : }
1523 : else
1524 0 : temp = curenv->prev_title_length;
1525 5472 : curenv->prev_title_length = curenv->title_length;
1526 5472 : curenv->title_length = temp;
1527 5472 : skip_line();
1528 5472 : }
1529 :
1530 97696 : void vertical_spacing()
1531 : {
1532 97696 : vunits temp;
1533 97696 : if (has_arg() && read_vunits(&temp, 'p', curenv->vertical_spacing)) {
1534 96831 : if (temp < V0) {
1535 0 : warning(WARN_RANGE, "vertical spacing must be nonnegative");
1536 0 : temp = vresolution;
1537 : }
1538 : }
1539 : else
1540 865 : temp = curenv->prev_vertical_spacing;
1541 97696 : curenv->prev_vertical_spacing = curenv->vertical_spacing;
1542 97696 : curenv->vertical_spacing = temp;
1543 97696 : skip_line();
1544 97696 : }
1545 :
1546 0 : void post_vertical_spacing()
1547 : {
1548 0 : vunits temp;
1549 0 : if (has_arg() && read_vunits(&temp, 'p',
1550 : curenv->post_vertical_spacing)) {
1551 0 : if (temp < V0) {
1552 0 : warning(WARN_RANGE, "post-vertical spacing must be nonnegative");
1553 0 : temp = V0;
1554 : }
1555 : }
1556 : else
1557 0 : temp = curenv->prev_post_vertical_spacing;
1558 0 : curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
1559 0 : curenv->post_vertical_spacing = temp;
1560 0 : skip_line();
1561 0 : }
1562 :
1563 13264 : void line_spacing()
1564 : {
1565 : int temp;
1566 13264 : if (has_arg() && read_integer(&temp)) {
1567 9242 : if (temp < 1) {
1568 0 : warning(WARN_RANGE, "line spacing value '%1' is out of range;"
1569 0 : " assuming '1'", temp);
1570 0 : temp = 1;
1571 : }
1572 : }
1573 : else
1574 4022 : temp = curenv->prev_line_spacing;
1575 13264 : curenv->prev_line_spacing = curenv->line_spacing;
1576 13264 : curenv->line_spacing = temp;
1577 13264 : skip_line();
1578 13264 : }
1579 :
1580 162371 : void indent()
1581 : {
1582 162371 : hunits temp;
1583 162371 : if (has_arg() && read_hunits(&temp, 'm', curenv->indent)) {
1584 161358 : if (temp < H0) {
1585 0 : warning(WARN_RANGE, "treating %1u indentation as zero",
1586 0 : temp.to_units());
1587 0 : temp = H0;
1588 : }
1589 : }
1590 : else
1591 1013 : temp = curenv->prev_indent;
1592 186179 : while (!tok.is_newline() && !tok.is_eof())
1593 23808 : tok.next();
1594 162371 : if (was_invoked_with_regular_control_character)
1595 160401 : curenv->do_break();
1596 162371 : curenv->have_temporary_indent = false;
1597 162371 : curenv->prev_indent = curenv->indent;
1598 162371 : curenv->indent = temp;
1599 162371 : curdiv->modified_tag.incl(MTSM_IN);
1600 162371 : tok.next();
1601 162371 : }
1602 :
1603 6425 : void temporary_indent()
1604 : {
1605 6425 : bool is_valid = true;
1606 6425 : hunits temp = H0;
1607 6425 : if (!has_arg()) {
1608 0 : warning(WARN_MISSING, "temporary indentation request expects"
1609 : " argument");
1610 0 : skip_line();
1611 : // _Don't_ return early; when invoked with the ordinary control
1612 : // character this request still breaks the line.
1613 : }
1614 : else {
1615 6425 : if (!read_hunits(&temp, 'm', curenv->get_indent()))
1616 0 : is_valid = false;
1617 6425 : while (!tok.is_newline() && !tok.is_eof())
1618 0 : tok.next();
1619 : }
1620 6425 : if (was_invoked_with_regular_control_character)
1621 6160 : curenv->do_break();
1622 6425 : if (temp < H0) {
1623 0 : warning(WARN_RANGE, "treating total indentation %1u as zero",
1624 0 : temp.to_units());
1625 0 : temp = H0;
1626 : }
1627 6425 : if (is_valid) {
1628 6425 : curenv->temporary_indent = temp;
1629 6425 : curenv->have_temporary_indent = true;
1630 6425 : curdiv->modified_tag.incl(MTSM_TI);
1631 : }
1632 6425 : tok.next();
1633 6425 : }
1634 :
1635 3953 : void configure_underlining(bool want_spaces_underlined)
1636 : {
1637 : int n;
1638 3953 : if (!has_arg() || !read_integer(&n))
1639 9 : n = 1;
1640 3953 : if (n <= 0) {
1641 3944 : if (curenv->underlined_line_count > 0) {
1642 0 : curenv->prev_fontno = curenv->fontno;
1643 0 : curenv->fontno = curenv->pre_underline_fontno;
1644 0 : if (want_spaces_underlined) {
1645 0 : curenv->underline_spaces = false;
1646 0 : curenv->add_node(configure_space_underlining(false));
1647 : }
1648 : }
1649 3944 : curenv->underlined_line_count = 0;
1650 : }
1651 : else {
1652 9 : curenv->underlined_line_count = n;
1653 9 : curenv->pre_underline_fontno = curenv->fontno;
1654 9 : curenv->fontno = get_underline_fontno();
1655 9 : if (want_spaces_underlined) {
1656 0 : curenv->underline_spaces = true;
1657 0 : curenv->add_node(configure_space_underlining(true));
1658 : }
1659 : }
1660 3953 : skip_line();
1661 3953 : }
1662 :
1663 0 : void continuous_underline()
1664 : {
1665 0 : configure_underlining(true /* underline spaces */);
1666 0 : }
1667 :
1668 3953 : void underline()
1669 : {
1670 3953 : configure_underlining(false /* underline spaces */);
1671 3953 : }
1672 :
1673 9097 : void margin_character()
1674 : {
1675 9097 : tok.skip_spaces();
1676 9097 : charinfo *ci = tok.get_charinfo();
1677 9097 : if (0 /* nullptr */ == ci) { // no argument
1678 9097 : tok.diagnose_non_character();
1679 9097 : curenv->margin_character_flags &= ~environment::MC_ON;
1680 9097 : if (curenv->margin_character_flags == 0U) {
1681 9097 : delete curenv->margin_character_node;
1682 9097 : curenv->margin_character_node = 0 /* nullptr */;
1683 : }
1684 : }
1685 : else {
1686 : // Call tok.next() only after making the node so that
1687 : // .mc \s+9\(br\s0 works.
1688 0 : node *nd = curenv->make_char_node(ci);
1689 0 : tok.next();
1690 0 : if (nd) {
1691 0 : delete curenv->margin_character_node;
1692 0 : curenv->margin_character_node = nd;
1693 0 : curenv->margin_character_flags = environment::MC_ON
1694 : | environment::MC_NEXT;
1695 0 : hunits d;
1696 0 : if (has_arg() && read_hunits(&d, 'm'))
1697 0 : curenv->margin_character_distance = d;
1698 : }
1699 : }
1700 9097 : skip_line();
1701 9097 : }
1702 :
1703 548 : void number_lines()
1704 : {
1705 548 : delete_node_list(curenv->numbering_nodes);
1706 548 : curenv->numbering_nodes = 0 /* nullptr */;
1707 548 : if (has_arg()) {
1708 21 : node *nd = 0 /* nullptr */;
1709 231 : for (int i = '9'; i >= '0'; i--) {
1710 210 : node *tem = make_node(charset_table[i], curenv);
1711 210 : if (!tem) {
1712 0 : skip_line();
1713 0 : return;
1714 : }
1715 210 : tem->next = nd;
1716 210 : nd = tem;
1717 : }
1718 21 : curenv->numbering_nodes = nd;
1719 21 : curenv->line_number_digit_width = env_digit_width(curenv);
1720 : int n;
1721 21 : if (!tok.is_usable_as_delimiter()) { // XXX abuse of function
1722 21 : if (read_integer(&n, next_line_number)) {
1723 21 : next_line_number = n;
1724 21 : if (next_line_number < 0) {
1725 0 : warning(WARN_RANGE, "output line number cannot be negative");
1726 0 : next_line_number = 0;
1727 : }
1728 : }
1729 : }
1730 : else
1731 0 : while (!tok.is_space() && !tok.is_newline() && !tok.is_eof())
1732 0 : tok.next();
1733 21 : if (has_arg()) {
1734 0 : if (!tok.is_usable_as_delimiter()) { // XXX abuse of function
1735 0 : if (read_integer(&n)) {
1736 0 : if (n <= 0) {
1737 0 : warning(WARN_RANGE, "output line number multiple cannot"
1738 : "be nonpositive");
1739 : }
1740 : else
1741 0 : curenv->line_number_multiple = n;
1742 : }
1743 : }
1744 : else
1745 0 : while (!tok.is_space() && !tok.is_newline() && !tok.is_eof())
1746 0 : tok.next();
1747 0 : if (has_arg()) {
1748 0 : if (!tok.is_usable_as_delimiter()) { // XXX abuse of function
1749 0 : if (read_integer(&n))
1750 0 : curenv->number_text_separation = n;
1751 : }
1752 : else
1753 0 : while (!tok.is_space() && !tok.is_newline() && !tok.is_eof())
1754 0 : tok.next();
1755 0 : if (has_arg() && !tok.is_usable_as_delimiter() // XXX abuse of function
1756 0 : && read_integer(&n))
1757 0 : curenv->line_number_indent = n;
1758 : }
1759 : }
1760 : }
1761 548 : skip_line();
1762 : }
1763 :
1764 950 : void no_number()
1765 : {
1766 : int n;
1767 950 : if (has_arg() && read_integer(&n))
1768 946 : curenv->no_number_count = n > 0 ? n : 0;
1769 : else
1770 4 : curenv->no_number_count = 1;
1771 950 : skip_line();
1772 950 : }
1773 :
1774 4354 : void no_hyphenate()
1775 : {
1776 4354 : curenv->hyphenation_mode = 0;
1777 4354 : skip_line();
1778 4354 : }
1779 :
1780 27702 : void hyphenate_request()
1781 : {
1782 : int n;
1783 27702 : if (has_arg() && read_integer(&n)) {
1784 26194 : if (n < HYPHEN_NONE) {
1785 0 : warning(WARN_RANGE, "ignoring negative hyphenation mode: %1", n);
1786 26194 : } else if (n > HYPHEN_MAX) {
1787 0 : warning(WARN_RANGE, "hyphenation mode must be in range 0..%1, got"
1788 0 : " %2", HYPHEN_MAX, n);
1789 26194 : } else if (((n & HYPHEN_DEFAULT) && (n & ~HYPHEN_DEFAULT))
1790 26194 : || ((n & HYPHEN_FIRST_CHAR) && (n & HYPHEN_NOT_FIRST_CHARS))
1791 26194 : || ((n & HYPHEN_LAST_CHAR) && (n & HYPHEN_NOT_LAST_CHARS)))
1792 0 : warning(WARN_SYNTAX, "ignoring self-contradictory hyphenation"
1793 0 : " mode: %1", n);
1794 : else
1795 26194 : curenv->hyphenation_mode = n;
1796 : }
1797 : else
1798 1508 : curenv->hyphenation_mode = curenv->hyphenation_mode_default;
1799 27702 : skip_line();
1800 27702 : }
1801 :
1802 1550 : void set_hyphenation_mode_default()
1803 : {
1804 1550 : if (!has_arg()) {
1805 0 : warning(WARN_MISSING, "hyphenation mode default setting request"
1806 : " expects an argument");
1807 0 : skip_line();
1808 0 : return;
1809 : }
1810 : int n;
1811 1550 : if (!read_integer(&n)) { // throws a diagnostic if necessary
1812 0 : skip_line();
1813 0 : return;
1814 : }
1815 1550 : if (n < 0) {
1816 0 : warning(WARN_RANGE, "hyphenation mode default cannot be negative");
1817 0 : skip_line();
1818 0 : return;
1819 : }
1820 1550 : curenv->hyphenation_mode_default = n;
1821 1550 : skip_line();
1822 : }
1823 :
1824 : // Set hyphenation character, which the input uses to mark the position
1825 : // of a discretionary break ("dbreak") in a word.
1826 0 : void hyphenation_character_request()
1827 : {
1828 0 : curenv->hyphen_indicator_char = read_character();
1829 : // TODO?: If null pointer, set to ESCAPE_PERCENT, eliminating test(s)
1830 : // while processing output line?
1831 0 : skip_line();
1832 0 : }
1833 :
1834 923 : void hyphen_line_max_request()
1835 : {
1836 : int n;
1837 923 : if (has_arg() && read_integer(&n))
1838 923 : curenv->hyphen_line_max = n;
1839 : else
1840 0 : curenv->hyphen_line_max = -1;
1841 923 : skip_line();
1842 923 : }
1843 :
1844 112600 : void environment::interrupt()
1845 : {
1846 112600 : if (!is_dummy_env) {
1847 112596 : add_node(new transparent_dummy_node);
1848 112596 : was_line_interrupted = true;
1849 : }
1850 112600 : }
1851 :
1852 750761 : void environment::newline()
1853 : {
1854 750761 : bool was_centered = false;
1855 750761 : if (underlined_line_count > 0) {
1856 9 : if (--underlined_line_count == 0) {
1857 9 : prev_fontno = fontno;
1858 9 : fontno = pre_underline_fontno;
1859 9 : if (underline_spaces) {
1860 0 : underline_spaces = false;
1861 0 : add_node(configure_space_underlining(false));
1862 : }
1863 : }
1864 : }
1865 750761 : if (has_current_field)
1866 0 : wrap_up_field();
1867 750761 : if (current_tab != TAB_NONE)
1868 275 : wrap_up_tab();
1869 : // strip trailing spaces
1870 751046 : while (line != 0 /* nullptr */ && line->discardable()) {
1871 285 : width_total -= line->width();
1872 285 : space_total -= line->nspaces();
1873 285 : node *tem = line;
1874 285 : line = line->next;
1875 285 : delete tem;
1876 : }
1877 750761 : node *to_be_output = 0 /* nullptr */;
1878 750761 : hunits to_be_output_width;
1879 750761 : was_previous_line_interrupted = 0;
1880 750761 : if (is_dummy_env)
1881 0 : space_newline();
1882 750761 : else if (was_line_interrupted) {
1883 112543 : was_line_interrupted = false;
1884 : // see environment::final_break
1885 112543 : was_previous_line_interrupted = is_exit_underway ? 2 : 1;
1886 : }
1887 638218 : else if (centered_line_count > 0) {
1888 816 : --centered_line_count;
1889 816 : hunits x = target_text_length - width_total;
1890 816 : if (x > H0)
1891 798 : saved_indent += x/2;
1892 816 : to_be_output = line;
1893 816 : was_centered = true;
1894 816 : to_be_output_width = width_total;
1895 816 : line = 0; /* nullptr */
1896 : }
1897 637402 : else if (right_aligned_line_count > 0) {
1898 219 : --right_aligned_line_count;
1899 219 : hunits x = target_text_length - width_total;
1900 219 : if (x > H0)
1901 159 : saved_indent += x;
1902 219 : to_be_output = line;
1903 219 : to_be_output_width = width_total;
1904 219 : line = 0 /* nullptr */;
1905 : }
1906 637183 : else if (is_filling)
1907 207266 : space_newline();
1908 : else {
1909 429917 : to_be_output = line;
1910 429917 : to_be_output_width = width_total;
1911 429917 : line = 0 /* nullptr */;
1912 : }
1913 750761 : input_line_start = line == 0 /* nullptr */ ? H0 : width_total;
1914 750761 : if (to_be_output) {
1915 430543 : if (is_writing_html && !is_filling) {
1916 7224 : curdiv->modified_tag.incl(MTSM_EOL);
1917 7224 : if (suppress_next_eol)
1918 232 : suppress_next_eol = false;
1919 : else
1920 6992 : seen_eol = true;
1921 : }
1922 :
1923 430543 : output_line(to_be_output, to_be_output_width, was_centered);
1924 430543 : hyphen_line_count = 0;
1925 : }
1926 750761 : if (input_trap_count > 0) {
1927 38753 : if (!(continued_input_trap && (was_previous_line_interrupted > 0)))
1928 36622 : if (--input_trap_count == 0)
1929 36554 : spring_trap(input_trap);
1930 : }
1931 750761 : }
1932 :
1933 527376 : void environment::output_line(node *nd, hunits width, bool was_centered)
1934 : {
1935 527376 : prev_text_length = width;
1936 527376 : if (margin_character_flags > 0U) {
1937 0 : hunits d = line_length + margin_character_distance - saved_indent
1938 0 : - width;
1939 0 : if (d > 0) {
1940 0 : nd = new hmotion_node(d, get_fill_color(), nd);
1941 0 : width += d;
1942 : }
1943 0 : margin_character_flags &= ~MC_NEXT;
1944 : node *tem;
1945 0 : if (!margin_character_flags) {
1946 0 : tem = margin_character_node;
1947 0 : margin_character_node = 0 /* nullptr */;
1948 : }
1949 : else
1950 0 : tem = margin_character_node->copy();
1951 0 : tem->next = nd;
1952 0 : nd = tem;
1953 0 : width += tem->width();
1954 : }
1955 527376 : node *nn = 0 /* nullptr */;
1956 14337523 : while (nd != 0 /* nullptr */) {
1957 13810147 : node *tem = nd->next;
1958 13810147 : nd->next = nn;
1959 13810147 : nn = nd;
1960 13810147 : nd = tem;
1961 : }
1962 527376 : if (!saved_indent.is_zero())
1963 359092 : nn = new hmotion_node(saved_indent, get_fill_color(), nn);
1964 527376 : width += saved_indent;
1965 527376 : if (no_number_count > 0)
1966 11964 : --no_number_count;
1967 515412 : else if (numbering_nodes) {
1968 46 : hunits w = (line_number_digit_width
1969 46 : * (3 + line_number_indent + number_text_separation));
1970 46 : if ((next_line_number % line_number_multiple) != 0)
1971 0 : nn = new hmotion_node(w, get_fill_color(), nn);
1972 : else {
1973 46 : hunits x = w;
1974 46 : nn = new hmotion_node(number_text_separation
1975 46 : * line_number_digit_width,
1976 46 : get_fill_color(), nn);
1977 46 : x -= number_text_separation*line_number_digit_width;
1978 : char buf[UINT_DIGITS];
1979 46 : (void) sprintf(buf, "%3u", next_line_number);
1980 113 : for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p)
1981 : {
1982 67 : node *gn = numbering_nodes;
1983 203 : for (int count = *p - '0'; count > 0; count--)
1984 136 : gn = gn->next;
1985 67 : gn = gn->copy();
1986 67 : x -= gn->width();
1987 67 : gn->next = nn;
1988 67 : nn = gn;
1989 : }
1990 46 : nn = new hmotion_node(x, get_fill_color(), nn);
1991 : }
1992 46 : width += w;
1993 46 : ++next_line_number;
1994 : }
1995 527376 : output(nn, !is_filling, vertical_spacing,
1996 : total_post_vertical_spacing(), width, was_centered);
1997 527361 : }
1998 :
1999 2577198 : void environment::start_line()
2000 : {
2001 2577198 : assert(line == 0 /* nullptr */);
2002 2577198 : is_discarding = false;
2003 2577198 : line = new line_start_node;
2004 2577198 : if (have_temporary_indent) {
2005 6242 : saved_indent = temporary_indent;
2006 6242 : have_temporary_indent = false;
2007 : }
2008 : else
2009 2570956 : saved_indent = indent;
2010 2577198 : target_text_length = line_length - saved_indent;
2011 2577198 : width_total = H0;
2012 2577198 : space_total = 0;
2013 2577198 : }
2014 :
2015 474 : hunits environment::get_hyphenation_space()
2016 : {
2017 474 : return hyphenation_space;
2018 : }
2019 :
2020 925 : void hyphenation_space_request()
2021 : {
2022 925 : hunits n;
2023 925 : if (read_hunits(&n, 'm')) {
2024 925 : if (n < H0) {
2025 0 : warning(WARN_RANGE, "hyphenation space cannot be negative");
2026 0 : n = H0;
2027 : }
2028 925 : curenv->hyphenation_space = n;
2029 : }
2030 925 : skip_line();
2031 925 : }
2032 :
2033 474 : hunits environment::get_hyphenation_margin()
2034 : {
2035 474 : return hyphenation_margin;
2036 : }
2037 :
2038 924 : void hyphenation_margin_request()
2039 : {
2040 924 : hunits n;
2041 924 : if (read_hunits(&n, 'm')) {
2042 924 : if (n < H0) {
2043 0 : warning(WARN_RANGE, "hyphenation margin cannot be negative");
2044 0 : n = H0;
2045 : }
2046 924 : curenv->hyphenation_margin = n;
2047 : }
2048 924 : skip_line();
2049 924 : }
2050 :
2051 30864 : breakpoint *environment::choose_breakpoint()
2052 : {
2053 30864 : hunits x = width_total;
2054 30864 : int s = space_total;
2055 30864 : node *nd = line;
2056 30864 : breakpoint *best_bp = 0 /* nullptr */; // the best breakpoint so far
2057 30864 : bool best_bp_fits = false;
2058 244614 : while (nd != 0 /* nullptr */) {
2059 244581 : x -= nd->width();
2060 244581 : s -= nd->nspaces();
2061 244581 : breakpoint *bp = nd->get_breakpoints(x, s);
2062 288049 : while (bp != 0 /* nullptr */) {
2063 74299 : if (bp->width <= target_text_length) {
2064 31201 : if (!bp->hyphenated) {
2065 24347 : breakpoint *tem = bp->next;
2066 24347 : bp->next = 0 /* nullptr */;
2067 24347 : while (tem != 0 /* nullptr */) {
2068 0 : breakpoint *tem1 = tem;
2069 0 : tem = tem->next;
2070 0 : delete tem1;
2071 : }
2072 24347 : if (best_bp_fits
2073 : // Decide whether to use the hyphenated breakpoint.
2074 297 : && ((hyphen_line_max < 0)
2075 : // Only choose the hyphenated breakpoint if it would
2076 : // not exceed the maximum number of consecutive
2077 : // hyphenated lines.
2078 295 : || (hyphen_line_count + 1 <= hyphen_line_max))
2079 24941 : && !((adjust_mode == ADJUST_BOTH)
2080 : // Don't choose the hyphenated breakpoint if the line
2081 : // can be justified by adding no more than
2082 : // hyphenation_space to any word space.
2083 285 : ? ((bp->nspaces > 0)
2084 285 : && (((target_text_length - bp->width)
2085 285 : + ((bp->nspaces - 1) * hresolution))
2086 285 : / bp->nspaces)
2087 570 : <= hyphenation_space)
2088 : // Don't choose the hyphenated breakpoint if the line
2089 : // is no more than hyphenation_margin short of the
2090 : // line length.
2091 24347 : : ((target_text_length - bp->width)
2092 12 : <= hyphenation_margin))) {
2093 296 : delete bp;
2094 296 : return best_bp;
2095 : }
2096 24051 : if (best_bp)
2097 23526 : delete best_bp;
2098 24051 : return bp;
2099 : }
2100 : else {
2101 13708 : if ((adjust_mode == ADJUST_BOTH
2102 6793 : ? hyphenation_space == H0
2103 61 : : hyphenation_margin == H0)
2104 13708 : && (hyphen_line_max < 0
2105 0 : || hyphen_line_count + 1 <= hyphen_line_max)) {
2106 : // No need to consider a non-hyphenated breakpoint.
2107 6484 : if (best_bp)
2108 6465 : delete best_bp;
2109 6484 : breakpoint *tem = bp->next;
2110 6484 : bp->next = 0 /* nullptr */;
2111 6497 : while (tem != 0 /* nullptr */) {
2112 13 : breakpoint *tem1 = tem;
2113 13 : tem = tem->next;
2114 13 : delete tem1;
2115 : }
2116 6484 : return bp;
2117 : }
2118 : // It fits but it's hyphenated.
2119 370 : if (!best_bp_fits) {
2120 297 : if (best_bp)
2121 297 : delete best_bp;
2122 297 : best_bp = bp;
2123 297 : bp = bp->next;
2124 297 : best_bp_fits = true;
2125 : }
2126 : else {
2127 73 : breakpoint *tem = bp;
2128 73 : bp = bp->next;
2129 73 : delete tem;
2130 : }
2131 : }
2132 : }
2133 : else {
2134 43098 : if (best_bp)
2135 12778 : delete best_bp;
2136 43098 : best_bp = bp;
2137 43098 : bp = bp->next;
2138 : }
2139 : }
2140 213750 : nd = nd->next;
2141 : }
2142 33 : if (best_bp)
2143 33 : return best_bp;
2144 0 : return 0 /* nullptr */;
2145 : }
2146 :
2147 : // Iterate over the nodes of the output line, looking for break points.
2148 : // The node list is in reverse order, so the first node we see is the
2149 : // last (or rightmost) on the line. Whether a break requires
2150 : // hyphenation depends on the properties of the node and the context;
2151 : // if we're at a word boundary, we can break without a hyphen regardless
2152 : // of the node's own hyphenation properties.
2153 30864 : void environment::possibly_hyphenate_line(bool must_break_here)
2154 : {
2155 30864 : assert(line != 0 /* nullptr */);
2156 30864 : hyphenation_type prev_type = line->get_hyphenation_type();
2157 : node **startp;
2158 30864 : if (must_break_here)
2159 535 : startp = &line;
2160 : else
2161 30329 : for (startp = &line->next; *startp != 0 /* nullptr */;
2162 0 : startp = &(*startp)->next) {
2163 30329 : hyphenation_type this_type = (*startp)->get_hyphenation_type();
2164 30329 : if (prev_type == HYPHENATION_UNNECESSARY
2165 30329 : && this_type == HYPHENATION_PERMITTED)
2166 30329 : break;
2167 0 : prev_type = this_type;
2168 : }
2169 30864 : if (*startp == 0 /* nullptr */)
2170 0 : return;
2171 30864 : node *tem = *startp;
2172 174967 : do {
2173 205831 : tem = tem->next;
2174 : } while (tem != 0 /* nullptr */
2175 205831 : && tem->get_hyphenation_type() == HYPHENATION_PERMITTED);
2176 : // This is for characters like hyphen and em dash.
2177 : bool inhibit = ((tem != 0 /* nullptr */)
2178 30864 : && (tem->get_hyphenation_type()
2179 30864 : == HYPHENATION_INHIBITED));
2180 30864 : node *end = tem;
2181 30864 : hyphen_list *sl = 0 /* nullptr */;
2182 30864 : tem = *startp;
2183 30864 : node *forward = 0 /* nullptr */;
2184 30864 : int i = 0;
2185 236695 : while (tem != end) {
2186 205831 : sl = tem->get_hyphen_list(sl, &i);
2187 205831 : node *tem1 = tem;
2188 205831 : tem = tem->next;
2189 205831 : tem1->next = forward;
2190 205831 : forward = tem1;
2191 : }
2192 30864 : if (!inhibit) {
2193 30197 : unsigned char prev_code = 0U;
2194 230213 : for (hyphen_list *h = sl; h; h = h->next) {
2195 200016 : h->is_breakable = (prev_code != 0U
2196 162835 : && h->next != 0 /* nullptr */
2197 362851 : && h->next->hyphenation_code != 0U);
2198 200016 : prev_code = h->hyphenation_code;
2199 : }
2200 : }
2201 61728 : if ((hyphenation_mode != 0)
2202 28395 : && !inhibit
2203 : // this may not be right if we have extra space on this line
2204 27762 : && !((hyphenation_mode & HYPHEN_NOT_LAST_LINE)
2205 27576 : && (curdiv->distance_to_next_trap()
2206 55152 : <= vertical_spacing + total_post_vertical_spacing()))
2207 59259 : && i >= (4
2208 27616 : - (hyphenation_mode & HYPHEN_FIRST_CHAR ? 1 : 0)
2209 27616 : - (hyphenation_mode & HYPHEN_LAST_CHAR ? 1 : 0)
2210 27616 : + (hyphenation_mode & HYPHEN_NOT_FIRST_CHARS ? 1 : 0)
2211 27616 : + (hyphenation_mode & HYPHEN_NOT_LAST_CHARS ? 1 : 0)))
2212 18530 : hyphenate(sl, hyphenation_mode);
2213 236695 : while (forward != 0 /* nullptr */) {
2214 205831 : node *tem1 = forward;
2215 205831 : forward = forward->next;
2216 205831 : tem1->next = 0 /* nullptr */;
2217 205831 : tem = tem1->add_self(tem, &sl);
2218 : }
2219 30864 : *startp = tem;
2220 : }
2221 :
2222 28596 : static node *node_list_reverse(node *nd)
2223 : {
2224 28596 : node *res = 0 /* nullptr */;
2225 2449862 : while (nd) {
2226 2421266 : node *tem = nd;
2227 2421266 : nd = nd->next;
2228 2421266 : tem->next = res;
2229 2421266 : res = tem;
2230 : }
2231 28596 : return res;
2232 : }
2233 :
2234 31845 : static bool distribute_space(node *nd, int nspaces,
2235 : hunits desired_space,
2236 : bool force_reverse_node_list = false)
2237 : {
2238 31845 : if (desired_space.is_zero() || (nspaces == 0) || (desired_space < H0))
2239 3978 : return false;
2240 : // Positive desired space is the typical case. Negative desired space
2241 : // is possible if we have overrun an unbreakable line. But we should
2242 : // not get here if there are no adjustable space nodes to adjust.
2243 27867 : assert(nspaces > 0);
2244 : // Space cannot always be distributed evenly among all of the space
2245 : // nodes in the node list: there are limits to device resolution. We
2246 : // add space until we run out, which might happen before the end of
2247 : // the line. To achieve uniform typographical grayness and avoid
2248 : // rivers, we switch the end from which space is initially distributed
2249 : // with each line requiring it, unless compelled to reverse it. We
2250 : // distribute space initially from the left, unlike AT&T troff.
2251 : static bool do_reverse_node_list = false;
2252 27867 : if (force_reverse_node_list || do_reverse_node_list)
2253 14298 : nd = node_list_reverse(nd);
2254 27054 : if (!force_reverse_node_list && spread_limit >= 0
2255 54921 : && desired_space.to_units() > 0) {
2256 0 : hunits em = curenv->get_size();
2257 0 : double Ems = static_cast<double>(desired_space.to_units()) / nspaces
2258 0 : / (em.is_zero() ? hresolution : em.to_units());
2259 0 : if (Ems > spread_limit)
2260 0 : output_warning(WARN_BREAK, "spreading %1m per space", Ems);
2261 : }
2262 2439341 : for (node *tem = nd; tem != 0 /* nullptr */; tem = tem->next)
2263 2411474 : tem->spread_space(&nspaces, &desired_space);
2264 27867 : if (force_reverse_node_list || do_reverse_node_list)
2265 14298 : (void) node_list_reverse(nd);
2266 27867 : if (!force_reverse_node_list)
2267 27054 : do_reverse_node_list = !do_reverse_node_list;
2268 27867 : return true;
2269 : }
2270 :
2271 : // from input.cpp
2272 : extern double warn_scale;
2273 : extern char warn_scaling_unit;
2274 :
2275 9800709 : void environment::possibly_break_line(bool must_break_here,
2276 : bool must_adjust)
2277 : {
2278 9800709 : bool was_centered = (centered_line_count > 0);
2279 9800709 : if (!is_filling || (current_tab != TAB_NONE) || has_current_field
2280 912960 : || is_dummy_env)
2281 8966658 : return;
2282 30862 : while ((line != 0 /* nullptr */)
2283 1729727 : && (must_adjust
2284 : // When a macro follows a paragraph in fill mode, the
2285 : // current line should not be empty.
2286 1729727 : || ((width_total - line->width()) > target_text_length))) {
2287 30864 : possibly_hyphenate_line(must_break_here);
2288 30864 : breakpoint *bp = choose_breakpoint();
2289 30864 : if (0 /* nullptr */ == bp)
2290 : // we'll find one eventually
2291 2 : return;
2292 : node *pre, *post;
2293 30864 : node **ndp = &line;
2294 242477 : while (*ndp != bp->nd)
2295 211613 : ndp = &(*ndp)->next;
2296 30864 : bp->nd->split(bp->index, &pre, &post);
2297 30864 : *ndp = post;
2298 : // The space deficit tells us how much the line is overset if
2299 : // negative, or underset if positive, relative to the configured
2300 : // line length.
2301 30864 : hunits space_deficit = (target_text_length - bp->width);
2302 : // An overset line always gets a warning.
2303 30864 : if (space_deficit < H0) {
2304 33 : double dsd = static_cast<double>(space_deficit.to_units());
2305 33 : output_warning(WARN_BREAK, "cannot %1 line; overset by %2%3",
2306 33 : (ADJUST_BOTH == adjust_mode) ? "adjust" : "break",
2307 : in_nroff_mode
2308 19 : ? static_cast<int>(ceil(fabs(dsd / hresolution)))
2309 14 : : fabs(dsd / warn_scale),
2310 66 : in_nroff_mode ? 'n' : warn_scaling_unit);
2311 : }
2312 : // An underset line warns only if it requires adjustment but no
2313 : // adjustable spaces exist on the line.
2314 61662 : else if ((ADJUST_BOTH == adjust_mode)
2315 30134 : && (space_deficit > H0)
2316 60965 : && (0 == bp->nspaces)) {
2317 7 : double dsd = static_cast<double>(space_deficit.to_units());
2318 7 : output_warning(WARN_BREAK, "cannot adjust line; underset by %1%2",
2319 : in_nroff_mode
2320 5 : ? static_cast<int>(ceil(fabs(dsd / hresolution)))
2321 2 : : fabs(dsd / warn_scale),
2322 14 : in_nroff_mode ? 'n' : warn_scaling_unit);
2323 : }
2324 : // The extra space is the amount of space to distribute among the
2325 : // adjustable space nodes in an output line; this process occurs
2326 : // only if adjustment is enabled. We may however want to synthesize
2327 : // an extra indentation from it if centering or right-aligning.
2328 30864 : hunits extra_space = H0;
2329 30864 : switch (adjust_mode) {
2330 30154 : case ADJUST_BOTH:
2331 30154 : if (bp->nspaces != 0)
2332 30113 : extra_space = space_deficit;
2333 30154 : break;
2334 80 : case ADJUST_CENTER:
2335 80 : saved_indent += space_deficit / 2;
2336 80 : was_centered = true;
2337 80 : break;
2338 10 : case ADJUST_RIGHT:
2339 10 : saved_indent += space_deficit;
2340 10 : break;
2341 620 : case ADJUST_LEFT:
2342 : case ADJUST_CENTER - 1:
2343 : case ADJUST_RIGHT - 1:
2344 620 : break;
2345 0 : default:
2346 0 : assert(0 == "unhandled case of `adjust_mode`");
2347 : }
2348 30864 : hunits output_width = bp->width; // 0 if no breakpoint is found.
2349 30864 : if (distribute_space(pre, bp->nspaces, extra_space))
2350 27054 : output_width += extra_space;
2351 : // If we had an unbreakable, overset line, we can do no more.
2352 30864 : if (output_width <= 0)
2353 2 : return;
2354 30862 : input_line_start -= output_width;
2355 30862 : if (bp->hyphenated)
2356 6783 : hyphen_line_count++;
2357 : else
2358 24079 : hyphen_line_count = 0;
2359 30862 : delete bp;
2360 : // Normally, the do_break() member function discards trailing spaces
2361 : // (cf. horizontal motions) from input lines. But when `\p` is
2362 : // used, that mechanism is bypassed, so we do the equivalent here.
2363 30862 : space_total = 0;
2364 30862 : width_total = 0;
2365 30862 : node *first_non_discardable = 0 /* nullptr */;
2366 : node *tem;
2367 242670 : for (tem = line; tem != 0 /* nullptr */; tem = tem->next)
2368 211808 : if (!tem->discardable())
2369 181230 : first_non_discardable = tem;
2370 : node *to_be_discarded;
2371 30862 : if (first_non_discardable != 0 /* nullptr */) {
2372 30798 : to_be_discarded = first_non_discardable->next;
2373 30798 : first_non_discardable->next = 0 /* nullptr */;
2374 242606 : for (tem = line; tem != 0 /* nullptr */; tem = tem->next) {
2375 211808 : width_total += tem->width();
2376 211808 : space_total += tem->nspaces();
2377 : }
2378 30798 : is_discarding = false;
2379 : }
2380 : else {
2381 64 : is_discarding = true;
2382 64 : to_be_discarded = line;
2383 64 : line = 0 /* nullptr */;
2384 : }
2385 : // Do output_line() here so that line will be 0 iff the
2386 : // the environment will be empty.
2387 30862 : output_line(pre, output_width, was_centered);
2388 30862 : while (to_be_discarded != 0 /* nullptr */) {
2389 0 : tem = to_be_discarded;
2390 0 : to_be_discarded = to_be_discarded->next;
2391 0 : input_line_start -= tem->width();
2392 0 : delete tem;
2393 : }
2394 30862 : if (line != 0 /* nullptr */) {
2395 30798 : if (have_temporary_indent) {
2396 6 : saved_indent = temporary_indent;
2397 6 : have_temporary_indent = false;
2398 : }
2399 : else
2400 30792 : saved_indent = indent;
2401 30798 : target_text_length = line_length - saved_indent;
2402 : }
2403 : }
2404 : }
2405 :
2406 : /*
2407 : Do the break at the end of input after the end macro (if any).
2408 :
2409 : Unix troff behaves as follows: if the last line is
2410 :
2411 : foo bar\c
2412 :
2413 : it will output foo on the current page, and bar on the next page;
2414 : if the last line is
2415 :
2416 : foo\c
2417 :
2418 : or
2419 :
2420 : foo bar
2421 :
2422 : everything will be output on the current page. This behaviour must be
2423 : considered a bug.
2424 :
2425 : The problem is that some macro packages rely on this. For example,
2426 : the ATK macros have an end macro that emits \c if it needs to print a
2427 : table of contents but doesn't do a 'bp in the end macro; instead the
2428 : 'bp is done in the bottom of page trap. This works with Unix troff,
2429 : provided that the current environment is not empty at the end of the
2430 : input file.
2431 :
2432 : The following will make macro packages that do that sort of thing work
2433 : even if the current environment is empty at the end of the input file.
2434 : If the last input line used \c and this line occurred in the end macro,
2435 : then we'll force everything out on the current page, but we'll make
2436 : sure that the environment isn't empty so that we won't exit at the
2437 : bottom of this page.
2438 : */
2439 :
2440 1309 : void environment::final_break()
2441 : {
2442 1309 : if (was_previous_line_interrupted == 2) {
2443 0 : do_break();
2444 0 : add_node(new transparent_dummy_node);
2445 : }
2446 : else
2447 1309 : do_break();
2448 1184 : }
2449 :
2450 1065 : node *environment::make_tag(const char *nm, int i)
2451 : {
2452 1065 : if (is_writing_html) {
2453 : /*
2454 : * need to emit tag for post-grohtml
2455 : * but we check to see whether we can emit specials
2456 : */
2457 9 : if ((curdiv == topdiv) && (topdiv->before_first_page_status > 0))
2458 0 : topdiv->begin_page();
2459 9 : macro m;
2460 9 : m.append_str("devtag:");
2461 54 : for (const char *p = nm; *p; p++)
2462 45 : if (!is_invalid_input_char(static_cast<unsigned char>(*p)))
2463 45 : m.append(*p);
2464 9 : m.append(' ');
2465 9 : m.append_int(i);
2466 9 : return new device_extension_node(m);
2467 : }
2468 1056 : return new transparent_dummy_node;
2469 : }
2470 :
2471 0 : void environment::dump_troff_state()
2472 : {
2473 : #define SPACES " "
2474 0 : fprintf(stderr, SPACES "register 'in' = %d\n",
2475 0 : curenv->indent.to_units());
2476 0 : if (curenv->have_temporary_indent)
2477 0 : fprintf(stderr, SPACES "register 'ti' = %d\n",
2478 0 : curenv->temporary_indent.to_units());
2479 0 : fprintf(stderr, SPACES "centered lines 'ce' = %d\n",
2480 : curenv->centered_line_count);
2481 0 : fprintf(stderr, SPACES "register 'll' = %d\n",
2482 0 : curenv->line_length.to_units());
2483 0 : fprintf(stderr, SPACES "%sfilling\n",
2484 0 : curenv->is_filling ? "" : "not ");
2485 0 : fprintf(stderr, SPACES "page offset 'po' = %d\n",
2486 0 : topdiv->get_page_offset().to_units());
2487 0 : fprintf(stderr, SPACES "seen_break = %d\n", curenv->seen_break);
2488 0 : fprintf(stderr, SPACES "seen_space = %d\n", curenv->seen_space);
2489 0 : fprintf(stderr, SPACES "is_discarding = %d\n", curenv->is_discarding);
2490 0 : fflush(stderr);
2491 : #undef SPACES
2492 0 : }
2493 :
2494 : extern void dump_node_list_in_reverse(node *);
2495 :
2496 0 : void environment::dump_pending_nodes()
2497 : {
2498 0 : dump_node_list_in_reverse(line);
2499 0 : }
2500 :
2501 4195498 : statem *environment::construct_state(bool has_only_eol)
2502 : {
2503 4195498 : if (is_writing_html) {
2504 106178 : statem *s = new statem();
2505 106178 : if (!has_only_eol) {
2506 106178 : s->add_tag(MTSM_IN, indent);
2507 106178 : s->add_tag(MTSM_LL, line_length);
2508 106178 : s->add_tag(MTSM_PO, topdiv->get_page_offset().to_units());
2509 106178 : s->add_tag(MTSM_RJ, right_aligned_line_count);
2510 106178 : if (have_temporary_indent)
2511 38 : s->add_tag(MTSM_TI, temporary_indent);
2512 106178 : s->add_tag_ta();
2513 106178 : if (seen_break)
2514 2444 : s->add_tag(MTSM_BR);
2515 106178 : if (seen_space)
2516 904 : s->add_tag(MTSM_SP, seen_space);
2517 106178 : seen_break = false;
2518 106178 : seen_space = false;
2519 : }
2520 106178 : if (seen_eol) {
2521 344 : s->add_tag(MTSM_EOL);
2522 344 : s->add_tag(MTSM_CE, centered_line_count);
2523 : }
2524 106178 : seen_eol = false;
2525 106178 : return s;
2526 : }
2527 : else
2528 4089320 : return 0 /* nullptr */;
2529 : }
2530 :
2531 527376 : void environment::construct_format_state(node *nd, bool was_centered,
2532 : int filling)
2533 : {
2534 527376 : if (is_writing_html) {
2535 : // find first glyph node which has a state.
2536 48428 : while (nd != 0 /* nullptr */ && nd->state == 0 /* nullptr */)
2537 39137 : nd = nd->next;
2538 9291 : if (nd == 0 /* nullptr */ || (nd->state == 0 /* nullptr */))
2539 4798 : return;
2540 4493 : if (seen_space)
2541 0 : nd->state->add_tag(MTSM_SP, seen_space);
2542 4493 : if (seen_eol && topdiv == curdiv)
2543 1112 : nd->state->add_tag(MTSM_EOL);
2544 4493 : seen_space = false;
2545 4493 : seen_eol = false;
2546 4493 : if (was_centered)
2547 61 : nd->state->add_tag(MTSM_CE, centered_line_count + 1);
2548 : else
2549 4432 : nd->state->add_tag_if_unknown(MTSM_CE, 0);
2550 4493 : nd->state->add_tag_if_unknown(MTSM_FI, filling);
2551 4493 : nd = nd->next;
2552 150082 : while (nd != 0 /* nullptr */) {
2553 145589 : if (nd->state != 0 /* nullptr */) {
2554 119152 : nd->state->sub_tag_ce();
2555 119152 : nd->state->add_tag_if_unknown(MTSM_FI, filling);
2556 : }
2557 145589 : nd = nd->next;
2558 : }
2559 : }
2560 : }
2561 :
2562 10493417 : void environment::construct_new_line_state(node *nd)
2563 : {
2564 10493417 : if (is_writing_html) {
2565 : // find first glyph node which has a state.
2566 226920 : while (nd != 0 /* nullptr */ && nd->state == 0 /* nullptr */)
2567 147059 : nd = nd->next;
2568 79861 : if (nd == 0 /* nullptr */ || nd->state == 0 /* nullptr */)
2569 28806 : return;
2570 51055 : if (seen_space)
2571 501 : nd->state->add_tag(MTSM_SP, seen_space);
2572 51055 : if (seen_eol && topdiv == curdiv)
2573 213 : nd->state->add_tag(MTSM_EOL);
2574 51055 : seen_space = false;
2575 51055 : seen_eol = false;
2576 : }
2577 : }
2578 :
2579 : extern int global_diverted_space;
2580 :
2581 : // "Forced adjustment" refers to spreading of adjustable spaces (and
2582 : // perhaps only that, even if in the future we implement "squeezing"),
2583 : // when it is not normally called for, as at the end of a paragraph.
2584 :
2585 568857 : void environment::do_break(bool want_forced_adjustment)
2586 : {
2587 568857 : bool was_centered = false;
2588 568857 : if ((curdiv == topdiv) && (topdiv->before_first_page_status > 0)) {
2589 544 : topdiv->begin_page();
2590 416 : return;
2591 : }
2592 568313 : if (current_tab != TAB_NONE)
2593 0 : wrap_up_tab();
2594 568313 : if (line) {
2595 : // this is so that hyphenation works
2596 65991 : if (line->nspaces() == 0) {
2597 4105 : line = new space_node(H0, get_fill_color(), line);
2598 4105 : space_total++;
2599 : }
2600 65991 : possibly_break_line(false /* must break here */,
2601 : want_forced_adjustment);
2602 : }
2603 634284 : while (line != 0 /* nullptr */ && line->discardable()) {
2604 65971 : width_total -= line->width();
2605 65971 : space_total -= line->nspaces();
2606 65971 : node *tem = line;
2607 65971 : line = line->next;
2608 65971 : delete tem;
2609 : }
2610 568313 : is_discarding = false;
2611 568313 : input_line_start = H0;
2612 568313 : if (line != 0 /* nullptr */) {
2613 65971 : if (is_filling) {
2614 65444 : switch (adjust_mode) {
2615 23411 : case ADJUST_CENTER:
2616 23411 : saved_indent += (target_text_length - width_total) / 2;
2617 23411 : was_centered = true;
2618 23411 : break;
2619 31 : case ADJUST_RIGHT:
2620 31 : saved_indent += target_text_length - width_total;
2621 31 : break;
2622 : }
2623 : }
2624 65971 : if (want_nodes_dumped && (curdiv == topdiv))
2625 0 : curenv->dump_pending_nodes();
2626 65971 : node *tem = line;
2627 65971 : line = 0 /* nullptr */;
2628 65971 : output_line(tem, width_total, was_centered);
2629 65956 : hyphen_line_count = 0;
2630 : }
2631 568298 : was_previous_line_interrupted = 0;
2632 : #ifdef WIDOW_CONTROL
2633 : mark_last_line();
2634 : output_pending_lines();
2635 : #endif /* WIDOW_CONTROL */
2636 568298 : if (!global_diverted_space) {
2637 568298 : curdiv->modified_tag.incl(MTSM_BR);
2638 568298 : seen_break = true;
2639 : }
2640 : }
2641 :
2642 1403 : bool environment::is_empty()
2643 : {
2644 1403 : return (current_tab == TAB_NONE) && line == 0 /* nullptr */
2645 2806 : && pending_lines == 0 /* nullptr */;
2646 : }
2647 :
2648 106500 : static void break_without_forced_adjustment_request()
2649 : {
2650 106500 : if (was_invoked_with_regular_control_character)
2651 106499 : curenv->do_break(false /* want forced adjustment */);
2652 106482 : skip_line();
2653 106482 : }
2654 :
2655 41 : static void break_with_forced_adjustment_request()
2656 : {
2657 41 : if (was_invoked_with_regular_control_character)
2658 40 : curenv->do_break(true /* want forced adjustment */);
2659 41 : skip_line();
2660 41 : }
2661 :
2662 3423 : void title()
2663 : {
2664 3423 : if (!has_arg(true /* peek */)) {
2665 0 : warning(WARN_MISSING, "title line request expects a delimited"
2666 : " argument");
2667 0 : skip_line();
2668 125 : return;
2669 : }
2670 3423 : if ((curdiv == topdiv) && (topdiv->before_first_page_status > 0)) {
2671 125 : handle_initial_title();
2672 125 : return;
2673 : }
2674 : node *part[3];
2675 13192 : hunits part_width[3];
2676 3298 : part[0] = part[1] = part[2] = 0 /* nullptr */;
2677 6596 : environment env(curenv);
2678 3298 : environment *oldenv = curenv;
2679 3298 : curenv = &env;
2680 3298 : read_title_parts(part, part_width);
2681 3298 : curenv = oldenv;
2682 3298 : curenv->size = env.size;
2683 3298 : curenv->prev_size = env.prev_size;
2684 3298 : curenv->requested_size = env.requested_size;
2685 3298 : curenv->prev_requested_size = env.prev_requested_size;
2686 3298 : curenv->char_height = env.char_height;
2687 3298 : curenv->char_slant = env.char_slant;
2688 3298 : curenv->fontno = env.fontno;
2689 3298 : curenv->prev_fontno = env.prev_fontno;
2690 3298 : curenv->stroke_color = env.stroke_color;
2691 3298 : curenv->prev_stroke_color = env.prev_stroke_color;
2692 3298 : curenv->fill_color = env.fill_color;
2693 3298 : curenv->prev_fill_color = env.prev_fill_color;
2694 3298 : node *nd = 0 /* nullptr */;
2695 3298 : node *p = part[2];
2696 21371 : while (p != 0 /* nullptr */) {
2697 18073 : node *tem = p;
2698 18073 : p = p->next;
2699 18073 : tem->next = nd;
2700 18073 : nd = tem;
2701 : }
2702 3298 : hunits length_title(curenv->title_length);
2703 3298 : hunits f = length_title - part_width[1];
2704 3298 : hunits f2 = f/2;
2705 3298 : nd = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(),
2706 3298 : nd);
2707 3298 : p = part[1];
2708 59739 : while (p != 0) {
2709 56441 : node *tem = p;
2710 56441 : p = p->next;
2711 56441 : tem->next = nd;
2712 56441 : nd = tem;
2713 : }
2714 3298 : nd = new hmotion_node(f - f2 - part_width[0],
2715 3298 : curenv->get_fill_color(), nd);
2716 3298 : p = part[0];
2717 40888 : while (p != 0) {
2718 37590 : node *tem = p;
2719 37590 : p = p->next;
2720 37590 : tem->next = nd;
2721 37590 : nd = tem;
2722 : }
2723 3298 : curenv->output_title(nd, !curenv->is_filling,
2724 : curenv->vertical_spacing,
2725 : curenv->total_post_vertical_spacing(),
2726 : length_title);
2727 3298 : curenv->hyphen_line_count = 0;
2728 3298 : tok.next();
2729 : }
2730 :
2731 65721 : void adjust()
2732 : {
2733 65721 : curenv->adjust_mode |= 1;
2734 65721 : if (has_arg()) {
2735 65517 : int c = tok.ch(); // safely compares to char literals; TODO: grochar
2736 65517 : switch (c) {
2737 2033 : case 'l':
2738 2033 : curenv->adjust_mode = ADJUST_LEFT;
2739 54435 : break;
2740 58 : case 'r':
2741 58 : curenv->adjust_mode = ADJUST_RIGHT;
2742 58 : break;
2743 31924 : case 'c':
2744 31924 : curenv->adjust_mode = ADJUST_CENTER;
2745 31924 : break;
2746 20420 : case 'b':
2747 : case 'n':
2748 20420 : curenv->adjust_mode = ADJUST_BOTH;
2749 20420 : break;
2750 11082 : default:
2751 : int n;
2752 11082 : if (read_integer(&n)) {
2753 11082 : if (n < 0)
2754 0 : warning(WARN_RANGE, "negative adjustment mode");
2755 11082 : else if (n > ADJUST_MAX)
2756 1 : warning(WARN_RANGE, "adjustment mode must be in range 0..%1"
2757 : " (or 'l', 'r', 'c', 'b', or 'n'), got %2",
2758 2 : ADJUST_MAX, n);
2759 : else
2760 11081 : curenv->adjust_mode = n;
2761 : }
2762 : }
2763 : }
2764 65721 : skip_line();
2765 65721 : }
2766 :
2767 9362 : void no_adjust()
2768 : {
2769 9362 : curenv->adjust_mode &= ~1;
2770 9362 : skip_line();
2771 9362 : }
2772 :
2773 40309 : void do_input_trap(bool respect_continuation)
2774 : {
2775 40309 : curenv->input_trap_count = -1;
2776 40309 : curenv->input_trap = 0 /* nullptr */;
2777 40309 : curenv->continued_input_trap = respect_continuation;
2778 : int n;
2779 40309 : if (has_arg() && read_integer(&n)) {
2780 40248 : if (n <= 0)
2781 0 : warning(WARN_RANGE,
2782 : "input trap line count must be greater than zero");
2783 : else {
2784 40248 : symbol s = read_identifier(true /* required */);
2785 40248 : if (!s.is_null()) {
2786 40248 : curenv->input_trap_count = n;
2787 40248 : curenv->input_trap = s;
2788 : }
2789 : }
2790 : }
2791 40309 : skip_line();
2792 40309 : }
2793 :
2794 31184 : void input_trap()
2795 : {
2796 31184 : do_input_trap(false);
2797 31184 : }
2798 :
2799 9125 : void input_trap_continued()
2800 : {
2801 9125 : do_input_trap(true);
2802 9125 : }
2803 :
2804 : /* tabs */
2805 :
2806 : // must not be R or C or L or a legitimate part of a numeric expression
2807 : // TODO: grochar
2808 : const unsigned char TAB_REPEAT_CHAR = (unsigned char)('T');
2809 :
2810 : struct tab {
2811 : tab *next;
2812 : hunits pos;
2813 : tab_type type;
2814 : tab(hunits, tab_type);
2815 : enum { BLOCK = 1024 };
2816 : };
2817 :
2818 3016314 : tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
2819 : {
2820 3016314 : }
2821 :
2822 2620 : tab_stops::tab_stops(hunits distance, tab_type type)
2823 2620 : : initial_list(0)
2824 : {
2825 2620 : repeated_list = new tab(distance, type);
2826 2620 : }
2827 :
2828 5708724 : tab_stops::~tab_stops()
2829 : {
2830 2854362 : clear();
2831 2854362 : }
2832 :
2833 1967775 : tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
2834 : {
2835 1967775 : hunits nextpos;
2836 :
2837 3935550 : return distance_to_next_tab(curpos, distance, &nextpos);
2838 : }
2839 :
2840 1968873 : tab_type tab_stops::distance_to_next_tab(hunits curpos,
2841 : hunits *distance,
2842 : hunits *nextpos)
2843 : {
2844 1968873 : hunits lastpos = 0;
2845 : tab *tem;
2846 1975781 : for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
2847 6908 : lastpos = tem->pos;
2848 1968873 : if (tem) {
2849 5378 : *distance = tem->pos - curpos;
2850 5378 : *nextpos = tem->pos;
2851 5378 : return tem->type;
2852 : }
2853 1963495 : if (repeated_list == 0 /* nullptr */)
2854 1709 : return TAB_NONE;
2855 1961786 : hunits base = lastpos;
2856 : for (;;) {
2857 92931476 : for (tem = repeated_list; tem && tem->pos + base <= curpos;
2858 45484845 : tem = tem->next)
2859 45484845 : lastpos = tem->pos;
2860 47446631 : if (tem) {
2861 1961786 : *distance = tem->pos + base - curpos;
2862 1961786 : *nextpos = tem->pos + base;
2863 1961786 : return tem->type;
2864 : }
2865 45484845 : if (lastpos < 0)
2866 0 : lastpos = 0;
2867 45484845 : base += lastpos;
2868 : }
2869 : return TAB_NONE;
2870 : }
2871 :
2872 522 : const char *tab_stops::to_string()
2873 : {
2874 : static char *buf = 0 /* nullptr */;
2875 : static int buf_size = 0;
2876 : // figure out a maximum on the amount of space we can need
2877 522 : int count = 0;
2878 : tab *p;
2879 529 : for (p = initial_list; p; p = p->next)
2880 7 : ++count;
2881 1037 : for (p = repeated_list; p; p = p->next)
2882 515 : ++count;
2883 : // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
2884 522 : int need = count*12 + 3;
2885 522 : if (buf == 0 || need > buf_size) {
2886 137 : if (buf)
2887 0 : delete[] buf;
2888 137 : buf_size = need;
2889 137 : buf = new char[buf_size]; // C++03: new char[buf_size]();
2890 137 : (void) memset(buf, 0, buf_size * sizeof(char));
2891 : }
2892 522 : char *ptr = buf;
2893 529 : for (p = initial_list; p; p = p->next) {
2894 7 : strcpy(ptr, i_to_a(p->pos.to_units()));
2895 7 : ptr = strchr(ptr, '\0');
2896 7 : *ptr++ = 'u';
2897 7 : *ptr = '\0';
2898 7 : switch (p->type) {
2899 7 : case TAB_LEFT:
2900 7 : break;
2901 0 : case TAB_RIGHT:
2902 0 : *ptr++ = 'R';
2903 0 : break;
2904 0 : case TAB_CENTER:
2905 0 : *ptr++ = 'C';
2906 0 : break;
2907 0 : case TAB_NONE:
2908 : default:
2909 0 : assert(0 == "unhandled case of `p->type` (tab_type)");
2910 : }
2911 : }
2912 522 : if (repeated_list)
2913 515 : *ptr++ = TAB_REPEAT_CHAR;
2914 1037 : for (p = repeated_list; p; p = p->next) {
2915 515 : strcpy(ptr, i_to_a(p->pos.to_units()));
2916 515 : ptr = strchr(ptr, '\0');
2917 515 : *ptr++ = 'u';
2918 515 : *ptr = '\0';
2919 515 : switch (p->type) {
2920 515 : case TAB_LEFT:
2921 515 : break;
2922 0 : case TAB_RIGHT:
2923 0 : *ptr++ = 'R';
2924 0 : break;
2925 0 : case TAB_CENTER:
2926 0 : *ptr++ = 'C';
2927 0 : break;
2928 0 : case TAB_NONE:
2929 : default:
2930 0 : assert(0 == "unhandled case of `p->type` (tab_type)");
2931 : }
2932 : }
2933 522 : *ptr++ = '\0';
2934 522 : return buf;
2935 : }
2936 :
2937 14369 : tab_stops::tab_stops() : initial_list(0), repeated_list(0)
2938 : {
2939 14369 : }
2940 :
2941 2839993 : tab_stops::tab_stops(const tab_stops &ts)
2942 2839993 : : initial_list(0), repeated_list(0)
2943 : {
2944 2839993 : tab **p = &initial_list;
2945 2839993 : tab *t = ts.initial_list;
2946 3571560 : while (t) {
2947 731567 : *p = new tab(t->pos, t->type);
2948 731567 : t = t->next;
2949 731567 : p = &(*p)->next;
2950 : }
2951 2839993 : p = &repeated_list;
2952 2839993 : t = ts.repeated_list;
2953 5063271 : while (t) {
2954 2223278 : *p = new tab(t->pos, t->type);
2955 2223278 : t = t->next;
2956 2223278 : p = &(*p)->next;
2957 : }
2958 2839993 : }
2959 :
2960 3645107 : void tab_stops::clear()
2961 : {
2962 3645107 : while (initial_list) {
2963 763757 : tab *tem = initial_list;
2964 763757 : initial_list = initial_list->next;
2965 763757 : delete tem;
2966 : }
2967 5131263 : while (repeated_list) {
2968 2249913 : tab *tem = repeated_list;
2969 2249913 : repeated_list = repeated_list->next;
2970 2249913 : delete tem;
2971 : }
2972 2881350 : }
2973 :
2974 23115 : void tab_stops::add_tab(hunits pos, tab_type type, bool is_repeated)
2975 : {
2976 : tab **p;
2977 40079 : for (p = is_repeated ? &repeated_list : &initial_list; *p;
2978 16964 : p = &(*p)->next)
2979 : ;
2980 23115 : *p = new tab(pos, type);
2981 23115 : }
2982 :
2983 :
2984 26988 : void tab_stops::operator=(const tab_stops &ts)
2985 : {
2986 26988 : clear();
2987 26988 : tab **p = &initial_list;
2988 26988 : tab *t = ts.initial_list;
2989 43493 : while (t) {
2990 16505 : *p = new tab(t->pos, t->type);
2991 16505 : t = t->next;
2992 16505 : p = &(*p)->next;
2993 : }
2994 26988 : p = &repeated_list;
2995 26988 : t = ts.repeated_list;
2996 46217 : while (t) {
2997 19229 : *p = new tab(t->pos, t->type);
2998 19229 : t = t->next;
2999 19229 : p = &(*p)->next;
3000 : }
3001 26988 : }
3002 :
3003 14369 : static void configure_tab_stops_request()
3004 : {
3005 14369 : hunits pos;
3006 14369 : hunits prev_pos = 0;
3007 14369 : bool is_first_stop = true;
3008 14369 : bool is_repeating_stop = false;
3009 28738 : tab_stops tabs;
3010 37484 : while (has_arg()) {
3011 23115 : if (tok.ch() == TAB_REPEAT_CHAR) {
3012 7127 : tok.next();
3013 7127 : is_repeating_stop = true;
3014 7127 : prev_pos = 0;
3015 : }
3016 23115 : if (!read_hunits(&pos, 'm', prev_pos))
3017 0 : break;
3018 23115 : tab_type type = TAB_LEFT;
3019 23115 : if (tok.ch() == int('C')) { // TODO: grochar
3020 5 : tok.next();
3021 5 : type = TAB_CENTER;
3022 : }
3023 23110 : else if (tok.ch() == int('R')) { // TODO: grochar
3024 238 : tok.next();
3025 238 : type = TAB_RIGHT;
3026 : }
3027 22872 : else if (tok.ch() == int('L')) { // TODO: grochar
3028 0 : tok.next();
3029 : }
3030 23115 : if (pos <= prev_pos && ((!is_first_stop) || is_repeating_stop))
3031 0 : warning(WARN_RANGE,
3032 : "positions of tab stops must be strictly increasing");
3033 : else {
3034 23115 : tabs.add_tab(pos, type, is_repeating_stop);
3035 23115 : prev_pos = pos;
3036 23115 : is_first_stop = false;
3037 : }
3038 : }
3039 14369 : curenv->tabs = tabs;
3040 14369 : curdiv->modified_tag.incl(MTSM_TA);
3041 14369 : skip_line();
3042 14369 : }
3043 :
3044 522 : const char *environment::get_tabs()
3045 : {
3046 522 : return tabs.to_string();
3047 : }
3048 :
3049 981 : tab_type environment::distance_to_next_tab(hunits *distance)
3050 : {
3051 981 : return using_line_tabs
3052 981 : ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
3053 981 : : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
3054 : }
3055 :
3056 1098 : tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
3057 : {
3058 1098 : return using_line_tabs
3059 1098 : ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
3060 1056 : : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
3061 1098 : leftpos);
3062 : }
3063 :
3064 : // XXX: Field characters are global; shouldn't they be environmental?
3065 1034 : static void field_characters_request()
3066 : {
3067 1034 : field_delimiter_char = read_character();
3068 1034 : if (field_delimiter_char)
3069 517 : padding_indicator_char = read_character();
3070 : else
3071 517 : padding_indicator_char = 0 /* nullptr */;
3072 1034 : skip_line();
3073 1034 : }
3074 :
3075 12 : void line_tabs_request()
3076 : {
3077 : int n;
3078 12 : if (has_arg() && read_integer(&n))
3079 12 : curenv->using_line_tabs = (n > 0);
3080 : else
3081 0 : curenv->using_line_tabs = true;
3082 12 : skip_line();
3083 12 : }
3084 :
3085 0 : int environment::is_using_line_tabs()
3086 : {
3087 0 : return using_line_tabs;
3088 : }
3089 :
3090 35627 : void environment::wrap_up_tab()
3091 : {
3092 35627 : if (current_tab == TAB_NONE)
3093 35339 : return;
3094 288 : if (line == 0)
3095 0 : start_line();
3096 288 : hunits tab_amount;
3097 288 : switch (current_tab) {
3098 283 : case TAB_RIGHT:
3099 283 : tab_amount = tab_distance - tab_width;
3100 283 : line = make_tab_node(tab_amount, line);
3101 283 : break;
3102 5 : case TAB_CENTER:
3103 5 : tab_amount = tab_distance - tab_width/2;
3104 5 : line = make_tab_node(tab_amount, line);
3105 5 : break;
3106 0 : case TAB_NONE:
3107 : case TAB_LEFT:
3108 : default:
3109 0 : assert(0 == "unhandled case of `current_tab` (tab_type)");
3110 : }
3111 288 : width_total += tab_amount;
3112 288 : width_total += tab_width;
3113 288 : if (has_current_field) {
3114 0 : if (tab_precedes_field) {
3115 0 : pre_field_width += tab_amount;
3116 0 : tab_precedes_field = false;
3117 : }
3118 0 : field_distance -= tab_amount;
3119 0 : field_spaces += tab_field_spaces;
3120 : }
3121 288 : if (tab_contents != 0 /* nullptr */) {
3122 : node *tem;
3123 1571 : for (tem = tab_contents; tem->next != 0 /* nullptr */;
3124 1291 : tem = tem->next)
3125 : ;
3126 280 : tem->next = line;
3127 280 : line = tab_contents;
3128 : }
3129 288 : tab_field_spaces = 0;
3130 288 : tab_contents = 0 /* nullptr */;
3131 288 : tab_width = H0;
3132 288 : tab_distance = H0;
3133 288 : current_tab = TAB_NONE;
3134 : }
3135 :
3136 1065 : node *environment::make_tab_node(hunits d, node *next)
3137 : {
3138 1065 : if ((leader_node != 0 /* nullptr */) && (d < 0)) {
3139 0 : error("motion generated by leader cannot be negative");
3140 0 : delete leader_node;
3141 0 : leader_node = 0 /* nullptr */;
3142 : }
3143 1065 : if (0 /* nullptr */ == leader_node)
3144 784 : return new hmotion_node(d, 1, 0, get_fill_color(), next);
3145 281 : node *nd = new hline_node(d, leader_node, next);
3146 281 : leader_node = 0 /* nullptr */;
3147 281 : return nd;
3148 : }
3149 :
3150 1098 : void environment::advance_to_tab_stop(bool use_leader)
3151 : {
3152 1098 : hunits d;
3153 1098 : hunits absolute;
3154 1098 : if (current_tab != TAB_NONE)
3155 13 : wrap_up_tab();
3156 1098 : charinfo *ci = use_leader ? leader_char : tab_char;
3157 1098 : delete leader_node;
3158 1098 : leader_node = 0 /* nullptr */;
3159 1098 : if (ci != 0 /* nullptr */)
3160 281 : leader_node = make_char_node(ci);
3161 1098 : tab_type t = distance_to_next_tab(&d, &absolute);
3162 1098 : switch (t) {
3163 33 : case TAB_NONE:
3164 810 : return;
3165 777 : case TAB_LEFT:
3166 777 : add_node(make_tag("tab L", absolute.to_units()));
3167 777 : add_node(make_tab_node(d));
3168 777 : return;
3169 283 : case TAB_RIGHT:
3170 283 : add_node(make_tag("tab R", absolute.to_units()));
3171 283 : break;
3172 5 : case TAB_CENTER:
3173 5 : add_node(make_tag("tab C", absolute.to_units()));
3174 5 : break;
3175 0 : default:
3176 0 : assert(0 == "unhandled case of `t` (tab_type)");
3177 : }
3178 288 : tab_width = 0;
3179 288 : tab_distance = d;
3180 288 : tab_contents = 0 /* nullptr */;
3181 288 : current_tab = t;
3182 288 : tab_field_spaces = 0;
3183 : }
3184 :
3185 981 : void environment::start_field()
3186 : {
3187 981 : assert(!has_current_field);
3188 981 : hunits d;
3189 981 : if (distance_to_next_tab(&d) != TAB_NONE) {
3190 981 : pre_field_width = get_text_length();
3191 981 : field_distance = d;
3192 981 : has_current_field = true;
3193 981 : field_spaces = 0;
3194 981 : tab_field_spaces = 0;
3195 11838 : for (node *p = line; p != 0 /* nullptr */; p = p->next)
3196 10857 : if (p->nspaces()) {
3197 245 : p->freeze_space();
3198 245 : space_total--;
3199 : }
3200 981 : tab_precedes_field = current_tab != TAB_NONE;
3201 : }
3202 : else
3203 0 : error("zero field width");
3204 981 : }
3205 :
3206 981 : void environment::wrap_up_field()
3207 : {
3208 981 : if ((current_tab == TAB_NONE) && field_spaces == 0)
3209 0 : add_padding();
3210 981 : hunits padding = field_distance - (get_text_length() - pre_field_width);
3211 981 : if ((current_tab != TAB_NONE) && tab_field_spaces != 0) {
3212 : hunits tab_padding = scale(padding,
3213 : tab_field_spaces,
3214 0 : field_spaces + tab_field_spaces);
3215 0 : padding -= tab_padding;
3216 0 : (void) distribute_space(tab_contents, tab_field_spaces, tab_padding,
3217 : true /* force reversal of node list */);
3218 0 : tab_field_spaces = 0;
3219 0 : tab_width += tab_padding;
3220 : }
3221 981 : if (field_spaces != 0) {
3222 981 : (void) distribute_space(line, field_spaces, padding,
3223 : true /* force reversal of node list */);
3224 981 : width_total += padding;
3225 981 : if (current_tab != TAB_NONE) {
3226 : // the start of the tab has been moved to the right by padding, so
3227 0 : tab_distance -= padding;
3228 0 : if (tab_distance <= H0) {
3229 : // use the next tab stop instead
3230 0 : current_tab = tabs.distance_to_next_tab(get_input_line_position()
3231 0 : - tab_width,
3232 : &tab_distance);
3233 0 : if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
3234 0 : width_total += tab_width;
3235 0 : if (current_tab == TAB_LEFT) {
3236 0 : line = make_tab_node(tab_distance, line);
3237 0 : width_total += tab_distance;
3238 0 : current_tab = TAB_NONE;
3239 : }
3240 0 : if (tab_contents != 0 /* nullptr */) {
3241 : node *tem;
3242 0 : for (tem = tab_contents; tem->next != 0 /* nullptr */;
3243 0 : tem = tem->next)
3244 : ;
3245 0 : tem->next = line;
3246 0 : line = tab_contents;
3247 0 : tab_contents = 0 /* nullptr */;
3248 : }
3249 0 : tab_width = H0;
3250 0 : tab_distance = H0;
3251 : }
3252 : }
3253 : }
3254 : }
3255 981 : has_current_field = false;
3256 981 : }
3257 :
3258 1640 : void environment::add_padding()
3259 : {
3260 1640 : if (current_tab != TAB_NONE) {
3261 0 : tab_contents = new space_node(H0, get_fill_color(), tab_contents);
3262 0 : tab_field_spaces++;
3263 : }
3264 : else {
3265 1640 : if (line == 0 /* nullptr */)
3266 21 : start_line();
3267 1640 : line = new space_node(H0, get_fill_color(), line);
3268 1640 : field_spaces++;
3269 : }
3270 1640 : }
3271 :
3272 : typedef int (environment::*INT_FUNCP)();
3273 : typedef unsigned int (environment::*UNSIGNED_FUNCP)();
3274 : typedef vunits (environment::*VUNITS_FUNCP)();
3275 : typedef hunits (environment::*HUNITS_FUNCP)();
3276 : typedef const char *(environment::*STRING_FUNCP)();
3277 :
3278 : class int_env_reg : public reg {
3279 : INT_FUNCP func;
3280 : public:
3281 : int_env_reg(INT_FUNCP);
3282 : const char *get_string();
3283 : bool get_value(units *val);
3284 : };
3285 :
3286 : class unsigned_env_reg : public reg {
3287 : UNSIGNED_FUNCP func;
3288 : public:
3289 : unsigned_env_reg(UNSIGNED_FUNCP);
3290 : const char *get_string();
3291 : bool get_value(unsigned int *val);
3292 : };
3293 :
3294 : class vunits_env_reg : public reg {
3295 : VUNITS_FUNCP func;
3296 : public:
3297 : vunits_env_reg(VUNITS_FUNCP f);
3298 : const char *get_string();
3299 : bool get_value(units *val);
3300 : };
3301 :
3302 : class hunits_env_reg : public reg {
3303 : HUNITS_FUNCP func;
3304 : public:
3305 : hunits_env_reg(HUNITS_FUNCP f);
3306 : const char *get_string();
3307 : bool get_value(units *val);
3308 : };
3309 :
3310 : class string_env_reg : public reg {
3311 : STRING_FUNCP func;
3312 : public:
3313 : string_env_reg(STRING_FUNCP);
3314 : const char *get_string();
3315 : };
3316 :
3317 28360 : int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
3318 : {
3319 28360 : }
3320 :
3321 0 : bool int_env_reg::get_value(units *val)
3322 : {
3323 0 : *val = (curenv->*func)();
3324 0 : return true;
3325 : }
3326 :
3327 27181 : const char *int_env_reg::get_string()
3328 : {
3329 27181 : return i_to_a((curenv->*func)());
3330 : }
3331 :
3332 4254 : unsigned_env_reg::unsigned_env_reg(UNSIGNED_FUNCP f) : func(f)
3333 : {
3334 4254 : }
3335 :
3336 0 : bool unsigned_env_reg::get_value(unsigned int *val)
3337 : {
3338 0 : *val = (curenv->*func)();
3339 0 : return true;
3340 : }
3341 :
3342 12263 : const char *unsigned_env_reg::get_string()
3343 : {
3344 12263 : return ui_to_a((curenv->*func)());
3345 : }
3346 :
3347 5672 : vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
3348 : {
3349 5672 : }
3350 :
3351 0 : bool vunits_env_reg::get_value(units *val)
3352 : {
3353 0 : *val = (curenv->*func)().to_units();
3354 0 : return true;
3355 : }
3356 :
3357 6419 : const char *vunits_env_reg::get_string()
3358 : {
3359 6419 : return i_to_a((curenv->*func)().to_units());
3360 : }
3361 :
3362 17016 : hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
3363 : {
3364 17016 : }
3365 :
3366 59 : bool hunits_env_reg::get_value(units *val)
3367 : {
3368 59 : *val = (curenv->*func)().to_units();
3369 59 : return true;
3370 : }
3371 :
3372 49427 : const char *hunits_env_reg::get_string()
3373 : {
3374 49427 : return i_to_a((curenv->*func)().to_units());
3375 : }
3376 :
3377 14180 : string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
3378 : {
3379 14180 : }
3380 :
3381 81956 : const char *string_env_reg::get_string()
3382 : {
3383 81956 : return (curenv->*func)();
3384 : }
3385 :
3386 : class horizontal_place_reg : public general_reg {
3387 : public:
3388 : horizontal_place_reg();
3389 : bool get_value(units *);
3390 : void set_value(units);
3391 : };
3392 :
3393 1418 : horizontal_place_reg::horizontal_place_reg()
3394 : {
3395 1418 : }
3396 :
3397 0 : bool horizontal_place_reg::get_value(units *res)
3398 : {
3399 0 : *res = curenv->get_input_line_position().to_units();
3400 0 : return true;
3401 : }
3402 :
3403 0 : void horizontal_place_reg::set_value(units n)
3404 : {
3405 0 : curenv->set_input_line_position(hunits(n));
3406 0 : }
3407 :
3408 0 : int environment::get_zoom()
3409 : {
3410 0 : return env_get_zoom(this);
3411 : }
3412 :
3413 493 : int environment::get_numbering_nodes()
3414 : {
3415 493 : return (curenv->numbering_nodes ? 1 : 0);
3416 : }
3417 :
3418 3676 : const char *environment::get_font_family_string()
3419 : {
3420 3676 : return family->nm.contents();
3421 : }
3422 :
3423 3764 : const char *environment::get_stroke_color_string()
3424 : {
3425 3764 : return stroke_color->nm.contents();
3426 : }
3427 :
3428 331 : const char *environment::get_fill_color_string()
3429 : {
3430 331 : return fill_color->nm.contents();
3431 : }
3432 :
3433 2 : const char *environment::get_prev_stroke_color_string()
3434 : {
3435 2 : return prev_stroke_color->nm.contents();
3436 : }
3437 :
3438 2 : const char *environment::get_prev_fill_color_string()
3439 : {
3440 2 : return prev_fill_color->nm.contents();
3441 : }
3442 :
3443 1139 : const char *environment::get_font_name_string()
3444 : {
3445 1139 : symbol f = get_font_name(fontno, this);
3446 2278 : return f.contents();
3447 : }
3448 :
3449 2658 : const char *environment::get_style_name_string()
3450 : {
3451 2658 : symbol f = get_style_name(fontno);
3452 5316 : return f.contents();
3453 : }
3454 :
3455 66750 : const char *environment::get_name_string()
3456 : {
3457 66750 : return name.contents();
3458 : }
3459 :
3460 : // Convert a quantity in scaled points to ascii decimal fraction.
3461 :
3462 3120 : const char *sptoa(int sp)
3463 : {
3464 3120 : assert(sp > 0);
3465 3120 : assert(sizescale > 0);
3466 3120 : if (sizescale == 1)
3467 822 : return i_to_a(sp);
3468 2298 : if (sp % sizescale == 0)
3469 2016 : return i_to_a(sp/sizescale);
3470 : // See if 1/sizescale is exactly representable as a decimal fraction,
3471 : // ie its only prime factors are 2 and 5.
3472 282 : int n = sizescale;
3473 282 : int power2 = 0;
3474 1128 : while ((n & 1) == 0) {
3475 846 : n >>= 1;
3476 846 : power2++;
3477 : }
3478 282 : int power5 = 0;
3479 1128 : while ((n % 5) == 0) {
3480 846 : n /= 5;
3481 846 : power5++;
3482 : }
3483 282 : if (n == 1) {
3484 282 : int decimal_point = power5 > power2 ? power5 : power2;
3485 282 : if (decimal_point <= 10) {
3486 282 : int factor = 1;
3487 : int t;
3488 282 : for (t = decimal_point - power2; --t >= 0;)
3489 0 : factor *= 2;
3490 282 : for (t = decimal_point - power5; --t >= 0;)
3491 0 : factor *= 5;
3492 282 : if (factor == 1 || sp <= INT_MAX/factor)
3493 282 : return if_to_a(sp*factor, decimal_point);
3494 : }
3495 : }
3496 0 : double s = double(sp)/double(sizescale);
3497 0 : double factor = 10.0;
3498 0 : double val = s;
3499 0 : int decimal_point = 0;
3500 0 : do {
3501 0 : double v = ceil(s*factor);
3502 0 : if (v > INT_MAX)
3503 0 : break;
3504 0 : val = v;
3505 0 : factor *= 10.0;
3506 0 : } while (++decimal_point < 10);
3507 0 : return if_to_a(int(val), decimal_point);
3508 : }
3509 :
3510 3120 : const char *environment::get_point_size_string()
3511 : {
3512 3120 : return sptoa(curenv->get_point_size());
3513 : }
3514 :
3515 0 : const char *environment::get_requested_point_size_string()
3516 : {
3517 0 : return sptoa(curenv->get_requested_point_size());
3518 : }
3519 :
3520 2 : void environment::dump()
3521 : {
3522 : // At the time this request is invoked, the following values are zero
3523 : // or meaningless.
3524 : //
3525 : // char_height, char_slant,
3526 : // was_line_interrupted
3527 : // current_tab, tab_width, tab_distance
3528 : // has_current_field, field_distance, pre_field_width, field_spaces,
3529 : // tab_field_spaces, tab_precedes_field
3530 : // composite
3531 : //
3532 2 : if (!in_nroff_mode) {
3533 2 : errprint(" previous type size: %1p (%2s)\n",
3534 2 : prev_size.to_points(), prev_size.to_scaled_points());
3535 2 : errprint(" type size: %1p (%2s)\n",
3536 2 : size.to_points(), size.to_scaled_points());
3537 2 : errprint(" previous requested type size: %1s\n",
3538 2 : prev_requested_size);
3539 2 : errprint(" requested type size: %1s\n", requested_size);
3540 2 : font_size::dump_size_list();
3541 2 : errprint(" previous default family: '%1'\n",
3542 2 : prev_family->nm.contents());
3543 2 : errprint(" default family: '%1'\n", family->nm.contents());
3544 : }
3545 2 : errprint(" previous resolved font selection: %1 ('%2')\n",
3546 2 : prev_fontno, get_font_name(prev_fontno, this).contents());
3547 2 : errprint(" resolved font selection: %1 ('%2')\n", fontno,
3548 2 : get_font_name(fontno, this).contents());
3549 2 : errprint(" space size: %1/12 of font space width\n", space_size);
3550 2 : errprint(" sentence space size: %1/12 of font space width\n",
3551 2 : sentence_space_size);
3552 2 : errprint(" previous line length: %1u\n",
3553 2 : prev_line_length.to_units());
3554 2 : errprint(" line length: %1u\n", line_length.to_units());
3555 2 : errprint(" previous title line length: %1u\n",
3556 2 : prev_title_length.to_units());
3557 2 : errprint(" title line length: %1u\n", title_length.to_units());
3558 2 : errprint(" previous line interrupted/continued: %1\n",
3559 2 : was_previous_line_interrupted ? "yes" : "no");
3560 2 : errprint(" filling: %1\n", is_filling ? "on" : "off");
3561 2 : errprint(" alignment/adjustment: %1\n",
3562 2 : adjust_mode == ADJUST_LEFT
3563 : ? "left"
3564 2 : : adjust_mode == ADJUST_BOTH
3565 2 : ? "both"
3566 0 : : adjust_mode == ADJUST_CENTER
3567 0 : ? "center"
3568 4 : : "right");
3569 2 : if (centered_line_count > 0)
3570 0 : errprint(" lines remaining to center: %1\n", centered_line_count);
3571 2 : if (right_aligned_line_count > 0)
3572 0 : errprint(" lines remaining to right-align: %1\n",
3573 0 : right_aligned_line_count);
3574 2 : errprint(" previous vertical spacing: %1u\n",
3575 2 : prev_vertical_spacing.to_units());
3576 2 : errprint(" vertical spacing: %1u\n", vertical_spacing.to_units());
3577 2 : errprint(" previous post-vertical spacing: %1u\n",
3578 2 : prev_post_vertical_spacing.to_units());
3579 2 : errprint(" post-vertical spacing: %1u\n",
3580 2 : post_vertical_spacing.to_units());
3581 2 : errprint(" previous line spacing: %1\n", prev_line_spacing);
3582 2 : errprint(" line spacing: %1\n", line_spacing);
3583 2 : errprint(" previous indentation: %1u\n", prev_indent.to_units());
3584 2 : errprint(" indentation: %1u\n", indent.to_units());
3585 2 : errprint(" temporary indentation: %1u\n",
3586 2 : temporary_indent.to_units());
3587 2 : errprint(" temporary indentation pending: %1\n",
3588 2 : have_temporary_indent ? "yes" : "no");
3589 2 : errprint(" total indentation: %1u\n", saved_indent.to_units());
3590 2 : if (underlined_line_count > 0) {
3591 0 : errprint(" lines remaining to underline: %1\n",
3592 0 : underlined_line_count);
3593 0 : errprint(" font number before underlining: %1\n",
3594 0 : pre_underline_fontno);
3595 0 : errprint(" underlining spaces: %1\n",
3596 0 : underline_spaces ? "yes" : "no");
3597 : }
3598 2 : if (input_trap.contents() != 0 /* nullptr */) {
3599 0 : errprint(" input trap macro: '%1'\n", input_trap.contents());
3600 0 : errprint(" lines remaining until input trap springs: %1\n",
3601 0 : input_trap_count);
3602 0 : errprint(" input trap respects output line continuation: %1\n",
3603 0 : continued_input_trap ? "yes" : "no");
3604 : }
3605 2 : errprint(" previous text length: %1u\n",
3606 2 : prev_text_length.to_units());
3607 2 : if (line != 0 /* nullptr */) {
3608 0 : errprint(" text length: %1u\n", width_total.to_units());
3609 0 : errprint(" number of adjustable spaces: %1\n", space_total);
3610 : }
3611 2 : errprint(" target text length: %1u\n",
3612 2 : target_text_length.to_units());
3613 2 : errprint(" input line start: %1u\n", input_line_start.to_units());
3614 2 : errprint(" computing tab stops from: %1\n",
3615 2 : using_line_tabs ? "output line start (\"line tabs\")"
3616 2 : : "input line start");
3617 2 : errprint(" forcing adjustment: %1\n", is_spreading ? "yes" : "no");
3618 2 : if (margin_character_node != 0 /* nullptr */) {
3619 0 : errprint(" margin character flags: %1\n",
3620 0 : margin_character_flags == MC_ON
3621 : ? "on"
3622 0 : : margin_character_flags == MC_NEXT
3623 0 : ? "next"
3624 0 : : margin_character_flags == (MC_ON | MC_NEXT)
3625 0 : ? "on, next"
3626 0 : : "none");
3627 0 : errprint(" margin character distance: %1u\n",
3628 0 : margin_character_distance.to_units());
3629 : }
3630 2 : if (numbering_nodes != 0 /* nullptr */) {
3631 0 : errprint(" line number digit width: %1u\n",
3632 0 : line_number_digit_width.to_units());
3633 0 : errprint(" separation between number and text: %1 digit spaces\n",
3634 0 : number_text_separation);
3635 0 : errprint(" line number indentation: %1 digit spaces\n",
3636 0 : line_number_indent);
3637 0 : errprint(" numbering every %1 line%2\n",
3638 0 : line_number_multiple > 1
3639 0 : ? i_to_a(line_number_multiple) : "",
3640 0 : line_number_multiple > 1 ? "s" : "");
3641 0 : errprint(" lines remaining for which to suppress numbering: %1\n",
3642 0 : no_number_count);
3643 : }
3644 4 : string hf = hyphenation_mode ? "on" : "off";
3645 2 : if (hyphenation_mode & HYPHEN_NOT_LAST_LINE)
3646 0 : hf += ", not on line before vertical position trap";
3647 2 : if (hyphenation_mode & HYPHEN_LAST_CHAR)
3648 0 : hf += ", allowed before last character";
3649 2 : if (hyphenation_mode & HYPHEN_NOT_LAST_CHARS)
3650 2 : hf += ", not allowed within last two characters";
3651 2 : if (hyphenation_mode & HYPHEN_FIRST_CHAR)
3652 0 : hf += ", allowed after first character";
3653 2 : if (hyphenation_mode & HYPHEN_NOT_FIRST_CHARS)
3654 0 : hf += ", not allowed within first two characters";
3655 2 : hf += '\0';
3656 2 : errprint(" hyphenation mode: %1 (%2)\n", hyphenation_mode,
3657 2 : hf.contents());
3658 2 : errprint(" hyphenation mode default: %1\n",
3659 2 : hyphenation_mode_default);
3660 2 : errprint(" count of consecutive hyphenated lines: %1\n",
3661 2 : hyphen_line_count);
3662 2 : errprint(" consecutive hyphenated line count limit: %1%2\n",
3663 2 : hyphen_line_max, hyphen_line_max < 0 ? " (unlimited)" : "");
3664 2 : errprint(" hyphenation space: %1u\n", hyphenation_space.to_units());
3665 2 : errprint(" hyphenation margin: %1u\n",
3666 2 : hyphenation_margin.to_units());
3667 : #ifdef WIDOW_CONTROL
3668 : errprint(" widow control: %1\n", want_widow_control ? "yes" : "no");
3669 : #endif /* WIDOW_CONTROL */
3670 2 : errprint(" previous stroke color: %1\n",
3671 2 : get_prev_stroke_color_string());
3672 2 : errprint(" stroke color: %1\n", get_stroke_color_string());
3673 2 : errprint(" previous fill color: %1\n",
3674 2 : get_prev_fill_color_string());
3675 2 : errprint(" fill color: %1\n", get_fill_color_string());
3676 2 : fflush(stderr);
3677 2 : }
3678 :
3679 2 : void print_environment_request()
3680 : {
3681 2 : errprint("Current Environment:\n");
3682 2 : curenv->dump();
3683 2 : dictionary_iterator iter(env_dictionary);
3684 2 : symbol s;
3685 : environment *e;
3686 4 : while (iter.get(&s, (void **)&e)) {
3687 2 : assert(!s.is_null());
3688 2 : errprint("Environment %1:\n", s.contents());
3689 2 : if (e != curenv)
3690 0 : e->dump();
3691 : else
3692 2 : errprint(" current\n");
3693 : }
3694 2 : fflush(stderr);
3695 2 : skip_line();
3696 2 : }
3697 :
3698 0 : static void print_pending_output_line_request()
3699 : {
3700 0 : curenv->dump_pending_nodes();
3701 0 : skip_line();
3702 0 : }
3703 :
3704 : // Hyphenation - TeX's hyphenation algorithm with a less fancy
3705 : // implementation.
3706 :
3707 : struct trie_node;
3708 :
3709 : class trie {
3710 : trie_node *tp;
3711 : virtual void do_match(int, void *) = 0;
3712 : virtual void do_delete(void *) = 0;
3713 : void delete_trie_node(trie_node *);
3714 : public:
3715 1512 : trie() : tp(0 /* nullptr */) {}
3716 : virtual ~trie(); // virtual to shut up g++
3717 : void insert(const char *, int, void *);
3718 : // find calls do_match for each match it finds
3719 : void find(const char *, int);
3720 : void clear();
3721 : };
3722 :
3723 : class hyphen_trie : private trie {
3724 : int *h;
3725 : void do_match(int i, void *v);
3726 : void do_delete(void *v);
3727 : void insert_pattern(const char *, int, int *);
3728 : void insert_hyphenation(dictionary *, const char *, int);
3729 : int hpf_getc(FILE *f);
3730 : public:
3731 1512 : hyphen_trie() {}
3732 0 : ~hyphen_trie() {}
3733 : void hyphenate(const char *, int, int *);
3734 : void read_patterns_file(const char *, int, dictionary *);
3735 : };
3736 :
3737 : struct hyphenation_language {
3738 : symbol name;
3739 : dictionary exceptions;
3740 : hyphen_trie patterns;
3741 1512 : hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
3742 : ~hyphenation_language() { }
3743 : };
3744 :
3745 : dictionary language_dictionary(5);
3746 : hyphenation_language *current_language = 0 /* nullptr */;
3747 :
3748 2380 : static void select_hyphenation_language()
3749 : {
3750 2380 : if (!has_arg()) {
3751 2 : current_language = 0 /* nullptr */;
3752 2 : skip_line();
3753 2 : return;
3754 : }
3755 2378 : symbol nm = read_identifier();
3756 2378 : if (!nm.is_null()) {
3757 2378 : current_language = static_cast<hyphenation_language *>
3758 2378 : (language_dictionary.lookup(nm));
3759 2378 : if (0 /* nullptr */ == current_language) {
3760 1512 : current_language = new hyphenation_language(nm);
3761 1512 : (void) language_dictionary.lookup(nm,
3762 : static_cast<hyphenation_language *>(current_language));
3763 : }
3764 : }
3765 2378 : skip_line();
3766 : }
3767 :
3768 12620 : void environment_copy()
3769 : {
3770 12620 : if (!has_arg()) {
3771 0 : warning(WARN_MISSING, "environment copy request expects an"
3772 : " argument");
3773 0 : skip_line();
3774 0 : return;
3775 : }
3776 12620 : environment *e = 0 /* nullptr */;
3777 12620 : tok.skip_spaces();
3778 12620 : symbol nm = read_long_identifier();
3779 12620 : assert(nm != 0 /* nullptr */);
3780 12620 : e = static_cast<environment *>(env_dictionary.lookup(nm));
3781 12620 : if (e != 0 /* nullptr */)
3782 12619 : curenv->copy(e);
3783 : else
3784 1 : error("cannot copy from nonexistent environment '%1'",
3785 2 : nm.contents());
3786 12620 : skip_line();
3787 : }
3788 :
3789 37462 : void environment_switch()
3790 : {
3791 37462 : if (curenv->is_dummy()) {
3792 0 : error("cannot switch out of dummy environment");
3793 : }
3794 : else {
3795 37462 : symbol nm = read_long_identifier();
3796 37462 : if (nm.is_null()) {
3797 18524 : if (env_stack == 0 /* nullptr */)
3798 0 : error("environment stack underflow");
3799 : else {
3800 18524 : bool seen_space = curenv->seen_space;
3801 18524 : bool seen_eol = curenv->seen_eol;
3802 18524 : bool suppress_next_eol = curenv->suppress_next_eol;
3803 18524 : curenv = env_stack->env;
3804 18524 : curenv->seen_space = seen_space;
3805 18524 : curenv->seen_eol = seen_eol;
3806 18524 : curenv->suppress_next_eol = suppress_next_eol;
3807 18524 : env_list_node *tem = env_stack;
3808 18524 : env_stack = env_stack->next;
3809 18524 : delete tem;
3810 : }
3811 : }
3812 : else {
3813 : environment *e
3814 18938 : = static_cast<environment *>(env_dictionary.lookup(nm));
3815 18938 : if (!e) {
3816 1202 : e = new environment(nm);
3817 1202 : (void) env_dictionary.lookup(nm, e);
3818 : }
3819 18938 : env_stack = new env_list_node(curenv, env_stack);
3820 18938 : curenv = e;
3821 : }
3822 : }
3823 37462 : skip_line();
3824 37462 : }
3825 :
3826 : const int WORD_MAX = 256; // we use unsigned char for offsets in
3827 : // hyphenation exceptions
3828 :
3829 4 : static void add_hyphenation_exception_words_request()
3830 : {
3831 4 : if (!has_arg()) {
3832 0 : warning(WARN_MISSING, "hyphenation exception word request expects"
3833 : " one or more arguments");
3834 0 : skip_line();
3835 0 : return;
3836 : }
3837 4 : if (0 /* nullptr */ == current_language) {
3838 0 : error("cannot add hyphenation exception words when no hyphenation"
3839 : " language is selected");
3840 0 : skip_line();
3841 0 : return;
3842 : }
3843 : char buf[WORD_MAX + 1];
3844 : unsigned char pos[WORD_MAX + 2];
3845 : for (;;) {
3846 8 : if (!has_arg())
3847 4 : break;
3848 4 : int i = 0;
3849 4 : int npos = 0;
3850 62 : while ((i < WORD_MAX)
3851 66 : && !tok.is_space()
3852 66 : && !tok.is_newline()
3853 132 : && !tok.is_eof()) {
3854 62 : charinfo *ci = tok.get_charinfo(true /* required */);
3855 62 : if (0 /* nullptr */ == ci) {
3856 0 : assert(0 == "attempted to use token without charinfo in"
3857 : " hyphenation exception word");
3858 : skip_line();
3859 : return;
3860 : }
3861 62 : tok.next();
3862 62 : if (ci->get_ascii_code() == '-') {
3863 9 : if (i > 0 && (npos == 0 || pos[npos - 1] != i))
3864 9 : pos[npos++] = i;
3865 : }
3866 : else {
3867 53 : unsigned char c = ci->get_hyphenation_code();
3868 53 : if (0U == c)
3869 0 : break;
3870 53 : buf[i++] = c;
3871 : }
3872 : }
3873 4 : if (i > 0) {
3874 4 : pos[npos] = 0;
3875 4 : buf[i] = '\0';
3876 : // C++03: new unsigned char[npos + 1]();
3877 4 : unsigned char *tem = new unsigned char[npos + 1];
3878 4 : (void) memset(tem, 0, ((npos + 1) * sizeof(unsigned char)));
3879 4 : memcpy(tem, pos, npos + 1);
3880 : tem = static_cast<unsigned char *>
3881 4 : (current_language->exceptions.lookup(symbol(buf), tem));
3882 4 : if (tem)
3883 0 : delete[] tem;
3884 : }
3885 4 : }
3886 4 : skip_line();
3887 : }
3888 :
3889 0 : static void print_hyphenation_exceptions()
3890 : {
3891 0 : if (0 /* nullptr */ == current_language)
3892 0 : return;
3893 0 : dictionary_iterator iter(current_language->exceptions);
3894 0 : symbol entry;
3895 : unsigned char *hypoint;
3896 : // Pathologically, we could have a hyphenation point after every
3897 : // character in a word except the last. The word may have a trailing
3898 : // space; see `hyphen_trie::read_patterns_file()`.
3899 0 : const size_t bufsz = WORD_MAX * 2;
3900 : char wordbuf[bufsz]; // need to `errprint()` it, so not `unsigned`
3901 : // We must use the nuclear `reinterpret_cast` operator because GNU
3902 : // troff's dictionary types use a pre-STL approach to containers.
3903 0 : while (iter.get(&entry, reinterpret_cast<void **>(&hypoint))) {
3904 0 : assert(!entry.is_null());
3905 0 : assert(hypoint != 0 /* nullptr */);
3906 0 : string word = entry.contents();
3907 0 : (void) memset(wordbuf, '\0', bufsz);
3908 0 : size_t i = 0, j = 0, len = word.length();
3909 0 : bool is_mode_dependent = false;
3910 0 : while (i < len) {
3911 0 : if ((hypoint != 0 /* nullptr */) && (*hypoint == i)) {
3912 0 : wordbuf[j++] = '-';
3913 0 : hypoint++;
3914 : }
3915 0 : if (word[i] == ' ') {
3916 0 : assert(i == (len - 1));
3917 0 : is_mode_dependent = true;
3918 : }
3919 0 : wordbuf[j++] = word[i++];
3920 : }
3921 0 : errprint("%1", wordbuf);
3922 0 : if (is_mode_dependent)
3923 0 : errprint("\t*");
3924 0 : errprint("\n");
3925 : }
3926 0 : fflush(stderr);
3927 0 : skip_line();
3928 : }
3929 :
3930 : struct trie_node {
3931 : char c;
3932 : trie_node *down;
3933 : trie_node *right;
3934 : void *val;
3935 : trie_node(char, trie_node *);
3936 : };
3937 :
3938 12928892 : trie_node::trie_node(char ch, trie_node *p)
3939 12928892 : : c(ch), down(0), right(p), val(0)
3940 : {
3941 12928892 : }
3942 :
3943 0 : trie::~trie()
3944 : {
3945 0 : clear();
3946 0 : }
3947 :
3948 1534 : void trie::clear()
3949 : {
3950 1534 : delete_trie_node(tp);
3951 1534 : tp = 0;
3952 1534 : }
3953 :
3954 :
3955 404638 : void trie::delete_trie_node(trie_node *p)
3956 : {
3957 404638 : if (p) {
3958 201552 : delete_trie_node(p->down);
3959 201552 : delete_trie_node(p->right);
3960 201552 : if (p->val)
3961 118512 : do_delete(p->val);
3962 201552 : delete p;
3963 : }
3964 404638 : }
3965 :
3966 7660772 : void trie::insert(const char *pat, int patlen, void *val)
3967 : {
3968 7660772 : trie_node **p = &tp;
3969 7660772 : assert(patlen > 0 && pat != 0);
3970 : for (;;) {
3971 228460644 : while (*p != 0 && (*p)->c < pat[0])
3972 196009964 : p = &((*p)->right);
3973 32450680 : if (*p == 0 || (*p)->c != pat[0])
3974 12928892 : *p = new trie_node(pat[0], *p);
3975 32450680 : if (--patlen == 0) {
3976 7660772 : (*p)->val = val;
3977 7660772 : break;
3978 : }
3979 24789908 : ++pat;
3980 24789908 : p = &((*p)->down);
3981 : }
3982 7660772 : }
3983 :
3984 159336 : void trie::find(const char *pat, int patlen)
3985 : {
3986 159336 : trie_node *p = tp;
3987 600433 : for (int i = 0; p != 0 && i < patlen; i++) {
3988 4236189 : while (p != 0 && p->c < pat[i])
3989 3660010 : p = p->right;
3990 576179 : if (p != 0 && p->c == pat[i]) {
3991 441097 : if (p->val != 0)
3992 92491 : do_match(i+1, p->val);
3993 441097 : p = p->down;
3994 : }
3995 : else
3996 : break;
3997 : }
3998 159336 : }
3999 :
4000 : struct operation {
4001 : operation *next;
4002 : short distance;
4003 : short num;
4004 : operation(int, int, operation *);
4005 : };
4006 :
4007 8900388 : operation::operation(int i, int j, operation *op)
4008 8900388 : : next(op), distance(j), num(i)
4009 : {
4010 8900388 : }
4011 :
4012 7660772 : void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
4013 : {
4014 7660772 : operation *op = 0;
4015 47772224 : for (int i = 0; i < patlen+1; i++)
4016 40111452 : if (num[i] != 0)
4017 8900388 : op = new operation(num[i], patlen - i, op);
4018 7660772 : insert(pat, patlen, op);
4019 7660772 : }
4020 :
4021 162330 : void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
4022 : int patlen)
4023 : {
4024 : char buf[WORD_MAX + 2];
4025 : unsigned char pos[WORD_MAX + 2];
4026 162330 : int i = 0, j = 0;
4027 162330 : int npos = 0;
4028 2209638 : while (j < patlen) {
4029 2047308 : unsigned char c = pat[j++];
4030 2047308 : if (c == '-') {
4031 214024 : if (i > 0 && (npos == 0 || pos[npos - 1] != i))
4032 214024 : pos[npos++] = i;
4033 : }
4034 1833284 : else if (c == ' ')
4035 162330 : buf[i++] = ' ';
4036 : else
4037 1670954 : buf[i++] = hpf_code_table[c];
4038 : }
4039 162330 : if (i > 0) {
4040 162330 : pos[npos] = 0;
4041 162330 : buf[i] = '\0';
4042 : // C++03: new unsigned char[npos + 1]();
4043 162330 : unsigned char *tem = new unsigned char[npos + 1];
4044 162330 : (void) memset(tem, 0, ((npos + 1) * sizeof(unsigned char)));
4045 162330 : memcpy(tem, pos, npos + 1);
4046 162330 : tem = static_cast<unsigned char *>(ex->lookup(symbol(buf), tem));
4047 162330 : if (0 /* nullptr */ == tem)
4048 159666 : delete[] tem;
4049 : }
4050 162330 : }
4051 :
4052 19065 : void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
4053 : {
4054 : int j;
4055 216531 : for (j = 0; j < len + 1; j++)
4056 197466 : hyphens[j] = 0;
4057 178401 : for (j = 0; j < len - 1; j++) {
4058 159336 : h = hyphens + j;
4059 159336 : find(word + j, len - j);
4060 : }
4061 19065 : }
4062 :
4063 111411 : inline int max(int m, int n)
4064 : {
4065 111411 : return m > n ? m : n;
4066 : }
4067 :
4068 92491 : void hyphen_trie::do_match(int i, void *v)
4069 : {
4070 92491 : operation *op = static_cast<operation *>(v);
4071 203902 : while (op != 0) {
4072 111411 : h[i - op->distance] = max(h[i - op->distance], op->num);
4073 111411 : op = op->next;
4074 : }
4075 92491 : }
4076 :
4077 118512 : void hyphen_trie::do_delete(void *v)
4078 : {
4079 118512 : operation *op = static_cast<operation *>(v);
4080 255096 : while (op) {
4081 136584 : operation *tem = op;
4082 136584 : op = tem->next;
4083 136584 : delete tem;
4084 : }
4085 118512 : }
4086 :
4087 : /* We use very simple rules to parse TeX's hyphenation patterns.
4088 :
4089 : . '%' starts a comment even if preceded by '\'.
4090 :
4091 : . No support for digraphs and like '\$'.
4092 :
4093 : . '^^xx' ('x' is 0-9 or a-f), and '^^x' (character code of 'x' in the
4094 : range 0-127) are recognized; other use of '^' causes an error.
4095 :
4096 : . No macro expansion.
4097 :
4098 : . We check for the expression '\patterns{...}' (possibly with
4099 : whitespace before and after the braces). Everything between the
4100 : braces is taken as hyphenation patterns. Consequently, '{' and '}'
4101 : are not allowed in patterns.
4102 :
4103 : . Similarly, '\hyphenation{...}' gives a list of hyphenation
4104 : exceptions.
4105 :
4106 : . '\endinput' is recognized also.
4107 :
4108 : . For backward compatibility, if '\patterns' is missing, the
4109 : whole file is treated as a list of hyphenation patterns (only
4110 : recognizing '%' as the start of a comment.
4111 :
4112 : */
4113 :
4114 51700806 : int hyphen_trie::hpf_getc(FILE *f)
4115 : {
4116 51700806 : int c = getc(f);
4117 : int c1;
4118 51700806 : int cc = 0;
4119 51700806 : if (c != '^')
4120 51683544 : return c;
4121 17262 : c = getc(f);
4122 17262 : if (c != '^')
4123 0 : goto fail;
4124 17262 : c = getc(f);
4125 17262 : c1 = getc(f);
4126 17262 : if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
4127 17262 : && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
4128 17262 : if (c >= '0' && c <= '9')
4129 0 : c -= '0';
4130 : else
4131 17262 : c = c - 'a' + 10;
4132 17262 : if (c1 >= '0' && c1 <= '9')
4133 17262 : c1 -= '0';
4134 : else
4135 0 : c1 = c1 - 'a' + 10;
4136 17262 : cc = c * 16 + c1;
4137 : }
4138 : else {
4139 0 : ungetc(c1, f);
4140 0 : if (c >= 0 && c <= 63)
4141 0 : cc = c + 64;
4142 0 : else if (c >= 64 && c <= 127)
4143 0 : cc = c - 64;
4144 : else
4145 0 : goto fail;
4146 : }
4147 17262 : return cc;
4148 0 : fail:
4149 0 : error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
4150 0 : return c;
4151 : }
4152 :
4153 2988 : void hyphen_trie::read_patterns_file(const char *name, int append,
4154 : dictionary *ex)
4155 : {
4156 2988 : if (!append)
4157 1534 : clear();
4158 : char buf[WORD_MAX + 1];
4159 770904 : for (int i = 0; i < WORD_MAX + 1; i++)
4160 767916 : buf[i] = 0;
4161 : int num[WORD_MAX + 1];
4162 2988 : errno = 0;
4163 2988 : char *path = 0;
4164 2988 : FILE *fp = mac_path->open_file(name, &path);
4165 2988 : if (0 /* nullptr */ == fp) {
4166 0 : error("cannot open hyphenation pattern file '%1': %2", name,
4167 0 : strerror(errno));
4168 0 : return;
4169 : }
4170 2988 : int c = hpf_getc(fp);
4171 2988 : bool have_patterns = false; // seen \patterns
4172 2988 : bool is_final_pattern = false; // have a trailing closing brace
4173 2988 : bool have_hyphenation = false; // seen \hyphenation
4174 2988 : bool is_final_hyphenation = false; // have a trailing closing brace
4175 2988 : bool have_keyword = false; // seen \patterns or \hyphenation
4176 2988 : bool is_traditional = false; // don't handle \patterns
4177 : for (;;) {
4178 : for (;;) {
4179 16215612 : if (c == '%') { // skip comments
4180 4344826 : do {
4181 4464958 : c = getc(fp);
4182 4464958 : } while (c != EOF && c != '\n');
4183 : }
4184 16215612 : if (c == EOF || !csspace(c))
4185 7838470 : break;
4186 8377142 : c = hpf_getc(fp);
4187 : }
4188 7838470 : if (c == EOF) {
4189 2962 : if (have_keyword || is_traditional) // we are done
4190 : break;
4191 : else { // rescan file in 'is_traditional' mode
4192 0 : rewind(fp);
4193 0 : is_traditional = true;
4194 0 : c = hpf_getc(fp);
4195 0 : continue;
4196 : }
4197 : }
4198 7835508 : int i = 0;
4199 7835508 : num[0] = 0;
4200 7835508 : if (!(c == '{' || c == '}')) { // skip braces at line start
4201 35479248 : do { // scan patterns
4202 43308836 : if (csdigit(c))
4203 8900890 : num[i] = c - '0';
4204 : else {
4205 34407946 : buf[i++] = c;
4206 34407946 : num[i] = 0;
4207 : }
4208 43308836 : c = hpf_getc(fp);
4209 43308836 : } while (i < WORD_MAX && c != EOF && !csspace(c)
4210 86617672 : && c != '%' && c != '{' && c != '}');
4211 : }
4212 7835508 : if (!is_traditional) {
4213 7835508 : if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
4214 1534 : while (csspace(c))
4215 0 : c = hpf_getc(fp);
4216 1534 : if (c == '{') {
4217 1534 : if (have_patterns || have_hyphenation)
4218 0 : error("\\patterns is not allowed inside of %1 group",
4219 0 : have_patterns ? "\\patterns" : "\\hyphenation");
4220 : else {
4221 1534 : have_patterns = true;
4222 1534 : have_keyword = true;
4223 : }
4224 1534 : c = hpf_getc(fp);
4225 1534 : continue;
4226 : }
4227 : }
4228 7833974 : else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
4229 4350 : while (csspace(c))
4230 0 : c = hpf_getc(fp);
4231 4350 : if (c == '{') {
4232 4350 : if (have_patterns || have_hyphenation)
4233 0 : error("\\hyphenation is not allowed inside of %1 group",
4234 0 : have_patterns ? "\\patterns" : "\\hyphenation");
4235 : else {
4236 4350 : have_hyphenation = true;
4237 4350 : have_keyword = true;
4238 : }
4239 4350 : c = hpf_getc(fp);
4240 4350 : continue;
4241 : }
4242 : }
4243 7829624 : else if (strstr(buf, "\\endinput")) {
4244 26 : if (have_patterns || have_hyphenation)
4245 0 : error("found \\endinput inside of %1 group",
4246 0 : have_patterns ? "\\patterns" : "\\hyphenation");
4247 26 : break;
4248 : }
4249 7829598 : else if (c == '}') {
4250 5920 : if (have_patterns) {
4251 1534 : have_patterns = false;
4252 1534 : if (i > 0)
4253 0 : is_final_pattern = true;
4254 : }
4255 4386 : else if (have_hyphenation) {
4256 4350 : have_hyphenation = false;
4257 4350 : if (i > 0)
4258 0 : is_final_hyphenation = true;
4259 : }
4260 5920 : c = hpf_getc(fp);
4261 : }
4262 7823678 : else if (c == '{') {
4263 36 : if (have_patterns || have_hyphenation)
4264 0 : error("'{' is not allowed within %1 group",
4265 0 : have_patterns ? "\\patterns" : "\\hyphenation");
4266 36 : c = hpf_getc(fp); // skipped if not starting
4267 : // \patterns or \hyphenation
4268 : }
4269 : }
4270 : else {
4271 0 : if (c == '{' || c == '}')
4272 0 : c = hpf_getc(fp);
4273 : }
4274 7829598 : if (i > 0) {
4275 7823678 : if (have_patterns || is_final_pattern || is_traditional) {
4276 40111452 : for (int j = 0; j < i; j++)
4277 32450680 : buf[j] = hpf_code_table[static_cast<unsigned char>(buf[j])];
4278 7660772 : insert_pattern(buf, i, num);
4279 7660772 : is_final_pattern = false;
4280 : }
4281 162906 : else if (have_hyphenation || is_final_hyphenation) {
4282 : // hyphenation exceptions in a pattern file are subject to `.hy'
4283 : // restrictions; we mark such entries with a trailing space
4284 162330 : buf[i++] = ' ';
4285 162330 : insert_hyphenation(ex, buf, i);
4286 162330 : is_final_hyphenation = false;
4287 : }
4288 : }
4289 7835482 : }
4290 2988 : fclose(fp);
4291 2988 : free(path);
4292 2988 : return;
4293 : }
4294 :
4295 : class hyphenation_language_reg : public reg {
4296 : public:
4297 : const char *get_string();
4298 : };
4299 :
4300 491 : const char *hyphenation_language_reg::get_string()
4301 : {
4302 491 : return (current_language != 0 /* nullptr */)
4303 491 : ? current_language->name.contents() : "";
4304 : }
4305 :
4306 : class hyphenation_default_mode_reg : public reg {
4307 : public:
4308 : const char *get_string();
4309 : };
4310 :
4311 0 : const char *hyphenation_default_mode_reg::get_string()
4312 : {
4313 0 : return i_to_a(curenv->get_hyphenation_mode_default());
4314 : }
4315 :
4316 : #define init_int_env_reg(name, func) \
4317 : register_dictionary.define(name, new int_env_reg(&environment::func))
4318 :
4319 : #define init_unsigned_env_reg(name, func) \
4320 : register_dictionary.define(name, new unsigned_env_reg(&environment::func))
4321 :
4322 : #define init_vunits_env_reg(name, func) \
4323 : register_dictionary.define(name, new vunits_env_reg(&environment::func))
4324 :
4325 : #define init_hunits_env_reg(name, func) \
4326 : register_dictionary.define(name, new hunits_env_reg(&environment::func))
4327 :
4328 : #define init_string_env_reg(name, func) \
4329 : register_dictionary.define(name, new string_env_reg(&environment::func))
4330 :
4331 : // Most hyphenation functionality is environment-specific; see
4332 : // init_hyphenation_pattern_requests() below for globally managed state.
4333 1418 : void init_env_requests()
4334 : {
4335 1418 : init_request("ad", adjust);
4336 1418 : init_request("br", break_without_forced_adjustment_request);
4337 1418 : init_request("brp", break_with_forced_adjustment_request);
4338 1418 : init_request("ce", center);
4339 1418 : init_request("cu", continuous_underline);
4340 1418 : init_request("ev", environment_switch);
4341 1418 : init_request("evc", environment_copy);
4342 1418 : init_request("fam", family_change);
4343 1418 : init_request("fc", field_characters_request);
4344 1418 : init_request("fi", fill);
4345 1418 : init_request("fcolor", select_fill_color_request);
4346 1418 : init_request("ft", select_font_request);
4347 1418 : init_request("gcolor", select_stroke_color_request);
4348 1418 : init_request("hc", hyphenation_character_request);
4349 1418 : init_request("hla", select_hyphenation_language);
4350 1418 : init_request("hlm", hyphen_line_max_request);
4351 1418 : init_request("hy", hyphenate_request);
4352 1418 : init_request("hydefault", set_hyphenation_mode_default);
4353 1418 : init_request("hym", hyphenation_margin_request);
4354 1418 : init_request("hys", hyphenation_space_request);
4355 1418 : init_request("in", indent);
4356 1418 : init_request("it", input_trap);
4357 1418 : init_request("itc", input_trap_continued);
4358 1418 : init_request("lc", leader_character_request);
4359 1418 : init_request("linetabs", line_tabs_request);
4360 1418 : init_request("ll", line_length);
4361 1418 : init_request("ls", line_spacing);
4362 1418 : init_request("lt", title_length);
4363 1418 : init_request("mc", margin_character);
4364 1418 : init_request("na", no_adjust);
4365 1418 : init_request("nf", no_fill);
4366 1418 : init_request("nh", no_hyphenate);
4367 1418 : init_request("nm", number_lines);
4368 1418 : init_request("nn", no_number);
4369 1418 : init_request("pev", print_environment_request);
4370 1418 : init_request("pline", print_pending_output_line_request);
4371 1418 : init_request("ps", point_size);
4372 1418 : init_request("pvs", post_vertical_spacing);
4373 1418 : init_request("rj", right_justify);
4374 1418 : init_request("sizes", override_available_type_sizes_request);
4375 1418 : init_request("ss", space_size);
4376 1418 : init_request("ta", configure_tab_stops_request);
4377 1418 : init_request("ti", temporary_indent);
4378 1418 : init_request("tc", tab_character_request);
4379 1418 : init_request("tl", title);
4380 1418 : init_request("ul", underline);
4381 1418 : init_request("vs", vertical_spacing);
4382 : #ifdef WIDOW_CONTROL
4383 : init_request("wdc", widow_control_request);
4384 : #endif /* WIDOW_CONTROL */
4385 1418 : init_hunits_env_reg(".b", get_emboldening_offset);
4386 1418 : init_vunits_env_reg(".cdp", get_prev_char_depth);
4387 1418 : init_int_env_reg(".ce", get_centered_line_count);
4388 1418 : init_vunits_env_reg(".cht", get_prev_char_height);
4389 1418 : init_hunits_env_reg(".csk", get_prev_char_skew);
4390 1418 : init_string_env_reg(".ev", get_name_string);
4391 1418 : init_int_env_reg(".f", get_font);
4392 1418 : init_string_env_reg(".fam", get_font_family_string);
4393 1418 : init_string_env_reg(".fn", get_font_name_string);
4394 1418 : init_int_env_reg(".height", get_char_height);
4395 1418 : register_dictionary.define(".hla", new hyphenation_language_reg);
4396 1418 : init_int_env_reg(".hlc", get_hyphen_line_count);
4397 1418 : init_int_env_reg(".hlm", get_hyphen_line_max);
4398 1418 : init_unsigned_env_reg(".hy", get_hyphenation_mode);
4399 1418 : init_unsigned_env_reg(".hydefault", get_hyphenation_mode_default);
4400 1418 : init_hunits_env_reg(".hym", get_hyphenation_margin);
4401 1418 : init_hunits_env_reg(".hys", get_hyphenation_space);
4402 1418 : init_hunits_env_reg(".i", get_indent);
4403 1418 : init_hunits_env_reg(".in", get_saved_indent);
4404 1418 : init_int_env_reg(".int", get_was_previous_line_interrupted);
4405 1418 : init_int_env_reg(".it", get_input_trap_line_count);
4406 1418 : init_int_env_reg(".itc", get_input_trap_respects_continuation);
4407 1418 : init_string_env_reg(".itm", get_input_trap_macro);
4408 1418 : init_int_env_reg(".linetabs", is_using_line_tabs);
4409 1418 : init_hunits_env_reg(".lt", get_title_length);
4410 1418 : init_unsigned_env_reg(".j", get_adjust_mode);
4411 1418 : init_hunits_env_reg(".k", get_text_length);
4412 1418 : init_int_env_reg(".L", get_line_spacing);
4413 1418 : init_hunits_env_reg(".l", get_line_length);
4414 1418 : init_hunits_env_reg(".ll", get_saved_line_length);
4415 1418 : init_string_env_reg(".M", get_fill_color_string);
4416 1418 : init_string_env_reg(".m", get_stroke_color_string);
4417 1418 : init_hunits_env_reg(".n", get_prev_text_length);
4418 1418 : init_int_env_reg(".nm", get_numbering_nodes);
4419 1418 : init_int_env_reg(".nn", get_no_number_count);
4420 1418 : init_int_env_reg(".ps", get_point_size);
4421 1418 : init_int_env_reg(".psr", get_requested_point_size);
4422 1418 : init_vunits_env_reg(".pvs", get_post_vertical_spacing);
4423 1418 : init_int_env_reg(".rj", get_right_aligned_line_count);
4424 1418 : init_string_env_reg(".s", get_point_size_string);
4425 1418 : init_int_env_reg(".slant", get_char_slant);
4426 1418 : init_int_env_reg(".ss", get_space_size);
4427 1418 : init_int_env_reg(".sss", get_sentence_space_size);
4428 1418 : init_string_env_reg(".sr", get_requested_point_size_string);
4429 1418 : init_string_env_reg(".sty", get_style_name_string);
4430 1418 : init_string_env_reg(".tabs", get_tabs);
4431 1418 : init_int_env_reg(".u", get_fill);
4432 1418 : init_vunits_env_reg(".v", get_vertical_spacing);
4433 1418 : init_hunits_env_reg(".w", get_prev_char_width);
4434 1418 : init_int_env_reg(".zoom", get_zoom);
4435 1418 : register_dictionary.define("ct", new variable_reg(&ct_reg_contents));
4436 1418 : register_dictionary.define("hp", new horizontal_place_reg);
4437 1418 : register_dictionary.define("ln", new variable_reg(&next_line_number));
4438 1418 : register_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
4439 1418 : register_dictionary.define("rst", new variable_reg(&rst_reg_contents));
4440 1418 : register_dictionary.define("sb", new variable_reg(&sb_reg_contents));
4441 1418 : register_dictionary.define("skw", new variable_reg(&skw_reg_contents));
4442 1418 : register_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
4443 1418 : register_dictionary.define("st", new variable_reg(&st_reg_contents));
4444 : // TODO: Kill the following off in groff 1.24.0 release + 2 years.
4445 1418 : deprecated_font_identifiers.push_back("AX");
4446 1418 : deprecated_font_identifiers.push_back("KR");
4447 1418 : deprecated_font_identifiers.push_back("KI");
4448 1418 : deprecated_font_identifiers.push_back("KB");
4449 1418 : deprecated_font_identifiers.push_back("KX");
4450 1418 : deprecated_font_identifiers.push_back("CW");
4451 1418 : deprecated_font_identifiers.push_back("C");
4452 1418 : deprecated_font_identifiers.push_back("CO");
4453 1418 : deprecated_font_identifiers.push_back("CX");
4454 1418 : deprecated_font_identifiers.push_back("H");
4455 1418 : deprecated_font_identifiers.push_back("HO");
4456 1418 : deprecated_font_identifiers.push_back("HX");
4457 1418 : deprecated_font_identifiers.push_back("Hr");
4458 1418 : deprecated_font_identifiers.push_back("Hi");
4459 1418 : deprecated_font_identifiers.push_back("Hb");
4460 1418 : deprecated_font_identifiers.push_back("Hx");
4461 1418 : deprecated_font_identifiers.push_back("PA");
4462 1418 : deprecated_font_identifiers.push_back("PX");
4463 1418 : deprecated_font_identifiers.push_back("NX");
4464 1418 : deprecated_font_identifiers.push_back("ZI");
4465 1418 : }
4466 :
4467 : // Appendix H of _The TeXbook_ is useful background for the following.
4468 :
4469 18530 : static void hyphenate(hyphen_list *h, unsigned int flags)
4470 : {
4471 18530 : if (0 /* nullptr */ == current_language)
4472 6 : return;
4473 43349 : while (h != 0 /* nullptr */) {
4474 34845 : while ((h != 0 /* nullptr */) && (0U == h->hyphenation_code))
4475 10020 : h = h->next;
4476 24825 : int len = 0;
4477 : // Locate hyphenable points within a (subset of) an input word.
4478 : //
4479 : // We first look up the word in the environment's hyphenation
4480 : // exceptions dictionary; its keys are C strings, so the buffer
4481 : // `hbuf` that holds our word needs to be null terminated and we
4482 : // allocate a byte for that. If the lookup fails, we apply the
4483 : // hyphenation patterns, which require that the word be bracketed at
4484 : // each end with a dot ('.'), so we allocate two further bytes.
4485 : //
4486 : // `hbuf` can be thought of as a mapping of the letters of the input
4487 : // word to the hyphenation codes that correspond to each letter.
4488 : // The hyphenation codes are normalized; "AbBoT" becomes "abbot".
4489 : //
4490 : // We can hyphenate words longer than WORD_MAX, but WORD_MAX is the
4491 : // maximum size of the "window" inside a word within which we apply
4492 : // the hyphenation patterns to determine a break point.
4493 : char hbuf[WORD_MAX + 2 + 1];
4494 24825 : (void) memset(hbuf, '\0', sizeof hbuf);
4495 24825 : char *bufp = hbuf + 1;
4496 : hyphen_list *tem;
4497 166506 : for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
4498 147982 : if (tem->hyphenation_code != 0U)
4499 141681 : bufp[len++] = tem->hyphenation_code;
4500 : else
4501 6301 : break;
4502 : }
4503 24825 : hyphen_list *nexth = tem;
4504 24825 : if (len >= 2) {
4505 : // Check hyphenation exceptions defined with `hw` request.
4506 19199 : assert((bufp + len) < (hbuf + sizeof hbuf));
4507 19199 : bufp[len] = '\0';
4508 : unsigned char *pos = static_cast<unsigned char *>(
4509 19199 : current_language->exceptions.lookup(bufp));
4510 19199 : if (pos != 0 /* nullptr */) {
4511 1 : int j = 0;
4512 1 : int i = 1;
4513 27 : for (tem = h; tem != 0 /* nullptr */; tem = tem->next, i++)
4514 26 : if (pos[j] == i) {
4515 6 : tem->is_hyphen = true;
4516 6 : j++;
4517 : }
4518 : }
4519 : else {
4520 : // Check `\hyphenation' entries from pattern files; such entries
4521 : // are marked with a trailing space.
4522 19198 : assert((hbuf + len + 1) < (hbuf + sizeof hbuf));
4523 19198 : bufp[len] = ' ';
4524 19198 : bufp[len + 1] = '\0';
4525 : pos = static_cast<unsigned char *>(
4526 19198 : current_language->exceptions.lookup(bufp));
4527 19198 : if (pos != 0 /* nullptr */) {
4528 133 : int j = 0;
4529 133 : int i = 1;
4530 133 : tem = h;
4531 133 : if (pos[j] == i) {
4532 0 : if (flags & HYPHEN_FIRST_CHAR)
4533 0 : tem->is_hyphen = true;
4534 0 : j++;
4535 : }
4536 133 : tem = tem->next;
4537 133 : i++;
4538 133 : if (pos[j] == i) {
4539 53 : if (!(flags & HYPHEN_NOT_FIRST_CHARS))
4540 53 : tem->is_hyphen = true;
4541 53 : j++;
4542 : }
4543 133 : tem = tem->next;
4544 133 : i++;
4545 133 : if (!(flags & HYPHEN_LAST_CHAR))
4546 133 : --len;
4547 133 : if (flags & HYPHEN_NOT_LAST_CHARS)
4548 133 : --len;
4549 492 : for (; i < len && tem; tem = tem->next, i++)
4550 359 : if (pos[j] == i) {
4551 77 : tem->is_hyphen = true;
4552 77 : j++;
4553 : }
4554 : }
4555 : else {
4556 19065 : hbuf[0] = hbuf[len + 1] = '.';
4557 : int num[WORD_MAX + 2 + 1];
4558 19065 : (void) memset(num, 0, sizeof num);
4559 19065 : current_language->patterns.hyphenate(hbuf, len + 2, num);
4560 : // The position of a hyphenation point gets marked with an odd
4561 : // number. Example:
4562 : //
4563 : // hbuf: . h e l p f u l .
4564 : // num: 0 0 0 2 4 3 0 0 0 0
4565 19065 : if (!(flags & HYPHEN_FIRST_CHAR))
4566 19050 : num[2] = 0;
4567 19065 : if (flags & HYPHEN_NOT_FIRST_CHARS)
4568 983 : num[3] = 0;
4569 19065 : if (flags & HYPHEN_LAST_CHAR)
4570 0 : ++len;
4571 19065 : if (flags & HYPHEN_NOT_LAST_CHARS)
4572 19018 : --len;
4573 : int i;
4574 102422 : for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
4575 83357 : if (num[i] & 1)
4576 21268 : tem->is_hyphen = true;
4577 : }
4578 : }
4579 : }
4580 24825 : h = nexth;
4581 : }
4582 : }
4583 :
4584 2988 : static void read_hyphenation_patterns_from_file(bool append)
4585 : {
4586 2988 : char *filename = read_rest_of_line_as_argument();
4587 2988 : if (filename != 0 /* nullptr */) {
4588 2988 : if (0 /* nullptr */ == current_language)
4589 0 : error("no current hyphenation language");
4590 : else
4591 2988 : current_language->patterns.read_patterns_file(filename, append,
4592 2988 : ¤t_language->exceptions);
4593 : }
4594 2988 : tok.next();
4595 2988 : }
4596 :
4597 1534 : static void load_hyphenation_patterns_from_file()
4598 : {
4599 1534 : if (!has_arg(true /* peek */)) {
4600 0 : warning(WARN_MISSING, "hyphenation pattern load request expects"
4601 : " argument");
4602 0 : skip_line();
4603 0 : return;
4604 : }
4605 1534 : read_hyphenation_patterns_from_file(false /* append */);
4606 : // No `skip_line()` here; the above function calls
4607 : // `read_rest_of_line_as_argument()` and `tok.next()`.
4608 : }
4609 :
4610 1454 : static void append_hyphenation_patterns_from_file()
4611 : {
4612 1454 : if (!has_arg(true /* peek */)) {
4613 0 : warning(WARN_MISSING, "hyphenation pattern appendment request"
4614 : " expects argument");
4615 0 : skip_line();
4616 0 : return;
4617 : }
4618 1454 : read_hyphenation_patterns_from_file(true /* append */);
4619 : // No `skip_line()` here; the above function calls
4620 : // `read_rest_of_line_as_argument()` and `tok.next()`.
4621 : }
4622 :
4623 : // Most hyphenation functionality is environment-specific; see
4624 : // init_env_requests() above.
4625 1418 : void init_hyphenation_pattern_requests()
4626 : {
4627 1418 : init_request("hpf", load_hyphenation_patterns_from_file);
4628 1418 : init_request("hpfa", append_hyphenation_patterns_from_file);
4629 1418 : init_request("hw", add_hyphenation_exception_words_request);
4630 1418 : init_request("phw", print_hyphenation_exceptions);
4631 1418 : }
4632 :
4633 : // Local Variables:
4634 : // fill-column: 72
4635 : // mode: C++
4636 : // End:
4637 : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
|