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