]> git.cameronkatri.com Git - mandoc.git/blob - html.c
Mostly complete implementation of the 'c' (character available)
[mandoc.git] / html.c
1 /* $Id: html.c,v 1.239 2018/08/16 13:54:06 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_DEVICE:
437 print_word(h, "html");
438 continue;
439 case ESCAPE_BREAK:
440 breakline = 1;
441 continue;
442 case ESCAPE_NOSPACE:
443 if ('\0' == *p)
444 nospace = 1;
445 continue;
446 case ESCAPE_OVERSTRIKE:
447 if (len == 0)
448 continue;
449 c = seq[len - 1];
450 break;
451 default:
452 continue;
453 }
454 if ((c < 0x20 && c != 0x09) ||
455 (c > 0x7E && c < 0xA0))
456 c = 0xFFFD;
457 if (c > 0x7E) {
458 (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c);
459 print_word(h, numbuf);
460 } else if (print_escape(h, c) == 0)
461 print_byte(h, c);
462 }
463
464 return nospace;
465 }
466
467 static void
468 print_href(struct html *h, const char *name, const char *sec, int man)
469 {
470 const char *p, *pp;
471
472 pp = man ? h->base_man : h->base_includes;
473 while ((p = strchr(pp, '%')) != NULL) {
474 print_encode(h, pp, p, 1);
475 if (man && p[1] == 'S') {
476 if (sec == NULL)
477 print_byte(h, '1');
478 else
479 print_encode(h, sec, NULL, 1);
480 } else if ((man && p[1] == 'N') ||
481 (man == 0 && p[1] == 'I'))
482 print_encode(h, name, NULL, 1);
483 else
484 print_encode(h, p, p + 2, 1);
485 pp = p + 2;
486 }
487 if (*pp != '\0')
488 print_encode(h, pp, NULL, 1);
489 }
490
491 struct tag *
492 print_otag(struct html *h, enum htmltag tag, const char *fmt, ...)
493 {
494 va_list ap;
495 struct tag *t;
496 const char *attr;
497 char *arg1, *arg2;
498 int tflags;
499
500 tflags = htmltags[tag].flags;
501
502 /* Push this tag onto the stack of open scopes. */
503
504 if ((tflags & HTML_NOSTACK) == 0) {
505 t = mandoc_malloc(sizeof(struct tag));
506 t->tag = tag;
507 t->next = h->tag;
508 h->tag = t;
509 } else
510 t = NULL;
511
512 if (tflags & HTML_NLBEFORE)
513 print_endline(h);
514 if (h->col == 0)
515 print_indent(h);
516 else if ((h->flags & HTML_NOSPACE) == 0) {
517 if (h->flags & HTML_KEEP)
518 print_word(h, "&#x00A0;");
519 else {
520 if (h->flags & HTML_PREKEEP)
521 h->flags |= HTML_KEEP;
522 print_endword(h);
523 }
524 }
525
526 if ( ! (h->flags & HTML_NONOSPACE))
527 h->flags &= ~HTML_NOSPACE;
528 else
529 h->flags |= HTML_NOSPACE;
530
531 /* Print out the tag name and attributes. */
532
533 print_byte(h, '<');
534 print_word(h, htmltags[tag].name);
535
536 va_start(ap, fmt);
537
538 while (*fmt != '\0') {
539
540 /* Parse attributes and arguments. */
541
542 arg1 = va_arg(ap, char *);
543 arg2 = NULL;
544 switch (*fmt++) {
545 case 'c':
546 attr = "class";
547 break;
548 case 'h':
549 attr = "href";
550 break;
551 case 'i':
552 attr = "id";
553 break;
554 case 's':
555 attr = "style";
556 arg2 = va_arg(ap, char *);
557 break;
558 case '?':
559 attr = arg1;
560 arg1 = va_arg(ap, char *);
561 break;
562 default:
563 abort();
564 }
565 if (*fmt == 'M')
566 arg2 = va_arg(ap, char *);
567 if (arg1 == NULL)
568 continue;
569
570 /* Print the attributes. */
571
572 print_byte(h, ' ');
573 print_word(h, attr);
574 print_byte(h, '=');
575 print_byte(h, '"');
576 switch (*fmt) {
577 case 'I':
578 print_href(h, arg1, NULL, 0);
579 fmt++;
580 break;
581 case 'M':
582 print_href(h, arg1, arg2, 1);
583 fmt++;
584 break;
585 case 'R':
586 print_byte(h, '#');
587 print_encode(h, arg1, NULL, 1);
588 fmt++;
589 break;
590 case 'T':
591 print_encode(h, arg1, NULL, 1);
592 print_word(h, "\" title=\"");
593 print_encode(h, arg1, NULL, 1);
594 fmt++;
595 break;
596 default:
597 if (arg2 == NULL)
598 print_encode(h, arg1, NULL, 1);
599 else {
600 print_word(h, arg1);
601 print_byte(h, ':');
602 print_byte(h, ' ');
603 print_word(h, arg2);
604 print_byte(h, ';');
605 }
606 break;
607 }
608 print_byte(h, '"');
609 }
610 va_end(ap);
611
612 /* Accommodate for "well-formed" singleton escaping. */
613
614 if (HTML_AUTOCLOSE & htmltags[tag].flags)
615 print_byte(h, '/');
616
617 print_byte(h, '>');
618
619 if (tflags & HTML_NLBEGIN)
620 print_endline(h);
621 else
622 h->flags |= HTML_NOSPACE;
623
624 if (tflags & HTML_INDENT)
625 h->indent++;
626 if (tflags & HTML_NOINDENT)
627 h->noindent++;
628
629 return t;
630 }
631
632 static void
633 print_ctag(struct html *h, struct tag *tag)
634 {
635 int tflags;
636
637 /*
638 * Remember to close out and nullify the current
639 * meta-font and table, if applicable.
640 */
641 if (tag == h->metaf)
642 h->metaf = NULL;
643 if (tag == h->tblt)
644 h->tblt = NULL;
645
646 tflags = htmltags[tag->tag].flags;
647
648 if (tflags & HTML_INDENT)
649 h->indent--;
650 if (tflags & HTML_NOINDENT)
651 h->noindent--;
652 if (tflags & HTML_NLEND)
653 print_endline(h);
654 print_indent(h);
655 print_byte(h, '<');
656 print_byte(h, '/');
657 print_word(h, htmltags[tag->tag].name);
658 print_byte(h, '>');
659 if (tflags & HTML_NLAFTER)
660 print_endline(h);
661
662 h->tag = tag->next;
663 free(tag);
664 }
665
666 void
667 print_gen_decls(struct html *h)
668 {
669 print_word(h, "<!DOCTYPE html>");
670 print_endline(h);
671 }
672
673 void
674 print_gen_comment(struct html *h, struct roff_node *n)
675 {
676 int wantblank;
677
678 print_word(h, "<!-- This is an automatically generated file."
679 " Do not edit.");
680 h->indent = 1;
681 wantblank = 0;
682 while (n != NULL && n->type == ROFFT_COMMENT) {
683 if (strstr(n->string, "-->") == NULL &&
684 (wantblank || *n->string != '\0')) {
685 print_endline(h);
686 print_indent(h);
687 print_word(h, n->string);
688 wantblank = *n->string != '\0';
689 }
690 n = n->next;
691 }
692 if (wantblank)
693 print_endline(h);
694 print_word(h, " -->");
695 print_endline(h);
696 h->indent = 0;
697 }
698
699 void
700 print_text(struct html *h, const char *word)
701 {
702 if (h->col && (h->flags & HTML_NOSPACE) == 0) {
703 if ( ! (HTML_KEEP & h->flags)) {
704 if (HTML_PREKEEP & h->flags)
705 h->flags |= HTML_KEEP;
706 print_endword(h);
707 } else
708 print_word(h, "&#x00A0;");
709 }
710
711 assert(NULL == h->metaf);
712 switch (h->metac) {
713 case HTMLFONT_ITALIC:
714 h->metaf = print_otag(h, TAG_I, "");
715 break;
716 case HTMLFONT_BOLD:
717 h->metaf = print_otag(h, TAG_B, "");
718 break;
719 case HTMLFONT_BI:
720 h->metaf = print_otag(h, TAG_B, "");
721 print_otag(h, TAG_I, "");
722 break;
723 default:
724 print_indent(h);
725 break;
726 }
727
728 assert(word);
729 if ( ! print_encode(h, word, NULL, 0)) {
730 if ( ! (h->flags & HTML_NONOSPACE))
731 h->flags &= ~HTML_NOSPACE;
732 h->flags &= ~HTML_NONEWLINE;
733 } else
734 h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
735
736 if (h->metaf) {
737 print_tagq(h, h->metaf);
738 h->metaf = NULL;
739 }
740
741 h->flags &= ~HTML_IGNDELIM;
742 }
743
744 void
745 print_tagq(struct html *h, const struct tag *until)
746 {
747 struct tag *tag;
748
749 while ((tag = h->tag) != NULL) {
750 print_ctag(h, tag);
751 if (until && tag == until)
752 return;
753 }
754 }
755
756 void
757 print_stagq(struct html *h, const struct tag *suntil)
758 {
759 struct tag *tag;
760
761 while ((tag = h->tag) != NULL) {
762 if (suntil && tag == suntil)
763 return;
764 print_ctag(h, tag);
765 }
766 }
767
768 void
769 print_paragraph(struct html *h)
770 {
771 struct tag *t;
772
773 t = print_otag(h, TAG_DIV, "c", "Pp");
774 print_tagq(h, t);
775 }
776
777
778 /***********************************************************************
779 * Low level output functions.
780 * They implement line breaking using a short static buffer.
781 ***********************************************************************/
782
783 /*
784 * Buffer one HTML output byte.
785 * If the buffer is full, flush and deactivate it and start a new line.
786 * If the buffer is inactive, print directly.
787 */
788 static void
789 print_byte(struct html *h, char c)
790 {
791 if ((h->flags & HTML_BUFFER) == 0) {
792 putchar(c);
793 h->col++;
794 return;
795 }
796
797 if (h->col + h->bufcol < sizeof(h->buf)) {
798 h->buf[h->bufcol++] = c;
799 return;
800 }
801
802 putchar('\n');
803 h->col = 0;
804 print_indent(h);
805 putchar(' ');
806 putchar(' ');
807 fwrite(h->buf, h->bufcol, 1, stdout);
808 putchar(c);
809 h->col = (h->indent + 1) * 2 + h->bufcol + 1;
810 h->bufcol = 0;
811 h->flags &= ~HTML_BUFFER;
812 }
813
814 /*
815 * If something was printed on the current output line, end it.
816 * Not to be called right after print_indent().
817 */
818 void
819 print_endline(struct html *h)
820 {
821 if (h->col == 0)
822 return;
823
824 if (h->bufcol) {
825 putchar(' ');
826 fwrite(h->buf, h->bufcol, 1, stdout);
827 h->bufcol = 0;
828 }
829 putchar('\n');
830 h->col = 0;
831 h->flags |= HTML_NOSPACE;
832 h->flags &= ~HTML_BUFFER;
833 }
834
835 /*
836 * Flush the HTML output buffer.
837 * If it is inactive, activate it.
838 */
839 static void
840 print_endword(struct html *h)
841 {
842 if (h->noindent) {
843 print_byte(h, ' ');
844 return;
845 }
846
847 if ((h->flags & HTML_BUFFER) == 0) {
848 h->col++;
849 h->flags |= HTML_BUFFER;
850 } else if (h->bufcol) {
851 putchar(' ');
852 fwrite(h->buf, h->bufcol, 1, stdout);
853 h->col += h->bufcol + 1;
854 }
855 h->bufcol = 0;
856 }
857
858 /*
859 * If at the beginning of a new output line,
860 * perform indentation and mark the line as containing output.
861 * Make sure to really produce some output right afterwards,
862 * but do not use print_otag() for producing it.
863 */
864 static void
865 print_indent(struct html *h)
866 {
867 size_t i;
868
869 if (h->col)
870 return;
871
872 if (h->noindent == 0) {
873 h->col = h->indent * 2;
874 for (i = 0; i < h->col; i++)
875 putchar(' ');
876 }
877 h->flags &= ~HTML_NOSPACE;
878 }
879
880 /*
881 * Print or buffer some characters
882 * depending on the current HTML output buffer state.
883 */
884 static void
885 print_word(struct html *h, const char *cp)
886 {
887 while (*cp != '\0')
888 print_byte(h, *cp++);
889 }