Merge branch 'master' of ssh://crater.dragonflybsd.org/repository/git/dragonfly
[dragonfly.git] / usr.bin / mandoc / man_term.c
1 /*      $Id: man_term.c,v 1.54 2009/11/12 08:21:05 kristaps 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 "man.h"
27 #include "term.h"
28 #include "chars.h"
29 #include "main.h"
30
31 #define INDENT            7
32 #define HALFINDENT        3
33
34 /* FIXME: have PD set the default vspace width. */
35
36 struct  mtermp {
37         int               fl;
38 #define MANT_LITERAL     (1 << 0)
39         /*
40          * Default amount to indent the left margin after leading text
41          * has been printed (e.g., `HP' left-indent, `TP' and `IP' body
42          * indent).  This needs to be saved because `HP' and so on, if
43          * not having a specified value, must default.
44          *
45          * Note that this is the indentation AFTER the left offset, so
46          * the total offset is usually offset + lmargin.
47          */
48         size_t            lmargin;
49         /*
50          * The default offset, i.e., the amount between any text and the
51          * page boundary.
52          */
53         size_t            offset;
54 };
55
56 #define DECL_ARGS         struct termp *p, \
57                           struct mtermp *mt, \
58                           const struct man_node *n, \
59                           const struct man_meta *m
60
61 struct  termact {
62         int             (*pre)(DECL_ARGS);
63         void            (*post)(DECL_ARGS);
64 };
65
66 #ifdef __linux__
67 extern  size_t            strlcpy(char *, const char *, size_t);
68 extern  size_t            strlcat(char *, const char *, size_t);
69 #endif
70
71 static  int               a2width(const struct man_node *);
72 static  int               a2height(const struct man_node *);
73
74 static  void              print_man_head(struct termp *,
75                                 const struct man_meta *);
76 static  void              print_man_nodelist(DECL_ARGS);
77 static  void              print_man_node(DECL_ARGS);
78 static  void              print_man_foot(struct termp *,
79                                 const struct man_meta *);
80 static  void              print_bvspace(struct termp *,
81                                 const struct man_node *);
82
83 static  int               pre_B(DECL_ARGS);
84 static  int               pre_BI(DECL_ARGS);
85 static  int               pre_HP(DECL_ARGS);
86 static  int               pre_I(DECL_ARGS);
87 static  int               pre_IP(DECL_ARGS);
88 static  int               pre_PP(DECL_ARGS);
89 static  int               pre_RB(DECL_ARGS);
90 static  int               pre_RI(DECL_ARGS);
91 static  int               pre_RS(DECL_ARGS);
92 static  int               pre_SH(DECL_ARGS);
93 static  int               pre_SS(DECL_ARGS);
94 static  int               pre_TP(DECL_ARGS);
95 static  int               pre_br(DECL_ARGS);
96 static  int               pre_fi(DECL_ARGS);
97 static  int               pre_ign(DECL_ARGS);
98 static  int               pre_nf(DECL_ARGS);
99 static  int               pre_sp(DECL_ARGS);
100
101 static  void              post_IP(DECL_ARGS);
102 static  void              post_HP(DECL_ARGS);
103 static  void              post_RS(DECL_ARGS);
104 static  void              post_SH(DECL_ARGS);
105 static  void              post_SS(DECL_ARGS);
106 static  void              post_TP(DECL_ARGS);
107
108 static  const struct termact termacts[MAN_MAX] = {
109         { pre_br, NULL }, /* br */
110         { NULL, NULL }, /* TH */
111         { pre_SH, post_SH }, /* SH */
112         { pre_SS, post_SS }, /* SS */
113         { pre_TP, post_TP }, /* TP */
114         { pre_PP, NULL }, /* LP */
115         { pre_PP, NULL }, /* PP */
116         { pre_PP, NULL }, /* P */
117         { pre_IP, post_IP }, /* IP */
118         { pre_HP, post_HP }, /* HP */
119         { NULL, NULL }, /* SM */
120         { pre_B, NULL }, /* SB */
121         { pre_BI, NULL }, /* BI */
122         { pre_BI, NULL }, /* IB */
123         { pre_RB, NULL }, /* BR */
124         { pre_RB, NULL }, /* RB */
125         { NULL, NULL }, /* R */
126         { pre_B, NULL }, /* B */
127         { pre_I, NULL }, /* I */
128         { pre_RI, NULL }, /* IR */
129         { pre_RI, NULL }, /* RI */
130         { NULL, NULL }, /* na */
131         { pre_I, NULL }, /* i */
132         { pre_sp, NULL }, /* sp */
133         { pre_nf, NULL }, /* nf */
134         { pre_fi, NULL }, /* fi */
135         { NULL, NULL }, /* r */
136         { NULL, NULL }, /* RE */
137         { pre_RS, post_RS }, /* RS */
138         { pre_ign, NULL }, /* DT */
139         { pre_ign, NULL }, /* UC */
140         { pre_ign, NULL }, /* PD */
141 };
142
143
144
145 void
146 terminal_man(void *arg, const struct man *man)
147 {
148         struct termp            *p;
149         const struct man_node   *n;
150         const struct man_meta   *m;
151         struct mtermp            mt;
152
153         p = (struct termp *)arg;
154
155         if (NULL == p->symtab)
156                 switch (p->enc) {
157                 case (TERMENC_ASCII):
158                         p->symtab = chars_init(CHARS_ASCII);
159                         break;
160                 default:
161                         abort();
162                         /* NOTREACHED */
163                 }
164
165         n = man_node(man);
166         m = man_meta(man);
167
168         print_man_head(p, m);
169         p->flags |= TERMP_NOSPACE;
170
171         mt.fl = 0;
172         mt.lmargin = INDENT;
173         mt.offset = INDENT;
174
175         if (n->child)
176                 print_man_nodelist(p, &mt, n->child, m);
177         print_man_foot(p, m);
178 }
179
180
181 static int
182 a2height(const struct man_node *n)
183 {
184         struct roffsu    su;
185
186         assert(MAN_TEXT == n->type);
187         assert(n->string);
188         if ( ! a2roffsu(n->string, &su, SCALE_VS))
189                 SCALE_VS_INIT(&su, strlen(n->string));
190
191         return((int)term_vspan(&su));
192 }
193
194
195 static int
196 a2width(const struct man_node *n)
197 {
198         struct roffsu    su;
199
200         assert(MAN_TEXT == n->type);
201         assert(n->string);
202         if ( ! a2roffsu(n->string, &su, SCALE_BU))
203                 return(-1);
204
205         return((int)term_hspan(&su));
206 }
207
208
209 static void
210 print_bvspace(struct termp *p, const struct man_node *n)
211 {
212         term_newln(p);
213
214         if (NULL == n->prev)
215                 return;
216
217         if (MAN_SS == n->prev->tok)
218                 return;
219         if (MAN_SH == n->prev->tok)
220                 return;
221
222         term_vspace(p);
223 }
224
225
226 /* ARGSUSED */
227 static int
228 pre_ign(DECL_ARGS)
229 {
230
231         return(0);
232 }
233
234
235 /* ARGSUSED */
236 static int
237 pre_I(DECL_ARGS)
238 {
239
240         term_fontrepl(p, TERMFONT_UNDER);
241         return(1);
242 }
243
244
245 /* ARGSUSED */
246 static int
247 pre_fi(DECL_ARGS)
248 {
249
250         mt->fl &= ~MANT_LITERAL;
251         return(1);
252 }
253
254
255 /* ARGSUSED */
256 static int
257 pre_nf(DECL_ARGS)
258 {
259
260         term_newln(p);
261         mt->fl |= MANT_LITERAL;
262         return(1);
263 }
264
265
266 /* ARGSUSED */
267 static int
268 pre_RB(DECL_ARGS)
269 {
270         const struct man_node *nn;
271         int              i;
272
273         for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
274                 if (i % 2 && MAN_RB == n->tok)
275                         term_fontrepl(p, TERMFONT_BOLD);
276                 else if ( ! (i % 2) && MAN_RB != n->tok)
277                         term_fontrepl(p, TERMFONT_BOLD);
278                 else
279                         term_fontrepl(p, TERMFONT_NONE);
280
281                 if (i > 0)
282                         p->flags |= TERMP_NOSPACE;
283
284                 print_man_node(p, mt, nn, m);
285         }
286         return(0);
287 }
288
289
290 /* ARGSUSED */
291 static int
292 pre_RI(DECL_ARGS)
293 {
294         const struct man_node *nn;
295         int              i;
296
297         for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
298                 if (i % 2 && MAN_RI == n->tok)
299                         term_fontrepl(p, TERMFONT_UNDER);
300                 else if ( ! (i % 2) && MAN_RI != n->tok)
301                         term_fontrepl(p, TERMFONT_UNDER);
302                 else
303                         term_fontrepl(p, TERMFONT_NONE);
304
305                 if (i > 0)
306                         p->flags |= TERMP_NOSPACE;
307
308                 print_man_node(p, mt, nn, m);
309         }
310         return(0);
311 }
312
313
314 /* ARGSUSED */
315 static int
316 pre_BI(DECL_ARGS)
317 {
318         const struct man_node   *nn;
319         int                      i;
320
321         for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
322                 if (i % 2 && MAN_BI == n->tok)
323                         term_fontrepl(p, TERMFONT_UNDER);
324                 else if (i % 2)
325                         term_fontrepl(p, TERMFONT_BOLD);
326                 else if (MAN_BI == n->tok)
327                         term_fontrepl(p, TERMFONT_BOLD);
328                 else
329                         term_fontrepl(p, TERMFONT_UNDER);
330
331                 if (i)
332                         p->flags |= TERMP_NOSPACE;
333
334                 print_man_node(p, mt, nn, m);
335         }
336         return(0);
337 }
338
339
340 /* ARGSUSED */
341 static int
342 pre_B(DECL_ARGS)
343 {
344
345         term_fontrepl(p, TERMFONT_BOLD);
346         return(1);
347 }
348
349
350 /* ARGSUSED */
351 static int
352 pre_sp(DECL_ARGS)
353 {
354         int              i, len;
355
356         len = n->child ? a2height(n->child) : 1;
357
358         if (0 == len)
359                 term_newln(p);
360         for (i = 0; i < len; i++)
361                 term_vspace(p);
362
363         return(0);
364 }
365
366
367 /* ARGSUSED */
368 static int
369 pre_br(DECL_ARGS)
370 {
371
372         term_newln(p);
373         return(0);
374 }
375
376
377 /* ARGSUSED */
378 static int
379 pre_HP(DECL_ARGS)
380 {
381         size_t                   len;
382         int                      ival;
383         const struct man_node   *nn;
384
385         switch (n->type) {
386         case (MAN_BLOCK):
387                 print_bvspace(p, n);
388                 return(1);
389         case (MAN_BODY):
390                 p->flags |= TERMP_NOBREAK;
391                 p->flags |= TERMP_TWOSPACE;
392                 break;
393         default:
394                 return(0);
395         }
396
397         len = mt->lmargin;
398         ival = -1;
399
400         /* Calculate offset. */
401
402         if (NULL != (nn = n->parent->head->child))
403                 if ((ival = a2width(nn)) >= 0)
404                         len = (size_t)ival;
405
406         if (0 == len)
407                 len = 1;
408
409         p->offset = mt->offset;
410         p->rmargin = mt->offset + len;
411
412         if (ival >= 0)
413                 mt->lmargin = (size_t)ival;
414
415         return(1);
416 }
417
418
419 /* ARGSUSED */
420 static void
421 post_HP(DECL_ARGS)
422 {
423
424         switch (n->type) {
425         case (MAN_BLOCK):
426                 term_flushln(p);
427                 break;
428         case (MAN_BODY):
429                 term_flushln(p);
430                 p->flags &= ~TERMP_NOBREAK;
431                 p->flags &= ~TERMP_TWOSPACE;
432                 p->offset = mt->offset;
433                 p->rmargin = p->maxrmargin;
434                 break;
435         default:
436                 break;
437         }
438 }
439
440
441 /* ARGSUSED */
442 static int
443 pre_PP(DECL_ARGS)
444 {
445
446         switch (n->type) {
447         case (MAN_BLOCK):
448                 mt->lmargin = INDENT;
449                 print_bvspace(p, n);
450                 break;
451         default:
452                 p->offset = mt->offset;
453                 break;
454         }
455
456         return(1);
457 }
458
459
460 /* ARGSUSED */
461 static int
462 pre_IP(DECL_ARGS)
463 {
464         const struct man_node   *nn;
465         size_t                   len;
466         int                      ival;
467
468         switch (n->type) {
469         case (MAN_BODY):
470                 p->flags |= TERMP_NOLPAD;
471                 p->flags |= TERMP_NOSPACE;
472                 break;
473         case (MAN_HEAD):
474                 p->flags |= TERMP_NOBREAK;
475                 p->flags |= TERMP_TWOSPACE;
476                 break;
477         case (MAN_BLOCK):
478                 print_bvspace(p, n);
479                 /* FALLTHROUGH */
480         default:
481                 return(1);
482         }
483
484         len = mt->lmargin;
485         ival = -1;
486
487         /* Calculate offset. */
488
489         if (NULL != (nn = n->parent->head->child))
490                 if (NULL != (nn = nn->next)) {
491                         for ( ; nn->next; nn = nn->next)
492                                 /* Do nothing. */ ;
493                         if ((ival = a2width(nn)) >= 0)
494                                 len = (size_t)ival;
495                 }
496
497         switch (n->type) {
498         case (MAN_HEAD):
499                 /* Handle zero-width lengths. */
500                 if (0 == len)
501                         len = 1;
502
503                 p->offset = mt->offset;
504                 p->rmargin = mt->offset + len;
505                 if (ival < 0)
506                         break;
507
508                 /* Set the saved left-margin. */
509                 mt->lmargin = (size_t)ival;
510
511                 /* Don't print the length value. */
512                 for (nn = n->child; nn->next; nn = nn->next)
513                         print_man_node(p, mt, nn, m);
514                 return(0);
515         case (MAN_BODY):
516                 p->offset = mt->offset + len;
517                 p->rmargin = p->maxrmargin;
518                 break;
519         default:
520                 break;
521         }
522
523         return(1);
524 }
525
526
527 /* ARGSUSED */
528 static void
529 post_IP(DECL_ARGS)
530 {
531
532         switch (n->type) {
533         case (MAN_HEAD):
534                 term_flushln(p);
535                 p->flags &= ~TERMP_NOBREAK;
536                 p->flags &= ~TERMP_TWOSPACE;
537                 p->rmargin = p->maxrmargin;
538                 break;
539         case (MAN_BODY):
540                 term_flushln(p);
541                 p->flags &= ~TERMP_NOLPAD;
542                 break;
543         default:
544                 break;
545         }
546 }
547
548
549 /* ARGSUSED */
550 static int
551 pre_TP(DECL_ARGS)
552 {
553         const struct man_node   *nn;
554         size_t                   len;
555         int                      ival;
556
557         switch (n->type) {
558         case (MAN_HEAD):
559                 p->flags |= TERMP_NOBREAK;
560                 p->flags |= TERMP_TWOSPACE;
561                 break;
562         case (MAN_BODY):
563                 p->flags |= TERMP_NOLPAD;
564                 p->flags |= TERMP_NOSPACE;
565                 break;
566         case (MAN_BLOCK):
567                 print_bvspace(p, n);
568                 /* FALLTHROUGH */
569         default:
570                 return(1);
571         }
572
573         len = (size_t)mt->lmargin;
574         ival = -1;
575
576         /* Calculate offset. */
577
578         if (NULL != (nn = n->parent->head->child))
579                 if (NULL != nn->next)
580                         if ((ival = a2width(nn)) >= 0)
581                                 len = (size_t)ival;
582
583         switch (n->type) {
584         case (MAN_HEAD):
585                 /* Handle zero-length properly. */
586                 if (0 == len)
587                         len = 1;
588
589                 p->offset = mt->offset;
590                 p->rmargin = mt->offset + len;
591
592                 /* Don't print same-line elements. */
593                 for (nn = n->child; nn; nn = nn->next)
594                         if (nn->line > n->line)
595                                 print_man_node(p, mt, nn, m);
596
597                 if (ival >= 0)
598                         mt->lmargin = (size_t)ival;
599
600                 return(0);
601         case (MAN_BODY):
602                 p->offset = mt->offset + len;
603                 p->rmargin = p->maxrmargin;
604                 break;
605         default:
606                 break;
607         }
608
609         return(1);
610 }
611
612
613 /* ARGSUSED */
614 static void
615 post_TP(DECL_ARGS)
616 {
617
618         switch (n->type) {
619         case (MAN_HEAD):
620                 term_flushln(p);
621                 p->flags &= ~TERMP_NOBREAK;
622                 p->flags &= ~TERMP_TWOSPACE;
623                 p->rmargin = p->maxrmargin;
624                 break;
625         case (MAN_BODY):
626                 term_flushln(p);
627                 p->flags &= ~TERMP_NOLPAD;
628                 break;
629         default:
630                 break;
631         }
632 }
633
634
635 /* ARGSUSED */
636 static int
637 pre_SS(DECL_ARGS)
638 {
639
640         switch (n->type) {
641         case (MAN_BLOCK):
642                 mt->lmargin = INDENT;
643                 mt->offset = INDENT;
644                 /* If following a prior empty `SS', no vspace. */
645                 if (n->prev && MAN_SS == n->prev->tok)
646                         if (NULL == n->prev->body->child)
647                                 break;
648                 if (NULL == n->prev)
649                         break;
650                 term_vspace(p);
651                 break;
652         case (MAN_HEAD):
653                 term_fontrepl(p, TERMFONT_BOLD);
654                 p->offset = HALFINDENT;
655                 break;
656         case (MAN_BODY):
657                 p->offset = mt->offset;
658                 break;
659         default:
660                 break;
661         }
662
663         return(1);
664 }
665
666
667 /* ARGSUSED */
668 static void
669 post_SS(DECL_ARGS)
670 {
671
672         switch (n->type) {
673         case (MAN_HEAD):
674                 term_newln(p);
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_SH(DECL_ARGS)
688 {
689
690         switch (n->type) {
691         case (MAN_BLOCK):
692                 mt->lmargin = INDENT;
693                 mt->offset = INDENT;
694                 /* If following a prior empty `SH', no vspace. */
695                 if (n->prev && MAN_SH == n->prev->tok)
696                         if (NULL == n->prev->body->child)
697                                 break;
698                 term_vspace(p);
699                 break;
700         case (MAN_HEAD):
701                 term_fontrepl(p, TERMFONT_BOLD);
702                 p->offset = 0;
703                 break;
704         case (MAN_BODY):
705                 p->offset = mt->offset;
706                 break;
707         default:
708                 break;
709         }
710
711         return(1);
712 }
713
714
715 /* ARGSUSED */
716 static void
717 post_SH(DECL_ARGS)
718 {
719
720         switch (n->type) {
721         case (MAN_HEAD):
722                 term_newln(p);
723                 break;
724         case (MAN_BODY):
725                 term_newln(p);
726                 break;
727         default:
728                 break;
729         }
730 }
731
732
733 /* ARGSUSED */
734 static int
735 pre_RS(DECL_ARGS)
736 {
737         const struct man_node   *nn;
738         int                      ival;
739
740         switch (n->type) {
741         case (MAN_BLOCK):
742                 term_newln(p);
743                 return(1);
744         case (MAN_HEAD):
745                 return(0);
746         default:
747                 break;
748         }
749
750         if (NULL == (nn = n->parent->head->child)) {
751                 mt->offset = mt->lmargin + INDENT;
752                 p->offset = mt->offset;
753                 return(1);
754         }
755
756         if ((ival = a2width(nn)) < 0)
757                 return(1);
758
759         mt->offset = INDENT + (size_t)ival;
760         p->offset = mt->offset;
761
762         return(1);
763 }
764
765
766 /* ARGSUSED */
767 static void
768 post_RS(DECL_ARGS)
769 {
770
771         switch (n->type) {
772         case (MAN_BLOCK):
773                 mt->offset = mt->lmargin = INDENT;
774                 break;
775         default:
776                 term_newln(p);
777                 p->offset = INDENT;
778                 break;
779         }
780 }
781
782
783 static void
784 print_man_node(DECL_ARGS)
785 {
786         int              c;
787
788         c = 1;
789
790         switch (n->type) {
791         case(MAN_TEXT):
792                 if (0 == *n->string) {
793                         term_vspace(p);
794                         break;
795                 }
796
797                 term_word(p, n->string);
798
799                 /* FIXME: this means that macro lines are munged!  */
800
801                 if (MANT_LITERAL & mt->fl) {
802                         p->flags |= TERMP_NOSPACE;
803                         term_flushln(p);
804                 }
805                 break;
806         default:
807                 term_fontrepl(p, TERMFONT_NONE);
808                 if (termacts[n->tok].pre)
809                         c = (*termacts[n->tok].pre)(p, mt, n, m);
810                 break;
811         }
812
813         if (c && n->child)
814                 print_man_nodelist(p, mt, n->child, m);
815
816         if (MAN_TEXT != n->type) {
817                 if (termacts[n->tok].post)
818                         (*termacts[n->tok].post)(p, mt, n, m);
819                 term_fontrepl(p, TERMFONT_NONE);
820         }
821 }
822
823
824 static void
825 print_man_nodelist(DECL_ARGS)
826 {
827
828         print_man_node(p, mt, n, m);
829         if ( ! n->next)
830                 return;
831         print_man_nodelist(p, mt, n->next, m);
832 }
833
834
835 static void
836 print_man_foot(struct termp *p, const struct man_meta *meta)
837 {
838         char            buf[DATESIZ];
839
840         term_fontrepl(p, TERMFONT_NONE);
841
842         time2a(meta->date, buf, DATESIZ);
843
844         term_vspace(p);
845
846         p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
847         p->rmargin = p->maxrmargin - strlen(buf);
848         p->offset = 0;
849
850         if (meta->source)
851                 term_word(p, meta->source);
852         if (meta->source)
853                 term_word(p, "");
854         term_flushln(p);
855
856         p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
857         p->offset = p->rmargin;
858         p->rmargin = p->maxrmargin;
859         p->flags &= ~TERMP_NOBREAK;
860
861         term_word(p, buf);
862         term_flushln(p);
863 }
864
865
866 static void
867 print_man_head(struct termp *p, const struct man_meta *m)
868 {
869         char            buf[BUFSIZ], title[BUFSIZ];
870
871         p->rmargin = p->maxrmargin;
872         p->offset = 0;
873         buf[0] = title[0] = '\0';
874
875         if (m->vol)
876                 strlcpy(buf, m->vol, BUFSIZ);
877
878         snprintf(title, BUFSIZ, "%s(%d)", m->title, m->msec);
879
880         p->offset = 0;
881         p->rmargin = (p->maxrmargin - strlen(buf) + 1) / 2;
882         p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
883
884         term_word(p, title);
885         term_flushln(p);
886
887         p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
888         p->offset = p->rmargin;
889         p->rmargin = p->maxrmargin - strlen(title);
890
891         term_word(p, buf);
892         term_flushln(p);
893
894         p->offset = p->rmargin;
895         p->rmargin = p->maxrmargin;
896         p->flags &= ~TERMP_NOBREAK;
897         p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
898
899         term_word(p, title);
900         term_flushln(p);
901
902         p->rmargin = p->maxrmargin;
903         p->offset = 0;
904         p->flags &= ~TERMP_NOSPACE;
905 }