xref: /openbsd/usr.bin/mandoc/tbl_term.c (revision cecf84d4)
1 /*	$OpenBSD: tbl_term.c,v 1.28 2015/03/09 17:41:36 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 <sys/types.h>
19 
20 #include <assert.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "mandoc.h"
26 #include "out.h"
27 #include "term.h"
28 
29 static	size_t	term_tbl_len(size_t, void *);
30 static	size_t	term_tbl_strlen(const char *, void *);
31 static	void	tbl_char(struct termp *, char, size_t);
32 static	void	tbl_data(struct termp *, const struct tbl_opts *,
33 			const struct tbl_dat *,
34 			const struct roffcol *);
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 *, int);
41 static	void	tbl_word(struct termp *, const struct tbl_dat *);
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_cell	*cp;
62 	const struct tbl_dat	*dp;
63 	static size_t		 offset;
64 	size_t			 rmargin, maxrmargin, tsz;
65 	int			 ic, horiz, spans, vert;
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 (tp->tbl.cols == NULL) {
83 		tp->tbl.len = term_tbl_len;
84 		tp->tbl.slen = term_tbl_strlen;
85 		tp->tbl.arg = tp;
86 
87 		tblcalc(&tp->tbl, sp, rmargin - tp->offset);
88 
89 		/* Center the table as a whole. */
90 
91 		offset = tp->offset;
92 		if (sp->opts->opts & TBL_OPT_CENTRE) {
93 			tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)
94 			    ? 2 : !!sp->opts->lvert + !!sp->opts->rvert;
95 			for (ic = 0; ic < sp->opts->cols; ic++)
96 				tsz += tp->tbl.cols[ic].width + 3;
97 			tsz -= 3;
98 			if (offset + tsz > rmargin)
99 				tsz -= 1;
100 			tp->offset = (offset + rmargin > tsz) ?
101 			    (offset + rmargin - tsz) / 2 : 0;
102 		}
103 
104 		/* Horizontal frame at the start of boxed tables. */
105 
106 		if (sp->opts->opts & TBL_OPT_DBOX)
107 			tbl_hrule(tp, sp, 2);
108 		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX))
109 			tbl_hrule(tp, sp, 1);
110 	}
111 
112 	/* Vertical frame at the start of each row. */
113 
114 	horiz = sp->pos == TBL_SPAN_HORIZ || sp->pos == TBL_SPAN_DHORIZ;
115 
116 	if (sp->layout->vert ||
117 	    (sp->prev != NULL && sp->prev->layout->vert) ||
118 	    sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX))
119 		term_word(tp, horiz ? "+" : "|");
120 	else if (sp->opts->lvert)
121 		tbl_char(tp, horiz ? '-' : ASCII_NBRSP, 1);
122 
123 	/*
124 	 * Now print the actual data itself depending on the span type.
125 	 * Match data cells to column numbers.
126 	 */
127 
128 	if (sp->pos == TBL_SPAN_DATA) {
129 		cp = sp->layout->first;
130 		dp = sp->first;
131 		spans = 0;
132 		for (ic = 0; ic < sp->opts->cols; ic++) {
133 
134 			/*
135 			 * Remeber whether we need a vertical bar
136 			 * after this cell.
137 			 */
138 
139 			vert = cp == NULL ? 0 : cp->vert;
140 
141 			/*
142 			 * Print the data and advance to the next cell.
143 			 */
144 
145 			if (spans == 0) {
146 				tbl_data(tp, sp->opts, dp, tp->tbl.cols + ic);
147 				if (dp != NULL) {
148 					spans = dp->spans;
149 					dp = dp->next;
150 				}
151 			} else
152 				spans--;
153 			if (cp != NULL)
154 				cp = cp->next;
155 
156 			/*
157 			 * Separate columns, except in the middle
158 			 * of spans and after the last cell.
159 			 */
160 
161 			if (ic + 1 == sp->opts->cols || spans)
162 				continue;
163 
164 			tbl_char(tp, ASCII_NBRSP, 1);
165 			if (vert > 0)
166 				tbl_char(tp, '|', vert);
167 			if (vert < 2)
168 				tbl_char(tp, ASCII_NBRSP, 2 - vert);
169 		}
170 	} else if (horiz)
171 		tbl_hrule(tp, sp, 0);
172 
173 	/* Vertical frame at the end of each row. */
174 
175 	if (sp->layout->last->vert ||
176 	    (sp->prev != NULL && sp->prev->layout->last->vert) ||
177 	    (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)))
178 		term_word(tp, horiz ? "+" : " |");
179 	else if (sp->opts->rvert)
180 		tbl_char(tp, horiz ? '-' : ASCII_NBRSP, 1);
181 	term_flushln(tp);
182 
183 	/*
184 	 * If we're the last row, clean up after ourselves: clear the
185 	 * existing table configuration and set it to NULL.
186 	 */
187 
188 	if (sp->next == NULL) {
189 		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) {
190 			tbl_hrule(tp, sp, 1);
191 			tp->skipvsp = 1;
192 		}
193 		if (sp->opts->opts & TBL_OPT_DBOX) {
194 			tbl_hrule(tp, sp, 2);
195 			tp->skipvsp = 2;
196 		}
197 		assert(tp->tbl.cols);
198 		free(tp->tbl.cols);
199 		tp->tbl.cols = NULL;
200 		tp->offset = offset;
201 	}
202 
203 	tp->flags &= ~TERMP_NONOSPACE;
204 	tp->rmargin = rmargin;
205 	tp->maxrmargin = maxrmargin;
206 }
207 
208 /*
209  * Kinds of horizontal rulers:
210  * 0: inside the table (single or double line with crossings)
211  * 1: inner frame (single line with crossings and ends)
212  * 2: outer frame (single line without crossings with ends)
213  */
214 static void
215 tbl_hrule(struct termp *tp, const struct tbl_span *sp, int kind)
216 {
217 	const struct tbl_cell *c1, *c2;
218 	int	 vert;
219 	char	 line, cross;
220 
221 	line = (kind == 0 && TBL_SPAN_DHORIZ == sp->pos) ? '=' : '-';
222 	cross = (kind < 2) ? '+' : '-';
223 
224 	if (kind)
225 		term_word(tp, "+");
226 	c1 = sp->layout->first;
227 	c2 = sp->prev == NULL ? NULL : sp->prev->layout->first;
228 	if (c2 == c1)
229 		c2 = NULL;
230 	for (;;) {
231 		tbl_char(tp, line, tp->tbl.cols[c1->col].width + 1);
232 		vert = c1->vert;
233 		if ((c1 = c1->next) == NULL)
234 			 break;
235 		if (c2 != NULL) {
236 			if (vert < c2->vert)
237 				vert = c2->vert;
238 			c2 = c2->next;
239 		}
240 		if (vert)
241 			tbl_char(tp, cross, vert);
242 		if (vert < 2)
243 			tbl_char(tp, line, 2 - vert);
244 	}
245 	if (kind) {
246 		term_word(tp, "+");
247 		term_flushln(tp);
248 	}
249 }
250 
251 static void
252 tbl_data(struct termp *tp, const struct tbl_opts *opts,
253 	const struct tbl_dat *dp,
254 	const struct roffcol *col)
255 {
256 
257 	if (dp == NULL) {
258 		tbl_char(tp, ASCII_NBRSP, col->width);
259 		return;
260 	}
261 
262 	switch (dp->pos) {
263 	case TBL_DATA_NONE:
264 		tbl_char(tp, ASCII_NBRSP, col->width);
265 		return;
266 	case TBL_DATA_HORIZ:
267 		/* FALLTHROUGH */
268 	case TBL_DATA_NHORIZ:
269 		tbl_char(tp, '-', col->width);
270 		return;
271 	case TBL_DATA_NDHORIZ:
272 		/* FALLTHROUGH */
273 	case TBL_DATA_DHORIZ:
274 		tbl_char(tp, '=', col->width);
275 		return;
276 	default:
277 		break;
278 	}
279 
280 	switch (dp->layout->pos) {
281 	case TBL_CELL_HORIZ:
282 		tbl_char(tp, '-', col->width);
283 		break;
284 	case TBL_CELL_DHORIZ:
285 		tbl_char(tp, '=', col->width);
286 		break;
287 	case TBL_CELL_LONG:
288 		/* FALLTHROUGH */
289 	case TBL_CELL_CENTRE:
290 		/* FALLTHROUGH */
291 	case TBL_CELL_LEFT:
292 		/* FALLTHROUGH */
293 	case TBL_CELL_RIGHT:
294 		tbl_literal(tp, dp, col);
295 		break;
296 	case TBL_CELL_NUMBER:
297 		tbl_number(tp, opts, dp, col);
298 		break;
299 	case TBL_CELL_DOWN:
300 		tbl_char(tp, ASCII_NBRSP, col->width);
301 		break;
302 	default:
303 		abort();
304 		/* NOTREACHED */
305 	}
306 }
307 
308 static void
309 tbl_char(struct termp *tp, char c, size_t len)
310 {
311 	size_t		i, sz;
312 	char		cp[2];
313 
314 	cp[0] = c;
315 	cp[1] = '\0';
316 
317 	sz = term_strlen(tp, cp);
318 
319 	for (i = 0; i < len; i += sz)
320 		term_word(tp, cp);
321 }
322 
323 static void
324 tbl_literal(struct termp *tp, const struct tbl_dat *dp,
325 		const struct roffcol *col)
326 {
327 	size_t		 len, padl, padr, width;
328 	int		 ic, spans;
329 
330 	assert(dp->string);
331 	len = term_strlen(tp, dp->string);
332 	width = col->width;
333 	ic = dp->layout->col;
334 	spans = dp->spans;
335 	while (spans--)
336 		width += tp->tbl.cols[++ic].width + 3;
337 
338 	padr = width > len ? width - len : 0;
339 	padl = 0;
340 
341 	switch (dp->layout->pos) {
342 	case TBL_CELL_LONG:
343 		padl = term_len(tp, 1);
344 		padr = padr > padl ? padr - padl : 0;
345 		break;
346 	case TBL_CELL_CENTRE:
347 		if (2 > padr)
348 			break;
349 		padl = padr / 2;
350 		padr -= padl;
351 		break;
352 	case TBL_CELL_RIGHT:
353 		padl = padr;
354 		padr = 0;
355 		break;
356 	default:
357 		break;
358 	}
359 
360 	tbl_char(tp, ASCII_NBRSP, padl);
361 	tbl_word(tp, dp);
362 	tbl_char(tp, ASCII_NBRSP, padr);
363 }
364 
365 static void
366 tbl_number(struct termp *tp, const struct tbl_opts *opts,
367 		const struct tbl_dat *dp,
368 		const struct roffcol *col)
369 {
370 	char		*cp;
371 	char		 buf[2];
372 	size_t		 sz, psz, ssz, d, padl;
373 	int		 i;
374 
375 	/*
376 	 * See calc_data_number().  Left-pad by taking the offset of our
377 	 * and the maximum decimal; right-pad by the remaining amount.
378 	 */
379 
380 	assert(dp->string);
381 
382 	sz = term_strlen(tp, dp->string);
383 
384 	buf[0] = opts->decimal;
385 	buf[1] = '\0';
386 
387 	psz = term_strlen(tp, buf);
388 
389 	if ((cp = strrchr(dp->string, opts->decimal)) != NULL) {
390 		for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
391 			buf[0] = dp->string[i];
392 			ssz += term_strlen(tp, buf);
393 		}
394 		d = ssz + psz;
395 	} else
396 		d = sz + psz;
397 
398 	if (col->decimal > d && col->width > sz) {
399 		padl = col->decimal - d;
400 		if (padl + sz > col->width)
401 			padl = col->width - sz;
402 		tbl_char(tp, ASCII_NBRSP, padl);
403 	} else
404 		padl = 0;
405 	tbl_word(tp, dp);
406 	if (col->width > sz + padl)
407 		tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
408 }
409 
410 static void
411 tbl_word(struct termp *tp, const struct tbl_dat *dp)
412 {
413 	int		 prev_font;
414 
415 	prev_font = tp->fonti;
416 	if (dp->layout->flags & TBL_CELL_BOLD)
417 		term_fontpush(tp, TERMFONT_BOLD);
418 	else if (dp->layout->flags & TBL_CELL_ITALIC)
419 		term_fontpush(tp, TERMFONT_UNDER);
420 
421 	term_word(tp, dp->string);
422 
423 	term_fontpopq(tp, prev_font);
424 }
425