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