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