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