tbl_term.c revision 279527
1/*	$Id: tbl_term.c,v 1.38 2015/01/31 00:12:41 schwarze Exp $ */
2/*
3 * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2011, 2012, 2014, 2015 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 "config.h"
19
20#include <sys/types.h>
21
22#include <assert.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include "mandoc.h"
28#include "out.h"
29#include "term.h"
30
31static	size_t	term_tbl_len(size_t, void *);
32static	size_t	term_tbl_strlen(const char *, void *);
33static	void	tbl_char(struct termp *, char, size_t);
34static	void	tbl_data(struct termp *, const struct tbl_opts *,
35			const struct tbl_dat *,
36			const struct roffcol *);
37static	void	tbl_literal(struct termp *, const struct tbl_dat *,
38			const struct roffcol *);
39static	void	tbl_number(struct termp *, const struct tbl_opts *,
40			const struct tbl_dat *,
41			const struct roffcol *);
42static	void	tbl_hrule(struct termp *, const struct tbl_span *, int);
43static	void	tbl_word(struct termp *, const struct tbl_dat *);
44
45
46static size_t
47term_tbl_strlen(const char *p, void *arg)
48{
49
50	return(term_strlen((const struct termp *)arg, p));
51}
52
53static size_t
54term_tbl_len(size_t sz, void *arg)
55{
56
57	return(term_len((const struct termp *)arg, sz));
58}
59
60void
61term_tbl(struct termp *tp, const struct tbl_span *sp)
62{
63	const struct tbl_cell	*cp;
64	const struct tbl_dat	*dp;
65	static size_t		 offset;
66	size_t			 rmargin, maxrmargin, tsz;
67	int			 ic, horiz, spans, vert;
68
69	rmargin = tp->rmargin;
70	maxrmargin = tp->maxrmargin;
71
72	tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN;
73
74	/* Inhibit printing of spaces: we do padding ourselves. */
75
76	tp->flags |= TERMP_NONOSPACE;
77	tp->flags |= TERMP_NOSPACE;
78
79	/*
80	 * The first time we're invoked for a given table block,
81	 * calculate the table widths and decimal positions.
82	 */
83
84	if (tp->tbl.cols == NULL) {
85		term_flushln(tp);
86
87		tp->tbl.len = term_tbl_len;
88		tp->tbl.slen = term_tbl_strlen;
89		tp->tbl.arg = tp;
90
91		tblcalc(&tp->tbl, sp, rmargin - tp->offset);
92
93		/* Center the table as a whole. */
94
95		offset = tp->offset;
96		if (sp->opts->opts & TBL_OPT_CENTRE) {
97			tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)
98			    ? 2 : !!sp->opts->lvert + !!sp->opts->rvert;
99			for (ic = 0; ic < sp->opts->cols; ic++)
100				tsz += tp->tbl.cols[ic].width + 3;
101			tsz -= 3;
102			if (offset + tsz > rmargin)
103				tsz -= 1;
104			tp->offset = (offset + rmargin > tsz) ?
105			    (offset + rmargin - tsz) / 2 : 0;
106		}
107
108		/* Horizontal frame at the start of boxed tables. */
109
110		if (sp->opts->opts & TBL_OPT_DBOX)
111			tbl_hrule(tp, sp, 2);
112		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX))
113			tbl_hrule(tp, sp, 1);
114	}
115
116	/* Vertical frame at the start of each row. */
117
118	horiz = sp->pos == TBL_SPAN_HORIZ || sp->pos == TBL_SPAN_DHORIZ;
119
120	if (sp->layout->vert ||
121	    (sp->prev != NULL && sp->prev->layout->vert) ||
122	    sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX))
123		term_word(tp, horiz ? "+" : "|");
124	else if (sp->opts->lvert)
125		tbl_char(tp, horiz ? '-' : ASCII_NBRSP, 1);
126
127	/*
128	 * Now print the actual data itself depending on the span type.
129	 * Match data cells to column numbers.
130	 */
131
132	if (sp->pos == TBL_SPAN_DATA) {
133		cp = sp->layout->first;
134		dp = sp->first;
135		spans = 0;
136		for (ic = 0; ic < sp->opts->cols; ic++) {
137
138			/*
139			 * Remeber whether we need a vertical bar
140			 * after this cell.
141			 */
142
143			vert = cp == NULL ? 0 : cp->vert;
144
145			/*
146			 * Print the data and advance to the next cell.
147			 */
148
149			if (spans == 0) {
150				tbl_data(tp, sp->opts, dp, tp->tbl.cols + ic);
151				if (dp != NULL) {
152					spans = dp->spans;
153					dp = dp->next;
154				}
155			} else
156				spans--;
157			if (cp != NULL)
158				cp = cp->next;
159
160			/*
161			 * Separate columns, except in the middle
162			 * of spans and after the last cell.
163			 */
164
165			if (ic + 1 == sp->opts->cols || spans)
166				continue;
167
168			tbl_char(tp, ASCII_NBRSP, 1);
169			if (vert > 0)
170				tbl_char(tp, '|', vert);
171			if (vert < 2)
172				tbl_char(tp, ASCII_NBRSP, 2 - vert);
173		}
174	} else if (horiz)
175		tbl_hrule(tp, sp, 0);
176
177	/* Vertical frame at the end of each row. */
178
179	if (sp->layout->last->vert ||
180	    (sp->prev != NULL && sp->prev->layout->last->vert) ||
181	    (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)))
182		term_word(tp, horiz ? "+" : " |");
183	else if (sp->opts->rvert)
184		tbl_char(tp, horiz ? '-' : ASCII_NBRSP, 1);
185	term_flushln(tp);
186
187	/*
188	 * If we're the last row, clean up after ourselves: clear the
189	 * existing table configuration and set it to NULL.
190	 */
191
192	if (sp->next == NULL) {
193		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) {
194			tbl_hrule(tp, sp, 1);
195			tp->skipvsp = 1;
196		}
197		if (sp->opts->opts & TBL_OPT_DBOX) {
198			tbl_hrule(tp, sp, 2);
199			tp->skipvsp = 2;
200		}
201		assert(tp->tbl.cols);
202		free(tp->tbl.cols);
203		tp->tbl.cols = NULL;
204		tp->offset = offset;
205	}
206
207	tp->flags &= ~TERMP_NONOSPACE;
208	tp->rmargin = rmargin;
209	tp->maxrmargin = maxrmargin;
210}
211
212/*
213 * Kinds of horizontal rulers:
214 * 0: inside the table (single or double line with crossings)
215 * 1: inner frame (single line with crossings and ends)
216 * 2: outer frame (single line without crossings with ends)
217 */
218static void
219tbl_hrule(struct termp *tp, const struct tbl_span *sp, int kind)
220{
221	const struct tbl_cell *c1, *c2;
222	int	 vert;
223	char	 line, cross;
224
225	line = (kind == 0 && TBL_SPAN_DHORIZ == sp->pos) ? '=' : '-';
226	cross = (kind < 2) ? '+' : '-';
227
228	if (kind)
229		term_word(tp, "+");
230	c1 = sp->layout->first;
231	c2 = sp->prev == NULL ? NULL : sp->prev->layout->first;
232	if (c2 == c1)
233		c2 = NULL;
234	for (;;) {
235		tbl_char(tp, line, tp->tbl.cols[c1->col].width + 1);
236		vert = c1->vert;
237		if ((c1 = c1->next) == NULL)
238			 break;
239		if (c2 != NULL) {
240			if (vert < c2->vert)
241				vert = c2->vert;
242			c2 = c2->next;
243		}
244		if (vert)
245			tbl_char(tp, cross, vert);
246		if (vert < 2)
247			tbl_char(tp, line, 2 - vert);
248	}
249	if (kind) {
250		term_word(tp, "+");
251		term_flushln(tp);
252	}
253}
254
255static void
256tbl_data(struct termp *tp, const struct tbl_opts *opts,
257	const struct tbl_dat *dp,
258	const struct roffcol *col)
259{
260
261	if (dp == NULL) {
262		tbl_char(tp, ASCII_NBRSP, col->width);
263		return;
264	}
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
312static void
313tbl_char(struct termp *tp, char c, size_t len)
314{
315	size_t		i, sz;
316	char		cp[2];
317
318	cp[0] = c;
319	cp[1] = '\0';
320
321	sz = term_strlen(tp, cp);
322
323	for (i = 0; i < len; i += sz)
324		term_word(tp, cp);
325}
326
327static void
328tbl_literal(struct termp *tp, const struct tbl_dat *dp,
329		const struct roffcol *col)
330{
331	size_t		 len, padl, padr, width;
332	int		 ic, spans;
333
334	assert(dp->string);
335	len = term_strlen(tp, dp->string);
336	width = col->width;
337	ic = dp->layout->col;
338	spans = dp->spans;
339	while (spans--)
340		width += tp->tbl.cols[++ic].width + 3;
341
342	padr = width > len ? width - len : 0;
343	padl = 0;
344
345	switch (dp->layout->pos) {
346	case TBL_CELL_LONG:
347		padl = term_len(tp, 1);
348		padr = padr > padl ? padr - padl : 0;
349		break;
350	case TBL_CELL_CENTRE:
351		if (2 > padr)
352			break;
353		padl = padr / 2;
354		padr -= padl;
355		break;
356	case TBL_CELL_RIGHT:
357		padl = padr;
358		padr = 0;
359		break;
360	default:
361		break;
362	}
363
364	tbl_char(tp, ASCII_NBRSP, padl);
365	tbl_word(tp, dp);
366	tbl_char(tp, ASCII_NBRSP, padr);
367}
368
369static void
370tbl_number(struct termp *tp, const struct tbl_opts *opts,
371		const struct tbl_dat *dp,
372		const struct roffcol *col)
373{
374	char		*cp;
375	char		 buf[2];
376	size_t		 sz, psz, ssz, d, padl;
377	int		 i;
378
379	/*
380	 * See calc_data_number().  Left-pad by taking the offset of our
381	 * and the maximum decimal; right-pad by the remaining amount.
382	 */
383
384	assert(dp->string);
385
386	sz = term_strlen(tp, dp->string);
387
388	buf[0] = opts->decimal;
389	buf[1] = '\0';
390
391	psz = term_strlen(tp, buf);
392
393	if ((cp = strrchr(dp->string, opts->decimal)) != NULL) {
394		for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
395			buf[0] = dp->string[i];
396			ssz += term_strlen(tp, buf);
397		}
398		d = ssz + psz;
399	} else
400		d = sz + psz;
401
402	if (col->decimal > d && col->width > sz) {
403		padl = col->decimal - d;
404		if (padl + sz > col->width)
405			padl = col->width - sz;
406		tbl_char(tp, ASCII_NBRSP, padl);
407	} else
408		padl = 0;
409	tbl_word(tp, dp);
410	if (col->width > sz + padl)
411		tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
412}
413
414static void
415tbl_word(struct termp *tp, const struct tbl_dat *dp)
416{
417	int		 prev_font;
418
419	prev_font = tp->fonti;
420	if (dp->layout->flags & TBL_CELL_BOLD)
421		term_fontpush(tp, TERMFONT_BOLD);
422	else if (dp->layout->flags & TBL_CELL_ITALIC)
423		term_fontpush(tp, TERMFONT_UNDER);
424
425	term_word(tp, dp->string);
426
427	term_fontpopq(tp, prev_font);
428}
429