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