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