1 /* $Id: term.c,v 1.201 2011/09/21 09:57:13 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010, 2011 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 AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 #ifdef HAVE_CONFIG_H 19 #include "config.h" 20 #endif 21 22 #include <sys/types.h> 23 24 #include <assert.h> 25 #include <ctype.h> 26 #include <stdint.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 31 #include "mandoc.h" 32 #include "out.h" 33 #include "term.h" 34 #include "main.h" 35 36 static void adjbuf(struct termp *p, int); 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 41 void 42 term_free(struct termp *p) 43 { 44 45 if (p->buf) 46 free(p->buf); 47 if (p->symtab) 48 mchars_free(p->symtab); 49 50 free(p); 51 } 52 53 54 void 55 term_begin(struct termp *p, term_margin head, 56 term_margin foot, const void *arg) 57 { 58 59 p->headf = head; 60 p->footf = foot; 61 p->argf = arg; 62 (*p->begin)(p); 63 } 64 65 66 void 67 term_end(struct termp *p) 68 { 69 70 (*p->end)(p); 71 } 72 73 /* 74 * Flush a line of text. A "line" is loosely defined as being something 75 * that should be followed by a newline, regardless of whether it's 76 * broken apart by newlines getting there. A line can also be a 77 * fragment of a columnar list (`Bl -tag' or `Bl -column'), which does 78 * not have a trailing newline. 79 * 80 * The following flags may be specified: 81 * 82 * - TERMP_NOBREAK: this is the most important and is used when making 83 * columns. In short: don't print a newline and instead expect the 84 * next call to do the padding up to the start of the next column. 85 * 86 * - TERMP_TWOSPACE: make sure there is room for at least two space 87 * characters of padding. Otherwise, rather break the line. 88 * 89 * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and 90 * the line is overrun, and don't pad-right if it's underrun. 91 * 92 * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when 93 * overrunning, instead save the position and continue at that point 94 * when the next invocation. 95 * 96 * In-line line breaking: 97 * 98 * If TERMP_NOBREAK is specified and the line overruns the right 99 * margin, it will break and pad-right to the right margin after 100 * writing. If maxrmargin is violated, it will break and continue 101 * writing from the right-margin, which will lead to the above scenario 102 * upon exit. Otherwise, the line will break at the right margin. 103 */ 104 void 105 term_flushln(struct termp *p) 106 { 107 int i; /* current input position in p->buf */ 108 size_t vis; /* current visual position on output */ 109 size_t vbl; /* number of blanks to prepend to output */ 110 size_t vend; /* end of word visual position on output */ 111 size_t bp; /* visual right border position */ 112 size_t dv; /* temporary for visual pos calculations */ 113 int j; /* temporary loop index for p->buf */ 114 int jhy; /* last hyph before overflow w/r/t j */ 115 size_t maxvis; /* output position of visible boundary */ 116 size_t mmax; /* used in calculating bp */ 117 118 /* 119 * First, establish the maximum columns of "visible" content. 120 * This is usually the difference between the right-margin and 121 * an indentation, but can be, for tagged lists or columns, a 122 * small set of values. 123 */ 124 assert (p->rmargin >= p->offset); 125 dv = p->rmargin - p->offset; 126 maxvis = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0; 127 dv = p->maxrmargin - p->offset; 128 mmax = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0; 129 130 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis; 131 132 /* 133 * Calculate the required amount of padding. 134 */ 135 vbl = p->offset + p->overstep > p->viscol ? 136 p->offset + p->overstep - p->viscol : 0; 137 138 vis = vend = 0; 139 i = 0; 140 141 while (i < p->col) { 142 /* 143 * Handle literal tab characters: collapse all 144 * subsequent tabs into a single huge set of spaces. 145 */ 146 while (i < p->col && '\t' == p->buf[i]) { 147 vend = (vis / p->tabwidth + 1) * p->tabwidth; 148 vbl += vend - vis; 149 vis = vend; 150 i++; 151 } 152 153 /* 154 * Count up visible word characters. Control sequences 155 * (starting with the CSI) aren't counted. A space 156 * generates a non-printing word, which is valid (the 157 * space is printed according to regular spacing rules). 158 */ 159 160 for (j = i, jhy = 0; j < p->col; j++) { 161 if ((j && ' ' == p->buf[j]) || '\t' == p->buf[j]) 162 break; 163 164 /* Back over the the last printed character. */ 165 if (8 == p->buf[j]) { 166 assert(j); 167 vend -= (*p->width)(p, p->buf[j - 1]); 168 continue; 169 } 170 171 /* Regular word. */ 172 /* Break at the hyphen point if we overrun. */ 173 if (vend > vis && vend < bp && 174 ASCII_HYPH == p->buf[j]) 175 jhy = j; 176 177 vend += (*p->width)(p, p->buf[j]); 178 } 179 180 /* 181 * Find out whether we would exceed the right margin. 182 * If so, break to the next line. 183 */ 184 if (vend > bp && 0 == jhy && vis > 0) { 185 vend -= vis; 186 (*p->endline)(p); 187 p->viscol = 0; 188 if (TERMP_NOBREAK & p->flags) { 189 vbl = p->rmargin; 190 vend += p->rmargin - p->offset; 191 } else 192 vbl = p->offset; 193 194 /* Remove the p->overstep width. */ 195 196 bp += (size_t)p->overstep; 197 p->overstep = 0; 198 } 199 200 /* Write out the [remaining] word. */ 201 for ( ; i < p->col; i++) { 202 if (vend > bp && jhy > 0 && i > jhy) 203 break; 204 if ('\t' == p->buf[i]) 205 break; 206 if (' ' == p->buf[i]) { 207 j = i; 208 while (' ' == p->buf[i]) 209 i++; 210 dv = (size_t)(i - j) * (*p->width)(p, ' '); 211 vbl += dv; 212 vend += dv; 213 break; 214 } 215 if (ASCII_NBRSP == p->buf[i]) { 216 vbl += (*p->width)(p, ' '); 217 continue; 218 } 219 220 /* 221 * Now we definitely know there will be 222 * printable characters to output, 223 * so write preceding white space now. 224 */ 225 if (vbl) { 226 (*p->advance)(p, vbl); 227 p->viscol += vbl; 228 vbl = 0; 229 } 230 231 if (ASCII_HYPH == p->buf[i]) { 232 (*p->letter)(p, '-'); 233 p->viscol += (*p->width)(p, '-'); 234 continue; 235 } 236 237 (*p->letter)(p, p->buf[i]); 238 if (8 == p->buf[i]) 239 p->viscol -= (*p->width)(p, p->buf[i-1]); 240 else 241 p->viscol += (*p->width)(p, p->buf[i]); 242 } 243 vis = vend; 244 } 245 246 /* 247 * If there was trailing white space, it was not printed; 248 * so reset the cursor position accordingly. 249 */ 250 if (vis) 251 vis -= vbl; 252 253 p->col = 0; 254 p->overstep = 0; 255 256 if ( ! (TERMP_NOBREAK & p->flags)) { 257 p->viscol = 0; 258 (*p->endline)(p); 259 return; 260 } 261 262 if (TERMP_HANG & p->flags) { 263 /* We need one blank after the tag. */ 264 p->overstep = (int)(vis - maxvis + (*p->width)(p, ' ')); 265 266 /* 267 * Behave exactly the same way as groff: 268 * If we have overstepped the margin, temporarily move 269 * it to the right and flag the rest of the line to be 270 * shorter. 271 * If we landed right at the margin, be happy. 272 * If we are one step before the margin, temporarily 273 * move it one step LEFT and flag the rest of the line 274 * to be longer. 275 */ 276 if (p->overstep < -1) 277 p->overstep = 0; 278 return; 279 280 } else if (TERMP_DANGLE & p->flags) 281 return; 282 283 /* If the column was overrun, break the line. */ 284 if (maxvis <= vis + 285 ((TERMP_TWOSPACE & p->flags) ? (*p->width)(p, ' ') : 0)) { 286 (*p->endline)(p); 287 p->viscol = 0; 288 } 289 } 290 291 292 /* 293 * A newline only breaks an existing line; it won't assert vertical 294 * space. All data in the output buffer is flushed prior to the newline 295 * assertion. 296 */ 297 void 298 term_newln(struct termp *p) 299 { 300 301 p->flags |= TERMP_NOSPACE; 302 if (p->col || p->viscol) 303 term_flushln(p); 304 } 305 306 307 /* 308 * Asserts a vertical space (a full, empty line-break between lines). 309 * Note that if used twice, this will cause two blank spaces and so on. 310 * All data in the output buffer is flushed prior to the newline 311 * assertion. 312 */ 313 void 314 term_vspace(struct termp *p) 315 { 316 317 term_newln(p); 318 p->viscol = 0; 319 (*p->endline)(p); 320 } 321 322 void 323 term_fontlast(struct termp *p) 324 { 325 enum termfont f; 326 327 f = p->fontl; 328 p->fontl = p->fontq[p->fonti]; 329 p->fontq[p->fonti] = f; 330 } 331 332 333 void 334 term_fontrepl(struct termp *p, enum termfont f) 335 { 336 337 p->fontl = p->fontq[p->fonti]; 338 p->fontq[p->fonti] = f; 339 } 340 341 342 void 343 term_fontpush(struct termp *p, enum termfont f) 344 { 345 346 assert(p->fonti + 1 < 10); 347 p->fontl = p->fontq[p->fonti]; 348 p->fontq[++p->fonti] = f; 349 } 350 351 352 const void * 353 term_fontq(struct termp *p) 354 { 355 356 return(&p->fontq[p->fonti]); 357 } 358 359 360 enum termfont 361 term_fonttop(struct termp *p) 362 { 363 364 return(p->fontq[p->fonti]); 365 } 366 367 368 void 369 term_fontpopq(struct termp *p, const void *key) 370 { 371 372 while (p->fonti >= 0 && key != &p->fontq[p->fonti]) 373 p->fonti--; 374 assert(p->fonti >= 0); 375 } 376 377 378 void 379 term_fontpop(struct termp *p) 380 { 381 382 assert(p->fonti); 383 p->fonti--; 384 } 385 386 /* 387 * Handle pwords, partial words, which may be either a single word or a 388 * phrase that cannot be broken down (such as a literal string). This 389 * handles word styling. 390 */ 391 void 392 term_word(struct termp *p, const char *word) 393 { 394 const char *seq, *cp; 395 char c; 396 int sz, uc; 397 size_t ssz; 398 enum mandoc_esc esc; 399 400 if ( ! (TERMP_NOSPACE & p->flags)) { 401 if ( ! (TERMP_KEEP & p->flags)) { 402 if (TERMP_PREKEEP & p->flags) 403 p->flags |= TERMP_KEEP; 404 bufferc(p, ' '); 405 if (TERMP_SENTENCE & p->flags) 406 bufferc(p, ' '); 407 } else 408 bufferc(p, ASCII_NBRSP); 409 } 410 411 if ( ! (p->flags & TERMP_NONOSPACE)) 412 p->flags &= ~TERMP_NOSPACE; 413 else 414 p->flags |= TERMP_NOSPACE; 415 416 p->flags &= ~(TERMP_SENTENCE | TERMP_IGNDELIM); 417 418 while ('\0' != *word) { 419 if ((ssz = strcspn(word, "\\")) > 0) 420 encode(p, word, ssz); 421 422 word += (int)ssz; 423 if ('\\' != *word) 424 continue; 425 426 word++; 427 esc = mandoc_escape(&word, &seq, &sz); 428 if (ESCAPE_ERROR == esc) 429 break; 430 431 if (TERMENC_ASCII != p->enc) 432 switch (esc) { 433 case (ESCAPE_UNICODE): 434 uc = mchars_num2uc(seq + 1, sz - 1); 435 if ('\0' == uc) 436 break; 437 encode1(p, uc); 438 continue; 439 case (ESCAPE_SPECIAL): 440 uc = mchars_spec2cp(p->symtab, seq, sz); 441 if (uc <= 0) 442 break; 443 encode1(p, uc); 444 continue; 445 default: 446 break; 447 } 448 449 switch (esc) { 450 case (ESCAPE_UNICODE): 451 encode1(p, '?'); 452 break; 453 case (ESCAPE_NUMBERED): 454 c = mchars_num2char(seq, sz); 455 if ('\0' != c) 456 encode(p, &c, 1); 457 break; 458 case (ESCAPE_SPECIAL): 459 cp = mchars_spec2str(p->symtab, seq, sz, &ssz); 460 if (NULL != cp) 461 encode(p, cp, ssz); 462 else if (1 == ssz) 463 encode(p, seq, sz); 464 break; 465 case (ESCAPE_FONTBOLD): 466 term_fontrepl(p, TERMFONT_BOLD); 467 break; 468 case (ESCAPE_FONTITALIC): 469 term_fontrepl(p, TERMFONT_UNDER); 470 break; 471 case (ESCAPE_FONT): 472 /* FALLTHROUGH */ 473 case (ESCAPE_FONTROMAN): 474 term_fontrepl(p, TERMFONT_NONE); 475 break; 476 case (ESCAPE_FONTPREV): 477 term_fontlast(p); 478 break; 479 case (ESCAPE_NOSPACE): 480 if ('\0' == *word) 481 p->flags |= TERMP_NOSPACE; 482 break; 483 default: 484 break; 485 } 486 } 487 } 488 489 static void 490 adjbuf(struct termp *p, int sz) 491 { 492 493 if (0 == p->maxcols) 494 p->maxcols = 1024; 495 while (sz >= p->maxcols) 496 p->maxcols <<= 2; 497 498 p->buf = mandoc_realloc 499 (p->buf, sizeof(int) * (size_t)p->maxcols); 500 } 501 502 static void 503 bufferc(struct termp *p, char c) 504 { 505 506 if (p->col + 1 >= p->maxcols) 507 adjbuf(p, p->col + 1); 508 509 p->buf[p->col++] = c; 510 } 511 512 /* 513 * See encode(). 514 * Do this for a single (probably unicode) value. 515 * Does not check for non-decorated glyphs. 516 */ 517 static void 518 encode1(struct termp *p, int c) 519 { 520 enum termfont f; 521 522 if (p->col + 4 >= p->maxcols) 523 adjbuf(p, p->col + 4); 524 525 f = term_fonttop(p); 526 527 if (TERMFONT_NONE == f) { 528 p->buf[p->col++] = c; 529 return; 530 } else if (TERMFONT_UNDER == f) { 531 p->buf[p->col++] = '_'; 532 } else 533 p->buf[p->col++] = c; 534 535 p->buf[p->col++] = 8; 536 p->buf[p->col++] = c; 537 } 538 539 static void 540 encode(struct termp *p, const char *word, size_t sz) 541 { 542 enum termfont f; 543 int i, len; 544 545 /* LINTED */ 546 len = sz; 547 548 /* 549 * Encode and buffer a string of characters. If the current 550 * font mode is unset, buffer directly, else encode then buffer 551 * character by character. 552 */ 553 554 if (TERMFONT_NONE == (f = term_fonttop(p))) { 555 if (p->col + len >= p->maxcols) 556 adjbuf(p, p->col + len); 557 for (i = 0; i < len; i++) 558 p->buf[p->col++] = word[i]; 559 return; 560 } 561 562 /* Pre-buffer, assuming worst-case. */ 563 564 if (p->col + 1 + (len * 3) >= p->maxcols) 565 adjbuf(p, p->col + 1 + (len * 3)); 566 567 for (i = 0; i < len; i++) { 568 if (ASCII_HYPH != word[i] && 569 ! isgraph((unsigned char)word[i])) { 570 p->buf[p->col++] = word[i]; 571 continue; 572 } 573 574 if (TERMFONT_UNDER == f) 575 p->buf[p->col++] = '_'; 576 else if (ASCII_HYPH == word[i]) 577 p->buf[p->col++] = '-'; 578 else 579 p->buf[p->col++] = word[i]; 580 581 p->buf[p->col++] = 8; 582 p->buf[p->col++] = word[i]; 583 } 584 } 585 586 size_t 587 term_len(const struct termp *p, size_t sz) 588 { 589 590 return((*p->width)(p, ' ') * sz); 591 } 592 593 594 size_t 595 term_strlen(const struct termp *p, const char *cp) 596 { 597 size_t sz, rsz, i; 598 int ssz, c; 599 const char *seq, *rhs; 600 enum mandoc_esc esc; 601 static const char rej[] = { '\\', ASCII_HYPH, ASCII_NBRSP, '\0' }; 602 603 /* 604 * Account for escaped sequences within string length 605 * calculations. This follows the logic in term_word() as we 606 * must calculate the width of produced strings. 607 */ 608 609 sz = 0; 610 while ('\0' != *cp) { 611 rsz = strcspn(cp, rej); 612 for (i = 0; i < rsz; i++) 613 sz += (*p->width)(p, *cp++); 614 615 c = 0; 616 switch (*cp) { 617 case ('\\'): 618 cp++; 619 esc = mandoc_escape(&cp, &seq, &ssz); 620 if (ESCAPE_ERROR == esc) 621 return(sz); 622 623 if (TERMENC_ASCII != p->enc) 624 switch (esc) { 625 case (ESCAPE_UNICODE): 626 c = mchars_num2uc 627 (seq + 1, ssz - 1); 628 if ('\0' == c) 629 break; 630 sz += (*p->width)(p, c); 631 continue; 632 case (ESCAPE_SPECIAL): 633 c = mchars_spec2cp 634 (p->symtab, seq, ssz); 635 if (c <= 0) 636 break; 637 sz += (*p->width)(p, c); 638 continue; 639 default: 640 break; 641 } 642 643 rhs = NULL; 644 645 switch (esc) { 646 case (ESCAPE_UNICODE): 647 sz += (*p->width)(p, '?'); 648 break; 649 case (ESCAPE_NUMBERED): 650 c = mchars_num2char(seq, ssz); 651 if ('\0' != c) 652 sz += (*p->width)(p, c); 653 break; 654 case (ESCAPE_SPECIAL): 655 rhs = mchars_spec2str 656 (p->symtab, seq, ssz, &rsz); 657 658 if (ssz != 1 || rhs) 659 break; 660 661 rhs = seq; 662 rsz = ssz; 663 break; 664 default: 665 break; 666 } 667 668 if (NULL == rhs) 669 break; 670 671 for (i = 0; i < rsz; i++) 672 sz += (*p->width)(p, *rhs++); 673 break; 674 case (ASCII_NBRSP): 675 sz += (*p->width)(p, ' '); 676 cp++; 677 break; 678 case (ASCII_HYPH): 679 sz += (*p->width)(p, '-'); 680 cp++; 681 break; 682 default: 683 break; 684 } 685 } 686 687 return(sz); 688 } 689 690 /* ARGSUSED */ 691 size_t 692 term_vspan(const struct termp *p, const struct roffsu *su) 693 { 694 double r; 695 696 switch (su->unit) { 697 case (SCALE_CM): 698 r = su->scale * 2; 699 break; 700 case (SCALE_IN): 701 r = su->scale * 6; 702 break; 703 case (SCALE_PC): 704 r = su->scale; 705 break; 706 case (SCALE_PT): 707 r = su->scale / 8; 708 break; 709 case (SCALE_MM): 710 r = su->scale / 1000; 711 break; 712 case (SCALE_VS): 713 r = su->scale; 714 break; 715 default: 716 r = su->scale - 1; 717 break; 718 } 719 720 if (r < 0.0) 721 r = 0.0; 722 return(/* LINTED */(size_t) 723 r); 724 } 725 726 size_t 727 term_hspan(const struct termp *p, const struct roffsu *su) 728 { 729 double v; 730 731 v = ((*p->hspan)(p, su)); 732 if (v < 0.0) 733 v = 0.0; 734 return((size_t) /* LINTED */ 735 v); 736 } 737