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