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