]>
git.cameronkatri.com Git - mandoc.git/blob - out.c
1 /* $Id: out.c,v 1.80 2021/08/10 12:55:03 schwarze Exp $ */
3 * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2011,2014,2015,2017,2018 Ingo Schwarze <schwarze@openbsd.org>
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.
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.
20 #include <sys/types.h>
30 #include "mandoc_aux.h"
36 struct tbl_colgroup
*next
;
42 static size_t tblcalc_data(struct rofftbl
*, struct roffcol
*,
43 const struct tbl_opts
*, const struct tbl_dat
*,
45 static size_t tblcalc_literal(struct rofftbl
*, struct roffcol
*,
46 const struct tbl_dat
*, size_t);
47 static size_t tblcalc_number(struct rofftbl
*, struct roffcol
*,
48 const struct tbl_opts
*, const struct tbl_dat
*);
52 * Parse the *src string and store a scaling unit into *dst.
53 * If the string doesn't specify the unit, use the default.
54 * If no default is specified, fail.
55 * Return a pointer to the byte after the last byte used,
56 * or NULL on total failure.
59 a2roffsu(const char *src
, struct roffsu
*dst
, enum roffscale def
)
63 dst
->unit
= def
== SCALE_MAX
? SCALE_BU
: def
;
64 dst
->scale
= strtod(src
, &endptr
);
101 if (SCALE_MAX
== def
)
110 * Calculate the abstract widths and decimal positions of columns in a
111 * table. This routine allocates the columns structures then runs over
112 * all rows and cells in the table. The function pointers in "tbl" are
113 * used for the actual width calculations.
116 tblcalc(struct rofftbl
*tbl
, const struct tbl_span
*sp_first
,
117 size_t offset
, size_t rmargin
)
120 const struct tbl_opts
*opts
;
121 const struct tbl_span
*sp
;
122 const struct tbl_dat
*dp
;
124 struct tbl_colgroup
*first_group
, **gp
, *g
;
126 size_t ewidth
, min1
, min2
, wanted
, width
, xwidth
;
127 int done
, icol
, maxcol
, necol
, nxcol
, quirkcol
;
130 * Allocate the master column specifiers. These will hold the
131 * widths and decimal positions for all cells in the column. It
132 * must be freed and nullified by the caller.
135 assert(tbl
->cols
== NULL
);
136 tbl
->cols
= mandoc_calloc((size_t)sp_first
->opts
->cols
,
137 sizeof(struct roffcol
));
138 opts
= sp_first
->opts
;
142 for (sp
= sp_first
; sp
!= NULL
; sp
= sp
->next
) {
143 if (sp
->pos
!= TBL_SPAN_DATA
)
147 * Account for the data cells in the layout, matching it
148 * to data cells in the data section.
152 for (dp
= sp
->first
; dp
!= NULL
; dp
= dp
->next
) {
153 icol
= dp
->layout
->col
;
154 while (maxcol
< icol
+ dp
->hspans
)
155 tbl
->cols
[++maxcol
].spacing
= SIZE_MAX
;
156 col
= tbl
->cols
+ icol
;
157 col
->flags
|= dp
->layout
->flags
;
158 if (dp
->layout
->flags
& TBL_CELL_WIGN
)
161 /* Handle explicit width specifications. */
163 if (dp
->layout
->wstr
!= NULL
&&
164 dp
->layout
->width
== 0 &&
165 a2roffsu(dp
->layout
->wstr
, &su
, SCALE_EN
)
168 (*tbl
->sulen
)(&su
, tbl
->arg
);
169 if (col
->width
< dp
->layout
->width
)
170 col
->width
= dp
->layout
->width
;
171 if (dp
->layout
->spacing
!= SIZE_MAX
&&
172 (col
->spacing
== SIZE_MAX
||
173 col
->spacing
< dp
->layout
->spacing
))
174 col
->spacing
= dp
->layout
->spacing
;
177 * Calculate an automatic width.
178 * Except for spanning cells, apply it.
181 width
= tblcalc_data(tbl
,
182 dp
->hspans
== 0 ? col
: NULL
,
185 dp
->layout
->width
? dp
->layout
->width
:
186 rmargin
? (rmargin
+ sp
->opts
->cols
/ 2)
187 / (sp
->opts
->cols
+ 1) : 0);
192 * Build an ordered, singly linked list
193 * of all groups of columns joined by spans,
194 * recording the minimum width for each group.
197 while (*gp
!= NULL
&& ((*gp
)->startcol
< icol
||
198 (*gp
)->endcol
< icol
+ dp
->hspans
))
200 if (*gp
== NULL
|| (*gp
)->startcol
> icol
||
201 (*gp
)->endcol
> icol
+ dp
->hspans
) {
202 g
= mandoc_malloc(sizeof(*g
));
206 g
->endcol
= icol
+ dp
->hspans
;
208 } else if ((*gp
)->wanted
< width
)
209 (*gp
)->wanted
= width
;
214 * The minimum width of columns explicitly specified
215 * in the layout is 1n.
218 if (maxcol
< sp_first
->opts
->cols
- 1)
219 maxcol
= sp_first
->opts
->cols
- 1;
220 for (icol
= 0; icol
<= maxcol
; icol
++) {
221 col
= tbl
->cols
+ icol
;
226 * Column spacings are needed for span width
227 * calculations, so set the default values now.
230 if (col
->spacing
== SIZE_MAX
|| icol
== maxcol
)
235 * Replace the minimum widths with the missing widths,
236 * and dismiss groups that are already wide enough.
240 while ((g
= *gp
) != NULL
) {
242 for (icol
= g
->startcol
; icol
<= g
->endcol
; icol
++) {
243 width
= tbl
->cols
[icol
].width
;
244 if (icol
< g
->endcol
)
245 width
+= tbl
->cols
[icol
].spacing
;
246 if (g
->wanted
<= width
) {
250 (*gp
)->wanted
-= width
;
259 colwidth
= mandoc_reallocarray(NULL
, maxcol
+ 1, sizeof(*colwidth
));
260 while (first_group
!= NULL
) {
263 * Rebuild the array of the widths of all columns
264 * participating in spans that require expansion.
267 for (icol
= 0; icol
<= maxcol
; icol
++)
268 colwidth
[icol
] = SIZE_MAX
;
269 for (g
= first_group
; g
!= NULL
; g
= g
->next
)
270 for (icol
= g
->startcol
; icol
<= g
->endcol
; icol
++)
271 colwidth
[icol
] = tbl
->cols
[icol
].width
;
274 * Find the smallest and second smallest column width
275 * among the columns which may need expamsion.
278 min1
= min2
= SIZE_MAX
;
279 for (icol
= 0; icol
<= maxcol
; icol
++) {
280 if (min1
> colwidth
[icol
]) {
282 min1
= colwidth
[icol
];
283 } else if (min1
< colwidth
[icol
] &&
284 min2
> colwidth
[icol
])
285 min2
= colwidth
[icol
];
289 * Find the minimum wanted width
290 * for any one of the narrowest columns,
291 * and mark the columns wanting that width.
295 for (g
= first_group
; g
!= NULL
; g
= g
->next
) {
297 for (icol
= g
->startcol
; icol
<= g
->endcol
; icol
++)
298 if (tbl
->cols
[icol
].width
== min1
)
302 width
= min1
+ (g
->wanted
- 1) / necol
+ 1;
307 for (icol
= g
->startcol
; icol
<= g
->endcol
; icol
++)
308 if (colwidth
[icol
] == min1
||
309 (colwidth
[icol
] < min2
&&
310 colwidth
[icol
] > width
))
311 colwidth
[icol
] = width
;
314 /* Record the effect of the widening on the group list. */
317 while ((g
= *gp
) != NULL
) {
319 for (icol
= g
->startcol
; icol
<= g
->endcol
; icol
++) {
320 if (colwidth
[icol
] != wanted
||
321 tbl
->cols
[icol
].width
== wanted
)
323 if (g
->wanted
<= wanted
- min1
) {
327 g
->wanted
-= wanted
- min1
;
336 /* Record the effect of the widening on the columns. */
338 for (icol
= 0; icol
<= maxcol
; icol
++)
339 if (colwidth
[icol
] == wanted
)
340 tbl
->cols
[icol
].width
= wanted
;
345 * Align numbers with text.
346 * Count columns to equalize and columns to maximize.
347 * Find maximum width of the columns to equalize.
348 * Find total width of the columns *not* to maximize.
353 for (icol
= 0; icol
<= maxcol
; icol
++) {
354 col
= tbl
->cols
+ icol
;
355 if (col
->width
> col
->nwidth
)
356 col
->decimal
+= (col
->width
- col
->nwidth
) / 2;
358 col
->width
= col
->nwidth
;
359 if (col
->flags
& TBL_CELL_EQUAL
) {
361 if (ewidth
< col
->width
)
364 if (col
->flags
& TBL_CELL_WMAX
)
367 xwidth
+= col
->width
;
371 * Equalize columns, if requested for any of them.
372 * Update total width of the columns not to maximize.
376 for (icol
= 0; icol
<= maxcol
; icol
++) {
377 col
= tbl
->cols
+ icol
;
378 if ( ! (col
->flags
& TBL_CELL_EQUAL
))
380 if (col
->width
== ewidth
)
382 if (nxcol
&& rmargin
)
383 xwidth
+= ewidth
- col
->width
;
389 * If there are any columns to maximize, find the total
390 * available width, deducting 3n margins between columns.
391 * Distribute the available width evenly.
394 if (nxcol
&& rmargin
) {
396 (opts
->opts
& (TBL_OPT_BOX
| TBL_OPT_DBOX
) ?
397 2 : !!opts
->lvert
+ !!opts
->rvert
);
398 if (rmargin
<= offset
+ xwidth
)
400 xwidth
= rmargin
- offset
- xwidth
;
403 * Emulate a bug in GNU tbl width calculation that
404 * manifests itself for large numbers of x-columns.
405 * Emulating it for 5 x-columns gives identical
406 * behaviour for up to 6 x-columns.
410 quirkcol
= xwidth
% nxcol
+ 2;
411 if (quirkcol
!= 3 && quirkcol
!= 4)
418 for (icol
= 0; icol
<= maxcol
; icol
++) {
419 col
= tbl
->cols
+ icol
;
420 if ( ! (col
->flags
& TBL_CELL_WMAX
))
422 col
->width
= (double)xwidth
* ++necol
/ nxcol
424 if (necol
== quirkcol
)
426 ewidth
+= col
->width
;
432 tblcalc_data(struct rofftbl
*tbl
, struct roffcol
*col
,
433 const struct tbl_opts
*opts
, const struct tbl_dat
*dp
, size_t mw
)
437 /* Branch down into data sub-types. */
439 switch (dp
->layout
->pos
) {
441 case TBL_CELL_DHORIZ
:
442 sz
= (*tbl
->len
)(1, tbl
->arg
);
443 if (col
!= NULL
&& col
->width
< sz
)
447 case TBL_CELL_CENTRE
:
450 return tblcalc_literal(tbl
, col
, dp
, mw
);
451 case TBL_CELL_NUMBER
:
452 return tblcalc_number(tbl
, col
, opts
, dp
);
461 tblcalc_literal(struct rofftbl
*tbl
, struct roffcol
*col
,
462 const struct tbl_dat
*dp
, size_t mw
)
464 const char *str
; /* Beginning of the first line. */
465 const char *beg
; /* Beginning of the current line. */
466 char *end
; /* End of the current line. */
467 size_t lsz
; /* Length of the current line. */
468 size_t wsz
; /* Length of the current word. */
469 size_t msz
; /* Length of the longest line. */
471 if (dp
->string
== NULL
|| *dp
->string
== '\0')
473 str
= mw
? mandoc_strdup(dp
->string
) : dp
->string
;
475 for (beg
= str
; beg
!= NULL
&& *beg
!= '\0'; beg
= end
) {
476 end
= mw
? strchr(beg
, ' ') : NULL
;
482 wsz
= (*tbl
->slen
)(beg
, tbl
->arg
);
483 if (mw
&& lsz
&& lsz
+ 1 + wsz
<= mw
)
492 if (col
!= NULL
&& col
->width
< msz
)
498 tblcalc_number(struct rofftbl
*tbl
, struct roffcol
*col
,
499 const struct tbl_opts
*opts
, const struct tbl_dat
*dp
)
501 const char *cp
, *lastdigit
, *lastpoint
;
505 if (dp
->string
== NULL
|| *dp
->string
== '\0')
508 totsz
= (*tbl
->slen
)(dp
->string
, tbl
->arg
);
513 * Find the last digit and
514 * the last decimal point that is adjacent to a digit.
515 * The alignment indicator "\&" overrides everything.
518 lastdigit
= lastpoint
= NULL
;
519 for (cp
= dp
->string
; cp
[0] != '\0'; cp
++) {
520 if (cp
[0] == '\\' && cp
[1] == '&') {
521 lastdigit
= lastpoint
= cp
;
523 } else if (cp
[0] == opts
->decimal
&&
524 (isdigit((unsigned char)cp
[1]) ||
525 (cp
> dp
->string
&& isdigit((unsigned char)cp
[-1]))))
527 else if (isdigit((unsigned char)cp
[0]))
531 /* Not a number, treat as a literal string. */
533 if (lastdigit
== NULL
) {
534 if (col
!= NULL
&& col
->width
< totsz
)
539 /* Measure the width of the integer part. */
541 if (lastpoint
== NULL
)
542 lastpoint
= lastdigit
+ 1;
545 for (cp
= dp
->string
; cp
< lastpoint
; cp
++) {
547 intsz
+= (*tbl
->slen
)(buf
, tbl
->arg
);
551 * If this number has more integer digits than all numbers
552 * seen on earlier lines, shift them all to the right.
553 * If it has fewer, shift this number to the right.
556 if (intsz
> col
->decimal
) {
557 col
->nwidth
+= intsz
- col
->decimal
;
558 col
->decimal
= intsz
;
560 totsz
+= col
->decimal
- intsz
;
562 /* Update the maximum total width seen so far. */
564 if (totsz
> col
->nwidth
)