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