1324581Sbapt/*	$Id: tbl_term.c,v 1.57 2017/07/31 16:14:10 schwarze Exp $ */
2241675Suqs/*
3241675Suqs * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4322249Sbapt * Copyright (c) 2011,2012,2014,2015,2017 Ingo Schwarze <schwarze@openbsd.org>
5241675Suqs *
6241675Suqs * Permission to use, copy, modify, and distribute this software for any
7241675Suqs * purpose with or without fee is hereby granted, provided that the above
8241675Suqs * copyright notice and this permission notice appear in all copies.
9241675Suqs *
10241675Suqs * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11241675Suqs * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12241675Suqs * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13241675Suqs * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14241675Suqs * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15241675Suqs * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16241675Suqs * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17241675Suqs */
18241675Suqs#include "config.h"
19241675Suqs
20275432Sbapt#include <sys/types.h>
21275432Sbapt
22241675Suqs#include <assert.h>
23241675Suqs#include <stdio.h>
24241675Suqs#include <stdlib.h>
25241675Suqs#include <string.h>
26241675Suqs
27241675Suqs#include "mandoc.h"
28241675Suqs#include "out.h"
29241675Suqs#include "term.h"
30241675Suqs
31322249Sbapt#define	IS_HORIZ(cp)	((cp)->pos == TBL_CELL_HORIZ || \
32322249Sbapt			 (cp)->pos == TBL_CELL_DHORIZ)
33322249Sbapt
34241675Suqsstatic	size_t	term_tbl_len(size_t, void *);
35241675Suqsstatic	size_t	term_tbl_strlen(const char *, void *);
36322249Sbaptstatic	size_t	term_tbl_sulen(const struct roffsu *, void *);
37241675Suqsstatic	void	tbl_char(struct termp *, char, size_t);
38261344Suqsstatic	void	tbl_data(struct termp *, const struct tbl_opts *,
39322249Sbapt			const struct tbl_cell *,
40274880Sbapt			const struct tbl_dat *,
41241675Suqs			const struct roffcol *);
42274880Sbaptstatic	void	tbl_literal(struct termp *, const struct tbl_dat *,
43241675Suqs			const struct roffcol *);
44274880Sbaptstatic	void	tbl_number(struct termp *, const struct tbl_opts *,
45274880Sbapt			const struct tbl_dat *,
46241675Suqs			const struct roffcol *);
47279527Sbaptstatic	void	tbl_hrule(struct termp *, const struct tbl_span *, int);
48275432Sbaptstatic	void	tbl_word(struct termp *, const struct tbl_dat *);
49241675Suqs
50241675Suqs
51241675Suqsstatic size_t
52322249Sbaptterm_tbl_sulen(const struct roffsu *su, void *arg)
53322249Sbapt{
54324581Sbapt	int	 i;
55324581Sbapt
56324581Sbapt	i = term_hen((const struct termp *)arg, su);
57324581Sbapt	return i > 0 ? i : 0;
58322249Sbapt}
59322249Sbapt
60322249Sbaptstatic size_t
61241675Suqsterm_tbl_strlen(const char *p, void *arg)
62241675Suqs{
63294113Sbapt	return term_strlen((const struct termp *)arg, p);
64241675Suqs}
65241675Suqs
66241675Suqsstatic size_t
67241675Suqsterm_tbl_len(size_t sz, void *arg)
68241675Suqs{
69294113Sbapt	return term_len((const struct termp *)arg, sz);
70241675Suqs}
71241675Suqs
72241675Suqsvoid
73241675Suqsterm_tbl(struct termp *tp, const struct tbl_span *sp)
74241675Suqs{
75322249Sbapt	const struct tbl_cell	*cp, *cpn, *cpp;
76241675Suqs	const struct tbl_dat	*dp;
77279527Sbapt	static size_t		 offset;
78322249Sbapt	size_t			 coloff, tsz;
79322249Sbapt	int			 ic, horiz, spans, vert, more;
80322249Sbapt	char			 fc;
81241675Suqs
82241675Suqs	/* Inhibit printing of spaces: we do padding ourselves. */
83241675Suqs
84322249Sbapt	tp->flags |= TERMP_NOSPACE | TERMP_NONOSPACE;
85241675Suqs
86241675Suqs	/*
87241675Suqs	 * The first time we're invoked for a given table block,
88241675Suqs	 * calculate the table widths and decimal positions.
89241675Suqs	 */
90241675Suqs
91279527Sbapt	if (tp->tbl.cols == NULL) {
92241675Suqs		tp->tbl.len = term_tbl_len;
93241675Suqs		tp->tbl.slen = term_tbl_strlen;
94322249Sbapt		tp->tbl.sulen = term_tbl_sulen;
95241675Suqs		tp->tbl.arg = tp;
96241675Suqs
97322249Sbapt		tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin);
98241675Suqs
99322249Sbapt		/* Tables leak .ta settings to subsequent text. */
100322249Sbapt
101322249Sbapt		term_tab_set(tp, NULL);
102322249Sbapt		coloff = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ||
103322249Sbapt		    sp->opts->lvert;
104322249Sbapt		for (ic = 0; ic < sp->opts->cols; ic++) {
105322249Sbapt			coloff += tp->tbl.cols[ic].width;
106322249Sbapt			term_tab_iset(coloff);
107322249Sbapt			coloff += tp->tbl.cols[ic].spacing;
108322249Sbapt		}
109322249Sbapt
110279527Sbapt		/* Center the table as a whole. */
111241675Suqs
112322249Sbapt		offset = tp->tcol->offset;
113279527Sbapt		if (sp->opts->opts & TBL_OPT_CENTRE) {
114279527Sbapt			tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)
115279527Sbapt			    ? 2 : !!sp->opts->lvert + !!sp->opts->rvert;
116322249Sbapt			for (ic = 0; ic + 1 < sp->opts->cols; ic++)
117322249Sbapt				tsz += tp->tbl.cols[ic].width +
118322249Sbapt				    tp->tbl.cols[ic].spacing;
119322249Sbapt			if (sp->opts->cols)
120322249Sbapt				tsz += tp->tbl.cols[sp->opts->cols - 1].width;
121322249Sbapt			if (offset + tsz > tp->tcol->rmargin)
122279527Sbapt				tsz -= 1;
123322249Sbapt			tp->tcol->offset = offset + tp->tcol->rmargin > tsz ?
124322249Sbapt			    (offset + tp->tcol->rmargin - tsz) / 2 : 0;
125279527Sbapt		}
126279527Sbapt
127279527Sbapt		/* Horizontal frame at the start of boxed tables. */
128279527Sbapt
129279527Sbapt		if (sp->opts->opts & TBL_OPT_DBOX)
130322249Sbapt			tbl_hrule(tp, sp, 3);
131322249Sbapt		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX))
132279527Sbapt			tbl_hrule(tp, sp, 2);
133241675Suqs	}
134241675Suqs
135322249Sbapt	/* Set up the columns. */
136241675Suqs
137322249Sbapt	tp->flags |= TERMP_MULTICOL;
138322249Sbapt	horiz = 0;
139322249Sbapt	switch (sp->pos) {
140322249Sbapt	case TBL_SPAN_HORIZ:
141322249Sbapt	case TBL_SPAN_DHORIZ:
142322249Sbapt		horiz = 1;
143322249Sbapt		term_setcol(tp, 1);
144322249Sbapt		break;
145322249Sbapt	case TBL_SPAN_DATA:
146322249Sbapt		term_setcol(tp, sp->opts->cols + 2);
147322249Sbapt		coloff = tp->tcol->offset;
148241675Suqs
149322249Sbapt		/* Set up a column for a left vertical frame. */
150279527Sbapt
151322249Sbapt		if (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ||
152322249Sbapt		    sp->opts->lvert)
153322249Sbapt			coloff++;
154322249Sbapt		tp->tcol->rmargin = coloff;
155241675Suqs
156322249Sbapt		/* Set up the data columns. */
157322249Sbapt
158241675Suqs		dp = sp->first;
159241675Suqs		spans = 0;
160279527Sbapt		for (ic = 0; ic < sp->opts->cols; ic++) {
161322249Sbapt			if (spans == 0) {
162322249Sbapt				tp->tcol++;
163322249Sbapt				tp->tcol->offset = coloff;
164322249Sbapt			}
165322249Sbapt			coloff += tp->tbl.cols[ic].width;
166322249Sbapt			tp->tcol->rmargin = coloff;
167322249Sbapt			if (ic + 1 < sp->opts->cols)
168322249Sbapt				coloff += tp->tbl.cols[ic].spacing;
169322249Sbapt			if (spans) {
170322249Sbapt				spans--;
171322249Sbapt				continue;
172322249Sbapt			}
173322249Sbapt			if (dp == NULL)
174322249Sbapt				continue;
175322249Sbapt			spans = dp->spans;
176322249Sbapt			if (ic || sp->layout->first->pos != TBL_CELL_SPAN)
177322249Sbapt				dp = dp->next;
178322249Sbapt		}
179261344Suqs
180322249Sbapt		/* Set up a column for a right vertical frame. */
181241675Suqs
182322249Sbapt		tp->tcol++;
183322249Sbapt		tp->tcol->offset = coloff + 1;
184322249Sbapt		tp->tcol->rmargin = tp->maxrmargin;
185241675Suqs
186322249Sbapt		/* Spans may have reduced the number of columns. */
187241675Suqs
188322249Sbapt		tp->lasttcol = tp->tcol - tp->tcols;
189322249Sbapt
190322249Sbapt		/* Fill the buffers for all data columns. */
191322249Sbapt
192322249Sbapt		tp->tcol = tp->tcols;
193322249Sbapt		cp = cpn = sp->layout->first;
194322249Sbapt		dp = sp->first;
195322249Sbapt		spans = 0;
196322249Sbapt		for (ic = 0; ic < sp->opts->cols; ic++) {
197322249Sbapt			if (cpn != NULL) {
198322249Sbapt				cp = cpn;
199322249Sbapt				cpn = cpn->next;
200322249Sbapt			}
201322249Sbapt			if (spans) {
202322249Sbapt				spans--;
203322249Sbapt				continue;
204322249Sbapt			}
205322249Sbapt			tp->tcol++;
206322249Sbapt			tp->col = 0;
207322249Sbapt			tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic);
208322249Sbapt			if (dp == NULL)
209322249Sbapt				continue;
210322249Sbapt			spans = dp->spans;
211322249Sbapt			if (cp->pos != TBL_CELL_SPAN)
212322249Sbapt				dp = dp->next;
213322249Sbapt		}
214322249Sbapt		break;
215322249Sbapt	}
216322249Sbapt
217322249Sbapt	do {
218322249Sbapt		/* Print the vertical frame at the start of each row. */
219322249Sbapt
220322249Sbapt		tp->tcol = tp->tcols;
221322249Sbapt		fc = '\0';
222322249Sbapt		if (sp->layout->vert ||
223322249Sbapt		    (sp->next != NULL && sp->next->layout->vert &&
224322249Sbapt		     sp->next->pos == TBL_SPAN_DATA) ||
225322249Sbapt		    (sp->prev != NULL && sp->prev->layout->vert &&
226322249Sbapt		     (horiz || (IS_HORIZ(sp->layout->first) &&
227322249Sbapt		       !IS_HORIZ(sp->prev->layout->first)))) ||
228322249Sbapt		    sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX))
229322249Sbapt			fc = horiz || IS_HORIZ(sp->layout->first) ? '+' : '|';
230322249Sbapt		else if (horiz && sp->opts->lvert)
231322249Sbapt			fc = '-';
232322249Sbapt		if (fc != '\0') {
233322249Sbapt			(*tp->advance)(tp, tp->tcols->offset);
234322249Sbapt			(*tp->letter)(tp, fc);
235322249Sbapt			tp->viscol = tp->tcol->offset + 1;
236322249Sbapt		}
237322249Sbapt
238322249Sbapt		/* Print the data cells. */
239322249Sbapt
240322249Sbapt		more = 0;
241322249Sbapt		if (horiz) {
242322249Sbapt			tbl_hrule(tp, sp, 0);
243322249Sbapt			term_flushln(tp);
244322249Sbapt		} else {
245322249Sbapt			cp = sp->layout->first;
246322249Sbapt			cpn = sp->next == NULL ? NULL :
247322249Sbapt			    sp->next->layout->first;
248322249Sbapt			cpp = sp->prev == NULL ? NULL :
249322249Sbapt			    sp->prev->layout->first;
250322249Sbapt			dp = sp->first;
251322249Sbapt			spans = 0;
252322249Sbapt			for (ic = 0; ic < sp->opts->cols; ic++) {
253322249Sbapt
254322249Sbapt				/*
255322249Sbapt				 * Figure out whether to print a
256322249Sbapt				 * vertical line after this cell
257322249Sbapt				 * and advance to next layout cell.
258322249Sbapt				 */
259322249Sbapt
260322249Sbapt				if (cp != NULL) {
261322249Sbapt					vert = cp->vert;
262322249Sbapt					switch (cp->pos) {
263322249Sbapt					case TBL_CELL_HORIZ:
264322249Sbapt						fc = '-';
265322249Sbapt						break;
266322249Sbapt					case TBL_CELL_DHORIZ:
267322249Sbapt						fc = '=';
268322249Sbapt						break;
269322249Sbapt					default:
270322249Sbapt						fc = ' ';
271322249Sbapt						break;
272322249Sbapt					}
273322249Sbapt				} else {
274322249Sbapt					vert = 0;
275322249Sbapt					fc = ' ';
276322249Sbapt				}
277322249Sbapt				if (cpp != NULL) {
278322249Sbapt					if (vert == 0 &&
279322249Sbapt					    cp != NULL &&
280322249Sbapt					    ((IS_HORIZ(cp) &&
281322249Sbapt					      !IS_HORIZ(cpp)) ||
282322249Sbapt					     (cp->next != NULL &&
283322249Sbapt					      cpp->next != NULL &&
284322249Sbapt					      IS_HORIZ(cp->next) &&
285322249Sbapt					      !IS_HORIZ(cpp->next))))
286322249Sbapt						vert = cpp->vert;
287322249Sbapt					cpp = cpp->next;
288322249Sbapt				}
289322249Sbapt				if (vert == 0 &&
290322249Sbapt				    sp->opts->opts & TBL_OPT_ALLBOX)
291322249Sbapt					vert = 1;
292322249Sbapt				if (cpn != NULL) {
293322249Sbapt					if (vert == 0)
294322249Sbapt						vert = cpn->vert;
295322249Sbapt					cpn = cpn->next;
296322249Sbapt				}
297322249Sbapt				if (cp != NULL)
298322249Sbapt					cp = cp->next;
299322249Sbapt
300322249Sbapt				/*
301322249Sbapt				 * Skip later cells in a span,
302322249Sbapt				 * figure out whether to start a span,
303322249Sbapt				 * and advance to next data cell.
304322249Sbapt				 */
305322249Sbapt
306322249Sbapt				if (spans) {
307322249Sbapt					spans--;
308322249Sbapt					continue;
309322249Sbapt				}
310279527Sbapt				if (dp != NULL) {
311279527Sbapt					spans = dp->spans;
312322249Sbapt					if (ic || sp->layout->first->pos
313322249Sbapt					    != TBL_CELL_SPAN)
314322249Sbapt						dp = dp->next;
315279527Sbapt				}
316241675Suqs
317322249Sbapt				/*
318322249Sbapt				 * Print one line of text in the cell
319322249Sbapt				 * and remember whether there is more.
320322249Sbapt				 */
321241675Suqs
322322249Sbapt				tp->tcol++;
323322249Sbapt				if (tp->tcol->col < tp->tcol->lastcol)
324322249Sbapt					term_flushln(tp);
325322249Sbapt				if (tp->tcol->col < tp->tcol->lastcol)
326322249Sbapt					more = 1;
327279527Sbapt
328322249Sbapt				/*
329322249Sbapt				 * Vertical frames between data cells,
330322249Sbapt				 * but not after the last column.
331322249Sbapt				 */
332322249Sbapt
333322249Sbapt				if (fc == ' ' && ((vert == 0 &&
334322249Sbapt				     (cp == NULL || !IS_HORIZ(cp))) ||
335322249Sbapt				    tp->tcol + 1 == tp->tcols + tp->lasttcol))
336322249Sbapt					continue;
337322249Sbapt
338322249Sbapt				if (tp->viscol < tp->tcol->rmargin) {
339322249Sbapt					(*tp->advance)(tp, tp->tcol->rmargin
340322249Sbapt					   - tp->viscol);
341322249Sbapt					tp->viscol = tp->tcol->rmargin;
342322249Sbapt				}
343322249Sbapt				while (tp->viscol < tp->tcol->rmargin +
344322249Sbapt				    tp->tbl.cols[ic].spacing / 2) {
345322249Sbapt					(*tp->letter)(tp, fc);
346322249Sbapt					tp->viscol++;
347322249Sbapt				}
348322249Sbapt
349322249Sbapt				if (tp->tcol + 1 == tp->tcols + tp->lasttcol)
350322249Sbapt					continue;
351322249Sbapt
352322249Sbapt				if (fc == ' ' && cp != NULL) {
353322249Sbapt					switch (cp->pos) {
354322249Sbapt					case TBL_CELL_HORIZ:
355322249Sbapt						fc = '-';
356322249Sbapt						break;
357322249Sbapt					case TBL_CELL_DHORIZ:
358322249Sbapt						fc = '=';
359322249Sbapt						break;
360322249Sbapt					default:
361322249Sbapt						break;
362322249Sbapt					}
363322249Sbapt				}
364322249Sbapt				if (tp->tbl.cols[ic].spacing) {
365322249Sbapt					(*tp->letter)(tp, fc == ' ' ? '|' :
366322249Sbapt					    vert ? '+' : fc);
367322249Sbapt					tp->viscol++;
368322249Sbapt				}
369322249Sbapt
370322249Sbapt				if (fc != ' ') {
371322249Sbapt					if (cp != NULL &&
372322249Sbapt					    cp->pos == TBL_CELL_HORIZ)
373322249Sbapt						fc = '-';
374322249Sbapt					else if (cp != NULL &&
375322249Sbapt					    cp->pos == TBL_CELL_DHORIZ)
376322249Sbapt						fc = '=';
377322249Sbapt					else
378322249Sbapt						fc = ' ';
379322249Sbapt				}
380322249Sbapt				if (tp->tbl.cols[ic].spacing > 2 &&
381322249Sbapt				    (vert > 1 || fc != ' ')) {
382322249Sbapt					(*tp->letter)(tp, fc == ' ' ? '|' :
383322249Sbapt					    vert > 1 ? '+' : fc);
384322249Sbapt					tp->viscol++;
385322249Sbapt				}
386322249Sbapt			}
387241675Suqs		}
388241675Suqs
389322249Sbapt		/* Print the vertical frame at the end of each row. */
390241675Suqs
391322249Sbapt		fc = '\0';
392322249Sbapt		if ((sp->layout->last->vert &&
393322249Sbapt		     sp->layout->last->col + 1 == sp->opts->cols) ||
394322249Sbapt		    (sp->next != NULL &&
395322249Sbapt		     sp->next->layout->last->vert &&
396322249Sbapt		     sp->next->layout->last->col + 1 == sp->opts->cols) ||
397322249Sbapt		    (sp->prev != NULL &&
398322249Sbapt		     sp->prev->layout->last->vert &&
399322249Sbapt		     sp->prev->layout->last->col + 1 == sp->opts->cols &&
400322249Sbapt		     (horiz || (IS_HORIZ(sp->layout->last) &&
401322249Sbapt		      !IS_HORIZ(sp->prev->layout->last)))) ||
402322249Sbapt		    (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)))
403322249Sbapt			fc = horiz || IS_HORIZ(sp->layout->last) ? '+' : '|';
404322249Sbapt		else if (horiz && sp->opts->rvert)
405322249Sbapt			fc = '-';
406322249Sbapt		if (fc != '\0') {
407322249Sbapt			if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 ||
408322249Sbapt			    sp->layout->last->col + 1 < sp->opts->cols)) {
409322249Sbapt				tp->tcol++;
410322249Sbapt				(*tp->advance)(tp,
411322249Sbapt				    tp->tcol->offset > tp->viscol ?
412322249Sbapt				    tp->tcol->offset - tp->viscol : 1);
413322249Sbapt			}
414322249Sbapt			(*tp->letter)(tp, fc);
415322249Sbapt		}
416322249Sbapt		(*tp->endline)(tp);
417322249Sbapt		tp->viscol = 0;
418322249Sbapt	} while (more);
419241675Suqs
420241675Suqs	/*
421322249Sbapt	 * Clean up after this row.  If it is the last line
422322249Sbapt	 * of the table, print the box line and clean up
423322249Sbapt	 * column data; otherwise, print the allbox line.
424241675Suqs	 */
425241675Suqs
426322249Sbapt	term_setcol(tp, 1);
427322249Sbapt	tp->flags &= ~TERMP_MULTICOL;
428322249Sbapt	tp->tcol->rmargin = tp->maxrmargin;
429279527Sbapt	if (sp->next == NULL) {
430279527Sbapt		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) {
431322249Sbapt			tbl_hrule(tp, sp, 2);
432261344Suqs			tp->skipvsp = 1;
433261344Suqs		}
434279527Sbapt		if (sp->opts->opts & TBL_OPT_DBOX) {
435322249Sbapt			tbl_hrule(tp, sp, 3);
436261344Suqs			tp->skipvsp = 2;
437261344Suqs		}
438241675Suqs		assert(tp->tbl.cols);
439241675Suqs		free(tp->tbl.cols);
440241675Suqs		tp->tbl.cols = NULL;
441322249Sbapt		tp->tcol->offset = offset;
442322249Sbapt	} else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX &&
443322249Sbapt	    (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA ||
444322249Sbapt	     sp->next->next != NULL))
445322249Sbapt		tbl_hrule(tp, sp, 1);
446241675Suqs
447241675Suqs	tp->flags &= ~TERMP_NONOSPACE;
448241675Suqs}
449241675Suqs
450241675Suqs/*
451279527Sbapt * Kinds of horizontal rulers:
452279527Sbapt * 0: inside the table (single or double line with crossings)
453322249Sbapt * 1: inside the table (single or double line with crossings and ends)
454322249Sbapt * 2: inner frame (single line with crossings and ends)
455322249Sbapt * 3: outer frame (single line without crossings with ends)
456241675Suqs */
457241675Suqsstatic void
458279527Sbapttbl_hrule(struct termp *tp, const struct tbl_span *sp, int kind)
459241675Suqs{
460322249Sbapt	const struct tbl_cell *cp, *cpn, *cpp;
461322249Sbapt	const struct roffcol *col;
462279527Sbapt	int	 vert;
463279527Sbapt	char	 line, cross;
464241675Suqs
465322249Sbapt	line = (kind < 2 && TBL_SPAN_DHORIZ == sp->pos) ? '=' : '-';
466322249Sbapt	cross = (kind < 3) ? '+' : '-';
467241675Suqs
468279527Sbapt	if (kind)
469279527Sbapt		term_word(tp, "+");
470322249Sbapt	cp = sp->layout->first;
471322249Sbapt	cpp = kind || sp->prev == NULL ? NULL : sp->prev->layout->first;
472322249Sbapt	if (cpp == cp)
473322249Sbapt		cpp = NULL;
474322249Sbapt	cpn = kind > 1 || sp->next == NULL ? NULL : sp->next->layout->first;
475322249Sbapt	if (cpn == cp)
476322249Sbapt		cpn = NULL;
477279527Sbapt	for (;;) {
478322249Sbapt		col = tp->tbl.cols + cp->col;
479322249Sbapt		tbl_char(tp, line, col->width + col->spacing / 2);
480322249Sbapt		vert = cp->vert;
481322249Sbapt		if ((cp = cp->next) == NULL)
482279527Sbapt			 break;
483322249Sbapt		if (cpp != NULL) {
484322249Sbapt			if (vert < cpp->vert)
485322249Sbapt				vert = cpp->vert;
486322249Sbapt			cpp = cpp->next;
487279527Sbapt		}
488322249Sbapt		if (cpn != NULL) {
489322249Sbapt			if (vert < cpn->vert)
490322249Sbapt				vert = cpn->vert;
491322249Sbapt			cpn = cpn->next;
492322249Sbapt		}
493322249Sbapt		if (sp->opts->opts & TBL_OPT_ALLBOX && !vert)
494322249Sbapt			vert = 1;
495322249Sbapt		if (col->spacing)
496322249Sbapt			tbl_char(tp, vert ? cross : line, 1);
497322249Sbapt		if (col->spacing > 2)
498322249Sbapt			tbl_char(tp, vert > 1 ? cross : line, 1);
499322249Sbapt		if (col->spacing > 4)
500322249Sbapt			tbl_char(tp, line, (col->spacing - 3) / 2);
501261344Suqs	}
502279527Sbapt	if (kind) {
503279527Sbapt		term_word(tp, "+");
504279527Sbapt		term_flushln(tp);
505261344Suqs	}
506241675Suqs}
507241675Suqs
508241675Suqsstatic void
509261344Suqstbl_data(struct termp *tp, const struct tbl_opts *opts,
510322249Sbapt    const struct tbl_cell *cp, const struct tbl_dat *dp,
511322249Sbapt    const struct roffcol *col)
512241675Suqs{
513322249Sbapt	switch (cp->pos) {
514322249Sbapt	case TBL_CELL_HORIZ:
515322249Sbapt		tbl_char(tp, '-', col->width);
516241675Suqs		return;
517322249Sbapt	case TBL_CELL_DHORIZ:
518322249Sbapt		tbl_char(tp, '=', col->width);
519322249Sbapt		return;
520322249Sbapt	default:
521322249Sbapt		break;
522241675Suqs	}
523241675Suqs
524322249Sbapt	if (dp == NULL)
525322249Sbapt		return;
526322249Sbapt
527241675Suqs	switch (dp->pos) {
528274880Sbapt	case TBL_DATA_NONE:
529241675Suqs		return;
530274880Sbapt	case TBL_DATA_HORIZ:
531274880Sbapt	case TBL_DATA_NHORIZ:
532241675Suqs		tbl_char(tp, '-', col->width);
533241675Suqs		return;
534274880Sbapt	case TBL_DATA_NDHORIZ:
535274880Sbapt	case TBL_DATA_DHORIZ:
536241675Suqs		tbl_char(tp, '=', col->width);
537241675Suqs		return;
538241675Suqs	default:
539241675Suqs		break;
540241675Suqs	}
541274880Sbapt
542322249Sbapt	switch (cp->pos) {
543274880Sbapt	case TBL_CELL_LONG:
544274880Sbapt	case TBL_CELL_CENTRE:
545274880Sbapt	case TBL_CELL_LEFT:
546274880Sbapt	case TBL_CELL_RIGHT:
547241675Suqs		tbl_literal(tp, dp, col);
548241675Suqs		break;
549274880Sbapt	case TBL_CELL_NUMBER:
550261344Suqs		tbl_number(tp, opts, dp, col);
551241675Suqs		break;
552274880Sbapt	case TBL_CELL_DOWN:
553322249Sbapt	case TBL_CELL_SPAN:
554241675Suqs		break;
555241675Suqs	default:
556241675Suqs		abort();
557241675Suqs	}
558241675Suqs}
559241675Suqs
560241675Suqsstatic void
561241675Suqstbl_char(struct termp *tp, char c, size_t len)
562241675Suqs{
563241675Suqs	size_t		i, sz;
564241675Suqs	char		cp[2];
565241675Suqs
566241675Suqs	cp[0] = c;
567241675Suqs	cp[1] = '\0';
568241675Suqs
569241675Suqs	sz = term_strlen(tp, cp);
570241675Suqs
571241675Suqs	for (i = 0; i < len; i += sz)
572241675Suqs		term_word(tp, cp);
573241675Suqs}
574241675Suqs
575241675Suqsstatic void
576274880Sbapttbl_literal(struct termp *tp, const struct tbl_dat *dp,
577241675Suqs		const struct roffcol *col)
578241675Suqs{
579279527Sbapt	size_t		 len, padl, padr, width;
580279527Sbapt	int		 ic, spans;
581241675Suqs
582241675Suqs	assert(dp->string);
583241675Suqs	len = term_strlen(tp, dp->string);
584261344Suqs	width = col->width;
585279527Sbapt	ic = dp->layout->col;
586279527Sbapt	spans = dp->spans;
587279527Sbapt	while (spans--)
588279527Sbapt		width += tp->tbl.cols[++ic].width + 3;
589261344Suqs
590261344Suqs	padr = width > len ? width - len : 0;
591241675Suqs	padl = 0;
592241675Suqs
593241675Suqs	switch (dp->layout->pos) {
594274880Sbapt	case TBL_CELL_LONG:
595241675Suqs		padl = term_len(tp, 1);
596241675Suqs		padr = padr > padl ? padr - padl : 0;
597241675Suqs		break;
598274880Sbapt	case TBL_CELL_CENTRE:
599241675Suqs		if (2 > padr)
600241675Suqs			break;
601241675Suqs		padl = padr / 2;
602241675Suqs		padr -= padl;
603241675Suqs		break;
604274880Sbapt	case TBL_CELL_RIGHT:
605241675Suqs		padl = padr;
606241675Suqs		padr = 0;
607241675Suqs		break;
608241675Suqs	default:
609241675Suqs		break;
610241675Suqs	}
611241675Suqs
612241675Suqs	tbl_char(tp, ASCII_NBRSP, padl);
613275432Sbapt	tbl_word(tp, dp);
614241675Suqs	tbl_char(tp, ASCII_NBRSP, padr);
615241675Suqs}
616241675Suqs
617241675Suqsstatic void
618261344Suqstbl_number(struct termp *tp, const struct tbl_opts *opts,
619241675Suqs		const struct tbl_dat *dp,
620241675Suqs		const struct roffcol *col)
621241675Suqs{
622241675Suqs	char		*cp;
623241675Suqs	char		 buf[2];
624241675Suqs	size_t		 sz, psz, ssz, d, padl;
625241675Suqs	int		 i;
626241675Suqs
627241675Suqs	/*
628241675Suqs	 * See calc_data_number().  Left-pad by taking the offset of our
629241675Suqs	 * and the maximum decimal; right-pad by the remaining amount.
630241675Suqs	 */
631241675Suqs
632241675Suqs	assert(dp->string);
633241675Suqs
634241675Suqs	sz = term_strlen(tp, dp->string);
635241675Suqs
636261344Suqs	buf[0] = opts->decimal;
637241675Suqs	buf[1] = '\0';
638241675Suqs
639241675Suqs	psz = term_strlen(tp, buf);
640241675Suqs
641279527Sbapt	if ((cp = strrchr(dp->string, opts->decimal)) != NULL) {
642241675Suqs		for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
643241675Suqs			buf[0] = dp->string[i];
644241675Suqs			ssz += term_strlen(tp, buf);
645241675Suqs		}
646241675Suqs		d = ssz + psz;
647241675Suqs	} else
648241675Suqs		d = sz + psz;
649241675Suqs
650279527Sbapt	if (col->decimal > d && col->width > sz) {
651279527Sbapt		padl = col->decimal - d;
652279527Sbapt		if (padl + sz > col->width)
653279527Sbapt			padl = col->width - sz;
654279527Sbapt		tbl_char(tp, ASCII_NBRSP, padl);
655279527Sbapt	} else
656279527Sbapt		padl = 0;
657275432Sbapt	tbl_word(tp, dp);
658241675Suqs	if (col->width > sz + padl)
659241675Suqs		tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
660241675Suqs}
661241675Suqs
662275432Sbaptstatic void
663275432Sbapttbl_word(struct termp *tp, const struct tbl_dat *dp)
664275432Sbapt{
665279527Sbapt	int		 prev_font;
666275432Sbapt
667279527Sbapt	prev_font = tp->fonti;
668275432Sbapt	if (dp->layout->flags & TBL_CELL_BOLD)
669275432Sbapt		term_fontpush(tp, TERMFONT_BOLD);
670275432Sbapt	else if (dp->layout->flags & TBL_CELL_ITALIC)
671275432Sbapt		term_fontpush(tp, TERMFONT_UNDER);
672275432Sbapt
673275432Sbapt	term_word(tp, dp->string);
674275432Sbapt
675275432Sbapt	term_fontpopq(tp, prev_font);
676275432Sbapt}
677