xref: /openbsd-src/usr.bin/mandoc/out.c (revision 5b859c19fe53bbea08f5c342e0a4470e99f883e1)
1 /*	$OpenBSD: out.c,v 1.25 2014/10/14 18:16:57 schwarze Exp $ */
2 /*
3  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2011, 2014 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 <sys/types.h>
19 
20 #include <assert.h>
21 #include <ctype.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <time.h>
26 
27 #include "mandoc_aux.h"
28 #include "mandoc.h"
29 #include "out.h"
30 
31 static	void	tblcalc_data(struct rofftbl *, struct roffcol *,
32 			const struct tbl_opts *, const struct tbl_dat *);
33 static	void	tblcalc_literal(struct rofftbl *, struct roffcol *,
34 			const struct tbl_dat *);
35 static	void	tblcalc_number(struct rofftbl *, struct roffcol *,
36 			const struct tbl_opts *, const struct tbl_dat *);
37 
38 
39 /*
40  * Convert a `scaling unit' to a consistent form, or fail.  Scaling
41  * units are documented in groff.7, mdoc.7, man.7.
42  */
43 int
44 a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
45 {
46 	char		 buf[BUFSIZ], hasd;
47 	int		 i;
48 	enum roffscale	 unit;
49 
50 	if ('\0' == *src)
51 		return(0);
52 
53 	i = hasd = 0;
54 
55 	switch (*src) {
56 	case '+':
57 		src++;
58 		break;
59 	case '-':
60 		buf[i++] = *src++;
61 		break;
62 	default:
63 		break;
64 	}
65 
66 	if ('\0' == *src)
67 		return(0);
68 
69 	while (i < BUFSIZ) {
70 		if ( ! isdigit((unsigned char)*src)) {
71 			if ('.' != *src)
72 				break;
73 			else if (hasd)
74 				break;
75 			else
76 				hasd = 1;
77 		}
78 		buf[i++] = *src++;
79 	}
80 
81 	if (BUFSIZ == i || (*src && *(src + 1)))
82 		return(0);
83 
84 	buf[i] = '\0';
85 
86 	switch (*src) {
87 	case 'c':
88 		unit = SCALE_CM;
89 		break;
90 	case 'i':
91 		unit = SCALE_IN;
92 		break;
93 	case 'P':
94 		unit = SCALE_PC;
95 		break;
96 	case 'p':
97 		unit = SCALE_PT;
98 		break;
99 	case 'f':
100 		unit = SCALE_FS;
101 		break;
102 	case 'v':
103 		unit = SCALE_VS;
104 		break;
105 	case 'm':
106 		unit = SCALE_EM;
107 		break;
108 	case '\0':
109 		if (SCALE_MAX == def)
110 			return(0);
111 		unit = SCALE_EN;
112 		break;
113 	case 'u':
114 		unit = SCALE_BU;
115 		break;
116 	case 'M':
117 		unit = SCALE_MM;
118 		break;
119 	case 'n':
120 		unit = SCALE_EN;
121 		break;
122 	default:
123 		return(0);
124 	}
125 
126 	/* FIXME: do this in the caller. */
127 	if ((dst->scale = atof(buf)) < 0.0)
128 		dst->scale = 0.0;
129 	dst->unit = unit;
130 	return(1);
131 }
132 
133 /*
134  * Calculate the abstract widths and decimal positions of columns in a
135  * table.  This routine allocates the columns structures then runs over
136  * all rows and cells in the table.  The function pointers in "tbl" are
137  * used for the actual width calculations.
138  */
139 void
140 tblcalc(struct rofftbl *tbl, const struct tbl_span *sp,
141 	size_t totalwidth)
142 {
143 	const struct tbl_dat	*dp;
144 	struct roffcol		*col;
145 	size_t			 ewidth, xwidth;
146 	int			 spans;
147 	int			 icol, maxcol, necol, nxcol;
148 
149 	/*
150 	 * Allocate the master column specifiers.  These will hold the
151 	 * widths and decimal positions for all cells in the column.  It
152 	 * must be freed and nullified by the caller.
153 	 */
154 
155 	assert(NULL == tbl->cols);
156 	tbl->cols = mandoc_calloc((size_t)sp->opts->cols,
157 	    sizeof(struct roffcol));
158 
159 	for (maxcol = -1; sp; sp = sp->next) {
160 		if (TBL_SPAN_DATA != sp->pos)
161 			continue;
162 		spans = 1;
163 		/*
164 		 * Account for the data cells in the layout, matching it
165 		 * to data cells in the data section.
166 		 */
167 		for (dp = sp->first; dp; dp = dp->next) {
168 			/* Do not used spanned cells in the calculation. */
169 			if (0 < --spans)
170 				continue;
171 			spans = dp->spans;
172 			if (1 < spans)
173 				continue;
174 			icol = dp->layout->head->ident;
175 			if (maxcol < icol)
176 				maxcol = icol;
177 			col = tbl->cols + icol;
178 			col->flags |= dp->layout->flags;
179 			if (dp->layout->flags & TBL_CELL_WIGN)
180 				continue;
181 			tblcalc_data(tbl, col, sp->opts, dp);
182 		}
183 	}
184 
185 	/*
186 	 * Count columns to equalize and columns to maximize.
187 	 * Find maximum width of the columns to equalize.
188 	 * Find total width of the columns *not* to maximize.
189 	 */
190 
191 	necol = nxcol = 0;
192 	ewidth = xwidth = 0;
193 	for (icol = 0; icol <= maxcol; icol++) {
194 		col = tbl->cols + icol;
195 		if (col->flags & TBL_CELL_EQUAL) {
196 			necol++;
197 			if (ewidth < col->width)
198 				ewidth = col->width;
199 		}
200 		if (col->flags & TBL_CELL_WMAX)
201 			nxcol++;
202 		else
203 			xwidth += col->width;
204 	}
205 
206 	/*
207 	 * Equalize columns, if requested for any of them.
208 	 * Update total width of the columns not to maximize.
209 	 */
210 
211 	if (necol) {
212 		for (icol = 0; icol <= maxcol; icol++) {
213 			col = tbl->cols + icol;
214 			if ( ! (col->flags & TBL_CELL_EQUAL))
215 				continue;
216 			if (col->width == ewidth)
217 				continue;
218 			if (nxcol && totalwidth)
219 				xwidth += ewidth - col->width;
220 			col->width = ewidth;
221 		}
222 	}
223 
224 	/*
225 	 * If there are any columns to maximize, find the total
226 	 * available width, deducting 3n margins between columns.
227 	 * Distribute the available width evenly.
228 	 */
229 
230 	if (nxcol && totalwidth) {
231 		xwidth = totalwidth - 3*maxcol - xwidth;
232 		for (icol = 0; icol <= maxcol; icol++) {
233 			col = tbl->cols + icol;
234 			if ( ! (col->flags & TBL_CELL_WMAX))
235 				continue;
236 			col->width = xwidth / nxcol--;
237 			xwidth -= col->width;
238 		}
239 	}
240 }
241 
242 static void
243 tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
244 		const struct tbl_opts *opts, const struct tbl_dat *dp)
245 {
246 	size_t		 sz;
247 
248 	/* Branch down into data sub-types. */
249 
250 	switch (dp->layout->pos) {
251 	case TBL_CELL_HORIZ:
252 		/* FALLTHROUGH */
253 	case TBL_CELL_DHORIZ:
254 		sz = (*tbl->len)(1, tbl->arg);
255 		if (col->width < sz)
256 			col->width = sz;
257 		break;
258 	case TBL_CELL_LONG:
259 		/* FALLTHROUGH */
260 	case TBL_CELL_CENTRE:
261 		/* FALLTHROUGH */
262 	case TBL_CELL_LEFT:
263 		/* FALLTHROUGH */
264 	case TBL_CELL_RIGHT:
265 		tblcalc_literal(tbl, col, dp);
266 		break;
267 	case TBL_CELL_NUMBER:
268 		tblcalc_number(tbl, col, opts, dp);
269 		break;
270 	case TBL_CELL_DOWN:
271 		break;
272 	default:
273 		abort();
274 		/* NOTREACHED */
275 	}
276 }
277 
278 static void
279 tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
280 		const struct tbl_dat *dp)
281 {
282 	size_t		 sz;
283 	const char	*str;
284 
285 	str = dp->string ? dp->string : "";
286 	sz = (*tbl->slen)(str, tbl->arg);
287 
288 	if (col->width < sz)
289 		col->width = sz;
290 }
291 
292 static void
293 tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
294 		const struct tbl_opts *opts, const struct tbl_dat *dp)
295 {
296 	int		 i;
297 	size_t		 sz, psz, ssz, d;
298 	const char	*str;
299 	char		*cp;
300 	char		 buf[2];
301 
302 	/*
303 	 * First calculate number width and decimal place (last + 1 for
304 	 * non-decimal numbers).  If the stored decimal is subsequent to
305 	 * ours, make our size longer by that difference
306 	 * (right-"shifting"); similarly, if ours is subsequent the
307 	 * stored, then extend the stored size by the difference.
308 	 * Finally, re-assign the stored values.
309 	 */
310 
311 	str = dp->string ? dp->string : "";
312 	sz = (*tbl->slen)(str, tbl->arg);
313 
314 	/* FIXME: TBL_DATA_HORIZ et al.? */
315 
316 	buf[0] = opts->decimal;
317 	buf[1] = '\0';
318 
319 	psz = (*tbl->slen)(buf, tbl->arg);
320 
321 	if (NULL != (cp = strrchr(str, opts->decimal))) {
322 		buf[1] = '\0';
323 		for (ssz = 0, i = 0; cp != &str[i]; i++) {
324 			buf[0] = str[i];
325 			ssz += (*tbl->slen)(buf, tbl->arg);
326 		}
327 		d = ssz + psz;
328 	} else
329 		d = sz + psz;
330 
331 	/* Adjust the settings for this column. */
332 
333 	if (col->decimal > d) {
334 		sz += col->decimal - d;
335 		d = col->decimal;
336 	} else
337 		col->width += d - col->decimal;
338 
339 	if (sz > col->width)
340 		col->width = sz;
341 	if (d > col->decimal)
342 		col->decimal = d;
343 }
344