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