]> git.cameronkatri.com Git - mandoc.git/blob - tbl_term.c
Add an option -T html -O toc to add a brief table of contents near
[mandoc.git] / tbl_term.c
1 /* $Id: tbl_term.c,v 1.60 2018/08/19 23:10:28 schwarze Exp $ */
2 /*
3 * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2011-2018 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #include "config.h"
19
20 #include <sys/types.h>
21
22 #include <assert.h>
23 #include <ctype.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "mandoc.h"
29 #include "out.h"
30 #include "term.h"
31
32 #define IS_HORIZ(cp) ((cp)->pos == TBL_CELL_HORIZ || \
33 (cp)->pos == TBL_CELL_DHORIZ)
34
35 static size_t term_tbl_len(size_t, void *);
36 static size_t term_tbl_strlen(const char *, void *);
37 static size_t term_tbl_sulen(const struct roffsu *, void *);
38 static void tbl_char(struct termp *, char, size_t);
39 static void tbl_data(struct termp *, const struct tbl_opts *,
40 const struct tbl_cell *,
41 const struct tbl_dat *,
42 const struct roffcol *);
43 static void tbl_literal(struct termp *, const struct tbl_dat *,
44 const struct roffcol *);
45 static void tbl_number(struct termp *, const struct tbl_opts *,
46 const struct tbl_dat *,
47 const struct roffcol *);
48 static void tbl_hrule(struct termp *, const struct tbl_span *, int);
49 static void tbl_word(struct termp *, const struct tbl_dat *);
50
51
52 static size_t
53 term_tbl_sulen(const struct roffsu *su, void *arg)
54 {
55 int i;
56
57 i = term_hen((const struct termp *)arg, su);
58 return i > 0 ? i : 0;
59 }
60
61 static size_t
62 term_tbl_strlen(const char *p, void *arg)
63 {
64 return term_strlen((const struct termp *)arg, p);
65 }
66
67 static size_t
68 term_tbl_len(size_t sz, void *arg)
69 {
70 return term_len((const struct termp *)arg, sz);
71 }
72
73 void
74 term_tbl(struct termp *tp, const struct tbl_span *sp)
75 {
76 const struct tbl_cell *cp, *cpn, *cpp;
77 const struct tbl_dat *dp;
78 static size_t offset;
79 size_t coloff, tsz;
80 int ic, horiz, spans, vert, more;
81 char fc;
82
83 /* Inhibit printing of spaces: we do padding ourselves. */
84
85 tp->flags |= TERMP_NOSPACE | TERMP_NONOSPACE;
86
87 /*
88 * The first time we're invoked for a given table block,
89 * calculate the table widths and decimal positions.
90 */
91
92 if (tp->tbl.cols == NULL) {
93 tp->tbl.len = term_tbl_len;
94 tp->tbl.slen = term_tbl_strlen;
95 tp->tbl.sulen = term_tbl_sulen;
96 tp->tbl.arg = tp;
97
98 tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin);
99
100 /* Tables leak .ta settings to subsequent text. */
101
102 term_tab_set(tp, NULL);
103 coloff = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ||
104 sp->opts->lvert;
105 for (ic = 0; ic < sp->opts->cols; ic++) {
106 coloff += tp->tbl.cols[ic].width;
107 term_tab_iset(coloff);
108 coloff += tp->tbl.cols[ic].spacing;
109 }
110
111 /* Center the table as a whole. */
112
113 offset = tp->tcol->offset;
114 if (sp->opts->opts & TBL_OPT_CENTRE) {
115 tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)
116 ? 2 : !!sp->opts->lvert + !!sp->opts->rvert;
117 for (ic = 0; ic + 1 < sp->opts->cols; ic++)
118 tsz += tp->tbl.cols[ic].width +
119 tp->tbl.cols[ic].spacing;
120 if (sp->opts->cols)
121 tsz += tp->tbl.cols[sp->opts->cols - 1].width;
122 if (offset + tsz > tp->tcol->rmargin)
123 tsz -= 1;
124 tp->tcol->offset = offset + tp->tcol->rmargin > tsz ?
125 (offset + tp->tcol->rmargin - tsz) / 2 : 0;
126 }
127
128 /* Horizontal frame at the start of boxed tables. */
129
130 if (sp->opts->opts & TBL_OPT_DBOX)
131 tbl_hrule(tp, sp, 3);
132 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX))
133 tbl_hrule(tp, sp, 2);
134 }
135
136 /* Set up the columns. */
137
138 tp->flags |= TERMP_MULTICOL;
139 horiz = 0;
140 switch (sp->pos) {
141 case TBL_SPAN_HORIZ:
142 case TBL_SPAN_DHORIZ:
143 horiz = 1;
144 term_setcol(tp, 1);
145 break;
146 case TBL_SPAN_DATA:
147 term_setcol(tp, sp->opts->cols + 2);
148 coloff = tp->tcol->offset;
149
150 /* Set up a column for a left vertical frame. */
151
152 if (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ||
153 sp->opts->lvert)
154 coloff++;
155 tp->tcol->rmargin = coloff;
156
157 /* Set up the data columns. */
158
159 dp = sp->first;
160 spans = 0;
161 for (ic = 0; ic < sp->opts->cols; ic++) {
162 if (spans == 0) {
163 tp->tcol++;
164 tp->tcol->offset = coloff;
165 }
166 coloff += tp->tbl.cols[ic].width;
167 tp->tcol->rmargin = coloff;
168 if (ic + 1 < sp->opts->cols)
169 coloff += tp->tbl.cols[ic].spacing;
170 if (spans) {
171 spans--;
172 continue;
173 }
174 if (dp == NULL)
175 continue;
176 spans = dp->spans;
177 if (ic || sp->layout->first->pos != TBL_CELL_SPAN)
178 dp = dp->next;
179 }
180
181 /* Set up a column for a right vertical frame. */
182
183 tp->tcol++;
184 tp->tcol->offset = coloff + 1;
185 tp->tcol->rmargin = tp->maxrmargin;
186
187 /* Spans may have reduced the number of columns. */
188
189 tp->lasttcol = tp->tcol - tp->tcols;
190
191 /* Fill the buffers for all data columns. */
192
193 tp->tcol = tp->tcols;
194 cp = cpn = sp->layout->first;
195 dp = sp->first;
196 spans = 0;
197 for (ic = 0; ic < sp->opts->cols; ic++) {
198 if (cpn != NULL) {
199 cp = cpn;
200 cpn = cpn->next;
201 }
202 if (spans) {
203 spans--;
204 continue;
205 }
206 tp->tcol++;
207 tp->col = 0;
208 tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic);
209 if (dp == NULL)
210 continue;
211 spans = dp->spans;
212 if (cp->pos != TBL_CELL_SPAN)
213 dp = dp->next;
214 }
215 break;
216 }
217
218 do {
219 /* Print the vertical frame at the start of each row. */
220
221 tp->tcol = tp->tcols;
222 fc = '\0';
223 if (sp->layout->vert ||
224 (sp->next != NULL && sp->next->layout->vert &&
225 sp->next->pos == TBL_SPAN_DATA) ||
226 (sp->prev != NULL && sp->prev->layout->vert &&
227 (horiz || (IS_HORIZ(sp->layout->first) &&
228 !IS_HORIZ(sp->prev->layout->first)))) ||
229 sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX))
230 fc = horiz || IS_HORIZ(sp->layout->first) ? '+' : '|';
231 else if (horiz && sp->opts->lvert)
232 fc = '-';
233 if (fc != '\0') {
234 (*tp->advance)(tp, tp->tcols->offset);
235 (*tp->letter)(tp, fc);
236 tp->viscol = tp->tcol->offset + 1;
237 }
238
239 /* Print the data cells. */
240
241 more = 0;
242 if (horiz) {
243 tbl_hrule(tp, sp, 0);
244 term_flushln(tp);
245 } else {
246 cp = sp->layout->first;
247 cpn = sp->next == NULL ? NULL :
248 sp->next->layout->first;
249 cpp = sp->prev == NULL ? NULL :
250 sp->prev->layout->first;
251 dp = sp->first;
252 spans = 0;
253 for (ic = 0; ic < sp->opts->cols; ic++) {
254
255 /*
256 * Figure out whether to print a
257 * vertical line after this cell
258 * and advance to next layout cell.
259 */
260
261 if (cp != NULL) {
262 vert = cp->vert;
263 switch (cp->pos) {
264 case TBL_CELL_HORIZ:
265 fc = '-';
266 break;
267 case TBL_CELL_DHORIZ:
268 fc = '=';
269 break;
270 default:
271 fc = ' ';
272 break;
273 }
274 } else {
275 vert = 0;
276 fc = ' ';
277 }
278 if (cpp != NULL) {
279 if (vert == 0 &&
280 cp != NULL &&
281 ((IS_HORIZ(cp) &&
282 !IS_HORIZ(cpp)) ||
283 (cp->next != NULL &&
284 cpp->next != NULL &&
285 IS_HORIZ(cp->next) &&
286 !IS_HORIZ(cpp->next))))
287 vert = cpp->vert;
288 cpp = cpp->next;
289 }
290 if (vert == 0 &&
291 sp->opts->opts & TBL_OPT_ALLBOX)
292 vert = 1;
293 if (cpn != NULL) {
294 if (vert == 0)
295 vert = cpn->vert;
296 cpn = cpn->next;
297 }
298 if (cp != NULL)
299 cp = cp->next;
300
301 /*
302 * Skip later cells in a span,
303 * figure out whether to start a span,
304 * and advance to next data cell.
305 */
306
307 if (spans) {
308 spans--;
309 continue;
310 }
311 if (dp != NULL) {
312 spans = dp->spans;
313 if (ic || sp->layout->first->pos
314 != TBL_CELL_SPAN)
315 dp = dp->next;
316 }
317
318 /*
319 * Print one line of text in the cell
320 * and remember whether there is more.
321 */
322
323 tp->tcol++;
324 if (tp->tcol->col < tp->tcol->lastcol)
325 term_flushln(tp);
326 if (tp->tcol->col < tp->tcol->lastcol)
327 more = 1;
328
329 /*
330 * Vertical frames between data cells,
331 * but not after the last column.
332 */
333
334 if (fc == ' ' && ((vert == 0 &&
335 (cp == NULL || !IS_HORIZ(cp))) ||
336 tp->tcol + 1 == tp->tcols + tp->lasttcol))
337 continue;
338
339 if (tp->viscol < tp->tcol->rmargin) {
340 (*tp->advance)(tp, tp->tcol->rmargin
341 - tp->viscol);
342 tp->viscol = tp->tcol->rmargin;
343 }
344 while (tp->viscol < tp->tcol->rmargin +
345 tp->tbl.cols[ic].spacing / 2) {
346 (*tp->letter)(tp, fc);
347 tp->viscol++;
348 }
349
350 if (tp->tcol + 1 == tp->tcols + tp->lasttcol)
351 continue;
352
353 if (fc == ' ' && cp != NULL) {
354 switch (cp->pos) {
355 case TBL_CELL_HORIZ:
356 fc = '-';
357 break;
358 case TBL_CELL_DHORIZ:
359 fc = '=';
360 break;
361 default:
362 break;
363 }
364 }
365 if (tp->tbl.cols[ic].spacing) {
366 (*tp->letter)(tp, fc == ' ' ? '|' :
367 vert ? '+' : fc);
368 tp->viscol++;
369 }
370
371 if (fc != ' ') {
372 if (cp != NULL &&
373 cp->pos == TBL_CELL_HORIZ)
374 fc = '-';
375 else if (cp != NULL &&
376 cp->pos == TBL_CELL_DHORIZ)
377 fc = '=';
378 else
379 fc = ' ';
380 }
381 if (tp->tbl.cols[ic].spacing > 2 &&
382 (vert > 1 || fc != ' ')) {
383 (*tp->letter)(tp, fc == ' ' ? '|' :
384 vert > 1 ? '+' : fc);
385 tp->viscol++;
386 }
387 }
388 }
389
390 /* Print the vertical frame at the end of each row. */
391
392 fc = '\0';
393 if ((sp->layout->last->vert &&
394 sp->layout->last->col + 1 == sp->opts->cols) ||
395 (sp->next != NULL &&
396 sp->next->layout->last->vert &&
397 sp->next->layout->last->col + 1 == sp->opts->cols) ||
398 (sp->prev != NULL &&
399 sp->prev->layout->last->vert &&
400 sp->prev->layout->last->col + 1 == sp->opts->cols &&
401 (horiz || (IS_HORIZ(sp->layout->last) &&
402 !IS_HORIZ(sp->prev->layout->last)))) ||
403 (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)))
404 fc = horiz || IS_HORIZ(sp->layout->last) ? '+' : '|';
405 else if (horiz && sp->opts->rvert)
406 fc = '-';
407 if (fc != '\0') {
408 if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 ||
409 sp->layout->last->col + 1 < sp->opts->cols)) {
410 tp->tcol++;
411 (*tp->advance)(tp,
412 tp->tcol->offset > tp->viscol ?
413 tp->tcol->offset - tp->viscol : 1);
414 }
415 (*tp->letter)(tp, fc);
416 }
417 (*tp->endline)(tp);
418 tp->viscol = 0;
419 } while (more);
420
421 /*
422 * Clean up after this row. If it is the last line
423 * of the table, print the box line and clean up
424 * column data; otherwise, print the allbox line.
425 */
426
427 term_setcol(tp, 1);
428 tp->flags &= ~TERMP_MULTICOL;
429 tp->tcol->rmargin = tp->maxrmargin;
430 if (sp->next == NULL) {
431 if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) {
432 tbl_hrule(tp, sp, 2);
433 tp->skipvsp = 1;
434 }
435 if (sp->opts->opts & TBL_OPT_DBOX) {
436 tbl_hrule(tp, sp, 3);
437 tp->skipvsp = 2;
438 }
439 assert(tp->tbl.cols);
440 free(tp->tbl.cols);
441 tp->tbl.cols = NULL;
442 tp->tcol->offset = offset;
443 } else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX &&
444 (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA ||
445 sp->next->next != NULL))
446 tbl_hrule(tp, sp, 1);
447
448 tp->flags &= ~TERMP_NONOSPACE;
449 }
450
451 /*
452 * Kinds of horizontal rulers:
453 * 0: inside the table (single or double line with crossings)
454 * 1: inside the table (single or double line with crossings and ends)
455 * 2: inner frame (single line with crossings and ends)
456 * 3: outer frame (single line without crossings with ends)
457 */
458 static void
459 tbl_hrule(struct termp *tp, const struct tbl_span *sp, int kind)
460 {
461 const struct tbl_cell *cp, *cpn, *cpp;
462 const struct roffcol *col;
463 int vert;
464 char cross, line, stdcross, stdline;
465
466 stdline = (kind < 2 && TBL_SPAN_DHORIZ == sp->pos) ? '=' : '-';
467 stdcross = (kind < 3) ? '+' : '-';
468
469 cp = sp->layout->first;
470 cpp = kind || sp->prev == NULL ? NULL : sp->prev->layout->first;
471 if (cpp == cp)
472 cpp = NULL;
473 cpn = kind > 1 || sp->next == NULL ? NULL : sp->next->layout->first;
474 if (cpn == cp)
475 cpn = NULL;
476 if (kind)
477 term_word(tp,
478 cpn == NULL || cpn->pos != TBL_CELL_DOWN ? "+" : "|");
479 for (;;) {
480 col = tp->tbl.cols + cp->col;
481 if (cpn == NULL || cpn->pos != TBL_CELL_DOWN) {
482 line = stdline;
483 cross = stdcross;
484 } else {
485 line = ' ';
486 cross = (kind < 3) ? '|' : ' ';
487 }
488 tbl_char(tp, line, col->width + col->spacing / 2);
489 vert = cp->vert;
490 if ((cp = cp->next) == NULL)
491 break;
492 if (cpp != NULL) {
493 if (vert < cpp->vert)
494 vert = cpp->vert;
495 cpp = cpp->next;
496 }
497 if (cpn != NULL) {
498 if (vert < cpn->vert)
499 vert = cpn->vert;
500 cpn = cpn->next;
501 }
502 if (cpn == NULL || cpn->pos != TBL_CELL_DOWN) {
503 line = stdline;
504 cross = stdcross;
505 } else
506 line = ' ';
507 if (sp->opts->opts & TBL_OPT_ALLBOX && !vert)
508 vert = 1;
509 if (col->spacing)
510 tbl_char(tp, vert ? cross : line, 1);
511 if (col->spacing > 2)
512 tbl_char(tp, vert > 1 ? cross : line, 1);
513 if (col->spacing > 4)
514 tbl_char(tp, line, (col->spacing - 3) / 2);
515 }
516 if (kind) {
517 term_word(tp,
518 cpn == NULL || cpn->pos != TBL_CELL_DOWN ? "+" : "|");
519 term_flushln(tp);
520 }
521 }
522
523 static void
524 tbl_data(struct termp *tp, const struct tbl_opts *opts,
525 const struct tbl_cell *cp, const struct tbl_dat *dp,
526 const struct roffcol *col)
527 {
528 switch (cp->pos) {
529 case TBL_CELL_HORIZ:
530 tbl_char(tp, '-', col->width);
531 return;
532 case TBL_CELL_DHORIZ:
533 tbl_char(tp, '=', col->width);
534 return;
535 default:
536 break;
537 }
538
539 if (dp == NULL)
540 return;
541
542 switch (dp->pos) {
543 case TBL_DATA_NONE:
544 return;
545 case TBL_DATA_HORIZ:
546 case TBL_DATA_NHORIZ:
547 tbl_char(tp, '-', col->width);
548 return;
549 case TBL_DATA_NDHORIZ:
550 case TBL_DATA_DHORIZ:
551 tbl_char(tp, '=', col->width);
552 return;
553 default:
554 break;
555 }
556
557 switch (cp->pos) {
558 case TBL_CELL_LONG:
559 case TBL_CELL_CENTRE:
560 case TBL_CELL_LEFT:
561 case TBL_CELL_RIGHT:
562 tbl_literal(tp, dp, col);
563 break;
564 case TBL_CELL_NUMBER:
565 tbl_number(tp, opts, dp, col);
566 break;
567 case TBL_CELL_DOWN:
568 case TBL_CELL_SPAN:
569 break;
570 default:
571 abort();
572 }
573 }
574
575 static void
576 tbl_char(struct termp *tp, char c, size_t len)
577 {
578 size_t i, sz;
579 char cp[2];
580
581 cp[0] = c;
582 cp[1] = '\0';
583
584 sz = term_strlen(tp, cp);
585
586 for (i = 0; i < len; i += sz)
587 term_word(tp, cp);
588 }
589
590 static void
591 tbl_literal(struct termp *tp, const struct tbl_dat *dp,
592 const struct roffcol *col)
593 {
594 size_t len, padl, padr, width;
595 int ic, spans;
596
597 assert(dp->string);
598 len = term_strlen(tp, dp->string);
599 width = col->width;
600 ic = dp->layout->col;
601 spans = dp->spans;
602 while (spans--)
603 width += tp->tbl.cols[++ic].width + 3;
604
605 padr = width > len ? width - len : 0;
606 padl = 0;
607
608 switch (dp->layout->pos) {
609 case TBL_CELL_LONG:
610 padl = term_len(tp, 1);
611 padr = padr > padl ? padr - padl : 0;
612 break;
613 case TBL_CELL_CENTRE:
614 if (2 > padr)
615 break;
616 padl = padr / 2;
617 padr -= padl;
618 break;
619 case TBL_CELL_RIGHT:
620 padl = padr;
621 padr = 0;
622 break;
623 default:
624 break;
625 }
626
627 tbl_char(tp, ASCII_NBRSP, padl);
628 tbl_word(tp, dp);
629 tbl_char(tp, ASCII_NBRSP, padr);
630 }
631
632 static void
633 tbl_number(struct termp *tp, const struct tbl_opts *opts,
634 const struct tbl_dat *dp,
635 const struct roffcol *col)
636 {
637 const char *cp, *lastdigit, *lastpoint;
638 size_t intsz, padl, totsz;
639 char buf[2];
640
641 /*
642 * Almost the same code as in tblcalc_number():
643 * First find the position of the decimal point.
644 */
645
646 assert(dp->string);
647 lastdigit = lastpoint = NULL;
648 for (cp = dp->string; cp[0] != '\0'; cp++) {
649 if (cp[0] == '\\' && cp[1] == '&') {
650 lastdigit = lastpoint = cp;
651 break;
652 } else if (cp[0] == opts->decimal &&
653 (isdigit((unsigned char)cp[1]) ||
654 (cp > dp->string && isdigit((unsigned char)cp[-1]))))
655 lastpoint = cp;
656 else if (isdigit((unsigned char)cp[0]))
657 lastdigit = cp;
658 }
659
660 /* Then measure both widths. */
661
662 padl = 0;
663 totsz = term_strlen(tp, dp->string);
664 if (lastdigit != NULL) {
665 if (lastpoint == NULL)
666 lastpoint = lastdigit + 1;
667 intsz = 0;
668 buf[1] = '\0';
669 for (cp = dp->string; cp < lastpoint; cp++) {
670 buf[0] = cp[0];
671 intsz += term_strlen(tp, buf);
672 }
673
674 /*
675 * Pad left to match the decimal position,
676 * but avoid exceeding the total column width.
677 */
678
679 if (col->decimal > intsz && col->width > totsz) {
680 padl = col->decimal - intsz;
681 if (padl + totsz > col->width)
682 padl = col->width - totsz;
683 }
684
685 /* If it is not a number, simply center the string. */
686
687 } else if (col->width > totsz)
688 padl = (col->width - totsz) / 2;
689
690 tbl_char(tp, ASCII_NBRSP, padl);
691 tbl_word(tp, dp);
692
693 /* Pad right to fill the column. */
694
695 if (col->width > padl + totsz)
696 tbl_char(tp, ASCII_NBRSP, col->width - padl - totsz);
697 }
698
699 static void
700 tbl_word(struct termp *tp, const struct tbl_dat *dp)
701 {
702 int prev_font;
703
704 prev_font = tp->fonti;
705 if (dp->layout->flags & TBL_CELL_BOLD)
706 term_fontpush(tp, TERMFONT_BOLD);
707 else if (dp->layout->flags & TBL_CELL_ITALIC)
708 term_fontpush(tp, TERMFONT_UNDER);
709
710 term_word(tp, dp->string);
711
712 term_fontpopq(tp, prev_font);
713 }