1 /* $OpenBSD: html.c,v 1.108 2018/06/25 16:54:55 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2011-2015, 2017, 2018 Ingo Schwarze <schwarze@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 #include <sys/types.h> 19 20 #include <assert.h> 21 #include <ctype.h> 22 #include <stdarg.h> 23 #include <stddef.h> 24 #include <stdio.h> 25 #include <stdint.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 30 #include "mandoc_aux.h" 31 #include "mandoc_ohash.h" 32 #include "mandoc.h" 33 #include "roff.h" 34 #include "out.h" 35 #include "html.h" 36 #include "manconf.h" 37 #include "main.h" 38 39 struct htmldata { 40 const char *name; 41 int flags; 42 #define HTML_NOSTACK (1 << 0) 43 #define HTML_AUTOCLOSE (1 << 1) 44 #define HTML_NLBEFORE (1 << 2) 45 #define HTML_NLBEGIN (1 << 3) 46 #define HTML_NLEND (1 << 4) 47 #define HTML_NLAFTER (1 << 5) 48 #define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER) 49 #define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND) 50 #define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE) 51 #define HTML_INDENT (1 << 6) 52 #define HTML_NOINDENT (1 << 7) 53 }; 54 55 static const struct htmldata htmltags[TAG_MAX] = { 56 {"html", HTML_NLALL}, 57 {"head", HTML_NLALL | HTML_INDENT}, 58 {"body", HTML_NLALL}, 59 {"meta", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, 60 {"title", HTML_NLAROUND}, 61 {"div", HTML_NLAROUND}, 62 {"div", 0}, 63 {"h1", HTML_NLAROUND}, 64 {"h2", HTML_NLAROUND}, 65 {"span", 0}, 66 {"link", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, 67 {"br", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, 68 {"a", 0}, 69 {"table", HTML_NLALL | HTML_INDENT}, 70 {"tr", HTML_NLALL | HTML_INDENT}, 71 {"td", HTML_NLAROUND}, 72 {"li", HTML_NLAROUND | HTML_INDENT}, 73 {"ul", HTML_NLALL | HTML_INDENT}, 74 {"ol", HTML_NLALL | HTML_INDENT}, 75 {"dl", HTML_NLALL | HTML_INDENT}, 76 {"dt", HTML_NLAROUND}, 77 {"dd", HTML_NLAROUND | HTML_INDENT}, 78 {"pre", HTML_NLALL | HTML_NOINDENT}, 79 {"var", 0}, 80 {"cite", 0}, 81 {"b", 0}, 82 {"i", 0}, 83 {"code", 0}, 84 {"small", 0}, 85 {"style", HTML_NLALL | HTML_INDENT}, 86 {"math", HTML_NLALL | HTML_INDENT}, 87 {"mrow", 0}, 88 {"mi", 0}, 89 {"mn", 0}, 90 {"mo", 0}, 91 {"msup", 0}, 92 {"msub", 0}, 93 {"msubsup", 0}, 94 {"mfrac", 0}, 95 {"msqrt", 0}, 96 {"mfenced", 0}, 97 {"mtable", 0}, 98 {"mtr", 0}, 99 {"mtd", 0}, 100 {"munderover", 0}, 101 {"munder", 0}, 102 {"mover", 0}, 103 }; 104 105 /* Avoid duplicate HTML id= attributes. */ 106 static struct ohash id_unique; 107 108 static void print_byte(struct html *, char); 109 static void print_endword(struct html *); 110 static void print_indent(struct html *); 111 static void print_word(struct html *, const char *); 112 113 static void print_ctag(struct html *, struct tag *); 114 static int print_escape(struct html *, char); 115 static int print_encode(struct html *, const char *, const char *, int); 116 static void print_href(struct html *, const char *, const char *, int); 117 static void print_metaf(struct html *, enum mandoc_esc); 118 119 120 void * 121 html_alloc(const struct manoutput *outopts) 122 { 123 struct html *h; 124 125 h = mandoc_calloc(1, sizeof(struct html)); 126 127 h->tag = NULL; 128 h->style = outopts->style; 129 h->base_man = outopts->man; 130 h->base_includes = outopts->includes; 131 if (outopts->fragment) 132 h->oflags |= HTML_FRAGMENT; 133 134 mandoc_ohash_init(&id_unique, 4, 0); 135 136 return h; 137 } 138 139 void 140 html_free(void *p) 141 { 142 struct tag *tag; 143 struct html *h; 144 char *cp; 145 unsigned int slot; 146 147 h = (struct html *)p; 148 while ((tag = h->tag) != NULL) { 149 h->tag = tag->next; 150 free(tag); 151 } 152 free(h); 153 154 cp = ohash_first(&id_unique, &slot); 155 while (cp != NULL) { 156 free(cp); 157 cp = ohash_next(&id_unique, &slot); 158 } 159 ohash_delete(&id_unique); 160 } 161 162 void 163 print_gen_head(struct html *h) 164 { 165 struct tag *t; 166 167 print_otag(h, TAG_META, "?", "charset", "utf-8"); 168 if (h->style != NULL) { 169 print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", 170 h->style, "type", "text/css", "media", "all"); 171 return; 172 } 173 174 /* 175 * Print a minimal embedded style sheet. 176 */ 177 178 t = print_otag(h, TAG_STYLE, ""); 179 print_text(h, "table.head, table.foot { width: 100%; }"); 180 print_endline(h); 181 print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }"); 182 print_endline(h); 183 print_text(h, "td.head-vol { text-align: center; }"); 184 print_endline(h); 185 print_text(h, "div.Pp { margin: 1ex 0ex; }"); 186 print_endline(h); 187 print_text(h, "div.Nd, div.Bf, div.Op { display: inline; }"); 188 print_endline(h); 189 print_text(h, "span.Pa, span.Ad { font-style: italic; }"); 190 print_endline(h); 191 print_text(h, "span.Ms { font-weight: bold; }"); 192 print_endline(h); 193 print_text(h, "dl.Bl-diag "); 194 print_byte(h, '>'); 195 print_text(h, " dt { font-weight: bold; }"); 196 print_endline(h); 197 print_text(h, "code.Nm, code.Fl, code.Cm, code.Ic, " 198 "code.In, code.Fd, code.Fn,"); 199 print_endline(h); 200 print_text(h, "code.Cd { font-weight: bold; " 201 "font-family: inherit; }"); 202 print_tagq(h, t); 203 } 204 205 static void 206 print_metaf(struct html *h, enum mandoc_esc deco) 207 { 208 enum htmlfont font; 209 210 switch (deco) { 211 case ESCAPE_FONTPREV: 212 font = h->metal; 213 break; 214 case ESCAPE_FONTITALIC: 215 font = HTMLFONT_ITALIC; 216 break; 217 case ESCAPE_FONTBOLD: 218 font = HTMLFONT_BOLD; 219 break; 220 case ESCAPE_FONTBI: 221 font = HTMLFONT_BI; 222 break; 223 case ESCAPE_FONT: 224 case ESCAPE_FONTROMAN: 225 font = HTMLFONT_NONE; 226 break; 227 default: 228 abort(); 229 } 230 231 if (h->metaf) { 232 print_tagq(h, h->metaf); 233 h->metaf = NULL; 234 } 235 236 h->metal = h->metac; 237 h->metac = font; 238 239 switch (font) { 240 case HTMLFONT_ITALIC: 241 h->metaf = print_otag(h, TAG_I, ""); 242 break; 243 case HTMLFONT_BOLD: 244 h->metaf = print_otag(h, TAG_B, ""); 245 break; 246 case HTMLFONT_BI: 247 h->metaf = print_otag(h, TAG_B, ""); 248 print_otag(h, TAG_I, ""); 249 break; 250 default: 251 break; 252 } 253 } 254 255 char * 256 html_make_id(const struct roff_node *n, int unique) 257 { 258 const struct roff_node *nch; 259 char *buf, *bufs, *cp; 260 unsigned int slot; 261 int suffix; 262 263 for (nch = n->child; nch != NULL; nch = nch->next) 264 if (nch->type != ROFFT_TEXT) 265 return NULL; 266 267 buf = NULL; 268 deroff(&buf, n); 269 if (buf == NULL) 270 return NULL; 271 272 /* 273 * In ID attributes, only use ASCII characters that are 274 * permitted in URL-fragment strings according to the 275 * explicit list at: 276 * https://url.spec.whatwg.org/#url-fragment-string 277 */ 278 279 for (cp = buf; *cp != '\0'; cp++) 280 if (isalnum((unsigned char)*cp) == 0 && 281 strchr("!$&'()*+,-./:;=?@_~", *cp) == NULL) 282 *cp = '_'; 283 284 if (unique == 0) 285 return buf; 286 287 /* Avoid duplicate HTML id= attributes. */ 288 289 bufs = NULL; 290 suffix = 1; 291 slot = ohash_qlookup(&id_unique, buf); 292 cp = ohash_find(&id_unique, slot); 293 if (cp != NULL) { 294 while (cp != NULL) { 295 free(bufs); 296 if (++suffix > 127) { 297 free(buf); 298 return NULL; 299 } 300 mandoc_asprintf(&bufs, "%s_%d", buf, suffix); 301 slot = ohash_qlookup(&id_unique, bufs); 302 cp = ohash_find(&id_unique, slot); 303 } 304 free(buf); 305 buf = bufs; 306 } 307 ohash_insert(&id_unique, slot, buf); 308 return buf; 309 } 310 311 static int 312 print_escape(struct html *h, char c) 313 { 314 315 switch (c) { 316 case '<': 317 print_word(h, "<"); 318 break; 319 case '>': 320 print_word(h, ">"); 321 break; 322 case '&': 323 print_word(h, "&"); 324 break; 325 case '"': 326 print_word(h, """); 327 break; 328 case ASCII_NBRSP: 329 print_word(h, " "); 330 break; 331 case ASCII_HYPH: 332 print_byte(h, '-'); 333 break; 334 case ASCII_BREAK: 335 break; 336 default: 337 return 0; 338 } 339 return 1; 340 } 341 342 static int 343 print_encode(struct html *h, const char *p, const char *pend, int norecurse) 344 { 345 char numbuf[16]; 346 struct tag *t; 347 const char *seq; 348 size_t sz; 349 int c, len, breakline, nospace; 350 enum mandoc_esc esc; 351 static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"', 352 ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' }; 353 354 if (pend == NULL) 355 pend = strchr(p, '\0'); 356 357 breakline = 0; 358 nospace = 0; 359 360 while (p < pend) { 361 if (HTML_SKIPCHAR & h->flags && '\\' != *p) { 362 h->flags &= ~HTML_SKIPCHAR; 363 p++; 364 continue; 365 } 366 367 for (sz = strcspn(p, rejs); sz-- && p < pend; p++) 368 print_byte(h, *p); 369 370 if (breakline && 371 (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) { 372 t = print_otag(h, TAG_DIV, ""); 373 print_text(h, "\\~"); 374 print_tagq(h, t); 375 breakline = 0; 376 while (p < pend && (*p == ' ' || *p == ASCII_NBRSP)) 377 p++; 378 continue; 379 } 380 381 if (p >= pend) 382 break; 383 384 if (*p == ' ') { 385 print_endword(h); 386 p++; 387 continue; 388 } 389 390 if (print_escape(h, *p++)) 391 continue; 392 393 esc = mandoc_escape(&p, &seq, &len); 394 if (ESCAPE_ERROR == esc) 395 break; 396 397 switch (esc) { 398 case ESCAPE_FONT: 399 case ESCAPE_FONTPREV: 400 case ESCAPE_FONTBOLD: 401 case ESCAPE_FONTITALIC: 402 case ESCAPE_FONTBI: 403 case ESCAPE_FONTROMAN: 404 if (0 == norecurse) 405 print_metaf(h, esc); 406 continue; 407 case ESCAPE_SKIPCHAR: 408 h->flags |= HTML_SKIPCHAR; 409 continue; 410 default: 411 break; 412 } 413 414 if (h->flags & HTML_SKIPCHAR) { 415 h->flags &= ~HTML_SKIPCHAR; 416 continue; 417 } 418 419 switch (esc) { 420 case ESCAPE_UNICODE: 421 /* Skip past "u" header. */ 422 c = mchars_num2uc(seq + 1, len - 1); 423 break; 424 case ESCAPE_NUMBERED: 425 c = mchars_num2char(seq, len); 426 if (c < 0) 427 continue; 428 break; 429 case ESCAPE_SPECIAL: 430 c = mchars_spec2cp(seq, len); 431 if (c <= 0) 432 continue; 433 break; 434 case ESCAPE_BREAK: 435 breakline = 1; 436 continue; 437 case ESCAPE_NOSPACE: 438 if ('\0' == *p) 439 nospace = 1; 440 continue; 441 case ESCAPE_OVERSTRIKE: 442 if (len == 0) 443 continue; 444 c = seq[len - 1]; 445 break; 446 default: 447 continue; 448 } 449 if ((c < 0x20 && c != 0x09) || 450 (c > 0x7E && c < 0xA0)) 451 c = 0xFFFD; 452 if (c > 0x7E) { 453 (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c); 454 print_word(h, numbuf); 455 } else if (print_escape(h, c) == 0) 456 print_byte(h, c); 457 } 458 459 return nospace; 460 } 461 462 static void 463 print_href(struct html *h, const char *name, const char *sec, int man) 464 { 465 const char *p, *pp; 466 467 pp = man ? h->base_man : h->base_includes; 468 while ((p = strchr(pp, '%')) != NULL) { 469 print_encode(h, pp, p, 1); 470 if (man && p[1] == 'S') { 471 if (sec == NULL) 472 print_byte(h, '1'); 473 else 474 print_encode(h, sec, NULL, 1); 475 } else if ((man && p[1] == 'N') || 476 (man == 0 && p[1] == 'I')) 477 print_encode(h, name, NULL, 1); 478 else 479 print_encode(h, p, p + 2, 1); 480 pp = p + 2; 481 } 482 if (*pp != '\0') 483 print_encode(h, pp, NULL, 1); 484 } 485 486 struct tag * 487 print_otag(struct html *h, enum htmltag tag, const char *fmt, ...) 488 { 489 va_list ap; 490 struct tag *t; 491 const char *attr; 492 char *arg1, *arg2; 493 int tflags; 494 495 tflags = htmltags[tag].flags; 496 497 /* Push this tag onto the stack of open scopes. */ 498 499 if ((tflags & HTML_NOSTACK) == 0) { 500 t = mandoc_malloc(sizeof(struct tag)); 501 t->tag = tag; 502 t->next = h->tag; 503 h->tag = t; 504 } else 505 t = NULL; 506 507 if (tflags & HTML_NLBEFORE) 508 print_endline(h); 509 if (h->col == 0) 510 print_indent(h); 511 else if ((h->flags & HTML_NOSPACE) == 0) { 512 if (h->flags & HTML_KEEP) 513 print_word(h, " "); 514 else { 515 if (h->flags & HTML_PREKEEP) 516 h->flags |= HTML_KEEP; 517 print_endword(h); 518 } 519 } 520 521 if ( ! (h->flags & HTML_NONOSPACE)) 522 h->flags &= ~HTML_NOSPACE; 523 else 524 h->flags |= HTML_NOSPACE; 525 526 /* Print out the tag name and attributes. */ 527 528 print_byte(h, '<'); 529 print_word(h, htmltags[tag].name); 530 531 va_start(ap, fmt); 532 533 while (*fmt != '\0') { 534 535 /* Parse attributes and arguments. */ 536 537 arg1 = va_arg(ap, char *); 538 arg2 = NULL; 539 switch (*fmt++) { 540 case 'c': 541 attr = "class"; 542 break; 543 case 'h': 544 attr = "href"; 545 break; 546 case 'i': 547 attr = "id"; 548 break; 549 case 's': 550 attr = "style"; 551 arg2 = va_arg(ap, char *); 552 break; 553 case '?': 554 attr = arg1; 555 arg1 = va_arg(ap, char *); 556 break; 557 default: 558 abort(); 559 } 560 if (*fmt == 'M') 561 arg2 = va_arg(ap, char *); 562 if (arg1 == NULL) 563 continue; 564 565 /* Print the attributes. */ 566 567 print_byte(h, ' '); 568 print_word(h, attr); 569 print_byte(h, '='); 570 print_byte(h, '"'); 571 switch (*fmt) { 572 case 'I': 573 print_href(h, arg1, NULL, 0); 574 fmt++; 575 break; 576 case 'M': 577 print_href(h, arg1, arg2, 1); 578 fmt++; 579 break; 580 case 'R': 581 print_byte(h, '#'); 582 print_encode(h, arg1, NULL, 1); 583 fmt++; 584 break; 585 case 'T': 586 print_encode(h, arg1, NULL, 1); 587 print_word(h, "\" title=\""); 588 print_encode(h, arg1, NULL, 1); 589 fmt++; 590 break; 591 default: 592 if (arg2 == NULL) 593 print_encode(h, arg1, NULL, 1); 594 else { 595 print_word(h, arg1); 596 print_byte(h, ':'); 597 print_byte(h, ' '); 598 print_word(h, arg2); 599 print_byte(h, ';'); 600 } 601 break; 602 } 603 print_byte(h, '"'); 604 } 605 va_end(ap); 606 607 /* Accommodate for "well-formed" singleton escaping. */ 608 609 if (HTML_AUTOCLOSE & htmltags[tag].flags) 610 print_byte(h, '/'); 611 612 print_byte(h, '>'); 613 614 if (tflags & HTML_NLBEGIN) 615 print_endline(h); 616 else 617 h->flags |= HTML_NOSPACE; 618 619 if (tflags & HTML_INDENT) 620 h->indent++; 621 if (tflags & HTML_NOINDENT) 622 h->noindent++; 623 624 return t; 625 } 626 627 static void 628 print_ctag(struct html *h, struct tag *tag) 629 { 630 int tflags; 631 632 /* 633 * Remember to close out and nullify the current 634 * meta-font and table, if applicable. 635 */ 636 if (tag == h->metaf) 637 h->metaf = NULL; 638 if (tag == h->tblt) 639 h->tblt = NULL; 640 641 tflags = htmltags[tag->tag].flags; 642 643 if (tflags & HTML_INDENT) 644 h->indent--; 645 if (tflags & HTML_NOINDENT) 646 h->noindent--; 647 if (tflags & HTML_NLEND) 648 print_endline(h); 649 print_indent(h); 650 print_byte(h, '<'); 651 print_byte(h, '/'); 652 print_word(h, htmltags[tag->tag].name); 653 print_byte(h, '>'); 654 if (tflags & HTML_NLAFTER) 655 print_endline(h); 656 657 h->tag = tag->next; 658 free(tag); 659 } 660 661 void 662 print_gen_decls(struct html *h) 663 { 664 print_word(h, "<!DOCTYPE html>"); 665 print_endline(h); 666 } 667 668 void 669 print_gen_comment(struct html *h, struct roff_node *n) 670 { 671 int wantblank; 672 673 print_word(h, "<!-- This is an automatically generated file." 674 " Do not edit."); 675 h->indent = 1; 676 wantblank = 0; 677 while (n != NULL && n->type == ROFFT_COMMENT) { 678 if (strstr(n->string, "-->") == NULL && 679 (wantblank || *n->string != '\0')) { 680 print_endline(h); 681 print_indent(h); 682 print_word(h, n->string); 683 wantblank = *n->string != '\0'; 684 } 685 n = n->next; 686 } 687 if (wantblank) 688 print_endline(h); 689 print_word(h, " -->"); 690 print_endline(h); 691 h->indent = 0; 692 } 693 694 void 695 print_text(struct html *h, const char *word) 696 { 697 if (h->col && (h->flags & HTML_NOSPACE) == 0) { 698 if ( ! (HTML_KEEP & h->flags)) { 699 if (HTML_PREKEEP & h->flags) 700 h->flags |= HTML_KEEP; 701 print_endword(h); 702 } else 703 print_word(h, " "); 704 } 705 706 assert(NULL == h->metaf); 707 switch (h->metac) { 708 case HTMLFONT_ITALIC: 709 h->metaf = print_otag(h, TAG_I, ""); 710 break; 711 case HTMLFONT_BOLD: 712 h->metaf = print_otag(h, TAG_B, ""); 713 break; 714 case HTMLFONT_BI: 715 h->metaf = print_otag(h, TAG_B, ""); 716 print_otag(h, TAG_I, ""); 717 break; 718 default: 719 print_indent(h); 720 break; 721 } 722 723 assert(word); 724 if ( ! print_encode(h, word, NULL, 0)) { 725 if ( ! (h->flags & HTML_NONOSPACE)) 726 h->flags &= ~HTML_NOSPACE; 727 h->flags &= ~HTML_NONEWLINE; 728 } else 729 h->flags |= HTML_NOSPACE | HTML_NONEWLINE; 730 731 if (h->metaf) { 732 print_tagq(h, h->metaf); 733 h->metaf = NULL; 734 } 735 736 h->flags &= ~HTML_IGNDELIM; 737 } 738 739 void 740 print_tagq(struct html *h, const struct tag *until) 741 { 742 struct tag *tag; 743 744 while ((tag = h->tag) != NULL) { 745 print_ctag(h, tag); 746 if (until && tag == until) 747 return; 748 } 749 } 750 751 void 752 print_stagq(struct html *h, const struct tag *suntil) 753 { 754 struct tag *tag; 755 756 while ((tag = h->tag) != NULL) { 757 if (suntil && tag == suntil) 758 return; 759 print_ctag(h, tag); 760 } 761 } 762 763 void 764 print_paragraph(struct html *h) 765 { 766 struct tag *t; 767 768 t = print_otag(h, TAG_DIV, "c", "Pp"); 769 print_tagq(h, t); 770 } 771 772 773 /*********************************************************************** 774 * Low level output functions. 775 * They implement line breaking using a short static buffer. 776 ***********************************************************************/ 777 778 /* 779 * Buffer one HTML output byte. 780 * If the buffer is full, flush and deactivate it and start a new line. 781 * If the buffer is inactive, print directly. 782 */ 783 static void 784 print_byte(struct html *h, char c) 785 { 786 if ((h->flags & HTML_BUFFER) == 0) { 787 putchar(c); 788 h->col++; 789 return; 790 } 791 792 if (h->col + h->bufcol < sizeof(h->buf)) { 793 h->buf[h->bufcol++] = c; 794 return; 795 } 796 797 putchar('\n'); 798 h->col = 0; 799 print_indent(h); 800 putchar(' '); 801 putchar(' '); 802 fwrite(h->buf, h->bufcol, 1, stdout); 803 putchar(c); 804 h->col = (h->indent + 1) * 2 + h->bufcol + 1; 805 h->bufcol = 0; 806 h->flags &= ~HTML_BUFFER; 807 } 808 809 /* 810 * If something was printed on the current output line, end it. 811 * Not to be called right after print_indent(). 812 */ 813 void 814 print_endline(struct html *h) 815 { 816 if (h->col == 0) 817 return; 818 819 if (h->bufcol) { 820 putchar(' '); 821 fwrite(h->buf, h->bufcol, 1, stdout); 822 h->bufcol = 0; 823 } 824 putchar('\n'); 825 h->col = 0; 826 h->flags |= HTML_NOSPACE; 827 h->flags &= ~HTML_BUFFER; 828 } 829 830 /* 831 * Flush the HTML output buffer. 832 * If it is inactive, activate it. 833 */ 834 static void 835 print_endword(struct html *h) 836 { 837 if (h->noindent) { 838 print_byte(h, ' '); 839 return; 840 } 841 842 if ((h->flags & HTML_BUFFER) == 0) { 843 h->col++; 844 h->flags |= HTML_BUFFER; 845 } else if (h->bufcol) { 846 putchar(' '); 847 fwrite(h->buf, h->bufcol, 1, stdout); 848 h->col += h->bufcol + 1; 849 } 850 h->bufcol = 0; 851 } 852 853 /* 854 * If at the beginning of a new output line, 855 * perform indentation and mark the line as containing output. 856 * Make sure to really produce some output right afterwards, 857 * but do not use print_otag() for producing it. 858 */ 859 static void 860 print_indent(struct html *h) 861 { 862 size_t i; 863 864 if (h->col) 865 return; 866 867 if (h->noindent == 0) { 868 h->col = h->indent * 2; 869 for (i = 0; i < h->col; i++) 870 putchar(' '); 871 } 872 h->flags &= ~HTML_NOSPACE; 873 } 874 875 /* 876 * Print or buffer some characters 877 * depending on the current HTML output buffer state. 878 */ 879 static void 880 print_word(struct html *h, const char *cp) 881 { 882 while (*cp != '\0') 883 print_byte(h, *cp++); 884 } 885