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