]> git.cameronkatri.com Git - mandoc.git/blob - mdoc_term.c
Partial implementation of .Bd -centered.
[mandoc.git] / mdoc_term.c
1 /* $Id: mdoc_term.c,v 1.272 2014/07/29 13:58:18 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
5 * Copyright (c) 2013 Franco Fichtner <franco@lastsummer.de>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include <sys/types.h>
24
25 #include <assert.h>
26 #include <ctype.h>
27 #include <stdint.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "mandoc.h"
33 #include "mandoc_aux.h"
34 #include "out.h"
35 #include "term.h"
36 #include "mdoc.h"
37 #include "main.h"
38
39 struct termpair {
40 struct termpair *ppair;
41 int count;
42 };
43
44 #define DECL_ARGS struct termp *p, \
45 struct termpair *pair, \
46 const struct mdoc_meta *meta, \
47 struct mdoc_node *n
48
49 struct termact {
50 int (*pre)(DECL_ARGS);
51 void (*post)(DECL_ARGS);
52 };
53
54 static size_t a2width(const struct termp *, const char *);
55 static size_t a2height(const struct termp *, const char *);
56 static size_t a2offs(const struct termp *, const char *);
57
58 static void print_bvspace(struct termp *,
59 const struct mdoc_node *,
60 const struct mdoc_node *);
61 static void print_mdoc_node(DECL_ARGS);
62 static void print_mdoc_nodelist(DECL_ARGS);
63 static void print_mdoc_head(struct termp *, const void *);
64 static void print_mdoc_foot(struct termp *, const void *);
65 static void synopsis_pre(struct termp *,
66 const struct mdoc_node *);
67
68 static void termp____post(DECL_ARGS);
69 static void termp__t_post(DECL_ARGS);
70 static void termp_an_post(DECL_ARGS);
71 static void termp_bd_post(DECL_ARGS);
72 static void termp_bk_post(DECL_ARGS);
73 static void termp_bl_post(DECL_ARGS);
74 static void termp_fd_post(DECL_ARGS);
75 static void termp_fo_post(DECL_ARGS);
76 static void termp_in_post(DECL_ARGS);
77 static void termp_it_post(DECL_ARGS);
78 static void termp_lb_post(DECL_ARGS);
79 static void termp_nm_post(DECL_ARGS);
80 static void termp_pf_post(DECL_ARGS);
81 static void termp_quote_post(DECL_ARGS);
82 static void termp_sh_post(DECL_ARGS);
83 static void termp_ss_post(DECL_ARGS);
84
85 static int termp__a_pre(DECL_ARGS);
86 static int termp__t_pre(DECL_ARGS);
87 static int termp_an_pre(DECL_ARGS);
88 static int termp_ap_pre(DECL_ARGS);
89 static int termp_bd_pre(DECL_ARGS);
90 static int termp_bf_pre(DECL_ARGS);
91 static int termp_bk_pre(DECL_ARGS);
92 static int termp_bl_pre(DECL_ARGS);
93 static int termp_bold_pre(DECL_ARGS);
94 static int termp_bt_pre(DECL_ARGS);
95 static int termp_bx_pre(DECL_ARGS);
96 static int termp_cd_pre(DECL_ARGS);
97 static int termp_d1_pre(DECL_ARGS);
98 static int termp_es_pre(DECL_ARGS);
99 static int termp_ex_pre(DECL_ARGS);
100 static int termp_fa_pre(DECL_ARGS);
101 static int termp_fd_pre(DECL_ARGS);
102 static int termp_fl_pre(DECL_ARGS);
103 static int termp_fn_pre(DECL_ARGS);
104 static int termp_fo_pre(DECL_ARGS);
105 static int termp_ft_pre(DECL_ARGS);
106 static int termp_in_pre(DECL_ARGS);
107 static int termp_it_pre(DECL_ARGS);
108 static int termp_li_pre(DECL_ARGS);
109 static int termp_ll_pre(DECL_ARGS);
110 static int termp_lk_pre(DECL_ARGS);
111 static int termp_nd_pre(DECL_ARGS);
112 static int termp_nm_pre(DECL_ARGS);
113 static int termp_ns_pre(DECL_ARGS);
114 static int termp_quote_pre(DECL_ARGS);
115 static int termp_rs_pre(DECL_ARGS);
116 static int termp_rv_pre(DECL_ARGS);
117 static int termp_sh_pre(DECL_ARGS);
118 static int termp_sm_pre(DECL_ARGS);
119 static int termp_sp_pre(DECL_ARGS);
120 static int termp_ss_pre(DECL_ARGS);
121 static int termp_under_pre(DECL_ARGS);
122 static int termp_ud_pre(DECL_ARGS);
123 static int termp_vt_pre(DECL_ARGS);
124 static int termp_xr_pre(DECL_ARGS);
125 static int termp_xx_pre(DECL_ARGS);
126
127 static const struct termact termacts[MDOC_MAX] = {
128 { termp_ap_pre, NULL }, /* Ap */
129 { NULL, NULL }, /* Dd */
130 { NULL, NULL }, /* Dt */
131 { NULL, NULL }, /* Os */
132 { termp_sh_pre, termp_sh_post }, /* Sh */
133 { termp_ss_pre, termp_ss_post }, /* Ss */
134 { termp_sp_pre, NULL }, /* Pp */
135 { termp_d1_pre, termp_bl_post }, /* D1 */
136 { termp_d1_pre, termp_bl_post }, /* Dl */
137 { termp_bd_pre, termp_bd_post }, /* Bd */
138 { NULL, NULL }, /* Ed */
139 { termp_bl_pre, termp_bl_post }, /* Bl */
140 { NULL, NULL }, /* El */
141 { termp_it_pre, termp_it_post }, /* It */
142 { termp_under_pre, NULL }, /* Ad */
143 { termp_an_pre, termp_an_post }, /* An */
144 { termp_under_pre, NULL }, /* Ar */
145 { termp_cd_pre, NULL }, /* Cd */
146 { termp_bold_pre, NULL }, /* Cm */
147 { NULL, NULL }, /* Dv */
148 { NULL, NULL }, /* Er */
149 { NULL, NULL }, /* Ev */
150 { termp_ex_pre, NULL }, /* Ex */
151 { termp_fa_pre, NULL }, /* Fa */
152 { termp_fd_pre, termp_fd_post }, /* Fd */
153 { termp_fl_pre, NULL }, /* Fl */
154 { termp_fn_pre, NULL }, /* Fn */
155 { termp_ft_pre, NULL }, /* Ft */
156 { termp_bold_pre, NULL }, /* Ic */
157 { termp_in_pre, termp_in_post }, /* In */
158 { termp_li_pre, NULL }, /* Li */
159 { termp_nd_pre, NULL }, /* Nd */
160 { termp_nm_pre, termp_nm_post }, /* Nm */
161 { termp_quote_pre, termp_quote_post }, /* Op */
162 { termp_ft_pre, NULL }, /* Ot */
163 { termp_under_pre, NULL }, /* Pa */
164 { termp_rv_pre, NULL }, /* Rv */
165 { NULL, NULL }, /* St */
166 { termp_under_pre, NULL }, /* Va */
167 { termp_vt_pre, NULL }, /* Vt */
168 { termp_xr_pre, NULL }, /* Xr */
169 { termp__a_pre, termp____post }, /* %A */
170 { termp_under_pre, termp____post }, /* %B */
171 { NULL, termp____post }, /* %D */
172 { termp_under_pre, termp____post }, /* %I */
173 { termp_under_pre, termp____post }, /* %J */
174 { NULL, termp____post }, /* %N */
175 { NULL, termp____post }, /* %O */
176 { NULL, termp____post }, /* %P */
177 { NULL, termp____post }, /* %R */
178 { termp__t_pre, termp__t_post }, /* %T */
179 { NULL, termp____post }, /* %V */
180 { NULL, NULL }, /* Ac */
181 { termp_quote_pre, termp_quote_post }, /* Ao */
182 { termp_quote_pre, termp_quote_post }, /* Aq */
183 { NULL, NULL }, /* At */
184 { NULL, NULL }, /* Bc */
185 { termp_bf_pre, NULL }, /* Bf */
186 { termp_quote_pre, termp_quote_post }, /* Bo */
187 { termp_quote_pre, termp_quote_post }, /* Bq */
188 { termp_xx_pre, NULL }, /* Bsx */
189 { termp_bx_pre, NULL }, /* Bx */
190 { NULL, NULL }, /* Db */
191 { NULL, NULL }, /* Dc */
192 { termp_quote_pre, termp_quote_post }, /* Do */
193 { termp_quote_pre, termp_quote_post }, /* Dq */
194 { NULL, NULL }, /* Ec */ /* FIXME: no space */
195 { NULL, NULL }, /* Ef */
196 { termp_under_pre, NULL }, /* Em */
197 { termp_quote_pre, termp_quote_post }, /* Eo */
198 { termp_xx_pre, NULL }, /* Fx */
199 { termp_bold_pre, NULL }, /* Ms */
200 { NULL, NULL }, /* No */
201 { termp_ns_pre, NULL }, /* Ns */
202 { termp_xx_pre, NULL }, /* Nx */
203 { termp_xx_pre, NULL }, /* Ox */
204 { NULL, NULL }, /* Pc */
205 { NULL, termp_pf_post }, /* Pf */
206 { termp_quote_pre, termp_quote_post }, /* Po */
207 { termp_quote_pre, termp_quote_post }, /* Pq */
208 { NULL, NULL }, /* Qc */
209 { termp_quote_pre, termp_quote_post }, /* Ql */
210 { termp_quote_pre, termp_quote_post }, /* Qo */
211 { termp_quote_pre, termp_quote_post }, /* Qq */
212 { NULL, NULL }, /* Re */
213 { termp_rs_pre, NULL }, /* Rs */
214 { NULL, NULL }, /* Sc */
215 { termp_quote_pre, termp_quote_post }, /* So */
216 { termp_quote_pre, termp_quote_post }, /* Sq */
217 { termp_sm_pre, NULL }, /* Sm */
218 { termp_under_pre, NULL }, /* Sx */
219 { termp_bold_pre, NULL }, /* Sy */
220 { NULL, NULL }, /* Tn */
221 { termp_xx_pre, NULL }, /* Ux */
222 { NULL, NULL }, /* Xc */
223 { NULL, NULL }, /* Xo */
224 { termp_fo_pre, termp_fo_post }, /* Fo */
225 { NULL, NULL }, /* Fc */
226 { termp_quote_pre, termp_quote_post }, /* Oo */
227 { NULL, NULL }, /* Oc */
228 { termp_bk_pre, termp_bk_post }, /* Bk */
229 { NULL, NULL }, /* Ek */
230 { termp_bt_pre, NULL }, /* Bt */
231 { NULL, NULL }, /* Hf */
232 { termp_under_pre, NULL }, /* Fr */
233 { termp_ud_pre, NULL }, /* Ud */
234 { NULL, termp_lb_post }, /* Lb */
235 { termp_sp_pre, NULL }, /* Lp */
236 { termp_lk_pre, NULL }, /* Lk */
237 { termp_under_pre, NULL }, /* Mt */
238 { termp_quote_pre, termp_quote_post }, /* Brq */
239 { termp_quote_pre, termp_quote_post }, /* Bro */
240 { NULL, NULL }, /* Brc */
241 { NULL, termp____post }, /* %C */
242 { termp_es_pre, NULL }, /* Es */
243 { termp_quote_pre, termp_quote_post }, /* En */
244 { termp_xx_pre, NULL }, /* Dx */
245 { NULL, termp____post }, /* %Q */
246 { termp_sp_pre, NULL }, /* br */
247 { termp_sp_pre, NULL }, /* sp */
248 { NULL, termp____post }, /* %U */
249 { NULL, NULL }, /* Ta */
250 { termp_ll_pre, NULL }, /* ll */
251 };
252
253
254 void
255 terminal_mdoc(void *arg, const struct mdoc *mdoc)
256 {
257 const struct mdoc_node *n;
258 const struct mdoc_meta *meta;
259 struct termp *p;
260
261 p = (struct termp *)arg;
262
263 if (0 == p->defindent)
264 p->defindent = 5;
265
266 p->overstep = 0;
267 p->maxrmargin = p->defrmargin;
268 p->tabwidth = term_len(p, 5);
269
270 if (NULL == p->symtab)
271 p->symtab = mchars_alloc();
272
273 n = mdoc_node(mdoc);
274 meta = mdoc_meta(mdoc);
275
276 term_begin(p, print_mdoc_head, print_mdoc_foot, meta);
277
278 if (n->child) {
279 if (MDOC_Sh != n->child->tok)
280 term_vspace(p);
281 print_mdoc_nodelist(p, NULL, meta, n->child);
282 }
283
284 term_end(p);
285 }
286
287 static void
288 print_mdoc_nodelist(DECL_ARGS)
289 {
290
291 print_mdoc_node(p, pair, meta, n);
292 if (n->next)
293 print_mdoc_nodelist(p, pair, meta, n->next);
294 }
295
296 static void
297 print_mdoc_node(DECL_ARGS)
298 {
299 int chld;
300 struct termpair npair;
301 size_t offset, rmargin;
302
303 chld = 1;
304 offset = p->offset;
305 rmargin = p->rmargin;
306 n->prev_font = term_fontq(p);
307
308 memset(&npair, 0, sizeof(struct termpair));
309 npair.ppair = pair;
310
311 /*
312 * Keeps only work until the end of a line. If a keep was
313 * invoked in a prior line, revert it to PREKEEP.
314 */
315
316 if (TERMP_KEEP & p->flags) {
317 if (n->prev ? (n->prev->lastline != n->line) :
318 (n->parent && n->parent->line != n->line)) {
319 p->flags &= ~TERMP_KEEP;
320 p->flags |= TERMP_PREKEEP;
321 }
322 }
323
324 /*
325 * After the keep flags have been set up, we may now
326 * produce output. Note that some pre-handlers do so.
327 */
328
329 switch (n->type) {
330 case MDOC_TEXT:
331 if (' ' == *n->string && MDOC_LINE & n->flags)
332 term_newln(p);
333 if (MDOC_DELIMC & n->flags)
334 p->flags |= TERMP_NOSPACE;
335 term_word(p, n->string);
336 if (MDOC_DELIMO & n->flags)
337 p->flags |= TERMP_NOSPACE;
338 break;
339 case MDOC_EQN:
340 term_eqn(p, n->eqn);
341 break;
342 case MDOC_TBL:
343 term_tbl(p, n->span);
344 break;
345 default:
346 if (termacts[n->tok].pre && ENDBODY_NOT == n->end)
347 chld = (*termacts[n->tok].pre)
348 (p, &npair, meta, n);
349 break;
350 }
351
352 if (chld && n->child)
353 print_mdoc_nodelist(p, &npair, meta, n->child);
354
355 term_fontpopq(p,
356 (ENDBODY_NOT == n->end ? n : n->pending)->prev_font);
357
358 switch (n->type) {
359 case MDOC_TEXT:
360 break;
361 case MDOC_TBL:
362 break;
363 case MDOC_EQN:
364 break;
365 default:
366 if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags)
367 break;
368 (void)(*termacts[n->tok].post)(p, &npair, meta, n);
369
370 /*
371 * Explicit end tokens not only call the post
372 * handler, but also tell the respective block
373 * that it must not call the post handler again.
374 */
375 if (ENDBODY_NOT != n->end)
376 n->pending->flags |= MDOC_ENDED;
377
378 /*
379 * End of line terminating an implicit block
380 * while an explicit block is still open.
381 * Continue the explicit block without spacing.
382 */
383 if (ENDBODY_NOSPACE == n->end)
384 p->flags |= TERMP_NOSPACE;
385 break;
386 }
387
388 if (MDOC_EOS & n->flags)
389 p->flags |= TERMP_SENTENCE;
390
391 if (MDOC_ll != n->tok) {
392 p->offset = offset;
393 p->rmargin = rmargin;
394 }
395 }
396
397 static void
398 print_mdoc_foot(struct termp *p, const void *arg)
399 {
400 const struct mdoc_meta *meta;
401
402 meta = (const struct mdoc_meta *)arg;
403
404 term_fontrepl(p, TERMFONT_NONE);
405
406 /*
407 * Output the footer in new-groff style, that is, three columns
408 * with the middle being the manual date and flanking columns
409 * being the operating system:
410 *
411 * SYSTEM DATE SYSTEM
412 */
413
414 term_vspace(p);
415
416 p->offset = 0;
417 p->rmargin = (p->maxrmargin -
418 term_strlen(p, meta->date) + term_len(p, 1)) / 2;
419 p->trailspace = 1;
420 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
421
422 term_word(p, meta->os);
423 term_flushln(p);
424
425 p->offset = p->rmargin;
426 p->rmargin = p->maxrmargin - term_strlen(p, meta->os);
427 p->flags |= TERMP_NOSPACE;
428
429 term_word(p, meta->date);
430 term_flushln(p);
431
432 p->offset = p->rmargin;
433 p->rmargin = p->maxrmargin;
434 p->trailspace = 0;
435 p->flags &= ~TERMP_NOBREAK;
436 p->flags |= TERMP_NOSPACE;
437
438 term_word(p, meta->os);
439 term_flushln(p);
440
441 p->offset = 0;
442 p->rmargin = p->maxrmargin;
443 p->flags = 0;
444 }
445
446 static void
447 print_mdoc_head(struct termp *p, const void *arg)
448 {
449 const struct mdoc_meta *meta;
450 char *volume, *title;
451 size_t vollen, titlen;
452
453 meta = (const struct mdoc_meta *)arg;
454
455 /*
456 * The header is strange. It has three components, which are
457 * really two with the first duplicated. It goes like this:
458 *
459 * IDENTIFIER TITLE IDENTIFIER
460 *
461 * The IDENTIFIER is NAME(SECTION), which is the command-name
462 * (if given, or "unknown" if not) followed by the manual page
463 * section. These are given in `Dt'. The TITLE is a free-form
464 * string depending on the manual volume. If not specified, it
465 * switches on the manual section.
466 */
467
468 p->offset = 0;
469 p->rmargin = p->maxrmargin;
470
471 assert(meta->vol);
472 if (NULL == meta->arch)
473 volume = mandoc_strdup(meta->vol);
474 else
475 mandoc_asprintf(&volume, "%s (%s)",
476 meta->vol, meta->arch);
477 vollen = term_strlen(p, volume);
478
479 mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
480 titlen = term_strlen(p, title);
481
482 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
483 p->trailspace = 1;
484 p->offset = 0;
485 p->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
486 (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
487 p->maxrmargin - vollen;
488
489 term_word(p, title);
490 term_flushln(p);
491
492 p->flags |= TERMP_NOSPACE;
493 p->offset = p->rmargin;
494 p->rmargin = p->offset + vollen + titlen < p->maxrmargin ?
495 p->maxrmargin - titlen : p->maxrmargin;
496
497 term_word(p, volume);
498 term_flushln(p);
499
500 p->flags &= ~TERMP_NOBREAK;
501 p->trailspace = 0;
502 if (p->rmargin + titlen <= p->maxrmargin) {
503 p->flags |= TERMP_NOSPACE;
504 p->offset = p->rmargin;
505 p->rmargin = p->maxrmargin;
506 term_word(p, title);
507 term_flushln(p);
508 }
509
510 p->flags &= ~TERMP_NOSPACE;
511 p->offset = 0;
512 p->rmargin = p->maxrmargin;
513 free(title);
514 free(volume);
515 }
516
517 static size_t
518 a2height(const struct termp *p, const char *v)
519 {
520 struct roffsu su;
521
522
523 assert(v);
524 if ( ! a2roffsu(v, &su, SCALE_VS))
525 SCALE_VS_INIT(&su, atoi(v));
526
527 return(term_vspan(p, &su));
528 }
529
530 static size_t
531 a2width(const struct termp *p, const char *v)
532 {
533 struct roffsu su;
534
535 assert(v);
536 if ( ! a2roffsu(v, &su, SCALE_MAX))
537 SCALE_HS_INIT(&su, term_strlen(p, v));
538
539 return(term_hspan(p, &su));
540 }
541
542 static size_t
543 a2offs(const struct termp *p, const char *v)
544 {
545 struct roffsu su;
546
547 if ('\0' == *v)
548 return(0);
549 else if (0 == strcmp(v, "left"))
550 return(0);
551 else if (0 == strcmp(v, "indent"))
552 return(term_len(p, p->defindent + 1));
553 else if (0 == strcmp(v, "indent-two"))
554 return(term_len(p, (p->defindent + 1) * 2));
555 else if ( ! a2roffsu(v, &su, SCALE_MAX))
556 SCALE_HS_INIT(&su, term_strlen(p, v));
557
558 return(term_hspan(p, &su));
559 }
560
561 /*
562 * Determine how much space to print out before block elements of `It'
563 * (and thus `Bl') and `Bd'. And then go ahead and print that space,
564 * too.
565 */
566 static void
567 print_bvspace(struct termp *p,
568 const struct mdoc_node *bl,
569 const struct mdoc_node *n)
570 {
571 const struct mdoc_node *nn;
572
573 assert(n);
574
575 term_newln(p);
576
577 if (MDOC_Bd == bl->tok && bl->norm->Bd.comp)
578 return;
579 if (MDOC_Bl == bl->tok && bl->norm->Bl.comp)
580 return;
581
582 /* Do not vspace directly after Ss/Sh. */
583
584 for (nn = n; nn; nn = nn->parent) {
585 if (MDOC_BLOCK != nn->type)
586 continue;
587 if (MDOC_Ss == nn->tok)
588 return;
589 if (MDOC_Sh == nn->tok)
590 return;
591 if (NULL == nn->prev)
592 continue;
593 break;
594 }
595
596 /* A `-column' does not assert vspace within the list. */
597
598 if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type)
599 if (n->prev && MDOC_It == n->prev->tok)
600 return;
601
602 /* A `-diag' without body does not vspace. */
603
604 if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type)
605 if (n->prev && MDOC_It == n->prev->tok) {
606 assert(n->prev->body);
607 if (NULL == n->prev->body->child)
608 return;
609 }
610
611 term_vspace(p);
612 }
613
614
615 static int
616 termp_ll_pre(DECL_ARGS)
617 {
618
619 term_setwidth(p, n->nchild ? n->child->string : NULL);
620 return(0);
621 }
622
623 static int
624 termp_it_pre(DECL_ARGS)
625 {
626 const struct mdoc_node *bl, *nn;
627 char buf[24];
628 int i;
629 size_t width, offset, ncols, dcol;
630 enum mdoc_list type;
631
632 if (MDOC_BLOCK == n->type) {
633 print_bvspace(p, n->parent->parent, n);
634 return(1);
635 }
636
637 bl = n->parent->parent->parent;
638 type = bl->norm->Bl.type;
639
640 /*
641 * First calculate width and offset. This is pretty easy unless
642 * we're a -column list, in which case all prior columns must
643 * be accounted for.
644 */
645
646 width = offset = 0;
647
648 if (bl->norm->Bl.offs)
649 offset = a2offs(p, bl->norm->Bl.offs);
650
651 switch (type) {
652 case LIST_column:
653 if (MDOC_HEAD == n->type)
654 break;
655
656 /*
657 * Imitate groff's column handling:
658 * - For each earlier column, add its width.
659 * - For less than 5 columns, add four more blanks per
660 * column.
661 * - For exactly 5 columns, add three more blank per
662 * column.
663 * - For more than 5 columns, add only one column.
664 */
665 ncols = bl->norm->Bl.ncols;
666 dcol = ncols < 5 ? term_len(p, 4) :
667 ncols == 5 ? term_len(p, 3) : term_len(p, 1);
668
669 /*
670 * Calculate the offset by applying all prior MDOC_BODY,
671 * so we stop at the MDOC_HEAD (NULL == nn->prev).
672 */
673
674 for (i = 0, nn = n->prev;
675 nn->prev && i < (int)ncols;
676 nn = nn->prev, i++)
677 offset += dcol + a2width(p,
678 bl->norm->Bl.cols[i]);
679
680 /*
681 * When exceeding the declared number of columns, leave
682 * the remaining widths at 0. This will later be
683 * adjusted to the default width of 10, or, for the last
684 * column, stretched to the right margin.
685 */
686 if (i >= (int)ncols)
687 break;
688
689 /*
690 * Use the declared column widths, extended as explained
691 * in the preceding paragraph.
692 */
693 width = a2width(p, bl->norm->Bl.cols[i]) + dcol;
694 break;
695 default:
696 if (NULL == bl->norm->Bl.width)
697 break;
698
699 /*
700 * Note: buffer the width by 2, which is groff's magic
701 * number for buffering single arguments. See the above
702 * handling for column for how this changes.
703 */
704 assert(bl->norm->Bl.width);
705 width = a2width(p, bl->norm->Bl.width) + term_len(p, 2);
706 break;
707 }
708
709 /*
710 * List-type can override the width in the case of fixed-head
711 * values (bullet, dash/hyphen, enum). Tags need a non-zero
712 * offset.
713 */
714
715 switch (type) {
716 case LIST_bullet:
717 /* FALLTHROUGH */
718 case LIST_dash:
719 /* FALLTHROUGH */
720 case LIST_hyphen:
721 /* FALLTHROUGH */
722 case LIST_enum:
723 if (width < term_len(p, 2))
724 width = term_len(p, 2);
725 break;
726 case LIST_hang:
727 if (0 == width)
728 width = term_len(p, 8);
729 break;
730 case LIST_column:
731 /* FALLTHROUGH */
732 case LIST_tag:
733 if (0 == width)
734 width = term_len(p, 10);
735 break;
736 default:
737 break;
738 }
739
740 /*
741 * Whitespace control. Inset bodies need an initial space,
742 * while diagonal bodies need two.
743 */
744
745 p->flags |= TERMP_NOSPACE;
746
747 switch (type) {
748 case LIST_diag:
749 if (MDOC_BODY == n->type)
750 term_word(p, "\\ \\ ");
751 break;
752 case LIST_inset:
753 if (MDOC_BODY == n->type && n->parent->head->nchild)
754 term_word(p, "\\ ");
755 break;
756 default:
757 break;
758 }
759
760 p->flags |= TERMP_NOSPACE;
761
762 switch (type) {
763 case LIST_diag:
764 if (MDOC_HEAD == n->type)
765 term_fontpush(p, TERMFONT_BOLD);
766 break;
767 default:
768 break;
769 }
770
771 /*
772 * Pad and break control. This is the tricky part. These flags
773 * are documented in term_flushln() in term.c. Note that we're
774 * going to unset all of these flags in termp_it_post() when we
775 * exit.
776 */
777
778 switch (type) {
779 case LIST_enum:
780 /*
781 * Weird special case.
782 * Very narrow enum lists actually hang.
783 */
784 if (width == term_len(p, 2))
785 p->flags |= TERMP_HANG;
786 /* FALLTHROUGH */
787 case LIST_bullet:
788 /* FALLTHROUGH */
789 case LIST_dash:
790 /* FALLTHROUGH */
791 case LIST_hyphen:
792 if (MDOC_HEAD != n->type)
793 break;
794 p->flags |= TERMP_NOBREAK;
795 p->trailspace = 1;
796 break;
797 case LIST_hang:
798 if (MDOC_HEAD != n->type)
799 break;
800
801 /*
802 * This is ugly. If `-hang' is specified and the body
803 * is a `Bl' or `Bd', then we want basically to nullify
804 * the "overstep" effect in term_flushln() and treat
805 * this as a `-ohang' list instead.
806 */
807 if (n->next->child &&
808 (MDOC_Bl == n->next->child->tok ||
809 MDOC_Bd == n->next->child->tok))
810 break;
811
812 p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
813 p->trailspace = 1;
814 break;
815 case LIST_tag:
816 if (MDOC_HEAD != n->type)
817 break;
818
819 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
820 p->trailspace = 2;
821
822 if (NULL == n->next || NULL == n->next->child)
823 p->flags |= TERMP_DANGLE;
824 break;
825 case LIST_column:
826 if (MDOC_HEAD == n->type)
827 break;
828
829 if (NULL == n->next) {
830 p->flags &= ~TERMP_NOBREAK;
831 p->trailspace = 0;
832 } else {
833 p->flags |= TERMP_NOBREAK;
834 p->trailspace = 1;
835 }
836
837 break;
838 case LIST_diag:
839 if (MDOC_HEAD != n->type)
840 break;
841 p->flags |= TERMP_NOBREAK | TERMP_BRIND;
842 p->trailspace = 1;
843 break;
844 default:
845 break;
846 }
847
848 /*
849 * Margin control. Set-head-width lists have their right
850 * margins shortened. The body for these lists has the offset
851 * necessarily lengthened. Everybody gets the offset.
852 */
853
854 p->offset += offset;
855
856 switch (type) {
857 case LIST_hang:
858 /*
859 * Same stipulation as above, regarding `-hang'. We
860 * don't want to recalculate rmargin and offsets when
861 * using `Bd' or `Bl' within `-hang' overstep lists.
862 */
863 if (MDOC_HEAD == n->type && n->next->child &&
864 (MDOC_Bl == n->next->child->tok ||
865 MDOC_Bd == n->next->child->tok))
866 break;
867 /* FALLTHROUGH */
868 case LIST_bullet:
869 /* FALLTHROUGH */
870 case LIST_dash:
871 /* FALLTHROUGH */
872 case LIST_enum:
873 /* FALLTHROUGH */
874 case LIST_hyphen:
875 /* FALLTHROUGH */
876 case LIST_tag:
877 assert(width);
878 if (MDOC_HEAD == n->type)
879 p->rmargin = p->offset + width;
880 else {
881 p->offset += width;
882 if (p->rmargin < p->offset)
883 p->rmargin = p->offset;
884 }
885 break;
886 case LIST_column:
887 assert(width);
888 p->rmargin = p->offset + width;
889 /*
890 * XXX - this behaviour is not documented: the
891 * right-most column is filled to the right margin.
892 */
893 if (MDOC_HEAD == n->type)
894 break;
895 if (NULL == n->next && p->rmargin < p->maxrmargin)
896 p->rmargin = p->maxrmargin;
897 break;
898 default:
899 break;
900 }
901
902 /*
903 * The dash, hyphen, bullet and enum lists all have a special
904 * HEAD character (temporarily bold, in some cases).
905 */
906
907 if (MDOC_HEAD == n->type)
908 switch (type) {
909 case LIST_bullet:
910 term_fontpush(p, TERMFONT_BOLD);
911 term_word(p, "\\[bu]");
912 term_fontpop(p);
913 break;
914 case LIST_dash:
915 /* FALLTHROUGH */
916 case LIST_hyphen:
917 term_fontpush(p, TERMFONT_BOLD);
918 term_word(p, "\\(hy");
919 term_fontpop(p);
920 break;
921 case LIST_enum:
922 (pair->ppair->ppair->count)++;
923 (void)snprintf(buf, sizeof(buf), "%d.",
924 pair->ppair->ppair->count);
925 term_word(p, buf);
926 break;
927 default:
928 break;
929 }
930
931 /*
932 * If we're not going to process our children, indicate so here.
933 */
934
935 switch (type) {
936 case LIST_bullet:
937 /* FALLTHROUGH */
938 case LIST_item:
939 /* FALLTHROUGH */
940 case LIST_dash:
941 /* FALLTHROUGH */
942 case LIST_hyphen:
943 /* FALLTHROUGH */
944 case LIST_enum:
945 if (MDOC_HEAD == n->type)
946 return(0);
947 break;
948 case LIST_column:
949 if (MDOC_HEAD == n->type)
950 return(0);
951 break;
952 default:
953 break;
954 }
955
956 return(1);
957 }
958
959 static void
960 termp_it_post(DECL_ARGS)
961 {
962 enum mdoc_list type;
963
964 if (MDOC_BLOCK == n->type)
965 return;
966
967 type = n->parent->parent->parent->norm->Bl.type;
968
969 switch (type) {
970 case LIST_item:
971 /* FALLTHROUGH */
972 case LIST_diag:
973 /* FALLTHROUGH */
974 case LIST_inset:
975 if (MDOC_BODY == n->type)
976 term_newln(p);
977 break;
978 case LIST_column:
979 if (MDOC_BODY == n->type)
980 term_flushln(p);
981 break;
982 default:
983 term_newln(p);
984 break;
985 }
986
987 /*
988 * Now that our output is flushed, we can reset our tags. Since
989 * only `It' sets these flags, we're free to assume that nobody
990 * has munged them in the meanwhile.
991 */
992
993 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
994 TERMP_DANGLE | TERMP_HANG);
995 p->trailspace = 0;
996 }
997
998 static int
999 termp_nm_pre(DECL_ARGS)
1000 {
1001
1002 if (MDOC_BLOCK == n->type) {
1003 p->flags |= TERMP_PREKEEP;
1004 return(1);
1005 }
1006
1007 if (MDOC_BODY == n->type) {
1008 if (NULL == n->child)
1009 return(0);
1010 p->flags |= TERMP_NOSPACE;
1011 p->offset += term_len(p, 1) +
1012 (NULL == n->prev->child ?
1013 term_strlen(p, meta->name) :
1014 MDOC_TEXT == n->prev->child->type ?
1015 term_strlen(p, n->prev->child->string) :
1016 term_len(p, 5));
1017 if (p->rmargin < p->offset)
1018 p->rmargin = p->offset;
1019 return(1);
1020 }
1021
1022 if (NULL == n->child && NULL == meta->name)
1023 return(0);
1024
1025 if (MDOC_HEAD == n->type)
1026 synopsis_pre(p, n->parent);
1027
1028 if (MDOC_HEAD == n->type && n->next->child) {
1029 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK | TERMP_BRIND;
1030 p->trailspace = 1;
1031 p->rmargin = p->offset + term_len(p, 1);
1032 if (NULL == n->child) {
1033 p->rmargin += term_strlen(p, meta->name);
1034 } else if (MDOC_TEXT == n->child->type) {
1035 p->rmargin += term_strlen(p, n->child->string);
1036 if (n->child->next)
1037 p->flags |= TERMP_HANG;
1038 } else {
1039 p->rmargin += term_len(p, 5);
1040 p->flags |= TERMP_HANG;
1041 }
1042 }
1043
1044 term_fontpush(p, TERMFONT_BOLD);
1045 if (NULL == n->child)
1046 term_word(p, meta->name);
1047 return(1);
1048 }
1049
1050 static void
1051 termp_nm_post(DECL_ARGS)
1052 {
1053
1054 if (MDOC_BLOCK == n->type) {
1055 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
1056 } else if (MDOC_HEAD == n->type && n->next->child) {
1057 term_flushln(p);
1058 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
1059 p->trailspace = 0;
1060 } else if (MDOC_BODY == n->type && n->child)
1061 term_flushln(p);
1062 }
1063
1064 static int
1065 termp_fl_pre(DECL_ARGS)
1066 {
1067
1068 term_fontpush(p, TERMFONT_BOLD);
1069 term_word(p, "\\-");
1070
1071 if (n->child)
1072 p->flags |= TERMP_NOSPACE;
1073 else if (n->next && n->next->line == n->line)
1074 p->flags |= TERMP_NOSPACE;
1075
1076 return(1);
1077 }
1078
1079 static int
1080 termp__a_pre(DECL_ARGS)
1081 {
1082
1083 if (n->prev && MDOC__A == n->prev->tok)
1084 if (NULL == n->next || MDOC__A != n->next->tok)
1085 term_word(p, "and");
1086
1087 return(1);
1088 }
1089
1090 static int
1091 termp_an_pre(DECL_ARGS)
1092 {
1093
1094 if (NULL == n->child)
1095 return(1);
1096
1097 /*
1098 * If not in the AUTHORS section, `An -split' will cause
1099 * newlines to occur before the author name. If in the AUTHORS
1100 * section, by default, the first `An' invocation is nosplit,
1101 * then all subsequent ones, regardless of whether interspersed
1102 * with other macros/text, are split. -split, in this case,
1103 * will override the condition of the implied first -nosplit.
1104 */
1105
1106 if (n->sec == SEC_AUTHORS) {
1107 if ( ! (TERMP_ANPREC & p->flags)) {
1108 if (TERMP_SPLIT & p->flags)
1109 term_newln(p);
1110 return(1);
1111 }
1112 if (TERMP_NOSPLIT & p->flags)
1113 return(1);
1114 term_newln(p);
1115 return(1);
1116 }
1117
1118 if (TERMP_SPLIT & p->flags)
1119 term_newln(p);
1120
1121 return(1);
1122 }
1123
1124 static void
1125 termp_an_post(DECL_ARGS)
1126 {
1127
1128 if (n->child) {
1129 if (SEC_AUTHORS == n->sec)
1130 p->flags |= TERMP_ANPREC;
1131 return;
1132 }
1133
1134 if (AUTH_split == n->norm->An.auth) {
1135 p->flags &= ~TERMP_NOSPLIT;
1136 p->flags |= TERMP_SPLIT;
1137 } else if (AUTH_nosplit == n->norm->An.auth) {
1138 p->flags &= ~TERMP_SPLIT;
1139 p->flags |= TERMP_NOSPLIT;
1140 }
1141
1142 }
1143
1144 static int
1145 termp_ns_pre(DECL_ARGS)
1146 {
1147
1148 if ( ! (MDOC_LINE & n->flags))
1149 p->flags |= TERMP_NOSPACE;
1150 return(1);
1151 }
1152
1153 static int
1154 termp_rs_pre(DECL_ARGS)
1155 {
1156
1157 if (SEC_SEE_ALSO != n->sec)
1158 return(1);
1159 if (MDOC_BLOCK == n->type && n->prev)
1160 term_vspace(p);
1161 return(1);
1162 }
1163
1164 static int
1165 termp_rv_pre(DECL_ARGS)
1166 {
1167 int nchild;
1168
1169 term_newln(p);
1170 term_word(p, "The");
1171
1172 nchild = n->nchild;
1173 for (n = n->child; n; n = n->next) {
1174 term_fontpush(p, TERMFONT_BOLD);
1175 term_word(p, n->string);
1176 term_fontpop(p);
1177
1178 p->flags |= TERMP_NOSPACE;
1179 term_word(p, "()");
1180
1181 if (nchild > 2 && n->next) {
1182 p->flags |= TERMP_NOSPACE;
1183 term_word(p, ",");
1184 }
1185
1186 if (n->next && NULL == n->next->next)
1187 term_word(p, "and");
1188 }
1189
1190 if (nchild > 1)
1191 term_word(p, "functions return");
1192 else
1193 term_word(p, "function returns");
1194
1195 term_word(p, "the value 0 if successful; otherwise the "
1196 "value -1 is returned and the global variable");
1197
1198 term_fontpush(p, TERMFONT_UNDER);
1199 term_word(p, "errno");
1200 term_fontpop(p);
1201
1202 term_word(p, "is set to indicate the error.");
1203 p->flags |= TERMP_SENTENCE;
1204
1205 return(0);
1206 }
1207
1208 static int
1209 termp_ex_pre(DECL_ARGS)
1210 {
1211 int nchild;
1212
1213 term_newln(p);
1214 term_word(p, "The");
1215
1216 nchild = n->nchild;
1217 for (n = n->child; n; n = n->next) {
1218 term_fontpush(p, TERMFONT_BOLD);
1219 term_word(p, n->string);
1220 term_fontpop(p);
1221
1222 if (nchild > 2 && n->next) {
1223 p->flags |= TERMP_NOSPACE;
1224 term_word(p, ",");
1225 }
1226
1227 if (n->next && NULL == n->next->next)
1228 term_word(p, "and");
1229 }
1230
1231 if (nchild > 1)
1232 term_word(p, "utilities exit");
1233 else
1234 term_word(p, "utility exits");
1235
1236 term_word(p, "0 on success, and >0 if an error occurs.");
1237
1238 p->flags |= TERMP_SENTENCE;
1239 return(0);
1240 }
1241
1242 static int
1243 termp_nd_pre(DECL_ARGS)
1244 {
1245
1246 if (MDOC_BODY != n->type)
1247 return(1);
1248
1249 #if defined(__OpenBSD__) || defined(__linux__)
1250 term_word(p, "\\(en");
1251 #else
1252 term_word(p, "\\(em");
1253 #endif
1254 return(1);
1255 }
1256
1257 static int
1258 termp_bl_pre(DECL_ARGS)
1259 {
1260
1261 return(MDOC_HEAD != n->type);
1262 }
1263
1264 static void
1265 termp_bl_post(DECL_ARGS)
1266 {
1267
1268 if (MDOC_BLOCK == n->type)
1269 term_newln(p);
1270 }
1271
1272 static int
1273 termp_xr_pre(DECL_ARGS)
1274 {
1275
1276 if (NULL == (n = n->child))
1277 return(0);
1278
1279 assert(MDOC_TEXT == n->type);
1280 term_word(p, n->string);
1281
1282 if (NULL == (n = n->next))
1283 return(0);
1284
1285 p->flags |= TERMP_NOSPACE;
1286 term_word(p, "(");
1287 p->flags |= TERMP_NOSPACE;
1288
1289 assert(MDOC_TEXT == n->type);
1290 term_word(p, n->string);
1291
1292 p->flags |= TERMP_NOSPACE;
1293 term_word(p, ")");
1294
1295 return(0);
1296 }
1297
1298 /*
1299 * This decides how to assert whitespace before any of the SYNOPSIS set
1300 * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
1301 * macro combos).
1302 */
1303 static void
1304 synopsis_pre(struct termp *p, const struct mdoc_node *n)
1305 {
1306 /*
1307 * Obviously, if we're not in a SYNOPSIS or no prior macros
1308 * exist, do nothing.
1309 */
1310 if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
1311 return;
1312
1313 /*
1314 * If we're the second in a pair of like elements, emit our
1315 * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which
1316 * case we soldier on.
1317 */
1318 if (n->prev->tok == n->tok &&
1319 MDOC_Ft != n->tok &&
1320 MDOC_Fo != n->tok &&
1321 MDOC_Fn != n->tok) {
1322 term_newln(p);
1323 return;
1324 }
1325
1326 /*
1327 * If we're one of the SYNOPSIS set and non-like pair-wise after
1328 * another (or Fn/Fo, which we've let slip through) then assert
1329 * vertical space, else only newline and move on.
1330 */
1331 switch (n->prev->tok) {
1332 case MDOC_Fd:
1333 /* FALLTHROUGH */
1334 case MDOC_Fn:
1335 /* FALLTHROUGH */
1336 case MDOC_Fo:
1337 /* FALLTHROUGH */
1338 case MDOC_In:
1339 /* FALLTHROUGH */
1340 case MDOC_Vt:
1341 term_vspace(p);
1342 break;
1343 case MDOC_Ft:
1344 if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
1345 term_vspace(p);
1346 break;
1347 }
1348 /* FALLTHROUGH */
1349 default:
1350 term_newln(p);
1351 break;
1352 }
1353 }
1354
1355 static int
1356 termp_vt_pre(DECL_ARGS)
1357 {
1358
1359 if (MDOC_ELEM == n->type) {
1360 synopsis_pre(p, n);
1361 return(termp_under_pre(p, pair, meta, n));
1362 } else if (MDOC_BLOCK == n->type) {
1363 synopsis_pre(p, n);
1364 return(1);
1365 } else if (MDOC_HEAD == n->type)
1366 return(0);
1367
1368 return(termp_under_pre(p, pair, meta, n));
1369 }
1370
1371 static int
1372 termp_bold_pre(DECL_ARGS)
1373 {
1374
1375 term_fontpush(p, TERMFONT_BOLD);
1376 return(1);
1377 }
1378
1379 static int
1380 termp_fd_pre(DECL_ARGS)
1381 {
1382
1383 synopsis_pre(p, n);
1384 return(termp_bold_pre(p, pair, meta, n));
1385 }
1386
1387 static void
1388 termp_fd_post(DECL_ARGS)
1389 {
1390
1391 term_newln(p);
1392 }
1393
1394 static int
1395 termp_sh_pre(DECL_ARGS)
1396 {
1397
1398 /* No vspace between consecutive `Sh' calls. */
1399
1400 switch (n->type) {
1401 case MDOC_BLOCK:
1402 if (n->prev && MDOC_Sh == n->prev->tok)
1403 if (NULL == n->prev->body->child)
1404 break;
1405 term_vspace(p);
1406 break;
1407 case MDOC_HEAD:
1408 term_fontpush(p, TERMFONT_BOLD);
1409 break;
1410 case MDOC_BODY:
1411 p->offset = term_len(p, p->defindent);
1412 if (SEC_AUTHORS == n->sec)
1413 p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
1414 break;
1415 default:
1416 break;
1417 }
1418 return(1);
1419 }
1420
1421 static void
1422 termp_sh_post(DECL_ARGS)
1423 {
1424
1425 switch (n->type) {
1426 case MDOC_HEAD:
1427 term_newln(p);
1428 break;
1429 case MDOC_BODY:
1430 term_newln(p);
1431 p->offset = 0;
1432 break;
1433 default:
1434 break;
1435 }
1436 }
1437
1438 static int
1439 termp_bt_pre(DECL_ARGS)
1440 {
1441
1442 term_word(p, "is currently in beta test.");
1443 p->flags |= TERMP_SENTENCE;
1444 return(0);
1445 }
1446
1447 static void
1448 termp_lb_post(DECL_ARGS)
1449 {
1450
1451 if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags)
1452 term_newln(p);
1453 }
1454
1455 static int
1456 termp_ud_pre(DECL_ARGS)
1457 {
1458
1459 term_word(p, "currently under development.");
1460 p->flags |= TERMP_SENTENCE;
1461 return(0);
1462 }
1463
1464 static int
1465 termp_d1_pre(DECL_ARGS)
1466 {
1467
1468 if (MDOC_BLOCK != n->type)
1469 return(1);
1470 term_newln(p);
1471 p->offset += term_len(p, p->defindent + 1);
1472 return(1);
1473 }
1474
1475 static int
1476 termp_ft_pre(DECL_ARGS)
1477 {
1478
1479 /* NB: MDOC_LINE does not effect this! */
1480 synopsis_pre(p, n);
1481 term_fontpush(p, TERMFONT_UNDER);
1482 return(1);
1483 }
1484
1485 static int
1486 termp_fn_pre(DECL_ARGS)
1487 {
1488 size_t rmargin = 0;
1489 int pretty;
1490
1491 pretty = MDOC_SYNPRETTY & n->flags;
1492
1493 synopsis_pre(p, n);
1494
1495 if (NULL == (n = n->child))
1496 return(0);
1497
1498 if (pretty) {
1499 rmargin = p->rmargin;
1500 p->rmargin = p->offset + term_len(p, 4);
1501 p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
1502 }
1503
1504 assert(MDOC_TEXT == n->type);
1505 term_fontpush(p, TERMFONT_BOLD);
1506 term_word(p, n->string);
1507 term_fontpop(p);
1508
1509 if (pretty) {
1510 term_flushln(p);
1511 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
1512 p->offset = p->rmargin;
1513 p->rmargin = rmargin;
1514 }
1515
1516 p->flags |= TERMP_NOSPACE;
1517 term_word(p, "(");
1518 p->flags |= TERMP_NOSPACE;
1519
1520 for (n = n->next; n; n = n->next) {
1521 assert(MDOC_TEXT == n->type);
1522 term_fontpush(p, TERMFONT_UNDER);
1523 if (pretty)
1524 p->flags |= TERMP_NBRWORD;
1525 term_word(p, n->string);
1526 term_fontpop(p);
1527
1528 if (n->next) {
1529 p->flags |= TERMP_NOSPACE;
1530 term_word(p, ",");
1531 }
1532 }
1533
1534 p->flags |= TERMP_NOSPACE;
1535 term_word(p, ")");
1536
1537 if (pretty) {
1538 p->flags |= TERMP_NOSPACE;
1539 term_word(p, ";");
1540 term_flushln(p);
1541 }
1542
1543 return(0);
1544 }
1545
1546 static int
1547 termp_fa_pre(DECL_ARGS)
1548 {
1549 const struct mdoc_node *nn;
1550
1551 if (n->parent->tok != MDOC_Fo) {
1552 term_fontpush(p, TERMFONT_UNDER);
1553 return(1);
1554 }
1555
1556 for (nn = n->child; nn; nn = nn->next) {
1557 term_fontpush(p, TERMFONT_UNDER);
1558 p->flags |= TERMP_NBRWORD;
1559 term_word(p, nn->string);
1560 term_fontpop(p);
1561
1562 if (nn->next || (n->next && n->next->tok == MDOC_Fa)) {
1563 p->flags |= TERMP_NOSPACE;
1564 term_word(p, ",");
1565 }
1566 }
1567
1568 return(0);
1569 }
1570
1571 static int
1572 termp_bd_pre(DECL_ARGS)
1573 {
1574 size_t tabwidth, lm, len, rm, rmax;
1575 struct mdoc_node *nn;
1576
1577 if (MDOC_BLOCK == n->type) {
1578 print_bvspace(p, n, n);
1579 return(1);
1580 } else if (MDOC_HEAD == n->type)
1581 return(0);
1582
1583 if (n->norm->Bd.offs)
1584 p->offset += a2offs(p, n->norm->Bd.offs);
1585
1586 /*
1587 * If -ragged or -filled are specified, the block does nothing
1588 * but change the indentation. If -unfilled or -literal are
1589 * specified, text is printed exactly as entered in the display:
1590 * for macro lines, a newline is appended to the line. Blank
1591 * lines are allowed.
1592 */
1593
1594 if (DISP_literal != n->norm->Bd.type &&
1595 DISP_unfilled != n->norm->Bd.type &&
1596 DISP_centered != n->norm->Bd.type)
1597 return(1);
1598
1599 tabwidth = p->tabwidth;
1600 if (DISP_literal == n->norm->Bd.type)
1601 p->tabwidth = term_len(p, 8);
1602
1603 lm = p->offset;
1604 rm = p->rmargin;
1605 rmax = p->maxrmargin;
1606 p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1607
1608 for (nn = n->child; nn; nn = nn->next) {
1609 if (DISP_centered == n->norm->Bd.type) {
1610 if (MDOC_TEXT == nn->type) {
1611 len = term_strlen(p, nn->string);
1612 p->offset = len >= rm ? 0 :
1613 lm + len >= rm ? rm - len :
1614 (lm + rm - len) / 2;
1615 } else
1616 p->offset = lm;
1617 }
1618 print_mdoc_node(p, pair, meta, nn);
1619 /*
1620 * If the printed node flushes its own line, then we
1621 * needn't do it here as well. This is hacky, but the
1622 * notion of selective eoln whitespace is pretty dumb
1623 * anyway, so don't sweat it.
1624 */
1625 switch (nn->tok) {
1626 case MDOC_Sm:
1627 /* FALLTHROUGH */
1628 case MDOC_br:
1629 /* FALLTHROUGH */
1630 case MDOC_sp:
1631 /* FALLTHROUGH */
1632 case MDOC_Bl:
1633 /* FALLTHROUGH */
1634 case MDOC_D1:
1635 /* FALLTHROUGH */
1636 case MDOC_Dl:
1637 /* FALLTHROUGH */
1638 case MDOC_Lp:
1639 /* FALLTHROUGH */
1640 case MDOC_Pp:
1641 continue;
1642 default:
1643 break;
1644 }
1645 if (nn->next && nn->next->line == nn->line)
1646 continue;
1647 term_flushln(p);
1648 p->flags |= TERMP_NOSPACE;
1649 }
1650
1651 p->tabwidth = tabwidth;
1652 p->rmargin = rm;
1653 p->maxrmargin = rmax;
1654 return(0);
1655 }
1656
1657 static void
1658 termp_bd_post(DECL_ARGS)
1659 {
1660 size_t rm, rmax;
1661
1662 if (MDOC_BODY != n->type)
1663 return;
1664
1665 rm = p->rmargin;
1666 rmax = p->maxrmargin;
1667
1668 if (DISP_literal == n->norm->Bd.type ||
1669 DISP_unfilled == n->norm->Bd.type)
1670 p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1671
1672 p->flags |= TERMP_NOSPACE;
1673 term_newln(p);
1674
1675 p->rmargin = rm;
1676 p->maxrmargin = rmax;
1677 }
1678
1679 static int
1680 termp_bx_pre(DECL_ARGS)
1681 {
1682
1683 if (NULL != (n = n->child)) {
1684 term_word(p, n->string);
1685 p->flags |= TERMP_NOSPACE;
1686 term_word(p, "BSD");
1687 } else {
1688 term_word(p, "BSD");
1689 return(0);
1690 }
1691
1692 if (NULL != (n = n->next)) {
1693 p->flags |= TERMP_NOSPACE;
1694 term_word(p, "-");
1695 p->flags |= TERMP_NOSPACE;
1696 term_word(p, n->string);
1697 }
1698
1699 return(0);
1700 }
1701
1702 static int
1703 termp_xx_pre(DECL_ARGS)
1704 {
1705 const char *pp;
1706 int flags;
1707
1708 pp = NULL;
1709 switch (n->tok) {
1710 case MDOC_Bsx:
1711 pp = "BSD/OS";
1712 break;
1713 case MDOC_Dx:
1714 pp = "DragonFly";
1715 break;
1716 case MDOC_Fx:
1717 pp = "FreeBSD";
1718 break;
1719 case MDOC_Nx:
1720 pp = "NetBSD";
1721 break;
1722 case MDOC_Ox:
1723 pp = "OpenBSD";
1724 break;
1725 case MDOC_Ux:
1726 pp = "UNIX";
1727 break;
1728 default:
1729 abort();
1730 /* NOTREACHED */
1731 }
1732
1733 term_word(p, pp);
1734 if (n->child) {
1735 flags = p->flags;
1736 p->flags |= TERMP_KEEP;
1737 term_word(p, n->child->string);
1738 p->flags = flags;
1739 }
1740 return(0);
1741 }
1742
1743 static void
1744 termp_pf_post(DECL_ARGS)
1745 {
1746
1747 p->flags |= TERMP_NOSPACE;
1748 }
1749
1750 static int
1751 termp_ss_pre(DECL_ARGS)
1752 {
1753
1754 switch (n->type) {
1755 case MDOC_BLOCK:
1756 term_newln(p);
1757 if (n->prev)
1758 term_vspace(p);
1759 break;
1760 case MDOC_HEAD:
1761 term_fontpush(p, TERMFONT_BOLD);
1762 p->offset = term_len(p, (p->defindent+1)/2);
1763 break;
1764 default:
1765 break;
1766 }
1767
1768 return(1);
1769 }
1770
1771 static void
1772 termp_ss_post(DECL_ARGS)
1773 {
1774
1775 if (MDOC_HEAD == n->type)
1776 term_newln(p);
1777 }
1778
1779 static int
1780 termp_cd_pre(DECL_ARGS)
1781 {
1782
1783 synopsis_pre(p, n);
1784 term_fontpush(p, TERMFONT_BOLD);
1785 return(1);
1786 }
1787
1788 static int
1789 termp_in_pre(DECL_ARGS)
1790 {
1791
1792 synopsis_pre(p, n);
1793
1794 if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) {
1795 term_fontpush(p, TERMFONT_BOLD);
1796 term_word(p, "#include");
1797 term_word(p, "<");
1798 } else {
1799 term_word(p, "<");
1800 term_fontpush(p, TERMFONT_UNDER);
1801 }
1802
1803 p->flags |= TERMP_NOSPACE;
1804 return(1);
1805 }
1806
1807 static void
1808 termp_in_post(DECL_ARGS)
1809 {
1810
1811 if (MDOC_SYNPRETTY & n->flags)
1812 term_fontpush(p, TERMFONT_BOLD);
1813
1814 p->flags |= TERMP_NOSPACE;
1815 term_word(p, ">");
1816
1817 if (MDOC_SYNPRETTY & n->flags)
1818 term_fontpop(p);
1819 }
1820
1821 static int
1822 termp_sp_pre(DECL_ARGS)
1823 {
1824 size_t i, len;
1825
1826 switch (n->tok) {
1827 case MDOC_sp:
1828 len = n->child ? a2height(p, n->child->string) : 1;
1829 break;
1830 case MDOC_br:
1831 len = 0;
1832 break;
1833 default:
1834 len = 1;
1835 break;
1836 }
1837
1838 if (0 == len)
1839 term_newln(p);
1840 for (i = 0; i < len; i++)
1841 term_vspace(p);
1842
1843 return(0);
1844 }
1845
1846 static int
1847 termp_es_pre(DECL_ARGS)
1848 {
1849
1850 return(0);
1851 }
1852
1853 static int
1854 termp_quote_pre(DECL_ARGS)
1855 {
1856
1857 if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
1858 return(1);
1859
1860 switch (n->tok) {
1861 case MDOC_Ao:
1862 /* FALLTHROUGH */
1863 case MDOC_Aq:
1864 term_word(p, "<");
1865 break;
1866 case MDOC_Bro:
1867 /* FALLTHROUGH */
1868 case MDOC_Brq:
1869 term_word(p, "{");
1870 break;
1871 case MDOC_Oo:
1872 /* FALLTHROUGH */
1873 case MDOC_Op:
1874 /* FALLTHROUGH */
1875 case MDOC_Bo:
1876 /* FALLTHROUGH */
1877 case MDOC_Bq:
1878 term_word(p, "[");
1879 break;
1880 case MDOC_Do:
1881 /* FALLTHROUGH */
1882 case MDOC_Dq:
1883 term_word(p, "\\(lq");
1884 break;
1885 case MDOC_En:
1886 if (NULL == n->norm->Es ||
1887 NULL == n->norm->Es->child)
1888 return(1);
1889 term_word(p, n->norm->Es->child->string);
1890 break;
1891 case MDOC_Eo:
1892 break;
1893 case MDOC_Po:
1894 /* FALLTHROUGH */
1895 case MDOC_Pq:
1896 term_word(p, "(");
1897 break;
1898 case MDOC__T:
1899 /* FALLTHROUGH */
1900 case MDOC_Qo:
1901 /* FALLTHROUGH */
1902 case MDOC_Qq:
1903 term_word(p, "\"");
1904 break;
1905 case MDOC_Ql:
1906 /* FALLTHROUGH */
1907 case MDOC_So:
1908 /* FALLTHROUGH */
1909 case MDOC_Sq:
1910 term_word(p, "\\(oq");
1911 break;
1912 default:
1913 abort();
1914 /* NOTREACHED */
1915 }
1916
1917 p->flags |= TERMP_NOSPACE;
1918 return(1);
1919 }
1920
1921 static void
1922 termp_quote_post(DECL_ARGS)
1923 {
1924
1925 if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
1926 return;
1927
1928 if (MDOC_En != n->tok)
1929 p->flags |= TERMP_NOSPACE;
1930
1931 switch (n->tok) {
1932 case MDOC_Ao:
1933 /* FALLTHROUGH */
1934 case MDOC_Aq:
1935 term_word(p, ">");
1936 break;
1937 case MDOC_Bro:
1938 /* FALLTHROUGH */
1939 case MDOC_Brq:
1940 term_word(p, "}");
1941 break;
1942 case MDOC_Oo:
1943 /* FALLTHROUGH */
1944 case MDOC_Op:
1945 /* FALLTHROUGH */
1946 case MDOC_Bo:
1947 /* FALLTHROUGH */
1948 case MDOC_Bq:
1949 term_word(p, "]");
1950 break;
1951 case MDOC_Do:
1952 /* FALLTHROUGH */
1953 case MDOC_Dq:
1954 term_word(p, "\\(rq");
1955 break;
1956 case MDOC_En:
1957 if (NULL != n->norm->Es &&
1958 NULL != n->norm->Es->child &&
1959 NULL != n->norm->Es->child->next) {
1960 p->flags |= TERMP_NOSPACE;
1961 term_word(p, n->norm->Es->child->next->string);
1962 }
1963 break;
1964 case MDOC_Eo:
1965 break;
1966 case MDOC_Po:
1967 /* FALLTHROUGH */
1968 case MDOC_Pq:
1969 term_word(p, ")");
1970 break;
1971 case MDOC__T:
1972 /* FALLTHROUGH */
1973 case MDOC_Qo:
1974 /* FALLTHROUGH */
1975 case MDOC_Qq:
1976 term_word(p, "\"");
1977 break;
1978 case MDOC_Ql:
1979 /* FALLTHROUGH */
1980 case MDOC_So:
1981 /* FALLTHROUGH */
1982 case MDOC_Sq:
1983 term_word(p, "\\(cq");
1984 break;
1985 default:
1986 abort();
1987 /* NOTREACHED */
1988 }
1989 }
1990
1991 static int
1992 termp_fo_pre(DECL_ARGS)
1993 {
1994 size_t rmargin = 0;
1995 int pretty;
1996
1997 pretty = MDOC_SYNPRETTY & n->flags;
1998
1999 if (MDOC_BLOCK == n->type) {
2000 synopsis_pre(p, n);
2001 return(1);
2002 } else if (MDOC_BODY == n->type) {
2003 if (pretty) {
2004 rmargin = p->rmargin;
2005 p->rmargin = p->offset + term_len(p, 4);
2006 p->flags |= TERMP_NOBREAK | TERMP_BRIND |
2007 TERMP_HANG;
2008 }
2009 p->flags |= TERMP_NOSPACE;
2010 term_word(p, "(");
2011 p->flags |= TERMP_NOSPACE;
2012 if (pretty) {
2013 term_flushln(p);
2014 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
2015 TERMP_HANG);
2016 p->offset = p->rmargin;
2017 p->rmargin = rmargin;
2018 }
2019 return(1);
2020 }
2021
2022 if (NULL == n->child)
2023 return(0);
2024
2025 /* XXX: we drop non-initial arguments as per groff. */
2026
2027 assert(n->child->string);
2028 term_fontpush(p, TERMFONT_BOLD);
2029 term_word(p, n->child->string);
2030 return(0);
2031 }
2032
2033 static void
2034 termp_fo_post(DECL_ARGS)
2035 {
2036
2037 if (MDOC_BODY != n->type)
2038 return;
2039
2040 p->flags |= TERMP_NOSPACE;
2041 term_word(p, ")");
2042
2043 if (MDOC_SYNPRETTY & n->flags) {
2044 p->flags |= TERMP_NOSPACE;
2045 term_word(p, ";");
2046 term_flushln(p);
2047 }
2048 }
2049
2050 static int
2051 termp_bf_pre(DECL_ARGS)
2052 {
2053
2054 if (MDOC_HEAD == n->type)
2055 return(0);
2056 else if (MDOC_BODY != n->type)
2057 return(1);
2058
2059 if (FONT_Em == n->norm->Bf.font)
2060 term_fontpush(p, TERMFONT_UNDER);
2061 else if (FONT_Sy == n->norm->Bf.font)
2062 term_fontpush(p, TERMFONT_BOLD);
2063 else
2064 term_fontpush(p, TERMFONT_NONE);
2065
2066 return(1);
2067 }
2068
2069 static int
2070 termp_sm_pre(DECL_ARGS)
2071 {
2072
2073 if (NULL == n->child)
2074 p->flags ^= TERMP_NONOSPACE;
2075 else if (0 == strcmp("on", n->child->string))
2076 p->flags &= ~TERMP_NONOSPACE;
2077 else
2078 p->flags |= TERMP_NONOSPACE;
2079
2080 if (p->col && ! (TERMP_NONOSPACE & p->flags))
2081 p->flags &= ~TERMP_NOSPACE;
2082
2083 return(0);
2084 }
2085
2086 static int
2087 termp_ap_pre(DECL_ARGS)
2088 {
2089
2090 p->flags |= TERMP_NOSPACE;
2091 term_word(p, "'");
2092 p->flags |= TERMP_NOSPACE;
2093 return(1);
2094 }
2095
2096 static void
2097 termp____post(DECL_ARGS)
2098 {
2099
2100 /*
2101 * Handle lists of authors. In general, print each followed by
2102 * a comma. Don't print the comma if there are only two
2103 * authors.
2104 */
2105 if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
2106 if (NULL == n->next->next || MDOC__A != n->next->next->tok)
2107 if (NULL == n->prev || MDOC__A != n->prev->tok)
2108 return;
2109
2110 /* TODO: %U. */
2111
2112 if (NULL == n->parent || MDOC_Rs != n->parent->tok)
2113 return;
2114
2115 p->flags |= TERMP_NOSPACE;
2116 if (NULL == n->next) {
2117 term_word(p, ".");
2118 p->flags |= TERMP_SENTENCE;
2119 } else
2120 term_word(p, ",");
2121 }
2122
2123 static int
2124 termp_li_pre(DECL_ARGS)
2125 {
2126
2127 term_fontpush(p, TERMFONT_NONE);
2128 return(1);
2129 }
2130
2131 static int
2132 termp_lk_pre(DECL_ARGS)
2133 {
2134 const struct mdoc_node *link, *descr;
2135
2136 if (NULL == (link = n->child))
2137 return(0);
2138
2139 if (NULL != (descr = link->next)) {
2140 term_fontpush(p, TERMFONT_UNDER);
2141 while (NULL != descr) {
2142 term_word(p, descr->string);
2143 descr = descr->next;
2144 }
2145 p->flags |= TERMP_NOSPACE;
2146 term_word(p, ":");
2147 term_fontpop(p);
2148 }
2149
2150 term_fontpush(p, TERMFONT_BOLD);
2151 term_word(p, link->string);
2152 term_fontpop(p);
2153
2154 return(0);
2155 }
2156
2157 static int
2158 termp_bk_pre(DECL_ARGS)
2159 {
2160
2161 switch (n->type) {
2162 case MDOC_BLOCK:
2163 break;
2164 case MDOC_HEAD:
2165 return(0);
2166 case MDOC_BODY:
2167 if (n->parent->args || 0 == n->prev->nchild)
2168 p->flags |= TERMP_PREKEEP;
2169 break;
2170 default:
2171 abort();
2172 /* NOTREACHED */
2173 }
2174
2175 return(1);
2176 }
2177
2178 static void
2179 termp_bk_post(DECL_ARGS)
2180 {
2181
2182 if (MDOC_BODY == n->type)
2183 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
2184 }
2185
2186 static void
2187 termp__t_post(DECL_ARGS)
2188 {
2189
2190 /*
2191 * If we're in an `Rs' and there's a journal present, then quote
2192 * us instead of underlining us (for disambiguation).
2193 */
2194 if (n->parent && MDOC_Rs == n->parent->tok &&
2195 n->parent->norm->Rs.quote_T)
2196 termp_quote_post(p, pair, meta, n);
2197
2198 termp____post(p, pair, meta, n);
2199 }
2200
2201 static int
2202 termp__t_pre(DECL_ARGS)
2203 {
2204
2205 /*
2206 * If we're in an `Rs' and there's a journal present, then quote
2207 * us instead of underlining us (for disambiguation).
2208 */
2209 if (n->parent && MDOC_Rs == n->parent->tok &&
2210 n->parent->norm->Rs.quote_T)
2211 return(termp_quote_pre(p, pair, meta, n));
2212
2213 term_fontpush(p, TERMFONT_UNDER);
2214 return(1);
2215 }
2216
2217 static int
2218 termp_under_pre(DECL_ARGS)
2219 {
2220
2221 term_fontpush(p, TERMFONT_UNDER);
2222 return(1);
2223 }