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