]> git.cameronkatri.com Git - mandoc.git/blob - man_html.c
Significantly clean up Sh, Ss, SH, and SS handling in -Thtml. Now a
[mandoc.git] / man_html.c
1 /* $Id: man_html.c,v 1.54 2010/12/17 00:18:29 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009, 2010 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 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include <sys/types.h>
22
23 #include <assert.h>
24 #include <ctype.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "mandoc.h"
30 #include "out.h"
31 #include "html.h"
32 #include "man.h"
33 #include "main.h"
34
35 /* TODO: preserve ident widths. */
36 /* FIXME: have PD set the default vspace width. */
37
38 #define INDENT 5
39 #define HALFINDENT 3
40
41 #define MAN_ARGS const struct man_meta *m, \
42 const struct man_node *n, \
43 struct mhtml *mh, \
44 struct html *h
45
46 struct mhtml {
47 int fl;
48 #define MANH_LITERAL (1 << 0) /* literal context */
49 };
50
51 struct htmlman {
52 int (*pre)(MAN_ARGS);
53 int (*post)(MAN_ARGS);
54 };
55
56 static void print_man(MAN_ARGS);
57 static void print_man_head(MAN_ARGS);
58 static void print_man_nodelist(MAN_ARGS);
59 static void print_man_node(MAN_ARGS);
60
61 static int a2width(const struct man_node *,
62 struct roffsu *);
63
64 static int man_alt_pre(MAN_ARGS);
65 static int man_br_pre(MAN_ARGS);
66 static int man_ign_pre(MAN_ARGS);
67 static int man_in_pre(MAN_ARGS);
68 static int man_literal_pre(MAN_ARGS);
69 static void man_root_post(MAN_ARGS);
70 static int man_root_pre(MAN_ARGS);
71 static int man_B_pre(MAN_ARGS);
72 static int man_HP_pre(MAN_ARGS);
73 static int man_I_pre(MAN_ARGS);
74 static int man_IP_pre(MAN_ARGS);
75 static int man_PP_pre(MAN_ARGS);
76 static int man_RS_pre(MAN_ARGS);
77 static int man_SB_pre(MAN_ARGS);
78 static int man_SH_pre(MAN_ARGS);
79 static int man_SM_pre(MAN_ARGS);
80 static int man_SS_pre(MAN_ARGS);
81
82 static const struct htmlman mans[MAN_MAX] = {
83 { man_br_pre, NULL }, /* br */
84 { NULL, NULL }, /* TH */
85 { man_SH_pre, NULL }, /* SH */
86 { man_SS_pre, NULL }, /* SS */
87 { man_IP_pre, NULL }, /* TP */
88 { man_PP_pre, NULL }, /* LP */
89 { man_PP_pre, NULL }, /* PP */
90 { man_PP_pre, NULL }, /* P */
91 { man_IP_pre, NULL }, /* IP */
92 { man_HP_pre, NULL }, /* HP */
93 { man_SM_pre, NULL }, /* SM */
94 { man_SB_pre, NULL }, /* SB */
95 { man_alt_pre, NULL }, /* BI */
96 { man_alt_pre, NULL }, /* IB */
97 { man_alt_pre, NULL }, /* BR */
98 { man_alt_pre, NULL }, /* RB */
99 { NULL, NULL }, /* R */
100 { man_B_pre, NULL }, /* B */
101 { man_I_pre, NULL }, /* I */
102 { man_alt_pre, NULL }, /* IR */
103 { man_alt_pre, NULL }, /* RI */
104 { NULL, NULL }, /* na */
105 { man_br_pre, NULL }, /* sp */
106 { man_literal_pre, NULL }, /* nf */
107 { man_literal_pre, NULL }, /* fi */
108 { NULL, NULL }, /* RE */
109 { man_RS_pre, NULL }, /* RS */
110 { man_ign_pre, NULL }, /* DT */
111 { man_ign_pre, NULL }, /* UC */
112 { man_ign_pre, NULL }, /* PD */
113 { man_ign_pre, NULL }, /* AT */
114 { man_in_pre, NULL }, /* in */
115 { man_ign_pre, NULL }, /* ft */
116 };
117
118
119 void
120 html_man(void *arg, const struct man *m)
121 {
122 struct html *h;
123 struct tag *t;
124 struct mhtml mh;
125
126 h = (struct html *)arg;
127
128 print_gen_decls(h);
129
130 memset(&mh, 0, sizeof(struct mhtml));
131
132 t = print_otag(h, TAG_HTML, 0, NULL);
133 print_man(man_meta(m), man_node(m), &mh, h);
134 print_tagq(h, t);
135
136 printf("\n");
137 }
138
139
140 static void
141 print_man(MAN_ARGS)
142 {
143 struct tag *t;
144
145 t = print_otag(h, TAG_HEAD, 0, NULL);
146 print_man_head(m, n, mh, h);
147 print_tagq(h, t);
148
149 t = print_otag(h, TAG_BODY, 0, NULL);
150 print_man_nodelist(m, n, mh, h);
151 print_tagq(h, t);
152 }
153
154
155 /* ARGSUSED */
156 static void
157 print_man_head(MAN_ARGS)
158 {
159
160 print_gen_head(h);
161 bufinit(h);
162 buffmt(h, "%s(%s)", m->title, m->msec);
163
164 print_otag(h, TAG_TITLE, 0, NULL);
165 print_text(h, h->buf);
166 }
167
168
169 static void
170 print_man_nodelist(MAN_ARGS)
171 {
172
173 print_man_node(m, n, mh, h);
174 if (n->next)
175 print_man_nodelist(m, n->next, mh, h);
176 }
177
178
179 static void
180 print_man_node(MAN_ARGS)
181 {
182 int child;
183 struct tag *t;
184
185 child = 1;
186 t = h->tags.head;
187
188 bufinit(h);
189
190 /*
191 * FIXME: embedded elements within next-line scopes (e.g., `br'
192 * within an empty `B') will cause formatting to be forgotten
193 * due to scope closing out.
194 */
195
196 switch (n->type) {
197 case (MAN_ROOT):
198 child = man_root_pre(m, n, mh, h);
199 break;
200 case (MAN_TEXT):
201 print_text(h, n->string);
202
203 if (MANH_LITERAL & mh->fl)
204 print_otag(h, TAG_BR, 0, NULL);
205
206 return;
207 default:
208 /*
209 * Close out scope of font prior to opening a macro
210 * scope. Assert that the metafont is on the top of the
211 * stack (it's never nested).
212 */
213 if (h->metaf) {
214 assert(h->metaf == t);
215 print_tagq(h, h->metaf);
216 assert(NULL == h->metaf);
217 t = h->tags.head;
218 }
219 if (mans[n->tok].pre)
220 child = (*mans[n->tok].pre)(m, n, mh, h);
221 break;
222 }
223
224 if (child && n->child)
225 print_man_nodelist(m, n->child, mh, h);
226
227 /* This will automatically close out any font scope. */
228 print_stagq(h, t);
229
230 bufinit(h);
231
232 switch (n->type) {
233 case (MAN_ROOT):
234 man_root_post(m, n, mh, h);
235 break;
236 case (MAN_TEXT):
237 break;
238 default:
239 if (mans[n->tok].post)
240 (*mans[n->tok].post)(m, n, mh, h);
241 break;
242 }
243 }
244
245
246 static int
247 a2width(const struct man_node *n, struct roffsu *su)
248 {
249
250 if (MAN_TEXT != n->type)
251 return(0);
252 if (a2roffsu(n->string, su, SCALE_BU))
253 return(1);
254
255 return(0);
256 }
257
258
259 /* ARGSUSED */
260 static int
261 man_root_pre(MAN_ARGS)
262 {
263 struct htmlpair tag[3];
264 struct tag *t, *tt;
265 char b[BUFSIZ], title[BUFSIZ];
266
267 b[0] = 0;
268 if (m->vol)
269 (void)strlcat(b, m->vol, BUFSIZ);
270
271 snprintf(title, BUFSIZ - 1, "%s(%s)", m->title, m->msec);
272
273 PAIR_CLASS_INIT(&tag[0], "header");
274 bufcat_style(h, "width", "100%");
275 PAIR_STYLE_INIT(&tag[1], h);
276 PAIR_SUMMARY_INIT(&tag[2], "header");
277
278 t = print_otag(h, TAG_TABLE, 3, tag);
279 tt = print_otag(h, TAG_TR, 0, NULL);
280
281 bufinit(h);
282 bufcat_style(h, "width", "10%");
283 PAIR_STYLE_INIT(&tag[0], h);
284 print_otag(h, TAG_TD, 1, tag);
285 print_text(h, title);
286 print_stagq(h, tt);
287
288 bufinit(h);
289 bufcat_style(h, "width", "80%");
290 bufcat_style(h, "white-space", "nowrap");
291 bufcat_style(h, "text-align", "center");
292 PAIR_STYLE_INIT(&tag[0], h);
293 print_otag(h, TAG_TD, 1, tag);
294 print_text(h, b);
295 print_stagq(h, tt);
296
297 bufinit(h);
298 bufcat_style(h, "width", "10%");
299 bufcat_style(h, "text-align", "right");
300 PAIR_STYLE_INIT(&tag[0], h);
301 print_otag(h, TAG_TD, 1, tag);
302 print_text(h, title);
303 print_tagq(h, t);
304 return(1);
305 }
306
307
308 /* ARGSUSED */
309 static void
310 man_root_post(MAN_ARGS)
311 {
312 struct htmlpair tag[3];
313 struct tag *t, *tt;
314 char b[DATESIZ];
315
316 if (m->rawdate)
317 strlcpy(b, m->rawdate, DATESIZ);
318 else
319 time2a(m->date, b, DATESIZ);
320
321 PAIR_CLASS_INIT(&tag[0], "footer");
322 bufcat_style(h, "width", "100%");
323 PAIR_STYLE_INIT(&tag[1], h);
324 PAIR_SUMMARY_INIT(&tag[2], "footer");
325
326 t = print_otag(h, TAG_TABLE, 3, tag);
327 tt = print_otag(h, TAG_TR, 0, NULL);
328
329 bufinit(h);
330 bufcat_style(h, "width", "50%");
331 PAIR_STYLE_INIT(&tag[0], h);
332 print_otag(h, TAG_TD, 1, tag);
333 print_text(h, b);
334 print_stagq(h, tt);
335
336 bufinit(h);
337 bufcat_style(h, "width", "50%");
338 bufcat_style(h, "text-align", "right");
339 PAIR_STYLE_INIT(&tag[0], h);
340 print_otag(h, TAG_TD, 1, tag);
341 if (m->source)
342 print_text(h, m->source);
343 print_tagq(h, t);
344 }
345
346
347
348 /* ARGSUSED */
349 static int
350 man_br_pre(MAN_ARGS)
351 {
352 struct roffsu su;
353 struct htmlpair tag;
354
355 SCALE_VS_INIT(&su, 1);
356
357 if (MAN_sp == n->tok) {
358 if (n->child)
359 a2roffsu(n->child->string, &su, SCALE_VS);
360 } else
361 su.scale = 0;
362
363 bufcat_su(h, "height", &su);
364 PAIR_STYLE_INIT(&tag, h);
365 print_otag(h, TAG_DIV, 1, &tag);
366
367 /* So the div isn't empty: */
368 print_text(h, "\\~");
369
370 return(0);
371 }
372
373
374 /* ARGSUSED */
375 static int
376 man_SH_pre(MAN_ARGS)
377 {
378 struct htmlpair tag;
379
380 if (MAN_BLOCK == n->type) {
381 PAIR_CLASS_INIT(&tag, "section");
382 print_otag(h, TAG_DIV, 1, &tag);
383 return(1);
384 } else if (MAN_BODY == n->type)
385 return(1);
386
387 print_otag(h, TAG_H1, 0, NULL);
388 return(1);
389 }
390
391
392 /* ARGSUSED */
393 static int
394 man_alt_pre(MAN_ARGS)
395 {
396 const struct man_node *nn;
397 struct tag *t;
398 int i;
399 enum htmlfont fp;
400
401 for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
402 switch (n->tok) {
403 case (MAN_BI):
404 fp = i % 2 ? HTMLFONT_ITALIC : HTMLFONT_BOLD;
405 break;
406 case (MAN_IB):
407 fp = i % 2 ? HTMLFONT_BOLD : HTMLFONT_ITALIC;
408 break;
409 case (MAN_RI):
410 fp = i % 2 ? HTMLFONT_ITALIC : HTMLFONT_NONE;
411 break;
412 case (MAN_IR):
413 fp = i % 2 ? HTMLFONT_NONE : HTMLFONT_ITALIC;
414 break;
415 case (MAN_BR):
416 fp = i % 2 ? HTMLFONT_NONE : HTMLFONT_BOLD;
417 break;
418 case (MAN_RB):
419 fp = i % 2 ? HTMLFONT_BOLD : HTMLFONT_NONE;
420 break;
421 default:
422 abort();
423 /* NOTREACHED */
424 }
425
426 if (i)
427 h->flags |= HTML_NOSPACE;
428
429 /*
430 * Open and close the scope with each argument, so that
431 * internal \f escapes, which are common, are also
432 * closed out with the scope.
433 */
434 t = print_ofont(h, fp);
435 print_man_node(m, nn, mh, h);
436 print_tagq(h, t);
437 }
438
439 return(0);
440 }
441
442
443 /* ARGSUSED */
444 static int
445 man_SB_pre(MAN_ARGS)
446 {
447 struct htmlpair tag;
448
449 /* FIXME: print_ofont(). */
450 PAIR_CLASS_INIT(&tag, "small bold");
451 print_otag(h, TAG_SPAN, 1, &tag);
452 return(1);
453 }
454
455
456 /* ARGSUSED */
457 static int
458 man_SM_pre(MAN_ARGS)
459 {
460 struct htmlpair tag;
461
462 PAIR_CLASS_INIT(&tag, "small");
463 print_otag(h, TAG_SPAN, 1, &tag);
464 return(1);
465 }
466
467
468 /* ARGSUSED */
469 static int
470 man_SS_pre(MAN_ARGS)
471 {
472 struct htmlpair tag;
473
474 if (MAN_BLOCK == n->type) {
475 PAIR_CLASS_INIT(&tag, "subsection");
476 print_otag(h, TAG_DIV, 1, &tag);
477 return(1);
478 } else if (MAN_BODY == n->type)
479 return(1);
480
481 print_otag(h, TAG_H2, 0, NULL);
482 return(1);
483 }
484
485
486 /* ARGSUSED */
487 static int
488 man_PP_pre(MAN_ARGS)
489 {
490 struct htmlpair tag;
491 struct roffsu su;
492 int i;
493
494 if (MAN_BODY == n->type)
495 return(1);
496 if (MAN_HEAD == n->type)
497 return(0);
498
499 i = 0;
500
501 if (MAN_ROOT == n->parent->type) {
502 SCALE_HS_INIT(&su, INDENT);
503 bufcat_su(h, "margin-left", &su);
504 i = 1;
505 }
506 if (n->prev) {
507 SCALE_VS_INIT(&su, 1);
508 bufcat_su(h, "margin-top", &su);
509 i = 1;
510 }
511
512 PAIR_STYLE_INIT(&tag, h);
513 print_otag(h, TAG_DIV, i, &tag);
514
515 return(1);
516 }
517
518
519 /* ARGSUSED */
520 static int
521 man_IP_pre(MAN_ARGS)
522 {
523 struct roffsu su;
524 struct htmlpair tag;
525 const struct man_node *nn;
526 int width;
527
528 /*
529 * This scattering of 1-BU margins and pads is to make sure that
530 * when text overruns its box, the subsequent text isn't flush
531 * up against it. However, the rest of the right-hand box must
532 * also be adjusted in consideration of this 1-BU space.
533 */
534
535 if (MAN_BODY == n->type) {
536 SCALE_HS_INIT(&su, INDENT);
537 bufcat_su(h, "margin-left", &su);
538 PAIR_STYLE_INIT(&tag, h);
539 print_otag(h, TAG_DIV, 1, &tag);
540 return(1);
541 }
542
543 nn = MAN_BLOCK == n->type ?
544 n->head->child : n->parent->head->child;
545
546 SCALE_HS_INIT(&su, INDENT);
547 width = 0;
548
549 /* Width is the last token. */
550
551 if (MAN_IP == n->tok && NULL != nn)
552 if (NULL != (nn = nn->next)) {
553 for ( ; nn->next; nn = nn->next)
554 /* Do nothing. */ ;
555 width = a2width(nn, &su);
556 }
557
558 /* Width is the first token. */
559
560 if (MAN_TP == n->tok && NULL != nn) {
561 /* Skip past non-text children. */
562 while (nn && MAN_TEXT != nn->type)
563 nn = nn->next;
564 if (nn)
565 width = a2width(nn, &su);
566 }
567
568 if (MAN_BLOCK == n->type) {
569 bufcat_su(h, "margin-left", &su);
570 SCALE_VS_INIT(&su, 1);
571 bufcat_su(h, "margin-top", &su);
572 bufcat_style(h, "clear", "both");
573 PAIR_STYLE_INIT(&tag, h);
574 print_otag(h, TAG_DIV, 1, &tag);
575 return(1);
576 }
577
578 bufcat_su(h, "min-width", &su);
579 SCALE_INVERT(&su);
580 bufcat_su(h, "margin-left", &su);
581 SCALE_HS_INIT(&su, 1);
582 bufcat_su(h, "margin-right", &su);
583 bufcat_style(h, "clear", "left");
584
585 if (n->next && n->next->child)
586 bufcat_style(h, "float", "left");
587
588 PAIR_STYLE_INIT(&tag, h);
589 print_otag(h, TAG_DIV, 1, &tag);
590
591 /*
592 * Without a length string, we can print all of our children.
593 */
594
595 if ( ! width)
596 return(1);
597
598 /*
599 * When a length has been specified, we need to carefully print
600 * our child context: IP gets all children printed but the last
601 * (the width), while TP gets all children printed but the first
602 * (the width).
603 */
604
605 if (MAN_IP == n->tok)
606 for (nn = n->child; nn->next; nn = nn->next)
607 print_man_node(m, nn, mh, h);
608 if (MAN_TP == n->tok)
609 for (nn = n->child->next; nn; nn = nn->next)
610 print_man_node(m, nn, mh, h);
611
612 return(0);
613 }
614
615
616 /* ARGSUSED */
617 static int
618 man_HP_pre(MAN_ARGS)
619 {
620 const struct man_node *nn;
621 struct htmlpair tag;
622 struct roffsu su;
623
624 if (MAN_HEAD == n->type)
625 return(0);
626
627 nn = MAN_BLOCK == n->type ?
628 n->head->child : n->parent->head->child;
629
630 SCALE_HS_INIT(&su, INDENT);
631
632 if (NULL != nn)
633 (void)a2width(nn, &su);
634
635 if (MAN_BLOCK == n->type) {
636 bufcat_su(h, "margin-left", &su);
637 SCALE_VS_INIT(&su, 1);
638 bufcat_su(h, "margin-top", &su);
639 bufcat_style(h, "clear", "both");
640 PAIR_STYLE_INIT(&tag, h);
641 print_otag(h, TAG_DIV, 1, &tag);
642 return(1);
643 }
644
645 bufcat_su(h, "margin-left", &su);
646 SCALE_INVERT(&su);
647 bufcat_su(h, "text-indent", &su);
648
649 PAIR_STYLE_INIT(&tag, h);
650 print_otag(h, TAG_DIV, 1, &tag);
651 return(1);
652 }
653
654
655 /* ARGSUSED */
656 static int
657 man_B_pre(MAN_ARGS)
658 {
659
660 print_ofont(h, HTMLFONT_BOLD);
661 return(1);
662 }
663
664
665 /* ARGSUSED */
666 static int
667 man_I_pre(MAN_ARGS)
668 {
669
670 print_ofont(h, HTMLFONT_ITALIC);
671 return(1);
672 }
673
674
675 /* ARGSUSED */
676 static int
677 man_literal_pre(MAN_ARGS)
678 {
679
680 if (MAN_nf == n->tok) {
681 print_otag(h, TAG_BR, 0, NULL);
682 mh->fl |= MANH_LITERAL;
683 } else
684 mh->fl &= ~MANH_LITERAL;
685
686 return(1);
687 }
688
689
690 /* ARGSUSED */
691 static int
692 man_in_pre(MAN_ARGS)
693 {
694
695 print_otag(h, TAG_BR, 0, NULL);
696 return(0);
697 }
698
699
700 /* ARGSUSED */
701 static int
702 man_ign_pre(MAN_ARGS)
703 {
704
705 return(0);
706 }
707
708
709 /* ARGSUSED */
710 static int
711 man_RS_pre(MAN_ARGS)
712 {
713 struct htmlpair tag;
714 struct roffsu su;
715
716 if (MAN_HEAD == n->type)
717 return(0);
718 else if (MAN_BODY == n->type)
719 return(1);
720
721 SCALE_HS_INIT(&su, INDENT);
722 bufcat_su(h, "margin-left", &su);
723
724 if (n->head->child) {
725 SCALE_VS_INIT(&su, 1);
726 a2width(n->head->child, &su);
727 bufcat_su(h, "margin-top", &su);
728 }
729
730 PAIR_STYLE_INIT(&tag, h);
731 print_otag(h, TAG_DIV, 1, &tag);
732 return(1);
733 }