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