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