xref: /openbsd-src/usr.bin/mandoc/tbl_layout.c (revision 60395358bd468881069c44744132008b5b82170f)
1*60395358Sschwarze /*	$OpenBSD: tbl_layout.c,v 1.38 2025/01/05 18:03:51 schwarze Exp $ */
2393cb51eSschwarze /*
3*60395358Sschwarze  * Copyright (c) 2012, 2014, 2015, 2017, 2020, 2021, 2025
47d063611Sschwarze  *               Ingo Schwarze <schwarze@openbsd.org>
5*60395358Sschwarze  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
6393cb51eSschwarze  *
7393cb51eSschwarze  * Permission to use, copy, modify, and distribute this software for any
8393cb51eSschwarze  * purpose with or without fee is hereby granted, provided that the above
9393cb51eSschwarze  * copyright notice and this permission notice appear in all copies.
10393cb51eSschwarze  *
11393cb51eSschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12393cb51eSschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13393cb51eSschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14393cb51eSschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15393cb51eSschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16393cb51eSschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17393cb51eSschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18393cb51eSschwarze  */
1946fa2066Sschwarze #include <sys/types.h>
2046fa2066Sschwarze 
21393cb51eSschwarze #include <ctype.h>
22e8a65004Sschwarze #include <stdint.h>
23e501e731Sschwarze #include <stdio.h>
24393cb51eSschwarze #include <stdlib.h>
25393cb51eSschwarze #include <string.h>
262791bd1cSschwarze #include <time.h>
27393cb51eSschwarze 
284f4f7972Sschwarze #include "mandoc_aux.h"
29fae2491eSschwarze #include "mandoc.h"
30fae2491eSschwarze #include "tbl.h"
312791bd1cSschwarze #include "libmandoc.h"
32fb382a01Sschwarze #include "tbl_int.h"
33393cb51eSschwarze 
34393cb51eSschwarze struct	tbl_phrase {
35393cb51eSschwarze 	char		 name;
36393cb51eSschwarze 	enum tbl_cellt	 key;
37393cb51eSschwarze };
38393cb51eSschwarze 
39bba1fa43Sschwarze static	const struct tbl_phrase keys[] = {
40393cb51eSschwarze 	{ 'c',		 TBL_CELL_CENTRE },
41393cb51eSschwarze 	{ 'r',		 TBL_CELL_RIGHT },
42393cb51eSschwarze 	{ 'l',		 TBL_CELL_LEFT },
43393cb51eSschwarze 	{ 'n',		 TBL_CELL_NUMBER },
44393cb51eSschwarze 	{ 's',		 TBL_CELL_SPAN },
45393cb51eSschwarze 	{ 'a',		 TBL_CELL_LONG },
46393cb51eSschwarze 	{ '^',		 TBL_CELL_DOWN },
47393cb51eSschwarze 	{ '-',		 TBL_CELL_HORIZ },
48393cb51eSschwarze 	{ '_',		 TBL_CELL_HORIZ },
493006448aSschwarze 	{ '=',		 TBL_CELL_DHORIZ }
50393cb51eSschwarze };
51393cb51eSschwarze 
52bba1fa43Sschwarze #define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0])))
53bba1fa43Sschwarze 
54bba1fa43Sschwarze static	void		 mods(struct tbl_node *, struct tbl_cell *,
55393cb51eSschwarze 				int, const char *, int *);
56bba1fa43Sschwarze static	void		 cell(struct tbl_node *, struct tbl_row *,
572791bd1cSschwarze 				int, const char *, int *);
583006448aSschwarze static	struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
59fd9b947eSschwarze 				enum tbl_cellt);
60393cb51eSschwarze 
6149aff9f8Sschwarze 
62bba1fa43Sschwarze static void
632791bd1cSschwarze mods(struct tbl_node *tbl, struct tbl_cell *cp,
642791bd1cSschwarze 		int ln, const char *p, int *pos)
65393cb51eSschwarze {
66bba1fa43Sschwarze 	char		*endptr;
67c107dca7Sschwarze 	unsigned long	 spacing;
687d063611Sschwarze 	int		 isz;
697d063611Sschwarze 	enum mandoc_esc	 fontesc;
70a5e11edeSschwarze 
712791bd1cSschwarze mod:
72bba1fa43Sschwarze 	while (p[*pos] == ' ' || p[*pos] == '\t')
73bba1fa43Sschwarze 		(*pos)++;
74bba1fa43Sschwarze 
75bba1fa43Sschwarze 	/* Row delimiters and cell specifiers end modifier lists. */
76bba1fa43Sschwarze 
77fd9b947eSschwarze 	if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL)
78bba1fa43Sschwarze 		return;
79393cb51eSschwarze 
80ec04407bSschwarze 	/* Throw away parenthesised expression. */
81ec04407bSschwarze 
82ec04407bSschwarze 	if ('(' == p[*pos]) {
83ec04407bSschwarze 		(*pos)++;
84ec04407bSschwarze 		while (p[*pos] && ')' != p[*pos])
85ec04407bSschwarze 			(*pos)++;
86ec04407bSschwarze 		if (')' == p[*pos]) {
87ec04407bSschwarze 			(*pos)++;
88ec04407bSschwarze 			goto mod;
89ec04407bSschwarze 		}
90a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, ln, *pos, NULL);
91bba1fa43Sschwarze 		return;
92ec04407bSschwarze 	}
93ec04407bSschwarze 
94393cb51eSschwarze 	/* Parse numerical spacing from modifier string. */
95393cb51eSschwarze 
962791bd1cSschwarze 	if (isdigit((unsigned char)p[*pos])) {
97c107dca7Sschwarze 		if ((spacing = strtoul(p + *pos, &endptr, 10)) > 9)
98c107dca7Sschwarze 			mandoc_msg(MANDOCERR_TBLLAYOUT_SPC, ln, *pos,
99c107dca7Sschwarze 			    "%lu", spacing);
100c107dca7Sschwarze 		else
101c107dca7Sschwarze 			cp->spacing = spacing;
102bba1fa43Sschwarze 		*pos = endptr - p;
1032791bd1cSschwarze 		goto mod;
104393cb51eSschwarze 	}
105393cb51eSschwarze 
106ec04407bSschwarze 	switch (tolower((unsigned char)p[(*pos)++])) {
107bba1fa43Sschwarze 	case 'b':
1087d063611Sschwarze 		cp->font = ESCAPE_FONTBOLD;
109897e9af6Sschwarze 		goto mod;
110bba1fa43Sschwarze 	case 'd':
111bba1fa43Sschwarze 		cp->flags |= TBL_CELL_BALIGN;
1122791bd1cSschwarze 		goto mod;
11349aff9f8Sschwarze 	case 'e':
114393cb51eSschwarze 		cp->flags |= TBL_CELL_EQUAL;
1152791bd1cSschwarze 		goto mod;
116bba1fa43Sschwarze 	case 'f':
117bba1fa43Sschwarze 		break;
118897e9af6Sschwarze 	case 'i':
1197d063611Sschwarze 		cp->font = ESCAPE_FONTITALIC;
120897e9af6Sschwarze 		goto mod;
121bba1fa43Sschwarze 	case 'm':
122a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, ln, *pos, "m");
123bba1fa43Sschwarze 		goto mod;
124bba1fa43Sschwarze 	case 'p':
125bba1fa43Sschwarze 	case 'v':
126bba1fa43Sschwarze 		if (p[*pos] == '-' || p[*pos] == '+')
127bba1fa43Sschwarze 			(*pos)++;
128bba1fa43Sschwarze 		while (isdigit((unsigned char)p[*pos]))
129bba1fa43Sschwarze 			(*pos)++;
130bba1fa43Sschwarze 		goto mod;
13149aff9f8Sschwarze 	case 't':
132393cb51eSschwarze 		cp->flags |= TBL_CELL_TALIGN;
1332791bd1cSschwarze 		goto mod;
134bba1fa43Sschwarze 	case 'u':
135bba1fa43Sschwarze 		cp->flags |= TBL_CELL_UP;
1362791bd1cSschwarze 		goto mod;
1372c3e66c4Sschwarze 	case 'w':
1382c3e66c4Sschwarze 		if (p[*pos] == '(') {
1392c3e66c4Sschwarze 			(*pos)++;
140*60395358Sschwarze 			isz = 0;
141*60395358Sschwarze 			if (roff_evalnum(ln, p, pos, &isz, 'n', 1) == 0 ||
142*60395358Sschwarze 			    p[*pos] != ')')
143*60395358Sschwarze 				mandoc_msg(MANDOCERR_TBLLAYOUT_WIDTH,
144*60395358Sschwarze 				    ln, *pos, "%s", p + *pos);
145*60395358Sschwarze 			else {
146*60395358Sschwarze 				/* Convert from BU to EN and round. */
147*60395358Sschwarze 				cp->width = (isz + 11) /24;
1482c3e66c4Sschwarze 				(*pos)++;
1492c3e66c4Sschwarze 			}
150*60395358Sschwarze 		} else {
151*60395358Sschwarze 			cp->width = 0;
152*60395358Sschwarze 			while (isdigit((unsigned char)p[*pos])) {
153*60395358Sschwarze 				cp->width *= 10;
154*60395358Sschwarze 				cp->width += p[(*pos)++] - '0';
155*60395358Sschwarze 			}
156*60395358Sschwarze 			if (cp->width == 0)
157*60395358Sschwarze 				mandoc_msg(MANDOCERR_TBLLAYOUT_WIDTH,
158*60395358Sschwarze 				    ln, *pos, "%s", p + *pos);
159*60395358Sschwarze 		}
160a3dd34c5Sschwarze 		goto mod;
16146fa2066Sschwarze 	case 'x':
16246fa2066Sschwarze 		cp->flags |= TBL_CELL_WMAX;
16346fa2066Sschwarze 		goto mod;
164bba1fa43Sschwarze 	case 'z':
165bba1fa43Sschwarze 		cp->flags |= TBL_CELL_WIGN;
166bba1fa43Sschwarze 		goto mod;
167fd9b947eSschwarze 	case '|':
168fd9b947eSschwarze 		if (cp->vert < 2)
169fd9b947eSschwarze 			cp->vert++;
170fd9b947eSschwarze 		else
171fd9b947eSschwarze 			mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
172a5a5f808Sschwarze 			    ln, *pos - 1, NULL);
173fd9b947eSschwarze 		goto mod;
174393cb51eSschwarze 	default:
175a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
176bba1fa43Sschwarze 		    ln, *pos - 1, "%c", p[*pos - 1]);
177bba1fa43Sschwarze 		goto mod;
178393cb51eSschwarze 	}
179393cb51eSschwarze 
1807d063611Sschwarze 	while (p[*pos] == ' ' || p[*pos] == '\t')
1817d063611Sschwarze 		(*pos)++;
1827d063611Sschwarze 
183897e9af6Sschwarze 	/* Ignore parenthised font names for now. */
184897e9af6Sschwarze 
185897e9af6Sschwarze 	if (p[*pos] == '(')
186897e9af6Sschwarze 		goto mod;
187897e9af6Sschwarze 
1887d063611Sschwarze 	isz = 0;
1897d063611Sschwarze 	if (p[*pos] != '\0')
1907d063611Sschwarze 		isz++;
1917d063611Sschwarze 	if (strchr(" \t.", p[*pos + isz]) == NULL)
1927d063611Sschwarze 		isz++;
193897e9af6Sschwarze 
1947d063611Sschwarze 	fontesc = mandoc_font(p + *pos, isz);
1957d063611Sschwarze 
1967d063611Sschwarze 	switch (fontesc) {
1977d063611Sschwarze 	case ESCAPE_FONTPREV:
1987d063611Sschwarze 	case ESCAPE_ERROR:
199a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_FT_BAD,
200897e9af6Sschwarze 		    ln, *pos, "TS %s", p + *pos - 1);
2017d063611Sschwarze 		break;
202393cb51eSschwarze 	default:
2037d063611Sschwarze 		cp->font = fontesc;
2047d063611Sschwarze 		break;
2052fc8b5b2Sschwarze 	}
2067d063611Sschwarze 	*pos += isz;
2077d063611Sschwarze 	goto mod;
208393cb51eSschwarze }
209393cb51eSschwarze 
210bba1fa43Sschwarze static void
2112791bd1cSschwarze cell(struct tbl_node *tbl, struct tbl_row *rp,
2122791bd1cSschwarze 		int ln, const char *p, int *pos)
213393cb51eSschwarze {
214fd9b947eSschwarze 	int		 i;
215393cb51eSschwarze 	enum tbl_cellt	 c;
216393cb51eSschwarze 
217fd9b947eSschwarze 	/* Handle leading vertical lines */
2183006448aSschwarze 
219bba1fa43Sschwarze 	while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
220bba1fa43Sschwarze 		if (p[*pos] == '|') {
221fd9b947eSschwarze 			if (rp->vert < 2)
222fd9b947eSschwarze 				rp->vert++;
223bba1fa43Sschwarze 			else
224bba1fa43Sschwarze 				mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
225a5a5f808Sschwarze 				    ln, *pos, NULL);
226bba1fa43Sschwarze 		}
2273006448aSschwarze 		(*pos)++;
228bba1fa43Sschwarze 	}
2293006448aSschwarze 
230fd9b947eSschwarze again:
231fd9b947eSschwarze 	while (p[*pos] == ' ' || p[*pos] == '\t')
232fd9b947eSschwarze 		(*pos)++;
23392b16df0Sschwarze 
234fd9b947eSschwarze 	if (p[*pos] == '.' || p[*pos] == '\0')
235bba1fa43Sschwarze 		return;
23692b16df0Sschwarze 
2373006448aSschwarze 	/* Parse the column position (`c', `l', `r', ...). */
238393cb51eSschwarze 
2392791bd1cSschwarze 	for (i = 0; i < KEYS_MAX; i++)
240ec04407bSschwarze 		if (tolower((unsigned char)p[*pos]) == keys[i].name)
241393cb51eSschwarze 			break;
2422791bd1cSschwarze 
243bba1fa43Sschwarze 	if (i == KEYS_MAX) {
244a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
245bba1fa43Sschwarze 		    ln, *pos, "%c", p[*pos]);
246bba1fa43Sschwarze 		(*pos)++;
247bba1fa43Sschwarze 		goto again;
248393cb51eSschwarze 	}
2492791bd1cSschwarze 	c = keys[i].key;
250393cb51eSschwarze 
251bba1fa43Sschwarze 	/* Special cases of spanners. */
252ec04407bSschwarze 
253bba1fa43Sschwarze 	if (c == TBL_CELL_SPAN) {
254bba1fa43Sschwarze 		if (rp->last == NULL)
255a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN, ln, *pos, NULL);
256bba1fa43Sschwarze 		else if (rp->last->pos == TBL_CELL_HORIZ ||
257bba1fa43Sschwarze 		    rp->last->pos == TBL_CELL_DHORIZ)
258bba1fa43Sschwarze 			c = rp->last->pos;
259bba1fa43Sschwarze 	} else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
260a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN, ln, *pos, NULL);
261ec04407bSschwarze 
262ec04407bSschwarze 	(*pos)++;
263ec04407bSschwarze 
264393cb51eSschwarze 	/* Allocate cell then parse its modifiers. */
265393cb51eSschwarze 
266fd9b947eSschwarze 	mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
267393cb51eSschwarze }
268393cb51eSschwarze 
269dca985f9Sschwarze void
27039aede77Sschwarze tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
271393cb51eSschwarze {
272393cb51eSschwarze 	struct tbl_row	*rp;
273393cb51eSschwarze 
27400038a64Sschwarze 	rp = NULL;
27500038a64Sschwarze 	for (;;) {
27600038a64Sschwarze 		/* Skip whitespace before and after each cell. */
27700038a64Sschwarze 
278bba1fa43Sschwarze 		while (p[pos] == ' ' || p[pos] == '\t')
27900038a64Sschwarze 			pos++;
28000038a64Sschwarze 
28100038a64Sschwarze 		switch (p[pos]) {
28200038a64Sschwarze 		case ',':  /* Next row on this input line. */
28300038a64Sschwarze 			pos++;
28400038a64Sschwarze 			rp = NULL;
28500038a64Sschwarze 			continue;
28600038a64Sschwarze 		case '\0':  /* Next row on next input line. */
287dca985f9Sschwarze 			return;
28800038a64Sschwarze 		case '.':  /* End of layout. */
28900038a64Sschwarze 			pos++;
29000038a64Sschwarze 			tbl->part = TBL_PART_DATA;
291fd9b947eSschwarze 
292fd9b947eSschwarze 			/*
293fd9b947eSschwarze 			 * When the layout is completely empty,
294fd9b947eSschwarze 			 * default to one left-justified column.
295fd9b947eSschwarze 			 */
296fd9b947eSschwarze 
297fd9b947eSschwarze 			if (tbl->first_row == NULL) {
298b6434279Sschwarze 				tbl->first_row = tbl->last_row =
299b6434279Sschwarze 				    mandoc_calloc(1, sizeof(*rp));
300b6434279Sschwarze 			}
301b6434279Sschwarze 			if (tbl->first_row->first == NULL) {
302bba1fa43Sschwarze 				mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
303a5a5f808Sschwarze 				    ln, pos, NULL);
304b6434279Sschwarze 				cell_alloc(tbl, tbl->first_row,
305b6434279Sschwarze 				    TBL_CELL_LEFT);
3066a8d9d41Sschwarze 				if (tbl->opts.lvert < tbl->first_row->vert)
3076a8d9d41Sschwarze 					tbl->opts.lvert = tbl->first_row->vert;
308dca985f9Sschwarze 				return;
309fd9b947eSschwarze 			}
310fd9b947eSschwarze 
311fd9b947eSschwarze 			/*
312fd9b947eSschwarze 			 * Search for the widest line
313fd9b947eSschwarze 			 * along the left and right margins.
314fd9b947eSschwarze 			 */
315fd9b947eSschwarze 
316fd9b947eSschwarze 			for (rp = tbl->first_row; rp; rp = rp->next) {
317fd9b947eSschwarze 				if (tbl->opts.lvert < rp->vert)
318fd9b947eSschwarze 					tbl->opts.lvert = rp->vert;
319fd9b947eSschwarze 				if (rp->last != NULL &&
3205f6d1ba3Sschwarze 				    rp->last->col + 1 == tbl->opts.cols &&
321fd9b947eSschwarze 				    tbl->opts.rvert < rp->last->vert)
322fd9b947eSschwarze 					tbl->opts.rvert = rp->last->vert;
323b6434279Sschwarze 
324b6434279Sschwarze 				/* If the last line is empty, drop it. */
325b6434279Sschwarze 
326b6434279Sschwarze 				if (rp->next != NULL &&
327b6434279Sschwarze 				    rp->next->first == NULL) {
328b6434279Sschwarze 					free(rp->next);
329b6434279Sschwarze 					rp->next = NULL;
33016f08be4Sschwarze 					tbl->last_row = rp;
331b6434279Sschwarze 				}
332fd9b947eSschwarze 			}
333fd9b947eSschwarze 			return;
33400038a64Sschwarze 		default:  /* Cell. */
33500038a64Sschwarze 			break;
33600038a64Sschwarze 		}
33700038a64Sschwarze 
338b6434279Sschwarze 		/*
339b6434279Sschwarze 		 * If the last line had at least one cell,
340b6434279Sschwarze 		 * start a new one; otherwise, continue it.
341b6434279Sschwarze 		 */
342b6434279Sschwarze 
343b6434279Sschwarze 		if (rp == NULL) {
344b6434279Sschwarze 			if (tbl->last_row == NULL ||
345b6434279Sschwarze 			    tbl->last_row->first != NULL) {
34600038a64Sschwarze 				rp = mandoc_calloc(1, sizeof(*rp));
3473006448aSschwarze 				if (tbl->last_row)
3482791bd1cSschwarze 					tbl->last_row->next = rp;
3493006448aSschwarze 				else
3503006448aSschwarze 					tbl->first_row = rp;
3512791bd1cSschwarze 				tbl->last_row = rp;
352b6434279Sschwarze 			} else
353b6434279Sschwarze 				rp = tbl->last_row;
354393cb51eSschwarze 		}
355bba1fa43Sschwarze 		cell(tbl, rp, ln, p, &pos);
356393cb51eSschwarze 	}
35700038a64Sschwarze }
3582791bd1cSschwarze 
3592791bd1cSschwarze static struct tbl_cell *
360fd9b947eSschwarze cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
3612791bd1cSschwarze {
3622791bd1cSschwarze 	struct tbl_cell	*p, *pp;
3632791bd1cSschwarze 
36421da0636Sschwarze 	p = mandoc_calloc(1, sizeof(*p));
365e8a65004Sschwarze 	p->spacing = SIZE_MAX;
3667d063611Sschwarze 	p->font = ESCAPE_FONTROMAN;
3675f6d1ba3Sschwarze 	p->pos = pos;
3682791bd1cSschwarze 
36921da0636Sschwarze 	if ((pp = rp->last) != NULL) {
3703006448aSschwarze 		pp->next = p;
3715f6d1ba3Sschwarze 		p->col = pp->col + 1;
3725f6d1ba3Sschwarze 	} else
3733006448aSschwarze 		rp->first = p;
3742791bd1cSschwarze 	rp->last = p;
3752791bd1cSschwarze 
3765f6d1ba3Sschwarze 	if (tbl->opts.cols <= p->col)
3775f6d1ba3Sschwarze 		tbl->opts.cols = p->col + 1;
3782791bd1cSschwarze 
379526e306bSschwarze 	return p;
3802791bd1cSschwarze }
381