xref: /openbsd/usr.bin/mandoc/out.c (revision e8a65004)
1*e8a65004Sschwarze /*	$OpenBSD: out.c,v 1.42 2017/06/27 18:23:29 schwarze Exp $ */
24175bdabSschwarze /*
3ec04407bSschwarze  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4eb274914Sschwarze  * Copyright (c) 2011, 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org>
54175bdabSschwarze  *
64175bdabSschwarze  * Permission to use, copy, modify, and distribute this software for any
74175bdabSschwarze  * purpose with or without fee is hereby granted, provided that the above
84175bdabSschwarze  * copyright notice and this permission notice appear in all copies.
94175bdabSschwarze  *
104175bdabSschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
114175bdabSschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
124175bdabSschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
134175bdabSschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
144175bdabSschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
154175bdabSschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
164175bdabSschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
174175bdabSschwarze  */
184175bdabSschwarze #include <sys/types.h>
194175bdabSschwarze 
20b822ca0dSschwarze #include <assert.h>
21*e8a65004Sschwarze #include <stdint.h>
224175bdabSschwarze #include <stdlib.h>
23b822ca0dSschwarze #include <string.h>
24b822ca0dSschwarze #include <time.h>
254175bdabSschwarze 
264f4f7972Sschwarze #include "mandoc_aux.h"
27ec04407bSschwarze #include "mandoc.h"
284175bdabSschwarze #include "out.h"
294175bdabSschwarze 
30ec04407bSschwarze static	void	tblcalc_data(struct rofftbl *, struct roffcol *,
312c3e66c4Sschwarze 			const struct tbl_opts *, const struct tbl_dat *,
322c3e66c4Sschwarze 			size_t);
33ec04407bSschwarze static	void	tblcalc_literal(struct rofftbl *, struct roffcol *,
342c3e66c4Sschwarze 			const struct tbl_dat *, size_t);
35ec04407bSschwarze static	void	tblcalc_number(struct rofftbl *, struct roffcol *,
36c7497e73Sschwarze 			const struct tbl_opts *, const struct tbl_dat *);
37ec04407bSschwarze 
3849aff9f8Sschwarze 
394175bdabSschwarze /*
40a09d548bSschwarze  * Parse the *src string and store a scaling unit into *dst.
41a09d548bSschwarze  * If the string doesn't specify the unit, use the default.
42a09d548bSschwarze  * If no default is specified, fail.
43ecd22486Sschwarze  * Return a pointer to the byte after the last byte used,
44ecd22486Sschwarze  * or NULL on total failure.
454175bdabSschwarze  */
46ecd22486Sschwarze const char *
474175bdabSschwarze a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
484175bdabSschwarze {
49a09d548bSschwarze 	char		*endptr;
504175bdabSschwarze 
5184f230baSschwarze 	dst->unit = def == SCALE_MAX ? SCALE_BU : def;
5284f230baSschwarze 	dst->scale = strtod(src, &endptr);
5384f230baSschwarze 	if (endptr == src)
54ecd22486Sschwarze 		return NULL;
554175bdabSschwarze 
5684f230baSschwarze 	switch (*endptr++) {
5749aff9f8Sschwarze 	case 'c':
5884f230baSschwarze 		dst->unit = SCALE_CM;
594175bdabSschwarze 		break;
6049aff9f8Sschwarze 	case 'i':
6184f230baSschwarze 		dst->unit = SCALE_IN;
624175bdabSschwarze 		break;
6349aff9f8Sschwarze 	case 'f':
6484f230baSschwarze 		dst->unit = SCALE_FS;
654175bdabSschwarze 		break;
6649aff9f8Sschwarze 	case 'M':
6784f230baSschwarze 		dst->unit = SCALE_MM;
6884f230baSschwarze 		break;
6984f230baSschwarze 	case 'm':
7084f230baSschwarze 		dst->unit = SCALE_EM;
714175bdabSschwarze 		break;
7249aff9f8Sschwarze 	case 'n':
7384f230baSschwarze 		dst->unit = SCALE_EN;
744175bdabSschwarze 		break;
7584f230baSschwarze 	case 'P':
7684f230baSschwarze 		dst->unit = SCALE_PC;
7784f230baSschwarze 		break;
7884f230baSschwarze 	case 'p':
7984f230baSschwarze 		dst->unit = SCALE_PT;
8084f230baSschwarze 		break;
8184f230baSschwarze 	case 'u':
8284f230baSschwarze 		dst->unit = SCALE_BU;
8384f230baSschwarze 		break;
8484f230baSschwarze 	case 'v':
8584f230baSschwarze 		dst->unit = SCALE_VS;
8684f230baSschwarze 		break;
874175bdabSschwarze 	default:
8823b9ae24Sschwarze 		endptr--;
8984f230baSschwarze 		if (SCALE_MAX == def)
90ecd22486Sschwarze 			return NULL;
9184f230baSschwarze 		dst->unit = def;
9284f230baSschwarze 		break;
934175bdabSschwarze 	}
94ecd22486Sschwarze 	return endptr;
954175bdabSschwarze }
96b822ca0dSschwarze 
97ec04407bSschwarze /*
98ec04407bSschwarze  * Calculate the abstract widths and decimal positions of columns in a
99ec04407bSschwarze  * table.  This routine allocates the columns structures then runs over
100ec04407bSschwarze  * all rows and cells in the table.  The function pointers in "tbl" are
101ec04407bSschwarze  * used for the actual width calculations.
102ec04407bSschwarze  */
103ec04407bSschwarze void
10446fa2066Sschwarze tblcalc(struct rofftbl *tbl, const struct tbl_span *sp,
105d1c3ec49Sschwarze     size_t offset, size_t rmargin)
106ec04407bSschwarze {
1072c3e66c4Sschwarze 	struct roffsu		 su;
10899d91ba7Sschwarze 	const struct tbl_opts	*opts;
109ec04407bSschwarze 	const struct tbl_dat	*dp;
110ec04407bSschwarze 	struct roffcol		*col;
11146fa2066Sschwarze 	size_t			 ewidth, xwidth;
1120ebcd99eSschwarze 	int			 spans;
11399d91ba7Sschwarze 	int			 icol, maxcol, necol, nxcol, quirkcol;
114ec04407bSschwarze 
115ec04407bSschwarze 	/*
116ec04407bSschwarze 	 * Allocate the master column specifiers.  These will hold the
117ec04407bSschwarze 	 * widths and decimal positions for all cells in the column.  It
118ec04407bSschwarze 	 * must be freed and nullified by the caller.
119ec04407bSschwarze 	 */
120ec04407bSschwarze 
121ec04407bSschwarze 	assert(NULL == tbl->cols);
12249aff9f8Sschwarze 	tbl->cols = mandoc_calloc((size_t)sp->opts->cols,
12349aff9f8Sschwarze 	    sizeof(struct roffcol));
12499d91ba7Sschwarze 	opts = sp->opts;
125ec04407bSschwarze 
126b69c4eb3Sschwarze 	for (maxcol = -1; sp; sp = sp->next) {
127ec04407bSschwarze 		if (TBL_SPAN_DATA != sp->pos)
128ec04407bSschwarze 			continue;
1290ebcd99eSschwarze 		spans = 1;
130ec04407bSschwarze 		/*
131ec04407bSschwarze 		 * Account for the data cells in the layout, matching it
132ec04407bSschwarze 		 * to data cells in the data section.
133ec04407bSschwarze 		 */
134ec04407bSschwarze 		for (dp = sp->first; dp; dp = dp->next) {
1350ebcd99eSschwarze 			/* Do not used spanned cells in the calculation. */
1360ebcd99eSschwarze 			if (0 < --spans)
1370ebcd99eSschwarze 				continue;
1380ebcd99eSschwarze 			spans = dp->spans;
1390ebcd99eSschwarze 			if (1 < spans)
1400ebcd99eSschwarze 				continue;
1415f6d1ba3Sschwarze 			icol = dp->layout->col;
142*e8a65004Sschwarze 			while (maxcol < icol)
143*e8a65004Sschwarze 				tbl->cols[++maxcol].spacing = SIZE_MAX;
14446fa2066Sschwarze 			col = tbl->cols + icol;
14546fa2066Sschwarze 			col->flags |= dp->layout->flags;
14646fa2066Sschwarze 			if (dp->layout->flags & TBL_CELL_WIGN)
14746fa2066Sschwarze 				continue;
1482c3e66c4Sschwarze 			if (dp->layout->wstr != NULL &&
1492c3e66c4Sschwarze 			    dp->layout->width == 0 &&
1502c3e66c4Sschwarze 			    a2roffsu(dp->layout->wstr, &su, SCALE_EN)
1512c3e66c4Sschwarze 			    != NULL)
1522c3e66c4Sschwarze 				dp->layout->width =
1532c3e66c4Sschwarze 				    (*tbl->sulen)(&su, tbl->arg);
1542c3e66c4Sschwarze 			if (col->width < dp->layout->width)
1552c3e66c4Sschwarze 				col->width = dp->layout->width;
156*e8a65004Sschwarze 			if (dp->layout->spacing != SIZE_MAX &&
157*e8a65004Sschwarze 			    (col->spacing == SIZE_MAX ||
158*e8a65004Sschwarze 			     col->spacing < dp->layout->spacing))
159*e8a65004Sschwarze 				col->spacing = dp->layout->spacing;
160d1c3ec49Sschwarze 			tblcalc_data(tbl, col, opts, dp,
161238123d0Sschwarze 			    dp->block == 0 ? 0 :
162238123d0Sschwarze 			    dp->layout->width ? dp->layout->width :
163e6d4e954Sschwarze 			    rmargin ? (rmargin + sp->opts->cols / 2)
164e6d4e954Sschwarze 			    / (sp->opts->cols + 1) : 0);
165ec04407bSschwarze 		}
166ec04407bSschwarze 	}
16746fa2066Sschwarze 
16846fa2066Sschwarze 	/*
16946fa2066Sschwarze 	 * Count columns to equalize and columns to maximize.
17046fa2066Sschwarze 	 * Find maximum width of the columns to equalize.
17146fa2066Sschwarze 	 * Find total width of the columns *not* to maximize.
17246fa2066Sschwarze 	 */
17346fa2066Sschwarze 
17446fa2066Sschwarze 	necol = nxcol = 0;
17546fa2066Sschwarze 	ewidth = xwidth = 0;
17646fa2066Sschwarze 	for (icol = 0; icol <= maxcol; icol++) {
17746fa2066Sschwarze 		col = tbl->cols + icol;
178*e8a65004Sschwarze 		if (col->spacing == SIZE_MAX || icol == maxcol)
179*e8a65004Sschwarze 			col->spacing = 3;
18046fa2066Sschwarze 		if (col->flags & TBL_CELL_EQUAL) {
18146fa2066Sschwarze 			necol++;
18246fa2066Sschwarze 			if (ewidth < col->width)
18346fa2066Sschwarze 				ewidth = col->width;
18446fa2066Sschwarze 		}
18546fa2066Sschwarze 		if (col->flags & TBL_CELL_WMAX)
18646fa2066Sschwarze 			nxcol++;
18746fa2066Sschwarze 		else
18846fa2066Sschwarze 			xwidth += col->width;
18946fa2066Sschwarze 	}
19046fa2066Sschwarze 
19146fa2066Sschwarze 	/*
19246fa2066Sschwarze 	 * Equalize columns, if requested for any of them.
19346fa2066Sschwarze 	 * Update total width of the columns not to maximize.
19446fa2066Sschwarze 	 */
19546fa2066Sschwarze 
19646fa2066Sschwarze 	if (necol) {
19746fa2066Sschwarze 		for (icol = 0; icol <= maxcol; icol++) {
19846fa2066Sschwarze 			col = tbl->cols + icol;
19946fa2066Sschwarze 			if ( ! (col->flags & TBL_CELL_EQUAL))
20046fa2066Sschwarze 				continue;
20146fa2066Sschwarze 			if (col->width == ewidth)
20246fa2066Sschwarze 				continue;
203d1c3ec49Sschwarze 			if (nxcol && rmargin)
20446fa2066Sschwarze 				xwidth += ewidth - col->width;
20546fa2066Sschwarze 			col->width = ewidth;
20646fa2066Sschwarze 		}
20746fa2066Sschwarze 	}
20846fa2066Sschwarze 
20946fa2066Sschwarze 	/*
21046fa2066Sschwarze 	 * If there are any columns to maximize, find the total
21146fa2066Sschwarze 	 * available width, deducting 3n margins between columns.
21246fa2066Sschwarze 	 * Distribute the available width evenly.
21346fa2066Sschwarze 	 */
21446fa2066Sschwarze 
215d1c3ec49Sschwarze 	if (nxcol && rmargin) {
216eb274914Sschwarze 		xwidth += 3*maxcol +
21799d91ba7Sschwarze 		    (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ?
21899d91ba7Sschwarze 		     2 : !!opts->lvert + !!opts->rvert);
219d1c3ec49Sschwarze 		if (rmargin <= offset + xwidth)
220eb274914Sschwarze 			return;
221d1c3ec49Sschwarze 		xwidth = rmargin - offset - xwidth;
22299d91ba7Sschwarze 
22399d91ba7Sschwarze 		/*
22499d91ba7Sschwarze 		 * Emulate a bug in GNU tbl width calculation that
22599d91ba7Sschwarze 		 * manifests itself for large numbers of x-columns.
22699d91ba7Sschwarze 		 * Emulating it for 5 x-columns gives identical
22799d91ba7Sschwarze 		 * behaviour for up to 6 x-columns.
22899d91ba7Sschwarze 		 */
22999d91ba7Sschwarze 
23099d91ba7Sschwarze 		if (nxcol == 5) {
23199d91ba7Sschwarze 			quirkcol = xwidth % nxcol + 2;
23299d91ba7Sschwarze 			if (quirkcol != 3 && quirkcol != 4)
23399d91ba7Sschwarze 				quirkcol = -1;
23499d91ba7Sschwarze 		} else
23599d91ba7Sschwarze 			quirkcol = -1;
23699d91ba7Sschwarze 
23799d91ba7Sschwarze 		necol = 0;
23899d91ba7Sschwarze 		ewidth = 0;
23946fa2066Sschwarze 		for (icol = 0; icol <= maxcol; icol++) {
24046fa2066Sschwarze 			col = tbl->cols + icol;
24146fa2066Sschwarze 			if ( ! (col->flags & TBL_CELL_WMAX))
24246fa2066Sschwarze 				continue;
24399d91ba7Sschwarze 			col->width = (double)xwidth * ++necol / nxcol
24499d91ba7Sschwarze 			    - ewidth + 0.4995;
24599d91ba7Sschwarze 			if (necol == quirkcol)
24699d91ba7Sschwarze 				col->width--;
24799d91ba7Sschwarze 			ewidth += col->width;
24846fa2066Sschwarze 		}
24946fa2066Sschwarze 	}
250ec04407bSschwarze }
251ec04407bSschwarze 
252ec04407bSschwarze static void
253ec04407bSschwarze tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
2542c3e66c4Sschwarze     const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw)
255ec04407bSschwarze {
256ec04407bSschwarze 	size_t		 sz;
257ec04407bSschwarze 
258ec04407bSschwarze 	/* Branch down into data sub-types. */
259ec04407bSschwarze 
260ec04407bSschwarze 	switch (dp->layout->pos) {
26149aff9f8Sschwarze 	case TBL_CELL_HORIZ:
26249aff9f8Sschwarze 	case TBL_CELL_DHORIZ:
263ec04407bSschwarze 		sz = (*tbl->len)(1, tbl->arg);
264ec04407bSschwarze 		if (col->width < sz)
265ec04407bSschwarze 			col->width = sz;
266ec04407bSschwarze 		break;
26749aff9f8Sschwarze 	case TBL_CELL_LONG:
26849aff9f8Sschwarze 	case TBL_CELL_CENTRE:
26949aff9f8Sschwarze 	case TBL_CELL_LEFT:
27049aff9f8Sschwarze 	case TBL_CELL_RIGHT:
2712c3e66c4Sschwarze 		tblcalc_literal(tbl, col, dp, mw);
272ec04407bSschwarze 		break;
27349aff9f8Sschwarze 	case TBL_CELL_NUMBER:
274c7497e73Sschwarze 		tblcalc_number(tbl, col, opts, dp);
275ec04407bSschwarze 		break;
27649aff9f8Sschwarze 	case TBL_CELL_DOWN:
2778351ebcfSschwarze 		break;
278ec04407bSschwarze 	default:
279ec04407bSschwarze 		abort();
280ec04407bSschwarze 	}
281ec04407bSschwarze }
282ec04407bSschwarze 
283ec04407bSschwarze static void
284ec04407bSschwarze tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
2852c3e66c4Sschwarze     const struct tbl_dat *dp, size_t mw)
286ec04407bSschwarze {
2872c3e66c4Sschwarze 	const char	*str;	/* Beginning of the first line. */
2882c3e66c4Sschwarze 	const char	*beg;	/* Beginning of the current line. */
2892c3e66c4Sschwarze 	char		*end;	/* End of the current line. */
290d1c3ec49Sschwarze 	size_t		 lsz;	/* Length of the current line. */
291d1c3ec49Sschwarze 	size_t		 wsz;	/* Length of the current word. */
292ec04407bSschwarze 
2932c3e66c4Sschwarze 	if (dp->string == NULL || *dp->string == '\0')
2942c3e66c4Sschwarze 		return;
2952c3e66c4Sschwarze 	str = mw ? mandoc_strdup(dp->string) : dp->string;
296d1c3ec49Sschwarze 	lsz = 0;
2972c3e66c4Sschwarze 	for (beg = str; beg != NULL && *beg != '\0'; beg = end) {
2982c3e66c4Sschwarze 		end = mw ? strchr(beg, ' ') : NULL;
2992c3e66c4Sschwarze 		if (end != NULL) {
3002c3e66c4Sschwarze 			*end++ = '\0';
3012c3e66c4Sschwarze 			while (*end == ' ')
3022c3e66c4Sschwarze 				end++;
3032c3e66c4Sschwarze 		}
304d1c3ec49Sschwarze 		wsz = (*tbl->slen)(beg, tbl->arg);
305d1c3ec49Sschwarze 		if (mw && lsz && lsz + 1 + wsz <= mw)
306d1c3ec49Sschwarze 			lsz += 1 + wsz;
307d1c3ec49Sschwarze 		else
308d1c3ec49Sschwarze 			lsz = wsz;
309d1c3ec49Sschwarze 		if (col->width < lsz)
310d1c3ec49Sschwarze 			col->width = lsz;
311ec04407bSschwarze 	}
3122c3e66c4Sschwarze 	if (mw)
3132c3e66c4Sschwarze 		free((void *)str);
3142c3e66c4Sschwarze }
315ec04407bSschwarze 
316ec04407bSschwarze static void
317ec04407bSschwarze tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
318c7497e73Sschwarze 		const struct tbl_opts *opts, const struct tbl_dat *dp)
319ec04407bSschwarze {
320ec04407bSschwarze 	int		 i;
3218351ebcfSschwarze 	size_t		 sz, psz, ssz, d;
322ec04407bSschwarze 	const char	*str;
3238351ebcfSschwarze 	char		*cp;
324ec04407bSschwarze 	char		 buf[2];
325ec04407bSschwarze 
326ec04407bSschwarze 	/*
327ec04407bSschwarze 	 * First calculate number width and decimal place (last + 1 for
3280ebcd99eSschwarze 	 * non-decimal numbers).  If the stored decimal is subsequent to
329ec04407bSschwarze 	 * ours, make our size longer by that difference
330ec04407bSschwarze 	 * (right-"shifting"); similarly, if ours is subsequent the
331ec04407bSschwarze 	 * stored, then extend the stored size by the difference.
332ec04407bSschwarze 	 * Finally, re-assign the stored values.
333ec04407bSschwarze 	 */
334ec04407bSschwarze 
3358351ebcfSschwarze 	str = dp->string ? dp->string : "";
336ec04407bSschwarze 	sz = (*tbl->slen)(str, tbl->arg);
337ec04407bSschwarze 
3388351ebcfSschwarze 	/* FIXME: TBL_DATA_HORIZ et al.? */
3398351ebcfSschwarze 
340c7497e73Sschwarze 	buf[0] = opts->decimal;
341ec04407bSschwarze 	buf[1] = '\0';
342ec04407bSschwarze 
343ec04407bSschwarze 	psz = (*tbl->slen)(buf, tbl->arg);
344ec04407bSschwarze 
345c7497e73Sschwarze 	if (NULL != (cp = strrchr(str, opts->decimal))) {
346ec04407bSschwarze 		buf[1] = '\0';
347ec04407bSschwarze 		for (ssz = 0, i = 0; cp != &str[i]; i++) {
348ec04407bSschwarze 			buf[0] = str[i];
349ec04407bSschwarze 			ssz += (*tbl->slen)(buf, tbl->arg);
350ec04407bSschwarze 		}
351ec04407bSschwarze 		d = ssz + psz;
352ec04407bSschwarze 	} else
353ec04407bSschwarze 		d = sz + psz;
354ec04407bSschwarze 
355ec04407bSschwarze 	/* Adjust the settings for this column. */
356ec04407bSschwarze 
357ec04407bSschwarze 	if (col->decimal > d) {
358ec04407bSschwarze 		sz += col->decimal - d;
359ec04407bSschwarze 		d = col->decimal;
360ec04407bSschwarze 	} else
361ec04407bSschwarze 		col->width += d - col->decimal;
362ec04407bSschwarze 
363ec04407bSschwarze 	if (sz > col->width)
364ec04407bSschwarze 		col->width = sz;
365ec04407bSschwarze 	if (d > col->decimal)
366ec04407bSschwarze 		col->decimal = d;
367ec04407bSschwarze }
368