xref: /netbsd-src/external/bsd/mdocml/dist/tbl_layout.c (revision 6167eca2d062f3691f8b22e3b8ea212d6dde852a)
1*6167eca2Schristos /*	Id: tbl_layout.c,v 1.48 2018/12/14 05:18:03 schwarze Exp  */
2c0d9444aSjoerg /*
3b1e8115bSjoerg  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
414e7489eSchristos  * Copyright (c) 2012, 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org>
5c0d9444aSjoerg  *
6c0d9444aSjoerg  * Permission to use, copy, modify, and distribute this software for any
7c0d9444aSjoerg  * purpose with or without fee is hereby granted, provided that the above
8c0d9444aSjoerg  * copyright notice and this permission notice appear in all copies.
9c0d9444aSjoerg  *
10c0d9444aSjoerg  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11c0d9444aSjoerg  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12c0d9444aSjoerg  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13c0d9444aSjoerg  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14c0d9444aSjoerg  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15c0d9444aSjoerg  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16c0d9444aSjoerg  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17c0d9444aSjoerg  */
18c09c2a7bSnakayama #include "config.h"
19c09c2a7bSnakayama 
205c413d0cSchristos #include <sys/types.h>
215c413d0cSchristos 
22c0d9444aSjoerg #include <ctype.h>
2314e7489eSchristos #include <stdint.h>
24*6167eca2Schristos #include <stdio.h>
25c0d9444aSjoerg #include <stdlib.h>
26c0d9444aSjoerg #include <string.h>
27c0d9444aSjoerg #include <time.h>
28c0d9444aSjoerg 
295c413d0cSchristos #include "mandoc_aux.h"
30*6167eca2Schristos #include "mandoc.h"
31*6167eca2Schristos #include "tbl.h"
32c0d9444aSjoerg #include "libmandoc.h"
33*6167eca2Schristos #include "tbl_int.h"
34c0d9444aSjoerg 
35c0d9444aSjoerg struct	tbl_phrase {
36c0d9444aSjoerg 	char		 name;
37c0d9444aSjoerg 	enum tbl_cellt	 key;
38c0d9444aSjoerg };
39c0d9444aSjoerg 
405c413d0cSchristos static	const struct tbl_phrase keys[] = {
41c0d9444aSjoerg 	{ 'c',		 TBL_CELL_CENTRE },
42c0d9444aSjoerg 	{ 'r',		 TBL_CELL_RIGHT },
43c0d9444aSjoerg 	{ 'l',		 TBL_CELL_LEFT },
44c0d9444aSjoerg 	{ 'n',		 TBL_CELL_NUMBER },
45c0d9444aSjoerg 	{ 's',		 TBL_CELL_SPAN },
46c0d9444aSjoerg 	{ 'a',		 TBL_CELL_LONG },
47c0d9444aSjoerg 	{ '^',		 TBL_CELL_DOWN },
48c0d9444aSjoerg 	{ '-',		 TBL_CELL_HORIZ },
49c0d9444aSjoerg 	{ '_',		 TBL_CELL_HORIZ },
50603fc4ebSjoerg 	{ '=',		 TBL_CELL_DHORIZ }
51c0d9444aSjoerg };
52c0d9444aSjoerg 
535c413d0cSchristos #define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0])))
54c0d9444aSjoerg 
555c413d0cSchristos static	void		 mods(struct tbl_node *, struct tbl_cell *,
565c413d0cSchristos 				int, const char *, int *);
575c413d0cSchristos static	void		 cell(struct tbl_node *, struct tbl_row *,
585c413d0cSchristos 				int, const char *, int *);
595c413d0cSchristos static	struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
605c413d0cSchristos 				enum tbl_cellt);
615c413d0cSchristos 
625c413d0cSchristos 
635c413d0cSchristos static void
mods(struct tbl_node * tbl,struct tbl_cell * cp,int ln,const char * p,int * pos)64c0d9444aSjoerg mods(struct tbl_node *tbl, struct tbl_cell *cp,
65c0d9444aSjoerg 		int ln, const char *p, int *pos)
66c0d9444aSjoerg {
675c413d0cSchristos 	char		*endptr;
6814e7489eSchristos 	size_t		 sz;
69b1e8115bSjoerg 
70c0d9444aSjoerg mod:
715c413d0cSchristos 	while (p[*pos] == ' ' || p[*pos] == '\t')
725c413d0cSchristos 		(*pos)++;
735c413d0cSchristos 
745c413d0cSchristos 	/* Row delimiters and cell specifiers end modifier lists. */
755c413d0cSchristos 
765c413d0cSchristos 	if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL)
775c413d0cSchristos 		return;
78c0d9444aSjoerg 
79c0d9444aSjoerg 	/* Throw away parenthesised expression. */
80c0d9444aSjoerg 
81c0d9444aSjoerg 	if ('(' == p[*pos]) {
82c0d9444aSjoerg 		(*pos)++;
83c0d9444aSjoerg 		while (p[*pos] && ')' != p[*pos])
84c0d9444aSjoerg 			(*pos)++;
85c0d9444aSjoerg 		if (')' == p[*pos]) {
86c0d9444aSjoerg 			(*pos)++;
87c0d9444aSjoerg 			goto mod;
88c0d9444aSjoerg 		}
89*6167eca2Schristos 		mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, ln, *pos, NULL);
905c413d0cSchristos 		return;
91c0d9444aSjoerg 	}
92c0d9444aSjoerg 
93c0d9444aSjoerg 	/* Parse numerical spacing from modifier string. */
94c0d9444aSjoerg 
95c0d9444aSjoerg 	if (isdigit((unsigned char)p[*pos])) {
965c413d0cSchristos 		cp->spacing = strtoull(p + *pos, &endptr, 10);
975c413d0cSchristos 		*pos = endptr - p;
98c0d9444aSjoerg 		goto mod;
99c0d9444aSjoerg 	}
100c0d9444aSjoerg 
101c0d9444aSjoerg 	switch (tolower((unsigned char)p[(*pos)++])) {
1025c413d0cSchristos 	case 'b':
103c0d9444aSjoerg 		cp->flags |= TBL_CELL_BOLD;
104c0d9444aSjoerg 		goto mod;
1055c413d0cSchristos 	case 'd':
1065c413d0cSchristos 		cp->flags |= TBL_CELL_BALIGN;
1075c413d0cSchristos 		goto mod;
1085c413d0cSchristos 	case 'e':
1095c413d0cSchristos 		cp->flags |= TBL_CELL_EQUAL;
1105c413d0cSchristos 		goto mod;
1115c413d0cSchristos 	case 'f':
1125c413d0cSchristos 		break;
1135c413d0cSchristos 	case 'i':
114c0d9444aSjoerg 		cp->flags |= TBL_CELL_ITALIC;
115c0d9444aSjoerg 		goto mod;
1165c413d0cSchristos 	case 'm':
117*6167eca2Schristos 		mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, ln, *pos, "m");
1185c413d0cSchristos 		goto mod;
1195c413d0cSchristos 	case 'p':
1205c413d0cSchristos 	case 'v':
1215c413d0cSchristos 		if (p[*pos] == '-' || p[*pos] == '+')
1225c413d0cSchristos 			(*pos)++;
1235c413d0cSchristos 		while (isdigit((unsigned char)p[*pos]))
1245c413d0cSchristos 			(*pos)++;
1255c413d0cSchristos 		goto mod;
1265c413d0cSchristos 	case 't':
1275c413d0cSchristos 		cp->flags |= TBL_CELL_TALIGN;
1285c413d0cSchristos 		goto mod;
1295c413d0cSchristos 	case 'u':
1305c413d0cSchristos 		cp->flags |= TBL_CELL_UP;
1315c413d0cSchristos 		goto mod;
13214e7489eSchristos 	case 'w':
13314e7489eSchristos 		sz = 0;
13414e7489eSchristos 		if (p[*pos] == '(') {
13514e7489eSchristos 			(*pos)++;
13614e7489eSchristos 			while (p[*pos + sz] != '\0' && p[*pos + sz] != ')')
13714e7489eSchristos 				sz++;
13814e7489eSchristos 		} else
13914e7489eSchristos 			while (isdigit((unsigned char)p[*pos + sz]))
14014e7489eSchristos 				sz++;
14114e7489eSchristos 		if (sz) {
14214e7489eSchristos 			free(cp->wstr);
14314e7489eSchristos 			cp->wstr = mandoc_strndup(p + *pos, sz);
14414e7489eSchristos 			*pos += sz;
14514e7489eSchristos 			if (p[*pos] == ')')
14614e7489eSchristos 				(*pos)++;
14714e7489eSchristos 		}
1485c413d0cSchristos 		goto mod;
1495c413d0cSchristos 	case 'x':
1505c413d0cSchristos 		cp->flags |= TBL_CELL_WMAX;
1515c413d0cSchristos 		goto mod;
1525c413d0cSchristos 	case 'z':
1535c413d0cSchristos 		cp->flags |= TBL_CELL_WIGN;
1545c413d0cSchristos 		goto mod;
1555c413d0cSchristos 	case '|':
1565c413d0cSchristos 		if (cp->vert < 2)
1575c413d0cSchristos 			cp->vert++;
1585c413d0cSchristos 		else
1595c413d0cSchristos 			mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
160*6167eca2Schristos 			    ln, *pos - 1, NULL);
161b1e8115bSjoerg 		goto mod;
162c0d9444aSjoerg 	default:
163*6167eca2Schristos 		mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
1645c413d0cSchristos 		    ln, *pos - 1, "%c", p[*pos - 1]);
1655c413d0cSchristos 		goto mod;
166c0d9444aSjoerg 	}
167c0d9444aSjoerg 
1685c413d0cSchristos 	/* Ignore parenthised font names for now. */
1695c413d0cSchristos 
1705c413d0cSchristos 	if (p[*pos] == '(')
1715c413d0cSchristos 		goto mod;
1725c413d0cSchristos 
1735c413d0cSchristos 	/* Support only one-character font-names for now. */
1745c413d0cSchristos 
1755c413d0cSchristos 	if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) {
176*6167eca2Schristos 		mandoc_msg(MANDOCERR_FT_BAD,
1775c413d0cSchristos 		    ln, *pos, "TS %s", p + *pos - 1);
1785c413d0cSchristos 		if (p[*pos] != '\0')
1795c413d0cSchristos 			(*pos)++;
1805c413d0cSchristos 		if (p[*pos] != '\0')
1815c413d0cSchristos 			(*pos)++;
1825c413d0cSchristos 		goto mod;
183c0d9444aSjoerg 	}
184c0d9444aSjoerg 
1855c413d0cSchristos 	switch (p[(*pos)++]) {
1865c413d0cSchristos 	case '3':
1875c413d0cSchristos 	case 'B':
1885c413d0cSchristos 		cp->flags |= TBL_CELL_BOLD;
1895c413d0cSchristos 		goto mod;
1905c413d0cSchristos 	case '2':
1915c413d0cSchristos 	case 'I':
1925c413d0cSchristos 		cp->flags |= TBL_CELL_ITALIC;
1935c413d0cSchristos 		goto mod;
1945c413d0cSchristos 	case '1':
1955c413d0cSchristos 	case 'R':
1965c413d0cSchristos 		goto mod;
1975c413d0cSchristos 	default:
198*6167eca2Schristos 		mandoc_msg(MANDOCERR_FT_BAD,
1995c413d0cSchristos 		    ln, *pos - 1, "TS f%c", p[*pos - 1]);
2005c413d0cSchristos 		goto mod;
2015c413d0cSchristos 	}
2025c413d0cSchristos }
2035c413d0cSchristos 
2045c413d0cSchristos static void
cell(struct tbl_node * tbl,struct tbl_row * rp,int ln,const char * p,int * pos)205c0d9444aSjoerg cell(struct tbl_node *tbl, struct tbl_row *rp,
206c0d9444aSjoerg 		int ln, const char *p, int *pos)
207c0d9444aSjoerg {
2085c413d0cSchristos 	int		 i;
209c0d9444aSjoerg 	enum tbl_cellt	 c;
210c0d9444aSjoerg 
2115c413d0cSchristos 	/* Handle leading vertical lines */
212603fc4ebSjoerg 
2135c413d0cSchristos 	while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
2145c413d0cSchristos 		if (p[*pos] == '|') {
2155c413d0cSchristos 			if (rp->vert < 2)
2165c413d0cSchristos 				rp->vert++;
2175c413d0cSchristos 			else
2185c413d0cSchristos 				mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
219*6167eca2Schristos 				    ln, *pos, NULL);
2205c413d0cSchristos 		}
221603fc4ebSjoerg 		(*pos)++;
2225c413d0cSchristos 	}
2235c413d0cSchristos 
2245c413d0cSchristos again:
2255c413d0cSchristos 	while (p[*pos] == ' ' || p[*pos] == '\t')
2265c413d0cSchristos 		(*pos)++;
2275c413d0cSchristos 
2285c413d0cSchristos 	if (p[*pos] == '.' || p[*pos] == '\0')
2295c413d0cSchristos 		return;
230603fc4ebSjoerg 
231603fc4ebSjoerg 	/* Parse the column position (`c', `l', `r', ...). */
232c0d9444aSjoerg 
233c0d9444aSjoerg 	for (i = 0; i < KEYS_MAX; i++)
234c0d9444aSjoerg 		if (tolower((unsigned char)p[*pos]) == keys[i].name)
235c0d9444aSjoerg 			break;
236c0d9444aSjoerg 
2375c413d0cSchristos 	if (i == KEYS_MAX) {
238*6167eca2Schristos 		mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
2395c413d0cSchristos 		    ln, *pos, "%c", p[*pos]);
2405c413d0cSchristos 		(*pos)++;
2415c413d0cSchristos 		goto again;
242c0d9444aSjoerg 	}
243c0d9444aSjoerg 	c = keys[i].key;
244c0d9444aSjoerg 
2455c413d0cSchristos 	/* Special cases of spanners. */
246c0d9444aSjoerg 
2475c413d0cSchristos 	if (c == TBL_CELL_SPAN) {
2485c413d0cSchristos 		if (rp->last == NULL)
249*6167eca2Schristos 			mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN, ln, *pos, NULL);
2505c413d0cSchristos 		else if (rp->last->pos == TBL_CELL_HORIZ ||
2515c413d0cSchristos 		    rp->last->pos == TBL_CELL_DHORIZ)
2525c413d0cSchristos 			c = rp->last->pos;
2535c413d0cSchristos 	} else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
254*6167eca2Schristos 		mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN, ln, *pos, NULL);
255c0d9444aSjoerg 
256c0d9444aSjoerg 	(*pos)++;
257c0d9444aSjoerg 
258c0d9444aSjoerg 	/* Allocate cell then parse its modifiers. */
259c0d9444aSjoerg 
2605c413d0cSchristos 	mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
261c0d9444aSjoerg }
262c0d9444aSjoerg 
2635c413d0cSchristos void
tbl_layout(struct tbl_node * tbl,int ln,const char * p,int pos)2645c413d0cSchristos tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
265c0d9444aSjoerg {
266c0d9444aSjoerg 	struct tbl_row	*rp;
267c0d9444aSjoerg 
2685c413d0cSchristos 	rp = NULL;
2695c413d0cSchristos 	for (;;) {
2705c413d0cSchristos 		/* Skip whitespace before and after each cell. */
2715c413d0cSchristos 
2725c413d0cSchristos 		while (p[pos] == ' ' || p[pos] == '\t')
2735c413d0cSchristos 			pos++;
2745c413d0cSchristos 
2755c413d0cSchristos 		switch (p[pos]) {
2765c413d0cSchristos 		case ',':  /* Next row on this input line. */
2775c413d0cSchristos 			pos++;
2785c413d0cSchristos 			rp = NULL;
2795c413d0cSchristos 			continue;
2805c413d0cSchristos 		case '\0':  /* Next row on next input line. */
2815c413d0cSchristos 			return;
2825c413d0cSchristos 		case '.':  /* End of layout. */
2835c413d0cSchristos 			pos++;
2845c413d0cSchristos 			tbl->part = TBL_PART_DATA;
2855c413d0cSchristos 
2865c413d0cSchristos 			/*
2875c413d0cSchristos 			 * When the layout is completely empty,
2885c413d0cSchristos 			 * default to one left-justified column.
289c0d9444aSjoerg 			 */
290c0d9444aSjoerg 
2915c413d0cSchristos 			if (tbl->first_row == NULL) {
2925c413d0cSchristos 				tbl->first_row = tbl->last_row =
2935c413d0cSchristos 				    mandoc_calloc(1, sizeof(*rp));
2945c413d0cSchristos 			}
2955c413d0cSchristos 			if (tbl->first_row->first == NULL) {
2965c413d0cSchristos 				mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
297*6167eca2Schristos 				    ln, pos, NULL);
2985c413d0cSchristos 				cell_alloc(tbl, tbl->first_row,
2995c413d0cSchristos 				    TBL_CELL_LEFT);
30014e7489eSchristos 				if (tbl->opts.lvert < tbl->first_row->vert)
30114e7489eSchristos 					tbl->opts.lvert = tbl->first_row->vert;
3025c413d0cSchristos 				return;
3035c413d0cSchristos 			}
3045c413d0cSchristos 
3055c413d0cSchristos 			/*
3065c413d0cSchristos 			 * Search for the widest line
3075c413d0cSchristos 			 * along the left and right margins.
3085c413d0cSchristos 			 */
3095c413d0cSchristos 
3105c413d0cSchristos 			for (rp = tbl->first_row; rp; rp = rp->next) {
3115c413d0cSchristos 				if (tbl->opts.lvert < rp->vert)
3125c413d0cSchristos 					tbl->opts.lvert = rp->vert;
3135c413d0cSchristos 				if (rp->last != NULL &&
3145c413d0cSchristos 				    rp->last->col + 1 == tbl->opts.cols &&
3155c413d0cSchristos 				    tbl->opts.rvert < rp->last->vert)
3165c413d0cSchristos 					tbl->opts.rvert = rp->last->vert;
3175c413d0cSchristos 
3185c413d0cSchristos 				/* If the last line is empty, drop it. */
3195c413d0cSchristos 
3205c413d0cSchristos 				if (rp->next != NULL &&
3215c413d0cSchristos 				    rp->next->first == NULL) {
3225c413d0cSchristos 					free(rp->next);
3235c413d0cSchristos 					rp->next = NULL;
324f47368cfSchristos 					tbl->last_row = rp;
3255c413d0cSchristos 				}
3265c413d0cSchristos 			}
3275c413d0cSchristos 			return;
3285c413d0cSchristos 		default:  /* Cell. */
3295c413d0cSchristos 			break;
3305c413d0cSchristos 		}
3315c413d0cSchristos 
3325c413d0cSchristos 		/*
3335c413d0cSchristos 		 * If the last line had at least one cell,
3345c413d0cSchristos 		 * start a new one; otherwise, continue it.
3355c413d0cSchristos 		 */
3365c413d0cSchristos 
3375c413d0cSchristos 		if (rp == NULL) {
3385c413d0cSchristos 			if (tbl->last_row == NULL ||
3395c413d0cSchristos 			    tbl->last_row->first != NULL) {
3405c413d0cSchristos 				rp = mandoc_calloc(1, sizeof(*rp));
341603fc4ebSjoerg 				if (tbl->last_row)
342c0d9444aSjoerg 					tbl->last_row->next = rp;
343603fc4ebSjoerg 				else
344603fc4ebSjoerg 					tbl->first_row = rp;
345c0d9444aSjoerg 				tbl->last_row = rp;
3465c413d0cSchristos 			} else
3475c413d0cSchristos 				rp = tbl->last_row;
348c0d9444aSjoerg 		}
3495c413d0cSchristos 		cell(tbl, rp, ln, p, &pos);
350c0d9444aSjoerg 	}
351c0d9444aSjoerg }
352c0d9444aSjoerg 
353c0d9444aSjoerg static struct tbl_cell *
cell_alloc(struct tbl_node * tbl,struct tbl_row * rp,enum tbl_cellt pos)3545c413d0cSchristos cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
355c0d9444aSjoerg {
356c0d9444aSjoerg 	struct tbl_cell	*p, *pp;
357c0d9444aSjoerg 
3585c413d0cSchristos 	p = mandoc_calloc(1, sizeof(*p));
35914e7489eSchristos 	p->spacing = SIZE_MAX;
3605c413d0cSchristos 	p->pos = pos;
361c0d9444aSjoerg 
3625c413d0cSchristos 	if ((pp = rp->last) != NULL) {
363603fc4ebSjoerg 		pp->next = p;
3645c413d0cSchristos 		p->col = pp->col + 1;
3655c413d0cSchristos 	} else
366603fc4ebSjoerg 		rp->first = p;
367c0d9444aSjoerg 	rp->last = p;
368c0d9444aSjoerg 
3695c413d0cSchristos 	if (tbl->opts.cols <= p->col)
3705c413d0cSchristos 		tbl->opts.cols = p->col + 1;
371c0d9444aSjoerg 
372f47368cfSchristos 	return p;
373c0d9444aSjoerg }
374