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