Annotation of src/usr.bin/mandoc/out.c, Revision 1.54
1.54 ! schwarze 1: /* $OpenBSD: out.c,v 1.53 2021/09/07 14:50:56 schwarze Exp $ */
1.1 schwarze 2: /*
1.9 schwarze 3: * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.53 schwarze 4: * Copyright (c) 2011, 2014, 2015, 2017, 2018, 2019, 2021
5: * Ingo Schwarze <schwarze@openbsd.org>
1.1 schwarze 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 AUTHOR DISCLAIMS ALL WARRANTIES
12: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 <sys/types.h>
20:
1.2 schwarze 21: #include <assert.h>
1.45 schwarze 22: #include <ctype.h>
1.42 schwarze 23: #include <stdint.h>
1.52 schwarze 24: #include <stdio.h>
1.1 schwarze 25: #include <stdlib.h>
1.2 schwarze 26: #include <string.h>
27: #include <time.h>
1.1 schwarze 28:
1.20 schwarze 29: #include "mandoc_aux.h"
1.52 schwarze 30: #include "mandoc.h"
1.48 schwarze 31: #include "tbl.h"
1.1 schwarze 32: #include "out.h"
33:
1.47 schwarze 34: struct tbl_colgroup {
35: struct tbl_colgroup *next;
36: size_t wanted;
37: int startcol;
38: int endcol;
39: };
40:
41: static size_t tblcalc_data(struct rofftbl *, struct roffcol *,
1.37 schwarze 42: const struct tbl_opts *, const struct tbl_dat *,
43: size_t);
1.47 schwarze 44: static size_t tblcalc_literal(struct rofftbl *, struct roffcol *,
1.37 schwarze 45: const struct tbl_dat *, size_t);
1.47 schwarze 46: static size_t tblcalc_number(struct rofftbl *, struct roffcol *,
1.18 schwarze 47: const struct tbl_opts *, const struct tbl_dat *);
1.9 schwarze 48:
1.21 schwarze 49:
50: /*
1.27 schwarze 51: * Parse the *src string and store a scaling unit into *dst.
52: * If the string doesn't specify the unit, use the default.
53: * If no default is specified, fail.
1.36 schwarze 54: * Return a pointer to the byte after the last byte used,
55: * or NULL on total failure.
1.1 schwarze 56: */
1.36 schwarze 57: const char *
1.1 schwarze 58: a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
59: {
1.27 schwarze 60: char *endptr;
1.1 schwarze 61:
1.28 schwarze 62: dst->unit = def == SCALE_MAX ? SCALE_BU : def;
63: dst->scale = strtod(src, &endptr);
64: if (endptr == src)
1.36 schwarze 65: return NULL;
1.1 schwarze 66:
1.28 schwarze 67: switch (*endptr++) {
1.21 schwarze 68: case 'c':
1.28 schwarze 69: dst->unit = SCALE_CM;
1.1 schwarze 70: break;
1.21 schwarze 71: case 'i':
1.28 schwarze 72: dst->unit = SCALE_IN;
73: break;
74: case 'f':
75: dst->unit = SCALE_FS;
76: break;
77: case 'M':
78: dst->unit = SCALE_MM;
79: break;
80: case 'm':
81: dst->unit = SCALE_EM;
82: break;
83: case 'n':
84: dst->unit = SCALE_EN;
1.1 schwarze 85: break;
1.21 schwarze 86: case 'P':
1.28 schwarze 87: dst->unit = SCALE_PC;
1.1 schwarze 88: break;
1.21 schwarze 89: case 'p':
1.28 schwarze 90: dst->unit = SCALE_PT;
1.1 schwarze 91: break;
1.28 schwarze 92: case 'u':
93: dst->unit = SCALE_BU;
1.1 schwarze 94: break;
1.21 schwarze 95: case 'v':
1.28 schwarze 96: dst->unit = SCALE_VS;
1.1 schwarze 97: break;
1.40 schwarze 98: default:
1.28 schwarze 99: endptr--;
1.1 schwarze 100: if (SCALE_MAX == def)
1.36 schwarze 101: return NULL;
1.28 schwarze 102: dst->unit = def;
1.1 schwarze 103: break;
104: }
1.36 schwarze 105: return endptr;
1.3 schwarze 106: }
1.9 schwarze 107:
108: /*
109: * Calculate the abstract widths and decimal positions of columns in a
110: * table. This routine allocates the columns structures then runs over
111: * all rows and cells in the table. The function pointers in "tbl" are
112: * used for the actual width calculations.
113: */
114: void
1.47 schwarze 115: tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
1.38 schwarze 116: size_t offset, size_t rmargin)
1.9 schwarze 117: {
1.37 schwarze 118: struct roffsu su;
1.30 schwarze 119: const struct tbl_opts *opts;
1.47 schwarze 120: const struct tbl_span *sp;
1.9 schwarze 121: const struct tbl_dat *dp;
122: struct roffcol *col;
1.47 schwarze 123: struct tbl_colgroup *first_group, **gp, *g;
124: size_t ewidth, min1, min2, wanted, width, xwidth;
125: int done, icol, maxcol, necol, nxcol, quirkcol;
1.9 schwarze 126:
127: /*
128: * Allocate the master column specifiers. These will hold the
129: * widths and decimal positions for all cells in the column. It
130: * must be freed and nullified by the caller.
131: */
132:
1.47 schwarze 133: assert(tbl->cols == NULL);
134: tbl->cols = mandoc_calloc((size_t)sp_first->opts->cols,
1.21 schwarze 135: sizeof(struct roffcol));
1.47 schwarze 136: opts = sp_first->opts;
1.9 schwarze 137:
1.47 schwarze 138: maxcol = -1;
139: first_group = NULL;
140: for (sp = sp_first; sp != NULL; sp = sp->next) {
141: if (sp->pos != TBL_SPAN_DATA)
1.9 schwarze 142: continue;
1.47 schwarze 143:
1.9 schwarze 144: /*
145: * Account for the data cells in the layout, matching it
146: * to data cells in the data section.
147: */
1.47 schwarze 148:
149: gp = &first_group;
150: for (dp = sp->first; dp != NULL; dp = dp->next) {
1.31 schwarze 151: icol = dp->layout->col;
1.50 schwarze 152: while (maxcol < icol + dp->hspans)
1.42 schwarze 153: tbl->cols[++maxcol].spacing = SIZE_MAX;
1.24 schwarze 154: col = tbl->cols + icol;
155: col->flags |= dp->layout->flags;
156: if (dp->layout->flags & TBL_CELL_WIGN)
157: continue;
1.47 schwarze 158:
159: /* Handle explicit width specifications. */
160:
1.37 schwarze 161: if (dp->layout->wstr != NULL &&
162: dp->layout->width == 0 &&
163: a2roffsu(dp->layout->wstr, &su, SCALE_EN)
164: != NULL)
165: dp->layout->width =
166: (*tbl->sulen)(&su, tbl->arg);
167: if (col->width < dp->layout->width)
168: col->width = dp->layout->width;
1.42 schwarze 169: if (dp->layout->spacing != SIZE_MAX &&
170: (col->spacing == SIZE_MAX ||
171: col->spacing < dp->layout->spacing))
172: col->spacing = dp->layout->spacing;
1.47 schwarze 173:
174: /*
175: * Calculate an automatic width.
176: * Except for spanning cells, apply it.
177: */
178:
179: width = tblcalc_data(tbl,
180: dp->hspans == 0 ? col : NULL,
181: opts, dp,
1.39 schwarze 182: dp->block == 0 ? 0 :
183: dp->layout->width ? dp->layout->width :
1.41 schwarze 184: rmargin ? (rmargin + sp->opts->cols / 2)
185: / (sp->opts->cols + 1) : 0);
1.47 schwarze 186: if (dp->hspans == 0)
187: continue;
188:
189: /*
190: * Build an ordered, singly linked list
191: * of all groups of columns joined by spans,
192: * recording the minimum width for each group.
193: */
194:
195: while (*gp != NULL && ((*gp)->startcol < icol ||
196: (*gp)->endcol < icol + dp->hspans))
197: gp = &(*gp)->next;
198: if (*gp == NULL || (*gp)->startcol > icol ||
199: (*gp)->endcol > icol + dp->hspans) {
200: g = mandoc_malloc(sizeof(*g));
201: g->next = *gp;
202: g->wanted = width;
203: g->startcol = icol;
204: g->endcol = icol + dp->hspans;
205: *gp = g;
206: } else if ((*gp)->wanted < width)
207: (*gp)->wanted = width;
1.24 schwarze 208: }
209: }
210:
211: /*
1.51 schwarze 212: * The minimum width of columns explicitly specified
213: * in the layout is 1n.
1.47 schwarze 214: */
215:
1.51 schwarze 216: if (maxcol < sp_first->opts->cols - 1)
217: maxcol = sp_first->opts->cols - 1;
218: for (icol = 0; icol <= maxcol; icol++) {
219: col = tbl->cols + icol;
220: if (col->width < 1)
221: col->width = 1;
222:
223: /*
224: * Column spacings are needed for span width
225: * calculations, so set the default values now.
226: */
227:
228: if (col->spacing == SIZE_MAX || icol == maxcol)
229: col->spacing = 3;
230: }
1.47 schwarze 231:
232: /*
233: * Replace the minimum widths with the missing widths,
234: * and dismiss groups that are already wide enough.
235: */
236:
237: gp = &first_group;
238: while ((g = *gp) != NULL) {
239: done = 0;
240: for (icol = g->startcol; icol <= g->endcol; icol++) {
241: width = tbl->cols[icol].width;
242: if (icol < g->endcol)
243: width += tbl->cols[icol].spacing;
244: if (g->wanted <= width) {
245: done = 1;
246: break;
247: } else
248: (*gp)->wanted -= width;
249: }
250: if (done) {
251: *gp = g->next;
252: free(g);
253: } else
254: gp = &(*gp)->next;
255: }
256:
257: while (first_group != NULL) {
258:
259: /*
260: * Find the smallest and second smallest column width
261: * among the columns which may need expamsion.
262: */
263:
264: min1 = min2 = SIZE_MAX;
265: for (icol = 0; icol <= maxcol; icol++) {
1.54 ! schwarze 266: width = tbl->cols[icol].width;
! 267: if (min1 > width) {
1.47 schwarze 268: min2 = min1;
1.54 ! schwarze 269: min1 = width;
! 270: } else if (min1 < width && min2 > width)
! 271: min2 = width;
1.47 schwarze 272: }
273:
274: /*
275: * Find the minimum wanted width
276: * for any one of the narrowest columns,
277: * and mark the columns wanting that width.
278: */
279:
280: wanted = min2;
281: for (g = first_group; g != NULL; g = g->next) {
282: necol = 0;
283: for (icol = g->startcol; icol <= g->endcol; icol++)
284: if (tbl->cols[icol].width == min1)
285: necol++;
286: if (necol == 0)
287: continue;
288: width = min1 + (g->wanted - 1) / necol + 1;
289: if (width > min2)
290: width = min2;
291: if (wanted > width)
292: wanted = width;
293: }
294:
1.54 ! schwarze 295: /* Record the effect of the widening. */
1.47 schwarze 296:
297: gp = &first_group;
298: while ((g = *gp) != NULL) {
299: done = 0;
300: for (icol = g->startcol; icol <= g->endcol; icol++) {
1.54 ! schwarze 301: if (tbl->cols[icol].width != min1)
1.47 schwarze 302: continue;
303: if (g->wanted <= wanted - min1) {
1.54 ! schwarze 304: tbl->cols[icol].width += g->wanted;
1.47 schwarze 305: done = 1;
306: break;
307: }
1.54 ! schwarze 308: tbl->cols[icol].width = wanted;
1.47 schwarze 309: g->wanted -= wanted - min1;
310: }
311: if (done) {
312: *gp = g->next;
313: free(g);
314: } else
315: gp = &(*gp)->next;
316: }
317: }
318:
319: /*
1.44 schwarze 320: * Align numbers with text.
1.24 schwarze 321: * Count columns to equalize and columns to maximize.
322: * Find maximum width of the columns to equalize.
323: * Find total width of the columns *not* to maximize.
324: */
325:
326: necol = nxcol = 0;
327: ewidth = xwidth = 0;
328: for (icol = 0; icol <= maxcol; icol++) {
329: col = tbl->cols + icol;
1.44 schwarze 330: if (col->width > col->nwidth)
331: col->decimal += (col->width - col->nwidth) / 2;
1.24 schwarze 332: if (col->flags & TBL_CELL_EQUAL) {
333: necol++;
334: if (ewidth < col->width)
335: ewidth = col->width;
336: }
337: if (col->flags & TBL_CELL_WMAX)
338: nxcol++;
339: else
340: xwidth += col->width;
341: }
342:
343: /*
344: * Equalize columns, if requested for any of them.
345: * Update total width of the columns not to maximize.
346: */
347:
348: if (necol) {
349: for (icol = 0; icol <= maxcol; icol++) {
350: col = tbl->cols + icol;
351: if ( ! (col->flags & TBL_CELL_EQUAL))
352: continue;
353: if (col->width == ewidth)
354: continue;
1.38 schwarze 355: if (nxcol && rmargin)
1.24 schwarze 356: xwidth += ewidth - col->width;
357: col->width = ewidth;
358: }
359: }
360:
361: /*
362: * If there are any columns to maximize, find the total
363: * available width, deducting 3n margins between columns.
364: * Distribute the available width evenly.
365: */
366:
1.38 schwarze 367: if (nxcol && rmargin) {
1.35 schwarze 368: xwidth += 3*maxcol +
1.30 schwarze 369: (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ?
370: 2 : !!opts->lvert + !!opts->rvert);
1.38 schwarze 371: if (rmargin <= offset + xwidth)
1.35 schwarze 372: return;
1.38 schwarze 373: xwidth = rmargin - offset - xwidth;
1.30 schwarze 374:
375: /*
376: * Emulate a bug in GNU tbl width calculation that
377: * manifests itself for large numbers of x-columns.
378: * Emulating it for 5 x-columns gives identical
379: * behaviour for up to 6 x-columns.
380: */
381:
382: if (nxcol == 5) {
383: quirkcol = xwidth % nxcol + 2;
384: if (quirkcol != 3 && quirkcol != 4)
385: quirkcol = -1;
386: } else
387: quirkcol = -1;
388:
389: necol = 0;
390: ewidth = 0;
1.24 schwarze 391: for (icol = 0; icol <= maxcol; icol++) {
392: col = tbl->cols + icol;
393: if ( ! (col->flags & TBL_CELL_WMAX))
394: continue;
1.30 schwarze 395: col->width = (double)xwidth * ++necol / nxcol
396: - ewidth + 0.4995;
397: if (necol == quirkcol)
398: col->width--;
399: ewidth += col->width;
1.9 schwarze 400: }
401: }
402: }
403:
1.47 schwarze 404: static size_t
1.9 schwarze 405: tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
1.37 schwarze 406: const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw)
1.9 schwarze 407: {
408: size_t sz;
409:
410: /* Branch down into data sub-types. */
411:
412: switch (dp->layout->pos) {
1.21 schwarze 413: case TBL_CELL_HORIZ:
414: case TBL_CELL_DHORIZ:
1.9 schwarze 415: sz = (*tbl->len)(1, tbl->arg);
1.47 schwarze 416: if (col != NULL && col->width < sz)
1.9 schwarze 417: col->width = sz;
1.47 schwarze 418: return sz;
1.21 schwarze 419: case TBL_CELL_LONG:
420: case TBL_CELL_CENTRE:
421: case TBL_CELL_LEFT:
422: case TBL_CELL_RIGHT:
1.47 schwarze 423: return tblcalc_literal(tbl, col, dp, mw);
1.21 schwarze 424: case TBL_CELL_NUMBER:
1.47 schwarze 425: return tblcalc_number(tbl, col, opts, dp);
1.21 schwarze 426: case TBL_CELL_DOWN:
1.47 schwarze 427: return 0;
1.9 schwarze 428: default:
429: abort();
430: }
431: }
432:
1.47 schwarze 433: static size_t
1.9 schwarze 434: tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
1.37 schwarze 435: const struct tbl_dat *dp, size_t mw)
1.9 schwarze 436: {
1.37 schwarze 437: const char *str; /* Beginning of the first line. */
438: const char *beg; /* Beginning of the current line. */
439: char *end; /* End of the current line. */
1.38 schwarze 440: size_t lsz; /* Length of the current line. */
441: size_t wsz; /* Length of the current word. */
1.47 schwarze 442: size_t msz; /* Length of the longest line. */
1.37 schwarze 443:
444: if (dp->string == NULL || *dp->string == '\0')
1.47 schwarze 445: return 0;
1.37 schwarze 446: str = mw ? mandoc_strdup(dp->string) : dp->string;
1.47 schwarze 447: msz = lsz = 0;
1.37 schwarze 448: for (beg = str; beg != NULL && *beg != '\0'; beg = end) {
449: end = mw ? strchr(beg, ' ') : NULL;
450: if (end != NULL) {
451: *end++ = '\0';
452: while (*end == ' ')
453: end++;
454: }
1.38 schwarze 455: wsz = (*tbl->slen)(beg, tbl->arg);
456: if (mw && lsz && lsz + 1 + wsz <= mw)
457: lsz += 1 + wsz;
458: else
459: lsz = wsz;
1.47 schwarze 460: if (msz < lsz)
461: msz = lsz;
1.37 schwarze 462: }
463: if (mw)
464: free((void *)str);
1.47 schwarze 465: if (col != NULL && col->width < msz)
466: col->width = msz;
467: return msz;
1.9 schwarze 468: }
469:
1.47 schwarze 470: static size_t
1.9 schwarze 471: tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
1.18 schwarze 472: const struct tbl_opts *opts, const struct tbl_dat *dp)
1.9 schwarze 473: {
1.45 schwarze 474: const char *cp, *lastdigit, *lastpoint;
475: size_t intsz, totsz;
1.9 schwarze 476: char buf[2];
477:
1.45 schwarze 478: if (dp->string == NULL || *dp->string == '\0')
1.47 schwarze 479: return 0;
480:
481: totsz = (*tbl->slen)(dp->string, tbl->arg);
482: if (col == NULL)
483: return totsz;
1.45 schwarze 484:
1.9 schwarze 485: /*
1.45 schwarze 486: * Find the last digit and
487: * the last decimal point that is adjacent to a digit.
488: * The alignment indicator "\&" overrides everything.
1.9 schwarze 489: */
490:
1.45 schwarze 491: lastdigit = lastpoint = NULL;
492: for (cp = dp->string; cp[0] != '\0'; cp++) {
493: if (cp[0] == '\\' && cp[1] == '&') {
494: lastdigit = lastpoint = cp;
495: break;
496: } else if (cp[0] == opts->decimal &&
497: (isdigit((unsigned char)cp[1]) ||
498: (cp > dp->string && isdigit((unsigned char)cp[-1]))))
499: lastpoint = cp;
500: else if (isdigit((unsigned char)cp[0]))
501: lastdigit = cp;
502: }
503:
504: /* Not a number, treat as a literal string. */
505:
506: if (lastdigit == NULL) {
1.47 schwarze 507: if (col != NULL && col->width < totsz)
1.45 schwarze 508: col->width = totsz;
1.47 schwarze 509: return totsz;
1.45 schwarze 510: }
1.9 schwarze 511:
1.45 schwarze 512: /* Measure the width of the integer part. */
1.9 schwarze 513:
1.45 schwarze 514: if (lastpoint == NULL)
515: lastpoint = lastdigit + 1;
516: intsz = 0;
1.9 schwarze 517: buf[1] = '\0';
1.45 schwarze 518: for (cp = dp->string; cp < lastpoint; cp++) {
519: buf[0] = cp[0];
520: intsz += (*tbl->slen)(buf, tbl->arg);
521: }
522:
523: /*
524: * If this number has more integer digits than all numbers
525: * seen on earlier lines, shift them all to the right.
526: * If it has fewer, shift this number to the right.
527: */
1.9 schwarze 528:
1.45 schwarze 529: if (intsz > col->decimal) {
530: col->nwidth += intsz - col->decimal;
531: col->decimal = intsz;
1.9 schwarze 532: } else
1.45 schwarze 533: totsz += col->decimal - intsz;
1.9 schwarze 534:
1.45 schwarze 535: /* Update the maximum total width seen so far. */
1.9 schwarze 536:
1.45 schwarze 537: if (totsz > col->nwidth)
538: col->nwidth = totsz;
1.53 schwarze 539: if (col->nwidth > col->width)
540: col->width = col->nwidth;
1.47 schwarze 541: return totsz;
1.9 schwarze 542: }