GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mandoc/html.c Lines: 278 423 65.7 %
Date: 2017-11-07 Branches: 158 333 47.4 %

Line Branch Exec Source
1
/*	$OpenBSD: html.c,v 1.90 2017/09/06 16:24:11 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_aux.h"
30
#include "mandoc.h"
31
#include "roff.h"
32
#include "out.h"
33
#include "html.h"
34
#include "manconf.h"
35
#include "main.h"
36
37
struct	htmldata {
38
	const char	 *name;
39
	int		  flags;
40
#define	HTML_NOSTACK	 (1 << 0)
41
#define	HTML_AUTOCLOSE	 (1 << 1)
42
#define	HTML_NLBEFORE	 (1 << 2)
43
#define	HTML_NLBEGIN	 (1 << 3)
44
#define	HTML_NLEND	 (1 << 4)
45
#define	HTML_NLAFTER	 (1 << 5)
46
#define	HTML_NLAROUND	 (HTML_NLBEFORE | HTML_NLAFTER)
47
#define	HTML_NLINSIDE	 (HTML_NLBEGIN | HTML_NLEND)
48
#define	HTML_NLALL	 (HTML_NLAROUND | HTML_NLINSIDE)
49
#define	HTML_INDENT	 (1 << 6)
50
#define	HTML_NOINDENT	 (1 << 7)
51
};
52
53
static	const struct htmldata htmltags[TAG_MAX] = {
54
	{"html",	HTML_NLALL},
55
	{"head",	HTML_NLALL | HTML_INDENT},
56
	{"body",	HTML_NLALL},
57
	{"meta",	HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
58
	{"title",	HTML_NLAROUND},
59
	{"div",		HTML_NLAROUND},
60
	{"h1",		HTML_NLAROUND},
61
	{"h2",		HTML_NLAROUND},
62
	{"span",	0},
63
	{"link",	HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
64
	{"br",		HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
65
	{"a",		0},
66
	{"table",	HTML_NLALL | HTML_INDENT},
67
	{"colgroup",	HTML_NLALL | HTML_INDENT},
68
	{"col",		HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
69
	{"tr",		HTML_NLALL | HTML_INDENT},
70
	{"td",		HTML_NLAROUND},
71
	{"li",		HTML_NLAROUND | HTML_INDENT},
72
	{"ul",		HTML_NLALL | HTML_INDENT},
73
	{"ol",		HTML_NLALL | HTML_INDENT},
74
	{"dl",		HTML_NLALL | HTML_INDENT},
75
	{"dt",		HTML_NLAROUND},
76
	{"dd",		HTML_NLAROUND | HTML_INDENT},
77
	{"pre",		HTML_NLALL | HTML_NOINDENT},
78
	{"var",		0},
79
	{"cite",	0},
80
	{"b",		0},
81
	{"i",		0},
82
	{"code",	0},
83
	{"small",	0},
84
	{"style",	HTML_NLALL | HTML_INDENT},
85
	{"math",	HTML_NLALL | HTML_INDENT},
86
	{"mrow",	0},
87
	{"mi",		0},
88
	{"mn",		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
432
	h = mandoc_calloc(1, sizeof(struct html));
136
137
216
	h->tag = NULL;
138
216
	h->style = outopts->style;
139
216
	h->base_man = outopts->man;
140
216
	h->base_includes = outopts->includes;
141
216
	if (outopts->fragment)
142
		h->oflags |= HTML_FRAGMENT;
143
144
216
	return h;
145
}
146
147
void
148
html_free(void *p)
149
{
150
	struct tag	*tag;
151
	struct html	*h;
152
153
432
	h = (struct html *)p;
154
155
432
	while ((tag = h->tag) != NULL) {
156
		h->tag = tag->next;
157
		free(tag);
158
	}
159
160
216
	free(h);
161
216
}
162
163
void
164
print_gen_head(struct html *h)
165
{
166
	struct tag	*t;
167
168
432
	print_otag(h, TAG_META, "?", "charset", "utf-8");
169
170
	/*
171
	 * Print a default style-sheet.
172
	 */
173
174
216
	t = print_otag(h, TAG_STYLE, "");
175
216
	print_text(h, "table.head, table.foot { width: 100%; }");
176
216
	print_endline(h);
177
216
	print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }");
178
216
	print_endline(h);
179
216
	print_text(h, "td.head-vol { text-align: center; }");
180
216
	print_endline(h);
181
216
	print_text(h, "div.Pp { margin: 1ex 0ex; }");
182
216
	print_tagq(h, t);
183
184
216
	if (h->style)
185
		print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet",
186
		    h->style, "type", "text/css", "media", "all");
187
216
}
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
char *
240
html_make_id(const struct roff_node *n)
241
{
242
	const struct roff_node	*nch;
243
864
	char			*buf, *cp;
244
245
1728
	for (nch = n->child; nch != NULL; nch = nch->next)
246
432
		if (nch->type != ROFFT_TEXT)
247
			return NULL;
248
249
432
	buf = NULL;
250
432
	deroff(&buf, n);
251
432
	if (buf == NULL)
252
		return NULL;
253
254
	/* http://www.w3.org/TR/html5/dom.html#the-id-attribute */
255
256
7344
	for (cp = buf; *cp != '\0'; cp++)
257
3240
		if (*cp == ' ')
258
			*cp = '_';
259
260
432
	return buf;
261
432
}
262
263
int
264
html_strlen(const char *cp)
265
{
266
	size_t		 rsz;
267
	int		 skip, sz;
268
269
	/*
270
	 * Account for escaped sequences within string length
271
	 * calculations.  This follows the logic in term_strlen() as we
272
	 * must calculate the width of produced strings.
273
	 * Assume that characters are always width of "1".  This is
274
	 * hacky, but it gets the job done for approximation of widths.
275
	 */
276
277
	sz = 0;
278
	skip = 0;
279
	while (1) {
280
		rsz = strcspn(cp, "\\");
281
		if (rsz) {
282
			cp += rsz;
283
			if (skip) {
284
				skip = 0;
285
				rsz--;
286
			}
287
			sz += rsz;
288
		}
289
		if ('\0' == *cp)
290
			break;
291
		cp++;
292
		switch (mandoc_escape(&cp, NULL, NULL)) {
293
		case ESCAPE_ERROR:
294
			return sz;
295
		case ESCAPE_UNICODE:
296
		case ESCAPE_NUMBERED:
297
		case ESCAPE_SPECIAL:
298
		case ESCAPE_OVERSTRIKE:
299
			if (skip)
300
				skip = 0;
301
			else
302
				sz++;
303
			break;
304
		case ESCAPE_SKIPCHAR:
305
			skip = 1;
306
			break;
307
		default:
308
			break;
309
		}
310
	}
311
	return sz;
312
}
313
314
static int
315
print_escape(struct html *h, char c)
316
{
317
318


18459
	switch (c) {
319
	case '<':
320
288
		print_word(h, "&lt;");
321
288
		break;
322
	case '>':
323
288
		print_word(h, "&gt;");
324
288
		break;
325
	case '&':
326
9
		print_word(h, "&amp;");
327
9
		break;
328
	case '"':
329
27
		print_word(h, "&quot;");
330
27
		break;
331
	case ASCII_NBRSP:
332
		print_word(h, "&nbsp;");
333
		break;
334
	case ASCII_HYPH:
335
279
		print_byte(h, '-');
336
279
		break;
337
	case ASCII_BREAK:
338
		break;
339
	default:
340
7893
		return 0;
341
	}
342
891
	return 1;
343
8784
}
344
345
static int
346
print_encode(struct html *h, const char *p, const char *pend, int norecurse)
347
{
348
14220
	char		 numbuf[16];
349
	struct tag	*t;
350
14220
	const char	*seq;
351
	size_t		 sz;
352
14220
	int		 c, len, breakline, nospace;
353
	enum mandoc_esc	 esc;
354
	static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"',
355
		ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
356
357
14220
	if (pend == NULL)
358
14220
		pend = strchr(p, '\0');
359
360
	breakline = 0;
361
	nospace = 0;
362
363
51300
	while (p < pend) {
364

36477
		if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
365
			h->flags &= ~HTML_SKIPCHAR;
366
			p++;
367
			continue;
368
		}
369
370

562599
		for (sz = strcspn(p, rejs); sz-- && p < pend; p++)
371
163215
			print_byte(h, *p);
372
373

36477
		if (breakline &&
374
		    (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) {
375
			t = print_otag(h, TAG_DIV, "");
376
			print_text(h, "\\~");
377
			print_tagq(h, t);
378
			breakline = 0;
379
			while (p < pend && (*p == ' ' || *p == ASCII_NBRSP))
380
				p++;
381
			continue;
382
		}
383
384
36477
		if (p >= pend)
385
			break;
386
387
22860
		if (*p == ' ') {
388
14922
			print_endword(h);
389
14922
			p++;
390
14922
			continue;
391
		}
392
393
7938
		if (print_escape(h, *p++))
394
			continue;
395
396
7074
		esc = mandoc_escape(&p, &seq, &len);
397
7074
		if (ESCAPE_ERROR == esc)
398
			break;
399
400


7074
		switch (esc) {
401
		case ESCAPE_FONT:
402
		case ESCAPE_FONTPREV:
403
		case ESCAPE_FONTBOLD:
404
		case ESCAPE_FONTITALIC:
405
		case ESCAPE_FONTBI:
406
		case ESCAPE_FONTROMAN:
407
			if (0 == norecurse)
408
				print_metaf(h, esc);
409
			continue;
410
		case ESCAPE_SKIPCHAR:
411
			h->flags |= HTML_SKIPCHAR;
412
			continue;
413
		default:
414
			break;
415
		}
416
417
7074
		if (h->flags & HTML_SKIPCHAR) {
418
			h->flags &= ~HTML_SKIPCHAR;
419
			continue;
420
		}
421
422

34443
		switch (esc) {
423
		case ESCAPE_UNICODE:
424
			/* Skip past "u" header. */
425
3069
			c = mchars_num2uc(seq + 1, len - 1);
426
3069
			break;
427
		case ESCAPE_NUMBERED:
428
333
			c = mchars_num2char(seq, len);
429
333
			if (c < 0)
430
				continue;
431
			break;
432
		case ESCAPE_SPECIAL:
433
3672
			c = mchars_spec2cp(seq, len);
434
3672
			if (c <= 0)
435
				continue;
436
			break;
437
		case ESCAPE_BREAK:
438
			breakline = 1;
439
			continue;
440
		case ESCAPE_NOSPACE:
441
			if ('\0' == *p)
442
				nospace = 1;
443
			continue;
444
		case ESCAPE_OVERSTRIKE:
445
			if (len == 0)
446
				continue;
447
			c = seq[len - 1];
448
			break;
449
		default:
450
			continue;
451
		}
452
6957
		if ((c < 0x20 && c != 0x09) ||
453
6795
		    (c > 0x7E && c < 0xA0))
454
288
			c = 0xFFFD;
455
6957
		if (c > 0x7E) {
456
6111
			(void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c);
457
6111
			print_word(h, numbuf);
458
6957
		} else if (print_escape(h, c) == 0)
459
819
			print_byte(h, c);
460
	}
461
462
14220
	return nospace;
463
14220
}
464
465
static void
466
print_href(struct html *h, const char *name, const char *sec, int man)
467
{
468
	const char	*p, *pp;
469
470
	pp = man ? h->base_man : h->base_includes;
471
	while ((p = strchr(pp, '%')) != NULL) {
472
		print_encode(h, pp, p, 1);
473
		if (man && p[1] == 'S') {
474
			if (sec == NULL)
475
				print_byte(h, '1');
476
			else
477
				print_encode(h, sec, NULL, 1);
478
		} else if ((man && p[1] == 'N') ||
479
		    (man == 0 && p[1] == 'I'))
480
			print_encode(h, name, NULL, 1);
481
		else
482
			print_encode(h, p, p + 2, 1);
483
		pp = p + 2;
484
	}
485
	if (*pp != '\0')
486
		print_encode(h, pp, NULL, 1);
487
}
488
489
struct tag *
490
print_otag(struct html *h, enum htmltag tag, const char *fmt, ...)
491
{
492
16614
	va_list		 ap;
493
8307
	struct roffsu	 mysu, *su;
494
8307
	char		 numbuf[16];
495
	struct tag	*t;
496
	const char	*attr;
497
	char		*arg1, *arg2;
498
	double		 v;
499
	int		 i, have_style, tflags;
500
501
8307
	tflags = htmltags[tag].flags;
502
503
	/* Push this tag onto the stack of open scopes. */
504
505
8307
	if ((tflags & HTML_NOSTACK) == 0) {
506
8091
		t = mandoc_malloc(sizeof(struct tag));
507
8091
		t->tag = tag;
508
8091
		t->next = h->tag;
509
8091
		h->tag = t;
510
8091
	} else
511
		t = NULL;
512
513
8307
	if (tflags & HTML_NLBEFORE)
514
4113
		print_endline(h);
515
8307
	if (h->col == 0)
516
4410
		print_indent(h);
517
3897
	else if ((h->flags & HTML_NOSPACE) == 0) {
518
261
		if (h->flags & HTML_KEEP)
519
			print_word(h, "&#x00A0;");
520
		else {
521
261
			if (h->flags & HTML_PREKEEP)
522
				h->flags |= HTML_KEEP;
523
261
			print_endword(h);
524
		}
525
	}
526
527
16614
	if ( ! (h->flags & HTML_NONOSPACE))
528
8307
		h->flags &= ~HTML_NOSPACE;
529
	else
530
8307
		h->flags |= HTML_NOSPACE;
531
532
	/* Print out the tag name and attributes. */
533
534
8307
	print_byte(h, '<');
535
8307
	print_word(h, htmltags[tag].name);
536
537
8307
	va_start(ap, fmt);
538
539
	have_style = 0;
540
25920
	while (*fmt != '\0') {
541
4662
		if (*fmt == 's') {
542
			have_style = 1;
543
9
			fmt++;
544
9
			break;
545
		}
546
547
		/* Parse a non-style attribute and its arguments. */
548
549
13959
		arg1 = va_arg(ap, char *);
550

4653
		switch (*fmt++) {
551
		case 'c':
552
			attr = "class";
553
3060
			break;
554
		case 'h':
555
			attr = "href";
556
432
			break;
557
		case 'i':
558
			attr = "id";
559
432
			break;
560
		case '?':
561
			attr = arg1;
562
2187
			arg1 = va_arg(ap, char *);
563
729
			break;
564
		default:
565
			abort();
566
		}
567
		arg2 = NULL;
568
4653
		if (*fmt == 'M')
569
			arg2 = va_arg(ap, char *);
570
4653
		if (arg1 == NULL)
571
			continue;
572
573
		/* Print the non-style attributes. */
574
575
4653
		print_byte(h, ' ');
576
4653
		print_word(h, attr);
577
4653
		print_byte(h, '=');
578
4653
		print_byte(h, '"');
579

4653
		switch (*fmt) {
580
		case 'I':
581
			print_href(h, arg1, NULL, 0);
582
			fmt++;
583
			break;
584
		case 'M':
585
			print_href(h, arg1, arg2, 1);
586
			fmt++;
587
			break;
588
		case 'R':
589
432
			print_byte(h, '#');
590
432
			print_encode(h, arg1, NULL, 1);
591
432
			fmt++;
592
432
			break;
593
		case 'T':
594
738
			print_encode(h, arg1, NULL, 1);
595
738
			print_word(h, "\" title=\"");
596
738
			print_encode(h, arg1, NULL, 1);
597
738
			fmt++;
598
738
			break;
599
		default:
600
3483
			print_encode(h, arg1, NULL, 1);
601
3483
			break;
602
		}
603
4653
		print_byte(h, '"');
604
	}
605
606
	/* Print out styles. */
607
608
8325
	while (*fmt != '\0') {
609
		arg1 = NULL;
610
		su = NULL;
611
612
		/* First letter: input argument type. */
613
614

9
		switch (*fmt++) {
615
		case 'h':
616
27
			i = va_arg(ap, int);
617
			su = &mysu;
618
9
			SCALE_HS_INIT(su, i);
619
9
			break;
620
		case 's':
621
			arg1 = va_arg(ap, char *);
622
			break;
623
		case 'u':
624
			su = va_arg(ap, struct roffsu *);
625
			break;
626
		case 'w':
627
			if ((arg2 = va_arg(ap, char *)) != NULL) {
628
				su = &mysu;
629
				a2width(arg2, su);
630
			}
631
			if (*fmt == '*') {
632
				if (su != NULL && su->unit == SCALE_EN &&
633
				    su->scale > 5.9 && su->scale < 6.1)
634
					su = NULL;
635
				fmt++;
636
			}
637
			if (*fmt == '+') {
638
				if (su != NULL) {
639
					/* Make even bold text fit. */
640
					su->scale *= 1.2;
641
					/* Add padding. */
642
					su->scale += 3.0;
643
				}
644
				fmt++;
645
			}
646
			if (*fmt == '-') {
647
				if (su != NULL)
648
					su->scale *= -1.0;
649
				fmt++;
650
			}
651
			break;
652
		default:
653
			abort();
654
		}
655
656
		/* Second letter: style name. */
657
658

9
		switch (*fmt++) {
659
		case 'h':
660
			attr = "height";
661
			break;
662
		case 'i':
663
			attr = "text-indent";
664
			break;
665
		case 'l':
666
			attr = "margin-left";
667
9
			break;
668
		case 'w':
669
			attr = "width";
670
			break;
671
		case 'W':
672
			attr = "min-width";
673
			break;
674
		case '?':
675
			attr = arg1;
676
			arg1 = va_arg(ap, char *);
677
			break;
678
		default:
679
			abort();
680
		}
681
9
		if (su == NULL && arg1 == NULL)
682
			continue;
683
684
9
		if (have_style == 1)
685
9
			print_word(h, " style=\"");
686
		else
687
			print_byte(h, ' ');
688
9
		print_word(h, attr);
689
9
		print_byte(h, ':');
690
9
		print_byte(h, ' ');
691
9
		if (su != NULL) {
692
9
			v = su->scale;
693

9
			if (su->unit == SCALE_MM && (v /= 100.0) == 0.0)
694
				v = 1.0;
695
9
			else if (su->unit == SCALE_BU)
696
				v /= 24.0;
697
9
			(void)snprintf(numbuf, sizeof(numbuf), "%.2f", v);
698
9
			print_word(h, numbuf);
699
9
			print_word(h, roffscales[su->unit]);
700
9
		} else
701
			print_word(h, arg1);
702
9
		print_byte(h, ';');
703
		have_style = 2;
704
	}
705
8307
	if (have_style == 2)
706
9
		print_byte(h, '"');
707
708
8307
	va_end(ap);
709
710
	/* Accommodate for "well-formed" singleton escaping. */
711
712
8307
	if (HTML_AUTOCLOSE & htmltags[tag].flags)
713
216
		print_byte(h, '/');
714
715
8307
	print_byte(h, '>');
716
717
8307
	if (tflags & HTML_NLBEGIN)
718
2160
		print_endline(h);
719
	else
720
6147
		h->flags |= HTML_NOSPACE;
721
722
8307
	if (tflags & HTML_INDENT)
723
1440
		h->indent++;
724
8307
	if (tflags & HTML_NOINDENT)
725
72
		h->noindent++;
726
727
8307
	return t;
728
8307
}
729
730
static void
731
print_ctag(struct html *h, struct tag *tag)
732
{
733
	int	 tflags;
734
735
	/*
736
	 * Remember to close out and nullify the current
737
	 * meta-font and table, if applicable.
738
	 */
739
16182
	if (tag == h->metaf)
740
		h->metaf = NULL;
741
8091
	if (tag == h->tblt)
742
		h->tblt = NULL;
743
744
8091
	tflags = htmltags[tag->tag].flags;
745
746
8091
	if (tflags & HTML_INDENT)
747
1440
		h->indent--;
748
8091
	if (tflags & HTML_NOINDENT)
749
72
		h->noindent--;
750
8091
	if (tflags & HTML_NLEND)
751
1944
		print_endline(h);
752
8091
	print_indent(h);
753
8091
	print_byte(h, '<');
754
8091
	print_byte(h, '/');
755
8091
	print_word(h, htmltags[tag->tag].name);
756
8091
	print_byte(h, '>');
757
8091
	if (tflags & HTML_NLAFTER)
758
3897
		print_endline(h);
759
760
8091
	h->tag = tag->next;
761
8091
	free(tag);
762
8091
}
763
764
void
765
print_gen_decls(struct html *h)
766
{
767
432
	print_word(h, "<!DOCTYPE html>");
768
216
	print_endline(h);
769
216
}
770
771
void
772
print_text(struct html *h, const char *word)
773
{
774

22023
	if (h->col && (h->flags & HTML_NOSPACE) == 0) {
775
216
		if ( ! (HTML_KEEP & h->flags)) {
776
216
			if (HTML_PREKEEP & h->flags)
777
				h->flags |= HTML_KEEP;
778
216
			print_endword(h);
779
216
		} else
780
			print_word(h, "&#x00A0;");
781
	}
782
783
8829
	assert(NULL == h->metaf);
784

8829
	switch (h->metac) {
785
	case HTMLFONT_ITALIC:
786
		h->metaf = print_otag(h, TAG_I, "");
787
		break;
788
	case HTMLFONT_BOLD:
789
		h->metaf = print_otag(h, TAG_B, "");
790
		break;
791
	case HTMLFONT_BI:
792
		h->metaf = print_otag(h, TAG_B, "");
793
		print_otag(h, TAG_I, "");
794
		break;
795
	default:
796
8829
		print_indent(h);
797
8829
		break;
798
	}
799
800
8829
	assert(word);
801
8829
	if ( ! print_encode(h, word, NULL, 0)) {
802
8829
		if ( ! (h->flags & HTML_NONOSPACE))
803
6714
			h->flags &= ~HTML_NOSPACE;
804
8829
		h->flags &= ~HTML_NONEWLINE;
805
8829
	} else
806
		h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
807
808
8829
	if (h->metaf) {
809
		print_tagq(h, h->metaf);
810
		h->metaf = NULL;
811
	}
812
813
8829
	h->flags &= ~HTML_IGNDELIM;
814
8829
}
815
816
void
817
print_tagq(struct html *h, const struct tag *until)
818
{
819
	struct tag	*tag;
820
821
16038
	while ((tag = h->tag) != NULL) {
822
6192
		print_ctag(h, tag);
823

11952
		if (until && tag == until)
824
4599
			return;
825
	}
826
5031
}
827
828
void
829
print_stagq(struct html *h, const struct tag *suntil)
830
{
831
	struct tag	*tag;
832
833
22266
	while ((tag = h->tag) != NULL) {
834

16110
		if (suntil && tag == suntil)
835
6156
			return;
836
1899
		print_ctag(h, tag);
837
	}
838
6156
}
839
840
void
841
print_paragraph(struct html *h)
842
{
843
	struct tag	*t;
844
845
	t = print_otag(h, TAG_DIV, "c", "Pp");
846
	print_tagq(h, t);
847
}
848
849
850
/***********************************************************************
851
 * Low level output functions.
852
 * They implement line breaking using a short static buffer.
853
 ***********************************************************************/
854
855
/*
856
 * Buffer one HTML output byte.
857
 * If the buffer is full, flush and deactivate it and start a new line.
858
 * If the buffer is inactive, print directly.
859
 */
860
static void
861
print_byte(struct html *h, char c)
862
{
863
734364
	if ((h->flags & HTML_BUFFER) == 0) {
864
642132
		putchar(c);
865
321066
		h->col++;
866
321066
		return;
867
	}
868
869
46116
	if (h->col + h->bufcol < sizeof(h->buf)) {
870
45792
		h->buf[h->bufcol++] = c;
871
45792
		return;
872
	}
873
874
648
	putchar('\n');
875
324
	h->col = 0;
876
324
	print_indent(h);
877
648
	putchar(' ');
878
648
	putchar(' ');
879
324
	fwrite(h->buf, h->bufcol, 1, stdout);
880
648
	putchar(c);
881
324
	h->col = (h->indent + 1) * 2 + h->bufcol + 1;
882
324
	h->bufcol = 0;
883
324
	h->flags &= ~HTML_BUFFER;
884
367506
}
885
886
/*
887
 * If something was printed on the current output line, end it.
888
 * Not to be called right after print_indent().
889
 */
890
void
891
print_endline(struct html *h)
892
{
893
32436
	if (h->col == 0)
894
		return;
895
896
11115
	if (h->bufcol) {
897
3906
		putchar(' ');
898
1953
		fwrite(h->buf, h->bufcol, 1, stdout);
899
1953
		h->bufcol = 0;
900
1953
	}
901
22230
	putchar('\n');
902
11115
	h->col = 0;
903
11115
	h->flags |= HTML_NOSPACE;
904
11115
	h->flags &= ~HTML_BUFFER;
905
27333
}
906
907
/*
908
 * Flush the HTML output buffer.
909
 * If it is inactive, activate it.
910
 */
911
static void
912
print_endword(struct html *h)
913
{
914
30798
	if (h->noindent) {
915
8613
		print_byte(h, ' ');
916
8613
		return;
917
	}
918
919
6786
	if ((h->flags & HTML_BUFFER) == 0) {
920
2277
		h->col++;
921
2277
		h->flags |= HTML_BUFFER;
922
6786
	} else if (h->bufcol) {
923
9018
		putchar(' ');
924
4509
		fwrite(h->buf, h->bufcol, 1, stdout);
925
4509
		h->col += h->bufcol + 1;
926
4509
	}
927
6786
	h->bufcol = 0;
928
22185
}
929
930
/*
931
 * If at the beginning of a new output line,
932
 * perform indentation and mark the line as containing output.
933
 * Make sure to really produce some output right afterwards,
934
 * but do not use print_otag() for producing it.
935
 */
936
static void
937
print_indent(struct html *h)
938
{
939
	size_t	 i;
940
941
43308
	if (h->col)
942
10431
		return;
943
944
11223
	if (h->noindent == 0) {
945
7974
		h->col = h->indent * 2;
946
39672
		for (i = 0; i < h->col; i++)
947
23724
			putchar(' ');
948
	}
949
11223
	h->flags &= ~HTML_NOSPACE;
950
32877
}
951
952
/*
953
 * Print or buffer some characters
954
 * depending on the current HTML output buffer state.
955
 */
956
static void
957
print_word(struct html *h, const char *cp)
958
{
959
354438
	while (*cp != '\0')
960
134073
		print_byte(h, *cp++);
961
28764
}
962
963
/*
964
 * Calculate the scaling unit passed in a `-width' argument.  This uses
965
 * either a native scaling unit (e.g., 1i, 2m) or the string length of
966
 * the value.
967
 */
968
static void
969
a2width(const char *p, struct roffsu *su)
970
{
971
	const char	*end;
972
973
	end = a2roffsu(p, su, SCALE_MAX);
974
	if (end == NULL || *end != '\0') {
975
		su->unit = SCALE_EN;
976
		su->scale = html_strlen(p);
977
	} else if (su->scale < 0.0)
978
		su->scale = 0.0;
979
}