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