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