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