]> git.cameronkatri.com Git - mandoc.git/blob - mdoc_term.c
In .Bl -column, if some of the column width declarations are given
[mandoc.git] / mdoc_term.c
1 /* $Id: mdoc_term.c,v 1.274 2014/07/30 17:06:26 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
1171 nchild = n->nchild;
1172 if (nchild > 0) {
1173 term_word(p, "The");
1174
1175 for (n = n->child; n; n = n->next) {
1176 term_fontpush(p, TERMFONT_BOLD);
1177 term_word(p, n->string);
1178 term_fontpop(p);
1179
1180 p->flags |= TERMP_NOSPACE;
1181 term_word(p, "()");
1182
1183 if (n->next == NULL)
1184 continue;
1185
1186 if (nchild > 2) {
1187 p->flags |= TERMP_NOSPACE;
1188 term_word(p, ",");
1189 }
1190 if (n->next->next == NULL)
1191 term_word(p, "and");
1192 }
1193
1194 if (nchild > 1)
1195 term_word(p, "functions return");
1196 else
1197 term_word(p, "function returns");
1198
1199 term_word(p, "the value\\~0 if successful;");
1200 } else
1201 term_word(p, "Upon successful completion,"
1202 " the value\\~0 is returned;");
1203
1204 term_word(p, "otherwise the value\\~\\-1 is returned"
1205 " and the global variable");
1206
1207 term_fontpush(p, TERMFONT_UNDER);
1208 term_word(p, "errno");
1209 term_fontpop(p);
1210
1211 term_word(p, "is set to indicate the error.");
1212 p->flags |= TERMP_SENTENCE;
1213
1214 return(0);
1215 }
1216
1217 static int
1218 termp_ex_pre(DECL_ARGS)
1219 {
1220 int nchild;
1221
1222 term_newln(p);
1223 term_word(p, "The");
1224
1225 nchild = n->nchild;
1226 for (n = n->child; n; n = n->next) {
1227 term_fontpush(p, TERMFONT_BOLD);
1228 term_word(p, n->string);
1229 term_fontpop(p);
1230
1231 if (nchild > 2 && n->next) {
1232 p->flags |= TERMP_NOSPACE;
1233 term_word(p, ",");
1234 }
1235
1236 if (n->next && NULL == n->next->next)
1237 term_word(p, "and");
1238 }
1239
1240 if (nchild > 1)
1241 term_word(p, "utilities exit\\~0");
1242 else
1243 term_word(p, "utility exits\\~0");
1244
1245 term_word(p, "on success, and\\~>0 if an error occurs.");
1246
1247 p->flags |= TERMP_SENTENCE;
1248 return(0);
1249 }
1250
1251 static int
1252 termp_nd_pre(DECL_ARGS)
1253 {
1254
1255 if (MDOC_BODY != n->type)
1256 return(1);
1257
1258 #if defined(__OpenBSD__) || defined(__linux__)
1259 term_word(p, "\\(en");
1260 #else
1261 term_word(p, "\\(em");
1262 #endif
1263 return(1);
1264 }
1265
1266 static int
1267 termp_bl_pre(DECL_ARGS)
1268 {
1269
1270 return(MDOC_HEAD != n->type);
1271 }
1272
1273 static void
1274 termp_bl_post(DECL_ARGS)
1275 {
1276
1277 if (MDOC_BLOCK == n->type)
1278 term_newln(p);
1279 }
1280
1281 static int
1282 termp_xr_pre(DECL_ARGS)
1283 {
1284
1285 if (NULL == (n = n->child))
1286 return(0);
1287
1288 assert(MDOC_TEXT == n->type);
1289 term_word(p, n->string);
1290
1291 if (NULL == (n = n->next))
1292 return(0);
1293
1294 p->flags |= TERMP_NOSPACE;
1295 term_word(p, "(");
1296 p->flags |= TERMP_NOSPACE;
1297
1298 assert(MDOC_TEXT == n->type);
1299 term_word(p, n->string);
1300
1301 p->flags |= TERMP_NOSPACE;
1302 term_word(p, ")");
1303
1304 return(0);
1305 }
1306
1307 /*
1308 * This decides how to assert whitespace before any of the SYNOPSIS set
1309 * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
1310 * macro combos).
1311 */
1312 static void
1313 synopsis_pre(struct termp *p, const struct mdoc_node *n)
1314 {
1315 /*
1316 * Obviously, if we're not in a SYNOPSIS or no prior macros
1317 * exist, do nothing.
1318 */
1319 if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
1320 return;
1321
1322 /*
1323 * If we're the second in a pair of like elements, emit our
1324 * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which
1325 * case we soldier on.
1326 */
1327 if (n->prev->tok == n->tok &&
1328 MDOC_Ft != n->tok &&
1329 MDOC_Fo != n->tok &&
1330 MDOC_Fn != n->tok) {
1331 term_newln(p);
1332 return;
1333 }
1334
1335 /*
1336 * If we're one of the SYNOPSIS set and non-like pair-wise after
1337 * another (or Fn/Fo, which we've let slip through) then assert
1338 * vertical space, else only newline and move on.
1339 */
1340 switch (n->prev->tok) {
1341 case MDOC_Fd:
1342 /* FALLTHROUGH */
1343 case MDOC_Fn:
1344 /* FALLTHROUGH */
1345 case MDOC_Fo:
1346 /* FALLTHROUGH */
1347 case MDOC_In:
1348 /* FALLTHROUGH */
1349 case MDOC_Vt:
1350 term_vspace(p);
1351 break;
1352 case MDOC_Ft:
1353 if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
1354 term_vspace(p);
1355 break;
1356 }
1357 /* FALLTHROUGH */
1358 default:
1359 term_newln(p);
1360 break;
1361 }
1362 }
1363
1364 static int
1365 termp_vt_pre(DECL_ARGS)
1366 {
1367
1368 if (MDOC_ELEM == n->type) {
1369 synopsis_pre(p, n);
1370 return(termp_under_pre(p, pair, meta, n));
1371 } else if (MDOC_BLOCK == n->type) {
1372 synopsis_pre(p, n);
1373 return(1);
1374 } else if (MDOC_HEAD == n->type)
1375 return(0);
1376
1377 return(termp_under_pre(p, pair, meta, n));
1378 }
1379
1380 static int
1381 termp_bold_pre(DECL_ARGS)
1382 {
1383
1384 term_fontpush(p, TERMFONT_BOLD);
1385 return(1);
1386 }
1387
1388 static int
1389 termp_fd_pre(DECL_ARGS)
1390 {
1391
1392 synopsis_pre(p, n);
1393 return(termp_bold_pre(p, pair, meta, n));
1394 }
1395
1396 static void
1397 termp_fd_post(DECL_ARGS)
1398 {
1399
1400 term_newln(p);
1401 }
1402
1403 static int
1404 termp_sh_pre(DECL_ARGS)
1405 {
1406
1407 /* No vspace between consecutive `Sh' calls. */
1408
1409 switch (n->type) {
1410 case MDOC_BLOCK:
1411 if (n->prev && MDOC_Sh == n->prev->tok)
1412 if (NULL == n->prev->body->child)
1413 break;
1414 term_vspace(p);
1415 break;
1416 case MDOC_HEAD:
1417 term_fontpush(p, TERMFONT_BOLD);
1418 break;
1419 case MDOC_BODY:
1420 p->offset = term_len(p, p->defindent);
1421 if (SEC_AUTHORS == n->sec)
1422 p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
1423 break;
1424 default:
1425 break;
1426 }
1427 return(1);
1428 }
1429
1430 static void
1431 termp_sh_post(DECL_ARGS)
1432 {
1433
1434 switch (n->type) {
1435 case MDOC_HEAD:
1436 term_newln(p);
1437 break;
1438 case MDOC_BODY:
1439 term_newln(p);
1440 p->offset = 0;
1441 break;
1442 default:
1443 break;
1444 }
1445 }
1446
1447 static int
1448 termp_bt_pre(DECL_ARGS)
1449 {
1450
1451 term_word(p, "is currently in beta test.");
1452 p->flags |= TERMP_SENTENCE;
1453 return(0);
1454 }
1455
1456 static void
1457 termp_lb_post(DECL_ARGS)
1458 {
1459
1460 if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags)
1461 term_newln(p);
1462 }
1463
1464 static int
1465 termp_ud_pre(DECL_ARGS)
1466 {
1467
1468 term_word(p, "currently under development.");
1469 p->flags |= TERMP_SENTENCE;
1470 return(0);
1471 }
1472
1473 static int
1474 termp_d1_pre(DECL_ARGS)
1475 {
1476
1477 if (MDOC_BLOCK != n->type)
1478 return(1);
1479 term_newln(p);
1480 p->offset += term_len(p, p->defindent + 1);
1481 return(1);
1482 }
1483
1484 static int
1485 termp_ft_pre(DECL_ARGS)
1486 {
1487
1488 /* NB: MDOC_LINE does not effect this! */
1489 synopsis_pre(p, n);
1490 term_fontpush(p, TERMFONT_UNDER);
1491 return(1);
1492 }
1493
1494 static int
1495 termp_fn_pre(DECL_ARGS)
1496 {
1497 size_t rmargin = 0;
1498 int pretty;
1499
1500 pretty = MDOC_SYNPRETTY & n->flags;
1501
1502 synopsis_pre(p, n);
1503
1504 if (NULL == (n = n->child))
1505 return(0);
1506
1507 if (pretty) {
1508 rmargin = p->rmargin;
1509 p->rmargin = p->offset + term_len(p, 4);
1510 p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
1511 }
1512
1513 assert(MDOC_TEXT == n->type);
1514 term_fontpush(p, TERMFONT_BOLD);
1515 term_word(p, n->string);
1516 term_fontpop(p);
1517
1518 if (pretty) {
1519 term_flushln(p);
1520 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
1521 p->offset = p->rmargin;
1522 p->rmargin = rmargin;
1523 }
1524
1525 p->flags |= TERMP_NOSPACE;
1526 term_word(p, "(");
1527 p->flags |= TERMP_NOSPACE;
1528
1529 for (n = n->next; n; n = n->next) {
1530 assert(MDOC_TEXT == n->type);
1531 term_fontpush(p, TERMFONT_UNDER);
1532 if (pretty)
1533 p->flags |= TERMP_NBRWORD;
1534 term_word(p, n->string);
1535 term_fontpop(p);
1536
1537 if (n->next) {
1538 p->flags |= TERMP_NOSPACE;
1539 term_word(p, ",");
1540 }
1541 }
1542
1543 p->flags |= TERMP_NOSPACE;
1544 term_word(p, ")");
1545
1546 if (pretty) {
1547 p->flags |= TERMP_NOSPACE;
1548 term_word(p, ";");
1549 term_flushln(p);
1550 }
1551
1552 return(0);
1553 }
1554
1555 static int
1556 termp_fa_pre(DECL_ARGS)
1557 {
1558 const struct mdoc_node *nn;
1559
1560 if (n->parent->tok != MDOC_Fo) {
1561 term_fontpush(p, TERMFONT_UNDER);
1562 return(1);
1563 }
1564
1565 for (nn = n->child; nn; nn = nn->next) {
1566 term_fontpush(p, TERMFONT_UNDER);
1567 p->flags |= TERMP_NBRWORD;
1568 term_word(p, nn->string);
1569 term_fontpop(p);
1570
1571 if (nn->next || (n->next && n->next->tok == MDOC_Fa)) {
1572 p->flags |= TERMP_NOSPACE;
1573 term_word(p, ",");
1574 }
1575 }
1576
1577 return(0);
1578 }
1579
1580 static int
1581 termp_bd_pre(DECL_ARGS)
1582 {
1583 size_t tabwidth, lm, len, rm, rmax;
1584 struct mdoc_node *nn;
1585
1586 if (MDOC_BLOCK == n->type) {
1587 print_bvspace(p, n, n);
1588 return(1);
1589 } else if (MDOC_HEAD == n->type)
1590 return(0);
1591
1592 if (n->norm->Bd.offs)
1593 p->offset += a2offs(p, n->norm->Bd.offs);
1594
1595 /*
1596 * If -ragged or -filled are specified, the block does nothing
1597 * but change the indentation. If -unfilled or -literal are
1598 * specified, text is printed exactly as entered in the display:
1599 * for macro lines, a newline is appended to the line. Blank
1600 * lines are allowed.
1601 */
1602
1603 if (DISP_literal != n->norm->Bd.type &&
1604 DISP_unfilled != n->norm->Bd.type &&
1605 DISP_centered != n->norm->Bd.type)
1606 return(1);
1607
1608 tabwidth = p->tabwidth;
1609 if (DISP_literal == n->norm->Bd.type)
1610 p->tabwidth = term_len(p, 8);
1611
1612 lm = p->offset;
1613 rm = p->rmargin;
1614 rmax = p->maxrmargin;
1615 p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1616
1617 for (nn = n->child; nn; nn = nn->next) {
1618 if (DISP_centered == n->norm->Bd.type) {
1619 if (MDOC_TEXT == nn->type) {
1620 len = term_strlen(p, nn->string);
1621 p->offset = len >= rm ? 0 :
1622 lm + len >= rm ? rm - len :
1623 (lm + rm - len) / 2;
1624 } else
1625 p->offset = lm;
1626 }
1627 print_mdoc_node(p, pair, meta, nn);
1628 /*
1629 * If the printed node flushes its own line, then we
1630 * needn't do it here as well. This is hacky, but the
1631 * notion of selective eoln whitespace is pretty dumb
1632 * anyway, so don't sweat it.
1633 */
1634 switch (nn->tok) {
1635 case MDOC_Sm:
1636 /* FALLTHROUGH */
1637 case MDOC_br:
1638 /* FALLTHROUGH */
1639 case MDOC_sp:
1640 /* FALLTHROUGH */
1641 case MDOC_Bl:
1642 /* FALLTHROUGH */
1643 case MDOC_D1:
1644 /* FALLTHROUGH */
1645 case MDOC_Dl:
1646 /* FALLTHROUGH */
1647 case MDOC_Lp:
1648 /* FALLTHROUGH */
1649 case MDOC_Pp:
1650 continue;
1651 default:
1652 break;
1653 }
1654 if (nn->next && nn->next->line == nn->line)
1655 continue;
1656 term_flushln(p);
1657 p->flags |= TERMP_NOSPACE;
1658 }
1659
1660 p->tabwidth = tabwidth;
1661 p->rmargin = rm;
1662 p->maxrmargin = rmax;
1663 return(0);
1664 }
1665
1666 static void
1667 termp_bd_post(DECL_ARGS)
1668 {
1669 size_t rm, rmax;
1670
1671 if (MDOC_BODY != n->type)
1672 return;
1673
1674 rm = p->rmargin;
1675 rmax = p->maxrmargin;
1676
1677 if (DISP_literal == n->norm->Bd.type ||
1678 DISP_unfilled == n->norm->Bd.type)
1679 p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
1680
1681 p->flags |= TERMP_NOSPACE;
1682 term_newln(p);
1683
1684 p->rmargin = rm;
1685 p->maxrmargin = rmax;
1686 }
1687
1688 static int
1689 termp_bx_pre(DECL_ARGS)
1690 {
1691
1692 if (NULL != (n = n->child)) {
1693 term_word(p, n->string);
1694 p->flags |= TERMP_NOSPACE;
1695 term_word(p, "BSD");
1696 } else {
1697 term_word(p, "BSD");
1698 return(0);
1699 }
1700
1701 if (NULL != (n = n->next)) {
1702 p->flags |= TERMP_NOSPACE;
1703 term_word(p, "-");
1704 p->flags |= TERMP_NOSPACE;
1705 term_word(p, n->string);
1706 }
1707
1708 return(0);
1709 }
1710
1711 static int
1712 termp_xx_pre(DECL_ARGS)
1713 {
1714 const char *pp;
1715 int flags;
1716
1717 pp = NULL;
1718 switch (n->tok) {
1719 case MDOC_Bsx:
1720 pp = "BSD/OS";
1721 break;
1722 case MDOC_Dx:
1723 pp = "DragonFly";
1724 break;
1725 case MDOC_Fx:
1726 pp = "FreeBSD";
1727 break;
1728 case MDOC_Nx:
1729 pp = "NetBSD";
1730 break;
1731 case MDOC_Ox:
1732 pp = "OpenBSD";
1733 break;
1734 case MDOC_Ux:
1735 pp = "UNIX";
1736 break;
1737 default:
1738 abort();
1739 /* NOTREACHED */
1740 }
1741
1742 term_word(p, pp);
1743 if (n->child) {
1744 flags = p->flags;
1745 p->flags |= TERMP_KEEP;
1746 term_word(p, n->child->string);
1747 p->flags = flags;
1748 }
1749 return(0);
1750 }
1751
1752 static void
1753 termp_pf_post(DECL_ARGS)
1754 {
1755
1756 p->flags |= TERMP_NOSPACE;
1757 }
1758
1759 static int
1760 termp_ss_pre(DECL_ARGS)
1761 {
1762
1763 switch (n->type) {
1764 case MDOC_BLOCK:
1765 term_newln(p);
1766 if (n->prev)
1767 term_vspace(p);
1768 break;
1769 case MDOC_HEAD:
1770 term_fontpush(p, TERMFONT_BOLD);
1771 p->offset = term_len(p, (p->defindent+1)/2);
1772 break;
1773 case MDOC_BODY:
1774 p->offset = term_len(p, p->defindent);
1775 break;
1776 default:
1777 break;
1778 }
1779
1780 return(1);
1781 }
1782
1783 static void
1784 termp_ss_post(DECL_ARGS)
1785 {
1786
1787 if (n->type == MDOC_HEAD || n->type == MDOC_BODY)
1788 term_newln(p);
1789 }
1790
1791 static int
1792 termp_cd_pre(DECL_ARGS)
1793 {
1794
1795 synopsis_pre(p, n);
1796 term_fontpush(p, TERMFONT_BOLD);
1797 return(1);
1798 }
1799
1800 static int
1801 termp_in_pre(DECL_ARGS)
1802 {
1803
1804 synopsis_pre(p, n);
1805
1806 if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) {
1807 term_fontpush(p, TERMFONT_BOLD);
1808 term_word(p, "#include");
1809 term_word(p, "<");
1810 } else {
1811 term_word(p, "<");
1812 term_fontpush(p, TERMFONT_UNDER);
1813 }
1814
1815 p->flags |= TERMP_NOSPACE;
1816 return(1);
1817 }
1818
1819 static void
1820 termp_in_post(DECL_ARGS)
1821 {
1822
1823 if (MDOC_SYNPRETTY & n->flags)
1824 term_fontpush(p, TERMFONT_BOLD);
1825
1826 p->flags |= TERMP_NOSPACE;
1827 term_word(p, ">");
1828
1829 if (MDOC_SYNPRETTY & n->flags)
1830 term_fontpop(p);
1831 }
1832
1833 static int
1834 termp_sp_pre(DECL_ARGS)
1835 {
1836 size_t i, len;
1837
1838 switch (n->tok) {
1839 case MDOC_sp:
1840 len = n->child ? a2height(p, n->child->string) : 1;
1841 break;
1842 case MDOC_br:
1843 len = 0;
1844 break;
1845 default:
1846 len = 1;
1847 break;
1848 }
1849
1850 if (0 == len)
1851 term_newln(p);
1852 for (i = 0; i < len; i++)
1853 term_vspace(p);
1854
1855 return(0);
1856 }
1857
1858 static int
1859 termp_es_pre(DECL_ARGS)
1860 {
1861
1862 return(0);
1863 }
1864
1865 static int
1866 termp_quote_pre(DECL_ARGS)
1867 {
1868
1869 if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
1870 return(1);
1871
1872 switch (n->tok) {
1873 case MDOC_Ao:
1874 /* FALLTHROUGH */
1875 case MDOC_Aq:
1876 term_word(p, "<");
1877 break;
1878 case MDOC_Bro:
1879 /* FALLTHROUGH */
1880 case MDOC_Brq:
1881 term_word(p, "{");
1882 break;
1883 case MDOC_Oo:
1884 /* FALLTHROUGH */
1885 case MDOC_Op:
1886 /* FALLTHROUGH */
1887 case MDOC_Bo:
1888 /* FALLTHROUGH */
1889 case MDOC_Bq:
1890 term_word(p, "[");
1891 break;
1892 case MDOC_Do:
1893 /* FALLTHROUGH */
1894 case MDOC_Dq:
1895 term_word(p, "\\(lq");
1896 break;
1897 case MDOC_En:
1898 if (NULL == n->norm->Es ||
1899 NULL == n->norm->Es->child)
1900 return(1);
1901 term_word(p, n->norm->Es->child->string);
1902 break;
1903 case MDOC_Eo:
1904 break;
1905 case MDOC_Po:
1906 /* FALLTHROUGH */
1907 case MDOC_Pq:
1908 term_word(p, "(");
1909 break;
1910 case MDOC__T:
1911 /* FALLTHROUGH */
1912 case MDOC_Qo:
1913 /* FALLTHROUGH */
1914 case MDOC_Qq:
1915 term_word(p, "\"");
1916 break;
1917 case MDOC_Ql:
1918 /* FALLTHROUGH */
1919 case MDOC_So:
1920 /* FALLTHROUGH */
1921 case MDOC_Sq:
1922 term_word(p, "\\(oq");
1923 break;
1924 default:
1925 abort();
1926 /* NOTREACHED */
1927 }
1928
1929 p->flags |= TERMP_NOSPACE;
1930 return(1);
1931 }
1932
1933 static void
1934 termp_quote_post(DECL_ARGS)
1935 {
1936
1937 if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
1938 return;
1939
1940 if (MDOC_En != n->tok)
1941 p->flags |= TERMP_NOSPACE;
1942
1943 switch (n->tok) {
1944 case MDOC_Ao:
1945 /* FALLTHROUGH */
1946 case MDOC_Aq:
1947 term_word(p, ">");
1948 break;
1949 case MDOC_Bro:
1950 /* FALLTHROUGH */
1951 case MDOC_Brq:
1952 term_word(p, "}");
1953 break;
1954 case MDOC_Oo:
1955 /* FALLTHROUGH */
1956 case MDOC_Op:
1957 /* FALLTHROUGH */
1958 case MDOC_Bo:
1959 /* FALLTHROUGH */
1960 case MDOC_Bq:
1961 term_word(p, "]");
1962 break;
1963 case MDOC_Do:
1964 /* FALLTHROUGH */
1965 case MDOC_Dq:
1966 term_word(p, "\\(rq");
1967 break;
1968 case MDOC_En:
1969 if (NULL != n->norm->Es &&
1970 NULL != n->norm->Es->child &&
1971 NULL != n->norm->Es->child->next) {
1972 p->flags |= TERMP_NOSPACE;
1973 term_word(p, n->norm->Es->child->next->string);
1974 }
1975 break;
1976 case MDOC_Eo:
1977 break;
1978 case MDOC_Po:
1979 /* FALLTHROUGH */
1980 case MDOC_Pq:
1981 term_word(p, ")");
1982 break;
1983 case MDOC__T:
1984 /* FALLTHROUGH */
1985 case MDOC_Qo:
1986 /* FALLTHROUGH */
1987 case MDOC_Qq:
1988 term_word(p, "\"");
1989 break;
1990 case MDOC_Ql:
1991 /* FALLTHROUGH */
1992 case MDOC_So:
1993 /* FALLTHROUGH */
1994 case MDOC_Sq:
1995 term_word(p, "\\(cq");
1996 break;
1997 default:
1998 abort();
1999 /* NOTREACHED */
2000 }
2001 }
2002
2003 static int
2004 termp_fo_pre(DECL_ARGS)
2005 {
2006 size_t rmargin = 0;
2007 int pretty;
2008
2009 pretty = MDOC_SYNPRETTY & n->flags;
2010
2011 if (MDOC_BLOCK == n->type) {
2012 synopsis_pre(p, n);
2013 return(1);
2014 } else if (MDOC_BODY == n->type) {
2015 if (pretty) {
2016 rmargin = p->rmargin;
2017 p->rmargin = p->offset + term_len(p, 4);
2018 p->flags |= TERMP_NOBREAK | TERMP_BRIND |
2019 TERMP_HANG;
2020 }
2021 p->flags |= TERMP_NOSPACE;
2022 term_word(p, "(");
2023 p->flags |= TERMP_NOSPACE;
2024 if (pretty) {
2025 term_flushln(p);
2026 p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
2027 TERMP_HANG);
2028 p->offset = p->rmargin;
2029 p->rmargin = rmargin;
2030 }
2031 return(1);
2032 }
2033
2034 if (NULL == n->child)
2035 return(0);
2036
2037 /* XXX: we drop non-initial arguments as per groff. */
2038
2039 assert(n->child->string);
2040 term_fontpush(p, TERMFONT_BOLD);
2041 term_word(p, n->child->string);
2042 return(0);
2043 }
2044
2045 static void
2046 termp_fo_post(DECL_ARGS)
2047 {
2048
2049 if (MDOC_BODY != n->type)
2050 return;
2051
2052 p->flags |= TERMP_NOSPACE;
2053 term_word(p, ")");
2054
2055 if (MDOC_SYNPRETTY & n->flags) {
2056 p->flags |= TERMP_NOSPACE;
2057 term_word(p, ";");
2058 term_flushln(p);
2059 }
2060 }
2061
2062 static int
2063 termp_bf_pre(DECL_ARGS)
2064 {
2065
2066 if (MDOC_HEAD == n->type)
2067 return(0);
2068 else if (MDOC_BODY != n->type)
2069 return(1);
2070
2071 if (FONT_Em == n->norm->Bf.font)
2072 term_fontpush(p, TERMFONT_UNDER);
2073 else if (FONT_Sy == n->norm->Bf.font)
2074 term_fontpush(p, TERMFONT_BOLD);
2075 else
2076 term_fontpush(p, TERMFONT_NONE);
2077
2078 return(1);
2079 }
2080
2081 static int
2082 termp_sm_pre(DECL_ARGS)
2083 {
2084
2085 if (NULL == n->child)
2086 p->flags ^= TERMP_NONOSPACE;
2087 else if (0 == strcmp("on", n->child->string))
2088 p->flags &= ~TERMP_NONOSPACE;
2089 else
2090 p->flags |= TERMP_NONOSPACE;
2091
2092 if (p->col && ! (TERMP_NONOSPACE & p->flags))
2093 p->flags &= ~TERMP_NOSPACE;
2094
2095 return(0);
2096 }
2097
2098 static int
2099 termp_ap_pre(DECL_ARGS)
2100 {
2101
2102 p->flags |= TERMP_NOSPACE;
2103 term_word(p, "'");
2104 p->flags |= TERMP_NOSPACE;
2105 return(1);
2106 }
2107
2108 static void
2109 termp____post(DECL_ARGS)
2110 {
2111
2112 /*
2113 * Handle lists of authors. In general, print each followed by
2114 * a comma. Don't print the comma if there are only two
2115 * authors.
2116 */
2117 if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
2118 if (NULL == n->next->next || MDOC__A != n->next->next->tok)
2119 if (NULL == n->prev || MDOC__A != n->prev->tok)
2120 return;
2121
2122 /* TODO: %U. */
2123
2124 if (NULL == n->parent || MDOC_Rs != n->parent->tok)
2125 return;
2126
2127 p->flags |= TERMP_NOSPACE;
2128 if (NULL == n->next) {
2129 term_word(p, ".");
2130 p->flags |= TERMP_SENTENCE;
2131 } else
2132 term_word(p, ",");
2133 }
2134
2135 static int
2136 termp_li_pre(DECL_ARGS)
2137 {
2138
2139 term_fontpush(p, TERMFONT_NONE);
2140 return(1);
2141 }
2142
2143 static int
2144 termp_lk_pre(DECL_ARGS)
2145 {
2146 const struct mdoc_node *link, *descr;
2147
2148 if (NULL == (link = n->child))
2149 return(0);
2150
2151 if (NULL != (descr = link->next)) {
2152 term_fontpush(p, TERMFONT_UNDER);
2153 while (NULL != descr) {
2154 term_word(p, descr->string);
2155 descr = descr->next;
2156 }
2157 p->flags |= TERMP_NOSPACE;
2158 term_word(p, ":");
2159 term_fontpop(p);
2160 }
2161
2162 term_fontpush(p, TERMFONT_BOLD);
2163 term_word(p, link->string);
2164 term_fontpop(p);
2165
2166 return(0);
2167 }
2168
2169 static int
2170 termp_bk_pre(DECL_ARGS)
2171 {
2172
2173 switch (n->type) {
2174 case MDOC_BLOCK:
2175 break;
2176 case MDOC_HEAD:
2177 return(0);
2178 case MDOC_BODY:
2179 if (n->parent->args || 0 == n->prev->nchild)
2180 p->flags |= TERMP_PREKEEP;
2181 break;
2182 default:
2183 abort();
2184 /* NOTREACHED */
2185 }
2186
2187 return(1);
2188 }
2189
2190 static void
2191 termp_bk_post(DECL_ARGS)
2192 {
2193
2194 if (MDOC_BODY == n->type)
2195 p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
2196 }
2197
2198 static void
2199 termp__t_post(DECL_ARGS)
2200 {
2201
2202 /*
2203 * If we're in an `Rs' and there's a journal present, then quote
2204 * us instead of underlining us (for disambiguation).
2205 */
2206 if (n->parent && MDOC_Rs == n->parent->tok &&
2207 n->parent->norm->Rs.quote_T)
2208 termp_quote_post(p, pair, meta, n);
2209
2210 termp____post(p, pair, meta, n);
2211 }
2212
2213 static int
2214 termp__t_pre(DECL_ARGS)
2215 {
2216
2217 /*
2218 * If we're in an `Rs' and there's a journal present, then quote
2219 * us instead of underlining us (for disambiguation).
2220 */
2221 if (n->parent && MDOC_Rs == n->parent->tok &&
2222 n->parent->norm->Rs.quote_T)
2223 return(termp_quote_pre(p, pair, meta, n));
2224
2225 term_fontpush(p, TERMFONT_UNDER);
2226 return(1);
2227 }
2228
2229 static int
2230 termp_under_pre(DECL_ARGS)
2231 {
2232
2233 term_fontpush(p, TERMFONT_UNDER);
2234 return(1);
2235 }