1 /*      $Id: term.c,v 1.214 2013/12/25 00:39:31 schwarze Exp $ */
   2 /*
   3  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
   4  * Copyright (c) 2010, 2011, 2012, 2013 Ingo Schwarze <schwarze@openbsd.org>
   5  *
   6  * Permission to use, copy, modify, and distribute this software for any
   7  * purpose with or without fee is hereby granted, provided that the above
   8  * copyright notice and this permission notice appear in all copies.
   9  *
  10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  17  */
  18 #ifdef HAVE_CONFIG_H
  19 #include "config.h"
  20 #endif
  21 
  22 #include <sys/types.h>
  23 
  24 #include <assert.h>
  25 #include <ctype.h>
  26 #include <stdint.h>
  27 #include <stdio.h>
  28 #include <stdlib.h>
  29 #include <string.h>
  30 
  31 #include "mandoc.h"
  32 #include "out.h"
  33 #include "term.h"
  34 #include "main.h"
  35 
  36 static  size_t           cond_width(const struct termp *, int, int *);
  37 static  void             adjbuf(struct termp *p, size_t);
  38 static  void             bufferc(struct termp *, char);
  39 static  void             encode(struct termp *, const char *, size_t);
  40 static  void             encode1(struct termp *, int);
  41 
  42 void
  43 term_free(struct termp *p)
  44 {
  45 
  46         if (p->buf)
  47                 free(p->buf);
  48         if (p->symtab)
  49                 mchars_free(p->symtab);
  50 
  51         free(p);
  52 }
  53 
  54 
  55 void
  56 term_begin(struct termp *p, term_margin head, 
  57                 term_margin foot, const void *arg)
  58 {
  59 
  60         p->headf = head;
  61         p->footf = foot;
  62         p->argf = arg;
  63         (*p->begin)(p);
  64 }
  65 
  66 
  67 void
  68 term_end(struct termp *p)
  69 {
  70 
  71         (*p->end)(p);
  72 }
  73 
  74 /*
  75  * Flush a line of text.  A "line" is loosely defined as being something
  76  * that should be followed by a newline, regardless of whether it's
  77  * broken apart by newlines getting there.  A line can also be a
  78  * fragment of a columnar list (`Bl -tag' or `Bl -column'), which does
  79  * not have a trailing newline.
  80  *
  81  * The following flags may be specified:
  82  *
  83  *  - TERMP_NOBREAK: this is the most important and is used when making
  84  *    columns.  In short: don't print a newline and instead expect the
  85  *    next call to do the padding up to the start of the next column.
  86  *    p->trailspace may be set to 0, 1, or 2, depending on how many
  87  *    space characters are required at the end of the column.
  88  *
  89  *  - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
  90  *    the line is overrun, and don't pad-right if it's underrun.
  91  *
  92  *  - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
  93  *    overrunning, instead save the position and continue at that point
  94  *    when the next invocation.
  95  *
  96  *  In-line line breaking:
  97  *
  98  *  If TERMP_NOBREAK is specified and the line overruns the right
  99  *  margin, it will break and pad-right to the right margin after
 100  *  writing.  If maxrmargin is violated, it will break and continue
 101  *  writing from the right-margin, which will lead to the above scenario
 102  *  upon exit.  Otherwise, the line will break at the right margin.
 103  */
 104 void
 105 term_flushln(struct termp *p)
 106 {
 107         size_t           i;     /* current input position in p->buf */
 108         int              ntab;  /* number of tabs to prepend */
 109         size_t           vis;   /* current visual position on output */
 110         size_t           vbl;   /* number of blanks to prepend to output */
 111         size_t           vend;  /* end of word visual position on output */
 112         size_t           bp;    /* visual right border position */
 113         size_t           dv;    /* temporary for visual pos calculations */
 114         size_t           j;     /* temporary loop index for p->buf */
 115         size_t           jhy;   /* last hyph before overflow w/r/t j */
 116         size_t           maxvis; /* output position of visible boundary */
 117         size_t           mmax; /* used in calculating bp */
 118 
 119         /*
 120          * First, establish the maximum columns of "visible" content.
 121          * This is usually the difference between the right-margin and
 122          * an indentation, but can be, for tagged lists or columns, a
 123          * small set of values.
 124          *
 125          * The following unsigned-signed subtractions look strange,
 126          * but they are actually correct.  If the int p->overstep
 127          * is negative, it gets sign extended.  Subtracting that
 128          * very large size_t effectively adds a small number to dv.
 129          */
 130         assert  (p->rmargin >= p->offset);
 131         dv     = p->rmargin - p->offset;
 132         maxvis = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
 133         dv     = p->maxrmargin - p->offset;
 134         mmax   = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
 135 
 136         bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
 137 
 138         /*
 139          * Calculate the required amount of padding.
 140          */
 141         vbl = p->offset + p->overstep > p->viscol ?
 142               p->offset + p->overstep - p->viscol : 0;
 143 
 144         vis = vend = 0;
 145         i = 0;
 146 
 147         while (i < p->col) {
 148                 /*
 149                  * Handle literal tab characters: collapse all
 150                  * subsequent tabs into a single huge set of spaces.
 151                  */
 152                 ntab = 0;
 153                 while (i < p->col && '\t' == p->buf[i]) {
 154                         vend = (vis / p->tabwidth + 1) * p->tabwidth;
 155                         vbl += vend - vis;
 156                         vis = vend;
 157                         ntab++;
 158                         i++;
 159                 }
 160 
 161                 /*
 162                  * Count up visible word characters.  Control sequences
 163                  * (starting with the CSI) aren't counted.  A space
 164                  * generates a non-printing word, which is valid (the
 165                  * space is printed according to regular spacing rules).
 166                  */
 167 
 168                 for (j = i, jhy = 0; j < p->col; j++) {
 169                         if (' ' == p->buf[j] || '\t' == p->buf[j])
 170                                 break;
 171 
 172                         /* Back over the the last printed character. */
 173                         if (8 == p->buf[j]) {
 174                                 assert(j);
 175                                 vend -= (*p->width)(p, p->buf[j - 1]);
 176                                 continue;
 177                         }
 178 
 179                         /* Regular word. */
 180                         /* Break at the hyphen point if we overrun. */
 181                         if (vend > vis && vend < bp && 
 182                                         ASCII_HYPH == p->buf[j])
 183                                 jhy = j;
 184 
 185                         vend += (*p->width)(p, p->buf[j]);
 186                 }
 187 
 188                 /*
 189                  * Find out whether we would exceed the right margin.
 190                  * If so, break to the next line.
 191                  */
 192                 if (vend > bp && 0 == jhy && vis > 0) {
 193                         vend -= vis;
 194                         (*p->endline)(p);
 195                         p->viscol = 0;
 196                         if (TERMP_NOBREAK & p->flags) {
 197                                 vbl = p->rmargin;
 198                                 vend += p->rmargin - p->offset;
 199                         } else
 200                                 vbl = p->offset;
 201 
 202                         /* use pending tabs on the new line */
 203 
 204                         if (0 < ntab)
 205                                 vbl += ntab * p->tabwidth;
 206 
 207                         /*
 208                          * Remove the p->overstep width.
 209                          * Again, if p->overstep is negative,
 210                          * sign extension does the right thing.
 211                          */
 212 
 213                         bp += (size_t)p->overstep;
 214                         p->overstep = 0;
 215                 }
 216 
 217                 /* Write out the [remaining] word. */
 218                 for ( ; i < p->col; i++) {
 219                         if (vend > bp && jhy > 0 && i > jhy)
 220                                 break;
 221                         if ('\t' == p->buf[i])
 222                                 break;
 223                         if (' ' == p->buf[i]) {
 224                                 j = i;
 225                                 while (' ' == p->buf[i])
 226                                         i++;
 227                                 dv = (i - j) * (*p->width)(p, ' ');
 228                                 vbl += dv;
 229                                 vend += dv;
 230                                 break;
 231                         }
 232                         if (ASCII_NBRSP == p->buf[i]) {
 233                                 vbl += (*p->width)(p, ' ');
 234                                 continue;
 235                         }
 236 
 237                         /*
 238                          * Now we definitely know there will be
 239                          * printable characters to output,
 240                          * so write preceding white space now.
 241                          */
 242                         if (vbl) {
 243                                 (*p->advance)(p, vbl);
 244                                 p->viscol += vbl;
 245                                 vbl = 0;
 246                         }
 247 
 248                         if (ASCII_HYPH == p->buf[i]) {
 249                                 (*p->letter)(p, '-');
 250                                 p->viscol += (*p->width)(p, '-');
 251                                 continue;
 252                         }
 253 
 254                         (*p->letter)(p, p->buf[i]);
 255                         if (8 == p->buf[i])
 256                                 p->viscol -= (*p->width)(p, p->buf[i-1]);
 257                         else 
 258                                 p->viscol += (*p->width)(p, p->buf[i]);
 259                 }
 260                 vis = vend;
 261         }
 262 
 263         /*
 264          * If there was trailing white space, it was not printed;
 265          * so reset the cursor position accordingly.
 266          */
 267         if (vis)
 268                 vis -= vbl;
 269 
 270         p->col = 0;
 271         p->overstep = 0;
 272 
 273         if ( ! (TERMP_NOBREAK & p->flags)) {
 274                 p->viscol = 0;
 275                 (*p->endline)(p);
 276                 return;
 277         }
 278 
 279         if (TERMP_HANG & p->flags) {
 280                 p->overstep = (int)(vis - maxvis +
 281                                 p->trailspace * (*p->width)(p, ' '));
 282 
 283                 /*
 284                  * If we have overstepped the margin, temporarily move
 285                  * it to the right and flag the rest of the line to be
 286                  * shorter.
 287                  * If there is a request to keep the columns together,
 288                  * allow negative overstep when the column is not full.
 289                  */
 290                 if (p->trailspace && p->overstep < 0)
 291                         p->overstep = 0;
 292                 return;
 293 
 294         } else if (TERMP_DANGLE & p->flags)
 295                 return;
 296 
 297         /* If the column was overrun, break the line. */
 298         if (maxvis < vis + p->trailspace * (*p->width)(p, ' ')) {
 299                 (*p->endline)(p);
 300                 p->viscol = 0;
 301         }
 302 }
 303 
 304 
 305 /* 
 306  * A newline only breaks an existing line; it won't assert vertical
 307  * space.  All data in the output buffer is flushed prior to the newline
 308  * assertion.
 309  */
 310 void
 311 term_newln(struct termp *p)
 312 {
 313 
 314         p->flags |= TERMP_NOSPACE;
 315         if (p->col || p->viscol)
 316                 term_flushln(p);
 317 }
 318 
 319 
 320 /*
 321  * Asserts a vertical space (a full, empty line-break between lines).
 322  * Note that if used twice, this will cause two blank spaces and so on.
 323  * All data in the output buffer is flushed prior to the newline
 324  * assertion.
 325  */
 326 void
 327 term_vspace(struct termp *p)
 328 {
 329 
 330         term_newln(p);
 331         p->viscol = 0;
 332         if (0 < p->skipvsp)
 333                 p->skipvsp--;
 334         else
 335                 (*p->endline)(p);
 336 }
 337 
 338 void
 339 term_fontlast(struct termp *p)
 340 {
 341         enum termfont    f;
 342 
 343         f = p->fontl;
 344         p->fontl = p->fontq[p->fonti];
 345         p->fontq[p->fonti] = f;
 346 }
 347 
 348 
 349 void
 350 term_fontrepl(struct termp *p, enum termfont f)
 351 {
 352 
 353         p->fontl = p->fontq[p->fonti];
 354         p->fontq[p->fonti] = f;
 355 }
 356 
 357 
 358 void
 359 term_fontpush(struct termp *p, enum termfont f)
 360 {
 361 
 362         assert(p->fonti + 1 < 10);
 363         p->fontl = p->fontq[p->fonti];
 364         p->fontq[++p->fonti] = f;
 365 }
 366 
 367 
 368 const void *
 369 term_fontq(struct termp *p)
 370 {
 371 
 372         return(&p->fontq[p->fonti]);
 373 }
 374 
 375 
 376 enum termfont
 377 term_fonttop(struct termp *p)
 378 {
 379 
 380         return(p->fontq[p->fonti]);
 381 }
 382 
 383 
 384 void
 385 term_fontpopq(struct termp *p, const void *key)
 386 {
 387 
 388         while (p->fonti >= 0 && key < (void *)(p->fontq + p->fonti))
 389                 p->fonti--;
 390         assert(p->fonti >= 0);
 391 }
 392 
 393 
 394 void
 395 term_fontpop(struct termp *p)
 396 {
 397 
 398         assert(p->fonti);
 399         p->fonti--;
 400 }
 401 
 402 /*
 403  * Handle pwords, partial words, which may be either a single word or a
 404  * phrase that cannot be broken down (such as a literal string).  This
 405  * handles word styling.
 406  */
 407 void
 408 term_word(struct termp *p, const char *word)
 409 {
 410         const char       nbrsp[2] = { ASCII_NBRSP, 0 };
 411         const char      *seq, *cp;
 412         char             c;
 413         int              sz, uc;
 414         size_t           ssz;
 415         enum mandoc_esc  esc;
 416 
 417         if ( ! (TERMP_NOSPACE & p->flags)) {
 418                 if ( ! (TERMP_KEEP & p->flags)) {
 419                         bufferc(p, ' ');
 420                         if (TERMP_SENTENCE & p->flags)
 421                                 bufferc(p, ' ');
 422                 } else
 423                         bufferc(p, ASCII_NBRSP);
 424         }
 425         if (TERMP_PREKEEP & p->flags)
 426                 p->flags |= TERMP_KEEP;
 427 
 428         if ( ! (p->flags & TERMP_NONOSPACE))
 429                 p->flags &= ~TERMP_NOSPACE;
 430         else
 431                 p->flags |= TERMP_NOSPACE;
 432 
 433         p->flags &= ~TERMP_SENTENCE;
 434 
 435         while ('\0' != *word) {
 436                 if ('\\' != *word) {
 437                         if (TERMP_SKIPCHAR & p->flags) {
 438                                 p->flags &= ~TERMP_SKIPCHAR;
 439                                 word++;
 440                                 continue;
 441                         }
 442                         if (TERMP_NBRWORD & p->flags) {
 443                                 if (' ' == *word) {
 444                                         encode(p, nbrsp, 1);
 445                                         word++;
 446                                         continue;
 447                                 }
 448                                 ssz = strcspn(word, "\\ ");
 449                         } else
 450                                 ssz = strcspn(word, "\\");
 451                         encode(p, word, ssz);
 452                         word += (int)ssz;
 453                         continue;
 454                 }
 455 
 456                 word++;
 457                 esc = mandoc_escape(&word, &seq, &sz);
 458                 if (ESCAPE_ERROR == esc)
 459                         break;
 460 
 461                 if (TERMENC_ASCII != p->enc)
 462                         switch (esc) {
 463                         case (ESCAPE_UNICODE):
 464                                 uc = mchars_num2uc(seq + 1, sz - 1);
 465                                 if ('\0' == uc)
 466                                         break;
 467                                 encode1(p, uc);
 468                                 continue;
 469                         case (ESCAPE_SPECIAL):
 470                                 uc = mchars_spec2cp(p->symtab, seq, sz);
 471                                 if (uc <= 0)
 472                                         break;
 473                                 encode1(p, uc);
 474                                 continue;
 475                         default:
 476                                 break;
 477                         }
 478 
 479                 switch (esc) {
 480                 case (ESCAPE_UNICODE):
 481                         encode1(p, '?');
 482                         break;
 483                 case (ESCAPE_NUMBERED):
 484                         c = mchars_num2char(seq, sz);
 485                         if ('\0' != c)
 486                                 encode(p, &c, 1);
 487                         break;
 488                 case (ESCAPE_SPECIAL):
 489                         cp = mchars_spec2str(p->symtab, seq, sz, &ssz);
 490                         if (NULL != cp) 
 491                                 encode(p, cp, ssz);
 492                         else if (1 == ssz)
 493                                 encode(p, seq, sz);
 494                         break;
 495                 case (ESCAPE_FONTBOLD):
 496                         term_fontrepl(p, TERMFONT_BOLD);
 497                         break;
 498                 case (ESCAPE_FONTITALIC):
 499                         term_fontrepl(p, TERMFONT_UNDER);
 500                         break;
 501                 case (ESCAPE_FONTBI):
 502                         term_fontrepl(p, TERMFONT_BI);
 503                         break;
 504                 case (ESCAPE_FONT):
 505                         /* FALLTHROUGH */
 506                 case (ESCAPE_FONTROMAN):
 507                         term_fontrepl(p, TERMFONT_NONE);
 508                         break;
 509                 case (ESCAPE_FONTPREV):
 510                         term_fontlast(p);
 511                         break;
 512                 case (ESCAPE_NOSPACE):
 513                         if (TERMP_SKIPCHAR & p->flags)
 514                                 p->flags &= ~TERMP_SKIPCHAR;
 515                         else if ('\0' == *word)
 516                                 p->flags |= TERMP_NOSPACE;
 517                         break;
 518                 case (ESCAPE_SKIPCHAR):
 519                         p->flags |= TERMP_SKIPCHAR;
 520                         break;
 521                 default:
 522                         break;
 523                 }
 524         }
 525         p->flags &= ~TERMP_NBRWORD;
 526 }
 527 
 528 static void
 529 adjbuf(struct termp *p, size_t sz)
 530 {
 531 
 532         if (0 == p->maxcols)
 533                 p->maxcols = 1024;
 534         while (sz >= p->maxcols)
 535                 p->maxcols <<= 2;
 536 
 537         p->buf = mandoc_realloc(p->buf, sizeof(int) * p->maxcols);
 538 }
 539 
 540 static void
 541 bufferc(struct termp *p, char c)
 542 {
 543 
 544         if (p->col + 1 >= p->maxcols)
 545                 adjbuf(p, p->col + 1);
 546 
 547         p->buf[p->col++] = c;
 548 }
 549 
 550 /*
 551  * See encode().
 552  * Do this for a single (probably unicode) value.
 553  * Does not check for non-decorated glyphs.
 554  */
 555 static void
 556 encode1(struct termp *p, int c)
 557 {
 558         enum termfont     f;
 559 
 560         if (TERMP_SKIPCHAR & p->flags) {
 561                 p->flags &= ~TERMP_SKIPCHAR;
 562                 return;
 563         }
 564 
 565         if (p->col + 6 >= p->maxcols)
 566                 adjbuf(p, p->col + 6);
 567 
 568         f = term_fonttop(p);
 569 
 570         if (TERMFONT_UNDER == f || TERMFONT_BI == f) {
 571                 p->buf[p->col++] = '_';
 572                 p->buf[p->col++] = 8;
 573         }
 574         if (TERMFONT_BOLD == f || TERMFONT_BI == f) {
 575                 if (ASCII_HYPH == c)
 576                         p->buf[p->col++] = '-';
 577                 else
 578                         p->buf[p->col++] = c;
 579                 p->buf[p->col++] = 8;
 580         }
 581         p->buf[p->col++] = c;
 582 }
 583 
 584 static void
 585 encode(struct termp *p, const char *word, size_t sz)
 586 {
 587         size_t            i;
 588 
 589         if (TERMP_SKIPCHAR & p->flags) {
 590                 p->flags &= ~TERMP_SKIPCHAR;
 591                 return;
 592         }
 593 
 594         /*
 595          * Encode and buffer a string of characters.  If the current
 596          * font mode is unset, buffer directly, else encode then buffer
 597          * character by character.
 598          */
 599 
 600         if (TERMFONT_NONE == term_fonttop(p)) {
 601                 if (p->col + sz >= p->maxcols) 
 602                         adjbuf(p, p->col + sz);
 603                 for (i = 0; i < sz; i++)
 604                         p->buf[p->col++] = word[i];
 605                 return;
 606         }
 607 
 608         /* Pre-buffer, assuming worst-case. */
 609 
 610         if (p->col + 1 + (sz * 5) >= p->maxcols)
 611                 adjbuf(p, p->col + 1 + (sz * 5));
 612 
 613         for (i = 0; i < sz; i++) {
 614                 if (ASCII_HYPH == word[i] ||
 615                     isgraph((unsigned char)word[i]))
 616                         encode1(p, word[i]);
 617                 else
 618                         p->buf[p->col++] = word[i];
 619         }
 620 }
 621 
 622 size_t
 623 term_len(const struct termp *p, size_t sz)
 624 {
 625 
 626         return((*p->width)(p, ' ') * sz);
 627 }
 628 
 629 static size_t
 630 cond_width(const struct termp *p, int c, int *skip)
 631 {
 632 
 633         if (*skip) {
 634                 (*skip) = 0;
 635                 return(0);
 636         } else
 637                 return((*p->width)(p, c));
 638 }
 639 
 640 size_t
 641 term_strlen(const struct termp *p, const char *cp)
 642 {
 643         size_t           sz, rsz, i;
 644         int              ssz, skip, c;
 645         const char      *seq, *rhs;
 646         enum mandoc_esc  esc;
 647         static const char rej[] = { '\\', ASCII_HYPH, ASCII_NBRSP, '\0' };
 648 
 649         /*
 650          * Account for escaped sequences within string length
 651          * calculations.  This follows the logic in term_word() as we
 652          * must calculate the width of produced strings.
 653          */
 654 
 655         sz = 0;
 656         skip = 0;
 657         while ('\0' != *cp) {
 658                 rsz = strcspn(cp, rej);
 659                 for (i = 0; i < rsz; i++)
 660                         sz += cond_width(p, *cp++, &skip);
 661 
 662                 c = 0;
 663                 switch (*cp) {
 664                 case ('\\'):
 665                         cp++;
 666                         esc = mandoc_escape(&cp, &seq, &ssz);
 667                         if (ESCAPE_ERROR == esc)
 668                                 return(sz);
 669 
 670                         if (TERMENC_ASCII != p->enc)
 671                                 switch (esc) {
 672                                 case (ESCAPE_UNICODE):
 673                                         c = mchars_num2uc
 674                                                 (seq + 1, ssz - 1);
 675                                         if ('\0' == c)
 676                                                 break;
 677                                         sz += cond_width(p, c, &skip);
 678                                         continue;
 679                                 case (ESCAPE_SPECIAL):
 680                                         c = mchars_spec2cp
 681                                                 (p->symtab, seq, ssz);
 682                                         if (c <= 0)
 683                                                 break;
 684                                         sz += cond_width(p, c, &skip);
 685                                         continue;
 686                                 default:
 687                                         break;
 688                                 }
 689 
 690                         rhs = NULL;
 691 
 692                         switch (esc) {
 693                         case (ESCAPE_UNICODE):
 694                                 sz += cond_width(p, '?', &skip);
 695                                 break;
 696                         case (ESCAPE_NUMBERED):
 697                                 c = mchars_num2char(seq, ssz);
 698                                 if ('\0' != c)
 699                                         sz += cond_width(p, c, &skip);
 700                                 break;
 701                         case (ESCAPE_SPECIAL):
 702                                 rhs = mchars_spec2str
 703                                         (p->symtab, seq, ssz, &rsz);
 704 
 705                                 if (ssz != 1 || rhs)
 706                                         break;
 707 
 708                                 rhs = seq;
 709                                 rsz = ssz;
 710                                 break;
 711                         case (ESCAPE_SKIPCHAR):
 712                                 skip = 1;
 713                                 break;
 714                         default:
 715                                 break;
 716                         }
 717 
 718                         if (NULL == rhs)
 719                                 break;
 720 
 721                         if (skip) {
 722                                 skip = 0;
 723                                 break;
 724                         }
 725 
 726                         for (i = 0; i < rsz; i++)
 727                                 sz += (*p->width)(p, *rhs++);
 728                         break;
 729                 case (ASCII_NBRSP):
 730                         sz += cond_width(p, ' ', &skip);
 731                         cp++;
 732                         break;
 733                 case (ASCII_HYPH):
 734                         sz += cond_width(p, '-', &skip);
 735                         cp++;
 736                         break;
 737                 default:
 738                         break;
 739                 }
 740         }
 741 
 742         return(sz);
 743 }
 744 
 745 /* ARGSUSED */
 746 size_t
 747 term_vspan(const struct termp *p, const struct roffsu *su)
 748 {
 749         double           r;
 750 
 751         switch (su->unit) {
 752         case (SCALE_CM):
 753                 r = su->scale * 2;
 754                 break;
 755         case (SCALE_IN):
 756                 r = su->scale * 6;
 757                 break;
 758         case (SCALE_PC):
 759                 r = su->scale;
 760                 break;
 761         case (SCALE_PT):
 762                 r = su->scale / 8;
 763                 break;
 764         case (SCALE_MM):
 765                 r = su->scale / 1000;
 766                 break;
 767         case (SCALE_VS):
 768                 r = su->scale;
 769                 break;
 770         default:
 771                 r = su->scale - 1;
 772                 break;
 773         }
 774 
 775         if (r < 0.0)
 776                 r = 0.0;
 777         return(/* LINTED */(size_t)
 778                         r);
 779 }
 780 
 781 size_t
 782 term_hspan(const struct termp *p, const struct roffsu *su)
 783 {
 784         double           v;
 785 
 786         v = ((*p->hspan)(p, su));
 787         if (v < 0.0)
 788                 v = 0.0;
 789         return((size_t) /* LINTED */
 790                         v);
 791 }