xref: /openbsd-src/usr.bin/mandoc/man_html.c (revision 7d464165e831b6257b4befbd30d2fbb433d300de)
1 /*	$Id: man_html.c,v 1.37 2011/04/21 22:59:54 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/types.h>
18 
19 #include <assert.h>
20 #include <ctype.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "mandoc.h"
26 #include "out.h"
27 #include "html.h"
28 #include "man.h"
29 #include "main.h"
30 
31 /* TODO: preserve ident widths. */
32 /* FIXME: have PD set the default vspace width. */
33 
34 #define	INDENT		  5
35 #define	HALFINDENT	  3
36 
37 #define	MAN_ARGS	  const struct man_meta *m, \
38 			  const struct man_node *n, \
39 			  struct mhtml *mh, \
40 			  struct html *h
41 
42 struct	mhtml {
43 	int		  fl;
44 #define	MANH_LITERAL	 (1 << 0) /* literal context */
45 };
46 
47 struct	htmlman {
48 	int		(*pre)(MAN_ARGS);
49 	int		(*post)(MAN_ARGS);
50 };
51 
52 static	void		  print_man(MAN_ARGS);
53 static	void		  print_man_head(MAN_ARGS);
54 static	void		  print_man_nodelist(MAN_ARGS);
55 static	void		  print_man_node(MAN_ARGS);
56 
57 static	int		  a2width(const struct man_node *,
58 				struct roffsu *);
59 
60 static	int		  man_alt_pre(MAN_ARGS);
61 static	int		  man_br_pre(MAN_ARGS);
62 static	int		  man_ign_pre(MAN_ARGS);
63 static	int		  man_in_pre(MAN_ARGS);
64 static	int		  man_literal_pre(MAN_ARGS);
65 static	void		  man_root_post(MAN_ARGS);
66 static	int		  man_root_pre(MAN_ARGS);
67 static	int		  man_B_pre(MAN_ARGS);
68 static	int		  man_HP_pre(MAN_ARGS);
69 static	int		  man_I_pre(MAN_ARGS);
70 static	int		  man_IP_pre(MAN_ARGS);
71 static	int		  man_PP_pre(MAN_ARGS);
72 static	int		  man_RS_pre(MAN_ARGS);
73 static	int		  man_SH_pre(MAN_ARGS);
74 static	int		  man_SM_pre(MAN_ARGS);
75 static	int		  man_SS_pre(MAN_ARGS);
76 
77 static	const struct htmlman mans[MAN_MAX] = {
78 	{ man_br_pre, NULL }, /* br */
79 	{ NULL, NULL }, /* TH */
80 	{ man_SH_pre, NULL }, /* SH */
81 	{ man_SS_pre, NULL }, /* SS */
82 	{ man_IP_pre, NULL }, /* TP */
83 	{ man_PP_pre, NULL }, /* LP */
84 	{ man_PP_pre, NULL }, /* PP */
85 	{ man_PP_pre, NULL }, /* P */
86 	{ man_IP_pre, NULL }, /* IP */
87 	{ man_HP_pre, NULL }, /* HP */
88 	{ man_SM_pre, NULL }, /* SM */
89 	{ man_SM_pre, NULL }, /* SB */
90 	{ man_alt_pre, NULL }, /* BI */
91 	{ man_alt_pre, NULL }, /* IB */
92 	{ man_alt_pre, NULL }, /* BR */
93 	{ man_alt_pre, NULL }, /* RB */
94 	{ NULL, NULL }, /* R */
95 	{ man_B_pre, NULL }, /* B */
96 	{ man_I_pre, NULL }, /* I */
97 	{ man_alt_pre, NULL }, /* IR */
98 	{ man_alt_pre, NULL }, /* RI */
99 	{ man_ign_pre, NULL }, /* na */
100 	{ man_br_pre, NULL }, /* sp */
101 	{ man_literal_pre, NULL }, /* nf */
102 	{ man_literal_pre, NULL }, /* fi */
103 	{ NULL, NULL }, /* RE */
104 	{ man_RS_pre, NULL }, /* RS */
105 	{ man_ign_pre, NULL }, /* DT */
106 	{ man_ign_pre, NULL }, /* UC */
107 	{ man_ign_pre, NULL }, /* PD */
108 	{ man_ign_pre, NULL }, /* AT */
109 	{ man_in_pre, NULL }, /* in */
110 	{ man_ign_pre, NULL }, /* ft */
111 };
112 
113 
114 void
115 html_man(void *arg, const struct man *m)
116 {
117 	struct html	*h;
118 	struct tag	*t;
119 	struct mhtml	 mh;
120 
121 	h = (struct html *)arg;
122 
123 	print_gen_decls(h);
124 
125 	memset(&mh, 0, sizeof(struct mhtml));
126 
127 	t = print_otag(h, TAG_HTML, 0, NULL);
128 	print_man(man_meta(m), man_node(m), &mh, h);
129 	print_tagq(h, t);
130 
131 	printf("\n");
132 }
133 
134 
135 static void
136 print_man(MAN_ARGS)
137 {
138 	struct tag	*t;
139 
140 	t = print_otag(h, TAG_HEAD, 0, NULL);
141 	print_man_head(m, n, mh, h);
142 	print_tagq(h, t);
143 
144 	t = print_otag(h, TAG_BODY, 0, NULL);
145 	print_man_nodelist(m, n, mh, h);
146 	print_tagq(h, t);
147 }
148 
149 
150 /* ARGSUSED */
151 static void
152 print_man_head(MAN_ARGS)
153 {
154 
155 	print_gen_head(h);
156 	bufinit(h);
157 	buffmt(h, "%s(%s)", m->title, m->msec);
158 
159 	print_otag(h, TAG_TITLE, 0, NULL);
160 	print_text(h, h->buf);
161 }
162 
163 
164 static void
165 print_man_nodelist(MAN_ARGS)
166 {
167 
168 	print_man_node(m, n, mh, h);
169 	if (n->next)
170 		print_man_nodelist(m, n->next, mh, h);
171 }
172 
173 
174 static void
175 print_man_node(MAN_ARGS)
176 {
177 	int		 child;
178 	struct tag	*t;
179 	struct htmlpair	 tag;
180 
181 	child = 1;
182 	t = h->tags.head;
183 
184 	bufinit(h);
185 
186 	switch (n->type) {
187 	case (MAN_ROOT):
188 		child = man_root_pre(m, n, mh, h);
189 		break;
190 	case (MAN_TEXT):
191 		if ('\0' == *n->string) {
192 			print_otag(h, TAG_P, 0, NULL);
193 			return;
194 		} else if (' ' == *n->string && MAN_LINE & n->flags)
195 			print_otag(h, TAG_BR, 0, NULL);
196 
197 		print_text(h, n->string);
198 
199 		if (MANH_LITERAL & mh->fl &&
200 				(NULL == n->next ||
201 				 n->next->line > n->line))
202 			print_otag(h, TAG_BR, 0, NULL);
203 		return;
204 	case (MAN_EQN):
205 		PAIR_CLASS_INIT(&tag, "eqn");
206 		print_otag(h, TAG_SPAN, 1, &tag);
207 		print_text(h, n->eqn->data);
208 		break;
209 	case (MAN_TBL):
210 		/*
211 		 * This will take care of initialising all of the table
212 		 * state data for the first table, then tearing it down
213 		 * for the last one.
214 		 */
215 		print_tbl(h, n->span);
216 		return;
217 	default:
218 		/*
219 		 * Close out scope of font prior to opening a macro
220 		 * scope.
221 		 */
222 		if (HTMLFONT_NONE != h->metac) {
223 			h->metal = h->metac;
224 			h->metac = HTMLFONT_NONE;
225 		}
226 
227 		/*
228 		 * Close out the current table, if it's open, and unset
229 		 * the "meta" table state.  This will be reopened on the
230 		 * next table element.
231 		 */
232 		if (h->tblt) {
233 			print_tblclose(h);
234 			t = h->tags.head;
235 		}
236 		if (mans[n->tok].pre)
237 			child = (*mans[n->tok].pre)(m, n, mh, h);
238 		break;
239 	}
240 
241 	if (child && n->child)
242 		print_man_nodelist(m, n->child, mh, h);
243 
244 	/* This will automatically close out any font scope. */
245 	print_stagq(h, t);
246 
247 	bufinit(h);
248 
249 	switch (n->type) {
250 	case (MAN_ROOT):
251 		man_root_post(m, n, mh, h);
252 		break;
253 	case (MAN_EQN):
254 		break;
255 	default:
256 		if (mans[n->tok].post)
257 			(*mans[n->tok].post)(m, n, mh, h);
258 		break;
259 	}
260 }
261 
262 
263 static int
264 a2width(const struct man_node *n, struct roffsu *su)
265 {
266 
267 	if (MAN_TEXT != n->type)
268 		return(0);
269 	if (a2roffsu(n->string, su, SCALE_BU))
270 		return(1);
271 
272 	return(0);
273 }
274 
275 
276 /* ARGSUSED */
277 static int
278 man_root_pre(MAN_ARGS)
279 {
280 	struct htmlpair	 tag[3];
281 	struct tag	*t, *tt;
282 	char		 b[BUFSIZ], title[BUFSIZ];
283 
284 	b[0] = 0;
285 	if (m->vol)
286 		(void)strlcat(b, m->vol, BUFSIZ);
287 
288 	snprintf(title, BUFSIZ - 1, "%s(%s)", m->title, m->msec);
289 
290 	PAIR_SUMMARY_INIT(&tag[0], "Document Header");
291 	PAIR_CLASS_INIT(&tag[1], "head");
292 	if (NULL == h->style) {
293 		PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
294 		t = print_otag(h, TAG_TABLE, 3, tag);
295 		PAIR_INIT(&tag[0], ATTR_WIDTH, "30%");
296 		print_otag(h, TAG_COL, 1, tag);
297 		print_otag(h, TAG_COL, 1, tag);
298 		print_otag(h, TAG_COL, 1, tag);
299 	} else
300 		t = print_otag(h, TAG_TABLE, 2, tag);
301 
302 	print_otag(h, TAG_TBODY, 0, NULL);
303 
304 	tt = print_otag(h, TAG_TR, 0, NULL);
305 
306 	PAIR_CLASS_INIT(&tag[0], "head-ltitle");
307 	print_otag(h, TAG_TD, 1, tag);
308 
309 	print_text(h, title);
310 	print_stagq(h, tt);
311 
312 	PAIR_CLASS_INIT(&tag[0], "head-vol");
313 	if (NULL == h->style) {
314 		PAIR_INIT(&tag[1], ATTR_ALIGN, "center");
315 		print_otag(h, TAG_TD, 2, tag);
316 	} else
317 		print_otag(h, TAG_TD, 1, tag);
318 
319 	print_text(h, b);
320 	print_stagq(h, tt);
321 
322 	PAIR_CLASS_INIT(&tag[0], "head-rtitle");
323 	if (NULL == h->style) {
324 		PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
325 		print_otag(h, TAG_TD, 2, tag);
326 	} else
327 		print_otag(h, TAG_TD, 1, tag);
328 
329 	print_text(h, title);
330 	print_tagq(h, t);
331 	return(1);
332 }
333 
334 
335 /* ARGSUSED */
336 static void
337 man_root_post(MAN_ARGS)
338 {
339 	struct htmlpair	 tag[3];
340 	struct tag	*t, *tt;
341 
342 	PAIR_SUMMARY_INIT(&tag[0], "Document Footer");
343 	PAIR_CLASS_INIT(&tag[1], "foot");
344 	if (NULL == h->style) {
345 		PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
346 		t = print_otag(h, TAG_TABLE, 3, tag);
347 		PAIR_INIT(&tag[0], ATTR_WIDTH, "50%");
348 		print_otag(h, TAG_COL, 1, tag);
349 		print_otag(h, TAG_COL, 1, tag);
350 	} else
351 		t = print_otag(h, TAG_TABLE, 2, tag);
352 
353 	tt = print_otag(h, TAG_TR, 0, NULL);
354 
355 	PAIR_CLASS_INIT(&tag[0], "foot-date");
356 	print_otag(h, TAG_TD, 1, tag);
357 
358 	print_text(h, m->date);
359 	print_stagq(h, tt);
360 
361 	PAIR_CLASS_INIT(&tag[0], "foot-os");
362 	if (NULL == h->style) {
363 		PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
364 		print_otag(h, TAG_TD, 2, tag);
365 	} else
366 		print_otag(h, TAG_TD, 1, tag);
367 
368 	if (m->source)
369 		print_text(h, m->source);
370 	print_tagq(h, t);
371 }
372 
373 
374 
375 /* ARGSUSED */
376 static int
377 man_br_pre(MAN_ARGS)
378 {
379 	struct roffsu	 su;
380 	struct htmlpair	 tag;
381 
382 	SCALE_VS_INIT(&su, 1);
383 
384 	if (MAN_sp == n->tok) {
385 		if (n->child)
386 			a2roffsu(n->child->string, &su, SCALE_VS);
387 	} else
388 		su.scale = 0;
389 
390 	bufcat_su(h, "height", &su);
391 	PAIR_STYLE_INIT(&tag, h);
392 	print_otag(h, TAG_DIV, 1, &tag);
393 
394 	/* So the div isn't empty: */
395 	print_text(h, "\\~");
396 
397 	return(0);
398 }
399 
400 
401 /* ARGSUSED */
402 static int
403 man_SH_pre(MAN_ARGS)
404 {
405 	struct htmlpair	 tag;
406 
407 	if (MAN_BLOCK == n->type) {
408 		PAIR_CLASS_INIT(&tag, "section");
409 		print_otag(h, TAG_DIV, 1, &tag);
410 		return(1);
411 	} else if (MAN_BODY == n->type)
412 		return(1);
413 
414 	print_otag(h, TAG_H1, 0, NULL);
415 	return(1);
416 }
417 
418 
419 /* ARGSUSED */
420 static int
421 man_alt_pre(MAN_ARGS)
422 {
423 	const struct man_node	*nn;
424 	int		 i;
425 	enum htmltag	 fp;
426 	struct tag	*t;
427 
428 	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
429 		t = NULL;
430 		switch (n->tok) {
431 		case (MAN_BI):
432 			fp = i % 2 ? TAG_I : TAG_B;
433 			break;
434 		case (MAN_IB):
435 			fp = i % 2 ? TAG_B : TAG_I;
436 			break;
437 		case (MAN_RI):
438 			fp = i % 2 ? TAG_I : TAG_MAX;
439 			break;
440 		case (MAN_IR):
441 			fp = i % 2 ? TAG_MAX : TAG_I;
442 			break;
443 		case (MAN_BR):
444 			fp = i % 2 ? TAG_MAX : TAG_B;
445 			break;
446 		case (MAN_RB):
447 			fp = i % 2 ? TAG_B : TAG_MAX;
448 			break;
449 		default:
450 			abort();
451 			/* NOTREACHED */
452 		}
453 
454 		if (i)
455 			h->flags |= HTML_NOSPACE;
456 
457 		if (TAG_MAX != fp)
458 			t = print_otag(h, fp, 0, NULL);
459 
460 		print_man_node(m, nn, mh, h);
461 
462 		if (t)
463 			print_tagq(h, t);
464 	}
465 
466 	return(0);
467 }
468 
469 
470 /* ARGSUSED */
471 static int
472 man_SM_pre(MAN_ARGS)
473 {
474 
475 	print_otag(h, TAG_SMALL, 0, NULL);
476 	if (MAN_SB == n->tok)
477 		print_otag(h, TAG_B, 0, NULL);
478 	return(1);
479 }
480 
481 
482 /* ARGSUSED */
483 static int
484 man_SS_pre(MAN_ARGS)
485 {
486 	struct htmlpair	 tag;
487 
488 	if (MAN_BLOCK == n->type) {
489 		PAIR_CLASS_INIT(&tag, "subsection");
490 		print_otag(h, TAG_DIV, 1, &tag);
491 		return(1);
492 	} else if (MAN_BODY == n->type)
493 		return(1);
494 
495 	print_otag(h, TAG_H2, 0, NULL);
496 	return(1);
497 }
498 
499 
500 /* ARGSUSED */
501 static int
502 man_PP_pre(MAN_ARGS)
503 {
504 
505 	if (MAN_HEAD == n->type)
506 		return(0);
507 	else if (MAN_BODY == n->type && n->prev)
508 		print_otag(h, TAG_P, 0, NULL);
509 
510 	return(1);
511 }
512 
513 
514 /* ARGSUSED */
515 static int
516 man_IP_pre(MAN_ARGS)
517 {
518 	struct roffsu		 su;
519 	struct htmlpair	 	 tag;
520 	const struct man_node	*nn;
521 
522 	/*
523 	 * This scattering of 1-BU margins and pads is to make sure that
524 	 * when text overruns its box, the subsequent text isn't flush
525 	 * up against it.  However, the rest of the right-hand box must
526 	 * also be adjusted in consideration of this 1-BU space.
527 	 */
528 
529 	if (MAN_BODY == n->type) {
530 		print_otag(h, TAG_TD, 0, NULL);
531 		return(1);
532 	}
533 
534 	nn = MAN_BLOCK == n->type ?
535 		n->head->child : n->parent->head->child;
536 
537 	SCALE_HS_INIT(&su, INDENT);
538 
539 	/* Width is the second token. */
540 
541 	if (MAN_IP == n->tok && NULL != nn)
542 		if (NULL != (nn = nn->next))
543 			a2width(nn, &su);
544 
545 	/* Width is the first token. */
546 
547 	if (MAN_TP == n->tok && NULL != nn) {
548 		/* Skip past non-text children. */
549 		while (nn && MAN_TEXT != nn->type)
550 			nn = nn->next;
551 		if (nn)
552 			a2width(nn, &su);
553 	}
554 
555 	if (MAN_BLOCK == n->type) {
556 		print_otag(h, TAG_P, 0, NULL);
557 		print_otag(h, TAG_TABLE, 0, NULL);
558 		bufcat_su(h, "width", &su);
559 		PAIR_STYLE_INIT(&tag, h);
560 		print_otag(h, TAG_COL, 1, &tag);
561 		print_otag(h, TAG_COL, 0, NULL);
562 		print_otag(h, TAG_TBODY, 0, NULL);
563 		print_otag(h, TAG_TR, 0, NULL);
564 		return(1);
565 	}
566 
567 	print_otag(h, TAG_TD, 0, NULL);
568 
569 	/* For IP, only print the first header element. */
570 
571 	if (MAN_IP == n->tok && n->child)
572 		print_man_node(m, n->child, mh, h);
573 
574 	/* For TP, only print next-line header elements. */
575 
576 	if (MAN_TP == n->tok)
577 		for (nn = n->child; nn; nn = nn->next)
578 			if (nn->line > n->line)
579 				print_man_node(m, nn, mh, h);
580 
581 	return(0);
582 }
583 
584 
585 /* ARGSUSED */
586 static int
587 man_HP_pre(MAN_ARGS)
588 {
589 	struct htmlpair	 tag;
590 	struct roffsu	 su;
591 	const struct man_node *np;
592 
593 	np = MAN_BLOCK == n->type ?
594 		n->head->child :
595 		n->parent->head->child;
596 
597 	if (NULL == np || ! a2width(np, &su))
598 		SCALE_HS_INIT(&su, INDENT);
599 
600 	if (MAN_HEAD == n->type) {
601 		print_otag(h, TAG_TD, 0, NULL);
602 		return(0);
603 	} else if (MAN_BLOCK == n->type) {
604 		print_otag(h, TAG_P, 0, NULL);
605 		print_otag(h, TAG_TABLE, 0, NULL);
606 		bufcat_su(h, "width", &su);
607 		PAIR_STYLE_INIT(&tag, h);
608 		print_otag(h, TAG_COL, 1, &tag);
609 		print_otag(h, TAG_COL, 0, NULL);
610 		print_otag(h, TAG_TBODY, 0, NULL);
611 		print_otag(h, TAG_TR, 0, NULL);
612 		return(1);
613 	}
614 
615 	su.scale = -su.scale;
616 	bufcat_su(h, "text-indent", &su);
617 	PAIR_STYLE_INIT(&tag, h);
618 	print_otag(h, TAG_TD, 1, &tag);
619 	return(1);
620 }
621 
622 
623 /* ARGSUSED */
624 static int
625 man_B_pre(MAN_ARGS)
626 {
627 
628 	print_otag(h, TAG_B, 0, NULL);
629 	return(1);
630 }
631 
632 
633 /* ARGSUSED */
634 static int
635 man_I_pre(MAN_ARGS)
636 {
637 
638 	print_otag(h, TAG_I, 0, NULL);
639 	return(1);
640 }
641 
642 
643 /* ARGSUSED */
644 static int
645 man_literal_pre(MAN_ARGS)
646 {
647 
648 	if (MAN_nf == n->tok) {
649 		print_otag(h, TAG_BR, 0, NULL);
650 		mh->fl |= MANH_LITERAL;
651 	} else
652 		mh->fl &= ~MANH_LITERAL;
653 
654 	return(0);
655 }
656 
657 
658 /* ARGSUSED */
659 static int
660 man_in_pre(MAN_ARGS)
661 {
662 
663 	print_otag(h, TAG_BR, 0, NULL);
664 	return(0);
665 }
666 
667 
668 /* ARGSUSED */
669 static int
670 man_ign_pre(MAN_ARGS)
671 {
672 
673 	return(0);
674 }
675 
676 
677 /* ARGSUSED */
678 static int
679 man_RS_pre(MAN_ARGS)
680 {
681 	struct htmlpair	 tag;
682 	struct roffsu	 su;
683 
684 	if (MAN_HEAD == n->type)
685 		return(0);
686 	else if (MAN_BODY == n->type)
687 		return(1);
688 
689 	SCALE_HS_INIT(&su, INDENT);
690 	if (n->head->child)
691 		a2width(n->head->child, &su);
692 
693 	bufcat_su(h, "margin-left", &su);
694 	PAIR_STYLE_INIT(&tag, h);
695 	print_otag(h, TAG_DIV, 1, &tag);
696 	return(1);
697 }
698