xref: /openbsd-src/usr.bin/mandoc/tbl_term.c (revision 61b0e532b2dce0a91cf3ea67d346645a61a88cdd)
1*61b0e532Sschwarze /* $OpenBSD: tbl_term.c,v 1.66 2022/08/28 10:57:52 schwarze Exp $ */
2393cb51eSschwarze /*
32a491d59Sschwarze  * Copyright (c) 2011-2022 Ingo Schwarze <schwarze@openbsd.org>
404e980cbSschwarze  * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
5393cb51eSschwarze  *
6393cb51eSschwarze  * Permission to use, copy, modify, and distribute this software for any
7393cb51eSschwarze  * purpose with or without fee is hereby granted, provided that the above
8393cb51eSschwarze  * copyright notice and this permission notice appear in all copies.
9393cb51eSschwarze  *
10393cb51eSschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11393cb51eSschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12393cb51eSschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13393cb51eSschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14393cb51eSschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15393cb51eSschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16393cb51eSschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17393cb51eSschwarze  */
18fd1e7ec9Sschwarze #include <sys/types.h>
19fd1e7ec9Sschwarze 
20393cb51eSschwarze #include <assert.h>
21a7d5d23dSschwarze #include <ctype.h>
22393cb51eSschwarze #include <stdio.h>
23393cb51eSschwarze #include <stdlib.h>
24393cb51eSschwarze #include <string.h>
25393cb51eSschwarze 
262791bd1cSschwarze #include "mandoc.h"
27fae2491eSschwarze #include "tbl.h"
28c37b5927Sschwarze #include "out.h"
29c37b5927Sschwarze #include "term.h"
30393cb51eSschwarze 
31eaa4dcb4Sschwarze #define	IS_HORIZ(cp)	((cp)->pos == TBL_CELL_HORIZ || \
32eaa4dcb4Sschwarze 			 (cp)->pos == TBL_CELL_DHORIZ)
33eaa4dcb4Sschwarze 
34868ce2e9Sschwarze 
35ec04407bSschwarze static	size_t	term_tbl_len(size_t, void *);
36ec04407bSschwarze static	size_t	term_tbl_strlen(const char *, void *);
372c3e66c4Sschwarze static	size_t	term_tbl_sulen(const struct roffsu *, void *);
38c7497e73Sschwarze static	void	tbl_data(struct termp *, const struct tbl_opts *,
39eaa4dcb4Sschwarze 			const struct tbl_cell *,
402791bd1cSschwarze 			const struct tbl_dat *,
41ec04407bSschwarze 			const struct roffcol *);
42868ce2e9Sschwarze static	void	tbl_direct_border(struct termp *, int, size_t);
43868ce2e9Sschwarze static	void	tbl_fill_border(struct termp *, int, size_t);
44868ce2e9Sschwarze static	void	tbl_fill_char(struct termp *, char, size_t);
45868ce2e9Sschwarze static	void	tbl_fill_string(struct termp *, const char *, size_t);
460673fa65Sschwarze static	void	tbl_hrule(struct termp *, const struct tbl_span *,
47b3e6a325Sschwarze 			const struct tbl_span *, const struct tbl_span *,
48b3e6a325Sschwarze 			int);
49ec04407bSschwarze static	void	tbl_literal(struct termp *, const struct tbl_dat *,
50ec04407bSschwarze 			const struct roffcol *);
51c7497e73Sschwarze static	void	tbl_number(struct termp *, const struct tbl_opts *,
522791bd1cSschwarze 			const struct tbl_dat *,
53ec04407bSschwarze 			const struct roffcol *);
54fd1e7ec9Sschwarze static	void	tbl_word(struct termp *, const struct tbl_dat *);
55ec04407bSschwarze 
56ec04407bSschwarze 
57868ce2e9Sschwarze /*
58868ce2e9Sschwarze  * The following border-character tables are indexed
59868ce2e9Sschwarze  * by ternary (3-based) numbers, as opposed to binary or decimal.
60868ce2e9Sschwarze  * Each ternary digit describes the line width in one direction:
61868ce2e9Sschwarze  * 0 means no line, 1 single or light line, 2 double or heavy line.
62868ce2e9Sschwarze  */
63868ce2e9Sschwarze 
64868ce2e9Sschwarze /* Positional values of the four directions. */
65868ce2e9Sschwarze #define	BRIGHT	1
66868ce2e9Sschwarze #define	BDOWN	3
67868ce2e9Sschwarze #define	BLEFT	(3 * 3)
68868ce2e9Sschwarze #define	BUP	(3 * 3 * 3)
69868ce2e9Sschwarze #define	BHORIZ	(BLEFT + BRIGHT)
70868ce2e9Sschwarze 
71868ce2e9Sschwarze /* Code points to use for each combination of widths. */
72868ce2e9Sschwarze static  const int borders_utf8[81] = {
73868ce2e9Sschwarze 	0x0020, 0x2576, 0x257a,  /* 000 right */
74868ce2e9Sschwarze 	0x2577, 0x250c, 0x250d,  /* 001 down */
75868ce2e9Sschwarze 	0x257b, 0x250e, 0x250f,  /* 002 */
76868ce2e9Sschwarze 	0x2574, 0x2500, 0x257c,  /* 010 left */
77868ce2e9Sschwarze 	0x2510, 0x252c, 0x252e,  /* 011 left down */
78868ce2e9Sschwarze 	0x2512, 0x2530, 0x2532,  /* 012 */
79868ce2e9Sschwarze 	0x2578, 0x257e, 0x2501,  /* 020 left */
80868ce2e9Sschwarze 	0x2511, 0x252d, 0x252f,  /* 021 left down */
81868ce2e9Sschwarze 	0x2513, 0x2531, 0x2533,  /* 022 */
82868ce2e9Sschwarze 	0x2575, 0x2514, 0x2515,  /* 100 up */
83868ce2e9Sschwarze 	0x2502, 0x251c, 0x251d,  /* 101 up down */
84868ce2e9Sschwarze 	0x257d, 0x251f, 0x2522,  /* 102 */
85868ce2e9Sschwarze 	0x2518, 0x2534, 0x2536,  /* 110 up left */
86868ce2e9Sschwarze 	0x2524, 0x253c, 0x253e,  /* 111 all */
87868ce2e9Sschwarze 	0x2527, 0x2541, 0x2546,  /* 112 */
88868ce2e9Sschwarze 	0x2519, 0x2535, 0x2537,  /* 120 up left */
89868ce2e9Sschwarze 	0x2525, 0x253d, 0x253f,  /* 121 all */
90868ce2e9Sschwarze 	0x252a, 0x2545, 0x2548,  /* 122 */
91868ce2e9Sschwarze 	0x2579, 0x2516, 0x2517,  /* 200 up */
92868ce2e9Sschwarze 	0x257f, 0x251e, 0x2521,  /* 201 up down */
93868ce2e9Sschwarze 	0x2503, 0x2520, 0x2523,  /* 202 */
94868ce2e9Sschwarze 	0x251a, 0x2538, 0x253a,  /* 210 up left */
95868ce2e9Sschwarze 	0x2526, 0x2540, 0x2544,  /* 211 all */
96868ce2e9Sschwarze 	0x2528, 0x2542, 0x254a,  /* 212 */
97868ce2e9Sschwarze 	0x251b, 0x2539, 0x253b,  /* 220 up left */
98868ce2e9Sschwarze 	0x2529, 0x2543, 0x2547,  /* 221 all */
99868ce2e9Sschwarze 	0x252b, 0x2549, 0x254b,  /* 222 */
100868ce2e9Sschwarze };
101868ce2e9Sschwarze 
102868ce2e9Sschwarze /* ASCII approximations for these code points, compatible with groff. */
103868ce2e9Sschwarze static  const int borders_ascii[81] = {
104868ce2e9Sschwarze 	' ', '-', '=',  /* 000 right */
105868ce2e9Sschwarze 	'|', '+', '+',  /* 001 down */
106868ce2e9Sschwarze 	'|', '+', '+',  /* 002 */
107868ce2e9Sschwarze 	'-', '-', '=',  /* 010 left */
108868ce2e9Sschwarze 	'+', '+', '+',  /* 011 left down */
109868ce2e9Sschwarze 	'+', '+', '+',  /* 012 */
110868ce2e9Sschwarze 	'=', '=', '=',  /* 020 left */
111868ce2e9Sschwarze 	'+', '+', '+',  /* 021 left down */
112868ce2e9Sschwarze 	'+', '+', '+',  /* 022 */
113868ce2e9Sschwarze 	'|', '+', '+',  /* 100 up */
114868ce2e9Sschwarze 	'|', '+', '+',  /* 101 up down */
115868ce2e9Sschwarze 	'|', '+', '+',  /* 102 */
116868ce2e9Sschwarze 	'+', '+', '+',  /* 110 up left */
117868ce2e9Sschwarze 	'+', '+', '+',  /* 111 all */
118868ce2e9Sschwarze 	'+', '+', '+',  /* 112 */
119868ce2e9Sschwarze 	'+', '+', '+',  /* 120 up left */
120868ce2e9Sschwarze 	'+', '+', '+',  /* 121 all */
121868ce2e9Sschwarze 	'+', '+', '+',  /* 122 */
122868ce2e9Sschwarze 	'|', '+', '+',  /* 200 up */
123868ce2e9Sschwarze 	'|', '+', '+',  /* 201 up down */
124868ce2e9Sschwarze 	'|', '+', '+',  /* 202 */
125868ce2e9Sschwarze 	'+', '+', '+',  /* 210 up left */
126868ce2e9Sschwarze 	'+', '+', '+',  /* 211 all */
127868ce2e9Sschwarze 	'+', '+', '+',  /* 212 */
128868ce2e9Sschwarze 	'+', '+', '+',  /* 220 up left */
129868ce2e9Sschwarze 	'+', '+', '+',  /* 221 all */
130868ce2e9Sschwarze 	'+', '+', '+',  /* 222 */
131868ce2e9Sschwarze };
132868ce2e9Sschwarze 
133868ce2e9Sschwarze /* Either of the above according to the selected output encoding. */
134868ce2e9Sschwarze static	const int *borders_locale;
135868ce2e9Sschwarze 
136868ce2e9Sschwarze 
137ec04407bSschwarze static size_t
term_tbl_sulen(const struct roffsu * su,void * arg)1382c3e66c4Sschwarze term_tbl_sulen(const struct roffsu *su, void *arg)
1392c3e66c4Sschwarze {
1404fcba843Sschwarze 	int	 i;
1414fcba843Sschwarze 
1424fcba843Sschwarze 	i = term_hen((const struct termp *)arg, su);
1434fcba843Sschwarze 	return i > 0 ? i : 0;
1442c3e66c4Sschwarze }
1452c3e66c4Sschwarze 
1462c3e66c4Sschwarze static size_t
term_tbl_strlen(const char * p,void * arg)147ec04407bSschwarze term_tbl_strlen(const char *p, void *arg)
148ec04407bSschwarze {
149526e306bSschwarze 	return term_strlen((const struct termp *)arg, p);
150ec04407bSschwarze }
151ec04407bSschwarze 
152ec04407bSschwarze static size_t
term_tbl_len(size_t sz,void * arg)153ec04407bSschwarze term_tbl_len(size_t sz, void *arg)
154ec04407bSschwarze {
155526e306bSschwarze 	return term_len((const struct termp *)arg, sz);
156ec04407bSschwarze }
157393cb51eSschwarze 
158868ce2e9Sschwarze 
1592791bd1cSschwarze void
term_tbl(struct termp * tp,const struct tbl_span * sp)1602791bd1cSschwarze term_tbl(struct termp *tp, const struct tbl_span *sp)
161393cb51eSschwarze {
162868ce2e9Sschwarze 	const struct tbl_cell	*cp, *cpn, *cpp, *cps;
1632791bd1cSschwarze 	const struct tbl_dat	*dp;
1647d87a934Sschwarze 	static size_t		 offset;
1654aa95b9fSschwarze 	size_t			 save_offset;
1668afa4451Sschwarze 	size_t			 coloff, tsz;
167868ce2e9Sschwarze 	int			 hspans, ic, more;
168f03619d4Sschwarze 	int			 dvert, fc, horiz, lhori, rhori, uvert;
169ec04407bSschwarze 
1702791bd1cSschwarze 	/* Inhibit printing of spaces: we do padding ourselves. */
1712791bd1cSschwarze 
1728afa4451Sschwarze 	tp->flags |= TERMP_NOSPACE | TERMP_NONOSPACE;
1734aa95b9fSschwarze 	save_offset = tp->tcol->offset;
174393cb51eSschwarze 
175393cb51eSschwarze 	/*
176ec04407bSschwarze 	 * The first time we're invoked for a given table block,
177ec04407bSschwarze 	 * calculate the table widths and decimal positions.
178393cb51eSschwarze 	 */
179393cb51eSschwarze 
180cb151596Sschwarze 	if (tp->tbl.cols == NULL) {
181868ce2e9Sschwarze 		borders_locale = tp->enc == TERMENC_UTF8 ?
182868ce2e9Sschwarze 		    borders_utf8 : borders_ascii;
183868ce2e9Sschwarze 
184ec04407bSschwarze 		tp->tbl.len = term_tbl_len;
185ec04407bSschwarze 		tp->tbl.slen = term_tbl_strlen;
1862c3e66c4Sschwarze 		tp->tbl.sulen = term_tbl_sulen;
187ec04407bSschwarze 		tp->tbl.arg = tp;
188ec04407bSschwarze 
189d1c3ec49Sschwarze 		tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin);
190393cb51eSschwarze 
1917d87a934Sschwarze 		/* Center the table as a whole. */
1927d87a934Sschwarze 
193e93ea447Sschwarze 		offset = tp->tcol->offset;
1947d87a934Sschwarze 		if (sp->opts->opts & TBL_OPT_CENTRE) {
1957d87a934Sschwarze 			tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)
1967d87a934Sschwarze 			    ? 2 : !!sp->opts->lvert + !!sp->opts->rvert;
197e8a65004Sschwarze 			for (ic = 0; ic + 1 < sp->opts->cols; ic++)
198e8a65004Sschwarze 				tsz += tp->tbl.cols[ic].width +
199e8a65004Sschwarze 				    tp->tbl.cols[ic].spacing;
200e8a65004Sschwarze 			if (sp->opts->cols)
201e8a65004Sschwarze 				tsz += tp->tbl.cols[sp->opts->cols - 1].width;
202e93ea447Sschwarze 			if (offset + tsz > tp->tcol->rmargin)
2037d87a934Sschwarze 				tsz -= 1;
2044aa95b9fSschwarze 			offset = offset + tp->tcol->rmargin > tsz ?
205e93ea447Sschwarze 			    (offset + tp->tcol->rmargin - tsz) / 2 : 0;
2064aa95b9fSschwarze 			tp->tcol->offset = offset;
2077d87a934Sschwarze 		}
2087d87a934Sschwarze 
2092791bd1cSschwarze 		/* Horizontal frame at the start of boxed tables. */
2102791bd1cSschwarze 
211868ce2e9Sschwarze 		if (tp->enc == TERMENC_ASCII &&
212868ce2e9Sschwarze 		    sp->opts->opts & TBL_OPT_DBOX)
213b3e6a325Sschwarze 			tbl_hrule(tp, NULL, sp, sp, TBL_OPT_DBOX);
214fd9b947eSschwarze 		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX))
215b3e6a325Sschwarze 			tbl_hrule(tp, NULL, sp, sp, TBL_OPT_BOX);
2160ebcd99eSschwarze 	}
2172791bd1cSschwarze 
2188afa4451Sschwarze 	/* Set up the columns. */
2192791bd1cSschwarze 
2208afa4451Sschwarze 	tp->flags |= TERMP_MULTICOL;
2214aa95b9fSschwarze 	tp->tcol->offset = offset;
2228afa4451Sschwarze 	horiz = 0;
2238afa4451Sschwarze 	switch (sp->pos) {
2248afa4451Sschwarze 	case TBL_SPAN_HORIZ:
2258afa4451Sschwarze 	case TBL_SPAN_DHORIZ:
2268afa4451Sschwarze 		horiz = 1;
2278afa4451Sschwarze 		term_setcol(tp, 1);
2288afa4451Sschwarze 		break;
2298afa4451Sschwarze 	case TBL_SPAN_DATA:
2308afa4451Sschwarze 		term_setcol(tp, sp->opts->cols + 2);
2318afa4451Sschwarze 		coloff = tp->tcol->offset;
232fd9b947eSschwarze 
2338afa4451Sschwarze 		/* Set up a column for a left vertical frame. */
2348afa4451Sschwarze 
2358afa4451Sschwarze 		if (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ||
2368afa4451Sschwarze 		    sp->opts->lvert)
2378afa4451Sschwarze 			coloff++;
2388afa4451Sschwarze 		tp->tcol->rmargin = coloff;
2398afa4451Sschwarze 
2408afa4451Sschwarze 		/* Set up the data columns. */
2418afa4451Sschwarze 
2428afa4451Sschwarze 		dp = sp->first;
243a93944b2Sschwarze 		hspans = 0;
2448afa4451Sschwarze 		for (ic = 0; ic < sp->opts->cols; ic++) {
245a93944b2Sschwarze 			if (hspans == 0) {
2468afa4451Sschwarze 				tp->tcol++;
2478afa4451Sschwarze 				tp->tcol->offset = coloff;
2488afa4451Sschwarze 			}
2498afa4451Sschwarze 			coloff += tp->tbl.cols[ic].width;
2508afa4451Sschwarze 			tp->tcol->rmargin = coloff;
2518afa4451Sschwarze 			if (ic + 1 < sp->opts->cols)
252e8a65004Sschwarze 				coloff += tp->tbl.cols[ic].spacing;
253a93944b2Sschwarze 			if (hspans) {
254a93944b2Sschwarze 				hspans--;
2558afa4451Sschwarze 				continue;
2568afa4451Sschwarze 			}
257414e7448Sschwarze 			if (dp != NULL &&
258414e7448Sschwarze 			    (ic || sp->layout->first->pos != TBL_CELL_SPAN)) {
259a93944b2Sschwarze 				hspans = dp->hspans;
2608afa4451Sschwarze 				dp = dp->next;
2618afa4451Sschwarze 			}
262414e7448Sschwarze 		}
2638afa4451Sschwarze 
2648afa4451Sschwarze 		/* Set up a column for a right vertical frame. */
2658afa4451Sschwarze 
2668afa4451Sschwarze 		tp->tcol++;
267e8a65004Sschwarze 		tp->tcol->offset = coloff + 1;
268eaa4dcb4Sschwarze 		tp->tcol->rmargin = tp->maxrmargin;
2698afa4451Sschwarze 
2708afa4451Sschwarze 		/* Spans may have reduced the number of columns. */
2718afa4451Sschwarze 
2728afa4451Sschwarze 		tp->lasttcol = tp->tcol - tp->tcols;
2738afa4451Sschwarze 
2748afa4451Sschwarze 		/* Fill the buffers for all data columns. */
2758afa4451Sschwarze 
2768afa4451Sschwarze 		tp->tcol = tp->tcols;
277eaa4dcb4Sschwarze 		cp = cpn = sp->layout->first;
2788afa4451Sschwarze 		dp = sp->first;
279a93944b2Sschwarze 		hspans = 0;
2808afa4451Sschwarze 		for (ic = 0; ic < sp->opts->cols; ic++) {
281eaa4dcb4Sschwarze 			if (cpn != NULL) {
282eaa4dcb4Sschwarze 				cp = cpn;
283eaa4dcb4Sschwarze 				cpn = cpn->next;
284eaa4dcb4Sschwarze 			}
285a93944b2Sschwarze 			if (hspans) {
286a93944b2Sschwarze 				hspans--;
2878afa4451Sschwarze 				continue;
2888afa4451Sschwarze 			}
2898afa4451Sschwarze 			tp->tcol++;
2908afa4451Sschwarze 			tp->col = 0;
2912a491d59Sschwarze 			tp->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE);
292eaa4dcb4Sschwarze 			tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic);
293414e7448Sschwarze 			if (dp != NULL &&
294414e7448Sschwarze 			    (ic || sp->layout->first->pos != TBL_CELL_SPAN)) {
295a93944b2Sschwarze 				hspans = dp->hspans;
2968afa4451Sschwarze 				dp = dp->next;
2978afa4451Sschwarze 			}
298414e7448Sschwarze 		}
2998afa4451Sschwarze 		break;
3008afa4451Sschwarze 	}
3018afa4451Sschwarze 
3028afa4451Sschwarze 	do {
3038afa4451Sschwarze 		/* Print the vertical frame at the start of each row. */
3048afa4451Sschwarze 
3058afa4451Sschwarze 		tp->tcol = tp->tcols;
306868ce2e9Sschwarze 		uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 :
307868ce2e9Sschwarze 		    sp->opts->opts & TBL_OPT_BOX ? 1 : 0;
308868ce2e9Sschwarze 		if (sp->pos == TBL_SPAN_DATA && uvert < sp->layout->vert)
309868ce2e9Sschwarze 			uvert = dvert = sp->layout->vert;
310868ce2e9Sschwarze 		if (sp->next != NULL && sp->next->pos == TBL_SPAN_DATA &&
311868ce2e9Sschwarze 		    dvert < sp->next->layout->vert)
312868ce2e9Sschwarze 			dvert = sp->next->layout->vert;
313868ce2e9Sschwarze 		if (sp->prev != NULL && uvert < sp->prev->layout->vert &&
314eaa4dcb4Sschwarze 		    (horiz || (IS_HORIZ(sp->layout->first) &&
315868ce2e9Sschwarze 		      !IS_HORIZ(sp->prev->layout->first))))
316868ce2e9Sschwarze 			uvert = sp->prev->layout->vert;
317f03619d4Sschwarze 		rhori = sp->pos == TBL_SPAN_DHORIZ ||
318f03619d4Sschwarze 		    (sp->first != NULL && sp->first->pos == TBL_DATA_DHORIZ) ||
319868ce2e9Sschwarze 		    sp->layout->first->pos == TBL_CELL_DHORIZ ? 2 :
320868ce2e9Sschwarze 		    sp->pos == TBL_SPAN_HORIZ ||
321f03619d4Sschwarze 		    (sp->first != NULL && sp->first->pos == TBL_DATA_HORIZ) ||
322868ce2e9Sschwarze 		    sp->layout->first->pos == TBL_CELL_HORIZ ? 1 : 0;
323f03619d4Sschwarze 		fc = BUP * uvert + BDOWN * dvert + BRIGHT * rhori;
324868ce2e9Sschwarze 		if (uvert > 0 || dvert > 0 || (horiz && sp->opts->lvert)) {
3258afa4451Sschwarze 			(*tp->advance)(tp, tp->tcols->offset);
326868ce2e9Sschwarze 			tp->viscol = tp->tcol->offset;
327868ce2e9Sschwarze 			tbl_direct_border(tp, fc, 1);
3288afa4451Sschwarze 		}
3292791bd1cSschwarze 
3308afa4451Sschwarze 		/* Print the data cells. */
3312791bd1cSschwarze 
3328afa4451Sschwarze 		more = 0;
333868ce2e9Sschwarze 		if (horiz)
334b3e6a325Sschwarze 			tbl_hrule(tp, sp->prev, sp, sp->next, 0);
335868ce2e9Sschwarze 		else {
336fd9b947eSschwarze 			cp = sp->layout->first;
337eaa4dcb4Sschwarze 			cpn = sp->next == NULL ? NULL :
338eaa4dcb4Sschwarze 			    sp->next->layout->first;
339eaa4dcb4Sschwarze 			cpp = sp->prev == NULL ? NULL :
340eaa4dcb4Sschwarze 			    sp->prev->layout->first;
3412791bd1cSschwarze 			dp = sp->first;
342a93944b2Sschwarze 			hspans = 0;
3435f6d1ba3Sschwarze 			for (ic = 0; ic < sp->opts->cols; ic++) {
3448afa4451Sschwarze 
345eaa4dcb4Sschwarze 				/*
346eaa4dcb4Sschwarze 				 * Figure out whether to print a
347eaa4dcb4Sschwarze 				 * vertical line after this cell
348eaa4dcb4Sschwarze 				 * and advance to next layout cell.
349eaa4dcb4Sschwarze 				 */
3508afa4451Sschwarze 
351868ce2e9Sschwarze 				uvert = dvert = fc = 0;
3528afa4451Sschwarze 				if (cp != NULL) {
353868ce2e9Sschwarze 					cps = cp;
354868ce2e9Sschwarze 					while (cps->next != NULL &&
355868ce2e9Sschwarze 					    cps->next->pos == TBL_CELL_SPAN)
356868ce2e9Sschwarze 						cps = cps->next;
357868ce2e9Sschwarze 					if (sp->pos == TBL_SPAN_DATA)
358868ce2e9Sschwarze 						uvert = dvert = cps->vert;
359eaa4dcb4Sschwarze 					switch (cp->pos) {
360eaa4dcb4Sschwarze 					case TBL_CELL_HORIZ:
361868ce2e9Sschwarze 						fc = BHORIZ;
362eaa4dcb4Sschwarze 						break;
363eaa4dcb4Sschwarze 					case TBL_CELL_DHORIZ:
364868ce2e9Sschwarze 						fc = BHORIZ * 2;
365eaa4dcb4Sschwarze 						break;
366eaa4dcb4Sschwarze 					default:
367eaa4dcb4Sschwarze 						break;
368eaa4dcb4Sschwarze 					}
369eaa4dcb4Sschwarze 				}
370eaa4dcb4Sschwarze 				if (cpp != NULL) {
371868ce2e9Sschwarze 					if (uvert < cpp->vert &&
372eaa4dcb4Sschwarze 					    cp != NULL &&
373eaa4dcb4Sschwarze 					    ((IS_HORIZ(cp) &&
374eaa4dcb4Sschwarze 					      !IS_HORIZ(cpp)) ||
375eaa4dcb4Sschwarze 					     (cp->next != NULL &&
376eaa4dcb4Sschwarze 					      cpp->next != NULL &&
377eaa4dcb4Sschwarze 					      IS_HORIZ(cp->next) &&
378eaa4dcb4Sschwarze 					      !IS_HORIZ(cpp->next))))
379868ce2e9Sschwarze 						uvert = cpp->vert;
380eaa4dcb4Sschwarze 					cpp = cpp->next;
381eaa4dcb4Sschwarze 				}
382868ce2e9Sschwarze 				if (sp->opts->opts & TBL_OPT_ALLBOX) {
383868ce2e9Sschwarze 					if (uvert == 0)
384868ce2e9Sschwarze 						uvert = 1;
385868ce2e9Sschwarze 					if (dvert == 0)
386868ce2e9Sschwarze 						dvert = 1;
387868ce2e9Sschwarze 				}
388eaa4dcb4Sschwarze 				if (cpn != NULL) {
389868ce2e9Sschwarze 					if (dvert == 0 ||
390868ce2e9Sschwarze 					    (dvert < cpn->vert &&
391868ce2e9Sschwarze 					     tp->enc == TERMENC_UTF8))
392868ce2e9Sschwarze 						dvert = cpn->vert;
393eaa4dcb4Sschwarze 					cpn = cpn->next;
394eaa4dcb4Sschwarze 				}
39529981601Sschwarze 
396f03619d4Sschwarze 				lhori = (cp != NULL &&
397f03619d4Sschwarze 				     cp->pos == TBL_CELL_DHORIZ) ||
398f03619d4Sschwarze 				    (dp != NULL &&
399f03619d4Sschwarze 				     dp->pos == TBL_DATA_DHORIZ) ? 2 :
400f03619d4Sschwarze 				    (cp != NULL &&
401f03619d4Sschwarze 				     cp->pos == TBL_CELL_HORIZ) ||
402f03619d4Sschwarze 				    (dp != NULL &&
403f03619d4Sschwarze 				     dp->pos == TBL_DATA_HORIZ) ? 1 : 0;
404f03619d4Sschwarze 
405eaa4dcb4Sschwarze 				/*
406eaa4dcb4Sschwarze 				 * Skip later cells in a span,
407eaa4dcb4Sschwarze 				 * figure out whether to start a span,
408eaa4dcb4Sschwarze 				 * and advance to next data cell.
409eaa4dcb4Sschwarze 				 */
41029981601Sschwarze 
411a93944b2Sschwarze 				if (hspans) {
412a93944b2Sschwarze 					hspans--;
413868ce2e9Sschwarze 					cp = cp->next;
41429981601Sschwarze 					continue;
41529981601Sschwarze 				}
416414e7448Sschwarze 				if (dp != NULL && (ic ||
417414e7448Sschwarze 				    sp->layout->first->pos != TBL_CELL_SPAN)) {
418a93944b2Sschwarze 					hspans = dp->hspans;
41929981601Sschwarze 					dp = dp->next;
42029981601Sschwarze 				}
42129981601Sschwarze 
422eaa4dcb4Sschwarze 				/*
423eaa4dcb4Sschwarze 				 * Print one line of text in the cell
424eaa4dcb4Sschwarze 				 * and remember whether there is more.
425eaa4dcb4Sschwarze 				 */
42629981601Sschwarze 
42729981601Sschwarze 				tp->tcol++;
42829981601Sschwarze 				if (tp->tcol->col < tp->tcol->lastcol)
42929981601Sschwarze 					term_flushln(tp);
43029981601Sschwarze 				if (tp->tcol->col < tp->tcol->lastcol)
43129981601Sschwarze 					more = 1;
43229981601Sschwarze 
43329981601Sschwarze 				/*
43429981601Sschwarze 				 * Vertical frames between data cells,
43529981601Sschwarze 				 * but not after the last column.
43629981601Sschwarze 				 */
43729981601Sschwarze 
4385191762fSschwarze 				if (fc == 0 &&
4395191762fSschwarze 				    ((uvert == 0 && dvert == 0 &&
4405191762fSschwarze 				      cp != NULL && (cp->next == NULL ||
441868ce2e9Sschwarze 				      !IS_HORIZ(cp->next))) ||
4425191762fSschwarze 				     tp->tcol + 1 ==
4435191762fSschwarze 				      tp->tcols + tp->lasttcol)) {
4445191762fSschwarze 					if (cp != NULL)
445868ce2e9Sschwarze 						cp = cp->next;
446fd9b947eSschwarze 					continue;
447868ce2e9Sschwarze 				}
448fd9b947eSschwarze 
449e8a65004Sschwarze 				if (tp->viscol < tp->tcol->rmargin) {
4508afa4451Sschwarze 					(*tp->advance)(tp, tp->tcol->rmargin
451eaa4dcb4Sschwarze 					   - tp->viscol);
452eaa4dcb4Sschwarze 					tp->viscol = tp->tcol->rmargin;
4538351ebcfSschwarze 				}
454e8a65004Sschwarze 				while (tp->viscol < tp->tcol->rmargin +
455868ce2e9Sschwarze 				    tp->tbl.cols[ic].spacing / 2)
456f03619d4Sschwarze 					tbl_direct_border(tp,
457f03619d4Sschwarze 					    BHORIZ * lhori, 1);
458eaa4dcb4Sschwarze 
459eaa4dcb4Sschwarze 				if (tp->tcol + 1 == tp->tcols + tp->lasttcol)
460eaa4dcb4Sschwarze 					continue;
461eaa4dcb4Sschwarze 
462f03619d4Sschwarze 				if (cp != NULL)
463868ce2e9Sschwarze 					cp = cp->next;
464f03619d4Sschwarze 
465f03619d4Sschwarze 				rhori = (cp != NULL &&
466f03619d4Sschwarze 				     cp->pos == TBL_CELL_DHORIZ) ||
467f03619d4Sschwarze 				    (dp != NULL &&
468f03619d4Sschwarze 				     dp->pos == TBL_DATA_DHORIZ) ? 2 :
469f03619d4Sschwarze 				    (cp != NULL &&
470f03619d4Sschwarze 				     cp->pos == TBL_CELL_HORIZ) ||
471f03619d4Sschwarze 				    (dp != NULL &&
472f03619d4Sschwarze 				     dp->pos == TBL_DATA_HORIZ) ? 1 : 0;
473f03619d4Sschwarze 
474868ce2e9Sschwarze 				if (tp->tbl.cols[ic].spacing)
475f03619d4Sschwarze 					tbl_direct_border(tp,
476f03619d4Sschwarze 					    BLEFT * lhori + BRIGHT * rhori +
477868ce2e9Sschwarze 					    BUP * uvert + BDOWN * dvert, 1);
478eaa4dcb4Sschwarze 
479868ce2e9Sschwarze 				if (tp->enc == TERMENC_UTF8)
480868ce2e9Sschwarze 					uvert = dvert = 0;
481868ce2e9Sschwarze 
482e8a65004Sschwarze 				if (tp->tbl.cols[ic].spacing > 2 &&
483f03619d4Sschwarze 				    (uvert > 1 || dvert > 1 || rhori))
484f03619d4Sschwarze 					tbl_direct_border(tp,
485f03619d4Sschwarze 					    BHORIZ * rhori +
486868ce2e9Sschwarze 					    BUP * (uvert > 1) +
487868ce2e9Sschwarze 					    BDOWN * (dvert > 1), 1);
4888afa4451Sschwarze 			}
4898afa4451Sschwarze 		}
490393cb51eSschwarze 
4918afa4451Sschwarze 		/* Print the vertical frame at the end of each row. */
4920ebcd99eSschwarze 
493868ce2e9Sschwarze 		uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 :
494868ce2e9Sschwarze 		    sp->opts->opts & TBL_OPT_BOX ? 1 : 0;
495868ce2e9Sschwarze 		if (sp->pos == TBL_SPAN_DATA &&
496868ce2e9Sschwarze 		    uvert < sp->layout->last->vert &&
497868ce2e9Sschwarze 		    sp->layout->last->col + 1 == sp->opts->cols)
498868ce2e9Sschwarze 			uvert = dvert = sp->layout->last->vert;
499868ce2e9Sschwarze 		if (sp->next != NULL &&
500868ce2e9Sschwarze 		    dvert < sp->next->layout->last->vert &&
501868ce2e9Sschwarze 		    sp->next->layout->last->col + 1 == sp->opts->cols)
502868ce2e9Sschwarze 			dvert = sp->next->layout->last->vert;
503868ce2e9Sschwarze 		if (sp->prev != NULL &&
504868ce2e9Sschwarze 		    uvert < sp->prev->layout->last->vert &&
505eaa4dcb4Sschwarze 		    sp->prev->layout->last->col + 1 == sp->opts->cols &&
506eaa4dcb4Sschwarze 		    (horiz || (IS_HORIZ(sp->layout->last) &&
507868ce2e9Sschwarze 		     !IS_HORIZ(sp->prev->layout->last))))
508868ce2e9Sschwarze 			uvert = sp->prev->layout->last->vert;
509f03619d4Sschwarze 		lhori = sp->pos == TBL_SPAN_DHORIZ ||
510f03619d4Sschwarze 		    (sp->last != NULL &&
511f03619d4Sschwarze 		     sp->last->pos == TBL_DATA_DHORIZ &&
512f03619d4Sschwarze 		     sp->last->layout->col + 1 == sp->opts->cols) ||
513868ce2e9Sschwarze 		    (sp->layout->last->pos == TBL_CELL_DHORIZ &&
514868ce2e9Sschwarze 		     sp->layout->last->col + 1 == sp->opts->cols) ? 2 :
515868ce2e9Sschwarze 		    sp->pos == TBL_SPAN_HORIZ ||
516f03619d4Sschwarze 		    (sp->last != NULL &&
517f03619d4Sschwarze 		     sp->last->pos == TBL_DATA_HORIZ &&
518f03619d4Sschwarze 		     sp->last->layout->col + 1 == sp->opts->cols) ||
519868ce2e9Sschwarze 		    (sp->layout->last->pos == TBL_CELL_HORIZ &&
520868ce2e9Sschwarze 		     sp->layout->last->col + 1 == sp->opts->cols) ? 1 : 0;
521f03619d4Sschwarze 		fc = BUP * uvert + BDOWN * dvert + BLEFT * lhori;
522868ce2e9Sschwarze 		if (uvert > 0 || dvert > 0 || (horiz && sp->opts->rvert)) {
523eaa4dcb4Sschwarze 			if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 ||
524eaa4dcb4Sschwarze 			    sp->layout->last->col + 1 < sp->opts->cols)) {
5258afa4451Sschwarze 				tp->tcol++;
526f03619d4Sschwarze 				do {
527f03619d4Sschwarze 					tbl_direct_border(tp,
528f03619d4Sschwarze 					    BHORIZ * lhori, 1);
529f03619d4Sschwarze 				} while (tp->viscol < tp->tcol->offset);
5308afa4451Sschwarze 			}
531868ce2e9Sschwarze 			tbl_direct_border(tp, fc, 1);
5328afa4451Sschwarze 		}
5338afa4451Sschwarze 		(*tp->endline)(tp);
5348afa4451Sschwarze 		tp->viscol = 0;
5358afa4451Sschwarze 	} while (more);
5362791bd1cSschwarze 
5372791bd1cSschwarze 	/*
538eaa4dcb4Sschwarze 	 * Clean up after this row.  If it is the last line
539eaa4dcb4Sschwarze 	 * of the table, print the box line and clean up
540eaa4dcb4Sschwarze 	 * column data; otherwise, print the allbox line.
5412791bd1cSschwarze 	 */
5422791bd1cSschwarze 
5438afa4451Sschwarze 	term_setcol(tp, 1);
5448afa4451Sschwarze 	tp->flags &= ~TERMP_MULTICOL;
5458afa4451Sschwarze 	tp->tcol->rmargin = tp->maxrmargin;
546cb151596Sschwarze 	if (sp->next == NULL) {
547*61b0e532Sschwarze 		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX))
548b3e6a325Sschwarze 			tbl_hrule(tp, sp, sp, NULL, TBL_OPT_BOX);
549868ce2e9Sschwarze 		if (tp->enc == TERMENC_ASCII &&
550*61b0e532Sschwarze 		    sp->opts->opts & TBL_OPT_DBOX)
551b3e6a325Sschwarze 			tbl_hrule(tp, sp, sp, NULL, TBL_OPT_DBOX);
552ec04407bSschwarze 		assert(tp->tbl.cols);
553ec04407bSschwarze 		free(tp->tbl.cols);
554ec04407bSschwarze 		tp->tbl.cols = NULL;
5556c9ac24eSschwarze 	} else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX &&
5566c9ac24eSschwarze 	    (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA ||
5576c9ac24eSschwarze 	     sp->next->next != NULL))
558b3e6a325Sschwarze 		tbl_hrule(tp, sp, sp, sp->next, TBL_OPT_ALLBOX);
55906875e85Sschwarze 
5604aa95b9fSschwarze 	tp->tcol->offset = save_offset;
5618afa4451Sschwarze 	tp->flags &= ~TERMP_NONOSPACE;
5622791bd1cSschwarze }
563393cb51eSschwarze 
564393cb51eSschwarze static void
tbl_hrule(struct termp * tp,const struct tbl_span * spp,const struct tbl_span * sp,const struct tbl_span * spn,int flags)5650673fa65Sschwarze tbl_hrule(struct termp *tp, const struct tbl_span *spp,
566b3e6a325Sschwarze     const struct tbl_span *sp, const struct tbl_span *spn, int flags)
567393cb51eSschwarze {
5687d622754Sschwarze 	const struct tbl_cell	*cpp;    /* Layout cell above this line. */
569b3e6a325Sschwarze 	const struct tbl_cell	*cp;     /* Layout cell in this line. */
5707d622754Sschwarze 	const struct tbl_cell	*cpn;    /* Layout cell below this line. */
5717d622754Sschwarze 	const struct tbl_dat	*dpn;	 /* Data cell below this line. */
5720673fa65Sschwarze 	const struct roffcol	*col;    /* Contains width and spacing. */
5730673fa65Sschwarze 	int			 opts;   /* For the table as a whole. */
5740673fa65Sschwarze 	int			 bw;	 /* Box line width. */
5750673fa65Sschwarze 	int			 hw;     /* Horizontal line width. */
5760673fa65Sschwarze 	int			 lw, rw; /* Left and right line widths. */
5770673fa65Sschwarze 	int			 uw, dw; /* Vertical line widths. */
578393cb51eSschwarze 
5790673fa65Sschwarze 	cpp = spp == NULL ? NULL : spp->layout->first;
580b3e6a325Sschwarze 	cp  = sp  == NULL ? NULL : sp->layout->first;
5810673fa65Sschwarze 	cpn = spn == NULL ? NULL : spn->layout->first;
5827d622754Sschwarze 	dpn = NULL;
5837d622754Sschwarze 	if (spn != NULL) {
5847d622754Sschwarze 		if (spn->pos == TBL_SPAN_DATA)
5857d622754Sschwarze 			dpn = spn->first;
5867d622754Sschwarze 		else if (spn->next != NULL)
5877d622754Sschwarze 			dpn = spn->next->first;
5887d622754Sschwarze 	}
589b3e6a325Sschwarze 	opts = sp->opts->opts;
5900673fa65Sschwarze 	bw = opts & TBL_OPT_DBOX ? (tp->enc == TERMENC_UTF8 ? 2 : 1) :
5910673fa65Sschwarze 	    opts & (TBL_OPT_BOX | TBL_OPT_ALLBOX) ? 1 : 0;
5920673fa65Sschwarze 	hw = flags == TBL_OPT_DBOX || flags == TBL_OPT_BOX ? bw :
593b3e6a325Sschwarze 	    sp->pos == TBL_SPAN_DHORIZ ? 2 : 1;
5940673fa65Sschwarze 
5950673fa65Sschwarze 	/* Print the left end of the line. */
5960673fa65Sschwarze 
597868ce2e9Sschwarze 	if (tp->viscol == 0) {
598868ce2e9Sschwarze 		(*tp->advance)(tp, tp->tcols->offset);
599868ce2e9Sschwarze 		tp->viscol = tp->tcols->offset;
600868ce2e9Sschwarze 	}
6010673fa65Sschwarze 	if (flags != 0)
6020673fa65Sschwarze 		tbl_direct_border(tp,
6030673fa65Sschwarze 		    (spp == NULL ? 0 : BUP * bw) +
6040673fa65Sschwarze 		    (spn == NULL ? 0 : BDOWN * bw) +
6050673fa65Sschwarze 		    (spp == NULL || cpn == NULL ||
6060673fa65Sschwarze 		     cpn->pos != TBL_CELL_DOWN ? BRIGHT * hw : 0), 1);
6070673fa65Sschwarze 
6087514a273Sschwarze 	col = tp->tbl.cols;
609fd9b947eSschwarze 	for (;;) {
6107514a273Sschwarze 		if (cp == NULL)
6117514a273Sschwarze 			col++;
6127514a273Sschwarze 		else
613b3e6a325Sschwarze 			col = tp->tbl.cols + cp->col;
6140673fa65Sschwarze 
6150673fa65Sschwarze 		/* Print the horizontal line inside this column. */
6160673fa65Sschwarze 
6170673fa65Sschwarze 		lw = cpp == NULL || cpn == NULL ||
6187d622754Sschwarze 		    (cpn->pos != TBL_CELL_DOWN &&
6195f6e3232Sschwarze 		     (dpn == NULL || dpn->string == NULL ||
6205f6e3232Sschwarze 		      strcmp(dpn->string, "\\^") != 0))
6217d622754Sschwarze 		    ? hw : 0;
6220673fa65Sschwarze 		tbl_direct_border(tp, BHORIZ * lw,
6230673fa65Sschwarze 		    col->width + col->spacing / 2);
6240673fa65Sschwarze 
6250673fa65Sschwarze 		/*
6260673fa65Sschwarze 		 * Figure out whether a vertical line is crossing
6270673fa65Sschwarze 		 * at the end of this column,
6280673fa65Sschwarze 		 * and advance to the next column.
6290673fa65Sschwarze 		 */
6300673fa65Sschwarze 
6310673fa65Sschwarze 		uw = dw = 0;
632eaa4dcb4Sschwarze 		if (cpp != NULL) {
6330673fa65Sschwarze 			if (flags != TBL_OPT_DBOX) {
6340673fa65Sschwarze 				uw = cpp->vert;
6350673fa65Sschwarze 				if (uw == 0 && opts & TBL_OPT_ALLBOX)
6360673fa65Sschwarze 					uw = 1;
6370673fa65Sschwarze 			}
638eaa4dcb4Sschwarze 			cpp = cpp->next;
6397514a273Sschwarze 		} else if (spp != NULL && opts & TBL_OPT_ALLBOX)
6407514a273Sschwarze 			uw = 1;
641b3e6a325Sschwarze 		if (cp != NULL)
642b3e6a325Sschwarze 			cp = cp->next;
643eaa4dcb4Sschwarze 		if (cpn != NULL) {
6440673fa65Sschwarze 			if (flags != TBL_OPT_DBOX) {
6450673fa65Sschwarze 				dw = cpn->vert;
6460673fa65Sschwarze 				if (dw == 0 && opts & TBL_OPT_ALLBOX)
6470673fa65Sschwarze 					dw = 1;
6480673fa65Sschwarze 			}
649eaa4dcb4Sschwarze 			cpn = cpn->next;
6507d622754Sschwarze 			while (dpn != NULL && dpn->layout != cpn)
6517d622754Sschwarze 				dpn = dpn->next;
6527514a273Sschwarze 		} else if (spn != NULL && opts & TBL_OPT_ALLBOX)
6537514a273Sschwarze 			dw = 1;
6547514a273Sschwarze 		if (col + 1 == tp->tbl.cols + sp->opts->cols)
6550673fa65Sschwarze 			break;
6560673fa65Sschwarze 
6570673fa65Sschwarze 		/* Vertical lines do not cross spanned cells. */
6580673fa65Sschwarze 
6590673fa65Sschwarze 		if (cpp != NULL && cpp->pos == TBL_CELL_SPAN)
6600673fa65Sschwarze 			uw = 0;
6610673fa65Sschwarze 		if (cpn != NULL && cpn->pos == TBL_CELL_SPAN)
6620673fa65Sschwarze 			dw = 0;
6630673fa65Sschwarze 
6640673fa65Sschwarze 		/* The horizontal line inside the next column. */
6650673fa65Sschwarze 
6660673fa65Sschwarze 		rw = cpp == NULL || cpn == NULL ||
6677d622754Sschwarze 		    (cpn->pos != TBL_CELL_DOWN &&
6685f6e3232Sschwarze 		     (dpn == NULL || dpn->string == NULL ||
6695f6e3232Sschwarze 		      strcmp(dpn->string, "\\^") != 0))
6707d622754Sschwarze 		    ? hw : 0;
6710673fa65Sschwarze 
6720673fa65Sschwarze 		/* The line crossing at the end of this column. */
6730673fa65Sschwarze 
674868ce2e9Sschwarze 		if (col->spacing)
6750673fa65Sschwarze 			tbl_direct_border(tp, BLEFT * lw +
6760673fa65Sschwarze 			    BRIGHT * rw + BUP * uw + BDOWN * dw, 1);
6770673fa65Sschwarze 
6780673fa65Sschwarze 		/*
6790673fa65Sschwarze 		 * In ASCII output, a crossing may print two characters.
6800673fa65Sschwarze 		 */
6810673fa65Sschwarze 
6820673fa65Sschwarze 		if (tp->enc != TERMENC_ASCII || (uw < 2 && dw < 2))
6830673fa65Sschwarze 			uw = dw = 0;
684868ce2e9Sschwarze 		if (col->spacing > 2)
6850673fa65Sschwarze 			tbl_direct_border(tp,
6860673fa65Sschwarze                             BHORIZ * rw + BUP * uw + BDOWN * dw, 1);
6870673fa65Sschwarze 
6880673fa65Sschwarze 		/* Padding before the start of the next column. */
6890673fa65Sschwarze 
690868ce2e9Sschwarze 		if (col->spacing > 4)
6910673fa65Sschwarze 			tbl_direct_border(tp,
6920673fa65Sschwarze 			    BHORIZ * rw, (col->spacing - 3) / 2);
693868ce2e9Sschwarze 	}
6940673fa65Sschwarze 
6950673fa65Sschwarze 	/* Print the right end of the line. */
6960673fa65Sschwarze 
6970673fa65Sschwarze 	if (flags != 0) {
6980673fa65Sschwarze 		tbl_direct_border(tp,
6990673fa65Sschwarze 		    (spp == NULL ? 0 : BUP * bw) +
7000673fa65Sschwarze 		    (spn == NULL ? 0 : BDOWN * bw) +
7010673fa65Sschwarze 		    (spp == NULL || spn == NULL ||
7020673fa65Sschwarze 		     spn->layout->last->pos != TBL_CELL_DOWN ?
7030673fa65Sschwarze 		     BLEFT * hw : 0), 1);
704868ce2e9Sschwarze 		(*tp->endline)(tp);
705868ce2e9Sschwarze 		tp->viscol = 0;
706393cb51eSschwarze 	}
707fd9b947eSschwarze }
708393cb51eSschwarze 
7092791bd1cSschwarze static void
tbl_data(struct termp * tp,const struct tbl_opts * opts,const struct tbl_cell * cp,const struct tbl_dat * dp,const struct roffcol * col)710c7497e73Sschwarze tbl_data(struct termp *tp, const struct tbl_opts *opts,
711eaa4dcb4Sschwarze     const struct tbl_cell *cp, const struct tbl_dat *dp,
712ec04407bSschwarze     const struct roffcol *col)
7132791bd1cSschwarze {
714eaa4dcb4Sschwarze 	switch (cp->pos) {
715eaa4dcb4Sschwarze 	case TBL_CELL_HORIZ:
716868ce2e9Sschwarze 		tbl_fill_border(tp, BHORIZ, col->width);
717eaa4dcb4Sschwarze 		return;
718eaa4dcb4Sschwarze 	case TBL_CELL_DHORIZ:
719868ce2e9Sschwarze 		tbl_fill_border(tp, BHORIZ * 2, col->width);
720eaa4dcb4Sschwarze 		return;
721eaa4dcb4Sschwarze 	default:
722eaa4dcb4Sschwarze 		break;
723eaa4dcb4Sschwarze 	}
7242791bd1cSschwarze 
7257522ddcbSschwarze 	if (dp == NULL)
7262791bd1cSschwarze 		return;
7272791bd1cSschwarze 
7282791bd1cSschwarze 	switch (dp->pos) {
72949aff9f8Sschwarze 	case TBL_DATA_NONE:
7302791bd1cSschwarze 		return;
73149aff9f8Sschwarze 	case TBL_DATA_HORIZ:
73249aff9f8Sschwarze 	case TBL_DATA_NHORIZ:
733868ce2e9Sschwarze 		tbl_fill_border(tp, BHORIZ, col->width);
7342791bd1cSschwarze 		return;
73549aff9f8Sschwarze 	case TBL_DATA_NDHORIZ:
73649aff9f8Sschwarze 	case TBL_DATA_DHORIZ:
737868ce2e9Sschwarze 		tbl_fill_border(tp, BHORIZ * 2, col->width);
7382791bd1cSschwarze 		return;
7392791bd1cSschwarze 	default:
7402791bd1cSschwarze 		break;
7412791bd1cSschwarze 	}
7422791bd1cSschwarze 
743eaa4dcb4Sschwarze 	switch (cp->pos) {
74449aff9f8Sschwarze 	case TBL_CELL_LONG:
74549aff9f8Sschwarze 	case TBL_CELL_CENTRE:
74649aff9f8Sschwarze 	case TBL_CELL_LEFT:
74749aff9f8Sschwarze 	case TBL_CELL_RIGHT:
748ec04407bSschwarze 		tbl_literal(tp, dp, col);
7492791bd1cSschwarze 		break;
75049aff9f8Sschwarze 	case TBL_CELL_NUMBER:
751c7497e73Sschwarze 		tbl_number(tp, opts, dp, col);
7522791bd1cSschwarze 		break;
75349aff9f8Sschwarze 	case TBL_CELL_DOWN:
7547522ddcbSschwarze 	case TBL_CELL_SPAN:
7558351ebcfSschwarze 		break;
7562791bd1cSschwarze 	default:
7572791bd1cSschwarze 		abort();
7582791bd1cSschwarze 	}
7592791bd1cSschwarze }
760ec04407bSschwarze 
7612791bd1cSschwarze static void
tbl_fill_string(struct termp * tp,const char * cp,size_t len)762868ce2e9Sschwarze tbl_fill_string(struct termp *tp, const char *cp, size_t len)
763393cb51eSschwarze {
764ec04407bSschwarze 	size_t	 i, sz;
765868ce2e9Sschwarze 
766868ce2e9Sschwarze 	sz = term_strlen(tp, cp);
767868ce2e9Sschwarze 	for (i = 0; i < len; i += sz)
768868ce2e9Sschwarze 		term_word(tp, cp);
769868ce2e9Sschwarze }
770868ce2e9Sschwarze 
771868ce2e9Sschwarze static void
tbl_fill_char(struct termp * tp,char c,size_t len)772868ce2e9Sschwarze tbl_fill_char(struct termp *tp, char c, size_t len)
773868ce2e9Sschwarze {
7742791bd1cSschwarze 	char	 cp[2];
775393cb51eSschwarze 
7762791bd1cSschwarze 	cp[0] = c;
7772791bd1cSschwarze 	cp[1] = '\0';
778868ce2e9Sschwarze 	tbl_fill_string(tp, cp, len);
779868ce2e9Sschwarze }
7802791bd1cSschwarze 
781868ce2e9Sschwarze static void
tbl_fill_border(struct termp * tp,int c,size_t len)782868ce2e9Sschwarze tbl_fill_border(struct termp *tp, int c, size_t len)
783868ce2e9Sschwarze {
784868ce2e9Sschwarze 	char	 buf[12];
7852791bd1cSschwarze 
786868ce2e9Sschwarze 	if ((c = borders_locale[c]) > 127) {
787868ce2e9Sschwarze 		(void)snprintf(buf, sizeof(buf), "\\[u%04x]", c);
788868ce2e9Sschwarze 		tbl_fill_string(tp, buf, len);
789868ce2e9Sschwarze 	} else
790868ce2e9Sschwarze 		tbl_fill_char(tp, c, len);
791868ce2e9Sschwarze }
792868ce2e9Sschwarze 
793868ce2e9Sschwarze static void
tbl_direct_border(struct termp * tp,int c,size_t len)794868ce2e9Sschwarze tbl_direct_border(struct termp *tp, int c, size_t len)
795868ce2e9Sschwarze {
796868ce2e9Sschwarze 	size_t	 i, sz;
797868ce2e9Sschwarze 
798868ce2e9Sschwarze 	c = borders_locale[c];
799868ce2e9Sschwarze 	sz = (*tp->width)(tp, c);
800868ce2e9Sschwarze 	for (i = 0; i < len; i += sz) {
801868ce2e9Sschwarze 		(*tp->letter)(tp, c);
802868ce2e9Sschwarze 		tp->viscol += sz;
803868ce2e9Sschwarze 	}
804393cb51eSschwarze }
805393cb51eSschwarze 
8062791bd1cSschwarze static void
tbl_literal(struct termp * tp,const struct tbl_dat * dp,const struct roffcol * col)807ec04407bSschwarze tbl_literal(struct termp *tp, const struct tbl_dat *dp,
808ec04407bSschwarze 		const struct roffcol *col)
8092791bd1cSschwarze {
8105f6d1ba3Sschwarze 	size_t		 len, padl, padr, width;
811a93944b2Sschwarze 	int		 ic, hspans;
8122791bd1cSschwarze 
8138351ebcfSschwarze 	assert(dp->string);
8140ebcd99eSschwarze 	len = term_strlen(tp, dp->string);
815fd436d91Sschwarze 	width = col->width;
8165f6d1ba3Sschwarze 	ic = dp->layout->col;
817a93944b2Sschwarze 	hspans = dp->hspans;
8186497b0adSschwarze 	while (hspans--) {
8196497b0adSschwarze 		width += tp->tbl.cols[ic].spacing;
8206497b0adSschwarze 		ic++;
8216497b0adSschwarze 		width += tp->tbl.cols[ic].width;
8226497b0adSschwarze 	}
823fd436d91Sschwarze 
824fd436d91Sschwarze 	padr = width > len ? width - len : 0;
8250ebcd99eSschwarze 	padl = 0;
8262791bd1cSschwarze 
8278351ebcfSschwarze 	switch (dp->layout->pos) {
82849aff9f8Sschwarze 	case TBL_CELL_LONG:
8290ebcd99eSschwarze 		padl = term_len(tp, 1);
8300ebcd99eSschwarze 		padr = padr > padl ? padr - padl : 0;
8312791bd1cSschwarze 		break;
83249aff9f8Sschwarze 	case TBL_CELL_CENTRE:
8330ebcd99eSschwarze 		if (2 > padr)
834ed0596dcSschwarze 			break;
8350ebcd99eSschwarze 		padl = padr / 2;
836ed0596dcSschwarze 		padr -= padl;
8372791bd1cSschwarze 		break;
83849aff9f8Sschwarze 	case TBL_CELL_RIGHT:
8390ebcd99eSschwarze 		padl = padr;
8400ebcd99eSschwarze 		padr = 0;
8412791bd1cSschwarze 		break;
8422791bd1cSschwarze 	default:
8432791bd1cSschwarze 		break;
8442791bd1cSschwarze 	}
8452791bd1cSschwarze 
846868ce2e9Sschwarze 	tbl_fill_char(tp, ASCII_NBRSP, padl);
847fd1e7ec9Sschwarze 	tbl_word(tp, dp);
848868ce2e9Sschwarze 	tbl_fill_char(tp, ASCII_NBRSP, padr);
8492791bd1cSschwarze }
850393cb51eSschwarze 
851393cb51eSschwarze static void
tbl_number(struct termp * tp,const struct tbl_opts * opts,const struct tbl_dat * dp,const struct roffcol * col)852c7497e73Sschwarze tbl_number(struct termp *tp, const struct tbl_opts *opts,
8532791bd1cSschwarze 		const struct tbl_dat *dp,
854ec04407bSschwarze 		const struct roffcol *col)
855393cb51eSschwarze {
856a7d5d23dSschwarze 	const char	*cp, *lastdigit, *lastpoint;
857a7d5d23dSschwarze 	size_t		 intsz, padl, totsz;
858ec04407bSschwarze 	char		 buf[2];
8592791bd1cSschwarze 
8602791bd1cSschwarze 	/*
861a7d5d23dSschwarze 	 * Almost the same code as in tblcalc_number():
862a7d5d23dSschwarze 	 * First find the position of the decimal point.
8632791bd1cSschwarze 	 */
8642791bd1cSschwarze 
8658351ebcfSschwarze 	assert(dp->string);
866a7d5d23dSschwarze 	lastdigit = lastpoint = NULL;
867a7d5d23dSschwarze 	for (cp = dp->string; cp[0] != '\0'; cp++) {
868a7d5d23dSschwarze 		if (cp[0] == '\\' && cp[1] == '&') {
869a7d5d23dSschwarze 			lastdigit = lastpoint = cp;
870a7d5d23dSschwarze 			break;
871a7d5d23dSschwarze 		} else if (cp[0] == opts->decimal &&
872a7d5d23dSschwarze 		    (isdigit((unsigned char)cp[1]) ||
873a7d5d23dSschwarze 		     (cp > dp->string && isdigit((unsigned char)cp[-1]))))
874a7d5d23dSschwarze 			lastpoint = cp;
875a7d5d23dSschwarze 		else if (isdigit((unsigned char)cp[0]))
876a7d5d23dSschwarze 			lastdigit = cp;
8772791bd1cSschwarze 	}
878393cb51eSschwarze 
879a7d5d23dSschwarze 	/* Then measure both widths. */
880a7d5d23dSschwarze 
881f2c51b35Sschwarze 	padl = 0;
882a7d5d23dSschwarze 	totsz = term_strlen(tp, dp->string);
883a7d5d23dSschwarze 	if (lastdigit != NULL) {
884a7d5d23dSschwarze 		if (lastpoint == NULL)
885a7d5d23dSschwarze 			lastpoint = lastdigit + 1;
886a7d5d23dSschwarze 		intsz = 0;
887a7d5d23dSschwarze 		buf[1] = '\0';
888a7d5d23dSschwarze 		for (cp = dp->string; cp < lastpoint; cp++) {
889a7d5d23dSschwarze 			buf[0] = cp[0];
890a7d5d23dSschwarze 			intsz += term_strlen(tp, buf);
891a7d5d23dSschwarze 		}
892a7d5d23dSschwarze 
893a7d5d23dSschwarze 		/*
894a7d5d23dSschwarze 		 * Pad left to match the decimal position,
895a7d5d23dSschwarze 		 * but avoid exceeding the total column width.
896a7d5d23dSschwarze 		 */
897a7d5d23dSschwarze 
898a7d5d23dSschwarze 		if (col->decimal > intsz && col->width > totsz) {
899a7d5d23dSschwarze 			padl = col->decimal - intsz;
900a7d5d23dSschwarze 			if (padl + totsz > col->width)
901a7d5d23dSschwarze 				padl = col->width - totsz;
902a7d5d23dSschwarze 		}
903a7d5d23dSschwarze 
904a7d5d23dSschwarze 	/* If it is not a number, simply center the string. */
905a7d5d23dSschwarze 
906a7d5d23dSschwarze 	} else if (col->width > totsz)
907a7d5d23dSschwarze 		padl = (col->width - totsz) / 2;
908a7d5d23dSschwarze 
909868ce2e9Sschwarze 	tbl_fill_char(tp, ASCII_NBRSP, padl);
910fd1e7ec9Sschwarze 	tbl_word(tp, dp);
911a7d5d23dSschwarze 
912a7d5d23dSschwarze 	/* Pad right to fill the column.  */
913a7d5d23dSschwarze 
914a7d5d23dSschwarze 	if (col->width > padl + totsz)
915868ce2e9Sschwarze 		tbl_fill_char(tp, ASCII_NBRSP, col->width - padl - totsz);
916393cb51eSschwarze }
917393cb51eSschwarze 
918fd1e7ec9Sschwarze static void
tbl_word(struct termp * tp,const struct tbl_dat * dp)919fd1e7ec9Sschwarze tbl_word(struct termp *tp, const struct tbl_dat *dp)
920fd1e7ec9Sschwarze {
921f29a1da1Sschwarze 	int		 prev_font;
922fd1e7ec9Sschwarze 
923f29a1da1Sschwarze 	prev_font = tp->fonti;
9247d063611Sschwarze 	switch (dp->layout->font) {
9257d063611Sschwarze 		case ESCAPE_FONTBI:
9267d063611Sschwarze 			term_fontpush(tp, TERMFONT_BI);
9277d063611Sschwarze 			break;
9287d063611Sschwarze 		case ESCAPE_FONTBOLD:
9297d063611Sschwarze 		case ESCAPE_FONTCB:
930fd1e7ec9Sschwarze 			term_fontpush(tp, TERMFONT_BOLD);
9317d063611Sschwarze 			break;
9327d063611Sschwarze 		case ESCAPE_FONTITALIC:
9337d063611Sschwarze 		case ESCAPE_FONTCI:
934fd1e7ec9Sschwarze 			term_fontpush(tp, TERMFONT_UNDER);
9357d063611Sschwarze 			break;
9367d063611Sschwarze 		case ESCAPE_FONTROMAN:
9377d063611Sschwarze 		case ESCAPE_FONTCR:
9387d063611Sschwarze 			break;
9397d063611Sschwarze 		default:
9407d063611Sschwarze 			abort();
9417d063611Sschwarze 	}
942fd1e7ec9Sschwarze 
943fd1e7ec9Sschwarze 	term_word(tp, dp->string);
944fd1e7ec9Sschwarze 
945fd1e7ec9Sschwarze 	term_fontpopq(tp, prev_font);
946fd1e7ec9Sschwarze }
947