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