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