xref: /freebsd/contrib/mandoc/html.c (revision 6d38604fc532a3fc060788e3ce40464b46047eaf)
1*6d38604fSBaptiste Daroussin /* $Id: html.c,v 1.275 2021/09/09 14:47:24 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
361d06d6bSBaptiste Daroussin  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4*6d38604fSBaptiste Daroussin  * Copyright (c) 2011-2015, 2017-2021 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.
17*6d38604fSBaptiste Daroussin  *
18*6d38604fSBaptiste Daroussin  * Common functions for mandoc(1) HTML formatters.
19*6d38604fSBaptiste Daroussin  * For use by individual formatters and by the main program.
2061d06d6bSBaptiste Daroussin  */
2161d06d6bSBaptiste Daroussin #include "config.h"
2261d06d6bSBaptiste Daroussin 
2361d06d6bSBaptiste Daroussin #include <sys/types.h>
247295610fSBaptiste Daroussin #include <sys/stat.h>
2561d06d6bSBaptiste Daroussin 
2661d06d6bSBaptiste Daroussin #include <assert.h>
2761d06d6bSBaptiste Daroussin #include <ctype.h>
2861d06d6bSBaptiste Daroussin #include <stdarg.h>
2961d06d6bSBaptiste Daroussin #include <stddef.h>
3061d06d6bSBaptiste Daroussin #include <stdio.h>
3161d06d6bSBaptiste Daroussin #include <stdint.h>
3261d06d6bSBaptiste Daroussin #include <stdlib.h>
3361d06d6bSBaptiste Daroussin #include <string.h>
3461d06d6bSBaptiste Daroussin #include <unistd.h>
3561d06d6bSBaptiste Daroussin 
3661d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
3761d06d6bSBaptiste Daroussin #include "mandoc_ohash.h"
3861d06d6bSBaptiste Daroussin #include "mandoc.h"
3961d06d6bSBaptiste Daroussin #include "roff.h"
4061d06d6bSBaptiste Daroussin #include "out.h"
4161d06d6bSBaptiste Daroussin #include "html.h"
4261d06d6bSBaptiste Daroussin #include "manconf.h"
4361d06d6bSBaptiste Daroussin #include "main.h"
4461d06d6bSBaptiste Daroussin 
4561d06d6bSBaptiste Daroussin struct	htmldata {
4661d06d6bSBaptiste Daroussin 	const char	 *name;
4761d06d6bSBaptiste Daroussin 	int		  flags;
48*6d38604fSBaptiste Daroussin #define	HTML_INPHRASE	 (1 << 0)  /* Can appear in phrasing context. */
49*6d38604fSBaptiste Daroussin #define	HTML_TOPHRASE	 (1 << 1)  /* Establishes phrasing context. */
50*6d38604fSBaptiste Daroussin #define	HTML_NOSTACK	 (1 << 2)  /* Does not have an end tag. */
51*6d38604fSBaptiste Daroussin #define	HTML_NLBEFORE	 (1 << 3)  /* Output line break before opening. */
52*6d38604fSBaptiste Daroussin #define	HTML_NLBEGIN	 (1 << 4)  /* Output line break after opening. */
53*6d38604fSBaptiste Daroussin #define	HTML_NLEND	 (1 << 5)  /* Output line break before closing. */
54*6d38604fSBaptiste Daroussin #define	HTML_NLAFTER	 (1 << 6)  /* Output line break after closing. */
5561d06d6bSBaptiste Daroussin #define	HTML_NLAROUND	 (HTML_NLBEFORE | HTML_NLAFTER)
5661d06d6bSBaptiste Daroussin #define	HTML_NLINSIDE	 (HTML_NLBEGIN | HTML_NLEND)
5761d06d6bSBaptiste Daroussin #define	HTML_NLALL	 (HTML_NLAROUND | HTML_NLINSIDE)
58*6d38604fSBaptiste Daroussin #define	HTML_INDENT	 (1 << 7)  /* Indent content by two spaces. */
59*6d38604fSBaptiste Daroussin #define	HTML_NOINDENT	 (1 << 8)  /* Exception: never indent content. */
6061d06d6bSBaptiste Daroussin };
6161d06d6bSBaptiste Daroussin 
6261d06d6bSBaptiste Daroussin static	const struct htmldata htmltags[TAG_MAX] = {
6361d06d6bSBaptiste Daroussin 	{"html",	HTML_NLALL},
6461d06d6bSBaptiste Daroussin 	{"head",	HTML_NLALL | HTML_INDENT},
65*6d38604fSBaptiste Daroussin 	{"meta",	HTML_NOSTACK | HTML_NLALL},
66*6d38604fSBaptiste Daroussin 	{"link",	HTML_NOSTACK | HTML_NLALL},
67*6d38604fSBaptiste Daroussin 	{"style",	HTML_NLALL | HTML_INDENT},
6861d06d6bSBaptiste Daroussin 	{"title",	HTML_NLAROUND},
69*6d38604fSBaptiste Daroussin 	{"body",	HTML_NLALL},
7061d06d6bSBaptiste Daroussin 	{"div",		HTML_NLAROUND},
717295610fSBaptiste Daroussin 	{"section",	HTML_NLALL},
7261d06d6bSBaptiste Daroussin 	{"table",	HTML_NLALL | HTML_INDENT},
7361d06d6bSBaptiste Daroussin 	{"tr",		HTML_NLALL | HTML_INDENT},
7461d06d6bSBaptiste Daroussin 	{"td",		HTML_NLAROUND},
7561d06d6bSBaptiste Daroussin 	{"li",		HTML_NLAROUND | HTML_INDENT},
7661d06d6bSBaptiste Daroussin 	{"ul",		HTML_NLALL | HTML_INDENT},
7761d06d6bSBaptiste Daroussin 	{"ol",		HTML_NLALL | HTML_INDENT},
7861d06d6bSBaptiste Daroussin 	{"dl",		HTML_NLALL | HTML_INDENT},
7961d06d6bSBaptiste Daroussin 	{"dt",		HTML_NLAROUND},
8061d06d6bSBaptiste Daroussin 	{"dd",		HTML_NLAROUND | HTML_INDENT},
81*6d38604fSBaptiste Daroussin 	{"h1",		HTML_TOPHRASE | HTML_NLAROUND},
82*6d38604fSBaptiste Daroussin 	{"h2",		HTML_TOPHRASE | HTML_NLAROUND},
83*6d38604fSBaptiste Daroussin 	{"p",		HTML_TOPHRASE | HTML_NLAROUND | HTML_INDENT},
84*6d38604fSBaptiste Daroussin 	{"pre",		HTML_TOPHRASE | HTML_NLAROUND | HTML_NOINDENT},
85*6d38604fSBaptiste Daroussin 	{"a",		HTML_INPHRASE | HTML_TOPHRASE},
86*6d38604fSBaptiste Daroussin 	{"b",		HTML_INPHRASE | HTML_TOPHRASE},
87*6d38604fSBaptiste Daroussin 	{"cite",	HTML_INPHRASE | HTML_TOPHRASE},
88*6d38604fSBaptiste Daroussin 	{"code",	HTML_INPHRASE | HTML_TOPHRASE},
89*6d38604fSBaptiste Daroussin 	{"i",		HTML_INPHRASE | HTML_TOPHRASE},
90*6d38604fSBaptiste Daroussin 	{"small",	HTML_INPHRASE | HTML_TOPHRASE},
91*6d38604fSBaptiste Daroussin 	{"span",	HTML_INPHRASE | HTML_TOPHRASE},
92*6d38604fSBaptiste Daroussin 	{"var",		HTML_INPHRASE | HTML_TOPHRASE},
93*6d38604fSBaptiste Daroussin 	{"br",		HTML_INPHRASE | HTML_NOSTACK | HTML_NLALL},
94*6d38604fSBaptiste Daroussin 	{"hr",		HTML_INPHRASE | HTML_NOSTACK},
95*6d38604fSBaptiste Daroussin 	{"mark",	HTML_INPHRASE },
96*6d38604fSBaptiste Daroussin 	{"math",	HTML_INPHRASE | HTML_NLALL | HTML_INDENT},
9761d06d6bSBaptiste Daroussin 	{"mrow",	0},
9861d06d6bSBaptiste Daroussin 	{"mi",		0},
9961d06d6bSBaptiste Daroussin 	{"mn",		0},
10061d06d6bSBaptiste Daroussin 	{"mo",		0},
10161d06d6bSBaptiste Daroussin 	{"msup",	0},
10261d06d6bSBaptiste Daroussin 	{"msub",	0},
10361d06d6bSBaptiste Daroussin 	{"msubsup",	0},
10461d06d6bSBaptiste Daroussin 	{"mfrac",	0},
10561d06d6bSBaptiste Daroussin 	{"msqrt",	0},
10661d06d6bSBaptiste Daroussin 	{"mfenced",	0},
10761d06d6bSBaptiste Daroussin 	{"mtable",	0},
10861d06d6bSBaptiste Daroussin 	{"mtr",		0},
10961d06d6bSBaptiste Daroussin 	{"mtd",		0},
11061d06d6bSBaptiste Daroussin 	{"munderover",	0},
11161d06d6bSBaptiste Daroussin 	{"munder",	0},
11261d06d6bSBaptiste Daroussin 	{"mover",	0},
11361d06d6bSBaptiste Daroussin };
11461d06d6bSBaptiste Daroussin 
11561d06d6bSBaptiste Daroussin /* Avoid duplicate HTML id= attributes. */
116*6d38604fSBaptiste Daroussin 
117*6d38604fSBaptiste Daroussin struct	id_entry {
118*6d38604fSBaptiste Daroussin 	int	 ord;	/* Ordinal number of the latest occurrence. */
119*6d38604fSBaptiste Daroussin 	char	 id[];	/* The id= attribute without any ordinal suffix. */
120*6d38604fSBaptiste Daroussin };
12161d06d6bSBaptiste Daroussin static	struct ohash	 id_unique;
12261d06d6bSBaptiste Daroussin 
1237295610fSBaptiste Daroussin static	void	 html_reset_internal(struct html *);
12461d06d6bSBaptiste Daroussin static	void	 print_byte(struct html *, char);
12561d06d6bSBaptiste Daroussin static	void	 print_endword(struct html *);
12661d06d6bSBaptiste Daroussin static	void	 print_indent(struct html *);
12761d06d6bSBaptiste Daroussin static	void	 print_word(struct html *, const char *);
12861d06d6bSBaptiste Daroussin 
12961d06d6bSBaptiste Daroussin static	void	 print_ctag(struct html *, struct tag *);
13061d06d6bSBaptiste Daroussin static	int	 print_escape(struct html *, char);
13161d06d6bSBaptiste Daroussin static	int	 print_encode(struct html *, const char *, const char *, int);
13261d06d6bSBaptiste Daroussin static	void	 print_href(struct html *, const char *, const char *, int);
13345a5aec3SBaptiste Daroussin static	void	 print_metaf(struct html *);
13461d06d6bSBaptiste Daroussin 
13561d06d6bSBaptiste Daroussin 
13661d06d6bSBaptiste Daroussin void *
13761d06d6bSBaptiste Daroussin html_alloc(const struct manoutput *outopts)
13861d06d6bSBaptiste Daroussin {
13961d06d6bSBaptiste Daroussin 	struct html	*h;
14061d06d6bSBaptiste Daroussin 
14161d06d6bSBaptiste Daroussin 	h = mandoc_calloc(1, sizeof(struct html));
14261d06d6bSBaptiste Daroussin 
14361d06d6bSBaptiste Daroussin 	h->tag = NULL;
144*6d38604fSBaptiste Daroussin 	h->metac = h->metal = ESCAPE_FONTROMAN;
14561d06d6bSBaptiste Daroussin 	h->style = outopts->style;
1467295610fSBaptiste Daroussin 	if ((h->base_man1 = outopts->man) == NULL)
1477295610fSBaptiste Daroussin 		h->base_man2 = NULL;
1487295610fSBaptiste Daroussin 	else if ((h->base_man2 = strchr(h->base_man1, ';')) != NULL)
1497295610fSBaptiste Daroussin 		*h->base_man2++ = '\0';
15061d06d6bSBaptiste Daroussin 	h->base_includes = outopts->includes;
15161d06d6bSBaptiste Daroussin 	if (outopts->fragment)
15261d06d6bSBaptiste Daroussin 		h->oflags |= HTML_FRAGMENT;
1537295610fSBaptiste Daroussin 	if (outopts->toc)
1547295610fSBaptiste Daroussin 		h->oflags |= HTML_TOC;
15561d06d6bSBaptiste Daroussin 
156*6d38604fSBaptiste Daroussin 	mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id));
15761d06d6bSBaptiste Daroussin 
15861d06d6bSBaptiste Daroussin 	return h;
15961d06d6bSBaptiste Daroussin }
16061d06d6bSBaptiste Daroussin 
1617295610fSBaptiste Daroussin static void
1627295610fSBaptiste Daroussin html_reset_internal(struct html *h)
16361d06d6bSBaptiste Daroussin {
16461d06d6bSBaptiste Daroussin 	struct tag	*tag;
165*6d38604fSBaptiste Daroussin 	struct id_entry	*entry;
16661d06d6bSBaptiste Daroussin 	unsigned int	 slot;
16761d06d6bSBaptiste Daroussin 
16861d06d6bSBaptiste Daroussin 	while ((tag = h->tag) != NULL) {
16961d06d6bSBaptiste Daroussin 		h->tag = tag->next;
17061d06d6bSBaptiste Daroussin 		free(tag);
17161d06d6bSBaptiste Daroussin 	}
172*6d38604fSBaptiste Daroussin 	entry = ohash_first(&id_unique, &slot);
173*6d38604fSBaptiste Daroussin 	while (entry != NULL) {
174*6d38604fSBaptiste Daroussin 		free(entry);
175*6d38604fSBaptiste Daroussin 		entry = ohash_next(&id_unique, &slot);
17661d06d6bSBaptiste Daroussin 	}
17761d06d6bSBaptiste Daroussin 	ohash_delete(&id_unique);
17861d06d6bSBaptiste Daroussin }
17961d06d6bSBaptiste Daroussin 
18061d06d6bSBaptiste Daroussin void
1817295610fSBaptiste Daroussin html_reset(void *p)
1827295610fSBaptiste Daroussin {
1837295610fSBaptiste Daroussin 	html_reset_internal(p);
184*6d38604fSBaptiste Daroussin 	mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id));
1857295610fSBaptiste Daroussin }
1867295610fSBaptiste Daroussin 
1877295610fSBaptiste Daroussin void
1887295610fSBaptiste Daroussin html_free(void *p)
1897295610fSBaptiste Daroussin {
1907295610fSBaptiste Daroussin 	html_reset_internal(p);
1917295610fSBaptiste Daroussin 	free(p);
1927295610fSBaptiste Daroussin }
1937295610fSBaptiste Daroussin 
1947295610fSBaptiste Daroussin void
19561d06d6bSBaptiste Daroussin print_gen_head(struct html *h)
19661d06d6bSBaptiste Daroussin {
19761d06d6bSBaptiste Daroussin 	struct tag	*t;
19861d06d6bSBaptiste Daroussin 
19961d06d6bSBaptiste Daroussin 	print_otag(h, TAG_META, "?", "charset", "utf-8");
200*6d38604fSBaptiste Daroussin 	print_otag(h, TAG_META, "??", "name", "viewport",
201*6d38604fSBaptiste Daroussin 	    "content", "width=device-width, initial-scale=1.0");
20261d06d6bSBaptiste Daroussin 	if (h->style != NULL) {
20361d06d6bSBaptiste Daroussin 		print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet",
20461d06d6bSBaptiste Daroussin 		    h->style, "type", "text/css", "media", "all");
20561d06d6bSBaptiste Daroussin 		return;
20661d06d6bSBaptiste Daroussin 	}
20761d06d6bSBaptiste Daroussin 
20861d06d6bSBaptiste Daroussin 	/*
20961d06d6bSBaptiste Daroussin 	 * Print a minimal embedded style sheet.
21061d06d6bSBaptiste Daroussin 	 */
21161d06d6bSBaptiste Daroussin 
21261d06d6bSBaptiste Daroussin 	t = print_otag(h, TAG_STYLE, "");
21361d06d6bSBaptiste Daroussin 	print_text(h, "table.head, table.foot { width: 100%; }");
21461d06d6bSBaptiste Daroussin 	print_endline(h);
21561d06d6bSBaptiste Daroussin 	print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }");
21661d06d6bSBaptiste Daroussin 	print_endline(h);
21761d06d6bSBaptiste Daroussin 	print_text(h, "td.head-vol { text-align: center; }");
21861d06d6bSBaptiste Daroussin 	print_endline(h);
219*6d38604fSBaptiste Daroussin 	print_text(h, ".Nd, .Bf, .Op { display: inline; }");
22061d06d6bSBaptiste Daroussin 	print_endline(h);
221*6d38604fSBaptiste Daroussin 	print_text(h, ".Pa, .Ad { font-style: italic; }");
22261d06d6bSBaptiste Daroussin 	print_endline(h);
223*6d38604fSBaptiste Daroussin 	print_text(h, ".Ms { font-weight: bold; }");
22461d06d6bSBaptiste Daroussin 	print_endline(h);
225*6d38604fSBaptiste Daroussin 	print_text(h, ".Bl-diag ");
22661d06d6bSBaptiste Daroussin 	print_byte(h, '>');
22761d06d6bSBaptiste Daroussin 	print_text(h, " dt { font-weight: bold; }");
22861d06d6bSBaptiste Daroussin 	print_endline(h);
229*6d38604fSBaptiste Daroussin 	print_text(h, "code.Nm, .Fl, .Cm, .Ic, code.In, .Fd, .Fn, .Cd "
230*6d38604fSBaptiste Daroussin 	    "{ font-weight: bold; font-family: inherit; }");
23161d06d6bSBaptiste Daroussin 	print_tagq(h, t);
23261d06d6bSBaptiste Daroussin }
23361d06d6bSBaptiste Daroussin 
23445a5aec3SBaptiste Daroussin int
23545a5aec3SBaptiste Daroussin html_setfont(struct html *h, enum mandoc_esc font)
23661d06d6bSBaptiste Daroussin {
23745a5aec3SBaptiste Daroussin 	switch (font) {
23861d06d6bSBaptiste Daroussin 	case ESCAPE_FONTPREV:
23961d06d6bSBaptiste Daroussin 		font = h->metal;
24061d06d6bSBaptiste Daroussin 		break;
24161d06d6bSBaptiste Daroussin 	case ESCAPE_FONTITALIC:
24261d06d6bSBaptiste Daroussin 	case ESCAPE_FONTBOLD:
24361d06d6bSBaptiste Daroussin 	case ESCAPE_FONTBI:
24445a5aec3SBaptiste Daroussin 	case ESCAPE_FONTROMAN:
245*6d38604fSBaptiste Daroussin 	case ESCAPE_FONTCR:
246*6d38604fSBaptiste Daroussin 	case ESCAPE_FONTCB:
247*6d38604fSBaptiste Daroussin 	case ESCAPE_FONTCI:
2487295610fSBaptiste Daroussin 		break;
24961d06d6bSBaptiste Daroussin 	case ESCAPE_FONT:
25045a5aec3SBaptiste Daroussin 		font = ESCAPE_FONTROMAN;
25161d06d6bSBaptiste Daroussin 		break;
25261d06d6bSBaptiste Daroussin 	default:
25345a5aec3SBaptiste Daroussin 		return 0;
25445a5aec3SBaptiste Daroussin 	}
25545a5aec3SBaptiste Daroussin 	h->metal = h->metac;
25645a5aec3SBaptiste Daroussin 	h->metac = font;
25745a5aec3SBaptiste Daroussin 	return 1;
25861d06d6bSBaptiste Daroussin }
25961d06d6bSBaptiste Daroussin 
26045a5aec3SBaptiste Daroussin static void
26145a5aec3SBaptiste Daroussin print_metaf(struct html *h)
26245a5aec3SBaptiste Daroussin {
26361d06d6bSBaptiste Daroussin 	if (h->metaf) {
26461d06d6bSBaptiste Daroussin 		print_tagq(h, h->metaf);
26561d06d6bSBaptiste Daroussin 		h->metaf = NULL;
26661d06d6bSBaptiste Daroussin 	}
26745a5aec3SBaptiste Daroussin 	switch (h->metac) {
26845a5aec3SBaptiste Daroussin 	case ESCAPE_FONTITALIC:
26961d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_I, "");
27061d06d6bSBaptiste Daroussin 		break;
27145a5aec3SBaptiste Daroussin 	case ESCAPE_FONTBOLD:
27261d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_B, "");
27361d06d6bSBaptiste Daroussin 		break;
27445a5aec3SBaptiste Daroussin 	case ESCAPE_FONTBI:
27561d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_B, "");
27661d06d6bSBaptiste Daroussin 		print_otag(h, TAG_I, "");
27761d06d6bSBaptiste Daroussin 		break;
278*6d38604fSBaptiste Daroussin 	case ESCAPE_FONTCR:
2797295610fSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
2807295610fSBaptiste Daroussin 		break;
281*6d38604fSBaptiste Daroussin 	case ESCAPE_FONTCB:
282*6d38604fSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
283*6d38604fSBaptiste Daroussin 		print_otag(h, TAG_B, "");
284*6d38604fSBaptiste Daroussin 		break;
285*6d38604fSBaptiste Daroussin 	case ESCAPE_FONTCI:
286*6d38604fSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
287*6d38604fSBaptiste Daroussin 		print_otag(h, TAG_I, "");
288*6d38604fSBaptiste Daroussin 		break;
28961d06d6bSBaptiste Daroussin 	default:
29061d06d6bSBaptiste Daroussin 		break;
29161d06d6bSBaptiste Daroussin 	}
29261d06d6bSBaptiste Daroussin }
29361d06d6bSBaptiste Daroussin 
2947295610fSBaptiste Daroussin void
2957295610fSBaptiste Daroussin html_close_paragraph(struct html *h)
2967295610fSBaptiste Daroussin {
297*6d38604fSBaptiste Daroussin 	struct tag	*this, *next;
298*6d38604fSBaptiste Daroussin 	int		 flags;
2997295610fSBaptiste Daroussin 
300*6d38604fSBaptiste Daroussin 	this = h->tag;
301*6d38604fSBaptiste Daroussin 	for (;;) {
302*6d38604fSBaptiste Daroussin 		next = this->next;
303*6d38604fSBaptiste Daroussin 		flags = htmltags[this->tag].flags;
304*6d38604fSBaptiste Daroussin 		if (flags & (HTML_INPHRASE | HTML_TOPHRASE))
305*6d38604fSBaptiste Daroussin 			print_ctag(h, this);
306*6d38604fSBaptiste Daroussin 		if ((flags & HTML_INPHRASE) == 0)
3077295610fSBaptiste Daroussin 			break;
308*6d38604fSBaptiste Daroussin 		this = next;
3097295610fSBaptiste Daroussin 	}
3107295610fSBaptiste Daroussin }
3117295610fSBaptiste Daroussin 
3127295610fSBaptiste Daroussin /*
3137295610fSBaptiste Daroussin  * ROFF_nf switches to no-fill mode, ROFF_fi to fill mode.
3147295610fSBaptiste Daroussin  * TOKEN_NONE does not switch.  The old mode is returned.
3157295610fSBaptiste Daroussin  */
3167295610fSBaptiste Daroussin enum roff_tok
3177295610fSBaptiste Daroussin html_fillmode(struct html *h, enum roff_tok want)
3187295610fSBaptiste Daroussin {
3197295610fSBaptiste Daroussin 	struct tag	*t;
3207295610fSBaptiste Daroussin 	enum roff_tok	 had;
3217295610fSBaptiste Daroussin 
3227295610fSBaptiste Daroussin 	for (t = h->tag; t != NULL; t = t->next)
3237295610fSBaptiste Daroussin 		if (t->tag == TAG_PRE)
3247295610fSBaptiste Daroussin 			break;
3257295610fSBaptiste Daroussin 
3267295610fSBaptiste Daroussin 	had = t == NULL ? ROFF_fi : ROFF_nf;
3277295610fSBaptiste Daroussin 
3287295610fSBaptiste Daroussin 	if (want != had) {
3297295610fSBaptiste Daroussin 		switch (want) {
3307295610fSBaptiste Daroussin 		case ROFF_fi:
3317295610fSBaptiste Daroussin 			print_tagq(h, t);
3327295610fSBaptiste Daroussin 			break;
3337295610fSBaptiste Daroussin 		case ROFF_nf:
3347295610fSBaptiste Daroussin 			html_close_paragraph(h);
3357295610fSBaptiste Daroussin 			print_otag(h, TAG_PRE, "");
3367295610fSBaptiste Daroussin 			break;
3377295610fSBaptiste Daroussin 		case TOKEN_NONE:
3387295610fSBaptiste Daroussin 			break;
3397295610fSBaptiste Daroussin 		default:
3407295610fSBaptiste Daroussin 			abort();
3417295610fSBaptiste Daroussin 		}
3427295610fSBaptiste Daroussin 	}
3437295610fSBaptiste Daroussin 	return had;
3447295610fSBaptiste Daroussin }
3457295610fSBaptiste Daroussin 
346*6d38604fSBaptiste Daroussin /*
347*6d38604fSBaptiste Daroussin  * Allocate a string to be used for the "id=" attribute of an HTML
348*6d38604fSBaptiste Daroussin  * element and/or as a segment identifier for a URI in an <a> element.
349*6d38604fSBaptiste Daroussin  * The function may fail and return NULL if the node lacks text data
350*6d38604fSBaptiste Daroussin  * to create the attribute from.
351*6d38604fSBaptiste Daroussin  * The caller is responsible for free(3)ing the returned string.
352*6d38604fSBaptiste Daroussin  *
353*6d38604fSBaptiste Daroussin  * If the "unique" argument is non-zero, the "id_unique" ohash table
354*6d38604fSBaptiste Daroussin  * is used for de-duplication.  If the "unique" argument is 1,
355*6d38604fSBaptiste Daroussin  * it is the first time the function is called for this tag and
356*6d38604fSBaptiste Daroussin  * location, so if an ordinal suffix is needed, it is incremented.
357*6d38604fSBaptiste Daroussin  * If the "unique" argument is 2, it is the second time the function
358*6d38604fSBaptiste Daroussin  * is called for this tag and location, so the ordinal suffix
359*6d38604fSBaptiste Daroussin  * remains unchanged.
360*6d38604fSBaptiste Daroussin  */
36161d06d6bSBaptiste Daroussin char *
36261d06d6bSBaptiste Daroussin html_make_id(const struct roff_node *n, int unique)
36361d06d6bSBaptiste Daroussin {
36461d06d6bSBaptiste Daroussin 	const struct roff_node	*nch;
365*6d38604fSBaptiste Daroussin 	struct id_entry		*entry;
366*6d38604fSBaptiste Daroussin 	char			*buf, *cp;
367*6d38604fSBaptiste Daroussin 	size_t			 len;
36861d06d6bSBaptiste Daroussin 	unsigned int		 slot;
36961d06d6bSBaptiste Daroussin 
370*6d38604fSBaptiste Daroussin 	if (n->tag != NULL)
371*6d38604fSBaptiste Daroussin 		buf = mandoc_strdup(n->tag);
372*6d38604fSBaptiste Daroussin 	else {
373*6d38604fSBaptiste Daroussin 		switch (n->tok) {
374*6d38604fSBaptiste Daroussin 		case MDOC_Sh:
375*6d38604fSBaptiste Daroussin 		case MDOC_Ss:
376*6d38604fSBaptiste Daroussin 		case MDOC_Sx:
377*6d38604fSBaptiste Daroussin 		case MAN_SH:
378*6d38604fSBaptiste Daroussin 		case MAN_SS:
37961d06d6bSBaptiste Daroussin 			for (nch = n->child; nch != NULL; nch = nch->next)
38061d06d6bSBaptiste Daroussin 				if (nch->type != ROFFT_TEXT)
38161d06d6bSBaptiste Daroussin 					return NULL;
38261d06d6bSBaptiste Daroussin 			buf = NULL;
38361d06d6bSBaptiste Daroussin 			deroff(&buf, n);
38461d06d6bSBaptiste Daroussin 			if (buf == NULL)
38561d06d6bSBaptiste Daroussin 				return NULL;
386*6d38604fSBaptiste Daroussin 			break;
387*6d38604fSBaptiste Daroussin 		default:
388*6d38604fSBaptiste Daroussin 			if (n->child == NULL || n->child->type != ROFFT_TEXT)
389*6d38604fSBaptiste Daroussin 				return NULL;
390*6d38604fSBaptiste Daroussin 			buf = mandoc_strdup(n->child->string);
391*6d38604fSBaptiste Daroussin 			break;
392*6d38604fSBaptiste Daroussin 		}
393*6d38604fSBaptiste Daroussin 	}
39461d06d6bSBaptiste Daroussin 
39561d06d6bSBaptiste Daroussin 	/*
39661d06d6bSBaptiste Daroussin 	 * In ID attributes, only use ASCII characters that are
39761d06d6bSBaptiste Daroussin 	 * permitted in URL-fragment strings according to the
39861d06d6bSBaptiste Daroussin 	 * explicit list at:
39961d06d6bSBaptiste Daroussin 	 * https://url.spec.whatwg.org/#url-fragment-string
400*6d38604fSBaptiste Daroussin 	 * In addition, reserve '~' for ordinal suffixes.
40161d06d6bSBaptiste Daroussin 	 */
40261d06d6bSBaptiste Daroussin 
40361d06d6bSBaptiste Daroussin 	for (cp = buf; *cp != '\0'; cp++)
40461d06d6bSBaptiste Daroussin 		if (isalnum((unsigned char)*cp) == 0 &&
405*6d38604fSBaptiste Daroussin 		    strchr("!$&'()*+,-./:;=?@_", *cp) == NULL)
40661d06d6bSBaptiste Daroussin 			*cp = '_';
40761d06d6bSBaptiste Daroussin 
40861d06d6bSBaptiste Daroussin 	if (unique == 0)
40961d06d6bSBaptiste Daroussin 		return buf;
41061d06d6bSBaptiste Daroussin 
41161d06d6bSBaptiste Daroussin 	/* Avoid duplicate HTML id= attributes. */
41261d06d6bSBaptiste Daroussin 
41361d06d6bSBaptiste Daroussin 	slot = ohash_qlookup(&id_unique, buf);
414*6d38604fSBaptiste Daroussin 	if ((entry = ohash_find(&id_unique, slot)) == NULL) {
415*6d38604fSBaptiste Daroussin 		len = strlen(buf) + 1;
416*6d38604fSBaptiste Daroussin 		entry = mandoc_malloc(sizeof(*entry) + len);
417*6d38604fSBaptiste Daroussin 		entry->ord = 1;
418*6d38604fSBaptiste Daroussin 		memcpy(entry->id, buf, len);
419*6d38604fSBaptiste Daroussin 		ohash_insert(&id_unique, slot, entry);
420*6d38604fSBaptiste Daroussin 	} else if (unique == 1)
421*6d38604fSBaptiste Daroussin 		entry->ord++;
422*6d38604fSBaptiste Daroussin 
423*6d38604fSBaptiste Daroussin 	if (entry->ord > 1) {
424*6d38604fSBaptiste Daroussin 		cp = buf;
425*6d38604fSBaptiste Daroussin 		mandoc_asprintf(&buf, "%s~%d", cp, entry->ord);
426*6d38604fSBaptiste Daroussin 		free(cp);
42761d06d6bSBaptiste Daroussin 	}
42861d06d6bSBaptiste Daroussin 	return buf;
42961d06d6bSBaptiste Daroussin }
43061d06d6bSBaptiste Daroussin 
43161d06d6bSBaptiste Daroussin static int
43261d06d6bSBaptiste Daroussin print_escape(struct html *h, char c)
43361d06d6bSBaptiste Daroussin {
43461d06d6bSBaptiste Daroussin 
43561d06d6bSBaptiste Daroussin 	switch (c) {
43661d06d6bSBaptiste Daroussin 	case '<':
43761d06d6bSBaptiste Daroussin 		print_word(h, "&lt;");
43861d06d6bSBaptiste Daroussin 		break;
43961d06d6bSBaptiste Daroussin 	case '>':
44061d06d6bSBaptiste Daroussin 		print_word(h, "&gt;");
44161d06d6bSBaptiste Daroussin 		break;
44261d06d6bSBaptiste Daroussin 	case '&':
44361d06d6bSBaptiste Daroussin 		print_word(h, "&amp;");
44461d06d6bSBaptiste Daroussin 		break;
44561d06d6bSBaptiste Daroussin 	case '"':
44661d06d6bSBaptiste Daroussin 		print_word(h, "&quot;");
44761d06d6bSBaptiste Daroussin 		break;
44861d06d6bSBaptiste Daroussin 	case ASCII_NBRSP:
44961d06d6bSBaptiste Daroussin 		print_word(h, "&nbsp;");
45061d06d6bSBaptiste Daroussin 		break;
45161d06d6bSBaptiste Daroussin 	case ASCII_HYPH:
45261d06d6bSBaptiste Daroussin 		print_byte(h, '-');
45361d06d6bSBaptiste Daroussin 		break;
45461d06d6bSBaptiste Daroussin 	case ASCII_BREAK:
45561d06d6bSBaptiste Daroussin 		break;
45661d06d6bSBaptiste Daroussin 	default:
45761d06d6bSBaptiste Daroussin 		return 0;
45861d06d6bSBaptiste Daroussin 	}
45961d06d6bSBaptiste Daroussin 	return 1;
46061d06d6bSBaptiste Daroussin }
46161d06d6bSBaptiste Daroussin 
46261d06d6bSBaptiste Daroussin static int
46361d06d6bSBaptiste Daroussin print_encode(struct html *h, const char *p, const char *pend, int norecurse)
46461d06d6bSBaptiste Daroussin {
46561d06d6bSBaptiste Daroussin 	char		 numbuf[16];
46661d06d6bSBaptiste Daroussin 	const char	*seq;
46761d06d6bSBaptiste Daroussin 	size_t		 sz;
46861d06d6bSBaptiste Daroussin 	int		 c, len, breakline, nospace;
46961d06d6bSBaptiste Daroussin 	enum mandoc_esc	 esc;
47061d06d6bSBaptiste Daroussin 	static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"',
47161d06d6bSBaptiste Daroussin 		ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
47261d06d6bSBaptiste Daroussin 
47361d06d6bSBaptiste Daroussin 	if (pend == NULL)
47461d06d6bSBaptiste Daroussin 		pend = strchr(p, '\0');
47561d06d6bSBaptiste Daroussin 
47661d06d6bSBaptiste Daroussin 	breakline = 0;
47761d06d6bSBaptiste Daroussin 	nospace = 0;
47861d06d6bSBaptiste Daroussin 
47961d06d6bSBaptiste Daroussin 	while (p < pend) {
48061d06d6bSBaptiste Daroussin 		if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
48161d06d6bSBaptiste Daroussin 			h->flags &= ~HTML_SKIPCHAR;
48261d06d6bSBaptiste Daroussin 			p++;
48361d06d6bSBaptiste Daroussin 			continue;
48461d06d6bSBaptiste Daroussin 		}
48561d06d6bSBaptiste Daroussin 
48661d06d6bSBaptiste Daroussin 		for (sz = strcspn(p, rejs); sz-- && p < pend; p++)
48761d06d6bSBaptiste Daroussin 			print_byte(h, *p);
48861d06d6bSBaptiste Daroussin 
48961d06d6bSBaptiste Daroussin 		if (breakline &&
49061d06d6bSBaptiste Daroussin 		    (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) {
4917295610fSBaptiste Daroussin 			print_otag(h, TAG_BR, "");
49261d06d6bSBaptiste Daroussin 			breakline = 0;
49361d06d6bSBaptiste Daroussin 			while (p < pend && (*p == ' ' || *p == ASCII_NBRSP))
49461d06d6bSBaptiste Daroussin 				p++;
49561d06d6bSBaptiste Daroussin 			continue;
49661d06d6bSBaptiste Daroussin 		}
49761d06d6bSBaptiste Daroussin 
49861d06d6bSBaptiste Daroussin 		if (p >= pend)
49961d06d6bSBaptiste Daroussin 			break;
50061d06d6bSBaptiste Daroussin 
50161d06d6bSBaptiste Daroussin 		if (*p == ' ') {
50261d06d6bSBaptiste Daroussin 			print_endword(h);
50361d06d6bSBaptiste Daroussin 			p++;
50461d06d6bSBaptiste Daroussin 			continue;
50561d06d6bSBaptiste Daroussin 		}
50661d06d6bSBaptiste Daroussin 
50761d06d6bSBaptiste Daroussin 		if (print_escape(h, *p++))
50861d06d6bSBaptiste Daroussin 			continue;
50961d06d6bSBaptiste Daroussin 
51061d06d6bSBaptiste Daroussin 		esc = mandoc_escape(&p, &seq, &len);
51161d06d6bSBaptiste Daroussin 		switch (esc) {
51261d06d6bSBaptiste Daroussin 		case ESCAPE_FONT:
51361d06d6bSBaptiste Daroussin 		case ESCAPE_FONTPREV:
51461d06d6bSBaptiste Daroussin 		case ESCAPE_FONTBOLD:
51561d06d6bSBaptiste Daroussin 		case ESCAPE_FONTITALIC:
51661d06d6bSBaptiste Daroussin 		case ESCAPE_FONTBI:
51761d06d6bSBaptiste Daroussin 		case ESCAPE_FONTROMAN:
518*6d38604fSBaptiste Daroussin 		case ESCAPE_FONTCR:
519*6d38604fSBaptiste Daroussin 		case ESCAPE_FONTCB:
520*6d38604fSBaptiste Daroussin 		case ESCAPE_FONTCI:
5217295610fSBaptiste Daroussin 			if (0 == norecurse) {
5227295610fSBaptiste Daroussin 				h->flags |= HTML_NOSPACE;
52345a5aec3SBaptiste Daroussin 				if (html_setfont(h, esc))
52445a5aec3SBaptiste Daroussin 					print_metaf(h);
5257295610fSBaptiste Daroussin 				h->flags &= ~HTML_NOSPACE;
5267295610fSBaptiste Daroussin 			}
52761d06d6bSBaptiste Daroussin 			continue;
52861d06d6bSBaptiste Daroussin 		case ESCAPE_SKIPCHAR:
52961d06d6bSBaptiste Daroussin 			h->flags |= HTML_SKIPCHAR;
53061d06d6bSBaptiste Daroussin 			continue;
5317295610fSBaptiste Daroussin 		case ESCAPE_ERROR:
5327295610fSBaptiste Daroussin 			continue;
53361d06d6bSBaptiste Daroussin 		default:
53461d06d6bSBaptiste Daroussin 			break;
53561d06d6bSBaptiste Daroussin 		}
53661d06d6bSBaptiste Daroussin 
53761d06d6bSBaptiste Daroussin 		if (h->flags & HTML_SKIPCHAR) {
53861d06d6bSBaptiste Daroussin 			h->flags &= ~HTML_SKIPCHAR;
53961d06d6bSBaptiste Daroussin 			continue;
54061d06d6bSBaptiste Daroussin 		}
54161d06d6bSBaptiste Daroussin 
54261d06d6bSBaptiste Daroussin 		switch (esc) {
54361d06d6bSBaptiste Daroussin 		case ESCAPE_UNICODE:
54461d06d6bSBaptiste Daroussin 			/* Skip past "u" header. */
54561d06d6bSBaptiste Daroussin 			c = mchars_num2uc(seq + 1, len - 1);
54661d06d6bSBaptiste Daroussin 			break;
54761d06d6bSBaptiste Daroussin 		case ESCAPE_NUMBERED:
54861d06d6bSBaptiste Daroussin 			c = mchars_num2char(seq, len);
54961d06d6bSBaptiste Daroussin 			if (c < 0)
55061d06d6bSBaptiste Daroussin 				continue;
55161d06d6bSBaptiste Daroussin 			break;
55261d06d6bSBaptiste Daroussin 		case ESCAPE_SPECIAL:
55361d06d6bSBaptiste Daroussin 			c = mchars_spec2cp(seq, len);
55461d06d6bSBaptiste Daroussin 			if (c <= 0)
55561d06d6bSBaptiste Daroussin 				continue;
55661d06d6bSBaptiste Daroussin 			break;
5577295610fSBaptiste Daroussin 		case ESCAPE_UNDEF:
5587295610fSBaptiste Daroussin 			c = *seq;
5597295610fSBaptiste Daroussin 			break;
5607295610fSBaptiste Daroussin 		case ESCAPE_DEVICE:
5617295610fSBaptiste Daroussin 			print_word(h, "html");
5627295610fSBaptiste Daroussin 			continue;
56361d06d6bSBaptiste Daroussin 		case ESCAPE_BREAK:
56461d06d6bSBaptiste Daroussin 			breakline = 1;
56561d06d6bSBaptiste Daroussin 			continue;
56661d06d6bSBaptiste Daroussin 		case ESCAPE_NOSPACE:
56761d06d6bSBaptiste Daroussin 			if ('\0' == *p)
56861d06d6bSBaptiste Daroussin 				nospace = 1;
56961d06d6bSBaptiste Daroussin 			continue;
57061d06d6bSBaptiste Daroussin 		case ESCAPE_OVERSTRIKE:
57161d06d6bSBaptiste Daroussin 			if (len == 0)
57261d06d6bSBaptiste Daroussin 				continue;
57361d06d6bSBaptiste Daroussin 			c = seq[len - 1];
57461d06d6bSBaptiste Daroussin 			break;
57561d06d6bSBaptiste Daroussin 		default:
57661d06d6bSBaptiste Daroussin 			continue;
57761d06d6bSBaptiste Daroussin 		}
57861d06d6bSBaptiste Daroussin 		if ((c < 0x20 && c != 0x09) ||
57961d06d6bSBaptiste Daroussin 		    (c > 0x7E && c < 0xA0))
58061d06d6bSBaptiste Daroussin 			c = 0xFFFD;
58161d06d6bSBaptiste Daroussin 		if (c > 0x7E) {
58261d06d6bSBaptiste Daroussin 			(void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c);
58361d06d6bSBaptiste Daroussin 			print_word(h, numbuf);
58461d06d6bSBaptiste Daroussin 		} else if (print_escape(h, c) == 0)
58561d06d6bSBaptiste Daroussin 			print_byte(h, c);
58661d06d6bSBaptiste Daroussin 	}
58761d06d6bSBaptiste Daroussin 
58861d06d6bSBaptiste Daroussin 	return nospace;
58961d06d6bSBaptiste Daroussin }
59061d06d6bSBaptiste Daroussin 
59161d06d6bSBaptiste Daroussin static void
59261d06d6bSBaptiste Daroussin print_href(struct html *h, const char *name, const char *sec, int man)
59361d06d6bSBaptiste Daroussin {
5947295610fSBaptiste Daroussin 	struct stat	 sb;
59561d06d6bSBaptiste Daroussin 	const char	*p, *pp;
5967295610fSBaptiste Daroussin 	char		*filename;
59761d06d6bSBaptiste Daroussin 
5987295610fSBaptiste Daroussin 	if (man) {
5997295610fSBaptiste Daroussin 		pp = h->base_man1;
6007295610fSBaptiste Daroussin 		if (h->base_man2 != NULL) {
6017295610fSBaptiste Daroussin 			mandoc_asprintf(&filename, "%s.%s", name, sec);
6027295610fSBaptiste Daroussin 			if (stat(filename, &sb) == -1)
6037295610fSBaptiste Daroussin 				pp = h->base_man2;
6047295610fSBaptiste Daroussin 			free(filename);
6057295610fSBaptiste Daroussin 		}
6067295610fSBaptiste Daroussin 	} else
6077295610fSBaptiste Daroussin 		pp = h->base_includes;
6087295610fSBaptiste Daroussin 
60961d06d6bSBaptiste Daroussin 	while ((p = strchr(pp, '%')) != NULL) {
61061d06d6bSBaptiste Daroussin 		print_encode(h, pp, p, 1);
61161d06d6bSBaptiste Daroussin 		if (man && p[1] == 'S') {
61261d06d6bSBaptiste Daroussin 			if (sec == NULL)
61361d06d6bSBaptiste Daroussin 				print_byte(h, '1');
61461d06d6bSBaptiste Daroussin 			else
61561d06d6bSBaptiste Daroussin 				print_encode(h, sec, NULL, 1);
61661d06d6bSBaptiste Daroussin 		} else if ((man && p[1] == 'N') ||
61761d06d6bSBaptiste Daroussin 		    (man == 0 && p[1] == 'I'))
61861d06d6bSBaptiste Daroussin 			print_encode(h, name, NULL, 1);
61961d06d6bSBaptiste Daroussin 		else
62061d06d6bSBaptiste Daroussin 			print_encode(h, p, p + 2, 1);
62161d06d6bSBaptiste Daroussin 		pp = p + 2;
62261d06d6bSBaptiste Daroussin 	}
62361d06d6bSBaptiste Daroussin 	if (*pp != '\0')
62461d06d6bSBaptiste Daroussin 		print_encode(h, pp, NULL, 1);
62561d06d6bSBaptiste Daroussin }
62661d06d6bSBaptiste Daroussin 
62761d06d6bSBaptiste Daroussin struct tag *
62861d06d6bSBaptiste Daroussin print_otag(struct html *h, enum htmltag tag, const char *fmt, ...)
62961d06d6bSBaptiste Daroussin {
63061d06d6bSBaptiste Daroussin 	va_list		 ap;
63161d06d6bSBaptiste Daroussin 	struct tag	*t;
63261d06d6bSBaptiste Daroussin 	const char	*attr;
63361d06d6bSBaptiste Daroussin 	char		*arg1, *arg2;
6347295610fSBaptiste Daroussin 	int		 style_written, tflags;
63561d06d6bSBaptiste Daroussin 
63661d06d6bSBaptiste Daroussin 	tflags = htmltags[tag].flags;
63761d06d6bSBaptiste Daroussin 
638*6d38604fSBaptiste Daroussin 	/* Flow content is not allowed in phrasing context. */
639*6d38604fSBaptiste Daroussin 
640*6d38604fSBaptiste Daroussin 	if ((tflags & HTML_INPHRASE) == 0) {
641*6d38604fSBaptiste Daroussin 		for (t = h->tag; t != NULL; t = t->next) {
642*6d38604fSBaptiste Daroussin 			if (t->closed)
643*6d38604fSBaptiste Daroussin 				continue;
644*6d38604fSBaptiste Daroussin 			assert((htmltags[t->tag].flags & HTML_TOPHRASE) == 0);
645*6d38604fSBaptiste Daroussin 			break;
646*6d38604fSBaptiste Daroussin 		}
647*6d38604fSBaptiste Daroussin 
648*6d38604fSBaptiste Daroussin 	/*
649*6d38604fSBaptiste Daroussin 	 * Always wrap phrasing elements in a paragraph
650*6d38604fSBaptiste Daroussin 	 * unless already contained in some flow container;
651*6d38604fSBaptiste Daroussin 	 * never put them directly into a section.
652*6d38604fSBaptiste Daroussin 	 */
653*6d38604fSBaptiste Daroussin 
654*6d38604fSBaptiste Daroussin 	} else if (tflags & HTML_TOPHRASE && h->tag->tag == TAG_SECTION)
655*6d38604fSBaptiste Daroussin 		print_otag(h, TAG_P, "c", "Pp");
656*6d38604fSBaptiste Daroussin 
65761d06d6bSBaptiste Daroussin 	/* Push this tag onto the stack of open scopes. */
65861d06d6bSBaptiste Daroussin 
65961d06d6bSBaptiste Daroussin 	if ((tflags & HTML_NOSTACK) == 0) {
66061d06d6bSBaptiste Daroussin 		t = mandoc_malloc(sizeof(struct tag));
66161d06d6bSBaptiste Daroussin 		t->tag = tag;
66261d06d6bSBaptiste Daroussin 		t->next = h->tag;
6637295610fSBaptiste Daroussin 		t->refcnt = 0;
6647295610fSBaptiste Daroussin 		t->closed = 0;
66561d06d6bSBaptiste Daroussin 		h->tag = t;
66661d06d6bSBaptiste Daroussin 	} else
66761d06d6bSBaptiste Daroussin 		t = NULL;
66861d06d6bSBaptiste Daroussin 
66961d06d6bSBaptiste Daroussin 	if (tflags & HTML_NLBEFORE)
67061d06d6bSBaptiste Daroussin 		print_endline(h);
67161d06d6bSBaptiste Daroussin 	if (h->col == 0)
67261d06d6bSBaptiste Daroussin 		print_indent(h);
67361d06d6bSBaptiste Daroussin 	else if ((h->flags & HTML_NOSPACE) == 0) {
67461d06d6bSBaptiste Daroussin 		if (h->flags & HTML_KEEP)
67561d06d6bSBaptiste Daroussin 			print_word(h, "&#x00A0;");
67661d06d6bSBaptiste Daroussin 		else {
67761d06d6bSBaptiste Daroussin 			if (h->flags & HTML_PREKEEP)
67861d06d6bSBaptiste Daroussin 				h->flags |= HTML_KEEP;
67961d06d6bSBaptiste Daroussin 			print_endword(h);
68061d06d6bSBaptiste Daroussin 		}
68161d06d6bSBaptiste Daroussin 	}
68261d06d6bSBaptiste Daroussin 
68361d06d6bSBaptiste Daroussin 	if ( ! (h->flags & HTML_NONOSPACE))
68461d06d6bSBaptiste Daroussin 		h->flags &= ~HTML_NOSPACE;
68561d06d6bSBaptiste Daroussin 	else
68661d06d6bSBaptiste Daroussin 		h->flags |= HTML_NOSPACE;
68761d06d6bSBaptiste Daroussin 
68861d06d6bSBaptiste Daroussin 	/* Print out the tag name and attributes. */
68961d06d6bSBaptiste Daroussin 
69061d06d6bSBaptiste Daroussin 	print_byte(h, '<');
69161d06d6bSBaptiste Daroussin 	print_word(h, htmltags[tag].name);
69261d06d6bSBaptiste Daroussin 
69361d06d6bSBaptiste Daroussin 	va_start(ap, fmt);
69461d06d6bSBaptiste Daroussin 
6957295610fSBaptiste Daroussin 	while (*fmt != '\0' && *fmt != 's') {
69661d06d6bSBaptiste Daroussin 
69761d06d6bSBaptiste Daroussin 		/* Parse attributes and arguments. */
69861d06d6bSBaptiste Daroussin 
69961d06d6bSBaptiste Daroussin 		arg1 = va_arg(ap, char *);
70061d06d6bSBaptiste Daroussin 		arg2 = NULL;
70161d06d6bSBaptiste Daroussin 		switch (*fmt++) {
70261d06d6bSBaptiste Daroussin 		case 'c':
70361d06d6bSBaptiste Daroussin 			attr = "class";
70461d06d6bSBaptiste Daroussin 			break;
70561d06d6bSBaptiste Daroussin 		case 'h':
70661d06d6bSBaptiste Daroussin 			attr = "href";
70761d06d6bSBaptiste Daroussin 			break;
70861d06d6bSBaptiste Daroussin 		case 'i':
70961d06d6bSBaptiste Daroussin 			attr = "id";
71061d06d6bSBaptiste Daroussin 			break;
71161d06d6bSBaptiste Daroussin 		case '?':
71261d06d6bSBaptiste Daroussin 			attr = arg1;
71361d06d6bSBaptiste Daroussin 			arg1 = va_arg(ap, char *);
71461d06d6bSBaptiste Daroussin 			break;
71561d06d6bSBaptiste Daroussin 		default:
71661d06d6bSBaptiste Daroussin 			abort();
71761d06d6bSBaptiste Daroussin 		}
71861d06d6bSBaptiste Daroussin 		if (*fmt == 'M')
71961d06d6bSBaptiste Daroussin 			arg2 = va_arg(ap, char *);
72061d06d6bSBaptiste Daroussin 		if (arg1 == NULL)
72161d06d6bSBaptiste Daroussin 			continue;
72261d06d6bSBaptiste Daroussin 
72361d06d6bSBaptiste Daroussin 		/* Print the attributes. */
72461d06d6bSBaptiste Daroussin 
72561d06d6bSBaptiste Daroussin 		print_byte(h, ' ');
72661d06d6bSBaptiste Daroussin 		print_word(h, attr);
72761d06d6bSBaptiste Daroussin 		print_byte(h, '=');
72861d06d6bSBaptiste Daroussin 		print_byte(h, '"');
72961d06d6bSBaptiste Daroussin 		switch (*fmt) {
73061d06d6bSBaptiste Daroussin 		case 'I':
73161d06d6bSBaptiste Daroussin 			print_href(h, arg1, NULL, 0);
73261d06d6bSBaptiste Daroussin 			fmt++;
73361d06d6bSBaptiste Daroussin 			break;
73461d06d6bSBaptiste Daroussin 		case 'M':
73561d06d6bSBaptiste Daroussin 			print_href(h, arg1, arg2, 1);
73661d06d6bSBaptiste Daroussin 			fmt++;
73761d06d6bSBaptiste Daroussin 			break;
73861d06d6bSBaptiste Daroussin 		case 'R':
73961d06d6bSBaptiste Daroussin 			print_byte(h, '#');
74061d06d6bSBaptiste Daroussin 			print_encode(h, arg1, NULL, 1);
74161d06d6bSBaptiste Daroussin 			fmt++;
74261d06d6bSBaptiste Daroussin 			break;
74361d06d6bSBaptiste Daroussin 		default:
74461d06d6bSBaptiste Daroussin 			print_encode(h, arg1, NULL, 1);
7457295610fSBaptiste Daroussin 			break;
7467295610fSBaptiste Daroussin 		}
7477295610fSBaptiste Daroussin 		print_byte(h, '"');
7487295610fSBaptiste Daroussin 	}
7497295610fSBaptiste Daroussin 
7507295610fSBaptiste Daroussin 	style_written = 0;
7517295610fSBaptiste Daroussin 	while (*fmt++ == 's') {
7527295610fSBaptiste Daroussin 		arg1 = va_arg(ap, char *);
7537295610fSBaptiste Daroussin 		arg2 = va_arg(ap, char *);
7547295610fSBaptiste Daroussin 		if (arg2 == NULL)
7557295610fSBaptiste Daroussin 			continue;
7567295610fSBaptiste Daroussin 		print_byte(h, ' ');
7577295610fSBaptiste Daroussin 		if (style_written == 0) {
7587295610fSBaptiste Daroussin 			print_word(h, "style=\"");
7597295610fSBaptiste Daroussin 			style_written = 1;
7607295610fSBaptiste Daroussin 		}
76161d06d6bSBaptiste Daroussin 		print_word(h, arg1);
76261d06d6bSBaptiste Daroussin 		print_byte(h, ':');
76361d06d6bSBaptiste Daroussin 		print_byte(h, ' ');
76461d06d6bSBaptiste Daroussin 		print_word(h, arg2);
76561d06d6bSBaptiste Daroussin 		print_byte(h, ';');
76661d06d6bSBaptiste Daroussin 	}
7677295610fSBaptiste Daroussin 	if (style_written)
76861d06d6bSBaptiste Daroussin 		print_byte(h, '"');
7697295610fSBaptiste Daroussin 
77061d06d6bSBaptiste Daroussin 	va_end(ap);
77161d06d6bSBaptiste Daroussin 
77261d06d6bSBaptiste Daroussin 	/* Accommodate for "well-formed" singleton escaping. */
77361d06d6bSBaptiste Daroussin 
774*6d38604fSBaptiste Daroussin 	if (htmltags[tag].flags & HTML_NOSTACK)
77561d06d6bSBaptiste Daroussin 		print_byte(h, '/');
77661d06d6bSBaptiste Daroussin 
77761d06d6bSBaptiste Daroussin 	print_byte(h, '>');
77861d06d6bSBaptiste Daroussin 
77961d06d6bSBaptiste Daroussin 	if (tflags & HTML_NLBEGIN)
78061d06d6bSBaptiste Daroussin 		print_endline(h);
78161d06d6bSBaptiste Daroussin 	else
78261d06d6bSBaptiste Daroussin 		h->flags |= HTML_NOSPACE;
78361d06d6bSBaptiste Daroussin 
78461d06d6bSBaptiste Daroussin 	if (tflags & HTML_INDENT)
78561d06d6bSBaptiste Daroussin 		h->indent++;
78661d06d6bSBaptiste Daroussin 	if (tflags & HTML_NOINDENT)
78761d06d6bSBaptiste Daroussin 		h->noindent++;
78861d06d6bSBaptiste Daroussin 
78961d06d6bSBaptiste Daroussin 	return t;
79061d06d6bSBaptiste Daroussin }
79161d06d6bSBaptiste Daroussin 
792*6d38604fSBaptiste Daroussin /*
793*6d38604fSBaptiste Daroussin  * Print an element with an optional "id=" attribute.
794*6d38604fSBaptiste Daroussin  * If the element has phrasing content and an "id=" attribute,
795*6d38604fSBaptiste Daroussin  * also add a permalink: outside if it can be in phrasing context,
796*6d38604fSBaptiste Daroussin  * inside otherwise.
797*6d38604fSBaptiste Daroussin  */
798*6d38604fSBaptiste Daroussin struct tag *
799*6d38604fSBaptiste Daroussin print_otag_id(struct html *h, enum htmltag elemtype, const char *cattr,
800*6d38604fSBaptiste Daroussin     struct roff_node *n)
801*6d38604fSBaptiste Daroussin {
802*6d38604fSBaptiste Daroussin 	struct roff_node *nch;
803*6d38604fSBaptiste Daroussin 	struct tag	*ret, *t;
804*6d38604fSBaptiste Daroussin 	char		*id, *href;
805*6d38604fSBaptiste Daroussin 
806*6d38604fSBaptiste Daroussin 	ret = NULL;
807*6d38604fSBaptiste Daroussin 	id = href = NULL;
808*6d38604fSBaptiste Daroussin 	if (n->flags & NODE_ID)
809*6d38604fSBaptiste Daroussin 		id = html_make_id(n, 1);
810*6d38604fSBaptiste Daroussin 	if (n->flags & NODE_HREF)
811*6d38604fSBaptiste Daroussin 		href = id == NULL ? html_make_id(n, 2) : id;
812*6d38604fSBaptiste Daroussin 	if (href != NULL && htmltags[elemtype].flags & HTML_INPHRASE)
813*6d38604fSBaptiste Daroussin 		ret = print_otag(h, TAG_A, "chR", "permalink", href);
814*6d38604fSBaptiste Daroussin 	t = print_otag(h, elemtype, "ci", cattr, id);
815*6d38604fSBaptiste Daroussin 	if (ret == NULL) {
816*6d38604fSBaptiste Daroussin 		ret = t;
817*6d38604fSBaptiste Daroussin 		if (href != NULL && (nch = n->child) != NULL) {
818*6d38604fSBaptiste Daroussin 			/* man(7) is safe, it tags phrasing content only. */
819*6d38604fSBaptiste Daroussin 			if (n->tok > MDOC_MAX ||
820*6d38604fSBaptiste Daroussin 			    htmltags[elemtype].flags & HTML_TOPHRASE)
821*6d38604fSBaptiste Daroussin 				nch = NULL;
822*6d38604fSBaptiste Daroussin 			else  /* For mdoc(7), beware of nested blocks. */
823*6d38604fSBaptiste Daroussin 				while (nch != NULL && nch->type == ROFFT_TEXT)
824*6d38604fSBaptiste Daroussin 					nch = nch->next;
825*6d38604fSBaptiste Daroussin 			if (nch == NULL)
826*6d38604fSBaptiste Daroussin 				print_otag(h, TAG_A, "chR", "permalink", href);
827*6d38604fSBaptiste Daroussin 		}
828*6d38604fSBaptiste Daroussin 	}
829*6d38604fSBaptiste Daroussin 	free(id);
830*6d38604fSBaptiste Daroussin 	if (id == NULL)
831*6d38604fSBaptiste Daroussin 		free(href);
832*6d38604fSBaptiste Daroussin 	return ret;
833*6d38604fSBaptiste Daroussin }
834*6d38604fSBaptiste Daroussin 
83561d06d6bSBaptiste Daroussin static void
83661d06d6bSBaptiste Daroussin print_ctag(struct html *h, struct tag *tag)
83761d06d6bSBaptiste Daroussin {
83861d06d6bSBaptiste Daroussin 	int	 tflags;
83961d06d6bSBaptiste Daroussin 
8407295610fSBaptiste Daroussin 	if (tag->closed == 0) {
8417295610fSBaptiste Daroussin 		tag->closed = 1;
84261d06d6bSBaptiste Daroussin 		if (tag == h->metaf)
84361d06d6bSBaptiste Daroussin 			h->metaf = NULL;
84461d06d6bSBaptiste Daroussin 		if (tag == h->tblt)
84561d06d6bSBaptiste Daroussin 			h->tblt = NULL;
84661d06d6bSBaptiste Daroussin 
84761d06d6bSBaptiste Daroussin 		tflags = htmltags[tag->tag].flags;
84861d06d6bSBaptiste Daroussin 		if (tflags & HTML_INDENT)
84961d06d6bSBaptiste Daroussin 			h->indent--;
85061d06d6bSBaptiste Daroussin 		if (tflags & HTML_NOINDENT)
85161d06d6bSBaptiste Daroussin 			h->noindent--;
85261d06d6bSBaptiste Daroussin 		if (tflags & HTML_NLEND)
85361d06d6bSBaptiste Daroussin 			print_endline(h);
85461d06d6bSBaptiste Daroussin 		print_indent(h);
85561d06d6bSBaptiste Daroussin 		print_byte(h, '<');
85661d06d6bSBaptiste Daroussin 		print_byte(h, '/');
85761d06d6bSBaptiste Daroussin 		print_word(h, htmltags[tag->tag].name);
85861d06d6bSBaptiste Daroussin 		print_byte(h, '>');
85961d06d6bSBaptiste Daroussin 		if (tflags & HTML_NLAFTER)
86061d06d6bSBaptiste Daroussin 			print_endline(h);
8617295610fSBaptiste Daroussin 	}
8627295610fSBaptiste Daroussin 	if (tag->refcnt == 0) {
86361d06d6bSBaptiste Daroussin 		h->tag = tag->next;
86461d06d6bSBaptiste Daroussin 		free(tag);
86561d06d6bSBaptiste Daroussin 	}
8667295610fSBaptiste Daroussin }
86761d06d6bSBaptiste Daroussin 
86861d06d6bSBaptiste Daroussin void
86961d06d6bSBaptiste Daroussin print_gen_decls(struct html *h)
87061d06d6bSBaptiste Daroussin {
87161d06d6bSBaptiste Daroussin 	print_word(h, "<!DOCTYPE html>");
87261d06d6bSBaptiste Daroussin 	print_endline(h);
87361d06d6bSBaptiste Daroussin }
87461d06d6bSBaptiste Daroussin 
87561d06d6bSBaptiste Daroussin void
87661d06d6bSBaptiste Daroussin print_gen_comment(struct html *h, struct roff_node *n)
87761d06d6bSBaptiste Daroussin {
87861d06d6bSBaptiste Daroussin 	int	 wantblank;
87961d06d6bSBaptiste Daroussin 
88061d06d6bSBaptiste Daroussin 	print_word(h, "<!-- This is an automatically generated file."
88161d06d6bSBaptiste Daroussin 	    "  Do not edit.");
88261d06d6bSBaptiste Daroussin 	h->indent = 1;
88361d06d6bSBaptiste Daroussin 	wantblank = 0;
88461d06d6bSBaptiste Daroussin 	while (n != NULL && n->type == ROFFT_COMMENT) {
88561d06d6bSBaptiste Daroussin 		if (strstr(n->string, "-->") == NULL &&
88661d06d6bSBaptiste Daroussin 		    (wantblank || *n->string != '\0')) {
88761d06d6bSBaptiste Daroussin 			print_endline(h);
88861d06d6bSBaptiste Daroussin 			print_indent(h);
88961d06d6bSBaptiste Daroussin 			print_word(h, n->string);
89061d06d6bSBaptiste Daroussin 			wantblank = *n->string != '\0';
89161d06d6bSBaptiste Daroussin 		}
89261d06d6bSBaptiste Daroussin 		n = n->next;
89361d06d6bSBaptiste Daroussin 	}
89461d06d6bSBaptiste Daroussin 	if (wantblank)
89561d06d6bSBaptiste Daroussin 		print_endline(h);
89661d06d6bSBaptiste Daroussin 	print_word(h, " -->");
89761d06d6bSBaptiste Daroussin 	print_endline(h);
89861d06d6bSBaptiste Daroussin 	h->indent = 0;
89961d06d6bSBaptiste Daroussin }
90061d06d6bSBaptiste Daroussin 
90161d06d6bSBaptiste Daroussin void
90261d06d6bSBaptiste Daroussin print_text(struct html *h, const char *word)
90361d06d6bSBaptiste Daroussin {
904*6d38604fSBaptiste Daroussin 	print_tagged_text(h, word, NULL);
905*6d38604fSBaptiste Daroussin }
906*6d38604fSBaptiste Daroussin 
907*6d38604fSBaptiste Daroussin void
908*6d38604fSBaptiste Daroussin print_tagged_text(struct html *h, const char *word, struct roff_node *n)
909*6d38604fSBaptiste Daroussin {
910*6d38604fSBaptiste Daroussin 	struct tag	*t;
911*6d38604fSBaptiste Daroussin 	char		*href;
912*6d38604fSBaptiste Daroussin 
913*6d38604fSBaptiste Daroussin 	/*
914*6d38604fSBaptiste Daroussin 	 * Always wrap text in a paragraph unless already contained in
915*6d38604fSBaptiste Daroussin 	 * some flow container; never put it directly into a section.
916*6d38604fSBaptiste Daroussin 	 */
917*6d38604fSBaptiste Daroussin 
918*6d38604fSBaptiste Daroussin 	if (h->tag->tag == TAG_SECTION)
919*6d38604fSBaptiste Daroussin 		print_otag(h, TAG_P, "c", "Pp");
920*6d38604fSBaptiste Daroussin 
921*6d38604fSBaptiste Daroussin 	/* Output whitespace before this text? */
922*6d38604fSBaptiste Daroussin 
92361d06d6bSBaptiste Daroussin 	if (h->col && (h->flags & HTML_NOSPACE) == 0) {
92461d06d6bSBaptiste Daroussin 		if ( ! (HTML_KEEP & h->flags)) {
92561d06d6bSBaptiste Daroussin 			if (HTML_PREKEEP & h->flags)
92661d06d6bSBaptiste Daroussin 				h->flags |= HTML_KEEP;
92761d06d6bSBaptiste Daroussin 			print_endword(h);
92861d06d6bSBaptiste Daroussin 		} else
92961d06d6bSBaptiste Daroussin 			print_word(h, "&#x00A0;");
93061d06d6bSBaptiste Daroussin 	}
93161d06d6bSBaptiste Daroussin 
932*6d38604fSBaptiste Daroussin 	/*
933*6d38604fSBaptiste Daroussin 	 * Optionally switch fonts, optionally write a permalink, then
934*6d38604fSBaptiste Daroussin 	 * print the text, optionally surrounded by HTML whitespace.
935*6d38604fSBaptiste Daroussin 	 */
936*6d38604fSBaptiste Daroussin 
93745a5aec3SBaptiste Daroussin 	assert(h->metaf == NULL);
93845a5aec3SBaptiste Daroussin 	print_metaf(h);
93961d06d6bSBaptiste Daroussin 	print_indent(h);
940*6d38604fSBaptiste Daroussin 
941*6d38604fSBaptiste Daroussin 	if (n != NULL && (href = html_make_id(n, 2)) != NULL) {
942*6d38604fSBaptiste Daroussin 		t = print_otag(h, TAG_A, "chR", "permalink", href);
943*6d38604fSBaptiste Daroussin 		free(href);
944*6d38604fSBaptiste Daroussin 	} else
945*6d38604fSBaptiste Daroussin 		t = NULL;
946*6d38604fSBaptiste Daroussin 
94761d06d6bSBaptiste Daroussin 	if ( ! print_encode(h, word, NULL, 0)) {
94861d06d6bSBaptiste Daroussin 		if ( ! (h->flags & HTML_NONOSPACE))
94961d06d6bSBaptiste Daroussin 			h->flags &= ~HTML_NOSPACE;
95061d06d6bSBaptiste Daroussin 		h->flags &= ~HTML_NONEWLINE;
95161d06d6bSBaptiste Daroussin 	} else
95261d06d6bSBaptiste Daroussin 		h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
95361d06d6bSBaptiste Daroussin 
95445a5aec3SBaptiste Daroussin 	if (h->metaf != NULL) {
95561d06d6bSBaptiste Daroussin 		print_tagq(h, h->metaf);
95661d06d6bSBaptiste Daroussin 		h->metaf = NULL;
957*6d38604fSBaptiste Daroussin 	} else if (t != NULL)
958*6d38604fSBaptiste Daroussin 		print_tagq(h, t);
95961d06d6bSBaptiste Daroussin 
96061d06d6bSBaptiste Daroussin 	h->flags &= ~HTML_IGNDELIM;
96161d06d6bSBaptiste Daroussin }
96261d06d6bSBaptiste Daroussin 
96361d06d6bSBaptiste Daroussin void
96461d06d6bSBaptiste Daroussin print_tagq(struct html *h, const struct tag *until)
96561d06d6bSBaptiste Daroussin {
9667295610fSBaptiste Daroussin 	struct tag	*this, *next;
96761d06d6bSBaptiste Daroussin 
9687295610fSBaptiste Daroussin 	for (this = h->tag; this != NULL; this = next) {
9697295610fSBaptiste Daroussin 		next = this == until ? NULL : this->next;
9707295610fSBaptiste Daroussin 		print_ctag(h, this);
97161d06d6bSBaptiste Daroussin 	}
97261d06d6bSBaptiste Daroussin }
97361d06d6bSBaptiste Daroussin 
9747295610fSBaptiste Daroussin /*
9757295610fSBaptiste Daroussin  * Close out all open elements up to but excluding suntil.
9767295610fSBaptiste Daroussin  * Note that a paragraph just inside stays open together with it
9777295610fSBaptiste Daroussin  * because paragraphs include subsequent phrasing content.
9787295610fSBaptiste Daroussin  */
97961d06d6bSBaptiste Daroussin void
98061d06d6bSBaptiste Daroussin print_stagq(struct html *h, const struct tag *suntil)
98161d06d6bSBaptiste Daroussin {
9827295610fSBaptiste Daroussin 	struct tag	*this, *next;
98361d06d6bSBaptiste Daroussin 
9847295610fSBaptiste Daroussin 	for (this = h->tag; this != NULL; this = next) {
9857295610fSBaptiste Daroussin 		next = this->next;
9867295610fSBaptiste Daroussin 		if (this == suntil || (next == suntil &&
9877295610fSBaptiste Daroussin 		    (this->tag == TAG_P || this->tag == TAG_PRE)))
9887295610fSBaptiste Daroussin 			break;
9897295610fSBaptiste Daroussin 		print_ctag(h, this);
99061d06d6bSBaptiste Daroussin 	}
99161d06d6bSBaptiste Daroussin }
99261d06d6bSBaptiste Daroussin 
99361d06d6bSBaptiste Daroussin 
99461d06d6bSBaptiste Daroussin /***********************************************************************
99561d06d6bSBaptiste Daroussin  * Low level output functions.
99661d06d6bSBaptiste Daroussin  * They implement line breaking using a short static buffer.
99761d06d6bSBaptiste Daroussin  ***********************************************************************/
99861d06d6bSBaptiste Daroussin 
99961d06d6bSBaptiste Daroussin /*
100061d06d6bSBaptiste Daroussin  * Buffer one HTML output byte.
100161d06d6bSBaptiste Daroussin  * If the buffer is full, flush and deactivate it and start a new line.
100261d06d6bSBaptiste Daroussin  * If the buffer is inactive, print directly.
100361d06d6bSBaptiste Daroussin  */
100461d06d6bSBaptiste Daroussin static void
100561d06d6bSBaptiste Daroussin print_byte(struct html *h, char c)
100661d06d6bSBaptiste Daroussin {
100761d06d6bSBaptiste Daroussin 	if ((h->flags & HTML_BUFFER) == 0) {
100861d06d6bSBaptiste Daroussin 		putchar(c);
100961d06d6bSBaptiste Daroussin 		h->col++;
101061d06d6bSBaptiste Daroussin 		return;
101161d06d6bSBaptiste Daroussin 	}
101261d06d6bSBaptiste Daroussin 
101361d06d6bSBaptiste Daroussin 	if (h->col + h->bufcol < sizeof(h->buf)) {
101461d06d6bSBaptiste Daroussin 		h->buf[h->bufcol++] = c;
101561d06d6bSBaptiste Daroussin 		return;
101661d06d6bSBaptiste Daroussin 	}
101761d06d6bSBaptiste Daroussin 
101861d06d6bSBaptiste Daroussin 	putchar('\n');
101961d06d6bSBaptiste Daroussin 	h->col = 0;
102061d06d6bSBaptiste Daroussin 	print_indent(h);
102161d06d6bSBaptiste Daroussin 	putchar(' ');
102261d06d6bSBaptiste Daroussin 	putchar(' ');
102361d06d6bSBaptiste Daroussin 	fwrite(h->buf, h->bufcol, 1, stdout);
102461d06d6bSBaptiste Daroussin 	putchar(c);
102561d06d6bSBaptiste Daroussin 	h->col = (h->indent + 1) * 2 + h->bufcol + 1;
102661d06d6bSBaptiste Daroussin 	h->bufcol = 0;
102761d06d6bSBaptiste Daroussin 	h->flags &= ~HTML_BUFFER;
102861d06d6bSBaptiste Daroussin }
102961d06d6bSBaptiste Daroussin 
103061d06d6bSBaptiste Daroussin /*
103161d06d6bSBaptiste Daroussin  * If something was printed on the current output line, end it.
103261d06d6bSBaptiste Daroussin  * Not to be called right after print_indent().
103361d06d6bSBaptiste Daroussin  */
103461d06d6bSBaptiste Daroussin void
103561d06d6bSBaptiste Daroussin print_endline(struct html *h)
103661d06d6bSBaptiste Daroussin {
103761d06d6bSBaptiste Daroussin 	if (h->col == 0)
103861d06d6bSBaptiste Daroussin 		return;
103961d06d6bSBaptiste Daroussin 
104061d06d6bSBaptiste Daroussin 	if (h->bufcol) {
104161d06d6bSBaptiste Daroussin 		putchar(' ');
104261d06d6bSBaptiste Daroussin 		fwrite(h->buf, h->bufcol, 1, stdout);
104361d06d6bSBaptiste Daroussin 		h->bufcol = 0;
104461d06d6bSBaptiste Daroussin 	}
104561d06d6bSBaptiste Daroussin 	putchar('\n');
104661d06d6bSBaptiste Daroussin 	h->col = 0;
104761d06d6bSBaptiste Daroussin 	h->flags |= HTML_NOSPACE;
104861d06d6bSBaptiste Daroussin 	h->flags &= ~HTML_BUFFER;
104961d06d6bSBaptiste Daroussin }
105061d06d6bSBaptiste Daroussin 
105161d06d6bSBaptiste Daroussin /*
105261d06d6bSBaptiste Daroussin  * Flush the HTML output buffer.
105361d06d6bSBaptiste Daroussin  * If it is inactive, activate it.
105461d06d6bSBaptiste Daroussin  */
105561d06d6bSBaptiste Daroussin static void
105661d06d6bSBaptiste Daroussin print_endword(struct html *h)
105761d06d6bSBaptiste Daroussin {
105861d06d6bSBaptiste Daroussin 	if (h->noindent) {
105961d06d6bSBaptiste Daroussin 		print_byte(h, ' ');
106061d06d6bSBaptiste Daroussin 		return;
106161d06d6bSBaptiste Daroussin 	}
106261d06d6bSBaptiste Daroussin 
106361d06d6bSBaptiste Daroussin 	if ((h->flags & HTML_BUFFER) == 0) {
106461d06d6bSBaptiste Daroussin 		h->col++;
106561d06d6bSBaptiste Daroussin 		h->flags |= HTML_BUFFER;
106661d06d6bSBaptiste Daroussin 	} else if (h->bufcol) {
106761d06d6bSBaptiste Daroussin 		putchar(' ');
106861d06d6bSBaptiste Daroussin 		fwrite(h->buf, h->bufcol, 1, stdout);
106961d06d6bSBaptiste Daroussin 		h->col += h->bufcol + 1;
107061d06d6bSBaptiste Daroussin 	}
107161d06d6bSBaptiste Daroussin 	h->bufcol = 0;
107261d06d6bSBaptiste Daroussin }
107361d06d6bSBaptiste Daroussin 
107461d06d6bSBaptiste Daroussin /*
107561d06d6bSBaptiste Daroussin  * If at the beginning of a new output line,
107661d06d6bSBaptiste Daroussin  * perform indentation and mark the line as containing output.
107761d06d6bSBaptiste Daroussin  * Make sure to really produce some output right afterwards,
107861d06d6bSBaptiste Daroussin  * but do not use print_otag() for producing it.
107961d06d6bSBaptiste Daroussin  */
108061d06d6bSBaptiste Daroussin static void
108161d06d6bSBaptiste Daroussin print_indent(struct html *h)
108261d06d6bSBaptiste Daroussin {
108361d06d6bSBaptiste Daroussin 	size_t	 i;
108461d06d6bSBaptiste Daroussin 
1085*6d38604fSBaptiste Daroussin 	if (h->col || h->noindent)
108661d06d6bSBaptiste Daroussin 		return;
108761d06d6bSBaptiste Daroussin 
108861d06d6bSBaptiste Daroussin 	h->col = h->indent * 2;
108961d06d6bSBaptiste Daroussin 	for (i = 0; i < h->col; i++)
109061d06d6bSBaptiste Daroussin 		putchar(' ');
109161d06d6bSBaptiste Daroussin }
109261d06d6bSBaptiste Daroussin 
109361d06d6bSBaptiste Daroussin /*
109461d06d6bSBaptiste Daroussin  * Print or buffer some characters
109561d06d6bSBaptiste Daroussin  * depending on the current HTML output buffer state.
109661d06d6bSBaptiste Daroussin  */
109761d06d6bSBaptiste Daroussin static void
109861d06d6bSBaptiste Daroussin print_word(struct html *h, const char *cp)
109961d06d6bSBaptiste Daroussin {
110061d06d6bSBaptiste Daroussin 	while (*cp != '\0')
110161d06d6bSBaptiste Daroussin 		print_byte(h, *cp++);
110261d06d6bSBaptiste Daroussin }
1103