]> git.cameronkatri.com Git - mandoc.git/blob - man_html.c
Start with baby steps towards responsive design:
[mandoc.git] / man_html.c
1 /* $Id: man_html.c,v 1.150 2018/05/25 20:23:51 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2013,2014,2015,2017,2018 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 <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "mandoc_aux.h"
29 #include "mandoc.h"
30 #include "roff.h"
31 #include "man.h"
32 #include "out.h"
33 #include "html.h"
34 #include "main.h"
35
36 /* FIXME: have PD set the default vspace width. */
37
38 #define INDENT 5
39
40 #define MAN_ARGS const struct roff_meta *man, \
41 const struct roff_node *n, \
42 struct html *h
43
44 struct htmlman {
45 int (*pre)(MAN_ARGS);
46 int (*post)(MAN_ARGS);
47 };
48
49 static void print_bvspace(struct html *,
50 const struct roff_node *);
51 static void print_man_head(const struct roff_meta *,
52 struct html *);
53 static void print_man_nodelist(MAN_ARGS);
54 static void print_man_node(MAN_ARGS);
55 static int fillmode(struct html *, int);
56 static int a2width(const struct roff_node *,
57 struct roffsu *);
58 static int man_B_pre(MAN_ARGS);
59 static int man_HP_pre(MAN_ARGS);
60 static int man_IP_pre(MAN_ARGS);
61 static int man_I_pre(MAN_ARGS);
62 static int man_OP_pre(MAN_ARGS);
63 static int man_PP_pre(MAN_ARGS);
64 static int man_RS_pre(MAN_ARGS);
65 static int man_SH_pre(MAN_ARGS);
66 static int man_SM_pre(MAN_ARGS);
67 static int man_SS_pre(MAN_ARGS);
68 static int man_UR_pre(MAN_ARGS);
69 static int man_alt_pre(MAN_ARGS);
70 static int man_ign_pre(MAN_ARGS);
71 static int man_in_pre(MAN_ARGS);
72 static void man_root_post(const struct roff_meta *,
73 struct html *);
74 static void man_root_pre(const struct roff_meta *,
75 struct html *);
76
77 static const struct htmlman __mans[MAN_MAX - MAN_TH] = {
78 { NULL, NULL }, /* TH */
79 { man_SH_pre, NULL }, /* SH */
80 { man_SS_pre, NULL }, /* SS */
81 { man_IP_pre, NULL }, /* TP */
82 { man_PP_pre, NULL }, /* LP */
83 { man_PP_pre, NULL }, /* PP */
84 { man_PP_pre, NULL }, /* P */
85 { man_IP_pre, NULL }, /* IP */
86 { man_HP_pre, NULL }, /* HP */
87 { man_SM_pre, NULL }, /* SM */
88 { man_SM_pre, NULL }, /* SB */
89 { man_alt_pre, NULL }, /* BI */
90 { man_alt_pre, NULL }, /* IB */
91 { man_alt_pre, NULL }, /* BR */
92 { man_alt_pre, NULL }, /* RB */
93 { NULL, NULL }, /* R */
94 { man_B_pre, NULL }, /* B */
95 { man_I_pre, NULL }, /* I */
96 { man_alt_pre, NULL }, /* IR */
97 { man_alt_pre, NULL }, /* RI */
98 { NULL, NULL }, /* nf */
99 { NULL, NULL }, /* fi */
100 { NULL, NULL }, /* RE */
101 { man_RS_pre, NULL }, /* RS */
102 { man_ign_pre, NULL }, /* DT */
103 { man_ign_pre, NULL }, /* UC */
104 { man_ign_pre, NULL }, /* PD */
105 { man_ign_pre, NULL }, /* AT */
106 { man_in_pre, NULL }, /* in */
107 { man_OP_pre, NULL }, /* OP */
108 { NULL, NULL }, /* EX */
109 { NULL, NULL }, /* EE */
110 { man_UR_pre, NULL }, /* UR */
111 { NULL, NULL }, /* UE */
112 { man_UR_pre, NULL }, /* MT */
113 { NULL, NULL }, /* ME */
114 };
115 static const struct htmlman *const mans = __mans - MAN_TH;
116
117
118 /*
119 * Printing leading vertical space before a block.
120 * This is used for the paragraph macros.
121 * The rules are pretty simple, since there's very little nesting going
122 * on here. Basically, if we're the first within another block (SS/SH),
123 * then don't emit vertical space. If we are (RS), then do. If not the
124 * first, print it.
125 */
126 static void
127 print_bvspace(struct html *h, const struct roff_node *n)
128 {
129
130 if (n->body && n->body->child)
131 if (n->body->child->type == ROFFT_TBL)
132 return;
133
134 if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
135 if (NULL == n->prev)
136 return;
137
138 print_paragraph(h);
139 }
140
141 void
142 html_man(void *arg, const struct roff_man *man)
143 {
144 struct html *h;
145 struct roff_node *n;
146 struct tag *t;
147
148 h = (struct html *)arg;
149 n = man->first->child;
150
151 if ((h->oflags & HTML_FRAGMENT) == 0) {
152 print_gen_decls(h);
153 print_otag(h, TAG_HTML, "");
154 if (n->type == ROFFT_COMMENT)
155 print_gen_comment(h, n);
156 t = print_otag(h, TAG_HEAD, "");
157 print_man_head(&man->meta, h);
158 print_tagq(h, t);
159 print_otag(h, TAG_BODY, "");
160 }
161
162 man_root_pre(&man->meta, h);
163 t = print_otag(h, TAG_DIV, "c", "manual-text");
164 print_man_nodelist(&man->meta, n, h);
165 print_tagq(h, t);
166 man_root_post(&man->meta, h);
167 print_tagq(h, NULL);
168 }
169
170 static void
171 print_man_head(const struct roff_meta *man, struct html *h)
172 {
173 char *cp;
174
175 print_gen_head(h);
176 mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec);
177 print_otag(h, TAG_TITLE, "");
178 print_text(h, cp);
179 free(cp);
180 }
181
182 static void
183 print_man_nodelist(MAN_ARGS)
184 {
185
186 while (n != NULL) {
187 print_man_node(man, n, h);
188 n = n->next;
189 }
190 }
191
192 static void
193 print_man_node(MAN_ARGS)
194 {
195 static int want_fillmode = MAN_fi;
196 static int save_fillmode;
197
198 struct tag *t;
199 int child;
200
201 /*
202 * Handle fill mode switch requests up front,
203 * they would just cause trouble in the subsequent code.
204 */
205
206 switch (n->tok) {
207 case MAN_nf:
208 case MAN_EX:
209 want_fillmode = MAN_nf;
210 return;
211 case MAN_fi:
212 case MAN_EE:
213 want_fillmode = MAN_fi;
214 if (fillmode(h, 0) == MAN_fi)
215 print_otag(h, TAG_BR, "");
216 return;
217 default:
218 break;
219 }
220
221 /* Set up fill mode for the upcoming node. */
222
223 switch (n->type) {
224 case ROFFT_BLOCK:
225 save_fillmode = 0;
226 /* Some block macros suspend or cancel .nf. */
227 switch (n->tok) {
228 case MAN_TP: /* Tagged paragraphs */
229 case MAN_IP: /* temporarily disable .nf */
230 case MAN_HP: /* for the head. */
231 save_fillmode = want_fillmode;
232 /* FALLTHROUGH */
233 case MAN_SH: /* Section headers */
234 case MAN_SS: /* permanently cancel .nf. */
235 want_fillmode = MAN_fi;
236 /* FALLTHROUGH */
237 case MAN_PP: /* These have no head. */
238 case MAN_LP: /* They will simply */
239 case MAN_P: /* reopen .nf in the body. */
240 case MAN_RS:
241 case MAN_UR:
242 case MAN_MT:
243 fillmode(h, MAN_fi);
244 break;
245 default:
246 break;
247 }
248 break;
249 case ROFFT_TBL:
250 fillmode(h, MAN_fi);
251 break;
252 case ROFFT_ELEM:
253 /*
254 * Some in-line macros produce tags and/or text
255 * in the handler, so they require fill mode to be
256 * configured up front just like for text nodes.
257 * For the others, keep the traditional approach
258 * of doing the same, for now.
259 */
260 fillmode(h, want_fillmode);
261 break;
262 case ROFFT_TEXT:
263 if (fillmode(h, want_fillmode) == MAN_fi &&
264 want_fillmode == MAN_fi &&
265 n->flags & NODE_LINE && *n->string == ' ' &&
266 (h->flags & HTML_NONEWLINE) == 0)
267 print_otag(h, TAG_BR, "");
268 if (*n->string != '\0')
269 break;
270 print_paragraph(h);
271 return;
272 case ROFFT_COMMENT:
273 return;
274 default:
275 break;
276 }
277
278 /* Produce output for this node. */
279
280 child = 1;
281 switch (n->type) {
282 case ROFFT_TEXT:
283 t = h->tag;
284 print_text(h, n->string);
285 break;
286 case ROFFT_EQN:
287 t = h->tag;
288 print_eqn(h, n->eqn);
289 break;
290 case ROFFT_TBL:
291 /*
292 * This will take care of initialising all of the table
293 * state data for the first table, then tearing it down
294 * for the last one.
295 */
296 print_tbl(h, n->span);
297 return;
298 default:
299 /*
300 * Close out scope of font prior to opening a macro
301 * scope.
302 */
303 if (HTMLFONT_NONE != h->metac) {
304 h->metal = h->metac;
305 h->metac = HTMLFONT_NONE;
306 }
307
308 /*
309 * Close out the current table, if it's open, and unset
310 * the "meta" table state. This will be reopened on the
311 * next table element.
312 */
313 if (h->tblt)
314 print_tblclose(h);
315
316 t = h->tag;
317 if (n->tok < ROFF_MAX) {
318 roff_html_pre(h, n);
319 child = 0;
320 break;
321 }
322
323 assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
324 if (mans[n->tok].pre)
325 child = (*mans[n->tok].pre)(man, n, h);
326
327 /* Some block macros resume .nf in the body. */
328 if (save_fillmode && n->type == ROFFT_BODY)
329 want_fillmode = save_fillmode;
330
331 break;
332 }
333
334 if (child && n->child)
335 print_man_nodelist(man, n->child, h);
336
337 /* This will automatically close out any font scope. */
338 print_stagq(h, t);
339
340 if (fillmode(h, 0) == MAN_nf &&
341 n->next != NULL && n->next->flags & NODE_LINE)
342 print_endline(h);
343 }
344
345 /*
346 * MAN_nf switches to no-fill mode, MAN_fi to fill mode.
347 * Other arguments do not switch.
348 * The old mode is returned.
349 */
350 static int
351 fillmode(struct html *h, int want)
352 {
353 struct tag *pre;
354 int had;
355
356 for (pre = h->tag; pre != NULL; pre = pre->next)
357 if (pre->tag == TAG_PRE)
358 break;
359
360 had = pre == NULL ? MAN_fi : MAN_nf;
361
362 if (want && want != had) {
363 if (want == MAN_nf)
364 print_otag(h, TAG_PRE, "");
365 else
366 print_tagq(h, pre);
367 }
368 return had;
369 }
370
371 static int
372 a2width(const struct roff_node *n, struct roffsu *su)
373 {
374 if (n->type != ROFFT_TEXT)
375 return 0;
376 return a2roffsu(n->string, su, SCALE_EN) != NULL;
377 }
378
379 static void
380 man_root_pre(const struct roff_meta *man, struct html *h)
381 {
382 struct tag *t, *tt;
383 char *title;
384
385 assert(man->title);
386 assert(man->msec);
387 mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
388
389 t = print_otag(h, TAG_TABLE, "c", "head");
390 tt = print_otag(h, TAG_TR, "");
391
392 print_otag(h, TAG_TD, "c", "head-ltitle");
393 print_text(h, title);
394 print_stagq(h, tt);
395
396 print_otag(h, TAG_TD, "c", "head-vol");
397 if (NULL != man->vol)
398 print_text(h, man->vol);
399 print_stagq(h, tt);
400
401 print_otag(h, TAG_TD, "c", "head-rtitle");
402 print_text(h, title);
403 print_tagq(h, t);
404 free(title);
405 }
406
407 static void
408 man_root_post(const struct roff_meta *man, struct html *h)
409 {
410 struct tag *t, *tt;
411
412 t = print_otag(h, TAG_TABLE, "c", "foot");
413 tt = print_otag(h, TAG_TR, "");
414
415 print_otag(h, TAG_TD, "c", "foot-date");
416 print_text(h, man->date);
417 print_stagq(h, tt);
418
419 print_otag(h, TAG_TD, "c", "foot-os");
420 if (man->os)
421 print_text(h, man->os);
422 print_tagq(h, t);
423 }
424
425 static int
426 man_SH_pre(MAN_ARGS)
427 {
428 char *id;
429
430 if (n->type == ROFFT_HEAD) {
431 id = html_make_id(n, 1);
432 print_otag(h, TAG_H1, "cTi", "Sh", id);
433 if (id != NULL)
434 print_otag(h, TAG_A, "chR", "permalink", id);
435 }
436 return 1;
437 }
438
439 static int
440 man_alt_pre(MAN_ARGS)
441 {
442 const struct roff_node *nn;
443 int i;
444 enum htmltag fp;
445 struct tag *t;
446
447 for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
448 switch (n->tok) {
449 case MAN_BI:
450 fp = i % 2 ? TAG_I : TAG_B;
451 break;
452 case MAN_IB:
453 fp = i % 2 ? TAG_B : TAG_I;
454 break;
455 case MAN_RI:
456 fp = i % 2 ? TAG_I : TAG_MAX;
457 break;
458 case MAN_IR:
459 fp = i % 2 ? TAG_MAX : TAG_I;
460 break;
461 case MAN_BR:
462 fp = i % 2 ? TAG_MAX : TAG_B;
463 break;
464 case MAN_RB:
465 fp = i % 2 ? TAG_B : TAG_MAX;
466 break;
467 default:
468 abort();
469 }
470
471 if (i)
472 h->flags |= HTML_NOSPACE;
473
474 if (fp != TAG_MAX)
475 t = print_otag(h, fp, "");
476
477 print_text(h, nn->string);
478
479 if (fp != TAG_MAX)
480 print_tagq(h, t);
481 }
482 return 0;
483 }
484
485 static int
486 man_SM_pre(MAN_ARGS)
487 {
488 print_otag(h, TAG_SMALL, "");
489 if (MAN_SB == n->tok)
490 print_otag(h, TAG_B, "");
491 return 1;
492 }
493
494 static int
495 man_SS_pre(MAN_ARGS)
496 {
497 char *id;
498
499 if (n->type == ROFFT_HEAD) {
500 id = html_make_id(n, 1);
501 print_otag(h, TAG_H2, "cTi", "Ss", id);
502 if (id != NULL)
503 print_otag(h, TAG_A, "chR", "permalink", id);
504 }
505 return 1;
506 }
507
508 static int
509 man_PP_pre(MAN_ARGS)
510 {
511
512 if (n->type == ROFFT_HEAD)
513 return 0;
514 else if (n->type == ROFFT_BLOCK)
515 print_bvspace(h, n);
516
517 return 1;
518 }
519
520 static int
521 man_IP_pre(MAN_ARGS)
522 {
523 const struct roff_node *nn;
524
525 if (n->type == ROFFT_BODY) {
526 print_otag(h, TAG_DD, "");
527 return 1;
528 } else if (n->type != ROFFT_HEAD) {
529 print_otag(h, TAG_DL, "c", "Bl-tag");
530 return 1;
531 }
532
533 /* FIXME: width specification. */
534
535 print_otag(h, TAG_DT, "");
536
537 /* For IP, only print the first header element. */
538
539 if (MAN_IP == n->tok && n->child)
540 print_man_node(man, n->child, h);
541
542 /* For TP, only print next-line header elements. */
543
544 if (MAN_TP == n->tok) {
545 nn = n->child;
546 while (NULL != nn && 0 == (NODE_LINE & nn->flags))
547 nn = nn->next;
548 while (NULL != nn) {
549 print_man_node(man, nn, h);
550 nn = nn->next;
551 }
552 }
553
554 return 0;
555 }
556
557 static int
558 man_HP_pre(MAN_ARGS)
559 {
560 struct roffsu sum, sui;
561 const struct roff_node *np;
562
563 if (n->type == ROFFT_HEAD)
564 return 0;
565 else if (n->type != ROFFT_BLOCK)
566 return 1;
567
568 np = n->head->child;
569
570 if (np == NULL || !a2width(np, &sum))
571 SCALE_HS_INIT(&sum, INDENT);
572
573 sui.unit = sum.unit;
574 sui.scale = -sum.scale;
575
576 print_bvspace(h, n);
577 print_otag(h, TAG_DIV, "csului", "Pp", &sum, &sui);
578 return 1;
579 }
580
581 static int
582 man_OP_pre(MAN_ARGS)
583 {
584 struct tag *tt;
585
586 print_text(h, "[");
587 h->flags |= HTML_NOSPACE;
588 tt = print_otag(h, TAG_SPAN, "c", "Op");
589
590 if (NULL != (n = n->child)) {
591 print_otag(h, TAG_B, "");
592 print_text(h, n->string);
593 }
594
595 print_stagq(h, tt);
596
597 if (NULL != n && NULL != n->next) {
598 print_otag(h, TAG_I, "");
599 print_text(h, n->next->string);
600 }
601
602 print_stagq(h, tt);
603 h->flags |= HTML_NOSPACE;
604 print_text(h, "]");
605 return 0;
606 }
607
608 static int
609 man_B_pre(MAN_ARGS)
610 {
611 print_otag(h, TAG_B, "");
612 return 1;
613 }
614
615 static int
616 man_I_pre(MAN_ARGS)
617 {
618 print_otag(h, TAG_I, "");
619 return 1;
620 }
621
622 static int
623 man_in_pre(MAN_ARGS)
624 {
625 print_otag(h, TAG_BR, "");
626 return 0;
627 }
628
629 static int
630 man_ign_pre(MAN_ARGS)
631 {
632
633 return 0;
634 }
635
636 static int
637 man_RS_pre(MAN_ARGS)
638 {
639 struct roffsu su;
640
641 if (n->type == ROFFT_HEAD)
642 return 0;
643 else if (n->type == ROFFT_BODY)
644 return 1;
645
646 SCALE_HS_INIT(&su, INDENT);
647 if (n->head->child)
648 a2width(n->head->child, &su);
649
650 print_otag(h, TAG_DIV, "sul", &su);
651 return 1;
652 }
653
654 static int
655 man_UR_pre(MAN_ARGS)
656 {
657 char *cp;
658 n = n->child;
659 assert(n->type == ROFFT_HEAD);
660 if (n->child != NULL) {
661 assert(n->child->type == ROFFT_TEXT);
662 if (n->tok == MAN_MT) {
663 mandoc_asprintf(&cp, "mailto:%s", n->child->string);
664 print_otag(h, TAG_A, "cTh", "Mt", cp);
665 free(cp);
666 } else
667 print_otag(h, TAG_A, "cTh", "Lk", n->child->string);
668 }
669
670 assert(n->next->type == ROFFT_BODY);
671 if (n->next->child != NULL)
672 n = n->next;
673
674 print_man_nodelist(man, n->child, h);
675
676 return 0;
677 }