xref: /illumos-gate/usr/src/cmd/mandoc/tbl_term.c (revision c193478586214940af708897e19c9a878b6a6223)
1 /*	$Id: tbl_term.c,v 1.43 2015/10/12 00:08:16 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 	case TBL_DATA_NHORIZ:
270 		tbl_char(tp, '-', col->width);
271 		return;
272 	case TBL_DATA_NDHORIZ:
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 	case TBL_CELL_CENTRE:
289 	case TBL_CELL_LEFT:
290 	case TBL_CELL_RIGHT:
291 		tbl_literal(tp, dp, col);
292 		break;
293 	case TBL_CELL_NUMBER:
294 		tbl_number(tp, opts, dp, col);
295 		break;
296 	case TBL_CELL_DOWN:
297 		tbl_char(tp, ASCII_NBRSP, col->width);
298 		break;
299 	default:
300 		abort();
301 	}
302 }
303 
304 static void
305 tbl_char(struct termp *tp, char c, size_t len)
306 {
307 	size_t		i, sz;
308 	char		cp[2];
309 
310 	cp[0] = c;
311 	cp[1] = '\0';
312 
313 	sz = term_strlen(tp, cp);
314 
315 	for (i = 0; i < len; i += sz)
316 		term_word(tp, cp);
317 }
318 
319 static void
320 tbl_literal(struct termp *tp, const struct tbl_dat *dp,
321 		const struct roffcol *col)
322 {
323 	size_t		 len, padl, padr, width;
324 	int		 ic, spans;
325 
326 	assert(dp->string);
327 	len = term_strlen(tp, dp->string);
328 	width = col->width;
329 	ic = dp->layout->col;
330 	spans = dp->spans;
331 	while (spans--)
332 		width += tp->tbl.cols[++ic].width + 3;
333 
334 	padr = width > len ? width - len : 0;
335 	padl = 0;
336 
337 	switch (dp->layout->pos) {
338 	case TBL_CELL_LONG:
339 		padl = term_len(tp, 1);
340 		padr = padr > padl ? padr - padl : 0;
341 		break;
342 	case TBL_CELL_CENTRE:
343 		if (2 > padr)
344 			break;
345 		padl = padr / 2;
346 		padr -= padl;
347 		break;
348 	case TBL_CELL_RIGHT:
349 		padl = padr;
350 		padr = 0;
351 		break;
352 	default:
353 		break;
354 	}
355 
356 	tbl_char(tp, ASCII_NBRSP, padl);
357 	tbl_word(tp, dp);
358 	tbl_char(tp, ASCII_NBRSP, padr);
359 }
360 
361 static void
362 tbl_number(struct termp *tp, const struct tbl_opts *opts,
363 		const struct tbl_dat *dp,
364 		const struct roffcol *col)
365 {
366 	char		*cp;
367 	char		 buf[2];
368 	size_t		 sz, psz, ssz, d, padl;
369 	int		 i;
370 
371 	/*
372 	 * See calc_data_number().  Left-pad by taking the offset of our
373 	 * and the maximum decimal; right-pad by the remaining amount.
374 	 */
375 
376 	assert(dp->string);
377 
378 	sz = term_strlen(tp, dp->string);
379 
380 	buf[0] = opts->decimal;
381 	buf[1] = '\0';
382 
383 	psz = term_strlen(tp, buf);
384 
385 	if ((cp = strrchr(dp->string, opts->decimal)) != NULL) {
386 		for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
387 			buf[0] = dp->string[i];
388 			ssz += term_strlen(tp, buf);
389 		}
390 		d = ssz + psz;
391 	} else
392 		d = sz + psz;
393 
394 	if (col->decimal > d && col->width > sz) {
395 		padl = col->decimal - d;
396 		if (padl + sz > col->width)
397 			padl = col->width - sz;
398 		tbl_char(tp, ASCII_NBRSP, padl);
399 	} else
400 		padl = 0;
401 	tbl_word(tp, dp);
402 	if (col->width > sz + padl)
403 		tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
404 }
405 
406 static void
407 tbl_word(struct termp *tp, const struct tbl_dat *dp)
408 {
409 	int		 prev_font;
410 
411 	prev_font = tp->fonti;
412 	if (dp->layout->flags & TBL_CELL_BOLD)
413 		term_fontpush(tp, TERMFONT_BOLD);
414 	else if (dp->layout->flags & TBL_CELL_ITALIC)
415 		term_fontpush(tp, TERMFONT_UNDER);
416 
417 	term_word(tp, dp->string);
418 
419 	term_fontpopq(tp, prev_font);
420 }
421