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