xref: /titanic_50/usr/src/cmd/mandoc/tbl_layout.c (revision 260e9a87725c090ba5835b1f9f0b62fa2f96036f)
1*260e9a87SYuri Pankov /*	$Id: tbl_layout.c,v 1.38 2015/02/10 11:03:13 schwarze Exp $ */
295c635efSGarrett D'Amore /*
395c635efSGarrett D'Amore  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4*260e9a87SYuri Pankov  * Copyright (c) 2012, 2014, 2015 Ingo Schwarze <schwarze@openbsd.org>
595c635efSGarrett D'Amore  *
695c635efSGarrett D'Amore  * Permission to use, copy, modify, and distribute this software for any
795c635efSGarrett D'Amore  * purpose with or without fee is hereby granted, provided that the above
895c635efSGarrett D'Amore  * copyright notice and this permission notice appear in all copies.
995c635efSGarrett D'Amore  *
1095c635efSGarrett D'Amore  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1195c635efSGarrett D'Amore  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1295c635efSGarrett D'Amore  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1395c635efSGarrett D'Amore  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1495c635efSGarrett D'Amore  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1595c635efSGarrett D'Amore  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1695c635efSGarrett D'Amore  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1795c635efSGarrett D'Amore  */
1895c635efSGarrett D'Amore #include "config.h"
1995c635efSGarrett D'Amore 
20*260e9a87SYuri Pankov #include <sys/types.h>
21*260e9a87SYuri Pankov 
2295c635efSGarrett D'Amore #include <ctype.h>
2395c635efSGarrett D'Amore #include <stdlib.h>
2495c635efSGarrett D'Amore #include <string.h>
2595c635efSGarrett D'Amore #include <time.h>
2695c635efSGarrett D'Amore 
2795c635efSGarrett D'Amore #include "mandoc.h"
28*260e9a87SYuri Pankov #include "mandoc_aux.h"
2995c635efSGarrett D'Amore #include "libmandoc.h"
3095c635efSGarrett D'Amore #include "libroff.h"
3195c635efSGarrett D'Amore 
3295c635efSGarrett D'Amore struct	tbl_phrase {
3395c635efSGarrett D'Amore 	char		 name;
3495c635efSGarrett D'Amore 	enum tbl_cellt	 key;
3595c635efSGarrett D'Amore };
3695c635efSGarrett D'Amore 
37*260e9a87SYuri Pankov static	const struct tbl_phrase keys[] = {
3895c635efSGarrett D'Amore 	{ 'c',		 TBL_CELL_CENTRE },
3995c635efSGarrett D'Amore 	{ 'r',		 TBL_CELL_RIGHT },
4095c635efSGarrett D'Amore 	{ 'l',		 TBL_CELL_LEFT },
4195c635efSGarrett D'Amore 	{ 'n',		 TBL_CELL_NUMBER },
4295c635efSGarrett D'Amore 	{ 's',		 TBL_CELL_SPAN },
4395c635efSGarrett D'Amore 	{ 'a',		 TBL_CELL_LONG },
4495c635efSGarrett D'Amore 	{ '^',		 TBL_CELL_DOWN },
4595c635efSGarrett D'Amore 	{ '-',		 TBL_CELL_HORIZ },
4695c635efSGarrett D'Amore 	{ '_',		 TBL_CELL_HORIZ },
47698f87a4SGarrett D'Amore 	{ '=',		 TBL_CELL_DHORIZ }
4895c635efSGarrett D'Amore };
4995c635efSGarrett D'Amore 
50*260e9a87SYuri Pankov #define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0])))
5195c635efSGarrett D'Amore 
52*260e9a87SYuri Pankov static	void		 mods(struct tbl_node *, struct tbl_cell *,
53*260e9a87SYuri Pankov 				int, const char *, int *);
54*260e9a87SYuri Pankov static	void		 cell(struct tbl_node *, struct tbl_row *,
55*260e9a87SYuri Pankov 				int, const char *, int *);
56*260e9a87SYuri Pankov static	struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
57*260e9a87SYuri Pankov 				enum tbl_cellt);
58*260e9a87SYuri Pankov 
59*260e9a87SYuri Pankov 
60*260e9a87SYuri Pankov static void
mods(struct tbl_node * tbl,struct tbl_cell * cp,int ln,const char * p,int * pos)6195c635efSGarrett D'Amore mods(struct tbl_node *tbl, struct tbl_cell *cp,
6295c635efSGarrett D'Amore 		int ln, const char *p, int *pos)
6395c635efSGarrett D'Amore {
64*260e9a87SYuri Pankov 	char		*endptr;
6595c635efSGarrett D'Amore 
6695c635efSGarrett D'Amore mod:
67*260e9a87SYuri Pankov 	while (p[*pos] == ' ' || p[*pos] == '\t')
68*260e9a87SYuri Pankov 		(*pos)++;
69*260e9a87SYuri Pankov 
70*260e9a87SYuri Pankov 	/* Row delimiters and cell specifiers end modifier lists. */
71*260e9a87SYuri Pankov 
72*260e9a87SYuri Pankov 	if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL)
73*260e9a87SYuri Pankov 		return;
7495c635efSGarrett D'Amore 
7595c635efSGarrett D'Amore 	/* Throw away parenthesised expression. */
7695c635efSGarrett D'Amore 
7795c635efSGarrett D'Amore 	if ('(' == p[*pos]) {
7895c635efSGarrett D'Amore 		(*pos)++;
7995c635efSGarrett D'Amore 		while (p[*pos] && ')' != p[*pos])
8095c635efSGarrett D'Amore 			(*pos)++;
8195c635efSGarrett D'Amore 		if (')' == p[*pos]) {
8295c635efSGarrett D'Amore 			(*pos)++;
8395c635efSGarrett D'Amore 			goto mod;
8495c635efSGarrett D'Amore 		}
85*260e9a87SYuri Pankov 		mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, tbl->parse,
86*260e9a87SYuri Pankov 		    ln, *pos, NULL);
87*260e9a87SYuri Pankov 		return;
8895c635efSGarrett D'Amore 	}
8995c635efSGarrett D'Amore 
9095c635efSGarrett D'Amore 	/* Parse numerical spacing from modifier string. */
9195c635efSGarrett D'Amore 
9295c635efSGarrett D'Amore 	if (isdigit((unsigned char)p[*pos])) {
93*260e9a87SYuri Pankov 		cp->spacing = strtoull(p + *pos, &endptr, 10);
94*260e9a87SYuri Pankov 		*pos = endptr - p;
9595c635efSGarrett D'Amore 		goto mod;
9695c635efSGarrett D'Amore 	}
9795c635efSGarrett D'Amore 
9895c635efSGarrett D'Amore 	switch (tolower((unsigned char)p[(*pos)++])) {
99*260e9a87SYuri Pankov 	case 'b':
10095c635efSGarrett D'Amore 		cp->flags |= TBL_CELL_BOLD;
10195c635efSGarrett D'Amore 		goto mod;
102*260e9a87SYuri Pankov 	case 'd':
103*260e9a87SYuri Pankov 		cp->flags |= TBL_CELL_BALIGN;
104*260e9a87SYuri Pankov 		goto mod;
105*260e9a87SYuri Pankov 	case 'e':
106*260e9a87SYuri Pankov 		cp->flags |= TBL_CELL_EQUAL;
107*260e9a87SYuri Pankov 		goto mod;
108*260e9a87SYuri Pankov 	case 'f':
109*260e9a87SYuri Pankov 		break;
110*260e9a87SYuri Pankov 	case 'i':
11195c635efSGarrett D'Amore 		cp->flags |= TBL_CELL_ITALIC;
11295c635efSGarrett D'Amore 		goto mod;
113*260e9a87SYuri Pankov 	case 'm':
114*260e9a87SYuri Pankov 		mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, tbl->parse,
115*260e9a87SYuri Pankov 		    ln, *pos, "m");
116*260e9a87SYuri Pankov 		goto mod;
117*260e9a87SYuri Pankov 	case 'p':
11895c635efSGarrett D'Amore 		/* FALLTHROUGH */
119*260e9a87SYuri Pankov 	case 'v':
120*260e9a87SYuri Pankov 		if (p[*pos] == '-' || p[*pos] == '+')
121*260e9a87SYuri Pankov 			(*pos)++;
122*260e9a87SYuri Pankov 		while (isdigit((unsigned char)p[*pos]))
123*260e9a87SYuri Pankov 			(*pos)++;
124*260e9a87SYuri Pankov 		goto mod;
125*260e9a87SYuri Pankov 	case 't':
126*260e9a87SYuri Pankov 		cp->flags |= TBL_CELL_TALIGN;
127*260e9a87SYuri Pankov 		goto mod;
128*260e9a87SYuri Pankov 	case 'u':
129*260e9a87SYuri Pankov 		cp->flags |= TBL_CELL_UP;
130*260e9a87SYuri Pankov 		goto mod;
131*260e9a87SYuri Pankov 	case 'w':  /* XXX for now, ignore minimal column width */
132*260e9a87SYuri Pankov 		goto mod;
133*260e9a87SYuri Pankov 	case 'x':
134*260e9a87SYuri Pankov 		cp->flags |= TBL_CELL_WMAX;
135*260e9a87SYuri Pankov 		goto mod;
136*260e9a87SYuri Pankov 	case 'z':
137*260e9a87SYuri Pankov 		cp->flags |= TBL_CELL_WIGN;
138*260e9a87SYuri Pankov 		goto mod;
139*260e9a87SYuri Pankov 	case '|':
140*260e9a87SYuri Pankov 		if (cp->vert < 2)
141*260e9a87SYuri Pankov 			cp->vert++;
142*260e9a87SYuri Pankov 		else
143*260e9a87SYuri Pankov 			mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
144*260e9a87SYuri Pankov 			    tbl->parse, ln, *pos - 1, NULL);
14595c635efSGarrett D'Amore 		goto mod;
14695c635efSGarrett D'Amore 	default:
147*260e9a87SYuri Pankov 		mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse,
148*260e9a87SYuri Pankov 		    ln, *pos - 1, "%c", p[*pos - 1]);
149*260e9a87SYuri Pankov 		goto mod;
15095c635efSGarrett D'Amore 	}
15195c635efSGarrett D'Amore 
152*260e9a87SYuri Pankov 	/* Ignore parenthised font names for now. */
153*260e9a87SYuri Pankov 
154*260e9a87SYuri Pankov 	if (p[*pos] == '(')
155*260e9a87SYuri Pankov 		goto mod;
156*260e9a87SYuri Pankov 
157*260e9a87SYuri Pankov 	/* Support only one-character font-names for now. */
158*260e9a87SYuri Pankov 
159*260e9a87SYuri Pankov 	if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) {
160*260e9a87SYuri Pankov 		mandoc_vmsg(MANDOCERR_FT_BAD, tbl->parse,
161*260e9a87SYuri Pankov 		    ln, *pos, "TS %s", p + *pos - 1);
162*260e9a87SYuri Pankov 		if (p[*pos] != '\0')
163*260e9a87SYuri Pankov 			(*pos)++;
164*260e9a87SYuri Pankov 		if (p[*pos] != '\0')
165*260e9a87SYuri Pankov 			(*pos)++;
166*260e9a87SYuri Pankov 		goto mod;
16795c635efSGarrett D'Amore 	}
16895c635efSGarrett D'Amore 
169*260e9a87SYuri Pankov 	switch (p[(*pos)++]) {
170*260e9a87SYuri Pankov 	case '3':
171*260e9a87SYuri Pankov 		/* FALLTHROUGH */
172*260e9a87SYuri Pankov 	case 'B':
173*260e9a87SYuri Pankov 		cp->flags |= TBL_CELL_BOLD;
174*260e9a87SYuri Pankov 		goto mod;
175*260e9a87SYuri Pankov 	case '2':
176*260e9a87SYuri Pankov 		/* FALLTHROUGH */
177*260e9a87SYuri Pankov 	case 'I':
178*260e9a87SYuri Pankov 		cp->flags |= TBL_CELL_ITALIC;
179*260e9a87SYuri Pankov 		goto mod;
180*260e9a87SYuri Pankov 	case '1':
181*260e9a87SYuri Pankov 		/* FALLTHROUGH */
182*260e9a87SYuri Pankov 	case 'R':
183*260e9a87SYuri Pankov 		goto mod;
184*260e9a87SYuri Pankov 	default:
185*260e9a87SYuri Pankov 		mandoc_vmsg(MANDOCERR_FT_BAD, tbl->parse,
186*260e9a87SYuri Pankov 		    ln, *pos - 1, "TS f%c", p[*pos - 1]);
187*260e9a87SYuri Pankov 		goto mod;
188*260e9a87SYuri Pankov 	}
189*260e9a87SYuri Pankov }
190*260e9a87SYuri Pankov 
191*260e9a87SYuri Pankov static void
cell(struct tbl_node * tbl,struct tbl_row * rp,int ln,const char * p,int * pos)19295c635efSGarrett D'Amore cell(struct tbl_node *tbl, struct tbl_row *rp,
19395c635efSGarrett D'Amore 		int ln, const char *p, int *pos)
19495c635efSGarrett D'Amore {
195*260e9a87SYuri Pankov 	int		 i;
19695c635efSGarrett D'Amore 	enum tbl_cellt	 c;
19795c635efSGarrett D'Amore 
198*260e9a87SYuri Pankov 	/* Handle leading vertical lines */
199698f87a4SGarrett D'Amore 
200*260e9a87SYuri Pankov 	while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
201*260e9a87SYuri Pankov 		if (p[*pos] == '|') {
202*260e9a87SYuri Pankov 			if (rp->vert < 2)
203*260e9a87SYuri Pankov 				rp->vert++;
204*260e9a87SYuri Pankov 			else
205*260e9a87SYuri Pankov 				mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
206*260e9a87SYuri Pankov 				    tbl->parse, ln, *pos, NULL);
207*260e9a87SYuri Pankov 		}
208698f87a4SGarrett D'Amore 		(*pos)++;
209*260e9a87SYuri Pankov 	}
210*260e9a87SYuri Pankov 
211*260e9a87SYuri Pankov again:
212*260e9a87SYuri Pankov 	while (p[*pos] == ' ' || p[*pos] == '\t')
213*260e9a87SYuri Pankov 		(*pos)++;
214*260e9a87SYuri Pankov 
215*260e9a87SYuri Pankov 	if (p[*pos] == '.' || p[*pos] == '\0')
216*260e9a87SYuri Pankov 		return;
217698f87a4SGarrett D'Amore 
218698f87a4SGarrett D'Amore 	/* Parse the column position (`c', `l', `r', ...). */
21995c635efSGarrett D'Amore 
22095c635efSGarrett D'Amore 	for (i = 0; i < KEYS_MAX; i++)
22195c635efSGarrett D'Amore 		if (tolower((unsigned char)p[*pos]) == keys[i].name)
22295c635efSGarrett D'Amore 			break;
22395c635efSGarrett D'Amore 
224*260e9a87SYuri Pankov 	if (i == KEYS_MAX) {
225*260e9a87SYuri Pankov 		mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse,
226*260e9a87SYuri Pankov 		    ln, *pos, "%c", p[*pos]);
227*260e9a87SYuri Pankov 		(*pos)++;
228*260e9a87SYuri Pankov 		goto again;
22995c635efSGarrett D'Amore 	}
23095c635efSGarrett D'Amore 	c = keys[i].key;
23195c635efSGarrett D'Amore 
232*260e9a87SYuri Pankov 	/* Special cases of spanners. */
23395c635efSGarrett D'Amore 
234*260e9a87SYuri Pankov 	if (c == TBL_CELL_SPAN) {
235*260e9a87SYuri Pankov 		if (rp->last == NULL)
236*260e9a87SYuri Pankov 			mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN,
237*260e9a87SYuri Pankov 			    tbl->parse, ln, *pos, NULL);
238*260e9a87SYuri Pankov 		else if (rp->last->pos == TBL_CELL_HORIZ ||
239*260e9a87SYuri Pankov 		    rp->last->pos == TBL_CELL_DHORIZ)
240*260e9a87SYuri Pankov 			c = rp->last->pos;
241*260e9a87SYuri Pankov 	} else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
242*260e9a87SYuri Pankov 		mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN,
243*260e9a87SYuri Pankov 		    tbl->parse, ln, *pos, NULL);
24495c635efSGarrett D'Amore 
24595c635efSGarrett D'Amore 	(*pos)++;
24695c635efSGarrett D'Amore 
24795c635efSGarrett D'Amore 	/* Allocate cell then parse its modifiers. */
24895c635efSGarrett D'Amore 
249*260e9a87SYuri Pankov 	mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
25095c635efSGarrett D'Amore }
25195c635efSGarrett D'Amore 
252*260e9a87SYuri Pankov void
tbl_layout(struct tbl_node * tbl,int ln,const char * p,int pos)253*260e9a87SYuri Pankov tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
25495c635efSGarrett D'Amore {
25595c635efSGarrett D'Amore 	struct tbl_row	*rp;
25695c635efSGarrett D'Amore 
257*260e9a87SYuri Pankov 	rp = NULL;
258*260e9a87SYuri Pankov 	for (;;) {
259*260e9a87SYuri Pankov 		/* Skip whitespace before and after each cell. */
260*260e9a87SYuri Pankov 
261*260e9a87SYuri Pankov 		while (p[pos] == ' ' || p[pos] == '\t')
262*260e9a87SYuri Pankov 			pos++;
263*260e9a87SYuri Pankov 
264*260e9a87SYuri Pankov 		switch (p[pos]) {
265*260e9a87SYuri Pankov 		case ',':  /* Next row on this input line. */
266*260e9a87SYuri Pankov 			pos++;
267*260e9a87SYuri Pankov 			rp = NULL;
268*260e9a87SYuri Pankov 			continue;
269*260e9a87SYuri Pankov 		case '\0':  /* Next row on next input line. */
270*260e9a87SYuri Pankov 			return;
271*260e9a87SYuri Pankov 		case '.':  /* End of layout. */
272*260e9a87SYuri Pankov 			pos++;
273*260e9a87SYuri Pankov 			tbl->part = TBL_PART_DATA;
274*260e9a87SYuri Pankov 
275*260e9a87SYuri Pankov 			/*
276*260e9a87SYuri Pankov 			 * When the layout is completely empty,
277*260e9a87SYuri Pankov 			 * default to one left-justified column.
27895c635efSGarrett D'Amore 			 */
27995c635efSGarrett D'Amore 
280*260e9a87SYuri Pankov 			if (tbl->first_row == NULL) {
281*260e9a87SYuri Pankov 				tbl->first_row = tbl->last_row =
282*260e9a87SYuri Pankov 				    mandoc_calloc(1, sizeof(*rp));
283*260e9a87SYuri Pankov 			}
284*260e9a87SYuri Pankov 			if (tbl->first_row->first == NULL) {
285*260e9a87SYuri Pankov 				mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
286*260e9a87SYuri Pankov 				    tbl->parse, ln, pos, NULL);
287*260e9a87SYuri Pankov 				cell_alloc(tbl, tbl->first_row,
288*260e9a87SYuri Pankov 				    TBL_CELL_LEFT);
289*260e9a87SYuri Pankov 				return;
290*260e9a87SYuri Pankov 			}
291*260e9a87SYuri Pankov 
292*260e9a87SYuri Pankov 			/*
293*260e9a87SYuri Pankov 			 * Search for the widest line
294*260e9a87SYuri Pankov 			 * along the left and right margins.
295*260e9a87SYuri Pankov 			 */
296*260e9a87SYuri Pankov 
297*260e9a87SYuri Pankov 			for (rp = tbl->first_row; rp; rp = rp->next) {
298*260e9a87SYuri Pankov 				if (tbl->opts.lvert < rp->vert)
299*260e9a87SYuri Pankov 					tbl->opts.lvert = rp->vert;
300*260e9a87SYuri Pankov 				if (rp->last != NULL &&
301*260e9a87SYuri Pankov 				    rp->last->col + 1 == tbl->opts.cols &&
302*260e9a87SYuri Pankov 				    tbl->opts.rvert < rp->last->vert)
303*260e9a87SYuri Pankov 					tbl->opts.rvert = rp->last->vert;
304*260e9a87SYuri Pankov 
305*260e9a87SYuri Pankov 				/* If the last line is empty, drop it. */
306*260e9a87SYuri Pankov 
307*260e9a87SYuri Pankov 				if (rp->next != NULL &&
308*260e9a87SYuri Pankov 				    rp->next->first == NULL) {
309*260e9a87SYuri Pankov 					free(rp->next);
310*260e9a87SYuri Pankov 					rp->next = NULL;
311*260e9a87SYuri Pankov 				}
312*260e9a87SYuri Pankov 			}
313*260e9a87SYuri Pankov 			return;
314*260e9a87SYuri Pankov 		default:  /* Cell. */
315*260e9a87SYuri Pankov 			break;
316*260e9a87SYuri Pankov 		}
317*260e9a87SYuri Pankov 
318*260e9a87SYuri Pankov 		/*
319*260e9a87SYuri Pankov 		 * If the last line had at least one cell,
320*260e9a87SYuri Pankov 		 * start a new one; otherwise, continue it.
321*260e9a87SYuri Pankov 		 */
322*260e9a87SYuri Pankov 
323*260e9a87SYuri Pankov 		if (rp == NULL) {
324*260e9a87SYuri Pankov 			if (tbl->last_row == NULL ||
325*260e9a87SYuri Pankov 			    tbl->last_row->first != NULL) {
326*260e9a87SYuri Pankov 				rp = mandoc_calloc(1, sizeof(*rp));
327698f87a4SGarrett D'Amore 				if (tbl->last_row)
32895c635efSGarrett D'Amore 					tbl->last_row->next = rp;
329698f87a4SGarrett D'Amore 				else
330698f87a4SGarrett D'Amore 					tbl->first_row = rp;
33195c635efSGarrett D'Amore 				tbl->last_row = rp;
332*260e9a87SYuri Pankov 			} else
333*260e9a87SYuri Pankov 				rp = tbl->last_row;
33495c635efSGarrett D'Amore 		}
335*260e9a87SYuri Pankov 		cell(tbl, rp, ln, p, &pos);
33695c635efSGarrett D'Amore 	}
33795c635efSGarrett D'Amore }
33895c635efSGarrett D'Amore 
33995c635efSGarrett D'Amore static struct tbl_cell *
cell_alloc(struct tbl_node * tbl,struct tbl_row * rp,enum tbl_cellt pos)340*260e9a87SYuri Pankov cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
34195c635efSGarrett D'Amore {
34295c635efSGarrett D'Amore 	struct tbl_cell	*p, *pp;
34395c635efSGarrett D'Amore 
344*260e9a87SYuri Pankov 	p = mandoc_calloc(1, sizeof(*p));
345*260e9a87SYuri Pankov 	p->pos = pos;
34695c635efSGarrett D'Amore 
347*260e9a87SYuri Pankov 	if ((pp = rp->last) != NULL) {
348698f87a4SGarrett D'Amore 		pp->next = p;
349*260e9a87SYuri Pankov 		p->col = pp->col + 1;
350*260e9a87SYuri Pankov 	} else
351698f87a4SGarrett D'Amore 		rp->first = p;
35295c635efSGarrett D'Amore 	rp->last = p;
35395c635efSGarrett D'Amore 
354*260e9a87SYuri Pankov 	if (tbl->opts.cols <= p->col)
355*260e9a87SYuri Pankov 		tbl->opts.cols = p->col + 1;
35695c635efSGarrett D'Amore 
35795c635efSGarrett D'Amore 	return(p);
35895c635efSGarrett D'Amore }
359