xref: /openbsd-src/usr.bin/mandoc/tbl_term.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$Id: tbl_term.c,v 1.16 2014/04/20 16:44:44 schwarze Exp $ */
2 /*
3  * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2011, 2012, 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 <assert.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "mandoc.h"
24 #include "out.h"
25 #include "term.h"
26 
27 static	size_t	term_tbl_len(size_t, void *);
28 static	size_t	term_tbl_strlen(const char *, void *);
29 static	void	tbl_char(struct termp *, char, size_t);
30 static	void	tbl_data(struct termp *, const struct tbl_opts *,
31 			const struct tbl_dat *,
32 			const struct roffcol *);
33 static	size_t	tbl_rulewidth(struct termp *, const struct tbl_head *);
34 static	void	tbl_hframe(struct termp *, const struct tbl_span *, int);
35 static	void	tbl_literal(struct termp *, const struct tbl_dat *,
36 			const struct roffcol *);
37 static	void	tbl_number(struct termp *, const struct tbl_opts *,
38 			const struct tbl_dat *,
39 			const struct roffcol *);
40 static	void	tbl_hrule(struct termp *, const struct tbl_span *);
41 static	void	tbl_vrule(struct termp *, const struct tbl_head *);
42 
43 
44 static size_t
45 term_tbl_strlen(const char *p, void *arg)
46 {
47 
48 	return(term_strlen((const struct termp *)arg, p));
49 }
50 
51 static size_t
52 term_tbl_len(size_t sz, void *arg)
53 {
54 
55 	return(term_len((const struct termp *)arg, sz));
56 }
57 
58 void
59 term_tbl(struct termp *tp, const struct tbl_span *sp)
60 {
61 	const struct tbl_head	*hp;
62 	const struct tbl_dat	*dp;
63 	struct roffcol		*col;
64 	int			 spans;
65 	size_t			 rmargin, maxrmargin;
66 
67 	rmargin = tp->rmargin;
68 	maxrmargin = tp->maxrmargin;
69 
70 	tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN;
71 
72 	/* Inhibit printing of spaces: we do padding ourselves. */
73 
74 	tp->flags |= TERMP_NONOSPACE;
75 	tp->flags |= TERMP_NOSPACE;
76 
77 	/*
78 	 * The first time we're invoked for a given table block,
79 	 * calculate the table widths and decimal positions.
80 	 */
81 
82 	if (TBL_SPAN_FIRST & sp->flags) {
83 		term_flushln(tp);
84 
85 		tp->tbl.len = term_tbl_len;
86 		tp->tbl.slen = term_tbl_strlen;
87 		tp->tbl.arg = tp;
88 
89 		tblcalc(&tp->tbl, sp);
90 	}
91 
92 	/* Horizontal frame at the start of boxed tables. */
93 
94 	if (TBL_SPAN_FIRST & sp->flags) {
95 		if (TBL_OPT_DBOX & sp->opts->opts)
96 			tbl_hframe(tp, sp, 1);
97 		if (TBL_OPT_DBOX & sp->opts->opts ||
98 		    TBL_OPT_BOX  & sp->opts->opts)
99 			tbl_hframe(tp, sp, 0);
100 	}
101 
102 	/* Vertical frame at the start of each row. */
103 
104 	if ((TBL_OPT_BOX | TBL_OPT_DBOX) & sp->opts->opts ||
105 	    sp->head->vert)
106 		term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
107 		    TBL_SPAN_DHORIZ == sp->pos ? "+" : "|");
108 
109 	/*
110 	 * Now print the actual data itself depending on the span type.
111 	 * Spanner spans get a horizontal rule; data spanners have their
112 	 * data printed by matching data to header.
113 	 */
114 
115 	switch (sp->pos) {
116 	case TBL_SPAN_HORIZ:
117 		/* FALLTHROUGH */
118 	case TBL_SPAN_DHORIZ:
119 		tbl_hrule(tp, sp);
120 		break;
121 	case TBL_SPAN_DATA:
122 		/* Iterate over template headers. */
123 		dp = sp->first;
124 		spans = 0;
125 		for (hp = sp->head; hp; hp = hp->next) {
126 
127 			/*
128 			 * If the current data header is invoked during
129 			 * a spanner ("spans" > 0), don't emit anything
130 			 * at all.
131 			 */
132 
133 			if (--spans >= 0)
134 				continue;
135 
136 			/* Separate columns. */
137 
138 			if (NULL != hp->prev)
139 				tbl_vrule(tp, hp);
140 
141 			col = &tp->tbl.cols[hp->ident];
142 			tbl_data(tp, sp->opts, dp, col);
143 
144 			/*
145 			 * Go to the next data cell and assign the
146 			 * number of subsequent spans, if applicable.
147 			 */
148 
149 			if (dp) {
150 				spans = dp->spans;
151 				dp = dp->next;
152 			}
153 		}
154 		break;
155 	}
156 
157 	/* Vertical frame at the end of each row. */
158 
159 	if ((TBL_OPT_BOX | TBL_OPT_DBOX) & sp->opts->opts ||
160 	    sp->layout->vert)
161 		term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
162 		    TBL_SPAN_DHORIZ == sp->pos ? "+" : " |");
163 	term_flushln(tp);
164 
165 	/*
166 	 * If we're the last row, clean up after ourselves: clear the
167 	 * existing table configuration and set it to NULL.
168 	 */
169 
170 	if (TBL_SPAN_LAST & sp->flags) {
171 		if (TBL_OPT_DBOX & sp->opts->opts ||
172 		    TBL_OPT_BOX  & sp->opts->opts) {
173 			tbl_hframe(tp, sp, 0);
174 			tp->skipvsp = 1;
175 		}
176 		if (TBL_OPT_DBOX & sp->opts->opts) {
177 			tbl_hframe(tp, sp, 1);
178 			tp->skipvsp = 2;
179 		}
180 		assert(tp->tbl.cols);
181 		free(tp->tbl.cols);
182 		tp->tbl.cols = NULL;
183 	}
184 
185 	tp->flags &= ~TERMP_NONOSPACE;
186 	tp->rmargin = rmargin;
187 	tp->maxrmargin = maxrmargin;
188 
189 }
190 
191 /*
192  * Horizontal rules extend across the entire table.
193  * Calculate the width by iterating over columns.
194  */
195 static size_t
196 tbl_rulewidth(struct termp *tp, const struct tbl_head *hp)
197 {
198 	size_t		 width;
199 
200 	width = tp->tbl.cols[hp->ident].width;
201 
202 	/* Account for leading blanks. */
203 	if (hp->prev)
204 		width += 2 - hp->vert;
205 
206 	/* Account for trailing blank. */
207 	width++;
208 
209 	return(width);
210 }
211 
212 /*
213  * Rules inside the table can be single or double
214  * and have crossings with vertical rules marked with pluses.
215  */
216 static void
217 tbl_hrule(struct termp *tp, const struct tbl_span *sp)
218 {
219 	const struct tbl_head *hp;
220 	char		 c;
221 
222 	c = '-';
223 	if (TBL_SPAN_DHORIZ == sp->pos)
224 		c = '=';
225 
226 	for (hp = sp->head; hp; hp = hp->next) {
227 		if (hp->prev && hp->vert)
228 			tbl_char(tp, '+', hp->vert);
229 		tbl_char(tp, c, tbl_rulewidth(tp, hp));
230 	}
231 }
232 
233 /*
234  * Rules above and below the table are always single
235  * and have an additional plus at the beginning and end.
236  * For double frames, this function is called twice,
237  * and the outer one does not have crossings.
238  */
239 static void
240 tbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer)
241 {
242 	const struct tbl_head *hp;
243 
244 	term_word(tp, "+");
245 	for (hp = sp->head; hp; hp = hp->next) {
246 		if (hp->prev && hp->vert)
247 			tbl_char(tp, (outer ? '-' : '+'), hp->vert);
248 		tbl_char(tp, '-', tbl_rulewidth(tp, hp));
249 	}
250 	term_word(tp, "+");
251 	term_flushln(tp);
252 }
253 
254 static void
255 tbl_data(struct termp *tp, const struct tbl_opts *opts,
256 	const struct tbl_dat *dp,
257 	const struct roffcol *col)
258 {
259 
260 	if (NULL == dp) {
261 		tbl_char(tp, ASCII_NBRSP, col->width);
262 		return;
263 	}
264 	assert(dp->layout);
265 
266 	switch (dp->pos) {
267 	case TBL_DATA_NONE:
268 		tbl_char(tp, ASCII_NBRSP, col->width);
269 		return;
270 	case TBL_DATA_HORIZ:
271 		/* FALLTHROUGH */
272 	case TBL_DATA_NHORIZ:
273 		tbl_char(tp, '-', col->width);
274 		return;
275 	case TBL_DATA_NDHORIZ:
276 		/* FALLTHROUGH */
277 	case TBL_DATA_DHORIZ:
278 		tbl_char(tp, '=', col->width);
279 		return;
280 	default:
281 		break;
282 	}
283 
284 	switch (dp->layout->pos) {
285 	case TBL_CELL_HORIZ:
286 		tbl_char(tp, '-', col->width);
287 		break;
288 	case TBL_CELL_DHORIZ:
289 		tbl_char(tp, '=', col->width);
290 		break;
291 	case TBL_CELL_LONG:
292 		/* FALLTHROUGH */
293 	case TBL_CELL_CENTRE:
294 		/* FALLTHROUGH */
295 	case TBL_CELL_LEFT:
296 		/* FALLTHROUGH */
297 	case TBL_CELL_RIGHT:
298 		tbl_literal(tp, dp, col);
299 		break;
300 	case TBL_CELL_NUMBER:
301 		tbl_number(tp, opts, dp, col);
302 		break;
303 	case TBL_CELL_DOWN:
304 		tbl_char(tp, ASCII_NBRSP, col->width);
305 		break;
306 	default:
307 		abort();
308 		/* NOTREACHED */
309 	}
310 }
311 
312 static void
313 tbl_vrule(struct termp *tp, const struct tbl_head *hp)
314 {
315 
316 	tbl_char(tp, ASCII_NBRSP, 1);
317 	if (0 < hp->vert)
318 		tbl_char(tp, '|', hp->vert);
319 	if (2 > hp->vert)
320 		tbl_char(tp, ASCII_NBRSP, 2 - hp->vert);
321 }
322 
323 static void
324 tbl_char(struct termp *tp, char c, size_t len)
325 {
326 	size_t		i, sz;
327 	char		cp[2];
328 
329 	cp[0] = c;
330 	cp[1] = '\0';
331 
332 	sz = term_strlen(tp, cp);
333 
334 	for (i = 0; i < len; i += sz)
335 		term_word(tp, cp);
336 }
337 
338 static void
339 tbl_literal(struct termp *tp, const struct tbl_dat *dp,
340 		const struct roffcol *col)
341 {
342 	struct tbl_head		*hp;
343 	size_t			 width, len, padl, padr;
344 	int			 spans;
345 
346 	assert(dp->string);
347 	len = term_strlen(tp, dp->string);
348 
349 	hp = dp->layout->head->next;
350 	width = col->width;
351 	for (spans = dp->spans; spans--; hp = hp->next)
352 		width += tp->tbl.cols[hp->ident].width + 3;
353 
354 	padr = width > len ? width - len : 0;
355 	padl = 0;
356 
357 	switch (dp->layout->pos) {
358 	case TBL_CELL_LONG:
359 		padl = term_len(tp, 1);
360 		padr = padr > padl ? padr - padl : 0;
361 		break;
362 	case TBL_CELL_CENTRE:
363 		if (2 > padr)
364 			break;
365 		padl = padr / 2;
366 		padr -= padl;
367 		break;
368 	case TBL_CELL_RIGHT:
369 		padl = padr;
370 		padr = 0;
371 		break;
372 	default:
373 		break;
374 	}
375 
376 	tbl_char(tp, ASCII_NBRSP, padl);
377 	term_word(tp, dp->string);
378 	tbl_char(tp, ASCII_NBRSP, padr);
379 }
380 
381 static void
382 tbl_number(struct termp *tp, const struct tbl_opts *opts,
383 		const struct tbl_dat *dp,
384 		const struct roffcol *col)
385 {
386 	char		*cp;
387 	char		 buf[2];
388 	size_t		 sz, psz, ssz, d, padl;
389 	int		 i;
390 
391 	/*
392 	 * See calc_data_number().  Left-pad by taking the offset of our
393 	 * and the maximum decimal; right-pad by the remaining amount.
394 	 */
395 
396 	assert(dp->string);
397 
398 	sz = term_strlen(tp, dp->string);
399 
400 	buf[0] = opts->decimal;
401 	buf[1] = '\0';
402 
403 	psz = term_strlen(tp, buf);
404 
405 	if (NULL != (cp = strrchr(dp->string, opts->decimal))) {
406 		buf[1] = '\0';
407 		for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
408 			buf[0] = dp->string[i];
409 			ssz += term_strlen(tp, buf);
410 		}
411 		d = ssz + psz;
412 	} else
413 		d = sz + psz;
414 
415 	padl = col->decimal - d;
416 
417 	tbl_char(tp, ASCII_NBRSP, padl);
418 	term_word(tp, dp->string);
419 	if (col->width > sz + padl)
420 		tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
421 }
422 
423