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