xref: /openbsd-src/usr.bin/mandoc/tbl_term.c (revision d1df930ffab53da22f3324c32bed7ac5709915e6)
1 /*	$OpenBSD: tbl_term.c,v 1.48 2018/08/19 23:10:16 schwarze Exp $ */
2 /*
3  * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2011-2018 Ingo Schwarze <schwarze@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #include <sys/types.h>
19 
20 #include <assert.h>
21 #include <ctype.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "mandoc.h"
27 #include "out.h"
28 #include "term.h"
29 
30 #define	IS_HORIZ(cp)	((cp)->pos == TBL_CELL_HORIZ || \
31 			 (cp)->pos == TBL_CELL_DHORIZ)
32 
33 static	size_t	term_tbl_len(size_t, void *);
34 static	size_t	term_tbl_strlen(const char *, void *);
35 static	size_t	term_tbl_sulen(const struct roffsu *, void *);
36 static	void	tbl_char(struct termp *, char, size_t);
37 static	void	tbl_data(struct termp *, const struct tbl_opts *,
38 			const struct tbl_cell *,
39 			const struct tbl_dat *,
40 			const struct roffcol *);
41 static	void	tbl_literal(struct termp *, const struct tbl_dat *,
42 			const struct roffcol *);
43 static	void	tbl_number(struct termp *, const struct tbl_opts *,
44 			const struct tbl_dat *,
45 			const struct roffcol *);
46 static	void	tbl_hrule(struct termp *, const struct tbl_span *, int);
47 static	void	tbl_word(struct termp *, const struct tbl_dat *);
48 
49 
50 static size_t
51 term_tbl_sulen(const struct roffsu *su, void *arg)
52 {
53 	int	 i;
54 
55 	i = term_hen((const struct termp *)arg, su);
56 	return i > 0 ? i : 0;
57 }
58 
59 static size_t
60 term_tbl_strlen(const char *p, void *arg)
61 {
62 	return term_strlen((const struct termp *)arg, p);
63 }
64 
65 static size_t
66 term_tbl_len(size_t sz, void *arg)
67 {
68 	return term_len((const struct termp *)arg, sz);
69 }
70 
71 void
72 term_tbl(struct termp *tp, const struct tbl_span *sp)
73 {
74 	const struct tbl_cell	*cp, *cpn, *cpp;
75 	const struct tbl_dat	*dp;
76 	static size_t		 offset;
77 	size_t			 coloff, tsz;
78 	int			 ic, horiz, spans, vert, more;
79 	char			 fc;
80 
81 	/* Inhibit printing of spaces: we do padding ourselves. */
82 
83 	tp->flags |= TERMP_NOSPACE | TERMP_NONOSPACE;
84 
85 	/*
86 	 * The first time we're invoked for a given table block,
87 	 * calculate the table widths and decimal positions.
88 	 */
89 
90 	if (tp->tbl.cols == NULL) {
91 		tp->tbl.len = term_tbl_len;
92 		tp->tbl.slen = term_tbl_strlen;
93 		tp->tbl.sulen = term_tbl_sulen;
94 		tp->tbl.arg = tp;
95 
96 		tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin);
97 
98 		/* Tables leak .ta settings to subsequent text. */
99 
100 		term_tab_set(tp, NULL);
101 		coloff = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ||
102 		    sp->opts->lvert;
103 		for (ic = 0; ic < sp->opts->cols; ic++) {
104 			coloff += tp->tbl.cols[ic].width;
105 			term_tab_iset(coloff);
106 			coloff += tp->tbl.cols[ic].spacing;
107 		}
108 
109 		/* Center the table as a whole. */
110 
111 		offset = tp->tcol->offset;
112 		if (sp->opts->opts & TBL_OPT_CENTRE) {
113 			tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)
114 			    ? 2 : !!sp->opts->lvert + !!sp->opts->rvert;
115 			for (ic = 0; ic + 1 < sp->opts->cols; ic++)
116 				tsz += tp->tbl.cols[ic].width +
117 				    tp->tbl.cols[ic].spacing;
118 			if (sp->opts->cols)
119 				tsz += tp->tbl.cols[sp->opts->cols - 1].width;
120 			if (offset + tsz > tp->tcol->rmargin)
121 				tsz -= 1;
122 			tp->tcol->offset = offset + tp->tcol->rmargin > tsz ?
123 			    (offset + tp->tcol->rmargin - tsz) / 2 : 0;
124 		}
125 
126 		/* Horizontal frame at the start of boxed tables. */
127 
128 		if (sp->opts->opts & TBL_OPT_DBOX)
129 			tbl_hrule(tp, sp, 3);
130 		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX))
131 			tbl_hrule(tp, sp, 2);
132 	}
133 
134 	/* Set up the columns. */
135 
136 	tp->flags |= TERMP_MULTICOL;
137 	horiz = 0;
138 	switch (sp->pos) {
139 	case TBL_SPAN_HORIZ:
140 	case TBL_SPAN_DHORIZ:
141 		horiz = 1;
142 		term_setcol(tp, 1);
143 		break;
144 	case TBL_SPAN_DATA:
145 		term_setcol(tp, sp->opts->cols + 2);
146 		coloff = tp->tcol->offset;
147 
148 		/* Set up a column for a left vertical frame. */
149 
150 		if (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ||
151 		    sp->opts->lvert)
152 			coloff++;
153 		tp->tcol->rmargin = coloff;
154 
155 		/* Set up the data columns. */
156 
157 		dp = sp->first;
158 		spans = 0;
159 		for (ic = 0; ic < sp->opts->cols; ic++) {
160 			if (spans == 0) {
161 				tp->tcol++;
162 				tp->tcol->offset = coloff;
163 			}
164 			coloff += tp->tbl.cols[ic].width;
165 			tp->tcol->rmargin = coloff;
166 			if (ic + 1 < sp->opts->cols)
167 				coloff += tp->tbl.cols[ic].spacing;
168 			if (spans) {
169 				spans--;
170 				continue;
171 			}
172 			if (dp == NULL)
173 				continue;
174 			spans = dp->spans;
175 			if (ic || sp->layout->first->pos != TBL_CELL_SPAN)
176 				dp = dp->next;
177 		}
178 
179 		/* Set up a column for a right vertical frame. */
180 
181 		tp->tcol++;
182 		tp->tcol->offset = coloff + 1;
183 		tp->tcol->rmargin = tp->maxrmargin;
184 
185 		/* Spans may have reduced the number of columns. */
186 
187 		tp->lasttcol = tp->tcol - tp->tcols;
188 
189 		/* Fill the buffers for all data columns. */
190 
191 		tp->tcol = tp->tcols;
192 		cp = cpn = sp->layout->first;
193 		dp = sp->first;
194 		spans = 0;
195 		for (ic = 0; ic < sp->opts->cols; ic++) {
196 			if (cpn != NULL) {
197 				cp = cpn;
198 				cpn = cpn->next;
199 			}
200 			if (spans) {
201 				spans--;
202 				continue;
203 			}
204 			tp->tcol++;
205 			tp->col = 0;
206 			tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic);
207 			if (dp == NULL)
208 				continue;
209 			spans = dp->spans;
210 			if (cp->pos != TBL_CELL_SPAN)
211 				dp = dp->next;
212 		}
213 		break;
214 	}
215 
216 	do {
217 		/* Print the vertical frame at the start of each row. */
218 
219 		tp->tcol = tp->tcols;
220 		fc = '\0';
221 		if (sp->layout->vert ||
222 		    (sp->next != NULL && sp->next->layout->vert &&
223 		     sp->next->pos == TBL_SPAN_DATA) ||
224 		    (sp->prev != NULL && sp->prev->layout->vert &&
225 		     (horiz || (IS_HORIZ(sp->layout->first) &&
226 		       !IS_HORIZ(sp->prev->layout->first)))) ||
227 		    sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX))
228 			fc = horiz || IS_HORIZ(sp->layout->first) ? '+' : '|';
229 		else if (horiz && sp->opts->lvert)
230 			fc = '-';
231 		if (fc != '\0') {
232 			(*tp->advance)(tp, tp->tcols->offset);
233 			(*tp->letter)(tp, fc);
234 			tp->viscol = tp->tcol->offset + 1;
235 		}
236 
237 		/* Print the data cells. */
238 
239 		more = 0;
240 		if (horiz) {
241 			tbl_hrule(tp, sp, 0);
242 			term_flushln(tp);
243 		} else {
244 			cp = sp->layout->first;
245 			cpn = sp->next == NULL ? NULL :
246 			    sp->next->layout->first;
247 			cpp = sp->prev == NULL ? NULL :
248 			    sp->prev->layout->first;
249 			dp = sp->first;
250 			spans = 0;
251 			for (ic = 0; ic < sp->opts->cols; ic++) {
252 
253 				/*
254 				 * Figure out whether to print a
255 				 * vertical line after this cell
256 				 * and advance to next layout cell.
257 				 */
258 
259 				if (cp != NULL) {
260 					vert = cp->vert;
261 					switch (cp->pos) {
262 					case TBL_CELL_HORIZ:
263 						fc = '-';
264 						break;
265 					case TBL_CELL_DHORIZ:
266 						fc = '=';
267 						break;
268 					default:
269 						fc = ' ';
270 						break;
271 					}
272 				} else {
273 					vert = 0;
274 					fc = ' ';
275 				}
276 				if (cpp != NULL) {
277 					if (vert == 0 &&
278 					    cp != NULL &&
279 					    ((IS_HORIZ(cp) &&
280 					      !IS_HORIZ(cpp)) ||
281 					     (cp->next != NULL &&
282 					      cpp->next != NULL &&
283 					      IS_HORIZ(cp->next) &&
284 					      !IS_HORIZ(cpp->next))))
285 						vert = cpp->vert;
286 					cpp = cpp->next;
287 				}
288 				if (vert == 0 &&
289 				    sp->opts->opts & TBL_OPT_ALLBOX)
290 					vert = 1;
291 				if (cpn != NULL) {
292 					if (vert == 0)
293 						vert = cpn->vert;
294 					cpn = cpn->next;
295 				}
296 				if (cp != NULL)
297 					cp = cp->next;
298 
299 				/*
300 				 * Skip later cells in a span,
301 				 * figure out whether to start a span,
302 				 * and advance to next data cell.
303 				 */
304 
305 				if (spans) {
306 					spans--;
307 					continue;
308 				}
309 				if (dp != NULL) {
310 					spans = dp->spans;
311 					if (ic || sp->layout->first->pos
312 					    != TBL_CELL_SPAN)
313 						dp = dp->next;
314 				}
315 
316 				/*
317 				 * Print one line of text in the cell
318 				 * and remember whether there is more.
319 				 */
320 
321 				tp->tcol++;
322 				if (tp->tcol->col < tp->tcol->lastcol)
323 					term_flushln(tp);
324 				if (tp->tcol->col < tp->tcol->lastcol)
325 					more = 1;
326 
327 				/*
328 				 * Vertical frames between data cells,
329 				 * but not after the last column.
330 				 */
331 
332 				if (fc == ' ' && ((vert == 0 &&
333 				     (cp == NULL || !IS_HORIZ(cp))) ||
334 				    tp->tcol + 1 == tp->tcols + tp->lasttcol))
335 					continue;
336 
337 				if (tp->viscol < tp->tcol->rmargin) {
338 					(*tp->advance)(tp, tp->tcol->rmargin
339 					   - tp->viscol);
340 					tp->viscol = tp->tcol->rmargin;
341 				}
342 				while (tp->viscol < tp->tcol->rmargin +
343 				    tp->tbl.cols[ic].spacing / 2) {
344 					(*tp->letter)(tp, fc);
345 					tp->viscol++;
346 				}
347 
348 				if (tp->tcol + 1 == tp->tcols + tp->lasttcol)
349 					continue;
350 
351 				if (fc == ' ' && cp != NULL) {
352 					switch (cp->pos) {
353 					case TBL_CELL_HORIZ:
354 						fc = '-';
355 						break;
356 					case TBL_CELL_DHORIZ:
357 						fc = '=';
358 						break;
359 					default:
360 						break;
361 					}
362 				}
363 				if (tp->tbl.cols[ic].spacing) {
364 					(*tp->letter)(tp, fc == ' ' ? '|' :
365 					    vert ? '+' : fc);
366 					tp->viscol++;
367 				}
368 
369 				if (fc != ' ') {
370 					if (cp != NULL &&
371 					    cp->pos == TBL_CELL_HORIZ)
372 						fc = '-';
373 					else if (cp != NULL &&
374 					    cp->pos == TBL_CELL_DHORIZ)
375 						fc = '=';
376 					else
377 						fc = ' ';
378 				}
379 				if (tp->tbl.cols[ic].spacing > 2 &&
380 				    (vert > 1 || fc != ' ')) {
381 					(*tp->letter)(tp, fc == ' ' ? '|' :
382 					    vert > 1 ? '+' : fc);
383 					tp->viscol++;
384 				}
385 			}
386 		}
387 
388 		/* Print the vertical frame at the end of each row. */
389 
390 		fc = '\0';
391 		if ((sp->layout->last->vert &&
392 		     sp->layout->last->col + 1 == sp->opts->cols) ||
393 		    (sp->next != NULL &&
394 		     sp->next->layout->last->vert &&
395 		     sp->next->layout->last->col + 1 == sp->opts->cols) ||
396 		    (sp->prev != NULL &&
397 		     sp->prev->layout->last->vert &&
398 		     sp->prev->layout->last->col + 1 == sp->opts->cols &&
399 		     (horiz || (IS_HORIZ(sp->layout->last) &&
400 		      !IS_HORIZ(sp->prev->layout->last)))) ||
401 		    (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)))
402 			fc = horiz || IS_HORIZ(sp->layout->last) ? '+' : '|';
403 		else if (horiz && sp->opts->rvert)
404 			fc = '-';
405 		if (fc != '\0') {
406 			if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 ||
407 			    sp->layout->last->col + 1 < sp->opts->cols)) {
408 				tp->tcol++;
409 				(*tp->advance)(tp,
410 				    tp->tcol->offset > tp->viscol ?
411 				    tp->tcol->offset - tp->viscol : 1);
412 			}
413 			(*tp->letter)(tp, fc);
414 		}
415 		(*tp->endline)(tp);
416 		tp->viscol = 0;
417 	} while (more);
418 
419 	/*
420 	 * Clean up after this row.  If it is the last line
421 	 * of the table, print the box line and clean up
422 	 * column data; otherwise, print the allbox line.
423 	 */
424 
425 	term_setcol(tp, 1);
426 	tp->flags &= ~TERMP_MULTICOL;
427 	tp->tcol->rmargin = tp->maxrmargin;
428 	if (sp->next == NULL) {
429 		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) {
430 			tbl_hrule(tp, sp, 2);
431 			tp->skipvsp = 1;
432 		}
433 		if (sp->opts->opts & TBL_OPT_DBOX) {
434 			tbl_hrule(tp, sp, 3);
435 			tp->skipvsp = 2;
436 		}
437 		assert(tp->tbl.cols);
438 		free(tp->tbl.cols);
439 		tp->tbl.cols = NULL;
440 		tp->tcol->offset = offset;
441 	} else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX &&
442 	    (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA ||
443 	     sp->next->next != NULL))
444 		tbl_hrule(tp, sp, 1);
445 
446 	tp->flags &= ~TERMP_NONOSPACE;
447 }
448 
449 /*
450  * Kinds of horizontal rulers:
451  * 0: inside the table (single or double line with crossings)
452  * 1: inside the table (single or double line with crossings and ends)
453  * 2: inner frame (single line with crossings and ends)
454  * 3: outer frame (single line without crossings with ends)
455  */
456 static void
457 tbl_hrule(struct termp *tp, const struct tbl_span *sp, int kind)
458 {
459 	const struct tbl_cell *cp, *cpn, *cpp;
460 	const struct roffcol *col;
461 	int	 vert;
462 	char	 cross, line, stdcross, stdline;
463 
464 	stdline = (kind < 2 && TBL_SPAN_DHORIZ == sp->pos) ? '=' : '-';
465 	stdcross = (kind < 3) ? '+' : '-';
466 
467 	cp = sp->layout->first;
468 	cpp = kind || sp->prev == NULL ? NULL : sp->prev->layout->first;
469 	if (cpp == cp)
470 		cpp = NULL;
471 	cpn = kind > 1 || sp->next == NULL ? NULL : sp->next->layout->first;
472 	if (cpn == cp)
473 		cpn = NULL;
474 	if (kind)
475 		term_word(tp,
476 		    cpn == NULL || cpn->pos != TBL_CELL_DOWN ? "+" : "|");
477 	for (;;) {
478 		col = tp->tbl.cols + cp->col;
479 		if (cpn == NULL || cpn->pos != TBL_CELL_DOWN) {
480 			line = stdline;
481 			cross = stdcross;
482 		} else {
483 			line = ' ';
484 			cross = (kind < 3) ? '|' : ' ';
485 		}
486 		tbl_char(tp, line, col->width + col->spacing / 2);
487 		vert = cp->vert;
488 		if ((cp = cp->next) == NULL)
489 			 break;
490 		if (cpp != NULL) {
491 			if (vert < cpp->vert)
492 				vert = cpp->vert;
493 			cpp = cpp->next;
494 		}
495 		if (cpn != NULL) {
496 			if (vert < cpn->vert)
497 				vert = cpn->vert;
498 			cpn = cpn->next;
499 		}
500 		if (cpn == NULL || cpn->pos != TBL_CELL_DOWN) {
501 			line = stdline;
502 			cross = stdcross;
503 		} else
504 			line = ' ';
505 		if (sp->opts->opts & TBL_OPT_ALLBOX && !vert)
506 			vert = 1;
507 		if (col->spacing)
508 			tbl_char(tp, vert ? cross : line, 1);
509 		if (col->spacing > 2)
510 			tbl_char(tp, vert > 1 ? cross : line, 1);
511 		if (col->spacing > 4)
512 			tbl_char(tp, line, (col->spacing - 3) / 2);
513 	}
514 	if (kind) {
515 		term_word(tp,
516 		    cpn == NULL || cpn->pos != TBL_CELL_DOWN ? "+" : "|");
517 		term_flushln(tp);
518 	}
519 }
520 
521 static void
522 tbl_data(struct termp *tp, const struct tbl_opts *opts,
523     const struct tbl_cell *cp, const struct tbl_dat *dp,
524     const struct roffcol *col)
525 {
526 	switch (cp->pos) {
527 	case TBL_CELL_HORIZ:
528 		tbl_char(tp, '-', col->width);
529 		return;
530 	case TBL_CELL_DHORIZ:
531 		tbl_char(tp, '=', col->width);
532 		return;
533 	default:
534 		break;
535 	}
536 
537 	if (dp == NULL)
538 		return;
539 
540 	switch (dp->pos) {
541 	case TBL_DATA_NONE:
542 		return;
543 	case TBL_DATA_HORIZ:
544 	case TBL_DATA_NHORIZ:
545 		tbl_char(tp, '-', col->width);
546 		return;
547 	case TBL_DATA_NDHORIZ:
548 	case TBL_DATA_DHORIZ:
549 		tbl_char(tp, '=', col->width);
550 		return;
551 	default:
552 		break;
553 	}
554 
555 	switch (cp->pos) {
556 	case TBL_CELL_LONG:
557 	case TBL_CELL_CENTRE:
558 	case TBL_CELL_LEFT:
559 	case TBL_CELL_RIGHT:
560 		tbl_literal(tp, dp, col);
561 		break;
562 	case TBL_CELL_NUMBER:
563 		tbl_number(tp, opts, dp, col);
564 		break;
565 	case TBL_CELL_DOWN:
566 	case TBL_CELL_SPAN:
567 		break;
568 	default:
569 		abort();
570 	}
571 }
572 
573 static void
574 tbl_char(struct termp *tp, char c, size_t len)
575 {
576 	size_t		i, sz;
577 	char		cp[2];
578 
579 	cp[0] = c;
580 	cp[1] = '\0';
581 
582 	sz = term_strlen(tp, cp);
583 
584 	for (i = 0; i < len; i += sz)
585 		term_word(tp, cp);
586 }
587 
588 static void
589 tbl_literal(struct termp *tp, const struct tbl_dat *dp,
590 		const struct roffcol *col)
591 {
592 	size_t		 len, padl, padr, width;
593 	int		 ic, spans;
594 
595 	assert(dp->string);
596 	len = term_strlen(tp, dp->string);
597 	width = col->width;
598 	ic = dp->layout->col;
599 	spans = dp->spans;
600 	while (spans--)
601 		width += tp->tbl.cols[++ic].width + 3;
602 
603 	padr = width > len ? width - len : 0;
604 	padl = 0;
605 
606 	switch (dp->layout->pos) {
607 	case TBL_CELL_LONG:
608 		padl = term_len(tp, 1);
609 		padr = padr > padl ? padr - padl : 0;
610 		break;
611 	case TBL_CELL_CENTRE:
612 		if (2 > padr)
613 			break;
614 		padl = padr / 2;
615 		padr -= padl;
616 		break;
617 	case TBL_CELL_RIGHT:
618 		padl = padr;
619 		padr = 0;
620 		break;
621 	default:
622 		break;
623 	}
624 
625 	tbl_char(tp, ASCII_NBRSP, padl);
626 	tbl_word(tp, dp);
627 	tbl_char(tp, ASCII_NBRSP, padr);
628 }
629 
630 static void
631 tbl_number(struct termp *tp, const struct tbl_opts *opts,
632 		const struct tbl_dat *dp,
633 		const struct roffcol *col)
634 {
635 	const char	*cp, *lastdigit, *lastpoint;
636 	size_t		 intsz, padl, totsz;
637 	char		 buf[2];
638 
639 	/*
640 	 * Almost the same code as in tblcalc_number():
641 	 * First find the position of the decimal point.
642 	 */
643 
644 	assert(dp->string);
645 	lastdigit = lastpoint = NULL;
646 	for (cp = dp->string; cp[0] != '\0'; cp++) {
647 		if (cp[0] == '\\' && cp[1] == '&') {
648 			lastdigit = lastpoint = cp;
649 			break;
650 		} else if (cp[0] == opts->decimal &&
651 		    (isdigit((unsigned char)cp[1]) ||
652 		     (cp > dp->string && isdigit((unsigned char)cp[-1]))))
653 			lastpoint = cp;
654 		else if (isdigit((unsigned char)cp[0]))
655 			lastdigit = cp;
656 	}
657 
658 	/* Then measure both widths. */
659 
660 	padl = 0;
661 	totsz = term_strlen(tp, dp->string);
662 	if (lastdigit != NULL) {
663 		if (lastpoint == NULL)
664 			lastpoint = lastdigit + 1;
665 		intsz = 0;
666 		buf[1] = '\0';
667 		for (cp = dp->string; cp < lastpoint; cp++) {
668 			buf[0] = cp[0];
669 			intsz += term_strlen(tp, buf);
670 		}
671 
672 		/*
673 		 * Pad left to match the decimal position,
674 		 * but avoid exceeding the total column width.
675 		 */
676 
677 		if (col->decimal > intsz && col->width > totsz) {
678 			padl = col->decimal - intsz;
679 			if (padl + totsz > col->width)
680 				padl = col->width - totsz;
681 		}
682 
683 	/* If it is not a number, simply center the string. */
684 
685 	} else if (col->width > totsz)
686 		padl = (col->width - totsz) / 2;
687 
688 	tbl_char(tp, ASCII_NBRSP, padl);
689 	tbl_word(tp, dp);
690 
691 	/* Pad right to fill the column.  */
692 
693 	if (col->width > padl + totsz)
694 		tbl_char(tp, ASCII_NBRSP, col->width - padl - totsz);
695 }
696 
697 static void
698 tbl_word(struct termp *tp, const struct tbl_dat *dp)
699 {
700 	int		 prev_font;
701 
702 	prev_font = tp->fonti;
703 	if (dp->layout->flags & TBL_CELL_BOLD)
704 		term_fontpush(tp, TERMFONT_BOLD);
705 	else if (dp->layout->flags & TBL_CELL_ITALIC)
706 		term_fontpush(tp, TERMFONT_UNDER);
707 
708 	term_word(tp, dp->string);
709 
710 	term_fontpopq(tp, prev_font);
711 }
712