1 /* $OpenBSD: term.c,v 1.143 2021/08/10 12:36:42 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 case ESCAPE_FONTCB: 591 term_fontrepl(p, TERMFONT_BOLD); 592 continue; 593 case ESCAPE_FONTITALIC: 594 case ESCAPE_FONTCI: 595 term_fontrepl(p, TERMFONT_UNDER); 596 continue; 597 case ESCAPE_FONTBI: 598 term_fontrepl(p, TERMFONT_BI); 599 continue; 600 case ESCAPE_FONT: 601 case ESCAPE_FONTCR: 602 case ESCAPE_FONTROMAN: 603 term_fontrepl(p, TERMFONT_NONE); 604 continue; 605 case ESCAPE_FONTPREV: 606 term_fontlast(p); 607 continue; 608 case ESCAPE_BREAK: 609 bufferc(p, '\n'); 610 continue; 611 case ESCAPE_NOSPACE: 612 if (p->flags & TERMP_BACKAFTER) 613 p->flags &= ~TERMP_BACKAFTER; 614 else if (*word == '\0') 615 p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE); 616 continue; 617 case ESCAPE_DEVICE: 618 if (p->type == TERMTYPE_PDF) 619 encode(p, "pdf", 3); 620 else if (p->type == TERMTYPE_PS) 621 encode(p, "ps", 2); 622 else if (p->enc == TERMENC_ASCII) 623 encode(p, "ascii", 5); 624 else 625 encode(p, "utf8", 4); 626 continue; 627 case ESCAPE_HORIZ: 628 if (*seq == '|') { 629 seq++; 630 uc = -p->col; 631 } else 632 uc = 0; 633 if (a2roffsu(seq, &su, SCALE_EM) == NULL) 634 continue; 635 uc += term_hen(p, &su); 636 if (uc > 0) 637 while (uc-- > 0) 638 bufferc(p, ASCII_NBRSP); 639 else if (p->col > (size_t)(-uc)) 640 p->col += uc; 641 else { 642 uc += p->col; 643 p->col = 0; 644 if (p->tcol->offset > (size_t)(-uc)) { 645 p->ti += uc; 646 p->tcol->offset += uc; 647 } else { 648 p->ti -= p->tcol->offset; 649 p->tcol->offset = 0; 650 } 651 } 652 continue; 653 case ESCAPE_HLINE: 654 if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL) 655 continue; 656 uc = term_hen(p, &su); 657 if (uc <= 0) { 658 if (p->tcol->rmargin <= p->tcol->offset) 659 continue; 660 lsz = p->tcol->rmargin - p->tcol->offset; 661 } else 662 lsz = uc; 663 if (*cp == seq[-1]) 664 uc = -1; 665 else if (*cp == '\\') { 666 seq = cp + 1; 667 esc = mandoc_escape(&seq, &cp, &sz); 668 switch (esc) { 669 case ESCAPE_UNICODE: 670 uc = mchars_num2uc(cp + 1, sz - 1); 671 break; 672 case ESCAPE_NUMBERED: 673 uc = mchars_num2char(cp, sz); 674 break; 675 case ESCAPE_SPECIAL: 676 uc = mchars_spec2cp(cp, sz); 677 break; 678 case ESCAPE_UNDEF: 679 uc = *seq; 680 break; 681 default: 682 uc = -1; 683 break; 684 } 685 } else 686 uc = *cp; 687 if (uc < 0x20 || (uc > 0x7E && uc < 0xA0)) 688 uc = '_'; 689 if (p->enc == TERMENC_ASCII) { 690 cp = ascii_uc2str(uc); 691 csz = term_strlen(p, cp); 692 ssz = strlen(cp); 693 } else 694 csz = (*p->width)(p, uc); 695 while (lsz >= csz) { 696 if (p->enc == TERMENC_ASCII) 697 encode(p, cp, ssz); 698 else 699 encode1(p, uc); 700 lsz -= csz; 701 } 702 continue; 703 case ESCAPE_SKIPCHAR: 704 p->flags |= TERMP_BACKAFTER; 705 continue; 706 case ESCAPE_OVERSTRIKE: 707 cp = seq + sz; 708 while (seq < cp) { 709 if (*seq == '\\') { 710 mandoc_escape(&seq, NULL, NULL); 711 continue; 712 } 713 encode1(p, *seq++); 714 if (seq < cp) { 715 if (p->flags & TERMP_BACKBEFORE) 716 p->flags |= TERMP_BACKAFTER; 717 else 718 p->flags |= TERMP_BACKBEFORE; 719 } 720 } 721 /* Trim trailing backspace/blank pair. */ 722 if (p->tcol->lastcol > 2 && 723 (p->tcol->buf[p->tcol->lastcol - 1] == ' ' || 724 p->tcol->buf[p->tcol->lastcol - 1] == '\t')) 725 p->tcol->lastcol -= 2; 726 if (p->col > p->tcol->lastcol) 727 p->col = p->tcol->lastcol; 728 continue; 729 default: 730 continue; 731 } 732 733 /* 734 * Common handling for Unicode and numbered 735 * character escape sequences. 736 */ 737 738 if (p->enc == TERMENC_ASCII) { 739 cp = ascii_uc2str(uc); 740 encode(p, cp, strlen(cp)); 741 } else { 742 if ((uc < 0x20 && uc != 0x09) || 743 (uc > 0x7E && uc < 0xA0)) 744 uc = 0xFFFD; 745 encode1(p, uc); 746 } 747 } 748 p->flags &= ~TERMP_NBRWORD; 749 } 750 751 static void 752 adjbuf(struct termp_col *c, size_t sz) 753 { 754 if (c->maxcols == 0) 755 c->maxcols = 1024; 756 while (c->maxcols <= sz) 757 c->maxcols <<= 2; 758 c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf)); 759 } 760 761 static void 762 bufferc(struct termp *p, char c) 763 { 764 if (p->flags & TERMP_NOBUF) { 765 (*p->letter)(p, c); 766 return; 767 } 768 if (p->col + 1 >= p->tcol->maxcols) 769 adjbuf(p->tcol, p->col + 1); 770 if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP)) 771 p->tcol->buf[p->col] = c; 772 if (p->tcol->lastcol < ++p->col) 773 p->tcol->lastcol = p->col; 774 } 775 776 /* 777 * See encode(). 778 * Do this for a single (probably unicode) value. 779 * Does not check for non-decorated glyphs. 780 */ 781 static void 782 encode1(struct termp *p, int c) 783 { 784 enum termfont f; 785 786 if (p->flags & TERMP_NOBUF) { 787 (*p->letter)(p, c); 788 return; 789 } 790 791 if (p->col + 7 >= p->tcol->maxcols) 792 adjbuf(p->tcol, p->col + 7); 793 794 f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ? 795 p->fontq[p->fonti] : TERMFONT_NONE; 796 797 if (p->flags & TERMP_BACKBEFORE) { 798 if (p->tcol->buf[p->col - 1] == ' ' || 799 p->tcol->buf[p->col - 1] == '\t') 800 p->col--; 801 else 802 p->tcol->buf[p->col++] = '\b'; 803 p->flags &= ~TERMP_BACKBEFORE; 804 } 805 if (f == TERMFONT_UNDER || f == TERMFONT_BI) { 806 p->tcol->buf[p->col++] = '_'; 807 p->tcol->buf[p->col++] = '\b'; 808 } 809 if (f == TERMFONT_BOLD || f == TERMFONT_BI) { 810 if (c == ASCII_HYPH) 811 p->tcol->buf[p->col++] = '-'; 812 else 813 p->tcol->buf[p->col++] = c; 814 p->tcol->buf[p->col++] = '\b'; 815 } 816 if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP)) 817 p->tcol->buf[p->col] = c; 818 if (p->tcol->lastcol < ++p->col) 819 p->tcol->lastcol = p->col; 820 if (p->flags & TERMP_BACKAFTER) { 821 p->flags |= TERMP_BACKBEFORE; 822 p->flags &= ~TERMP_BACKAFTER; 823 } 824 } 825 826 static void 827 encode(struct termp *p, const char *word, size_t sz) 828 { 829 size_t i; 830 831 if (p->flags & TERMP_NOBUF) { 832 for (i = 0; i < sz; i++) 833 (*p->letter)(p, word[i]); 834 return; 835 } 836 837 if (p->col + 2 + (sz * 5) >= p->tcol->maxcols) 838 adjbuf(p->tcol, p->col + 2 + (sz * 5)); 839 840 for (i = 0; i < sz; i++) { 841 if (ASCII_HYPH == word[i] || 842 isgraph((unsigned char)word[i])) 843 encode1(p, word[i]); 844 else { 845 if (p->tcol->lastcol <= p->col || 846 (word[i] != ' ' && word[i] != ASCII_NBRSP)) 847 p->tcol->buf[p->col] = word[i]; 848 p->col++; 849 850 /* 851 * Postpone the effect of \z while handling 852 * an overstrike sequence from ascii_uc2str(). 853 */ 854 855 if (word[i] == '\b' && 856 (p->flags & TERMP_BACKBEFORE)) { 857 p->flags &= ~TERMP_BACKBEFORE; 858 p->flags |= TERMP_BACKAFTER; 859 } 860 } 861 } 862 if (p->tcol->lastcol < p->col) 863 p->tcol->lastcol = p->col; 864 } 865 866 void 867 term_setwidth(struct termp *p, const char *wstr) 868 { 869 struct roffsu su; 870 int iop, width; 871 872 iop = 0; 873 width = 0; 874 if (NULL != wstr) { 875 switch (*wstr) { 876 case '+': 877 iop = 1; 878 wstr++; 879 break; 880 case '-': 881 iop = -1; 882 wstr++; 883 break; 884 default: 885 break; 886 } 887 if (a2roffsu(wstr, &su, SCALE_MAX) != NULL) 888 width = term_hspan(p, &su); 889 else 890 iop = 0; 891 } 892 (*p->setwidth)(p, iop, width); 893 } 894 895 size_t 896 term_len(const struct termp *p, size_t sz) 897 { 898 899 return (*p->width)(p, ' ') * sz; 900 } 901 902 static size_t 903 cond_width(const struct termp *p, int c, int *skip) 904 { 905 906 if (*skip) { 907 (*skip) = 0; 908 return 0; 909 } else 910 return (*p->width)(p, c); 911 } 912 913 size_t 914 term_strlen(const struct termp *p, const char *cp) 915 { 916 size_t sz, rsz, i; 917 int ssz, skip, uc; 918 const char *seq, *rhs; 919 enum mandoc_esc esc; 920 static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH, 921 ASCII_BREAK, '\0' }; 922 923 /* 924 * Account for escaped sequences within string length 925 * calculations. This follows the logic in term_word() as we 926 * must calculate the width of produced strings. 927 */ 928 929 sz = 0; 930 skip = 0; 931 while ('\0' != *cp) { 932 rsz = strcspn(cp, rej); 933 for (i = 0; i < rsz; i++) 934 sz += cond_width(p, *cp++, &skip); 935 936 switch (*cp) { 937 case '\\': 938 cp++; 939 rhs = NULL; 940 esc = mandoc_escape(&cp, &seq, &ssz); 941 switch (esc) { 942 case ESCAPE_UNICODE: 943 uc = mchars_num2uc(seq + 1, ssz - 1); 944 break; 945 case ESCAPE_NUMBERED: 946 uc = mchars_num2char(seq, ssz); 947 if (uc < 0) 948 continue; 949 break; 950 case ESCAPE_SPECIAL: 951 if (p->enc == TERMENC_ASCII) { 952 rhs = mchars_spec2str(seq, ssz, &rsz); 953 if (rhs != NULL) 954 break; 955 } else { 956 uc = mchars_spec2cp(seq, ssz); 957 if (uc > 0) 958 sz += cond_width(p, uc, &skip); 959 } 960 continue; 961 case ESCAPE_UNDEF: 962 uc = *seq; 963 break; 964 case ESCAPE_DEVICE: 965 if (p->type == TERMTYPE_PDF) { 966 rhs = "pdf"; 967 rsz = 3; 968 } else if (p->type == TERMTYPE_PS) { 969 rhs = "ps"; 970 rsz = 2; 971 } else if (p->enc == TERMENC_ASCII) { 972 rhs = "ascii"; 973 rsz = 5; 974 } else { 975 rhs = "utf8"; 976 rsz = 4; 977 } 978 break; 979 case ESCAPE_SKIPCHAR: 980 skip = 1; 981 continue; 982 case ESCAPE_OVERSTRIKE: 983 rsz = 0; 984 rhs = seq + ssz; 985 while (seq < rhs) { 986 if (*seq == '\\') { 987 mandoc_escape(&seq, NULL, NULL); 988 continue; 989 } 990 i = (*p->width)(p, *seq++); 991 if (rsz < i) 992 rsz = i; 993 } 994 sz += rsz; 995 continue; 996 default: 997 continue; 998 } 999 1000 /* 1001 * Common handling for Unicode and numbered 1002 * character escape sequences. 1003 */ 1004 1005 if (rhs == NULL) { 1006 if (p->enc == TERMENC_ASCII) { 1007 rhs = ascii_uc2str(uc); 1008 rsz = strlen(rhs); 1009 } else { 1010 if ((uc < 0x20 && uc != 0x09) || 1011 (uc > 0x7E && uc < 0xA0)) 1012 uc = 0xFFFD; 1013 sz += cond_width(p, uc, &skip); 1014 continue; 1015 } 1016 } 1017 1018 if (skip) { 1019 skip = 0; 1020 break; 1021 } 1022 1023 /* 1024 * Common handling for all escape sequences 1025 * printing more than one character. 1026 */ 1027 1028 for (i = 0; i < rsz; i++) 1029 sz += (*p->width)(p, *rhs++); 1030 break; 1031 case ASCII_NBRSP: 1032 sz += cond_width(p, ' ', &skip); 1033 cp++; 1034 break; 1035 case ASCII_HYPH: 1036 sz += cond_width(p, '-', &skip); 1037 cp++; 1038 break; 1039 default: 1040 break; 1041 } 1042 } 1043 1044 return sz; 1045 } 1046 1047 int 1048 term_vspan(const struct termp *p, const struct roffsu *su) 1049 { 1050 double r; 1051 int ri; 1052 1053 switch (su->unit) { 1054 case SCALE_BU: 1055 r = su->scale / 40.0; 1056 break; 1057 case SCALE_CM: 1058 r = su->scale * 6.0 / 2.54; 1059 break; 1060 case SCALE_FS: 1061 r = su->scale * 65536.0 / 40.0; 1062 break; 1063 case SCALE_IN: 1064 r = su->scale * 6.0; 1065 break; 1066 case SCALE_MM: 1067 r = su->scale * 0.006; 1068 break; 1069 case SCALE_PC: 1070 r = su->scale; 1071 break; 1072 case SCALE_PT: 1073 r = su->scale / 12.0; 1074 break; 1075 case SCALE_EN: 1076 case SCALE_EM: 1077 r = su->scale * 0.6; 1078 break; 1079 case SCALE_VS: 1080 r = su->scale; 1081 break; 1082 default: 1083 abort(); 1084 } 1085 ri = r > 0.0 ? r + 0.4995 : r - 0.4995; 1086 return ri < 66 ? ri : 1; 1087 } 1088 1089 /* 1090 * Convert a scaling width to basic units, rounding towards 0. 1091 */ 1092 int 1093 term_hspan(const struct termp *p, const struct roffsu *su) 1094 { 1095 1096 return (*p->hspan)(p, su); 1097 } 1098 1099 /* 1100 * Convert a scaling width to basic units, rounding to closest. 1101 */ 1102 int 1103 term_hen(const struct termp *p, const struct roffsu *su) 1104 { 1105 int bu; 1106 1107 if ((bu = (*p->hspan)(p, su)) >= 0) 1108 return (bu + 11) / 24; 1109 else 1110 return -((-bu + 11) / 24); 1111 } 1112