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