xref: /illumos-gate/usr/src/cmd/mandoc/tbl_layout.c (revision bf5d9f18edeb77c14df996d367853599bdd43fd1)
1 /*	$Id: tbl_layout.c,v 1.44 2017/06/27 18:25:02 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 <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 
28 #include "mandoc.h"
29 #include "mandoc_aux.h"
30 #include "libmandoc.h"
31 #include "libroff.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, tbl->parse,
88 		    ln, *pos, NULL);
89 		return;
90 	}
91 
92 	/* Parse numerical spacing from modifier string. */
93 
94 	if (isdigit((unsigned char)p[*pos])) {
95 		cp->spacing = strtoull(p + *pos, &endptr, 10);
96 		*pos = endptr - p;
97 		goto mod;
98 	}
99 
100 	switch (tolower((unsigned char)p[(*pos)++])) {
101 	case 'b':
102 		cp->flags |= TBL_CELL_BOLD;
103 		goto mod;
104 	case 'd':
105 		cp->flags |= TBL_CELL_BALIGN;
106 		goto mod;
107 	case 'e':
108 		cp->flags |= TBL_CELL_EQUAL;
109 		goto mod;
110 	case 'f':
111 		break;
112 	case 'i':
113 		cp->flags |= TBL_CELL_ITALIC;
114 		goto mod;
115 	case 'm':
116 		mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, tbl->parse,
117 		    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 			    tbl->parse, ln, *pos - 1, NULL);
161 		goto mod;
162 	default:
163 		mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse,
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_vmsg(MANDOCERR_FT_BAD, tbl->parse,
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_vmsg(MANDOCERR_FT_BAD, tbl->parse,
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 				    tbl->parse, 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_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse,
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,
250 			    tbl->parse, ln, *pos, NULL);
251 		else if (rp->last->pos == TBL_CELL_HORIZ ||
252 		    rp->last->pos == TBL_CELL_DHORIZ)
253 			c = rp->last->pos;
254 	} else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
255 		mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN,
256 		    tbl->parse, ln, *pos, NULL);
257 
258 	(*pos)++;
259 
260 	/* Allocate cell then parse its modifiers. */
261 
262 	mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
263 }
264 
265 void
266 tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
267 {
268 	struct tbl_row	*rp;
269 
270 	rp = NULL;
271 	for (;;) {
272 		/* Skip whitespace before and after each cell. */
273 
274 		while (p[pos] == ' ' || p[pos] == '\t')
275 			pos++;
276 
277 		switch (p[pos]) {
278 		case ',':  /* Next row on this input line. */
279 			pos++;
280 			rp = NULL;
281 			continue;
282 		case '\0':  /* Next row on next input line. */
283 			return;
284 		case '.':  /* End of layout. */
285 			pos++;
286 			tbl->part = TBL_PART_DATA;
287 
288 			/*
289 			 * When the layout is completely empty,
290 			 * default to one left-justified column.
291 			 */
292 
293 			if (tbl->first_row == NULL) {
294 				tbl->first_row = tbl->last_row =
295 				    mandoc_calloc(1, sizeof(*rp));
296 			}
297 			if (tbl->first_row->first == NULL) {
298 				mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
299 				    tbl->parse, ln, pos, NULL);
300 				cell_alloc(tbl, tbl->first_row,
301 				    TBL_CELL_LEFT);
302 				if (tbl->opts.lvert < tbl->first_row->vert)
303 					tbl->opts.lvert = tbl->first_row->vert;
304 				return;
305 			}
306 
307 			/*
308 			 * Search for the widest line
309 			 * along the left and right margins.
310 			 */
311 
312 			for (rp = tbl->first_row; rp; rp = rp->next) {
313 				if (tbl->opts.lvert < rp->vert)
314 					tbl->opts.lvert = rp->vert;
315 				if (rp->last != NULL &&
316 				    rp->last->col + 1 == tbl->opts.cols &&
317 				    tbl->opts.rvert < rp->last->vert)
318 					tbl->opts.rvert = rp->last->vert;
319 
320 				/* If the last line is empty, drop it. */
321 
322 				if (rp->next != NULL &&
323 				    rp->next->first == NULL) {
324 					free(rp->next);
325 					rp->next = NULL;
326 					tbl->last_row = rp;
327 				}
328 			}
329 			return;
330 		default:  /* Cell. */
331 			break;
332 		}
333 
334 		/*
335 		 * If the last line had at least one cell,
336 		 * start a new one; otherwise, continue it.
337 		 */
338 
339 		if (rp == NULL) {
340 			if (tbl->last_row == NULL ||
341 			    tbl->last_row->first != NULL) {
342 				rp = mandoc_calloc(1, sizeof(*rp));
343 				if (tbl->last_row)
344 					tbl->last_row->next = rp;
345 				else
346 					tbl->first_row = rp;
347 				tbl->last_row = rp;
348 			} else
349 				rp = tbl->last_row;
350 		}
351 		cell(tbl, rp, ln, p, &pos);
352 	}
353 }
354 
355 static struct tbl_cell *
356 cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
357 {
358 	struct tbl_cell	*p, *pp;
359 
360 	p = mandoc_calloc(1, sizeof(*p));
361 	p->spacing = SIZE_MAX;
362 	p->pos = pos;
363 
364 	if ((pp = rp->last) != NULL) {
365 		pp->next = p;
366 		p->col = pp->col + 1;
367 	} else
368 		rp->first = p;
369 	rp->last = p;
370 
371 	if (tbl->opts.cols <= p->col)
372 		tbl->opts.cols = p->col + 1;
373 
374 	return p;
375 }
376