1/*	$Id: tbl_layout.c,v 1.41 2015/10/12 00:08:16 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
32struct	tbl_phrase {
33	char		 name;
34	enum tbl_cellt	 key;
35};
36
37static	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
52static	void		 mods(struct tbl_node *, struct tbl_cell *,
53				int, const char *, int *);
54static	void		 cell(struct tbl_node *, struct tbl_row *,
55				int, const char *, int *);
56static	struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
57				enum tbl_cellt);
58
59
60static void
61mods(struct tbl_node *tbl, struct tbl_cell *cp,
62		int ln, const char *p, int *pos)
63{
64	char		*endptr;
65
66mod:
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	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':  /* XXX for now, ignore minimal column width */
131		goto mod;
132	case 'x':
133		cp->flags |= TBL_CELL_WMAX;
134		goto mod;
135	case 'z':
136		cp->flags |= TBL_CELL_WIGN;
137		goto mod;
138	case '|':
139		if (cp->vert < 2)
140			cp->vert++;
141		else
142			mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
143			    tbl->parse, ln, *pos - 1, NULL);
144		goto mod;
145	default:
146		mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse,
147		    ln, *pos - 1, "%c", p[*pos - 1]);
148		goto mod;
149	}
150
151	/* Ignore parenthised font names for now. */
152
153	if (p[*pos] == '(')
154		goto mod;
155
156	/* Support only one-character font-names for now. */
157
158	if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) {
159		mandoc_vmsg(MANDOCERR_FT_BAD, tbl->parse,
160		    ln, *pos, "TS %s", p + *pos - 1);
161		if (p[*pos] != '\0')
162			(*pos)++;
163		if (p[*pos] != '\0')
164			(*pos)++;
165		goto mod;
166	}
167
168	switch (p[(*pos)++]) {
169	case '3':
170	case 'B':
171		cp->flags |= TBL_CELL_BOLD;
172		goto mod;
173	case '2':
174	case 'I':
175		cp->flags |= TBL_CELL_ITALIC;
176		goto mod;
177	case '1':
178	case 'R':
179		goto mod;
180	default:
181		mandoc_vmsg(MANDOCERR_FT_BAD, tbl->parse,
182		    ln, *pos - 1, "TS f%c", p[*pos - 1]);
183		goto mod;
184	}
185}
186
187static void
188cell(struct tbl_node *tbl, struct tbl_row *rp,
189		int ln, const char *p, int *pos)
190{
191	int		 i;
192	enum tbl_cellt	 c;
193
194	/* Handle leading vertical lines */
195
196	while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
197		if (p[*pos] == '|') {
198			if (rp->vert < 2)
199				rp->vert++;
200			else
201				mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
202				    tbl->parse, ln, *pos, NULL);
203		}
204		(*pos)++;
205	}
206
207again:
208	while (p[*pos] == ' ' || p[*pos] == '\t')
209		(*pos)++;
210
211	if (p[*pos] == '.' || p[*pos] == '\0')
212		return;
213
214	/* Parse the column position (`c', `l', `r', ...). */
215
216	for (i = 0; i < KEYS_MAX; i++)
217		if (tolower((unsigned char)p[*pos]) == keys[i].name)
218			break;
219
220	if (i == KEYS_MAX) {
221		mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse,
222		    ln, *pos, "%c", p[*pos]);
223		(*pos)++;
224		goto again;
225	}
226	c = keys[i].key;
227
228	/* Special cases of spanners. */
229
230	if (c == TBL_CELL_SPAN) {
231		if (rp->last == NULL)
232			mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN,
233			    tbl->parse, ln, *pos, NULL);
234		else if (rp->last->pos == TBL_CELL_HORIZ ||
235		    rp->last->pos == TBL_CELL_DHORIZ)
236			c = rp->last->pos;
237	} else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
238		mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN,
239		    tbl->parse, ln, *pos, NULL);
240
241	(*pos)++;
242
243	/* Allocate cell then parse its modifiers. */
244
245	mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
246}
247
248void
249tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
250{
251	struct tbl_row	*rp;
252
253	rp = NULL;
254	for (;;) {
255		/* Skip whitespace before and after each cell. */
256
257		while (p[pos] == ' ' || p[pos] == '\t')
258			pos++;
259
260		switch (p[pos]) {
261		case ',':  /* Next row on this input line. */
262			pos++;
263			rp = NULL;
264			continue;
265		case '\0':  /* Next row on next input line. */
266			return;
267		case '.':  /* End of layout. */
268			pos++;
269			tbl->part = TBL_PART_DATA;
270
271			/*
272			 * When the layout is completely empty,
273			 * default to one left-justified column.
274			 */
275
276			if (tbl->first_row == NULL) {
277				tbl->first_row = tbl->last_row =
278				    mandoc_calloc(1, sizeof(*rp));
279			}
280			if (tbl->first_row->first == NULL) {
281				mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
282				    tbl->parse, ln, pos, NULL);
283				cell_alloc(tbl, tbl->first_row,
284				    TBL_CELL_LEFT);
285				return;
286			}
287
288			/*
289			 * Search for the widest line
290			 * along the left and right margins.
291			 */
292
293			for (rp = tbl->first_row; rp; rp = rp->next) {
294				if (tbl->opts.lvert < rp->vert)
295					tbl->opts.lvert = rp->vert;
296				if (rp->last != NULL &&
297				    rp->last->col + 1 == tbl->opts.cols &&
298				    tbl->opts.rvert < rp->last->vert)
299					tbl->opts.rvert = rp->last->vert;
300
301				/* If the last line is empty, drop it. */
302
303				if (rp->next != NULL &&
304				    rp->next->first == NULL) {
305					free(rp->next);
306					rp->next = NULL;
307					tbl->last_row = rp;
308				}
309			}
310			return;
311		default:  /* Cell. */
312			break;
313		}
314
315		/*
316		 * If the last line had at least one cell,
317		 * start a new one; otherwise, continue it.
318		 */
319
320		if (rp == NULL) {
321			if (tbl->last_row == NULL ||
322			    tbl->last_row->first != NULL) {
323				rp = mandoc_calloc(1, sizeof(*rp));
324				if (tbl->last_row)
325					tbl->last_row->next = rp;
326				else
327					tbl->first_row = rp;
328				tbl->last_row = rp;
329			} else
330				rp = tbl->last_row;
331		}
332		cell(tbl, rp, ln, p, &pos);
333	}
334}
335
336static struct tbl_cell *
337cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
338{
339	struct tbl_cell	*p, *pp;
340
341	p = mandoc_calloc(1, sizeof(*p));
342	p->pos = pos;
343
344	if ((pp = rp->last) != NULL) {
345		pp->next = p;
346		p->col = pp->col + 1;
347	} else
348		rp->first = p;
349	rp->last = p;
350
351	if (tbl->opts.cols <= p->col)
352		tbl->opts.cols = p->col + 1;
353
354	return p;
355}
356