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