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