1 /* $OpenBSD: term.c,v 1.142 2020/09/02 16:36:48 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010-2020 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 <stdint.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 27 #include "mandoc.h" 28 #include "mandoc_aux.h" 29 #include "out.h" 30 #include "term.h" 31 #include "main.h" 32 33 static size_t cond_width(const struct termp *, int, int *); 34 static void adjbuf(struct termp_col *, size_t); 35 static void bufferc(struct termp *, char); 36 static void encode(struct termp *, const char *, size_t); 37 static void encode1(struct termp *, int); 38 static void endline(struct termp *); 39 static void term_field(struct termp *, size_t, size_t); 40 static void term_fill(struct termp *, size_t *, size_t *, 41 size_t); 42 43 44 void 45 term_setcol(struct termp *p, size_t maxtcol) 46 { 47 if (maxtcol > p->maxtcol) { 48 p->tcols = mandoc_recallocarray(p->tcols, 49 p->maxtcol, maxtcol, sizeof(*p->tcols)); 50 p->maxtcol = maxtcol; 51 } 52 p->lasttcol = maxtcol - 1; 53 p->tcol = p->tcols; 54 } 55 56 void 57 term_free(struct termp *p) 58 { 59 for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++) 60 free(p->tcol->buf); 61 free(p->tcols); 62 free(p->fontq); 63 free(p); 64 } 65 66 void 67 term_begin(struct termp *p, term_margin head, 68 term_margin foot, const struct roff_meta *arg) 69 { 70 71 p->headf = head; 72 p->footf = foot; 73 p->argf = arg; 74 (*p->begin)(p); 75 } 76 77 void 78 term_end(struct termp *p) 79 { 80 81 (*p->end)(p); 82 } 83 84 /* 85 * Flush a chunk of text. By default, break the output line each time 86 * the right margin is reached, and continue output on the next line 87 * at the same offset as the chunk itself. By default, also break the 88 * output line at the end of the chunk. There are many flags modifying 89 * this behaviour, see the comments in the body of the function. 90 */ 91 void 92 term_flushln(struct termp *p) 93 { 94 size_t vbl; /* Number of blanks to prepend to the output. */ 95 size_t vbr; /* Actual visual position of the end of field. */ 96 size_t vfield; /* Desired visual field width. */ 97 size_t vtarget; /* Desired visual position of the right margin. */ 98 size_t ic; /* Character position in the input buffer. */ 99 size_t nbr; /* Number of characters to print in this field. */ 100 101 /* 102 * Normally, start writing at the left margin, but with the 103 * NOPAD flag, start writing at the current position instead. 104 */ 105 106 vbl = (p->flags & TERMP_NOPAD) || p->tcol->offset < p->viscol ? 107 0 : p->tcol->offset - p->viscol; 108 if (p->minbl && vbl < p->minbl) 109 vbl = p->minbl; 110 111 if ((p->flags & TERMP_MULTICOL) == 0) 112 p->tcol->col = 0; 113 114 /* Loop over output lines. */ 115 116 for (;;) { 117 vfield = p->tcol->rmargin > p->viscol + vbl ? 118 p->tcol->rmargin - p->viscol - vbl : 0; 119 120 /* 121 * Normally, break the line at the the right margin 122 * of the field, but with the NOBREAK flag, only 123 * break it at the max right margin of the screen, 124 * and with the BRNEVER flag, never break it at all. 125 */ 126 127 vtarget = (p->flags & TERMP_NOBREAK) == 0 ? vfield : 128 p->maxrmargin > p->viscol + vbl ? 129 p->maxrmargin - p->viscol - vbl : 0; 130 131 /* 132 * Figure out how much text will fit in the field. 133 * If there is whitespace only, print nothing. 134 */ 135 136 term_fill(p, &nbr, &vbr, 137 p->flags & TERMP_BRNEVER ? SIZE_MAX : vtarget); 138 if (nbr == 0) 139 break; 140 141 /* 142 * With the CENTER or RIGHT flag, increase the indentation 143 * to center the text between the left and right margins 144 * or to adjust it to the right margin, respectively. 145 */ 146 147 if (vbr < vtarget) { 148 if (p->flags & TERMP_CENTER) 149 vbl += (vtarget - vbr) / 2; 150 else if (p->flags & TERMP_RIGHT) 151 vbl += vtarget - vbr; 152 } 153 154 /* Finally, print the field content. */ 155 156 term_field(p, vbl, nbr); 157 158 /* 159 * If there is no text left in the field, exit the loop. 160 * If the BRTRSP flag is set, consider trailing 161 * whitespace significant when deciding whether 162 * the field fits or not. 163 */ 164 165 for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) { 166 switch (p->tcol->buf[ic]) { 167 case '\t': 168 if (p->flags & TERMP_BRTRSP) 169 vbr = term_tab_next(vbr); 170 continue; 171 case ' ': 172 if (p->flags & TERMP_BRTRSP) 173 vbr += (*p->width)(p, ' '); 174 continue; 175 case '\n': 176 case ASCII_BREAK: 177 continue; 178 default: 179 break; 180 } 181 break; 182 } 183 if (ic == p->tcol->lastcol) 184 break; 185 186 /* 187 * At the location of an automtic line break, input 188 * space characters are consumed by the line break. 189 */ 190 191 while (p->tcol->col < p->tcol->lastcol && 192 p->tcol->buf[p->tcol->col] == ' ') 193 p->tcol->col++; 194 195 /* 196 * In multi-column mode, leave the rest of the text 197 * in the buffer to be handled by a subsequent 198 * invocation, such that the other columns of the 199 * table can be handled first. 200 * In single-column mode, simply break the line. 201 */ 202 203 if (p->flags & TERMP_MULTICOL) 204 return; 205 206 endline(p); 207 p->viscol = 0; 208 209 /* 210 * Normally, start the next line at the same indentation 211 * as this one, but with the BRIND flag, start it at the 212 * right margin instead. This is used together with 213 * NOBREAK for the tags in various kinds of tagged lists. 214 */ 215 216 vbl = p->flags & TERMP_BRIND ? 217 p->tcol->rmargin : p->tcol->offset; 218 } 219 220 /* Reset output state in preparation for the next field. */ 221 222 p->col = p->tcol->col = p->tcol->lastcol = 0; 223 p->minbl = p->trailspace; 224 p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD); 225 226 if (p->flags & TERMP_MULTICOL) 227 return; 228 229 /* 230 * The HANG flag means that the next field 231 * always follows on the same line. 232 * The NOBREAK flag means that the next field 233 * follows on the same line unless the field was overrun. 234 * Normally, break the line at the end of each field. 235 */ 236 237 if ((p->flags & TERMP_HANG) == 0 && 238 ((p->flags & TERMP_NOBREAK) == 0 || 239 vbr + term_len(p, p->trailspace) > vfield)) 240 endline(p); 241 } 242 243 /* 244 * Store the number of input characters to print in this field in *nbr 245 * and their total visual width to print in *vbr. 246 * If there is only whitespace in the field, both remain zero. 247 * The desired visual width of the field is provided by vtarget. 248 * If the first word is longer, the field will be overrun. 249 */ 250 static void 251 term_fill(struct termp *p, size_t *nbr, size_t *vbr, size_t vtarget) 252 { 253 size_t ic; /* Character position in the input buffer. */ 254 size_t vis; /* Visual position of the current character. */ 255 size_t vn; /* Visual position of the next character. */ 256 int breakline; /* Break at the end of this word. */ 257 int graph; /* Last character was non-blank. */ 258 259 *nbr = *vbr = vis = 0; 260 breakline = graph = 0; 261 for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) { 262 switch (p->tcol->buf[ic]) { 263 case '\b': /* Escape \o (overstrike) or backspace markup. */ 264 assert(ic > 0); 265 vis -= (*p->width)(p, p->tcol->buf[ic - 1]); 266 continue; 267 268 case '\t': /* Normal ASCII whitespace. */ 269 case ' ': 270 case ASCII_BREAK: /* Escape \: (breakpoint). */ 271 switch (p->tcol->buf[ic]) { 272 case '\t': 273 vn = term_tab_next(vis); 274 break; 275 case ' ': 276 vn = vis + (*p->width)(p, ' '); 277 break; 278 case ASCII_BREAK: 279 vn = vis; 280 break; 281 default: 282 abort(); 283 } 284 /* Can break at the end of a word. */ 285 if (breakline || vn > vtarget) 286 break; 287 if (graph) { 288 *nbr = ic; 289 *vbr = vis; 290 graph = 0; 291 } 292 vis = vn; 293 continue; 294 295 case '\n': /* Escape \p (break at the end of the word). */ 296 breakline = 1; 297 continue; 298 299 case ASCII_HYPH: /* Breakable hyphen. */ 300 graph = 1; 301 /* 302 * We are about to decide whether to break the 303 * line or not, so we no longer need this hyphen 304 * to be marked as breakable. Put back a real 305 * hyphen such that we get the correct width. 306 */ 307 p->tcol->buf[ic] = '-'; 308 vis += (*p->width)(p, '-'); 309 if (vis > vtarget) { 310 ic++; 311 break; 312 } 313 *nbr = ic + 1; 314 *vbr = vis; 315 continue; 316 317 case ASCII_NBRSP: /* Non-breakable space. */ 318 p->tcol->buf[ic] = ' '; 319 /* FALLTHROUGH */ 320 default: /* Printable character. */ 321 graph = 1; 322 vis += (*p->width)(p, p->tcol->buf[ic]); 323 if (vis > vtarget && *nbr > 0) 324 return; 325 continue; 326 } 327 break; 328 } 329 330 /* 331 * If the last word extends to the end of the field without any 332 * trailing whitespace, the loop could not check yet whether it 333 * can remain on this line. So do the check now. 334 */ 335 336 if (graph && (vis <= vtarget || *nbr == 0)) { 337 *nbr = ic; 338 *vbr = vis; 339 } 340 } 341 342 /* 343 * Print the contents of one field 344 * with an indentation of vbl visual columns, 345 * and an input string length of nbr characters. 346 */ 347 static void 348 term_field(struct termp *p, size_t vbl, size_t nbr) 349 { 350 size_t ic; /* Character position in the input buffer. */ 351 size_t vis; /* Visual position of the current character. */ 352 size_t dv; /* Visual width of the current character. */ 353 size_t vn; /* Visual position of the next character. */ 354 355 vis = 0; 356 for (ic = p->tcol->col; ic < nbr; ic++) { 357 358 /* 359 * To avoid the printing of trailing whitespace, 360 * do not print whitespace right away, only count it. 361 */ 362 363 switch (p->tcol->buf[ic]) { 364 case '\n': 365 case ASCII_BREAK: 366 continue; 367 case '\t': 368 vn = term_tab_next(vis); 369 vbl += vn - vis; 370 vis = vn; 371 continue; 372 case ' ': 373 case ASCII_NBRSP: 374 dv = (*p->width)(p, ' '); 375 vbl += dv; 376 vis += dv; 377 continue; 378 default: 379 break; 380 } 381 382 /* 383 * We found a non-blank character to print, 384 * so write preceding white space now. 385 */ 386 387 if (vbl > 0) { 388 (*p->advance)(p, vbl); 389 p->viscol += vbl; 390 vbl = 0; 391 } 392 393 /* Print the character and adjust the visual position. */ 394 395 (*p->letter)(p, p->tcol->buf[ic]); 396 if (p->tcol->buf[ic] == '\b') { 397 dv = (*p->width)(p, p->tcol->buf[ic - 1]); 398 p->viscol -= dv; 399 vis -= dv; 400 } else { 401 dv = (*p->width)(p, p->tcol->buf[ic]); 402 p->viscol += dv; 403 vis += dv; 404 } 405 } 406 p->tcol->col = nbr; 407 } 408 409 static void 410 endline(struct termp *p) 411 { 412 if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) { 413 p->mc = NULL; 414 p->flags &= ~TERMP_ENDMC; 415 } 416 if (p->mc != NULL) { 417 if (p->viscol && p->maxrmargin >= p->viscol) 418 (*p->advance)(p, p->maxrmargin - p->viscol + 1); 419 p->flags |= TERMP_NOBUF | TERMP_NOSPACE; 420 term_word(p, p->mc); 421 p->flags &= ~(TERMP_NOBUF | TERMP_NEWMC); 422 } 423 p->viscol = 0; 424 p->minbl = 0; 425 (*p->endline)(p); 426 } 427 428 /* 429 * A newline only breaks an existing line; it won't assert vertical 430 * space. All data in the output buffer is flushed prior to the newline 431 * assertion. 432 */ 433 void 434 term_newln(struct termp *p) 435 { 436 437 p->flags |= TERMP_NOSPACE; 438 if (p->tcol->lastcol || p->viscol) 439 term_flushln(p); 440 } 441 442 /* 443 * Asserts a vertical space (a full, empty line-break between lines). 444 * Note that if used twice, this will cause two blank spaces and so on. 445 * All data in the output buffer is flushed prior to the newline 446 * assertion. 447 */ 448 void 449 term_vspace(struct termp *p) 450 { 451 452 term_newln(p); 453 p->viscol = 0; 454 p->minbl = 0; 455 if (0 < p->skipvsp) 456 p->skipvsp--; 457 else 458 (*p->endline)(p); 459 } 460 461 /* Swap current and previous font; for \fP and .ft P */ 462 void 463 term_fontlast(struct termp *p) 464 { 465 enum termfont f; 466 467 f = p->fontl; 468 p->fontl = p->fontq[p->fonti]; 469 p->fontq[p->fonti] = f; 470 } 471 472 /* Set font, save current, discard previous; for \f, .ft, .B etc. */ 473 void 474 term_fontrepl(struct termp *p, enum termfont f) 475 { 476 477 p->fontl = p->fontq[p->fonti]; 478 p->fontq[p->fonti] = f; 479 } 480 481 /* Set font, save previous. */ 482 void 483 term_fontpush(struct termp *p, enum termfont f) 484 { 485 486 p->fontl = p->fontq[p->fonti]; 487 if (++p->fonti == p->fontsz) { 488 p->fontsz += 8; 489 p->fontq = mandoc_reallocarray(p->fontq, 490 p->fontsz, sizeof(*p->fontq)); 491 } 492 p->fontq[p->fonti] = f; 493 } 494 495 /* Flush to make the saved pointer current again. */ 496 void 497 term_fontpopq(struct termp *p, int i) 498 { 499 500 assert(i >= 0); 501 if (p->fonti > i) 502 p->fonti = i; 503 } 504 505 /* Pop one font off the stack. */ 506 void 507 term_fontpop(struct termp *p) 508 { 509 510 assert(p->fonti); 511 p->fonti--; 512 } 513 514 /* 515 * Handle pwords, partial words, which may be either a single word or a 516 * phrase that cannot be broken down (such as a literal string). This 517 * handles word styling. 518 */ 519 void 520 term_word(struct termp *p, const char *word) 521 { 522 struct roffsu su; 523 const char nbrsp[2] = { ASCII_NBRSP, 0 }; 524 const char *seq, *cp; 525 int sz, uc; 526 size_t csz, lsz, ssz; 527 enum mandoc_esc esc; 528 529 if ((p->flags & TERMP_NOBUF) == 0) { 530 if ((p->flags & TERMP_NOSPACE) == 0) { 531 if ((p->flags & TERMP_KEEP) == 0) { 532 bufferc(p, ' '); 533 if (p->flags & TERMP_SENTENCE) 534 bufferc(p, ' '); 535 } else 536 bufferc(p, ASCII_NBRSP); 537 } 538 if (p->flags & TERMP_PREKEEP) 539 p->flags |= TERMP_KEEP; 540 if (p->flags & TERMP_NONOSPACE) 541 p->flags |= TERMP_NOSPACE; 542 else 543 p->flags &= ~TERMP_NOSPACE; 544 p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE); 545 p->skipvsp = 0; 546 } 547 548 while ('\0' != *word) { 549 if ('\\' != *word) { 550 if (TERMP_NBRWORD & p->flags) { 551 if (' ' == *word) { 552 encode(p, nbrsp, 1); 553 word++; 554 continue; 555 } 556 ssz = strcspn(word, "\\ "); 557 } else 558 ssz = strcspn(word, "\\"); 559 encode(p, word, ssz); 560 word += (int)ssz; 561 continue; 562 } 563 564 word++; 565 esc = mandoc_escape(&word, &seq, &sz); 566 switch (esc) { 567 case ESCAPE_UNICODE: 568 uc = mchars_num2uc(seq + 1, sz - 1); 569 break; 570 case ESCAPE_NUMBERED: 571 uc = mchars_num2char(seq, sz); 572 if (uc < 0) 573 continue; 574 break; 575 case ESCAPE_SPECIAL: 576 if (p->enc == TERMENC_ASCII) { 577 cp = mchars_spec2str(seq, sz, &ssz); 578 if (cp != NULL) 579 encode(p, cp, ssz); 580 } else { 581 uc = mchars_spec2cp(seq, sz); 582 if (uc > 0) 583 encode1(p, uc); 584 } 585 continue; 586 case ESCAPE_UNDEF: 587 uc = *seq; 588 break; 589 case ESCAPE_FONTBOLD: 590 term_fontrepl(p, TERMFONT_BOLD); 591 continue; 592 case ESCAPE_FONTITALIC: 593 term_fontrepl(p, TERMFONT_UNDER); 594 continue; 595 case ESCAPE_FONTBI: 596 term_fontrepl(p, TERMFONT_BI); 597 continue; 598 case ESCAPE_FONT: 599 case ESCAPE_FONTCW: 600 case ESCAPE_FONTROMAN: 601 term_fontrepl(p, TERMFONT_NONE); 602 continue; 603 case ESCAPE_FONTPREV: 604 term_fontlast(p); 605 continue; 606 case ESCAPE_BREAK: 607 bufferc(p, '\n'); 608 continue; 609 case ESCAPE_NOSPACE: 610 if (p->flags & TERMP_BACKAFTER) 611 p->flags &= ~TERMP_BACKAFTER; 612 else if (*word == '\0') 613 p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE); 614 continue; 615 case ESCAPE_DEVICE: 616 if (p->type == TERMTYPE_PDF) 617 encode(p, "pdf", 3); 618 else if (p->type == TERMTYPE_PS) 619 encode(p, "ps", 2); 620 else if (p->enc == TERMENC_ASCII) 621 encode(p, "ascii", 5); 622 else 623 encode(p, "utf8", 4); 624 continue; 625 case ESCAPE_HORIZ: 626 if (*seq == '|') { 627 seq++; 628 uc = -p->col; 629 } else 630 uc = 0; 631 if (a2roffsu(seq, &su, SCALE_EM) == NULL) 632 continue; 633 uc += term_hen(p, &su); 634 if (uc > 0) 635 while (uc-- > 0) 636 bufferc(p, ASCII_NBRSP); 637 else if (p->col > (size_t)(-uc)) 638 p->col += uc; 639 else { 640 uc += p->col; 641 p->col = 0; 642 if (p->tcol->offset > (size_t)(-uc)) { 643 p->ti += uc; 644 p->tcol->offset += uc; 645 } else { 646 p->ti -= p->tcol->offset; 647 p->tcol->offset = 0; 648 } 649 } 650 continue; 651 case ESCAPE_HLINE: 652 if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL) 653 continue; 654 uc = term_hen(p, &su); 655 if (uc <= 0) { 656 if (p->tcol->rmargin <= p->tcol->offset) 657 continue; 658 lsz = p->tcol->rmargin - p->tcol->offset; 659 } else 660 lsz = uc; 661 if (*cp == seq[-1]) 662 uc = -1; 663 else if (*cp == '\\') { 664 seq = cp + 1; 665 esc = mandoc_escape(&seq, &cp, &sz); 666 switch (esc) { 667 case ESCAPE_UNICODE: 668 uc = mchars_num2uc(cp + 1, sz - 1); 669 break; 670 case ESCAPE_NUMBERED: 671 uc = mchars_num2char(cp, sz); 672 break; 673 case ESCAPE_SPECIAL: 674 uc = mchars_spec2cp(cp, sz); 675 break; 676 case ESCAPE_UNDEF: 677 uc = *seq; 678 break; 679 default: 680 uc = -1; 681 break; 682 } 683 } else 684 uc = *cp; 685 if (uc < 0x20 || (uc > 0x7E && uc < 0xA0)) 686 uc = '_'; 687 if (p->enc == TERMENC_ASCII) { 688 cp = ascii_uc2str(uc); 689 csz = term_strlen(p, cp); 690 ssz = strlen(cp); 691 } else 692 csz = (*p->width)(p, uc); 693 while (lsz >= csz) { 694 if (p->enc == TERMENC_ASCII) 695 encode(p, cp, ssz); 696 else 697 encode1(p, uc); 698 lsz -= csz; 699 } 700 continue; 701 case ESCAPE_SKIPCHAR: 702 p->flags |= TERMP_BACKAFTER; 703 continue; 704 case ESCAPE_OVERSTRIKE: 705 cp = seq + sz; 706 while (seq < cp) { 707 if (*seq == '\\') { 708 mandoc_escape(&seq, NULL, NULL); 709 continue; 710 } 711 encode1(p, *seq++); 712 if (seq < cp) { 713 if (p->flags & TERMP_BACKBEFORE) 714 p->flags |= TERMP_BACKAFTER; 715 else 716 p->flags |= TERMP_BACKBEFORE; 717 } 718 } 719 /* Trim trailing backspace/blank pair. */ 720 if (p->tcol->lastcol > 2 && 721 (p->tcol->buf[p->tcol->lastcol - 1] == ' ' || 722 p->tcol->buf[p->tcol->lastcol - 1] == '\t')) 723 p->tcol->lastcol -= 2; 724 if (p->col > p->tcol->lastcol) 725 p->col = p->tcol->lastcol; 726 continue; 727 default: 728 continue; 729 } 730 731 /* 732 * Common handling for Unicode and numbered 733 * character escape sequences. 734 */ 735 736 if (p->enc == TERMENC_ASCII) { 737 cp = ascii_uc2str(uc); 738 encode(p, cp, strlen(cp)); 739 } else { 740 if ((uc < 0x20 && uc != 0x09) || 741 (uc > 0x7E && uc < 0xA0)) 742 uc = 0xFFFD; 743 encode1(p, uc); 744 } 745 } 746 p->flags &= ~TERMP_NBRWORD; 747 } 748 749 static void 750 adjbuf(struct termp_col *c, size_t sz) 751 { 752 if (c->maxcols == 0) 753 c->maxcols = 1024; 754 while (c->maxcols <= sz) 755 c->maxcols <<= 2; 756 c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf)); 757 } 758 759 static void 760 bufferc(struct termp *p, char c) 761 { 762 if (p->flags & TERMP_NOBUF) { 763 (*p->letter)(p, c); 764 return; 765 } 766 if (p->col + 1 >= p->tcol->maxcols) 767 adjbuf(p->tcol, p->col + 1); 768 if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP)) 769 p->tcol->buf[p->col] = c; 770 if (p->tcol->lastcol < ++p->col) 771 p->tcol->lastcol = p->col; 772 } 773 774 /* 775 * See encode(). 776 * Do this for a single (probably unicode) value. 777 * Does not check for non-decorated glyphs. 778 */ 779 static void 780 encode1(struct termp *p, int c) 781 { 782 enum termfont f; 783 784 if (p->flags & TERMP_NOBUF) { 785 (*p->letter)(p, c); 786 return; 787 } 788 789 if (p->col + 7 >= p->tcol->maxcols) 790 adjbuf(p->tcol, p->col + 7); 791 792 f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ? 793 p->fontq[p->fonti] : TERMFONT_NONE; 794 795 if (p->flags & TERMP_BACKBEFORE) { 796 if (p->tcol->buf[p->col - 1] == ' ' || 797 p->tcol->buf[p->col - 1] == '\t') 798 p->col--; 799 else 800 p->tcol->buf[p->col++] = '\b'; 801 p->flags &= ~TERMP_BACKBEFORE; 802 } 803 if (f == TERMFONT_UNDER || f == TERMFONT_BI) { 804 p->tcol->buf[p->col++] = '_'; 805 p->tcol->buf[p->col++] = '\b'; 806 } 807 if (f == TERMFONT_BOLD || f == TERMFONT_BI) { 808 if (c == ASCII_HYPH) 809 p->tcol->buf[p->col++] = '-'; 810 else 811 p->tcol->buf[p->col++] = c; 812 p->tcol->buf[p->col++] = '\b'; 813 } 814 if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP)) 815 p->tcol->buf[p->col] = c; 816 if (p->tcol->lastcol < ++p->col) 817 p->tcol->lastcol = p->col; 818 if (p->flags & TERMP_BACKAFTER) { 819 p->flags |= TERMP_BACKBEFORE; 820 p->flags &= ~TERMP_BACKAFTER; 821 } 822 } 823 824 static void 825 encode(struct termp *p, const char *word, size_t sz) 826 { 827 size_t i; 828 829 if (p->flags & TERMP_NOBUF) { 830 for (i = 0; i < sz; i++) 831 (*p->letter)(p, word[i]); 832 return; 833 } 834 835 if (p->col + 2 + (sz * 5) >= p->tcol->maxcols) 836 adjbuf(p->tcol, p->col + 2 + (sz * 5)); 837 838 for (i = 0; i < sz; i++) { 839 if (ASCII_HYPH == word[i] || 840 isgraph((unsigned char)word[i])) 841 encode1(p, word[i]); 842 else { 843 if (p->tcol->lastcol <= p->col || 844 (word[i] != ' ' && word[i] != ASCII_NBRSP)) 845 p->tcol->buf[p->col] = word[i]; 846 p->col++; 847 848 /* 849 * Postpone the effect of \z while handling 850 * an overstrike sequence from ascii_uc2str(). 851 */ 852 853 if (word[i] == '\b' && 854 (p->flags & TERMP_BACKBEFORE)) { 855 p->flags &= ~TERMP_BACKBEFORE; 856 p->flags |= TERMP_BACKAFTER; 857 } 858 } 859 } 860 if (p->tcol->lastcol < p->col) 861 p->tcol->lastcol = p->col; 862 } 863 864 void 865 term_setwidth(struct termp *p, const char *wstr) 866 { 867 struct roffsu su; 868 int iop, width; 869 870 iop = 0; 871 width = 0; 872 if (NULL != wstr) { 873 switch (*wstr) { 874 case '+': 875 iop = 1; 876 wstr++; 877 break; 878 case '-': 879 iop = -1; 880 wstr++; 881 break; 882 default: 883 break; 884 } 885 if (a2roffsu(wstr, &su, SCALE_MAX) != NULL) 886 width = term_hspan(p, &su); 887 else 888 iop = 0; 889 } 890 (*p->setwidth)(p, iop, width); 891 } 892 893 size_t 894 term_len(const struct termp *p, size_t sz) 895 { 896 897 return (*p->width)(p, ' ') * sz; 898 } 899 900 static size_t 901 cond_width(const struct termp *p, int c, int *skip) 902 { 903 904 if (*skip) { 905 (*skip) = 0; 906 return 0; 907 } else 908 return (*p->width)(p, c); 909 } 910 911 size_t 912 term_strlen(const struct termp *p, const char *cp) 913 { 914 size_t sz, rsz, i; 915 int ssz, skip, uc; 916 const char *seq, *rhs; 917 enum mandoc_esc esc; 918 static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH, 919 ASCII_BREAK, '\0' }; 920 921 /* 922 * Account for escaped sequences within string length 923 * calculations. This follows the logic in term_word() as we 924 * must calculate the width of produced strings. 925 */ 926 927 sz = 0; 928 skip = 0; 929 while ('\0' != *cp) { 930 rsz = strcspn(cp, rej); 931 for (i = 0; i < rsz; i++) 932 sz += cond_width(p, *cp++, &skip); 933 934 switch (*cp) { 935 case '\\': 936 cp++; 937 rhs = NULL; 938 esc = mandoc_escape(&cp, &seq, &ssz); 939 switch (esc) { 940 case ESCAPE_UNICODE: 941 uc = mchars_num2uc(seq + 1, ssz - 1); 942 break; 943 case ESCAPE_NUMBERED: 944 uc = mchars_num2char(seq, ssz); 945 if (uc < 0) 946 continue; 947 break; 948 case ESCAPE_SPECIAL: 949 if (p->enc == TERMENC_ASCII) { 950 rhs = mchars_spec2str(seq, ssz, &rsz); 951 if (rhs != NULL) 952 break; 953 } else { 954 uc = mchars_spec2cp(seq, ssz); 955 if (uc > 0) 956 sz += cond_width(p, uc, &skip); 957 } 958 continue; 959 case ESCAPE_UNDEF: 960 uc = *seq; 961 break; 962 case ESCAPE_DEVICE: 963 if (p->type == TERMTYPE_PDF) { 964 rhs = "pdf"; 965 rsz = 3; 966 } else if (p->type == TERMTYPE_PS) { 967 rhs = "ps"; 968 rsz = 2; 969 } else if (p->enc == TERMENC_ASCII) { 970 rhs = "ascii"; 971 rsz = 5; 972 } else { 973 rhs = "utf8"; 974 rsz = 4; 975 } 976 break; 977 case ESCAPE_SKIPCHAR: 978 skip = 1; 979 continue; 980 case ESCAPE_OVERSTRIKE: 981 rsz = 0; 982 rhs = seq + ssz; 983 while (seq < rhs) { 984 if (*seq == '\\') { 985 mandoc_escape(&seq, NULL, NULL); 986 continue; 987 } 988 i = (*p->width)(p, *seq++); 989 if (rsz < i) 990 rsz = i; 991 } 992 sz += rsz; 993 continue; 994 default: 995 continue; 996 } 997 998 /* 999 * Common handling for Unicode and numbered 1000 * character escape sequences. 1001 */ 1002 1003 if (rhs == NULL) { 1004 if (p->enc == TERMENC_ASCII) { 1005 rhs = ascii_uc2str(uc); 1006 rsz = strlen(rhs); 1007 } else { 1008 if ((uc < 0x20 && uc != 0x09) || 1009 (uc > 0x7E && uc < 0xA0)) 1010 uc = 0xFFFD; 1011 sz += cond_width(p, uc, &skip); 1012 continue; 1013 } 1014 } 1015 1016 if (skip) { 1017 skip = 0; 1018 break; 1019 } 1020 1021 /* 1022 * Common handling for all escape sequences 1023 * printing more than one character. 1024 */ 1025 1026 for (i = 0; i < rsz; i++) 1027 sz += (*p->width)(p, *rhs++); 1028 break; 1029 case ASCII_NBRSP: 1030 sz += cond_width(p, ' ', &skip); 1031 cp++; 1032 break; 1033 case ASCII_HYPH: 1034 sz += cond_width(p, '-', &skip); 1035 cp++; 1036 break; 1037 default: 1038 break; 1039 } 1040 } 1041 1042 return sz; 1043 } 1044 1045 int 1046 term_vspan(const struct termp *p, const struct roffsu *su) 1047 { 1048 double r; 1049 int ri; 1050 1051 switch (su->unit) { 1052 case SCALE_BU: 1053 r = su->scale / 40.0; 1054 break; 1055 case SCALE_CM: 1056 r = su->scale * 6.0 / 2.54; 1057 break; 1058 case SCALE_FS: 1059 r = su->scale * 65536.0 / 40.0; 1060 break; 1061 case SCALE_IN: 1062 r = su->scale * 6.0; 1063 break; 1064 case SCALE_MM: 1065 r = su->scale * 0.006; 1066 break; 1067 case SCALE_PC: 1068 r = su->scale; 1069 break; 1070 case SCALE_PT: 1071 r = su->scale / 12.0; 1072 break; 1073 case SCALE_EN: 1074 case SCALE_EM: 1075 r = su->scale * 0.6; 1076 break; 1077 case SCALE_VS: 1078 r = su->scale; 1079 break; 1080 default: 1081 abort(); 1082 } 1083 ri = r > 0.0 ? r + 0.4995 : r - 0.4995; 1084 return ri < 66 ? ri : 1; 1085 } 1086 1087 /* 1088 * Convert a scaling width to basic units, rounding towards 0. 1089 */ 1090 int 1091 term_hspan(const struct termp *p, const struct roffsu *su) 1092 { 1093 1094 return (*p->hspan)(p, su); 1095 } 1096 1097 /* 1098 * Convert a scaling width to basic units, rounding to closest. 1099 */ 1100 int 1101 term_hen(const struct termp *p, const struct roffsu *su) 1102 { 1103 int bu; 1104 1105 if ((bu = (*p->hspan)(p, su)) >= 0) 1106 return (bu + 11) / 24; 1107 else 1108 return -((-bu + 11) / 24); 1109 } 1110