xref: /netbsd-src/external/bsd/mdocml/dist/tbl_term.c (revision c2f76ff004a2cb67efe5b12d97bd3ef7fe89e18d)
1 /*	$Vendor-Id: tbl_term.c,v 1.13 2011/01/07 14:59:52 kristaps Exp $ */
2 /*
3  * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include <assert.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "mandoc.h"
27 #include "out.h"
28 #include "term.h"
29 
30 /* FIXME: `n' modifier doesn't always do the right thing. */
31 /* FIXME: `n' modifier doesn't use the cell-spacing buffer. */
32 
33 static	size_t	term_tbl_len(size_t, void *);
34 static	size_t	term_tbl_strlen(const char *, void *);
35 static	void	tbl_char(struct termp *, char, size_t);
36 static	void	tbl_data(struct termp *, const struct tbl *,
37 			const struct tbl_dat *,
38 			const struct roffcol *);
39 static	void	tbl_hframe(struct termp *, const struct tbl_span *);
40 static	void	tbl_literal(struct termp *, const struct tbl_dat *,
41 			const struct roffcol *);
42 static	void	tbl_number(struct termp *, const struct tbl *,
43 			const struct tbl_dat *,
44 			const struct roffcol *);
45 static	void	tbl_hrule(struct termp *, const struct tbl_span *);
46 static	void	tbl_vframe(struct termp *, const struct tbl *);
47 static	void	tbl_vrule(struct termp *, const struct tbl_head *);
48 
49 
50 static size_t
51 term_tbl_strlen(const char *p, void *arg)
52 {
53 
54 	return(term_strlen((const struct termp *)arg, p));
55 }
56 
57 static size_t
58 term_tbl_len(size_t sz, void *arg)
59 {
60 
61 	return(term_len((const struct termp *)arg, sz));
62 }
63 
64 void
65 term_tbl(struct termp *tp, const struct tbl_span *sp)
66 {
67 	const struct tbl_head	*hp;
68 	const struct tbl_dat	*dp;
69 	struct roffcol		*col;
70 	size_t		   	 rmargin, maxrmargin;
71 
72 	rmargin = tp->rmargin;
73 	maxrmargin = tp->maxrmargin;
74 
75 	tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN;
76 
77 	/* Inhibit printing of spaces: we do padding ourselves. */
78 
79 	tp->flags |= TERMP_NONOSPACE;
80 	tp->flags |= TERMP_NOSPACE;
81 
82 	/*
83 	 * The first time we're invoked for a given table block,
84 	 * calculate the table widths and decimal positions.
85 	 */
86 
87 	if (TBL_SPAN_FIRST & sp->flags) {
88 		term_flushln(tp);
89 
90 		tp->tbl.len = term_tbl_len;
91 		tp->tbl.slen = term_tbl_strlen;
92 		tp->tbl.arg = tp;
93 
94 		tblcalc(&tp->tbl, sp);
95 	}
96 
97 	/* Horizontal frame at the start of boxed tables. */
98 
99 	if (TBL_SPAN_FIRST & sp->flags)
100 		tbl_hframe(tp, sp);
101 
102 	/* Vertical frame at the start of each row. */
103 
104 	tbl_vframe(tp, sp->tbl);
105 
106 	/*
107 	 * Now print the actual data itself depending on the span type.
108 	 * Spanner spans get a horizontal rule; data spanners have their
109 	 * data printed by matching data to header.
110 	 */
111 
112 	switch (sp->pos) {
113 	case (TBL_SPAN_HORIZ):
114 		/* FALLTHROUGH */
115 	case (TBL_SPAN_DHORIZ):
116 		tbl_hrule(tp, sp);
117 		break;
118 	case (TBL_SPAN_DATA):
119 		/* Iterate over template headers. */
120 		dp = sp->first;
121 		for (hp = sp->head; hp; hp = hp->next) {
122 			switch (hp->pos) {
123 			case (TBL_HEAD_VERT):
124 				/* FALLTHROUGH */
125 			case (TBL_HEAD_DVERT):
126 				tbl_vrule(tp, hp);
127 				continue;
128 			case (TBL_HEAD_DATA):
129 				break;
130 			}
131 
132 			col = &tp->tbl.cols[hp->ident];
133 			tbl_data(tp, sp->tbl, dp, col);
134 
135 			/* Go to the next data cell. */
136 			if (dp)
137 				dp = dp->next;
138 		}
139 		break;
140 	}
141 
142 	tbl_vframe(tp, sp->tbl);
143 	term_flushln(tp);
144 
145 	/*
146 	 * If we're the last row, clean up after ourselves: clear the
147 	 * existing table configuration and set it to NULL.
148 	 */
149 
150 	if (TBL_SPAN_LAST & sp->flags) {
151 		tbl_hframe(tp, sp);
152 		assert(tp->tbl.cols);
153 		free(tp->tbl.cols);
154 		tp->tbl.cols = NULL;
155 	}
156 
157 	tp->flags &= ~TERMP_NONOSPACE;
158 	tp->rmargin = rmargin;
159 	tp->maxrmargin = maxrmargin;
160 
161 }
162 
163 static void
164 tbl_hrule(struct termp *tp, const struct tbl_span *sp)
165 {
166 	const struct tbl_head *hp;
167 	char		 c;
168 	size_t		 width;
169 
170 	/*
171 	 * An hrule extends across the entire table and is demarked by a
172 	 * standalone `_' or whatnot in lieu of a table row.  Spanning
173 	 * headers are marked by a `+', as are table boundaries.
174 	 */
175 
176 	c = '-';
177 	if (TBL_SPAN_DHORIZ == sp->pos)
178 		c = '=';
179 
180 	/* FIXME: don't use `+' between data and a spanner! */
181 
182 	for (hp = sp->head; hp; hp = hp->next) {
183 		width = tp->tbl.cols[hp->ident].width;
184 		switch (hp->pos) {
185 		case (TBL_HEAD_DATA):
186 			tbl_char(tp, c, width);
187 			break;
188 		case (TBL_HEAD_DVERT):
189 			tbl_char(tp, '+', width);
190 			/* FALLTHROUGH */
191 		case (TBL_HEAD_VERT):
192 			tbl_char(tp, '+', width);
193 			break;
194 		default:
195 			abort();
196 			/* NOTREACHED */
197 		}
198 	}
199 }
200 
201 static void
202 tbl_hframe(struct termp *tp, const struct tbl_span *sp)
203 {
204 	const struct tbl_head *hp;
205 	size_t		 width;
206 
207 	if ( ! (TBL_OPT_BOX & sp->tbl->opts ||
208 			TBL_OPT_DBOX & sp->tbl->opts))
209 		return;
210 
211 	/*
212 	 * Print out the horizontal part of a frame or double frame.  A
213 	 * double frame has an unbroken `-' outer line the width of the
214 	 * table, bordered by `+'.  The frame (or inner frame, in the
215 	 * case of the double frame) is a `-' bordered by `+' and broken
216 	 * by `+' whenever a span is encountered.
217 	 */
218 
219 	if (TBL_OPT_DBOX & sp->tbl->opts) {
220 		term_word(tp, "+");
221 		for (hp = sp->head; hp; hp = hp->next) {
222 			width = tp->tbl.cols[hp->ident].width;
223 			tbl_char(tp, '-', width);
224 		}
225 		term_word(tp, "+");
226 		term_flushln(tp);
227 	}
228 
229 	term_word(tp, "+");
230 	for (hp = sp->head; hp; hp = hp->next) {
231 		width = tp->tbl.cols[hp->ident].width;
232 		switch (hp->pos) {
233 		case (TBL_HEAD_DATA):
234 			tbl_char(tp, '-', width);
235 			break;
236 		default:
237 			tbl_char(tp, '+', width);
238 			break;
239 		}
240 	}
241 	term_word(tp, "+");
242 	term_flushln(tp);
243 }
244 
245 static void
246 tbl_data(struct termp *tp, const struct tbl *tbl,
247 		const struct tbl_dat *dp,
248 		const struct roffcol *col)
249 {
250 	enum tbl_cellt	 pos;
251 
252 	if (NULL == dp) {
253 		tbl_char(tp, ASCII_NBRSP, col->width);
254 		return;
255 	}
256 
257 	switch (dp->pos) {
258 	case (TBL_DATA_NONE):
259 		tbl_char(tp, ASCII_NBRSP, col->width);
260 		return;
261 	case (TBL_DATA_HORIZ):
262 		/* FALLTHROUGH */
263 	case (TBL_DATA_NHORIZ):
264 		tbl_char(tp, '-', col->width);
265 		return;
266 	case (TBL_DATA_NDHORIZ):
267 		/* FALLTHROUGH */
268 	case (TBL_DATA_DHORIZ):
269 		tbl_char(tp, '=', col->width);
270 		return;
271 	default:
272 		break;
273 	}
274 
275 	pos = dp && dp->layout ? dp->layout->pos : TBL_CELL_LEFT;
276 
277 	switch (pos) {
278 	case (TBL_CELL_HORIZ):
279 		tbl_char(tp, '-', col->width);
280 		break;
281 	case (TBL_CELL_DHORIZ):
282 		tbl_char(tp, '=', col->width);
283 		break;
284 	case (TBL_CELL_LONG):
285 		/* FALLTHROUGH */
286 	case (TBL_CELL_CENTRE):
287 		/* FALLTHROUGH */
288 	case (TBL_CELL_LEFT):
289 		/* FALLTHROUGH */
290 	case (TBL_CELL_RIGHT):
291 		tbl_literal(tp, dp, col);
292 		break;
293 	case (TBL_CELL_NUMBER):
294 		tbl_number(tp, tbl, dp, col);
295 		break;
296 	default:
297 		abort();
298 		/* NOTREACHED */
299 	}
300 }
301 
302 static void
303 tbl_vrule(struct termp *tp, const struct tbl_head *hp)
304 {
305 
306 	switch (hp->pos) {
307 	case (TBL_HEAD_VERT):
308 		term_word(tp, "|");
309 		break;
310 	case (TBL_HEAD_DVERT):
311 		term_word(tp, "||");
312 		break;
313 	default:
314 		break;
315 	}
316 }
317 
318 static void
319 tbl_vframe(struct termp *tp, const struct tbl *tbl)
320 {
321 
322 	if (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts)
323 		term_word(tp, "|");
324 }
325 
326 static void
327 tbl_char(struct termp *tp, char c, size_t len)
328 {
329 	size_t		i, sz;
330 	char		cp[2];
331 
332 	cp[0] = c;
333 	cp[1] = '\0';
334 
335 	sz = term_strlen(tp, cp);
336 
337 	for (i = 0; i < len; i += sz)
338 		term_word(tp, cp);
339 }
340 
341 static void
342 tbl_literal(struct termp *tp, const struct tbl_dat *dp,
343 		const struct roffcol *col)
344 {
345 	size_t		 padl, padr, ssz;
346 	enum tbl_cellt	 pos;
347 	const char	*str;
348 
349 	padl = padr = 0;
350 
351 	pos = dp && dp->layout ? dp->layout->pos : TBL_CELL_LEFT;
352 	str = dp && dp->string ? dp->string : "";
353 
354 	ssz = term_len(tp, 1);
355 
356 	switch (pos) {
357 	case (TBL_CELL_LONG):
358 		padl = ssz;
359 		padr = col->width - term_strlen(tp, str) - ssz;
360 		break;
361 	case (TBL_CELL_CENTRE):
362 		padl = col->width - term_strlen(tp, str);
363 		if (padl % 2)
364 			padr++;
365 		padl /= 2;
366 		padr += padl;
367 		break;
368 	case (TBL_CELL_RIGHT):
369 		padl = col->width - term_strlen(tp, str);
370 		break;
371 	default:
372 		padr = col->width - term_strlen(tp, str);
373 		break;
374 	}
375 
376 	tbl_char(tp, ASCII_NBRSP, padl);
377 	term_word(tp, str);
378 	tbl_char(tp, ASCII_NBRSP, padr);
379 }
380 
381 static void
382 tbl_number(struct termp *tp, const struct tbl *tbl,
383 		const struct tbl_dat *dp,
384 		const struct roffcol *col)
385 {
386 	char		*cp;
387 	char		 buf[2];
388 	const char	*str;
389 	size_t		 sz, psz, ssz, d, padl;
390 	int		 i;
391 
392 	/*
393 	 * See calc_data_number().  Left-pad by taking the offset of our
394 	 * and the maximum decimal; right-pad by the remaining amount.
395 	 */
396 
397 	str = dp && dp->string ? dp->string : "";
398 
399 	sz = term_strlen(tp, str);
400 
401 	buf[0] = tbl->decimal;
402 	buf[1] = '\0';
403 
404 	psz = term_strlen(tp, buf);
405 
406 	if (NULL != (cp = strchr(str, tbl->decimal))) {
407 		buf[1] = '\0';
408 		for (ssz = 0, i = 0; cp != &str[i]; i++) {
409 			buf[0] = str[i];
410 			ssz += term_strlen(tp, buf);
411 		}
412 		d = ssz + psz;
413 	} else
414 		d = sz + psz;
415 
416 	sz += term_len(tp, 2);
417 	d += term_len(tp, 1);
418 
419 	padl = col->decimal - d;
420 
421 	tbl_char(tp, ASCII_NBRSP, padl);
422 	term_word(tp, str);
423 	tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
424 }
425 
426