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