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, "&lt;");
 320                 break;
 321         case '>':
 322                 print_word(h, "&gt;");
 323                 break;
 324         case '&':
 325                 print_word(h, "&amp;");
 326                 break;
 327         case '"':
 328                 print_word(h, "&quot;");
 329                 break;
 330         case ASCII_NBRSP:
 331                 print_word(h, "&nbsp;");
 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, "&#x00A0;");
 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, "&#x00A0;");
 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 }