LCOV - code coverage report
Current view: top level - roff/troff - env.cpp (source / functions) Hit Total Coverage
Test: GNU roff Lines: 2283 2820 81.0 %
Date: 2026-01-16 17:51:41 Functions: 196 231 84.8 %
Legend: Lines: hit not hit

          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 :         &current_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:

Generated by: LCOV version 1.14