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