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