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