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