]> git.cameronkatri.com Git - mandoc.git/blob - man_term.c
a4ebb1b447250d423731fef58fe91d8fcad80f87
[mandoc.git] / man_term.c
1 /* $Id: man_term.c,v 1.121 2011/09/21 09:57:13 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2011 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 AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include <sys/types.h>
23
24 #include <assert.h>
25 #include <ctype.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "mandoc.h"
31 #include "out.h"
32 #include "man.h"
33 #include "term.h"
34 #include "main.h"
35
36 #define INDENT 7 /* fixed-width char full-indent */
37 #define HALFINDENT 3 /* fixed-width char half-indent */
38 #define MAXMARGINS 64 /* maximum number of indented scopes */
39
40 /* FIXME: have PD set the default vspace width. */
41
42 struct mtermp {
43 int fl;
44 #define MANT_LITERAL (1 << 0)
45 size_t lmargin[MAXMARGINS]; /* margins (incl. visible page) */
46 int lmargincur; /* index of current margin */
47 int lmarginsz; /* actual number of nested margins */
48 size_t offset; /* default offset to visible page */
49 };
50
51 #define DECL_ARGS struct termp *p, \
52 struct mtermp *mt, \
53 const struct man_node *n, \
54 const struct man_meta *m
55
56 struct termact {
57 int (*pre)(DECL_ARGS);
58 void (*post)(DECL_ARGS);
59 int flags;
60 #define MAN_NOTEXT (1 << 0) /* Never has text children. */
61 };
62
63 static int a2width(const struct termp *, const char *);
64 static size_t a2height(const struct termp *, const char *);
65
66 static void print_man_nodelist(DECL_ARGS);
67 static void print_man_node(DECL_ARGS);
68 static void print_man_head(struct termp *, const void *);
69 static void print_man_foot(struct termp *, const void *);
70 static void print_bvspace(struct termp *,
71 const struct man_node *);
72
73 static int pre_alternate(DECL_ARGS);
74 static int pre_B(DECL_ARGS);
75 static int pre_HP(DECL_ARGS);
76 static int pre_I(DECL_ARGS);
77 static int pre_IP(DECL_ARGS);
78 static int pre_PP(DECL_ARGS);
79 static int pre_RS(DECL_ARGS);
80 static int pre_SH(DECL_ARGS);
81 static int pre_SS(DECL_ARGS);
82 static int pre_TP(DECL_ARGS);
83 static int pre_ign(DECL_ARGS);
84 static int pre_in(DECL_ARGS);
85 static int pre_literal(DECL_ARGS);
86 static int pre_sp(DECL_ARGS);
87 static int pre_ft(DECL_ARGS);
88
89 static void post_IP(DECL_ARGS);
90 static void post_HP(DECL_ARGS);
91 static void post_RS(DECL_ARGS);
92 static void post_SH(DECL_ARGS);
93 static void post_SS(DECL_ARGS);
94 static void post_TP(DECL_ARGS);
95
96 static const struct termact termacts[MAN_MAX] = {
97 { pre_sp, NULL, MAN_NOTEXT }, /* br */
98 { NULL, NULL, 0 }, /* TH */
99 { pre_SH, post_SH, 0 }, /* SH */
100 { pre_SS, post_SS, 0 }, /* SS */
101 { pre_TP, post_TP, 0 }, /* TP */
102 { pre_PP, NULL, 0 }, /* LP */
103 { pre_PP, NULL, 0 }, /* PP */
104 { pre_PP, NULL, 0 }, /* P */
105 { pre_IP, post_IP, 0 }, /* IP */
106 { pre_HP, post_HP, 0 }, /* HP */
107 { NULL, NULL, 0 }, /* SM */
108 { pre_B, NULL, 0 }, /* SB */
109 { pre_alternate, NULL, 0 }, /* BI */
110 { pre_alternate, NULL, 0 }, /* IB */
111 { pre_alternate, NULL, 0 }, /* BR */
112 { pre_alternate, NULL, 0 }, /* RB */
113 { NULL, NULL, 0 }, /* R */
114 { pre_B, NULL, 0 }, /* B */
115 { pre_I, NULL, 0 }, /* I */
116 { pre_alternate, NULL, 0 }, /* IR */
117 { pre_alternate, NULL, 0 }, /* RI */
118 { pre_ign, NULL, MAN_NOTEXT }, /* na */
119 { pre_sp, NULL, MAN_NOTEXT }, /* sp */
120 { pre_literal, NULL, 0 }, /* nf */
121 { pre_literal, NULL, 0 }, /* fi */
122 { NULL, NULL, 0 }, /* RE */
123 { pre_RS, post_RS, 0 }, /* RS */
124 { pre_ign, NULL, 0 }, /* DT */
125 { pre_ign, NULL, 0 }, /* UC */
126 { pre_ign, NULL, 0 }, /* PD */
127 { pre_ign, NULL, 0 }, /* AT */
128 { pre_in, NULL, MAN_NOTEXT }, /* in */
129 { pre_ft, NULL, MAN_NOTEXT }, /* ft */
130 };
131
132
133
134 void
135 terminal_man(void *arg, const struct man *man)
136 {
137 struct termp *p;
138 const struct man_node *n;
139 const struct man_meta *m;
140 struct mtermp mt;
141
142 p = (struct termp *)arg;
143
144 p->overstep = 0;
145 p->maxrmargin = p->defrmargin;
146 p->tabwidth = term_len(p, 5);
147
148 if (NULL == p->symtab)
149 p->symtab = mchars_alloc();
150
151 n = man_node(man);
152 m = man_meta(man);
153
154 term_begin(p, print_man_head, print_man_foot, m);
155 p->flags |= TERMP_NOSPACE;
156
157 memset(&mt, 0, sizeof(struct mtermp));
158
159 mt.lmargin[mt.lmargincur] = term_len(p, INDENT);
160 mt.offset = term_len(p, INDENT);
161
162 if (n->child)
163 print_man_nodelist(p, &mt, n->child, m);
164
165 term_end(p);
166 }
167
168
169 static size_t
170 a2height(const struct termp *p, const char *cp)
171 {
172 struct roffsu su;
173
174 if ( ! a2roffsu(cp, &su, SCALE_VS))
175 SCALE_VS_INIT(&su, atoi(cp));
176
177 return(term_vspan(p, &su));
178 }
179
180
181 static int
182 a2width(const struct termp *p, const char *cp)
183 {
184 struct roffsu su;
185
186 if ( ! a2roffsu(cp, &su, SCALE_BU))
187 return(-1);
188
189 return((int)term_hspan(p, &su));
190 }
191
192 /*
193 * Printing leading vertical space before a block.
194 * This is used for the paragraph macros.
195 * The rules are pretty simple, since there's very little nesting going
196 * on here. Basically, if we're the first within another block (SS/SH),
197 * then don't emit vertical space. If we are (RS), then do. If not the
198 * first, print it.
199 */
200 static void
201 print_bvspace(struct termp *p, const struct man_node *n)
202 {
203
204 term_newln(p);
205
206 if (n->body && n->body->child)
207 if (MAN_TBL == n->body->child->type)
208 return;
209
210 if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok)
211 if (NULL == n->prev)
212 return;
213
214 term_vspace(p);
215 }
216
217 /* ARGSUSED */
218 static int
219 pre_ign(DECL_ARGS)
220 {
221
222 return(0);
223 }
224
225
226 /* ARGSUSED */
227 static int
228 pre_I(DECL_ARGS)
229 {
230
231 term_fontrepl(p, TERMFONT_UNDER);
232 return(1);
233 }
234
235
236 /* ARGSUSED */
237 static int
238 pre_literal(DECL_ARGS)
239 {
240
241 term_newln(p);
242
243 if (MAN_nf == n->tok)
244 mt->fl |= MANT_LITERAL;
245 else
246 mt->fl &= ~MANT_LITERAL;
247
248 /*
249 * Unlike .IP and .TP, .HP does not have a HEAD.
250 * So in case a second call to term_flushln() is needed,
251 * indentation has to be set up explicitly.
252 */
253 if (MAN_HP == n->parent->tok && p->rmargin < p->maxrmargin) {
254 p->offset = p->rmargin;
255 p->rmargin = p->maxrmargin;
256 p->flags &= ~(TERMP_NOBREAK | TERMP_TWOSPACE);
257 p->flags |= TERMP_NOSPACE;
258 }
259
260 return(0);
261 }
262
263 /* ARGSUSED */
264 static int
265 pre_alternate(DECL_ARGS)
266 {
267 enum termfont font[2];
268 const struct man_node *nn;
269 int savelit, i;
270
271 switch (n->tok) {
272 case (MAN_RB):
273 font[0] = TERMFONT_NONE;
274 font[1] = TERMFONT_BOLD;
275 break;
276 case (MAN_RI):
277 font[0] = TERMFONT_NONE;
278 font[1] = TERMFONT_UNDER;
279 break;
280 case (MAN_BR):
281 font[0] = TERMFONT_BOLD;
282 font[1] = TERMFONT_NONE;
283 break;
284 case (MAN_BI):
285 font[0] = TERMFONT_BOLD;
286 font[1] = TERMFONT_UNDER;
287 break;
288 case (MAN_IR):
289 font[0] = TERMFONT_UNDER;
290 font[1] = TERMFONT_NONE;
291 break;
292 case (MAN_IB):
293 font[0] = TERMFONT_UNDER;
294 font[1] = TERMFONT_BOLD;
295 break;
296 default:
297 abort();
298 }
299
300 savelit = MANT_LITERAL & mt->fl;
301 mt->fl &= ~MANT_LITERAL;
302
303 for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
304 term_fontrepl(p, font[i]);
305 if (savelit && NULL == nn->next)
306 mt->fl |= MANT_LITERAL;
307 print_man_node(p, mt, nn, m);
308 if (nn->next)
309 p->flags |= TERMP_NOSPACE;
310 }
311
312 return(0);
313 }
314
315 /* ARGSUSED */
316 static int
317 pre_B(DECL_ARGS)
318 {
319
320 term_fontrepl(p, TERMFONT_BOLD);
321 return(1);
322 }
323
324 /* ARGSUSED */
325 static int
326 pre_ft(DECL_ARGS)
327 {
328 const char *cp;
329
330 if (NULL == n->child) {
331 term_fontlast(p);
332 return(0);
333 }
334
335 cp = n->child->string;
336 switch (*cp) {
337 case ('4'):
338 /* FALLTHROUGH */
339 case ('3'):
340 /* FALLTHROUGH */
341 case ('B'):
342 term_fontrepl(p, TERMFONT_BOLD);
343 break;
344 case ('2'):
345 /* FALLTHROUGH */
346 case ('I'):
347 term_fontrepl(p, TERMFONT_UNDER);
348 break;
349 case ('P'):
350 term_fontlast(p);
351 break;
352 case ('1'):
353 /* FALLTHROUGH */
354 case ('C'):
355 /* FALLTHROUGH */
356 case ('R'):
357 term_fontrepl(p, TERMFONT_NONE);
358 break;
359 default:
360 break;
361 }
362 return(0);
363 }
364
365 /* ARGSUSED */
366 static int
367 pre_in(DECL_ARGS)
368 {
369 int len, less;
370 size_t v;
371 const char *cp;
372
373 term_newln(p);
374
375 if (NULL == n->child) {
376 p->offset = mt->offset;
377 return(0);
378 }
379
380 cp = n->child->string;
381 less = 0;
382
383 if ('-' == *cp)
384 less = -1;
385 else if ('+' == *cp)
386 less = 1;
387 else
388 cp--;
389
390 if ((len = a2width(p, ++cp)) < 0)
391 return(0);
392
393 v = (size_t)len;
394
395 if (less < 0)
396 p->offset -= p->offset > v ? v : p->offset;
397 else if (less > 0)
398 p->offset += v;
399 else
400 p->offset = v;
401
402 /* Don't let this creep beyond the right margin. */
403
404 if (p->offset > p->rmargin)
405 p->offset = p->rmargin;
406
407 return(0);
408 }
409
410
411 /* ARGSUSED */
412 static int
413 pre_sp(DECL_ARGS)
414 {
415 size_t i, len;
416
417 if ((NULL == n->prev && n->parent)) {
418 if (MAN_SS == n->parent->tok)
419 return(0);
420 if (MAN_SH == n->parent->tok)
421 return(0);
422 }
423
424 switch (n->tok) {
425 case (MAN_br):
426 len = 0;
427 break;
428 default:
429 len = n->child ? a2height(p, n->child->string) : 1;
430 break;
431 }
432
433 if (0 == len)
434 term_newln(p);
435 for (i = 0; i < len; i++)
436 term_vspace(p);
437
438 return(0);
439 }
440
441
442 /* ARGSUSED */
443 static int
444 pre_HP(DECL_ARGS)
445 {
446 size_t len, one;
447 int ival;
448 const struct man_node *nn;
449
450 switch (n->type) {
451 case (MAN_BLOCK):
452 print_bvspace(p, n);
453 return(1);
454 case (MAN_BODY):
455 p->flags |= TERMP_NOBREAK;
456 p->flags |= TERMP_TWOSPACE;
457 break;
458 default:
459 return(0);
460 }
461
462 len = mt->lmargin[mt->lmargincur];
463 ival = -1;
464
465 /* Calculate offset. */
466
467 if (NULL != (nn = n->parent->head->child))
468 if ((ival = a2width(p, nn->string)) >= 0)
469 len = (size_t)ival;
470
471 one = term_len(p, 1);
472 if (len < one)
473 len = one;
474
475 p->offset = mt->offset;
476 p->rmargin = mt->offset + len;
477
478 if (ival >= 0)
479 mt->lmargin[mt->lmargincur] = (size_t)ival;
480
481 return(1);
482 }
483
484
485 /* ARGSUSED */
486 static void
487 post_HP(DECL_ARGS)
488 {
489
490 switch (n->type) {
491 case (MAN_BLOCK):
492 term_flushln(p);
493 break;
494 case (MAN_BODY):
495 term_flushln(p);
496 p->flags &= ~TERMP_NOBREAK;
497 p->flags &= ~TERMP_TWOSPACE;
498 p->offset = mt->offset;
499 p->rmargin = p->maxrmargin;
500 break;
501 default:
502 break;
503 }
504 }
505
506
507 /* ARGSUSED */
508 static int
509 pre_PP(DECL_ARGS)
510 {
511
512 switch (n->type) {
513 case (MAN_BLOCK):
514 mt->lmargin[mt->lmargincur] = term_len(p, INDENT);
515 print_bvspace(p, n);
516 break;
517 default:
518 p->offset = mt->offset;
519 break;
520 }
521
522 return(MAN_HEAD != n->type);
523 }
524
525
526 /* ARGSUSED */
527 static int
528 pre_IP(DECL_ARGS)
529 {
530 const struct man_node *nn;
531 size_t len;
532 int savelit, ival;
533
534 switch (n->type) {
535 case (MAN_BODY):
536 p->flags |= TERMP_NOSPACE;
537 break;
538 case (MAN_HEAD):
539 p->flags |= TERMP_NOBREAK;
540 break;
541 case (MAN_BLOCK):
542 print_bvspace(p, n);
543 /* FALLTHROUGH */
544 default:
545 return(1);
546 }
547
548 len = mt->lmargin[mt->lmargincur];
549 ival = -1;
550
551 /* Calculate the offset from the optional second argument. */
552 if (NULL != (nn = n->parent->head->child))
553 if (NULL != (nn = nn->next))
554 if ((ival = a2width(p, nn->string)) >= 0)
555 len = (size_t)ival;
556
557 switch (n->type) {
558 case (MAN_HEAD):
559 /* Handle zero-width lengths. */
560 if (0 == len)
561 len = term_len(p, 1);
562
563 p->offset = mt->offset;
564 p->rmargin = mt->offset + len;
565 if (ival < 0)
566 break;
567
568 /* Set the saved left-margin. */
569 mt->lmargin[mt->lmargincur] = (size_t)ival;
570
571 savelit = MANT_LITERAL & mt->fl;
572 mt->fl &= ~MANT_LITERAL;
573
574 if (n->child)
575 print_man_node(p, mt, n->child, m);
576
577 if (savelit)
578 mt->fl |= MANT_LITERAL;
579
580 return(0);
581 case (MAN_BODY):
582 p->offset = mt->offset + len;
583 p->rmargin = p->maxrmargin;
584 break;
585 default:
586 break;
587 }
588
589 return(1);
590 }
591
592
593 /* ARGSUSED */
594 static void
595 post_IP(DECL_ARGS)
596 {
597
598 switch (n->type) {
599 case (MAN_HEAD):
600 term_flushln(p);
601 p->flags &= ~TERMP_NOBREAK;
602 p->rmargin = p->maxrmargin;
603 break;
604 case (MAN_BODY):
605 term_newln(p);
606 break;
607 default:
608 break;
609 }
610 }
611
612
613 /* ARGSUSED */
614 static int
615 pre_TP(DECL_ARGS)
616 {
617 const struct man_node *nn;
618 size_t len;
619 int savelit, ival;
620
621 switch (n->type) {
622 case (MAN_HEAD):
623 p->flags |= TERMP_NOBREAK;
624 break;
625 case (MAN_BODY):
626 p->flags |= TERMP_NOSPACE;
627 break;
628 case (MAN_BLOCK):
629 print_bvspace(p, n);
630 /* FALLTHROUGH */
631 default:
632 return(1);
633 }
634
635 len = (size_t)mt->lmargin[mt->lmargincur];
636 ival = -1;
637
638 /* Calculate offset. */
639
640 if (NULL != (nn = n->parent->head->child))
641 if (nn->string && nn->parent->line == nn->line)
642 if ((ival = a2width(p, nn->string)) >= 0)
643 len = (size_t)ival;
644
645 switch (n->type) {
646 case (MAN_HEAD):
647 /* Handle zero-length properly. */
648 if (0 == len)
649 len = term_len(p, 1);
650
651 p->offset = mt->offset;
652 p->rmargin = mt->offset + len;
653
654 savelit = MANT_LITERAL & mt->fl;
655 mt->fl &= ~MANT_LITERAL;
656
657 /* Don't print same-line elements. */
658 for (nn = n->child; nn; nn = nn->next)
659 if (nn->line > n->line)
660 print_man_node(p, mt, nn, m);
661
662 if (savelit)
663 mt->fl |= MANT_LITERAL;
664 if (ival >= 0)
665 mt->lmargin[mt->lmargincur] = (size_t)ival;
666
667 return(0);
668 case (MAN_BODY):
669 p->offset = mt->offset + len;
670 p->rmargin = p->maxrmargin;
671 break;
672 default:
673 break;
674 }
675
676 return(1);
677 }
678
679
680 /* ARGSUSED */
681 static void
682 post_TP(DECL_ARGS)
683 {
684
685 switch (n->type) {
686 case (MAN_HEAD):
687 term_flushln(p);
688 p->flags &= ~TERMP_NOBREAK;
689 p->flags &= ~TERMP_TWOSPACE;
690 p->rmargin = p->maxrmargin;
691 break;
692 case (MAN_BODY):
693 term_newln(p);
694 break;
695 default:
696 break;
697 }
698 }
699
700
701 /* ARGSUSED */
702 static int
703 pre_SS(DECL_ARGS)
704 {
705
706 switch (n->type) {
707 case (MAN_BLOCK):
708 mt->fl &= ~MANT_LITERAL;
709 mt->lmargin[mt->lmargincur] = term_len(p, INDENT);
710 mt->offset = term_len(p, INDENT);
711 /* If following a prior empty `SS', no vspace. */
712 if (n->prev && MAN_SS == n->prev->tok)
713 if (NULL == n->prev->body->child)
714 break;
715 if (NULL == n->prev)
716 break;
717 term_vspace(p);
718 break;
719 case (MAN_HEAD):
720 term_fontrepl(p, TERMFONT_BOLD);
721 p->offset = term_len(p, HALFINDENT);
722 break;
723 case (MAN_BODY):
724 p->offset = mt->offset;
725 break;
726 default:
727 break;
728 }
729
730 return(1);
731 }
732
733
734 /* ARGSUSED */
735 static void
736 post_SS(DECL_ARGS)
737 {
738
739 switch (n->type) {
740 case (MAN_HEAD):
741 term_newln(p);
742 break;
743 case (MAN_BODY):
744 term_newln(p);
745 break;
746 default:
747 break;
748 }
749 }
750
751
752 /* ARGSUSED */
753 static int
754 pre_SH(DECL_ARGS)
755 {
756
757 switch (n->type) {
758 case (MAN_BLOCK):
759 mt->fl &= ~MANT_LITERAL;
760 mt->lmargin[mt->lmargincur] = term_len(p, INDENT);
761 mt->offset = term_len(p, INDENT);
762 /* If following a prior empty `SH', no vspace. */
763 if (n->prev && MAN_SH == n->prev->tok)
764 if (NULL == n->prev->body->child)
765 break;
766 /* If the first macro, no vspae. */
767 if (NULL == n->prev)
768 break;
769 term_vspace(p);
770 break;
771 case (MAN_HEAD):
772 term_fontrepl(p, TERMFONT_BOLD);
773 p->offset = 0;
774 break;
775 case (MAN_BODY):
776 p->offset = mt->offset;
777 break;
778 default:
779 break;
780 }
781
782 return(1);
783 }
784
785
786 /* ARGSUSED */
787 static void
788 post_SH(DECL_ARGS)
789 {
790
791 switch (n->type) {
792 case (MAN_HEAD):
793 term_newln(p);
794 break;
795 case (MAN_BODY):
796 term_newln(p);
797 break;
798 default:
799 break;
800 }
801 }
802
803 /* ARGSUSED */
804 static int
805 pre_RS(DECL_ARGS)
806 {
807 int ival;
808 size_t sz;
809
810 switch (n->type) {
811 case (MAN_BLOCK):
812 term_newln(p);
813 return(1);
814 case (MAN_HEAD):
815 return(0);
816 default:
817 break;
818 }
819
820 sz = term_len(p, INDENT);
821
822 if (NULL != (n = n->parent->head->child))
823 if ((ival = a2width(p, n->string)) >= 0)
824 sz = (size_t)ival;
825
826 mt->offset += sz;
827 p->rmargin = p->maxrmargin;
828 p->offset = mt->offset < p->rmargin ? mt->offset : p->rmargin;
829
830 if (++mt->lmarginsz < MAXMARGINS)
831 mt->lmargincur = mt->lmarginsz;
832
833 mt->lmargin[mt->lmargincur] = mt->lmargin[mt->lmargincur - 1];
834 return(1);
835 }
836
837 /* ARGSUSED */
838 static void
839 post_RS(DECL_ARGS)
840 {
841 int ival;
842 size_t sz;
843
844 switch (n->type) {
845 case (MAN_BLOCK):
846 return;
847 case (MAN_HEAD):
848 return;
849 default:
850 term_newln(p);
851 break;
852 }
853
854 sz = term_len(p, INDENT);
855
856 if (NULL != (n = n->parent->head->child))
857 if ((ival = a2width(p, n->string)) >= 0)
858 sz = (size_t)ival;
859
860 mt->offset = mt->offset < sz ? 0 : mt->offset - sz;
861 p->offset = mt->offset;
862
863 if (--mt->lmarginsz < MAXMARGINS)
864 mt->lmargincur = mt->lmarginsz;
865 }
866
867 static void
868 print_man_node(DECL_ARGS)
869 {
870 size_t rm, rmax;
871 int c;
872
873 switch (n->type) {
874 case(MAN_TEXT):
875 /*
876 * If we have a blank line, output a vertical space.
877 * If we have a space as the first character, break
878 * before printing the line's data.
879 */
880 if ('\0' == *n->string) {
881 term_vspace(p);
882 return;
883 } else if (' ' == *n->string && MAN_LINE & n->flags)
884 term_newln(p);
885
886 term_word(p, n->string);
887
888 /*
889 * If we're in a literal context, make sure that words
890 * togehter on the same line stay together. This is a
891 * POST-printing call, so we check the NEXT word. Since
892 * -man doesn't have nested macros, we don't need to be
893 * more specific than this.
894 */
895 if (MANT_LITERAL & mt->fl && ! (TERMP_NOBREAK & p->flags) &&
896 (NULL == n->next ||
897 n->next->line > n->line)) {
898 rm = p->rmargin;
899 rmax = p->maxrmargin;
900 p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
901 p->flags |= TERMP_NOSPACE;
902 term_flushln(p);
903 p->rmargin = rm;
904 p->maxrmargin = rmax;
905 }
906
907 if (MAN_EOS & n->flags)
908 p->flags |= TERMP_SENTENCE;
909 return;
910 case (MAN_EQN):
911 term_eqn(p, n->eqn);
912 return;
913 case (MAN_TBL):
914 /*
915 * Tables are preceded by a newline. Then process a
916 * table line, which will cause line termination,
917 */
918 if (TBL_SPAN_FIRST & n->span->flags)
919 term_newln(p);
920 term_tbl(p, n->span);
921 return;
922 default:
923 break;
924 }
925
926 if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
927 term_fontrepl(p, TERMFONT_NONE);
928
929 c = 1;
930 if (termacts[n->tok].pre)
931 c = (*termacts[n->tok].pre)(p, mt, n, m);
932
933 if (c && n->child)
934 print_man_nodelist(p, mt, n->child, m);
935
936 if (termacts[n->tok].post)
937 (*termacts[n->tok].post)(p, mt, n, m);
938 if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
939 term_fontrepl(p, TERMFONT_NONE);
940
941 if (MAN_EOS & n->flags)
942 p->flags |= TERMP_SENTENCE;
943 }
944
945
946 static void
947 print_man_nodelist(DECL_ARGS)
948 {
949
950 print_man_node(p, mt, n, m);
951 if ( ! n->next)
952 return;
953 print_man_nodelist(p, mt, n->next, m);
954 }
955
956
957 static void
958 print_man_foot(struct termp *p, const void *arg)
959 {
960 const struct man_meta *meta;
961
962 meta = (const struct man_meta *)arg;
963
964 term_fontrepl(p, TERMFONT_NONE);
965
966 term_vspace(p);
967 term_vspace(p);
968 term_vspace(p);
969
970 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
971 p->rmargin = p->maxrmargin - term_strlen(p, meta->date);
972 p->offset = 0;
973
974 /* term_strlen() can return zero. */
975 if (p->rmargin == p->maxrmargin)
976 p->rmargin--;
977
978 if (meta->source)
979 term_word(p, meta->source);
980 if (meta->source)
981 term_word(p, "");
982 term_flushln(p);
983
984 p->flags |= TERMP_NOSPACE;
985 p->offset = p->rmargin;
986 p->rmargin = p->maxrmargin;
987 p->flags &= ~TERMP_NOBREAK;
988
989 term_word(p, meta->date);
990 term_flushln(p);
991 }
992
993
994 static void
995 print_man_head(struct termp *p, const void *arg)
996 {
997 char buf[BUFSIZ], title[BUFSIZ];
998 size_t buflen, titlen;
999 const struct man_meta *m;
1000
1001 m = (const struct man_meta *)arg;
1002
1003 /*
1004 * Note that old groff would spit out some spaces before the
1005 * header. We discontinue this strange behaviour, but at one
1006 * point we did so here.
1007 */
1008
1009 p->offset = 0;
1010 p->rmargin = p->maxrmargin;
1011
1012 buf[0] = title[0] = '\0';
1013
1014 if (m->vol)
1015 strlcpy(buf, m->vol, BUFSIZ);
1016 buflen = term_strlen(p, buf);
1017
1018 snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec);
1019 titlen = term_strlen(p, title);
1020
1021 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1022 p->offset = 0;
1023 p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
1024 (p->maxrmargin -
1025 term_strlen(p, buf) + term_len(p, 1)) / 2 :
1026 p->maxrmargin - buflen;
1027
1028 term_word(p, title);
1029 term_flushln(p);
1030
1031 p->flags |= TERMP_NOSPACE;
1032 p->offset = p->rmargin;
1033 p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
1034 p->maxrmargin - titlen : p->maxrmargin;
1035
1036 term_word(p, buf);
1037 term_flushln(p);
1038
1039 p->flags &= ~TERMP_NOBREAK;
1040 if (p->rmargin + titlen <= p->maxrmargin) {
1041 p->flags |= TERMP_NOSPACE;
1042 p->offset = p->rmargin;
1043 p->rmargin = p->maxrmargin;
1044 term_word(p, title);
1045 term_flushln(p);
1046 }
1047
1048 p->flags &= ~TERMP_NOSPACE;
1049 p->offset = 0;
1050 p->rmargin = p->maxrmargin;
1051
1052 /*
1053 * Groff likes to have some leading spaces before content. Well
1054 * that's fine by me.
1055 */
1056
1057 term_vspace(p);
1058 term_vspace(p);
1059 term_vspace(p);
1060 }