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