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