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