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