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