xref: /freebsd/contrib/mandoc/html.c (revision 7295610f5da64ab1818458ce007d9eb924496330)
1*7295610fSBaptiste Daroussin /*	$Id: html.c,v 1.254 2019/03/03 13:02:11 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
361d06d6bSBaptiste Daroussin  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4*7295610fSBaptiste Daroussin  * Copyright (c) 2011-2015, 2017-2019 Ingo Schwarze <schwarze@openbsd.org>
561d06d6bSBaptiste Daroussin  *
661d06d6bSBaptiste Daroussin  * Permission to use, copy, modify, and distribute this software for any
761d06d6bSBaptiste Daroussin  * purpose with or without fee is hereby granted, provided that the above
861d06d6bSBaptiste Daroussin  * copyright notice and this permission notice appear in all copies.
961d06d6bSBaptiste Daroussin  *
1061d06d6bSBaptiste Daroussin  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1161d06d6bSBaptiste Daroussin  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1261d06d6bSBaptiste Daroussin  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1361d06d6bSBaptiste Daroussin  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1461d06d6bSBaptiste Daroussin  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1561d06d6bSBaptiste Daroussin  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1661d06d6bSBaptiste Daroussin  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1761d06d6bSBaptiste Daroussin  */
1861d06d6bSBaptiste Daroussin #include "config.h"
1961d06d6bSBaptiste Daroussin 
2061d06d6bSBaptiste Daroussin #include <sys/types.h>
21*7295610fSBaptiste Daroussin #include <sys/stat.h>
2261d06d6bSBaptiste Daroussin 
2361d06d6bSBaptiste Daroussin #include <assert.h>
2461d06d6bSBaptiste Daroussin #include <ctype.h>
2561d06d6bSBaptiste Daroussin #include <stdarg.h>
2661d06d6bSBaptiste Daroussin #include <stddef.h>
2761d06d6bSBaptiste Daroussin #include <stdio.h>
2861d06d6bSBaptiste Daroussin #include <stdint.h>
2961d06d6bSBaptiste Daroussin #include <stdlib.h>
3061d06d6bSBaptiste Daroussin #include <string.h>
3161d06d6bSBaptiste Daroussin #include <unistd.h>
3261d06d6bSBaptiste Daroussin 
3361d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
3461d06d6bSBaptiste Daroussin #include "mandoc_ohash.h"
3561d06d6bSBaptiste Daroussin #include "mandoc.h"
3661d06d6bSBaptiste Daroussin #include "roff.h"
3761d06d6bSBaptiste Daroussin #include "out.h"
3861d06d6bSBaptiste Daroussin #include "html.h"
3961d06d6bSBaptiste Daroussin #include "manconf.h"
4061d06d6bSBaptiste Daroussin #include "main.h"
4161d06d6bSBaptiste Daroussin 
4261d06d6bSBaptiste Daroussin struct	htmldata {
4361d06d6bSBaptiste Daroussin 	const char	 *name;
4461d06d6bSBaptiste Daroussin 	int		  flags;
4561d06d6bSBaptiste Daroussin #define	HTML_NOSTACK	 (1 << 0)
4661d06d6bSBaptiste Daroussin #define	HTML_AUTOCLOSE	 (1 << 1)
4761d06d6bSBaptiste Daroussin #define	HTML_NLBEFORE	 (1 << 2)
4861d06d6bSBaptiste Daroussin #define	HTML_NLBEGIN	 (1 << 3)
4961d06d6bSBaptiste Daroussin #define	HTML_NLEND	 (1 << 4)
5061d06d6bSBaptiste Daroussin #define	HTML_NLAFTER	 (1 << 5)
5161d06d6bSBaptiste Daroussin #define	HTML_NLAROUND	 (HTML_NLBEFORE | HTML_NLAFTER)
5261d06d6bSBaptiste Daroussin #define	HTML_NLINSIDE	 (HTML_NLBEGIN | HTML_NLEND)
5361d06d6bSBaptiste Daroussin #define	HTML_NLALL	 (HTML_NLAROUND | HTML_NLINSIDE)
5461d06d6bSBaptiste Daroussin #define	HTML_INDENT	 (1 << 6)
5561d06d6bSBaptiste Daroussin #define	HTML_NOINDENT	 (1 << 7)
5661d06d6bSBaptiste Daroussin };
5761d06d6bSBaptiste Daroussin 
5861d06d6bSBaptiste Daroussin static	const struct htmldata htmltags[TAG_MAX] = {
5961d06d6bSBaptiste Daroussin 	{"html",	HTML_NLALL},
6061d06d6bSBaptiste Daroussin 	{"head",	HTML_NLALL | HTML_INDENT},
6161d06d6bSBaptiste Daroussin 	{"body",	HTML_NLALL},
6261d06d6bSBaptiste Daroussin 	{"meta",	HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
6361d06d6bSBaptiste Daroussin 	{"title",	HTML_NLAROUND},
6461d06d6bSBaptiste Daroussin 	{"div",		HTML_NLAROUND},
6561d06d6bSBaptiste Daroussin 	{"div",		0},
66*7295610fSBaptiste Daroussin 	{"section",	HTML_NLALL},
6761d06d6bSBaptiste Daroussin 	{"h1",		HTML_NLAROUND},
6861d06d6bSBaptiste Daroussin 	{"h2",		HTML_NLAROUND},
6961d06d6bSBaptiste Daroussin 	{"span",	0},
7061d06d6bSBaptiste Daroussin 	{"link",	HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
7161d06d6bSBaptiste Daroussin 	{"br",		HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
7261d06d6bSBaptiste Daroussin 	{"a",		0},
7361d06d6bSBaptiste Daroussin 	{"table",	HTML_NLALL | HTML_INDENT},
7461d06d6bSBaptiste Daroussin 	{"tr",		HTML_NLALL | HTML_INDENT},
7561d06d6bSBaptiste Daroussin 	{"td",		HTML_NLAROUND},
7661d06d6bSBaptiste Daroussin 	{"li",		HTML_NLAROUND | HTML_INDENT},
7761d06d6bSBaptiste Daroussin 	{"ul",		HTML_NLALL | HTML_INDENT},
7861d06d6bSBaptiste Daroussin 	{"ol",		HTML_NLALL | HTML_INDENT},
7961d06d6bSBaptiste Daroussin 	{"dl",		HTML_NLALL | HTML_INDENT},
8061d06d6bSBaptiste Daroussin 	{"dt",		HTML_NLAROUND},
8161d06d6bSBaptiste Daroussin 	{"dd",		HTML_NLAROUND | HTML_INDENT},
82*7295610fSBaptiste Daroussin 	{"p",		HTML_NLAROUND | HTML_INDENT},
8361d06d6bSBaptiste Daroussin 	{"pre",		HTML_NLALL | HTML_NOINDENT},
8461d06d6bSBaptiste Daroussin 	{"var",		0},
8561d06d6bSBaptiste Daroussin 	{"cite",	0},
8661d06d6bSBaptiste Daroussin 	{"b",		0},
8761d06d6bSBaptiste Daroussin 	{"i",		0},
8861d06d6bSBaptiste Daroussin 	{"code",	0},
8961d06d6bSBaptiste Daroussin 	{"small",	0},
9061d06d6bSBaptiste Daroussin 	{"style",	HTML_NLALL | HTML_INDENT},
9161d06d6bSBaptiste Daroussin 	{"math",	HTML_NLALL | HTML_INDENT},
9261d06d6bSBaptiste Daroussin 	{"mrow",	0},
9361d06d6bSBaptiste Daroussin 	{"mi",		0},
9461d06d6bSBaptiste Daroussin 	{"mn",		0},
9561d06d6bSBaptiste Daroussin 	{"mo",		0},
9661d06d6bSBaptiste Daroussin 	{"msup",	0},
9761d06d6bSBaptiste Daroussin 	{"msub",	0},
9861d06d6bSBaptiste Daroussin 	{"msubsup",	0},
9961d06d6bSBaptiste Daroussin 	{"mfrac",	0},
10061d06d6bSBaptiste Daroussin 	{"msqrt",	0},
10161d06d6bSBaptiste Daroussin 	{"mfenced",	0},
10261d06d6bSBaptiste Daroussin 	{"mtable",	0},
10361d06d6bSBaptiste Daroussin 	{"mtr",		0},
10461d06d6bSBaptiste Daroussin 	{"mtd",		0},
10561d06d6bSBaptiste Daroussin 	{"munderover",	0},
10661d06d6bSBaptiste Daroussin 	{"munder",	0},
10761d06d6bSBaptiste Daroussin 	{"mover",	0},
10861d06d6bSBaptiste Daroussin };
10961d06d6bSBaptiste Daroussin 
11061d06d6bSBaptiste Daroussin /* Avoid duplicate HTML id= attributes. */
11161d06d6bSBaptiste Daroussin static	struct ohash	 id_unique;
11261d06d6bSBaptiste Daroussin 
113*7295610fSBaptiste Daroussin static	void	 html_reset_internal(struct html *);
11461d06d6bSBaptiste Daroussin static	void	 print_byte(struct html *, char);
11561d06d6bSBaptiste Daroussin static	void	 print_endword(struct html *);
11661d06d6bSBaptiste Daroussin static	void	 print_indent(struct html *);
11761d06d6bSBaptiste Daroussin static	void	 print_word(struct html *, const char *);
11861d06d6bSBaptiste Daroussin 
11961d06d6bSBaptiste Daroussin static	void	 print_ctag(struct html *, struct tag *);
12061d06d6bSBaptiste Daroussin static	int	 print_escape(struct html *, char);
12161d06d6bSBaptiste Daroussin static	int	 print_encode(struct html *, const char *, const char *, int);
12261d06d6bSBaptiste Daroussin static	void	 print_href(struct html *, const char *, const char *, int);
12361d06d6bSBaptiste Daroussin 
12461d06d6bSBaptiste Daroussin 
12561d06d6bSBaptiste Daroussin void *
12661d06d6bSBaptiste Daroussin html_alloc(const struct manoutput *outopts)
12761d06d6bSBaptiste Daroussin {
12861d06d6bSBaptiste Daroussin 	struct html	*h;
12961d06d6bSBaptiste Daroussin 
13061d06d6bSBaptiste Daroussin 	h = mandoc_calloc(1, sizeof(struct html));
13161d06d6bSBaptiste Daroussin 
13261d06d6bSBaptiste Daroussin 	h->tag = NULL;
13361d06d6bSBaptiste Daroussin 	h->style = outopts->style;
134*7295610fSBaptiste Daroussin 	if ((h->base_man1 = outopts->man) == NULL)
135*7295610fSBaptiste Daroussin 		h->base_man2 = NULL;
136*7295610fSBaptiste Daroussin 	else if ((h->base_man2 = strchr(h->base_man1, ';')) != NULL)
137*7295610fSBaptiste Daroussin 		*h->base_man2++ = '\0';
13861d06d6bSBaptiste Daroussin 	h->base_includes = outopts->includes;
13961d06d6bSBaptiste Daroussin 	if (outopts->fragment)
14061d06d6bSBaptiste Daroussin 		h->oflags |= HTML_FRAGMENT;
141*7295610fSBaptiste Daroussin 	if (outopts->toc)
142*7295610fSBaptiste Daroussin 		h->oflags |= HTML_TOC;
14361d06d6bSBaptiste Daroussin 
14461d06d6bSBaptiste Daroussin 	mandoc_ohash_init(&id_unique, 4, 0);
14561d06d6bSBaptiste Daroussin 
14661d06d6bSBaptiste Daroussin 	return h;
14761d06d6bSBaptiste Daroussin }
14861d06d6bSBaptiste Daroussin 
149*7295610fSBaptiste Daroussin static void
150*7295610fSBaptiste Daroussin html_reset_internal(struct html *h)
15161d06d6bSBaptiste Daroussin {
15261d06d6bSBaptiste Daroussin 	struct tag	*tag;
15361d06d6bSBaptiste Daroussin 	char		*cp;
15461d06d6bSBaptiste Daroussin 	unsigned int	 slot;
15561d06d6bSBaptiste Daroussin 
15661d06d6bSBaptiste Daroussin 	while ((tag = h->tag) != NULL) {
15761d06d6bSBaptiste Daroussin 		h->tag = tag->next;
15861d06d6bSBaptiste Daroussin 		free(tag);
15961d06d6bSBaptiste Daroussin 	}
16061d06d6bSBaptiste Daroussin 	cp = ohash_first(&id_unique, &slot);
16161d06d6bSBaptiste Daroussin 	while (cp != NULL) {
16261d06d6bSBaptiste Daroussin 		free(cp);
16361d06d6bSBaptiste Daroussin 		cp = ohash_next(&id_unique, &slot);
16461d06d6bSBaptiste Daroussin 	}
16561d06d6bSBaptiste Daroussin 	ohash_delete(&id_unique);
16661d06d6bSBaptiste Daroussin }
16761d06d6bSBaptiste Daroussin 
16861d06d6bSBaptiste Daroussin void
169*7295610fSBaptiste Daroussin html_reset(void *p)
170*7295610fSBaptiste Daroussin {
171*7295610fSBaptiste Daroussin 	html_reset_internal(p);
172*7295610fSBaptiste Daroussin 	mandoc_ohash_init(&id_unique, 4, 0);
173*7295610fSBaptiste Daroussin }
174*7295610fSBaptiste Daroussin 
175*7295610fSBaptiste Daroussin void
176*7295610fSBaptiste Daroussin html_free(void *p)
177*7295610fSBaptiste Daroussin {
178*7295610fSBaptiste Daroussin 	html_reset_internal(p);
179*7295610fSBaptiste Daroussin 	free(p);
180*7295610fSBaptiste Daroussin }
181*7295610fSBaptiste Daroussin 
182*7295610fSBaptiste Daroussin void
18361d06d6bSBaptiste Daroussin print_gen_head(struct html *h)
18461d06d6bSBaptiste Daroussin {
18561d06d6bSBaptiste Daroussin 	struct tag	*t;
18661d06d6bSBaptiste Daroussin 
18761d06d6bSBaptiste Daroussin 	print_otag(h, TAG_META, "?", "charset", "utf-8");
18861d06d6bSBaptiste Daroussin 	if (h->style != NULL) {
18961d06d6bSBaptiste Daroussin 		print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet",
19061d06d6bSBaptiste Daroussin 		    h->style, "type", "text/css", "media", "all");
19161d06d6bSBaptiste Daroussin 		return;
19261d06d6bSBaptiste Daroussin 	}
19361d06d6bSBaptiste Daroussin 
19461d06d6bSBaptiste Daroussin 	/*
19561d06d6bSBaptiste Daroussin 	 * Print a minimal embedded style sheet.
19661d06d6bSBaptiste Daroussin 	 */
19761d06d6bSBaptiste Daroussin 
19861d06d6bSBaptiste Daroussin 	t = print_otag(h, TAG_STYLE, "");
19961d06d6bSBaptiste Daroussin 	print_text(h, "table.head, table.foot { width: 100%; }");
20061d06d6bSBaptiste Daroussin 	print_endline(h);
20161d06d6bSBaptiste Daroussin 	print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }");
20261d06d6bSBaptiste Daroussin 	print_endline(h);
20361d06d6bSBaptiste Daroussin 	print_text(h, "td.head-vol { text-align: center; }");
20461d06d6bSBaptiste Daroussin 	print_endline(h);
20561d06d6bSBaptiste Daroussin 	print_text(h, "div.Pp { margin: 1ex 0ex; }");
20661d06d6bSBaptiste Daroussin 	print_endline(h);
20761d06d6bSBaptiste Daroussin 	print_text(h, "div.Nd, div.Bf, div.Op { display: inline; }");
20861d06d6bSBaptiste Daroussin 	print_endline(h);
20961d06d6bSBaptiste Daroussin 	print_text(h, "span.Pa, span.Ad { font-style: italic; }");
21061d06d6bSBaptiste Daroussin 	print_endline(h);
21161d06d6bSBaptiste Daroussin 	print_text(h, "span.Ms { font-weight: bold; }");
21261d06d6bSBaptiste Daroussin 	print_endline(h);
21361d06d6bSBaptiste Daroussin 	print_text(h, "dl.Bl-diag ");
21461d06d6bSBaptiste Daroussin 	print_byte(h, '>');
21561d06d6bSBaptiste Daroussin 	print_text(h, " dt { font-weight: bold; }");
21661d06d6bSBaptiste Daroussin 	print_endline(h);
21761d06d6bSBaptiste Daroussin 	print_text(h, "code.Nm, code.Fl, code.Cm, code.Ic, "
21861d06d6bSBaptiste Daroussin 	    "code.In, code.Fd, code.Fn,");
21961d06d6bSBaptiste Daroussin 	print_endline(h);
22061d06d6bSBaptiste Daroussin 	print_text(h, "code.Cd { font-weight: bold; "
22161d06d6bSBaptiste Daroussin 	    "font-family: inherit; }");
22261d06d6bSBaptiste Daroussin 	print_tagq(h, t);
22361d06d6bSBaptiste Daroussin }
22461d06d6bSBaptiste Daroussin 
225*7295610fSBaptiste Daroussin void
22661d06d6bSBaptiste Daroussin print_metaf(struct html *h, enum mandoc_esc deco)
22761d06d6bSBaptiste Daroussin {
22861d06d6bSBaptiste Daroussin 	enum htmlfont	 font;
22961d06d6bSBaptiste Daroussin 
23061d06d6bSBaptiste Daroussin 	switch (deco) {
23161d06d6bSBaptiste Daroussin 	case ESCAPE_FONTPREV:
23261d06d6bSBaptiste Daroussin 		font = h->metal;
23361d06d6bSBaptiste Daroussin 		break;
23461d06d6bSBaptiste Daroussin 	case ESCAPE_FONTITALIC:
23561d06d6bSBaptiste Daroussin 		font = HTMLFONT_ITALIC;
23661d06d6bSBaptiste Daroussin 		break;
23761d06d6bSBaptiste Daroussin 	case ESCAPE_FONTBOLD:
23861d06d6bSBaptiste Daroussin 		font = HTMLFONT_BOLD;
23961d06d6bSBaptiste Daroussin 		break;
24061d06d6bSBaptiste Daroussin 	case ESCAPE_FONTBI:
24161d06d6bSBaptiste Daroussin 		font = HTMLFONT_BI;
24261d06d6bSBaptiste Daroussin 		break;
243*7295610fSBaptiste Daroussin 	case ESCAPE_FONTCW:
244*7295610fSBaptiste Daroussin 		font = HTMLFONT_CW;
245*7295610fSBaptiste Daroussin 		break;
24661d06d6bSBaptiste Daroussin 	case ESCAPE_FONT:
24761d06d6bSBaptiste Daroussin 	case ESCAPE_FONTROMAN:
24861d06d6bSBaptiste Daroussin 		font = HTMLFONT_NONE;
24961d06d6bSBaptiste Daroussin 		break;
25061d06d6bSBaptiste Daroussin 	default:
251*7295610fSBaptiste Daroussin 		return;
25261d06d6bSBaptiste Daroussin 	}
25361d06d6bSBaptiste Daroussin 
25461d06d6bSBaptiste Daroussin 	if (h->metaf) {
25561d06d6bSBaptiste Daroussin 		print_tagq(h, h->metaf);
25661d06d6bSBaptiste Daroussin 		h->metaf = NULL;
25761d06d6bSBaptiste Daroussin 	}
25861d06d6bSBaptiste Daroussin 
25961d06d6bSBaptiste Daroussin 	h->metal = h->metac;
26061d06d6bSBaptiste Daroussin 	h->metac = font;
26161d06d6bSBaptiste Daroussin 
26261d06d6bSBaptiste Daroussin 	switch (font) {
26361d06d6bSBaptiste Daroussin 	case HTMLFONT_ITALIC:
26461d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_I, "");
26561d06d6bSBaptiste Daroussin 		break;
26661d06d6bSBaptiste Daroussin 	case HTMLFONT_BOLD:
26761d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_B, "");
26861d06d6bSBaptiste Daroussin 		break;
26961d06d6bSBaptiste Daroussin 	case HTMLFONT_BI:
27061d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_B, "");
27161d06d6bSBaptiste Daroussin 		print_otag(h, TAG_I, "");
27261d06d6bSBaptiste Daroussin 		break;
273*7295610fSBaptiste Daroussin 	case HTMLFONT_CW:
274*7295610fSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
275*7295610fSBaptiste Daroussin 		break;
27661d06d6bSBaptiste Daroussin 	default:
27761d06d6bSBaptiste Daroussin 		break;
27861d06d6bSBaptiste Daroussin 	}
27961d06d6bSBaptiste Daroussin }
28061d06d6bSBaptiste Daroussin 
281*7295610fSBaptiste Daroussin void
282*7295610fSBaptiste Daroussin html_close_paragraph(struct html *h)
283*7295610fSBaptiste Daroussin {
284*7295610fSBaptiste Daroussin 	struct tag	*t;
285*7295610fSBaptiste Daroussin 
286*7295610fSBaptiste Daroussin 	for (t = h->tag; t != NULL && t->closed == 0; t = t->next) {
287*7295610fSBaptiste Daroussin 		switch(t->tag) {
288*7295610fSBaptiste Daroussin 		case TAG_P:
289*7295610fSBaptiste Daroussin 		case TAG_PRE:
290*7295610fSBaptiste Daroussin 			print_tagq(h, t);
291*7295610fSBaptiste Daroussin 			break;
292*7295610fSBaptiste Daroussin 		case TAG_A:
293*7295610fSBaptiste Daroussin 			print_tagq(h, t);
294*7295610fSBaptiste Daroussin 			continue;
295*7295610fSBaptiste Daroussin 		default:
296*7295610fSBaptiste Daroussin 			continue;
297*7295610fSBaptiste Daroussin 		}
298*7295610fSBaptiste Daroussin 		break;
299*7295610fSBaptiste Daroussin 	}
300*7295610fSBaptiste Daroussin }
301*7295610fSBaptiste Daroussin 
302*7295610fSBaptiste Daroussin /*
303*7295610fSBaptiste Daroussin  * ROFF_nf switches to no-fill mode, ROFF_fi to fill mode.
304*7295610fSBaptiste Daroussin  * TOKEN_NONE does not switch.  The old mode is returned.
305*7295610fSBaptiste Daroussin  */
306*7295610fSBaptiste Daroussin enum roff_tok
307*7295610fSBaptiste Daroussin html_fillmode(struct html *h, enum roff_tok want)
308*7295610fSBaptiste Daroussin {
309*7295610fSBaptiste Daroussin 	struct tag	*t;
310*7295610fSBaptiste Daroussin 	enum roff_tok	 had;
311*7295610fSBaptiste Daroussin 
312*7295610fSBaptiste Daroussin 	for (t = h->tag; t != NULL; t = t->next)
313*7295610fSBaptiste Daroussin 		if (t->tag == TAG_PRE)
314*7295610fSBaptiste Daroussin 			break;
315*7295610fSBaptiste Daroussin 
316*7295610fSBaptiste Daroussin 	had = t == NULL ? ROFF_fi : ROFF_nf;
317*7295610fSBaptiste Daroussin 
318*7295610fSBaptiste Daroussin 	if (want != had) {
319*7295610fSBaptiste Daroussin 		switch (want) {
320*7295610fSBaptiste Daroussin 		case ROFF_fi:
321*7295610fSBaptiste Daroussin 			print_tagq(h, t);
322*7295610fSBaptiste Daroussin 			break;
323*7295610fSBaptiste Daroussin 		case ROFF_nf:
324*7295610fSBaptiste Daroussin 			html_close_paragraph(h);
325*7295610fSBaptiste Daroussin 			print_otag(h, TAG_PRE, "");
326*7295610fSBaptiste Daroussin 			break;
327*7295610fSBaptiste Daroussin 		case TOKEN_NONE:
328*7295610fSBaptiste Daroussin 			break;
329*7295610fSBaptiste Daroussin 		default:
330*7295610fSBaptiste Daroussin 			abort();
331*7295610fSBaptiste Daroussin 		}
332*7295610fSBaptiste Daroussin 	}
333*7295610fSBaptiste Daroussin 	return had;
334*7295610fSBaptiste Daroussin }
335*7295610fSBaptiste Daroussin 
33661d06d6bSBaptiste Daroussin char *
33761d06d6bSBaptiste Daroussin html_make_id(const struct roff_node *n, int unique)
33861d06d6bSBaptiste Daroussin {
33961d06d6bSBaptiste Daroussin 	const struct roff_node	*nch;
34061d06d6bSBaptiste Daroussin 	char			*buf, *bufs, *cp;
34161d06d6bSBaptiste Daroussin 	unsigned int		 slot;
34261d06d6bSBaptiste Daroussin 	int			 suffix;
34361d06d6bSBaptiste Daroussin 
34461d06d6bSBaptiste Daroussin 	for (nch = n->child; nch != NULL; nch = nch->next)
34561d06d6bSBaptiste Daroussin 		if (nch->type != ROFFT_TEXT)
34661d06d6bSBaptiste Daroussin 			return NULL;
34761d06d6bSBaptiste Daroussin 
34861d06d6bSBaptiste Daroussin 	buf = NULL;
34961d06d6bSBaptiste Daroussin 	deroff(&buf, n);
35061d06d6bSBaptiste Daroussin 	if (buf == NULL)
35161d06d6bSBaptiste Daroussin 		return NULL;
35261d06d6bSBaptiste Daroussin 
35361d06d6bSBaptiste Daroussin 	/*
35461d06d6bSBaptiste Daroussin 	 * In ID attributes, only use ASCII characters that are
35561d06d6bSBaptiste Daroussin 	 * permitted in URL-fragment strings according to the
35661d06d6bSBaptiste Daroussin 	 * explicit list at:
35761d06d6bSBaptiste Daroussin 	 * https://url.spec.whatwg.org/#url-fragment-string
35861d06d6bSBaptiste Daroussin 	 */
35961d06d6bSBaptiste Daroussin 
36061d06d6bSBaptiste Daroussin 	for (cp = buf; *cp != '\0'; cp++)
36161d06d6bSBaptiste Daroussin 		if (isalnum((unsigned char)*cp) == 0 &&
36261d06d6bSBaptiste Daroussin 		    strchr("!$&'()*+,-./:;=?@_~", *cp) == NULL)
36361d06d6bSBaptiste Daroussin 			*cp = '_';
36461d06d6bSBaptiste Daroussin 
36561d06d6bSBaptiste Daroussin 	if (unique == 0)
36661d06d6bSBaptiste Daroussin 		return buf;
36761d06d6bSBaptiste Daroussin 
36861d06d6bSBaptiste Daroussin 	/* Avoid duplicate HTML id= attributes. */
36961d06d6bSBaptiste Daroussin 
37061d06d6bSBaptiste Daroussin 	bufs = NULL;
37161d06d6bSBaptiste Daroussin 	suffix = 1;
37261d06d6bSBaptiste Daroussin 	slot = ohash_qlookup(&id_unique, buf);
37361d06d6bSBaptiste Daroussin 	cp = ohash_find(&id_unique, slot);
37461d06d6bSBaptiste Daroussin 	if (cp != NULL) {
37561d06d6bSBaptiste Daroussin 		while (cp != NULL) {
37661d06d6bSBaptiste Daroussin 			free(bufs);
37761d06d6bSBaptiste Daroussin 			if (++suffix > 127) {
37861d06d6bSBaptiste Daroussin 				free(buf);
37961d06d6bSBaptiste Daroussin 				return NULL;
38061d06d6bSBaptiste Daroussin 			}
38161d06d6bSBaptiste Daroussin 			mandoc_asprintf(&bufs, "%s_%d", buf, suffix);
38261d06d6bSBaptiste Daroussin 			slot = ohash_qlookup(&id_unique, bufs);
38361d06d6bSBaptiste Daroussin 			cp = ohash_find(&id_unique, slot);
38461d06d6bSBaptiste Daroussin 		}
38561d06d6bSBaptiste Daroussin 		free(buf);
38661d06d6bSBaptiste Daroussin 		buf = bufs;
38761d06d6bSBaptiste Daroussin 	}
38861d06d6bSBaptiste Daroussin 	ohash_insert(&id_unique, slot, buf);
38961d06d6bSBaptiste Daroussin 	return buf;
39061d06d6bSBaptiste Daroussin }
39161d06d6bSBaptiste Daroussin 
39261d06d6bSBaptiste Daroussin static int
39361d06d6bSBaptiste Daroussin print_escape(struct html *h, char c)
39461d06d6bSBaptiste Daroussin {
39561d06d6bSBaptiste Daroussin 
39661d06d6bSBaptiste Daroussin 	switch (c) {
39761d06d6bSBaptiste Daroussin 	case '<':
39861d06d6bSBaptiste Daroussin 		print_word(h, "&lt;");
39961d06d6bSBaptiste Daroussin 		break;
40061d06d6bSBaptiste Daroussin 	case '>':
40161d06d6bSBaptiste Daroussin 		print_word(h, "&gt;");
40261d06d6bSBaptiste Daroussin 		break;
40361d06d6bSBaptiste Daroussin 	case '&':
40461d06d6bSBaptiste Daroussin 		print_word(h, "&amp;");
40561d06d6bSBaptiste Daroussin 		break;
40661d06d6bSBaptiste Daroussin 	case '"':
40761d06d6bSBaptiste Daroussin 		print_word(h, "&quot;");
40861d06d6bSBaptiste Daroussin 		break;
40961d06d6bSBaptiste Daroussin 	case ASCII_NBRSP:
41061d06d6bSBaptiste Daroussin 		print_word(h, "&nbsp;");
41161d06d6bSBaptiste Daroussin 		break;
41261d06d6bSBaptiste Daroussin 	case ASCII_HYPH:
41361d06d6bSBaptiste Daroussin 		print_byte(h, '-');
41461d06d6bSBaptiste Daroussin 		break;
41561d06d6bSBaptiste Daroussin 	case ASCII_BREAK:
41661d06d6bSBaptiste Daroussin 		break;
41761d06d6bSBaptiste Daroussin 	default:
41861d06d6bSBaptiste Daroussin 		return 0;
41961d06d6bSBaptiste Daroussin 	}
42061d06d6bSBaptiste Daroussin 	return 1;
42161d06d6bSBaptiste Daroussin }
42261d06d6bSBaptiste Daroussin 
42361d06d6bSBaptiste Daroussin static int
42461d06d6bSBaptiste Daroussin print_encode(struct html *h, const char *p, const char *pend, int norecurse)
42561d06d6bSBaptiste Daroussin {
42661d06d6bSBaptiste Daroussin 	char		 numbuf[16];
42761d06d6bSBaptiste Daroussin 	const char	*seq;
42861d06d6bSBaptiste Daroussin 	size_t		 sz;
42961d06d6bSBaptiste Daroussin 	int		 c, len, breakline, nospace;
43061d06d6bSBaptiste Daroussin 	enum mandoc_esc	 esc;
43161d06d6bSBaptiste Daroussin 	static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"',
43261d06d6bSBaptiste Daroussin 		ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
43361d06d6bSBaptiste Daroussin 
43461d06d6bSBaptiste Daroussin 	if (pend == NULL)
43561d06d6bSBaptiste Daroussin 		pend = strchr(p, '\0');
43661d06d6bSBaptiste Daroussin 
43761d06d6bSBaptiste Daroussin 	breakline = 0;
43861d06d6bSBaptiste Daroussin 	nospace = 0;
43961d06d6bSBaptiste Daroussin 
44061d06d6bSBaptiste Daroussin 	while (p < pend) {
44161d06d6bSBaptiste Daroussin 		if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
44261d06d6bSBaptiste Daroussin 			h->flags &= ~HTML_SKIPCHAR;
44361d06d6bSBaptiste Daroussin 			p++;
44461d06d6bSBaptiste Daroussin 			continue;
44561d06d6bSBaptiste Daroussin 		}
44661d06d6bSBaptiste Daroussin 
44761d06d6bSBaptiste Daroussin 		for (sz = strcspn(p, rejs); sz-- && p < pend; p++)
44861d06d6bSBaptiste Daroussin 			print_byte(h, *p);
44961d06d6bSBaptiste Daroussin 
45061d06d6bSBaptiste Daroussin 		if (breakline &&
45161d06d6bSBaptiste Daroussin 		    (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) {
452*7295610fSBaptiste Daroussin 			print_otag(h, TAG_BR, "");
45361d06d6bSBaptiste Daroussin 			breakline = 0;
45461d06d6bSBaptiste Daroussin 			while (p < pend && (*p == ' ' || *p == ASCII_NBRSP))
45561d06d6bSBaptiste Daroussin 				p++;
45661d06d6bSBaptiste Daroussin 			continue;
45761d06d6bSBaptiste Daroussin 		}
45861d06d6bSBaptiste Daroussin 
45961d06d6bSBaptiste Daroussin 		if (p >= pend)
46061d06d6bSBaptiste Daroussin 			break;
46161d06d6bSBaptiste Daroussin 
46261d06d6bSBaptiste Daroussin 		if (*p == ' ') {
46361d06d6bSBaptiste Daroussin 			print_endword(h);
46461d06d6bSBaptiste Daroussin 			p++;
46561d06d6bSBaptiste Daroussin 			continue;
46661d06d6bSBaptiste Daroussin 		}
46761d06d6bSBaptiste Daroussin 
46861d06d6bSBaptiste Daroussin 		if (print_escape(h, *p++))
46961d06d6bSBaptiste Daroussin 			continue;
47061d06d6bSBaptiste Daroussin 
47161d06d6bSBaptiste Daroussin 		esc = mandoc_escape(&p, &seq, &len);
47261d06d6bSBaptiste Daroussin 		switch (esc) {
47361d06d6bSBaptiste Daroussin 		case ESCAPE_FONT:
47461d06d6bSBaptiste Daroussin 		case ESCAPE_FONTPREV:
47561d06d6bSBaptiste Daroussin 		case ESCAPE_FONTBOLD:
47661d06d6bSBaptiste Daroussin 		case ESCAPE_FONTITALIC:
47761d06d6bSBaptiste Daroussin 		case ESCAPE_FONTBI:
478*7295610fSBaptiste Daroussin 		case ESCAPE_FONTCW:
47961d06d6bSBaptiste Daroussin 		case ESCAPE_FONTROMAN:
480*7295610fSBaptiste Daroussin 			if (0 == norecurse) {
481*7295610fSBaptiste Daroussin 				h->flags |= HTML_NOSPACE;
48261d06d6bSBaptiste Daroussin 				print_metaf(h, esc);
483*7295610fSBaptiste Daroussin 				h->flags &= ~HTML_NOSPACE;
484*7295610fSBaptiste Daroussin 			}
48561d06d6bSBaptiste Daroussin 			continue;
48661d06d6bSBaptiste Daroussin 		case ESCAPE_SKIPCHAR:
48761d06d6bSBaptiste Daroussin 			h->flags |= HTML_SKIPCHAR;
48861d06d6bSBaptiste Daroussin 			continue;
489*7295610fSBaptiste Daroussin 		case ESCAPE_ERROR:
490*7295610fSBaptiste Daroussin 			continue;
49161d06d6bSBaptiste Daroussin 		default:
49261d06d6bSBaptiste Daroussin 			break;
49361d06d6bSBaptiste Daroussin 		}
49461d06d6bSBaptiste Daroussin 
49561d06d6bSBaptiste Daroussin 		if (h->flags & HTML_SKIPCHAR) {
49661d06d6bSBaptiste Daroussin 			h->flags &= ~HTML_SKIPCHAR;
49761d06d6bSBaptiste Daroussin 			continue;
49861d06d6bSBaptiste Daroussin 		}
49961d06d6bSBaptiste Daroussin 
50061d06d6bSBaptiste Daroussin 		switch (esc) {
50161d06d6bSBaptiste Daroussin 		case ESCAPE_UNICODE:
50261d06d6bSBaptiste Daroussin 			/* Skip past "u" header. */
50361d06d6bSBaptiste Daroussin 			c = mchars_num2uc(seq + 1, len - 1);
50461d06d6bSBaptiste Daroussin 			break;
50561d06d6bSBaptiste Daroussin 		case ESCAPE_NUMBERED:
50661d06d6bSBaptiste Daroussin 			c = mchars_num2char(seq, len);
50761d06d6bSBaptiste Daroussin 			if (c < 0)
50861d06d6bSBaptiste Daroussin 				continue;
50961d06d6bSBaptiste Daroussin 			break;
51061d06d6bSBaptiste Daroussin 		case ESCAPE_SPECIAL:
51161d06d6bSBaptiste Daroussin 			c = mchars_spec2cp(seq, len);
51261d06d6bSBaptiste Daroussin 			if (c <= 0)
51361d06d6bSBaptiste Daroussin 				continue;
51461d06d6bSBaptiste Daroussin 			break;
515*7295610fSBaptiste Daroussin 		case ESCAPE_UNDEF:
516*7295610fSBaptiste Daroussin 			c = *seq;
517*7295610fSBaptiste Daroussin 			break;
518*7295610fSBaptiste Daroussin 		case ESCAPE_DEVICE:
519*7295610fSBaptiste Daroussin 			print_word(h, "html");
520*7295610fSBaptiste Daroussin 			continue;
52161d06d6bSBaptiste Daroussin 		case ESCAPE_BREAK:
52261d06d6bSBaptiste Daroussin 			breakline = 1;
52361d06d6bSBaptiste Daroussin 			continue;
52461d06d6bSBaptiste Daroussin 		case ESCAPE_NOSPACE:
52561d06d6bSBaptiste Daroussin 			if ('\0' == *p)
52661d06d6bSBaptiste Daroussin 				nospace = 1;
52761d06d6bSBaptiste Daroussin 			continue;
52861d06d6bSBaptiste Daroussin 		case ESCAPE_OVERSTRIKE:
52961d06d6bSBaptiste Daroussin 			if (len == 0)
53061d06d6bSBaptiste Daroussin 				continue;
53161d06d6bSBaptiste Daroussin 			c = seq[len - 1];
53261d06d6bSBaptiste Daroussin 			break;
53361d06d6bSBaptiste Daroussin 		default:
53461d06d6bSBaptiste Daroussin 			continue;
53561d06d6bSBaptiste Daroussin 		}
53661d06d6bSBaptiste Daroussin 		if ((c < 0x20 && c != 0x09) ||
53761d06d6bSBaptiste Daroussin 		    (c > 0x7E && c < 0xA0))
53861d06d6bSBaptiste Daroussin 			c = 0xFFFD;
53961d06d6bSBaptiste Daroussin 		if (c > 0x7E) {
54061d06d6bSBaptiste Daroussin 			(void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c);
54161d06d6bSBaptiste Daroussin 			print_word(h, numbuf);
54261d06d6bSBaptiste Daroussin 		} else if (print_escape(h, c) == 0)
54361d06d6bSBaptiste Daroussin 			print_byte(h, c);
54461d06d6bSBaptiste Daroussin 	}
54561d06d6bSBaptiste Daroussin 
54661d06d6bSBaptiste Daroussin 	return nospace;
54761d06d6bSBaptiste Daroussin }
54861d06d6bSBaptiste Daroussin 
54961d06d6bSBaptiste Daroussin static void
55061d06d6bSBaptiste Daroussin print_href(struct html *h, const char *name, const char *sec, int man)
55161d06d6bSBaptiste Daroussin {
552*7295610fSBaptiste Daroussin 	struct stat	 sb;
55361d06d6bSBaptiste Daroussin 	const char	*p, *pp;
554*7295610fSBaptiste Daroussin 	char		*filename;
55561d06d6bSBaptiste Daroussin 
556*7295610fSBaptiste Daroussin 	if (man) {
557*7295610fSBaptiste Daroussin 		pp = h->base_man1;
558*7295610fSBaptiste Daroussin 		if (h->base_man2 != NULL) {
559*7295610fSBaptiste Daroussin 			mandoc_asprintf(&filename, "%s.%s", name, sec);
560*7295610fSBaptiste Daroussin 			if (stat(filename, &sb) == -1)
561*7295610fSBaptiste Daroussin 				pp = h->base_man2;
562*7295610fSBaptiste Daroussin 			free(filename);
563*7295610fSBaptiste Daroussin 		}
564*7295610fSBaptiste Daroussin 	} else
565*7295610fSBaptiste Daroussin 		pp = h->base_includes;
566*7295610fSBaptiste Daroussin 
56761d06d6bSBaptiste Daroussin 	while ((p = strchr(pp, '%')) != NULL) {
56861d06d6bSBaptiste Daroussin 		print_encode(h, pp, p, 1);
56961d06d6bSBaptiste Daroussin 		if (man && p[1] == 'S') {
57061d06d6bSBaptiste Daroussin 			if (sec == NULL)
57161d06d6bSBaptiste Daroussin 				print_byte(h, '1');
57261d06d6bSBaptiste Daroussin 			else
57361d06d6bSBaptiste Daroussin 				print_encode(h, sec, NULL, 1);
57461d06d6bSBaptiste Daroussin 		} else if ((man && p[1] == 'N') ||
57561d06d6bSBaptiste Daroussin 		    (man == 0 && p[1] == 'I'))
57661d06d6bSBaptiste Daroussin 			print_encode(h, name, NULL, 1);
57761d06d6bSBaptiste Daroussin 		else
57861d06d6bSBaptiste Daroussin 			print_encode(h, p, p + 2, 1);
57961d06d6bSBaptiste Daroussin 		pp = p + 2;
58061d06d6bSBaptiste Daroussin 	}
58161d06d6bSBaptiste Daroussin 	if (*pp != '\0')
58261d06d6bSBaptiste Daroussin 		print_encode(h, pp, NULL, 1);
58361d06d6bSBaptiste Daroussin }
58461d06d6bSBaptiste Daroussin 
58561d06d6bSBaptiste Daroussin struct tag *
58661d06d6bSBaptiste Daroussin print_otag(struct html *h, enum htmltag tag, const char *fmt, ...)
58761d06d6bSBaptiste Daroussin {
58861d06d6bSBaptiste Daroussin 	va_list		 ap;
58961d06d6bSBaptiste Daroussin 	struct tag	*t;
59061d06d6bSBaptiste Daroussin 	const char	*attr;
59161d06d6bSBaptiste Daroussin 	char		*arg1, *arg2;
592*7295610fSBaptiste Daroussin 	int		 style_written, tflags;
59361d06d6bSBaptiste Daroussin 
59461d06d6bSBaptiste Daroussin 	tflags = htmltags[tag].flags;
59561d06d6bSBaptiste Daroussin 
59661d06d6bSBaptiste Daroussin 	/* Push this tag onto the stack of open scopes. */
59761d06d6bSBaptiste Daroussin 
59861d06d6bSBaptiste Daroussin 	if ((tflags & HTML_NOSTACK) == 0) {
59961d06d6bSBaptiste Daroussin 		t = mandoc_malloc(sizeof(struct tag));
60061d06d6bSBaptiste Daroussin 		t->tag = tag;
60161d06d6bSBaptiste Daroussin 		t->next = h->tag;
602*7295610fSBaptiste Daroussin 		t->refcnt = 0;
603*7295610fSBaptiste Daroussin 		t->closed = 0;
60461d06d6bSBaptiste Daroussin 		h->tag = t;
60561d06d6bSBaptiste Daroussin 	} else
60661d06d6bSBaptiste Daroussin 		t = NULL;
60761d06d6bSBaptiste Daroussin 
60861d06d6bSBaptiste Daroussin 	if (tflags & HTML_NLBEFORE)
60961d06d6bSBaptiste Daroussin 		print_endline(h);
61061d06d6bSBaptiste Daroussin 	if (h->col == 0)
61161d06d6bSBaptiste Daroussin 		print_indent(h);
61261d06d6bSBaptiste Daroussin 	else if ((h->flags & HTML_NOSPACE) == 0) {
61361d06d6bSBaptiste Daroussin 		if (h->flags & HTML_KEEP)
61461d06d6bSBaptiste Daroussin 			print_word(h, "&#x00A0;");
61561d06d6bSBaptiste Daroussin 		else {
61661d06d6bSBaptiste Daroussin 			if (h->flags & HTML_PREKEEP)
61761d06d6bSBaptiste Daroussin 				h->flags |= HTML_KEEP;
61861d06d6bSBaptiste Daroussin 			print_endword(h);
61961d06d6bSBaptiste Daroussin 		}
62061d06d6bSBaptiste Daroussin 	}
62161d06d6bSBaptiste Daroussin 
62261d06d6bSBaptiste Daroussin 	if ( ! (h->flags & HTML_NONOSPACE))
62361d06d6bSBaptiste Daroussin 		h->flags &= ~HTML_NOSPACE;
62461d06d6bSBaptiste Daroussin 	else
62561d06d6bSBaptiste Daroussin 		h->flags |= HTML_NOSPACE;
62661d06d6bSBaptiste Daroussin 
62761d06d6bSBaptiste Daroussin 	/* Print out the tag name and attributes. */
62861d06d6bSBaptiste Daroussin 
62961d06d6bSBaptiste Daroussin 	print_byte(h, '<');
63061d06d6bSBaptiste Daroussin 	print_word(h, htmltags[tag].name);
63161d06d6bSBaptiste Daroussin 
63261d06d6bSBaptiste Daroussin 	va_start(ap, fmt);
63361d06d6bSBaptiste Daroussin 
634*7295610fSBaptiste Daroussin 	while (*fmt != '\0' && *fmt != 's') {
63561d06d6bSBaptiste Daroussin 
63661d06d6bSBaptiste Daroussin 		/* Parse attributes and arguments. */
63761d06d6bSBaptiste Daroussin 
63861d06d6bSBaptiste Daroussin 		arg1 = va_arg(ap, char *);
63961d06d6bSBaptiste Daroussin 		arg2 = NULL;
64061d06d6bSBaptiste Daroussin 		switch (*fmt++) {
64161d06d6bSBaptiste Daroussin 		case 'c':
64261d06d6bSBaptiste Daroussin 			attr = "class";
64361d06d6bSBaptiste Daroussin 			break;
64461d06d6bSBaptiste Daroussin 		case 'h':
64561d06d6bSBaptiste Daroussin 			attr = "href";
64661d06d6bSBaptiste Daroussin 			break;
64761d06d6bSBaptiste Daroussin 		case 'i':
64861d06d6bSBaptiste Daroussin 			attr = "id";
64961d06d6bSBaptiste Daroussin 			break;
65061d06d6bSBaptiste Daroussin 		case '?':
65161d06d6bSBaptiste Daroussin 			attr = arg1;
65261d06d6bSBaptiste Daroussin 			arg1 = va_arg(ap, char *);
65361d06d6bSBaptiste Daroussin 			break;
65461d06d6bSBaptiste Daroussin 		default:
65561d06d6bSBaptiste Daroussin 			abort();
65661d06d6bSBaptiste Daroussin 		}
65761d06d6bSBaptiste Daroussin 		if (*fmt == 'M')
65861d06d6bSBaptiste Daroussin 			arg2 = va_arg(ap, char *);
65961d06d6bSBaptiste Daroussin 		if (arg1 == NULL)
66061d06d6bSBaptiste Daroussin 			continue;
66161d06d6bSBaptiste Daroussin 
66261d06d6bSBaptiste Daroussin 		/* Print the attributes. */
66361d06d6bSBaptiste Daroussin 
66461d06d6bSBaptiste Daroussin 		print_byte(h, ' ');
66561d06d6bSBaptiste Daroussin 		print_word(h, attr);
66661d06d6bSBaptiste Daroussin 		print_byte(h, '=');
66761d06d6bSBaptiste Daroussin 		print_byte(h, '"');
66861d06d6bSBaptiste Daroussin 		switch (*fmt) {
66961d06d6bSBaptiste Daroussin 		case 'I':
67061d06d6bSBaptiste Daroussin 			print_href(h, arg1, NULL, 0);
67161d06d6bSBaptiste Daroussin 			fmt++;
67261d06d6bSBaptiste Daroussin 			break;
67361d06d6bSBaptiste Daroussin 		case 'M':
67461d06d6bSBaptiste Daroussin 			print_href(h, arg1, arg2, 1);
67561d06d6bSBaptiste Daroussin 			fmt++;
67661d06d6bSBaptiste Daroussin 			break;
67761d06d6bSBaptiste Daroussin 		case 'R':
67861d06d6bSBaptiste Daroussin 			print_byte(h, '#');
67961d06d6bSBaptiste Daroussin 			print_encode(h, arg1, NULL, 1);
68061d06d6bSBaptiste Daroussin 			fmt++;
68161d06d6bSBaptiste Daroussin 			break;
68261d06d6bSBaptiste Daroussin 		default:
68361d06d6bSBaptiste Daroussin 			print_encode(h, arg1, NULL, 1);
684*7295610fSBaptiste Daroussin 			break;
685*7295610fSBaptiste Daroussin 		}
686*7295610fSBaptiste Daroussin 		print_byte(h, '"');
687*7295610fSBaptiste Daroussin 	}
688*7295610fSBaptiste Daroussin 
689*7295610fSBaptiste Daroussin 	style_written = 0;
690*7295610fSBaptiste Daroussin 	while (*fmt++ == 's') {
691*7295610fSBaptiste Daroussin 		arg1 = va_arg(ap, char *);
692*7295610fSBaptiste Daroussin 		arg2 = va_arg(ap, char *);
693*7295610fSBaptiste Daroussin 		if (arg2 == NULL)
694*7295610fSBaptiste Daroussin 			continue;
695*7295610fSBaptiste Daroussin 		print_byte(h, ' ');
696*7295610fSBaptiste Daroussin 		if (style_written == 0) {
697*7295610fSBaptiste Daroussin 			print_word(h, "style=\"");
698*7295610fSBaptiste Daroussin 			style_written = 1;
699*7295610fSBaptiste Daroussin 		}
70061d06d6bSBaptiste Daroussin 		print_word(h, arg1);
70161d06d6bSBaptiste Daroussin 		print_byte(h, ':');
70261d06d6bSBaptiste Daroussin 		print_byte(h, ' ');
70361d06d6bSBaptiste Daroussin 		print_word(h, arg2);
70461d06d6bSBaptiste Daroussin 		print_byte(h, ';');
70561d06d6bSBaptiste Daroussin 	}
706*7295610fSBaptiste Daroussin 	if (style_written)
70761d06d6bSBaptiste Daroussin 		print_byte(h, '"');
708*7295610fSBaptiste Daroussin 
70961d06d6bSBaptiste Daroussin 	va_end(ap);
71061d06d6bSBaptiste Daroussin 
71161d06d6bSBaptiste Daroussin 	/* Accommodate for "well-formed" singleton escaping. */
71261d06d6bSBaptiste Daroussin 
71361d06d6bSBaptiste Daroussin 	if (HTML_AUTOCLOSE & htmltags[tag].flags)
71461d06d6bSBaptiste Daroussin 		print_byte(h, '/');
71561d06d6bSBaptiste Daroussin 
71661d06d6bSBaptiste Daroussin 	print_byte(h, '>');
71761d06d6bSBaptiste Daroussin 
71861d06d6bSBaptiste Daroussin 	if (tflags & HTML_NLBEGIN)
71961d06d6bSBaptiste Daroussin 		print_endline(h);
72061d06d6bSBaptiste Daroussin 	else
72161d06d6bSBaptiste Daroussin 		h->flags |= HTML_NOSPACE;
72261d06d6bSBaptiste Daroussin 
72361d06d6bSBaptiste Daroussin 	if (tflags & HTML_INDENT)
72461d06d6bSBaptiste Daroussin 		h->indent++;
72561d06d6bSBaptiste Daroussin 	if (tflags & HTML_NOINDENT)
72661d06d6bSBaptiste Daroussin 		h->noindent++;
72761d06d6bSBaptiste Daroussin 
72861d06d6bSBaptiste Daroussin 	return t;
72961d06d6bSBaptiste Daroussin }
73061d06d6bSBaptiste Daroussin 
73161d06d6bSBaptiste Daroussin static void
73261d06d6bSBaptiste Daroussin print_ctag(struct html *h, struct tag *tag)
73361d06d6bSBaptiste Daroussin {
73461d06d6bSBaptiste Daroussin 	int	 tflags;
73561d06d6bSBaptiste Daroussin 
736*7295610fSBaptiste Daroussin 	if (tag->closed == 0) {
737*7295610fSBaptiste Daroussin 		tag->closed = 1;
73861d06d6bSBaptiste Daroussin 		if (tag == h->metaf)
73961d06d6bSBaptiste Daroussin 			h->metaf = NULL;
74061d06d6bSBaptiste Daroussin 		if (tag == h->tblt)
74161d06d6bSBaptiste Daroussin 			h->tblt = NULL;
74261d06d6bSBaptiste Daroussin 
74361d06d6bSBaptiste Daroussin 		tflags = htmltags[tag->tag].flags;
74461d06d6bSBaptiste Daroussin 		if (tflags & HTML_INDENT)
74561d06d6bSBaptiste Daroussin 			h->indent--;
74661d06d6bSBaptiste Daroussin 		if (tflags & HTML_NOINDENT)
74761d06d6bSBaptiste Daroussin 			h->noindent--;
74861d06d6bSBaptiste Daroussin 		if (tflags & HTML_NLEND)
74961d06d6bSBaptiste Daroussin 			print_endline(h);
75061d06d6bSBaptiste Daroussin 		print_indent(h);
75161d06d6bSBaptiste Daroussin 		print_byte(h, '<');
75261d06d6bSBaptiste Daroussin 		print_byte(h, '/');
75361d06d6bSBaptiste Daroussin 		print_word(h, htmltags[tag->tag].name);
75461d06d6bSBaptiste Daroussin 		print_byte(h, '>');
75561d06d6bSBaptiste Daroussin 		if (tflags & HTML_NLAFTER)
75661d06d6bSBaptiste Daroussin 			print_endline(h);
757*7295610fSBaptiste Daroussin 	}
758*7295610fSBaptiste Daroussin 	if (tag->refcnt == 0) {
75961d06d6bSBaptiste Daroussin 		h->tag = tag->next;
76061d06d6bSBaptiste Daroussin 		free(tag);
76161d06d6bSBaptiste Daroussin 	}
762*7295610fSBaptiste Daroussin }
76361d06d6bSBaptiste Daroussin 
76461d06d6bSBaptiste Daroussin void
76561d06d6bSBaptiste Daroussin print_gen_decls(struct html *h)
76661d06d6bSBaptiste Daroussin {
76761d06d6bSBaptiste Daroussin 	print_word(h, "<!DOCTYPE html>");
76861d06d6bSBaptiste Daroussin 	print_endline(h);
76961d06d6bSBaptiste Daroussin }
77061d06d6bSBaptiste Daroussin 
77161d06d6bSBaptiste Daroussin void
77261d06d6bSBaptiste Daroussin print_gen_comment(struct html *h, struct roff_node *n)
77361d06d6bSBaptiste Daroussin {
77461d06d6bSBaptiste Daroussin 	int	 wantblank;
77561d06d6bSBaptiste Daroussin 
77661d06d6bSBaptiste Daroussin 	print_word(h, "<!-- This is an automatically generated file."
77761d06d6bSBaptiste Daroussin 	    "  Do not edit.");
77861d06d6bSBaptiste Daroussin 	h->indent = 1;
77961d06d6bSBaptiste Daroussin 	wantblank = 0;
78061d06d6bSBaptiste Daroussin 	while (n != NULL && n->type == ROFFT_COMMENT) {
78161d06d6bSBaptiste Daroussin 		if (strstr(n->string, "-->") == NULL &&
78261d06d6bSBaptiste Daroussin 		    (wantblank || *n->string != '\0')) {
78361d06d6bSBaptiste Daroussin 			print_endline(h);
78461d06d6bSBaptiste Daroussin 			print_indent(h);
78561d06d6bSBaptiste Daroussin 			print_word(h, n->string);
78661d06d6bSBaptiste Daroussin 			wantblank = *n->string != '\0';
78761d06d6bSBaptiste Daroussin 		}
78861d06d6bSBaptiste Daroussin 		n = n->next;
78961d06d6bSBaptiste Daroussin 	}
79061d06d6bSBaptiste Daroussin 	if (wantblank)
79161d06d6bSBaptiste Daroussin 		print_endline(h);
79261d06d6bSBaptiste Daroussin 	print_word(h, " -->");
79361d06d6bSBaptiste Daroussin 	print_endline(h);
79461d06d6bSBaptiste Daroussin 	h->indent = 0;
79561d06d6bSBaptiste Daroussin }
79661d06d6bSBaptiste Daroussin 
79761d06d6bSBaptiste Daroussin void
79861d06d6bSBaptiste Daroussin print_text(struct html *h, const char *word)
79961d06d6bSBaptiste Daroussin {
80061d06d6bSBaptiste Daroussin 	if (h->col && (h->flags & HTML_NOSPACE) == 0) {
80161d06d6bSBaptiste Daroussin 		if ( ! (HTML_KEEP & h->flags)) {
80261d06d6bSBaptiste Daroussin 			if (HTML_PREKEEP & h->flags)
80361d06d6bSBaptiste Daroussin 				h->flags |= HTML_KEEP;
80461d06d6bSBaptiste Daroussin 			print_endword(h);
80561d06d6bSBaptiste Daroussin 		} else
80661d06d6bSBaptiste Daroussin 			print_word(h, "&#x00A0;");
80761d06d6bSBaptiste Daroussin 	}
80861d06d6bSBaptiste Daroussin 
80961d06d6bSBaptiste Daroussin 	assert(NULL == h->metaf);
81061d06d6bSBaptiste Daroussin 	switch (h->metac) {
81161d06d6bSBaptiste Daroussin 	case HTMLFONT_ITALIC:
81261d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_I, "");
81361d06d6bSBaptiste Daroussin 		break;
81461d06d6bSBaptiste Daroussin 	case HTMLFONT_BOLD:
81561d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_B, "");
81661d06d6bSBaptiste Daroussin 		break;
81761d06d6bSBaptiste Daroussin 	case HTMLFONT_BI:
81861d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_B, "");
81961d06d6bSBaptiste Daroussin 		print_otag(h, TAG_I, "");
82061d06d6bSBaptiste Daroussin 		break;
821*7295610fSBaptiste Daroussin 	case HTMLFONT_CW:
822*7295610fSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
823*7295610fSBaptiste Daroussin 		break;
82461d06d6bSBaptiste Daroussin 	default:
82561d06d6bSBaptiste Daroussin 		print_indent(h);
82661d06d6bSBaptiste Daroussin 		break;
82761d06d6bSBaptiste Daroussin 	}
82861d06d6bSBaptiste Daroussin 
82961d06d6bSBaptiste Daroussin 	assert(word);
83061d06d6bSBaptiste Daroussin 	if ( ! print_encode(h, word, NULL, 0)) {
83161d06d6bSBaptiste Daroussin 		if ( ! (h->flags & HTML_NONOSPACE))
83261d06d6bSBaptiste Daroussin 			h->flags &= ~HTML_NOSPACE;
83361d06d6bSBaptiste Daroussin 		h->flags &= ~HTML_NONEWLINE;
83461d06d6bSBaptiste Daroussin 	} else
83561d06d6bSBaptiste Daroussin 		h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
83661d06d6bSBaptiste Daroussin 
83761d06d6bSBaptiste Daroussin 	if (h->metaf) {
83861d06d6bSBaptiste Daroussin 		print_tagq(h, h->metaf);
83961d06d6bSBaptiste Daroussin 		h->metaf = NULL;
84061d06d6bSBaptiste Daroussin 	}
84161d06d6bSBaptiste Daroussin 
84261d06d6bSBaptiste Daroussin 	h->flags &= ~HTML_IGNDELIM;
84361d06d6bSBaptiste Daroussin }
84461d06d6bSBaptiste Daroussin 
84561d06d6bSBaptiste Daroussin void
84661d06d6bSBaptiste Daroussin print_tagq(struct html *h, const struct tag *until)
84761d06d6bSBaptiste Daroussin {
848*7295610fSBaptiste Daroussin 	struct tag	*this, *next;
84961d06d6bSBaptiste Daroussin 
850*7295610fSBaptiste Daroussin 	for (this = h->tag; this != NULL; this = next) {
851*7295610fSBaptiste Daroussin 		next = this == until ? NULL : this->next;
852*7295610fSBaptiste Daroussin 		print_ctag(h, this);
85361d06d6bSBaptiste Daroussin 	}
85461d06d6bSBaptiste Daroussin }
85561d06d6bSBaptiste Daroussin 
856*7295610fSBaptiste Daroussin /*
857*7295610fSBaptiste Daroussin  * Close out all open elements up to but excluding suntil.
858*7295610fSBaptiste Daroussin  * Note that a paragraph just inside stays open together with it
859*7295610fSBaptiste Daroussin  * because paragraphs include subsequent phrasing content.
860*7295610fSBaptiste Daroussin  */
86161d06d6bSBaptiste Daroussin void
86261d06d6bSBaptiste Daroussin print_stagq(struct html *h, const struct tag *suntil)
86361d06d6bSBaptiste Daroussin {
864*7295610fSBaptiste Daroussin 	struct tag	*this, *next;
86561d06d6bSBaptiste Daroussin 
866*7295610fSBaptiste Daroussin 	for (this = h->tag; this != NULL; this = next) {
867*7295610fSBaptiste Daroussin 		next = this->next;
868*7295610fSBaptiste Daroussin 		if (this == suntil || (next == suntil &&
869*7295610fSBaptiste Daroussin 		    (this->tag == TAG_P || this->tag == TAG_PRE)))
870*7295610fSBaptiste Daroussin 			break;
871*7295610fSBaptiste Daroussin 		print_ctag(h, this);
87261d06d6bSBaptiste Daroussin 	}
87361d06d6bSBaptiste Daroussin }
87461d06d6bSBaptiste Daroussin 
87561d06d6bSBaptiste Daroussin 
87661d06d6bSBaptiste Daroussin /***********************************************************************
87761d06d6bSBaptiste Daroussin  * Low level output functions.
87861d06d6bSBaptiste Daroussin  * They implement line breaking using a short static buffer.
87961d06d6bSBaptiste Daroussin  ***********************************************************************/
88061d06d6bSBaptiste Daroussin 
88161d06d6bSBaptiste Daroussin /*
88261d06d6bSBaptiste Daroussin  * Buffer one HTML output byte.
88361d06d6bSBaptiste Daroussin  * If the buffer is full, flush and deactivate it and start a new line.
88461d06d6bSBaptiste Daroussin  * If the buffer is inactive, print directly.
88561d06d6bSBaptiste Daroussin  */
88661d06d6bSBaptiste Daroussin static void
88761d06d6bSBaptiste Daroussin print_byte(struct html *h, char c)
88861d06d6bSBaptiste Daroussin {
88961d06d6bSBaptiste Daroussin 	if ((h->flags & HTML_BUFFER) == 0) {
89061d06d6bSBaptiste Daroussin 		putchar(c);
89161d06d6bSBaptiste Daroussin 		h->col++;
89261d06d6bSBaptiste Daroussin 		return;
89361d06d6bSBaptiste Daroussin 	}
89461d06d6bSBaptiste Daroussin 
89561d06d6bSBaptiste Daroussin 	if (h->col + h->bufcol < sizeof(h->buf)) {
89661d06d6bSBaptiste Daroussin 		h->buf[h->bufcol++] = c;
89761d06d6bSBaptiste Daroussin 		return;
89861d06d6bSBaptiste Daroussin 	}
89961d06d6bSBaptiste Daroussin 
90061d06d6bSBaptiste Daroussin 	putchar('\n');
90161d06d6bSBaptiste Daroussin 	h->col = 0;
90261d06d6bSBaptiste Daroussin 	print_indent(h);
90361d06d6bSBaptiste Daroussin 	putchar(' ');
90461d06d6bSBaptiste Daroussin 	putchar(' ');
90561d06d6bSBaptiste Daroussin 	fwrite(h->buf, h->bufcol, 1, stdout);
90661d06d6bSBaptiste Daroussin 	putchar(c);
90761d06d6bSBaptiste Daroussin 	h->col = (h->indent + 1) * 2 + h->bufcol + 1;
90861d06d6bSBaptiste Daroussin 	h->bufcol = 0;
90961d06d6bSBaptiste Daroussin 	h->flags &= ~HTML_BUFFER;
91061d06d6bSBaptiste Daroussin }
91161d06d6bSBaptiste Daroussin 
91261d06d6bSBaptiste Daroussin /*
91361d06d6bSBaptiste Daroussin  * If something was printed on the current output line, end it.
91461d06d6bSBaptiste Daroussin  * Not to be called right after print_indent().
91561d06d6bSBaptiste Daroussin  */
91661d06d6bSBaptiste Daroussin void
91761d06d6bSBaptiste Daroussin print_endline(struct html *h)
91861d06d6bSBaptiste Daroussin {
91961d06d6bSBaptiste Daroussin 	if (h->col == 0)
92061d06d6bSBaptiste Daroussin 		return;
92161d06d6bSBaptiste Daroussin 
92261d06d6bSBaptiste Daroussin 	if (h->bufcol) {
92361d06d6bSBaptiste Daroussin 		putchar(' ');
92461d06d6bSBaptiste Daroussin 		fwrite(h->buf, h->bufcol, 1, stdout);
92561d06d6bSBaptiste Daroussin 		h->bufcol = 0;
92661d06d6bSBaptiste Daroussin 	}
92761d06d6bSBaptiste Daroussin 	putchar('\n');
92861d06d6bSBaptiste Daroussin 	h->col = 0;
92961d06d6bSBaptiste Daroussin 	h->flags |= HTML_NOSPACE;
93061d06d6bSBaptiste Daroussin 	h->flags &= ~HTML_BUFFER;
93161d06d6bSBaptiste Daroussin }
93261d06d6bSBaptiste Daroussin 
93361d06d6bSBaptiste Daroussin /*
93461d06d6bSBaptiste Daroussin  * Flush the HTML output buffer.
93561d06d6bSBaptiste Daroussin  * If it is inactive, activate it.
93661d06d6bSBaptiste Daroussin  */
93761d06d6bSBaptiste Daroussin static void
93861d06d6bSBaptiste Daroussin print_endword(struct html *h)
93961d06d6bSBaptiste Daroussin {
94061d06d6bSBaptiste Daroussin 	if (h->noindent) {
94161d06d6bSBaptiste Daroussin 		print_byte(h, ' ');
94261d06d6bSBaptiste Daroussin 		return;
94361d06d6bSBaptiste Daroussin 	}
94461d06d6bSBaptiste Daroussin 
94561d06d6bSBaptiste Daroussin 	if ((h->flags & HTML_BUFFER) == 0) {
94661d06d6bSBaptiste Daroussin 		h->col++;
94761d06d6bSBaptiste Daroussin 		h->flags |= HTML_BUFFER;
94861d06d6bSBaptiste Daroussin 	} else if (h->bufcol) {
94961d06d6bSBaptiste Daroussin 		putchar(' ');
95061d06d6bSBaptiste Daroussin 		fwrite(h->buf, h->bufcol, 1, stdout);
95161d06d6bSBaptiste Daroussin 		h->col += h->bufcol + 1;
95261d06d6bSBaptiste Daroussin 	}
95361d06d6bSBaptiste Daroussin 	h->bufcol = 0;
95461d06d6bSBaptiste Daroussin }
95561d06d6bSBaptiste Daroussin 
95661d06d6bSBaptiste Daroussin /*
95761d06d6bSBaptiste Daroussin  * If at the beginning of a new output line,
95861d06d6bSBaptiste Daroussin  * perform indentation and mark the line as containing output.
95961d06d6bSBaptiste Daroussin  * Make sure to really produce some output right afterwards,
96061d06d6bSBaptiste Daroussin  * but do not use print_otag() for producing it.
96161d06d6bSBaptiste Daroussin  */
96261d06d6bSBaptiste Daroussin static void
96361d06d6bSBaptiste Daroussin print_indent(struct html *h)
96461d06d6bSBaptiste Daroussin {
96561d06d6bSBaptiste Daroussin 	size_t	 i;
96661d06d6bSBaptiste Daroussin 
96761d06d6bSBaptiste Daroussin 	if (h->col)
96861d06d6bSBaptiste Daroussin 		return;
96961d06d6bSBaptiste Daroussin 
97061d06d6bSBaptiste Daroussin 	if (h->noindent == 0) {
97161d06d6bSBaptiste Daroussin 		h->col = h->indent * 2;
97261d06d6bSBaptiste Daroussin 		for (i = 0; i < h->col; i++)
97361d06d6bSBaptiste Daroussin 			putchar(' ');
97461d06d6bSBaptiste Daroussin 	}
97561d06d6bSBaptiste Daroussin 	h->flags &= ~HTML_NOSPACE;
97661d06d6bSBaptiste Daroussin }
97761d06d6bSBaptiste Daroussin 
97861d06d6bSBaptiste Daroussin /*
97961d06d6bSBaptiste Daroussin  * Print or buffer some characters
98061d06d6bSBaptiste Daroussin  * depending on the current HTML output buffer state.
98161d06d6bSBaptiste Daroussin  */
98261d06d6bSBaptiste Daroussin static void
98361d06d6bSBaptiste Daroussin print_word(struct html *h, const char *cp)
98461d06d6bSBaptiste Daroussin {
98561d06d6bSBaptiste Daroussin 	while (*cp != '\0')
98661d06d6bSBaptiste Daroussin 		print_byte(h, *cp++);
98761d06d6bSBaptiste Daroussin }
988