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