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