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