xref: /freebsd/contrib/mandoc/tbl_layout.c (revision 7295610f5da64ab1818458ce007d9eb924496330)
1*7295610fSBaptiste Daroussin /*	$Id: tbl_layout.c,v 1.48 2018/12/14 05:18:03 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
361d06d6bSBaptiste Daroussin  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
461d06d6bSBaptiste Daroussin  * Copyright (c) 2012, 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org>
561d06d6bSBaptiste Daroussin  *
661d06d6bSBaptiste Daroussin  * Permission to use, copy, modify, and distribute this software for any
761d06d6bSBaptiste Daroussin  * purpose with or without fee is hereby granted, provided that the above
861d06d6bSBaptiste Daroussin  * copyright notice and this permission notice appear in all copies.
961d06d6bSBaptiste Daroussin  *
1061d06d6bSBaptiste Daroussin  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1161d06d6bSBaptiste Daroussin  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1261d06d6bSBaptiste Daroussin  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1361d06d6bSBaptiste Daroussin  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1461d06d6bSBaptiste Daroussin  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1561d06d6bSBaptiste Daroussin  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1661d06d6bSBaptiste Daroussin  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1761d06d6bSBaptiste Daroussin  */
1861d06d6bSBaptiste Daroussin #include "config.h"
1961d06d6bSBaptiste Daroussin 
2061d06d6bSBaptiste Daroussin #include <sys/types.h>
2161d06d6bSBaptiste Daroussin 
2261d06d6bSBaptiste Daroussin #include <ctype.h>
2361d06d6bSBaptiste Daroussin #include <stdint.h>
24*7295610fSBaptiste Daroussin #include <stdio.h>
2561d06d6bSBaptiste Daroussin #include <stdlib.h>
2661d06d6bSBaptiste Daroussin #include <string.h>
2761d06d6bSBaptiste Daroussin #include <time.h>
2861d06d6bSBaptiste Daroussin 
2961d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
30*7295610fSBaptiste Daroussin #include "mandoc.h"
31*7295610fSBaptiste Daroussin #include "tbl.h"
3261d06d6bSBaptiste Daroussin #include "libmandoc.h"
33*7295610fSBaptiste Daroussin #include "tbl_int.h"
3461d06d6bSBaptiste Daroussin 
3561d06d6bSBaptiste Daroussin struct	tbl_phrase {
3661d06d6bSBaptiste Daroussin 	char		 name;
3761d06d6bSBaptiste Daroussin 	enum tbl_cellt	 key;
3861d06d6bSBaptiste Daroussin };
3961d06d6bSBaptiste Daroussin 
4061d06d6bSBaptiste Daroussin static	const struct tbl_phrase keys[] = {
4161d06d6bSBaptiste Daroussin 	{ 'c',		 TBL_CELL_CENTRE },
4261d06d6bSBaptiste Daroussin 	{ 'r',		 TBL_CELL_RIGHT },
4361d06d6bSBaptiste Daroussin 	{ 'l',		 TBL_CELL_LEFT },
4461d06d6bSBaptiste Daroussin 	{ 'n',		 TBL_CELL_NUMBER },
4561d06d6bSBaptiste Daroussin 	{ 's',		 TBL_CELL_SPAN },
4661d06d6bSBaptiste Daroussin 	{ 'a',		 TBL_CELL_LONG },
4761d06d6bSBaptiste Daroussin 	{ '^',		 TBL_CELL_DOWN },
4861d06d6bSBaptiste Daroussin 	{ '-',		 TBL_CELL_HORIZ },
4961d06d6bSBaptiste Daroussin 	{ '_',		 TBL_CELL_HORIZ },
5061d06d6bSBaptiste Daroussin 	{ '=',		 TBL_CELL_DHORIZ }
5161d06d6bSBaptiste Daroussin };
5261d06d6bSBaptiste Daroussin 
5361d06d6bSBaptiste Daroussin #define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0])))
5461d06d6bSBaptiste Daroussin 
5561d06d6bSBaptiste Daroussin static	void		 mods(struct tbl_node *, struct tbl_cell *,
5661d06d6bSBaptiste Daroussin 				int, const char *, int *);
5761d06d6bSBaptiste Daroussin static	void		 cell(struct tbl_node *, struct tbl_row *,
5861d06d6bSBaptiste Daroussin 				int, const char *, int *);
5961d06d6bSBaptiste Daroussin static	struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
6061d06d6bSBaptiste Daroussin 				enum tbl_cellt);
6161d06d6bSBaptiste Daroussin 
6261d06d6bSBaptiste Daroussin 
6361d06d6bSBaptiste Daroussin static void
6461d06d6bSBaptiste Daroussin mods(struct tbl_node *tbl, struct tbl_cell *cp,
6561d06d6bSBaptiste Daroussin 		int ln, const char *p, int *pos)
6661d06d6bSBaptiste Daroussin {
6761d06d6bSBaptiste Daroussin 	char		*endptr;
6861d06d6bSBaptiste Daroussin 	size_t		 sz;
6961d06d6bSBaptiste Daroussin 
7061d06d6bSBaptiste Daroussin mod:
7161d06d6bSBaptiste Daroussin 	while (p[*pos] == ' ' || p[*pos] == '\t')
7261d06d6bSBaptiste Daroussin 		(*pos)++;
7361d06d6bSBaptiste Daroussin 
7461d06d6bSBaptiste Daroussin 	/* Row delimiters and cell specifiers end modifier lists. */
7561d06d6bSBaptiste Daroussin 
7661d06d6bSBaptiste Daroussin 	if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL)
7761d06d6bSBaptiste Daroussin 		return;
7861d06d6bSBaptiste Daroussin 
7961d06d6bSBaptiste Daroussin 	/* Throw away parenthesised expression. */
8061d06d6bSBaptiste Daroussin 
8161d06d6bSBaptiste Daroussin 	if ('(' == p[*pos]) {
8261d06d6bSBaptiste Daroussin 		(*pos)++;
8361d06d6bSBaptiste Daroussin 		while (p[*pos] && ')' != p[*pos])
8461d06d6bSBaptiste Daroussin 			(*pos)++;
8561d06d6bSBaptiste Daroussin 		if (')' == p[*pos]) {
8661d06d6bSBaptiste Daroussin 			(*pos)++;
8761d06d6bSBaptiste Daroussin 			goto mod;
8861d06d6bSBaptiste Daroussin 		}
89*7295610fSBaptiste Daroussin 		mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, ln, *pos, NULL);
9061d06d6bSBaptiste Daroussin 		return;
9161d06d6bSBaptiste Daroussin 	}
9261d06d6bSBaptiste Daroussin 
9361d06d6bSBaptiste Daroussin 	/* Parse numerical spacing from modifier string. */
9461d06d6bSBaptiste Daroussin 
9561d06d6bSBaptiste Daroussin 	if (isdigit((unsigned char)p[*pos])) {
9661d06d6bSBaptiste Daroussin 		cp->spacing = strtoull(p + *pos, &endptr, 10);
9761d06d6bSBaptiste Daroussin 		*pos = endptr - p;
9861d06d6bSBaptiste Daroussin 		goto mod;
9961d06d6bSBaptiste Daroussin 	}
10061d06d6bSBaptiste Daroussin 
10161d06d6bSBaptiste Daroussin 	switch (tolower((unsigned char)p[(*pos)++])) {
10261d06d6bSBaptiste Daroussin 	case 'b':
10361d06d6bSBaptiste Daroussin 		cp->flags |= TBL_CELL_BOLD;
10461d06d6bSBaptiste Daroussin 		goto mod;
10561d06d6bSBaptiste Daroussin 	case 'd':
10661d06d6bSBaptiste Daroussin 		cp->flags |= TBL_CELL_BALIGN;
10761d06d6bSBaptiste Daroussin 		goto mod;
10861d06d6bSBaptiste Daroussin 	case 'e':
10961d06d6bSBaptiste Daroussin 		cp->flags |= TBL_CELL_EQUAL;
11061d06d6bSBaptiste Daroussin 		goto mod;
11161d06d6bSBaptiste Daroussin 	case 'f':
11261d06d6bSBaptiste Daroussin 		break;
11361d06d6bSBaptiste Daroussin 	case 'i':
11461d06d6bSBaptiste Daroussin 		cp->flags |= TBL_CELL_ITALIC;
11561d06d6bSBaptiste Daroussin 		goto mod;
11661d06d6bSBaptiste Daroussin 	case 'm':
117*7295610fSBaptiste Daroussin 		mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, ln, *pos, "m");
11861d06d6bSBaptiste Daroussin 		goto mod;
11961d06d6bSBaptiste Daroussin 	case 'p':
12061d06d6bSBaptiste Daroussin 	case 'v':
12161d06d6bSBaptiste Daroussin 		if (p[*pos] == '-' || p[*pos] == '+')
12261d06d6bSBaptiste Daroussin 			(*pos)++;
12361d06d6bSBaptiste Daroussin 		while (isdigit((unsigned char)p[*pos]))
12461d06d6bSBaptiste Daroussin 			(*pos)++;
12561d06d6bSBaptiste Daroussin 		goto mod;
12661d06d6bSBaptiste Daroussin 	case 't':
12761d06d6bSBaptiste Daroussin 		cp->flags |= TBL_CELL_TALIGN;
12861d06d6bSBaptiste Daroussin 		goto mod;
12961d06d6bSBaptiste Daroussin 	case 'u':
13061d06d6bSBaptiste Daroussin 		cp->flags |= TBL_CELL_UP;
13161d06d6bSBaptiste Daroussin 		goto mod;
13261d06d6bSBaptiste Daroussin 	case 'w':
13361d06d6bSBaptiste Daroussin 		sz = 0;
13461d06d6bSBaptiste Daroussin 		if (p[*pos] == '(') {
13561d06d6bSBaptiste Daroussin 			(*pos)++;
13661d06d6bSBaptiste Daroussin 			while (p[*pos + sz] != '\0' && p[*pos + sz] != ')')
13761d06d6bSBaptiste Daroussin 				sz++;
13861d06d6bSBaptiste Daroussin 		} else
13961d06d6bSBaptiste Daroussin 			while (isdigit((unsigned char)p[*pos + sz]))
14061d06d6bSBaptiste Daroussin 				sz++;
14161d06d6bSBaptiste Daroussin 		if (sz) {
14261d06d6bSBaptiste Daroussin 			free(cp->wstr);
14361d06d6bSBaptiste Daroussin 			cp->wstr = mandoc_strndup(p + *pos, sz);
14461d06d6bSBaptiste Daroussin 			*pos += sz;
14561d06d6bSBaptiste Daroussin 			if (p[*pos] == ')')
14661d06d6bSBaptiste Daroussin 				(*pos)++;
14761d06d6bSBaptiste Daroussin 		}
14861d06d6bSBaptiste Daroussin 		goto mod;
14961d06d6bSBaptiste Daroussin 	case 'x':
15061d06d6bSBaptiste Daroussin 		cp->flags |= TBL_CELL_WMAX;
15161d06d6bSBaptiste Daroussin 		goto mod;
15261d06d6bSBaptiste Daroussin 	case 'z':
15361d06d6bSBaptiste Daroussin 		cp->flags |= TBL_CELL_WIGN;
15461d06d6bSBaptiste Daroussin 		goto mod;
15561d06d6bSBaptiste Daroussin 	case '|':
15661d06d6bSBaptiste Daroussin 		if (cp->vert < 2)
15761d06d6bSBaptiste Daroussin 			cp->vert++;
15861d06d6bSBaptiste Daroussin 		else
15961d06d6bSBaptiste Daroussin 			mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
160*7295610fSBaptiste Daroussin 			    ln, *pos - 1, NULL);
16161d06d6bSBaptiste Daroussin 		goto mod;
16261d06d6bSBaptiste Daroussin 	default:
163*7295610fSBaptiste Daroussin 		mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
16461d06d6bSBaptiste Daroussin 		    ln, *pos - 1, "%c", p[*pos - 1]);
16561d06d6bSBaptiste Daroussin 		goto mod;
16661d06d6bSBaptiste Daroussin 	}
16761d06d6bSBaptiste Daroussin 
16861d06d6bSBaptiste Daroussin 	/* Ignore parenthised font names for now. */
16961d06d6bSBaptiste Daroussin 
17061d06d6bSBaptiste Daroussin 	if (p[*pos] == '(')
17161d06d6bSBaptiste Daroussin 		goto mod;
17261d06d6bSBaptiste Daroussin 
17361d06d6bSBaptiste Daroussin 	/* Support only one-character font-names for now. */
17461d06d6bSBaptiste Daroussin 
17561d06d6bSBaptiste Daroussin 	if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) {
176*7295610fSBaptiste Daroussin 		mandoc_msg(MANDOCERR_FT_BAD,
17761d06d6bSBaptiste Daroussin 		    ln, *pos, "TS %s", p + *pos - 1);
17861d06d6bSBaptiste Daroussin 		if (p[*pos] != '\0')
17961d06d6bSBaptiste Daroussin 			(*pos)++;
18061d06d6bSBaptiste Daroussin 		if (p[*pos] != '\0')
18161d06d6bSBaptiste Daroussin 			(*pos)++;
18261d06d6bSBaptiste Daroussin 		goto mod;
18361d06d6bSBaptiste Daroussin 	}
18461d06d6bSBaptiste Daroussin 
18561d06d6bSBaptiste Daroussin 	switch (p[(*pos)++]) {
18661d06d6bSBaptiste Daroussin 	case '3':
18761d06d6bSBaptiste Daroussin 	case 'B':
18861d06d6bSBaptiste Daroussin 		cp->flags |= TBL_CELL_BOLD;
18961d06d6bSBaptiste Daroussin 		goto mod;
19061d06d6bSBaptiste Daroussin 	case '2':
19161d06d6bSBaptiste Daroussin 	case 'I':
19261d06d6bSBaptiste Daroussin 		cp->flags |= TBL_CELL_ITALIC;
19361d06d6bSBaptiste Daroussin 		goto mod;
19461d06d6bSBaptiste Daroussin 	case '1':
19561d06d6bSBaptiste Daroussin 	case 'R':
19661d06d6bSBaptiste Daroussin 		goto mod;
19761d06d6bSBaptiste Daroussin 	default:
198*7295610fSBaptiste Daroussin 		mandoc_msg(MANDOCERR_FT_BAD,
19961d06d6bSBaptiste Daroussin 		    ln, *pos - 1, "TS f%c", p[*pos - 1]);
20061d06d6bSBaptiste Daroussin 		goto mod;
20161d06d6bSBaptiste Daroussin 	}
20261d06d6bSBaptiste Daroussin }
20361d06d6bSBaptiste Daroussin 
20461d06d6bSBaptiste Daroussin static void
20561d06d6bSBaptiste Daroussin cell(struct tbl_node *tbl, struct tbl_row *rp,
20661d06d6bSBaptiste Daroussin 		int ln, const char *p, int *pos)
20761d06d6bSBaptiste Daroussin {
20861d06d6bSBaptiste Daroussin 	int		 i;
20961d06d6bSBaptiste Daroussin 	enum tbl_cellt	 c;
21061d06d6bSBaptiste Daroussin 
21161d06d6bSBaptiste Daroussin 	/* Handle leading vertical lines */
21261d06d6bSBaptiste Daroussin 
21361d06d6bSBaptiste Daroussin 	while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
21461d06d6bSBaptiste Daroussin 		if (p[*pos] == '|') {
21561d06d6bSBaptiste Daroussin 			if (rp->vert < 2)
21661d06d6bSBaptiste Daroussin 				rp->vert++;
21761d06d6bSBaptiste Daroussin 			else
21861d06d6bSBaptiste Daroussin 				mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
219*7295610fSBaptiste Daroussin 				    ln, *pos, NULL);
22061d06d6bSBaptiste Daroussin 		}
22161d06d6bSBaptiste Daroussin 		(*pos)++;
22261d06d6bSBaptiste Daroussin 	}
22361d06d6bSBaptiste Daroussin 
22461d06d6bSBaptiste Daroussin again:
22561d06d6bSBaptiste Daroussin 	while (p[*pos] == ' ' || p[*pos] == '\t')
22661d06d6bSBaptiste Daroussin 		(*pos)++;
22761d06d6bSBaptiste Daroussin 
22861d06d6bSBaptiste Daroussin 	if (p[*pos] == '.' || p[*pos] == '\0')
22961d06d6bSBaptiste Daroussin 		return;
23061d06d6bSBaptiste Daroussin 
23161d06d6bSBaptiste Daroussin 	/* Parse the column position (`c', `l', `r', ...). */
23261d06d6bSBaptiste Daroussin 
23361d06d6bSBaptiste Daroussin 	for (i = 0; i < KEYS_MAX; i++)
23461d06d6bSBaptiste Daroussin 		if (tolower((unsigned char)p[*pos]) == keys[i].name)
23561d06d6bSBaptiste Daroussin 			break;
23661d06d6bSBaptiste Daroussin 
23761d06d6bSBaptiste Daroussin 	if (i == KEYS_MAX) {
238*7295610fSBaptiste Daroussin 		mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
23961d06d6bSBaptiste Daroussin 		    ln, *pos, "%c", p[*pos]);
24061d06d6bSBaptiste Daroussin 		(*pos)++;
24161d06d6bSBaptiste Daroussin 		goto again;
24261d06d6bSBaptiste Daroussin 	}
24361d06d6bSBaptiste Daroussin 	c = keys[i].key;
24461d06d6bSBaptiste Daroussin 
24561d06d6bSBaptiste Daroussin 	/* Special cases of spanners. */
24661d06d6bSBaptiste Daroussin 
24761d06d6bSBaptiste Daroussin 	if (c == TBL_CELL_SPAN) {
24861d06d6bSBaptiste Daroussin 		if (rp->last == NULL)
249*7295610fSBaptiste Daroussin 			mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN, ln, *pos, NULL);
25061d06d6bSBaptiste Daroussin 		else if (rp->last->pos == TBL_CELL_HORIZ ||
25161d06d6bSBaptiste Daroussin 		    rp->last->pos == TBL_CELL_DHORIZ)
25261d06d6bSBaptiste Daroussin 			c = rp->last->pos;
25361d06d6bSBaptiste Daroussin 	} else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
254*7295610fSBaptiste Daroussin 		mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN, ln, *pos, NULL);
25561d06d6bSBaptiste Daroussin 
25661d06d6bSBaptiste Daroussin 	(*pos)++;
25761d06d6bSBaptiste Daroussin 
25861d06d6bSBaptiste Daroussin 	/* Allocate cell then parse its modifiers. */
25961d06d6bSBaptiste Daroussin 
26061d06d6bSBaptiste Daroussin 	mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
26161d06d6bSBaptiste Daroussin }
26261d06d6bSBaptiste Daroussin 
26361d06d6bSBaptiste Daroussin void
26461d06d6bSBaptiste Daroussin tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
26561d06d6bSBaptiste Daroussin {
26661d06d6bSBaptiste Daroussin 	struct tbl_row	*rp;
26761d06d6bSBaptiste Daroussin 
26861d06d6bSBaptiste Daroussin 	rp = NULL;
26961d06d6bSBaptiste Daroussin 	for (;;) {
27061d06d6bSBaptiste Daroussin 		/* Skip whitespace before and after each cell. */
27161d06d6bSBaptiste Daroussin 
27261d06d6bSBaptiste Daroussin 		while (p[pos] == ' ' || p[pos] == '\t')
27361d06d6bSBaptiste Daroussin 			pos++;
27461d06d6bSBaptiste Daroussin 
27561d06d6bSBaptiste Daroussin 		switch (p[pos]) {
27661d06d6bSBaptiste Daroussin 		case ',':  /* Next row on this input line. */
27761d06d6bSBaptiste Daroussin 			pos++;
27861d06d6bSBaptiste Daroussin 			rp = NULL;
27961d06d6bSBaptiste Daroussin 			continue;
28061d06d6bSBaptiste Daroussin 		case '\0':  /* Next row on next input line. */
28161d06d6bSBaptiste Daroussin 			return;
28261d06d6bSBaptiste Daroussin 		case '.':  /* End of layout. */
28361d06d6bSBaptiste Daroussin 			pos++;
28461d06d6bSBaptiste Daroussin 			tbl->part = TBL_PART_DATA;
28561d06d6bSBaptiste Daroussin 
28661d06d6bSBaptiste Daroussin 			/*
28761d06d6bSBaptiste Daroussin 			 * When the layout is completely empty,
28861d06d6bSBaptiste Daroussin 			 * default to one left-justified column.
28961d06d6bSBaptiste Daroussin 			 */
29061d06d6bSBaptiste Daroussin 
29161d06d6bSBaptiste Daroussin 			if (tbl->first_row == NULL) {
29261d06d6bSBaptiste Daroussin 				tbl->first_row = tbl->last_row =
29361d06d6bSBaptiste Daroussin 				    mandoc_calloc(1, sizeof(*rp));
29461d06d6bSBaptiste Daroussin 			}
29561d06d6bSBaptiste Daroussin 			if (tbl->first_row->first == NULL) {
29661d06d6bSBaptiste Daroussin 				mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
297*7295610fSBaptiste Daroussin 				    ln, pos, NULL);
29861d06d6bSBaptiste Daroussin 				cell_alloc(tbl, tbl->first_row,
29961d06d6bSBaptiste Daroussin 				    TBL_CELL_LEFT);
30061d06d6bSBaptiste Daroussin 				if (tbl->opts.lvert < tbl->first_row->vert)
30161d06d6bSBaptiste Daroussin 					tbl->opts.lvert = tbl->first_row->vert;
30261d06d6bSBaptiste Daroussin 				return;
30361d06d6bSBaptiste Daroussin 			}
30461d06d6bSBaptiste Daroussin 
30561d06d6bSBaptiste Daroussin 			/*
30661d06d6bSBaptiste Daroussin 			 * Search for the widest line
30761d06d6bSBaptiste Daroussin 			 * along the left and right margins.
30861d06d6bSBaptiste Daroussin 			 */
30961d06d6bSBaptiste Daroussin 
31061d06d6bSBaptiste Daroussin 			for (rp = tbl->first_row; rp; rp = rp->next) {
31161d06d6bSBaptiste Daroussin 				if (tbl->opts.lvert < rp->vert)
31261d06d6bSBaptiste Daroussin 					tbl->opts.lvert = rp->vert;
31361d06d6bSBaptiste Daroussin 				if (rp->last != NULL &&
31461d06d6bSBaptiste Daroussin 				    rp->last->col + 1 == tbl->opts.cols &&
31561d06d6bSBaptiste Daroussin 				    tbl->opts.rvert < rp->last->vert)
31661d06d6bSBaptiste Daroussin 					tbl->opts.rvert = rp->last->vert;
31761d06d6bSBaptiste Daroussin 
31861d06d6bSBaptiste Daroussin 				/* If the last line is empty, drop it. */
31961d06d6bSBaptiste Daroussin 
32061d06d6bSBaptiste Daroussin 				if (rp->next != NULL &&
32161d06d6bSBaptiste Daroussin 				    rp->next->first == NULL) {
32261d06d6bSBaptiste Daroussin 					free(rp->next);
32361d06d6bSBaptiste Daroussin 					rp->next = NULL;
32461d06d6bSBaptiste Daroussin 					tbl->last_row = rp;
32561d06d6bSBaptiste Daroussin 				}
32661d06d6bSBaptiste Daroussin 			}
32761d06d6bSBaptiste Daroussin 			return;
32861d06d6bSBaptiste Daroussin 		default:  /* Cell. */
32961d06d6bSBaptiste Daroussin 			break;
33061d06d6bSBaptiste Daroussin 		}
33161d06d6bSBaptiste Daroussin 
33261d06d6bSBaptiste Daroussin 		/*
33361d06d6bSBaptiste Daroussin 		 * If the last line had at least one cell,
33461d06d6bSBaptiste Daroussin 		 * start a new one; otherwise, continue it.
33561d06d6bSBaptiste Daroussin 		 */
33661d06d6bSBaptiste Daroussin 
33761d06d6bSBaptiste Daroussin 		if (rp == NULL) {
33861d06d6bSBaptiste Daroussin 			if (tbl->last_row == NULL ||
33961d06d6bSBaptiste Daroussin 			    tbl->last_row->first != NULL) {
34061d06d6bSBaptiste Daroussin 				rp = mandoc_calloc(1, sizeof(*rp));
34161d06d6bSBaptiste Daroussin 				if (tbl->last_row)
34261d06d6bSBaptiste Daroussin 					tbl->last_row->next = rp;
34361d06d6bSBaptiste Daroussin 				else
34461d06d6bSBaptiste Daroussin 					tbl->first_row = rp;
34561d06d6bSBaptiste Daroussin 				tbl->last_row = rp;
34661d06d6bSBaptiste Daroussin 			} else
34761d06d6bSBaptiste Daroussin 				rp = tbl->last_row;
34861d06d6bSBaptiste Daroussin 		}
34961d06d6bSBaptiste Daroussin 		cell(tbl, rp, ln, p, &pos);
35061d06d6bSBaptiste Daroussin 	}
35161d06d6bSBaptiste Daroussin }
35261d06d6bSBaptiste Daroussin 
35361d06d6bSBaptiste Daroussin static struct tbl_cell *
35461d06d6bSBaptiste Daroussin cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
35561d06d6bSBaptiste Daroussin {
35661d06d6bSBaptiste Daroussin 	struct tbl_cell	*p, *pp;
35761d06d6bSBaptiste Daroussin 
35861d06d6bSBaptiste Daroussin 	p = mandoc_calloc(1, sizeof(*p));
35961d06d6bSBaptiste Daroussin 	p->spacing = SIZE_MAX;
36061d06d6bSBaptiste Daroussin 	p->pos = pos;
36161d06d6bSBaptiste Daroussin 
36261d06d6bSBaptiste Daroussin 	if ((pp = rp->last) != NULL) {
36361d06d6bSBaptiste Daroussin 		pp->next = p;
36461d06d6bSBaptiste Daroussin 		p->col = pp->col + 1;
36561d06d6bSBaptiste Daroussin 	} else
36661d06d6bSBaptiste Daroussin 		rp->first = p;
36761d06d6bSBaptiste Daroussin 	rp->last = p;
36861d06d6bSBaptiste Daroussin 
36961d06d6bSBaptiste Daroussin 	if (tbl->opts.cols <= p->col)
37061d06d6bSBaptiste Daroussin 		tbl->opts.cols = p->col + 1;
37161d06d6bSBaptiste Daroussin 
37261d06d6bSBaptiste Daroussin 	return p;
37361d06d6bSBaptiste Daroussin }
374