1241675Suqs/*	$Id: tbl_term.c,v 1.21 2011/09/20 23:05:49 schwarze Exp $ */
2241675Suqs/*
3241675Suqs * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4241675Suqs * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
5241675Suqs *
6241675Suqs * Permission to use, copy, modify, and distribute this software for any
7241675Suqs * purpose with or without fee is hereby granted, provided that the above
8241675Suqs * copyright notice and this permission notice appear in all copies.
9241675Suqs *
10241675Suqs * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11241675Suqs * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12241675Suqs * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13241675Suqs * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14241675Suqs * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15241675Suqs * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16241675Suqs * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17241675Suqs */
18241675Suqs#ifdef HAVE_CONFIG_H
19241675Suqs#include "config.h"
20241675Suqs#endif
21241675Suqs
22241675Suqs#include <assert.h>
23241675Suqs#include <stdio.h>
24241675Suqs#include <stdlib.h>
25241675Suqs#include <string.h>
26241675Suqs
27241675Suqs#include "mandoc.h"
28241675Suqs#include "out.h"
29241675Suqs#include "term.h"
30241675Suqs
31241675Suqsstatic	size_t	term_tbl_len(size_t, void *);
32241675Suqsstatic	size_t	term_tbl_strlen(const char *, void *);
33241675Suqsstatic	void	tbl_char(struct termp *, char, size_t);
34241675Suqsstatic	void	tbl_data(struct termp *, const struct tbl *,
35241675Suqs			const struct tbl_dat *,
36241675Suqs			const struct roffcol *);
37241675Suqsstatic	size_t	tbl_rulewidth(struct termp *, const struct tbl_head *);
38241675Suqsstatic	void	tbl_hframe(struct termp *, const struct tbl_span *, int);
39241675Suqsstatic	void	tbl_literal(struct termp *, const struct tbl_dat *,
40241675Suqs			const struct roffcol *);
41241675Suqsstatic	void	tbl_number(struct termp *, const struct tbl *,
42241675Suqs			const struct tbl_dat *,
43241675Suqs			const struct roffcol *);
44241675Suqsstatic	void	tbl_hrule(struct termp *, const struct tbl_span *);
45241675Suqsstatic	void	tbl_vrule(struct termp *, const struct tbl_head *);
46241675Suqs
47241675Suqs
48241675Suqsstatic size_t
49241675Suqsterm_tbl_strlen(const char *p, void *arg)
50241675Suqs{
51241675Suqs
52241675Suqs	return(term_strlen((const struct termp *)arg, p));
53241675Suqs}
54241675Suqs
55241675Suqsstatic size_t
56241675Suqsterm_tbl_len(size_t sz, void *arg)
57241675Suqs{
58241675Suqs
59241675Suqs	return(term_len((const struct termp *)arg, sz));
60241675Suqs}
61241675Suqs
62241675Suqsvoid
63241675Suqsterm_tbl(struct termp *tp, const struct tbl_span *sp)
64241675Suqs{
65241675Suqs	const struct tbl_head	*hp;
66241675Suqs	const struct tbl_dat	*dp;
67241675Suqs	struct roffcol		*col;
68241675Suqs	int			 spans;
69241675Suqs	size_t		   	 rmargin, maxrmargin;
70241675Suqs
71241675Suqs	rmargin = tp->rmargin;
72241675Suqs	maxrmargin = tp->maxrmargin;
73241675Suqs
74241675Suqs	tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN;
75241675Suqs
76241675Suqs	/* Inhibit printing of spaces: we do padding ourselves. */
77241675Suqs
78241675Suqs	tp->flags |= TERMP_NONOSPACE;
79241675Suqs	tp->flags |= TERMP_NOSPACE;
80241675Suqs
81241675Suqs	/*
82241675Suqs	 * The first time we're invoked for a given table block,
83241675Suqs	 * calculate the table widths and decimal positions.
84241675Suqs	 */
85241675Suqs
86241675Suqs	if (TBL_SPAN_FIRST & sp->flags) {
87241675Suqs		term_flushln(tp);
88241675Suqs
89241675Suqs		tp->tbl.len = term_tbl_len;
90241675Suqs		tp->tbl.slen = term_tbl_strlen;
91241675Suqs		tp->tbl.arg = tp;
92241675Suqs
93241675Suqs		tblcalc(&tp->tbl, sp);
94241675Suqs	}
95241675Suqs
96241675Suqs	/* Horizontal frame at the start of boxed tables. */
97241675Suqs
98241675Suqs	if (TBL_SPAN_FIRST & sp->flags) {
99241675Suqs		if (TBL_OPT_DBOX & sp->tbl->opts)
100241675Suqs			tbl_hframe(tp, sp, 1);
101241675Suqs		if (TBL_OPT_DBOX & sp->tbl->opts ||
102241675Suqs		    TBL_OPT_BOX  & sp->tbl->opts)
103241675Suqs			tbl_hframe(tp, sp, 0);
104241675Suqs	}
105241675Suqs
106241675Suqs	/* Vertical frame at the start of each row. */
107241675Suqs
108241675Suqs	if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts)
109241675Suqs		term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
110241675Suqs			TBL_SPAN_DHORIZ == sp->pos ? "+" : "|");
111241675Suqs
112241675Suqs	/*
113241675Suqs	 * Now print the actual data itself depending on the span type.
114241675Suqs	 * Spanner spans get a horizontal rule; data spanners have their
115241675Suqs	 * data printed by matching data to header.
116241675Suqs	 */
117241675Suqs
118241675Suqs	switch (sp->pos) {
119241675Suqs	case (TBL_SPAN_HORIZ):
120241675Suqs		/* FALLTHROUGH */
121241675Suqs	case (TBL_SPAN_DHORIZ):
122241675Suqs		tbl_hrule(tp, sp);
123241675Suqs		break;
124241675Suqs	case (TBL_SPAN_DATA):
125241675Suqs		/* Iterate over template headers. */
126241675Suqs		dp = sp->first;
127241675Suqs		spans = 0;
128241675Suqs		for (hp = sp->head; hp; hp = hp->next) {
129241675Suqs			/*
130241675Suqs			 * If the current data header is invoked during
131241675Suqs			 * a spanner ("spans" > 0), don't emit anything
132241675Suqs			 * at all.
133241675Suqs			 */
134241675Suqs			switch (hp->pos) {
135241675Suqs			case (TBL_HEAD_VERT):
136241675Suqs				/* FALLTHROUGH */
137241675Suqs			case (TBL_HEAD_DVERT):
138241675Suqs				if (spans <= 0)
139241675Suqs					tbl_vrule(tp, hp);
140241675Suqs				continue;
141241675Suqs			case (TBL_HEAD_DATA):
142241675Suqs				break;
143241675Suqs			}
144241675Suqs
145241675Suqs			if (--spans >= 0)
146241675Suqs				continue;
147241675Suqs
148241675Suqs			/*
149241675Suqs			 * All cells get a leading blank, except the
150241675Suqs			 * first one and those after double rulers.
151241675Suqs			 */
152241675Suqs
153241675Suqs			if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos)
154241675Suqs				tbl_char(tp, ASCII_NBRSP, 1);
155241675Suqs
156241675Suqs			col = &tp->tbl.cols[hp->ident];
157241675Suqs			tbl_data(tp, sp->tbl, dp, col);
158241675Suqs
159241675Suqs			/* No trailing blanks. */
160241675Suqs
161241675Suqs			if (NULL == hp->next)
162241675Suqs				break;
163241675Suqs
164241675Suqs			/*
165241675Suqs			 * Add another blank between cells,
166241675Suqs			 * or two when there is no vertical ruler.
167241675Suqs			 */
168241675Suqs
169241675Suqs			tbl_char(tp, ASCII_NBRSP,
170241675Suqs			    TBL_HEAD_VERT  == hp->next->pos ||
171241675Suqs			    TBL_HEAD_DVERT == hp->next->pos ? 1 : 2);
172241675Suqs
173241675Suqs			/*
174241675Suqs			 * Go to the next data cell and assign the
175241675Suqs			 * number of subsequent spans, if applicable.
176241675Suqs			 */
177241675Suqs
178241675Suqs			if (dp) {
179241675Suqs				spans = dp->spans;
180241675Suqs				dp = dp->next;
181241675Suqs			}
182241675Suqs		}
183241675Suqs		break;
184241675Suqs	}
185241675Suqs
186241675Suqs	/* Vertical frame at the end of each row. */
187241675Suqs
188241675Suqs	if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts)
189241675Suqs		term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
190241675Suqs			TBL_SPAN_DHORIZ == sp->pos ? "+" : " |");
191241675Suqs	term_flushln(tp);
192241675Suqs
193241675Suqs	/*
194241675Suqs	 * If we're the last row, clean up after ourselves: clear the
195241675Suqs	 * existing table configuration and set it to NULL.
196241675Suqs	 */
197241675Suqs
198241675Suqs	if (TBL_SPAN_LAST & sp->flags) {
199241675Suqs		if (TBL_OPT_DBOX & sp->tbl->opts ||
200241675Suqs		    TBL_OPT_BOX  & sp->tbl->opts)
201241675Suqs			tbl_hframe(tp, sp, 0);
202241675Suqs		if (TBL_OPT_DBOX & sp->tbl->opts)
203241675Suqs			tbl_hframe(tp, sp, 1);
204241675Suqs		assert(tp->tbl.cols);
205241675Suqs		free(tp->tbl.cols);
206241675Suqs		tp->tbl.cols = NULL;
207241675Suqs	}
208241675Suqs
209241675Suqs	tp->flags &= ~TERMP_NONOSPACE;
210241675Suqs	tp->rmargin = rmargin;
211241675Suqs	tp->maxrmargin = maxrmargin;
212241675Suqs
213241675Suqs}
214241675Suqs
215241675Suqs/*
216241675Suqs * Horizontal rules extend across the entire table.
217241675Suqs * Calculate the width by iterating over columns.
218241675Suqs */
219241675Suqsstatic size_t
220241675Suqstbl_rulewidth(struct termp *tp, const struct tbl_head *hp)
221241675Suqs{
222241675Suqs	size_t		 width;
223241675Suqs
224241675Suqs	width = tp->tbl.cols[hp->ident].width;
225241675Suqs	if (TBL_HEAD_DATA == hp->pos) {
226241675Suqs		/* Account for leading blanks. */
227241675Suqs		if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos)
228241675Suqs			width++;
229241675Suqs		/* Account for trailing blanks. */
230241675Suqs		width++;
231241675Suqs		if (hp->next &&
232241675Suqs		    TBL_HEAD_VERT  != hp->next->pos &&
233241675Suqs		    TBL_HEAD_DVERT != hp->next->pos)
234241675Suqs			width++;
235241675Suqs	}
236241675Suqs	return(width);
237241675Suqs}
238241675Suqs
239241675Suqs/*
240241675Suqs * Rules inside the table can be single or double
241241675Suqs * and have crossings with vertical rules marked with pluses.
242241675Suqs */
243241675Suqsstatic void
244241675Suqstbl_hrule(struct termp *tp, const struct tbl_span *sp)
245241675Suqs{
246241675Suqs	const struct tbl_head *hp;
247241675Suqs	char		 c;
248241675Suqs
249241675Suqs	c = '-';
250241675Suqs	if (TBL_SPAN_DHORIZ == sp->pos)
251241675Suqs		c = '=';
252241675Suqs
253241675Suqs	for (hp = sp->head; hp; hp = hp->next)
254241675Suqs		tbl_char(tp,
255241675Suqs		    TBL_HEAD_DATA == hp->pos ? c : '+',
256241675Suqs		    tbl_rulewidth(tp, hp));
257241675Suqs}
258241675Suqs
259241675Suqs/*
260241675Suqs * Rules above and below the table are always single
261241675Suqs * and have an additional plus at the beginning and end.
262241675Suqs * For double frames, this function is called twice,
263241675Suqs * and the outer one does not have crossings.
264241675Suqs */
265241675Suqsstatic void
266241675Suqstbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer)
267241675Suqs{
268241675Suqs	const struct tbl_head *hp;
269241675Suqs
270241675Suqs	term_word(tp, "+");
271241675Suqs	for (hp = sp->head; hp; hp = hp->next)
272241675Suqs		tbl_char(tp,
273241675Suqs		    outer || TBL_HEAD_DATA == hp->pos ? '-' : '+',
274241675Suqs		    tbl_rulewidth(tp, hp));
275241675Suqs	term_word(tp, "+");
276241675Suqs	term_flushln(tp);
277241675Suqs}
278241675Suqs
279241675Suqsstatic void
280241675Suqstbl_data(struct termp *tp, const struct tbl *tbl,
281241675Suqs		const struct tbl_dat *dp,
282241675Suqs		const struct roffcol *col)
283241675Suqs{
284241675Suqs
285241675Suqs	if (NULL == dp) {
286241675Suqs		tbl_char(tp, ASCII_NBRSP, col->width);
287241675Suqs		return;
288241675Suqs	}
289241675Suqs	assert(dp->layout);
290241675Suqs
291241675Suqs	switch (dp->pos) {
292241675Suqs	case (TBL_DATA_NONE):
293241675Suqs		tbl_char(tp, ASCII_NBRSP, col->width);
294241675Suqs		return;
295241675Suqs	case (TBL_DATA_HORIZ):
296241675Suqs		/* FALLTHROUGH */
297241675Suqs	case (TBL_DATA_NHORIZ):
298241675Suqs		tbl_char(tp, '-', col->width);
299241675Suqs		return;
300241675Suqs	case (TBL_DATA_NDHORIZ):
301241675Suqs		/* FALLTHROUGH */
302241675Suqs	case (TBL_DATA_DHORIZ):
303241675Suqs		tbl_char(tp, '=', col->width);
304241675Suqs		return;
305241675Suqs	default:
306241675Suqs		break;
307241675Suqs	}
308241675Suqs
309241675Suqs	switch (dp->layout->pos) {
310241675Suqs	case (TBL_CELL_HORIZ):
311241675Suqs		tbl_char(tp, '-', col->width);
312241675Suqs		break;
313241675Suqs	case (TBL_CELL_DHORIZ):
314241675Suqs		tbl_char(tp, '=', col->width);
315241675Suqs		break;
316241675Suqs	case (TBL_CELL_LONG):
317241675Suqs		/* FALLTHROUGH */
318241675Suqs	case (TBL_CELL_CENTRE):
319241675Suqs		/* FALLTHROUGH */
320241675Suqs	case (TBL_CELL_LEFT):
321241675Suqs		/* FALLTHROUGH */
322241675Suqs	case (TBL_CELL_RIGHT):
323241675Suqs		tbl_literal(tp, dp, col);
324241675Suqs		break;
325241675Suqs	case (TBL_CELL_NUMBER):
326241675Suqs		tbl_number(tp, tbl, dp, col);
327241675Suqs		break;
328241675Suqs	case (TBL_CELL_DOWN):
329241675Suqs		tbl_char(tp, ASCII_NBRSP, col->width);
330241675Suqs		break;
331241675Suqs	default:
332241675Suqs		abort();
333241675Suqs		/* NOTREACHED */
334241675Suqs	}
335241675Suqs}
336241675Suqs
337241675Suqsstatic void
338241675Suqstbl_vrule(struct termp *tp, const struct tbl_head *hp)
339241675Suqs{
340241675Suqs
341241675Suqs	switch (hp->pos) {
342241675Suqs	case (TBL_HEAD_VERT):
343241675Suqs		term_word(tp, "|");
344241675Suqs		break;
345241675Suqs	case (TBL_HEAD_DVERT):
346241675Suqs		term_word(tp, "||");
347241675Suqs		break;
348241675Suqs	default:
349241675Suqs		break;
350241675Suqs	}
351241675Suqs}
352241675Suqs
353241675Suqsstatic void
354241675Suqstbl_char(struct termp *tp, char c, size_t len)
355241675Suqs{
356241675Suqs	size_t		i, sz;
357241675Suqs	char		cp[2];
358241675Suqs
359241675Suqs	cp[0] = c;
360241675Suqs	cp[1] = '\0';
361241675Suqs
362241675Suqs	sz = term_strlen(tp, cp);
363241675Suqs
364241675Suqs	for (i = 0; i < len; i += sz)
365241675Suqs		term_word(tp, cp);
366241675Suqs}
367241675Suqs
368241675Suqsstatic void
369241675Suqstbl_literal(struct termp *tp, const struct tbl_dat *dp,
370241675Suqs		const struct roffcol *col)
371241675Suqs{
372241675Suqs	size_t		 len, padl, padr;
373241675Suqs
374241675Suqs	assert(dp->string);
375241675Suqs	len = term_strlen(tp, dp->string);
376241675Suqs	padr = col->width > len ? col->width - len : 0;
377241675Suqs	padl = 0;
378241675Suqs
379241675Suqs	switch (dp->layout->pos) {
380241675Suqs	case (TBL_CELL_LONG):
381241675Suqs		padl = term_len(tp, 1);
382241675Suqs		padr = padr > padl ? padr - padl : 0;
383241675Suqs		break;
384241675Suqs	case (TBL_CELL_CENTRE):
385241675Suqs		if (2 > padr)
386241675Suqs			break;
387241675Suqs		padl = padr / 2;
388241675Suqs		padr -= padl;
389241675Suqs		break;
390241675Suqs	case (TBL_CELL_RIGHT):
391241675Suqs		padl = padr;
392241675Suqs		padr = 0;
393241675Suqs		break;
394241675Suqs	default:
395241675Suqs		break;
396241675Suqs	}
397241675Suqs
398241675Suqs	tbl_char(tp, ASCII_NBRSP, padl);
399241675Suqs	term_word(tp, dp->string);
400241675Suqs	tbl_char(tp, ASCII_NBRSP, padr);
401241675Suqs}
402241675Suqs
403241675Suqsstatic void
404241675Suqstbl_number(struct termp *tp, const struct tbl *tbl,
405241675Suqs		const struct tbl_dat *dp,
406241675Suqs		const struct roffcol *col)
407241675Suqs{
408241675Suqs	char		*cp;
409241675Suqs	char		 buf[2];
410241675Suqs	size_t		 sz, psz, ssz, d, padl;
411241675Suqs	int		 i;
412241675Suqs
413241675Suqs	/*
414241675Suqs	 * See calc_data_number().  Left-pad by taking the offset of our
415241675Suqs	 * and the maximum decimal; right-pad by the remaining amount.
416241675Suqs	 */
417241675Suqs
418241675Suqs	assert(dp->string);
419241675Suqs
420241675Suqs	sz = term_strlen(tp, dp->string);
421241675Suqs
422241675Suqs	buf[0] = tbl->decimal;
423241675Suqs	buf[1] = '\0';
424241675Suqs
425241675Suqs	psz = term_strlen(tp, buf);
426241675Suqs
427241675Suqs	if (NULL != (cp = strrchr(dp->string, tbl->decimal))) {
428241675Suqs		buf[1] = '\0';
429241675Suqs		for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
430241675Suqs			buf[0] = dp->string[i];
431241675Suqs			ssz += term_strlen(tp, buf);
432241675Suqs		}
433241675Suqs		d = ssz + psz;
434241675Suqs	} else
435241675Suqs		d = sz + psz;
436241675Suqs
437241675Suqs	padl = col->decimal - d;
438241675Suqs
439241675Suqs	tbl_char(tp, ASCII_NBRSP, padl);
440241675Suqs	term_word(tp, dp->string);
441241675Suqs	if (col->width > sz + padl)
442241675Suqs		tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
443241675Suqs}
444241675Suqs
445