xref: /openbsd-src/usr.bin/mandoc/tbl_layout.c (revision cb39b41371628601fbe4c618205356d538b9d08a)
1 /*	$OpenBSD: tbl_layout.c,v 1.26 2015/04/29 12:44:10 schwarze Exp $ */
2 /*
3  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 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 <ctype.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <time.h>
24 
25 #include "mandoc.h"
26 #include "mandoc_aux.h"
27 #include "libmandoc.h"
28 #include "libroff.h"
29 
30 struct	tbl_phrase {
31 	char		 name;
32 	enum tbl_cellt	 key;
33 };
34 
35 static	const struct tbl_phrase keys[] = {
36 	{ 'c',		 TBL_CELL_CENTRE },
37 	{ 'r',		 TBL_CELL_RIGHT },
38 	{ 'l',		 TBL_CELL_LEFT },
39 	{ 'n',		 TBL_CELL_NUMBER },
40 	{ 's',		 TBL_CELL_SPAN },
41 	{ 'a',		 TBL_CELL_LONG },
42 	{ '^',		 TBL_CELL_DOWN },
43 	{ '-',		 TBL_CELL_HORIZ },
44 	{ '_',		 TBL_CELL_HORIZ },
45 	{ '=',		 TBL_CELL_DHORIZ }
46 };
47 
48 #define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0])))
49 
50 static	void		 mods(struct tbl_node *, struct tbl_cell *,
51 				int, const char *, int *);
52 static	void		 cell(struct tbl_node *, struct tbl_row *,
53 				int, const char *, int *);
54 static	struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
55 				enum tbl_cellt);
56 
57 
58 static void
59 mods(struct tbl_node *tbl, struct tbl_cell *cp,
60 		int ln, const char *p, int *pos)
61 {
62 	char		*endptr;
63 
64 mod:
65 	while (p[*pos] == ' ' || p[*pos] == '\t')
66 		(*pos)++;
67 
68 	/* Row delimiters and cell specifiers end modifier lists. */
69 
70 	if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL)
71 		return;
72 
73 	/* Throw away parenthesised expression. */
74 
75 	if ('(' == p[*pos]) {
76 		(*pos)++;
77 		while (p[*pos] && ')' != p[*pos])
78 			(*pos)++;
79 		if (')' == p[*pos]) {
80 			(*pos)++;
81 			goto mod;
82 		}
83 		mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, tbl->parse,
84 		    ln, *pos, NULL);
85 		return;
86 	}
87 
88 	/* Parse numerical spacing from modifier string. */
89 
90 	if (isdigit((unsigned char)p[*pos])) {
91 		cp->spacing = strtoull(p + *pos, &endptr, 10);
92 		*pos = endptr - p;
93 		goto mod;
94 	}
95 
96 	switch (tolower((unsigned char)p[(*pos)++])) {
97 	case 'b':
98 		cp->flags |= TBL_CELL_BOLD;
99 		goto mod;
100 	case 'd':
101 		cp->flags |= TBL_CELL_BALIGN;
102 		goto mod;
103 	case 'e':
104 		cp->flags |= TBL_CELL_EQUAL;
105 		goto mod;
106 	case 'f':
107 		break;
108 	case 'i':
109 		cp->flags |= TBL_CELL_ITALIC;
110 		goto mod;
111 	case 'm':
112 		mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, tbl->parse,
113 		    ln, *pos, "m");
114 		goto mod;
115 	case 'p':
116 		/* FALLTHROUGH */
117 	case 'v':
118 		if (p[*pos] == '-' || p[*pos] == '+')
119 			(*pos)++;
120 		while (isdigit((unsigned char)p[*pos]))
121 			(*pos)++;
122 		goto mod;
123 	case 't':
124 		cp->flags |= TBL_CELL_TALIGN;
125 		goto mod;
126 	case 'u':
127 		cp->flags |= TBL_CELL_UP;
128 		goto mod;
129 	case 'w':  /* XXX for now, ignore minimal column width */
130 		goto mod;
131 	case 'x':
132 		cp->flags |= TBL_CELL_WMAX;
133 		goto mod;
134 	case 'z':
135 		cp->flags |= TBL_CELL_WIGN;
136 		goto mod;
137 	case '|':
138 		if (cp->vert < 2)
139 			cp->vert++;
140 		else
141 			mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
142 			    tbl->parse, ln, *pos - 1, NULL);
143 		goto mod;
144 	default:
145 		mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse,
146 		    ln, *pos - 1, "%c", p[*pos - 1]);
147 		goto mod;
148 	}
149 
150 	/* Ignore parenthised font names for now. */
151 
152 	if (p[*pos] == '(')
153 		goto mod;
154 
155 	/* Support only one-character font-names for now. */
156 
157 	if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) {
158 		mandoc_vmsg(MANDOCERR_FT_BAD, tbl->parse,
159 		    ln, *pos, "TS %s", p + *pos - 1);
160 		if (p[*pos] != '\0')
161 			(*pos)++;
162 		if (p[*pos] != '\0')
163 			(*pos)++;
164 		goto mod;
165 	}
166 
167 	switch (p[(*pos)++]) {
168 	case '3':
169 		/* FALLTHROUGH */
170 	case 'B':
171 		cp->flags |= TBL_CELL_BOLD;
172 		goto mod;
173 	case '2':
174 		/* FALLTHROUGH */
175 	case 'I':
176 		cp->flags |= TBL_CELL_ITALIC;
177 		goto mod;
178 	case '1':
179 		/* FALLTHROUGH */
180 	case 'R':
181 		goto mod;
182 	default:
183 		mandoc_vmsg(MANDOCERR_FT_BAD, tbl->parse,
184 		    ln, *pos - 1, "TS f%c", p[*pos - 1]);
185 		goto mod;
186 	}
187 }
188 
189 static void
190 cell(struct tbl_node *tbl, struct tbl_row *rp,
191 		int ln, const char *p, int *pos)
192 {
193 	int		 i;
194 	enum tbl_cellt	 c;
195 
196 	/* Handle leading vertical lines */
197 
198 	while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
199 		if (p[*pos] == '|') {
200 			if (rp->vert < 2)
201 				rp->vert++;
202 			else
203 				mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
204 				    tbl->parse, ln, *pos, NULL);
205 		}
206 		(*pos)++;
207 	}
208 
209 again:
210 	while (p[*pos] == ' ' || p[*pos] == '\t')
211 		(*pos)++;
212 
213 	if (p[*pos] == '.' || p[*pos] == '\0')
214 		return;
215 
216 	/* Parse the column position (`c', `l', `r', ...). */
217 
218 	for (i = 0; i < KEYS_MAX; i++)
219 		if (tolower((unsigned char)p[*pos]) == keys[i].name)
220 			break;
221 
222 	if (i == KEYS_MAX) {
223 		mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse,
224 		    ln, *pos, "%c", p[*pos]);
225 		(*pos)++;
226 		goto again;
227 	}
228 	c = keys[i].key;
229 
230 	/* Special cases of spanners. */
231 
232 	if (c == TBL_CELL_SPAN) {
233 		if (rp->last == NULL)
234 			mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN,
235 			    tbl->parse, ln, *pos, NULL);
236 		else if (rp->last->pos == TBL_CELL_HORIZ ||
237 		    rp->last->pos == TBL_CELL_DHORIZ)
238 			c = rp->last->pos;
239 	} else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
240 		mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN,
241 		    tbl->parse, ln, *pos, NULL);
242 
243 	(*pos)++;
244 
245 	/* Allocate cell then parse its modifiers. */
246 
247 	mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
248 }
249 
250 void
251 tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
252 {
253 	struct tbl_row	*rp;
254 
255 	rp = NULL;
256 	for (;;) {
257 		/* Skip whitespace before and after each cell. */
258 
259 		while (p[pos] == ' ' || p[pos] == '\t')
260 			pos++;
261 
262 		switch (p[pos]) {
263 		case ',':  /* Next row on this input line. */
264 			pos++;
265 			rp = NULL;
266 			continue;
267 		case '\0':  /* Next row on next input line. */
268 			return;
269 		case '.':  /* End of layout. */
270 			pos++;
271 			tbl->part = TBL_PART_DATA;
272 
273 			/*
274 			 * When the layout is completely empty,
275 			 * default to one left-justified column.
276 			 */
277 
278 			if (tbl->first_row == NULL) {
279 				tbl->first_row = tbl->last_row =
280 				    mandoc_calloc(1, sizeof(*rp));
281 			}
282 			if (tbl->first_row->first == NULL) {
283 				mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
284 				    tbl->parse, ln, pos, NULL);
285 				cell_alloc(tbl, tbl->first_row,
286 				    TBL_CELL_LEFT);
287 				return;
288 			}
289 
290 			/*
291 			 * Search for the widest line
292 			 * along the left and right margins.
293 			 */
294 
295 			for (rp = tbl->first_row; rp; rp = rp->next) {
296 				if (tbl->opts.lvert < rp->vert)
297 					tbl->opts.lvert = rp->vert;
298 				if (rp->last != NULL &&
299 				    rp->last->col + 1 == tbl->opts.cols &&
300 				    tbl->opts.rvert < rp->last->vert)
301 					tbl->opts.rvert = rp->last->vert;
302 
303 				/* If the last line is empty, drop it. */
304 
305 				if (rp->next != NULL &&
306 				    rp->next->first == NULL) {
307 					free(rp->next);
308 					rp->next = NULL;
309 					tbl->last_row = rp;
310 				}
311 			}
312 			return;
313 		default:  /* Cell. */
314 			break;
315 		}
316 
317 		/*
318 		 * If the last line had at least one cell,
319 		 * start a new one; otherwise, continue it.
320 		 */
321 
322 		if (rp == NULL) {
323 			if (tbl->last_row == NULL ||
324 			    tbl->last_row->first != NULL) {
325 				rp = mandoc_calloc(1, sizeof(*rp));
326 				if (tbl->last_row)
327 					tbl->last_row->next = rp;
328 				else
329 					tbl->first_row = rp;
330 				tbl->last_row = rp;
331 			} else
332 				rp = tbl->last_row;
333 		}
334 		cell(tbl, rp, ln, p, &pos);
335 	}
336 }
337 
338 static struct tbl_cell *
339 cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
340 {
341 	struct tbl_cell	*p, *pp;
342 
343 	p = mandoc_calloc(1, sizeof(*p));
344 	p->pos = pos;
345 
346 	if ((pp = rp->last) != NULL) {
347 		pp->next = p;
348 		p->col = pp->col + 1;
349 	} else
350 		rp->first = p;
351 	rp->last = p;
352 
353 	if (tbl->opts.cols <= p->col)
354 		tbl->opts.cols = p->col + 1;
355 
356 	return(p);
357 }
358