File: | src/usr.bin/mandoc/tbl_term.c |
Warning: | line 717, column 10 Access to field 'pos' results in a dereference of a null pointer (loaded from variable 'cp') |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* $OpenBSD: tbl_term.c,v 1.63 2021/08/10 12:36:42 schwarze Exp $ */ | |||
2 | /* | |||
3 | * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | |||
4 | * Copyright (c) 2011-2021 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 "tbl.h" | |||
28 | #include "out.h" | |||
29 | #include "term.h" | |||
30 | ||||
31 | #define IS_HORIZ(cp)((cp)->pos == TBL_CELL_HORIZ || (cp)->pos == TBL_CELL_DHORIZ ) ((cp)->pos == TBL_CELL_HORIZ || \ | |||
32 | (cp)->pos == TBL_CELL_DHORIZ) | |||
33 | ||||
34 | ||||
35 | static size_t term_tbl_len(size_t, void *); | |||
36 | static size_t term_tbl_strlen(const char *, void *); | |||
37 | static size_t term_tbl_sulen(const struct roffsu *, void *); | |||
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_direct_border(struct termp *, int, size_t); | |||
43 | static void tbl_fill_border(struct termp *, int, size_t); | |||
44 | static void tbl_fill_char(struct termp *, char, size_t); | |||
45 | static void tbl_fill_string(struct termp *, const char *, size_t); | |||
46 | static void tbl_hrule(struct termp *, const struct tbl_span *, | |||
47 | const struct tbl_span *, const struct tbl_span *, | |||
48 | int); | |||
49 | static void tbl_literal(struct termp *, const struct tbl_dat *, | |||
50 | const struct roffcol *); | |||
51 | static void tbl_number(struct termp *, const struct tbl_opts *, | |||
52 | const struct tbl_dat *, | |||
53 | const struct roffcol *); | |||
54 | static void tbl_word(struct termp *, const struct tbl_dat *); | |||
55 | ||||
56 | ||||
57 | /* | |||
58 | * The following border-character tables are indexed | |||
59 | * by ternary (3-based) numbers, as opposed to binary or decimal. | |||
60 | * Each ternary digit describes the line width in one direction: | |||
61 | * 0 means no line, 1 single or light line, 2 double or heavy line. | |||
62 | */ | |||
63 | ||||
64 | /* Positional values of the four directions. */ | |||
65 | #define BRIGHT1 1 | |||
66 | #define BDOWN3 3 | |||
67 | #define BLEFT(3 * 3) (3 * 3) | |||
68 | #define BUP(3 * 3 * 3) (3 * 3 * 3) | |||
69 | #define BHORIZ((3 * 3) + 1) (BLEFT(3 * 3) + BRIGHT1) | |||
70 | ||||
71 | /* Code points to use for each combination of widths. */ | |||
72 | static const int borders_utf8[81] = { | |||
73 | 0x0020, 0x2576, 0x257a, /* 000 right */ | |||
74 | 0x2577, 0x250c, 0x250d, /* 001 down */ | |||
75 | 0x257b, 0x250e, 0x250f, /* 002 */ | |||
76 | 0x2574, 0x2500, 0x257c, /* 010 left */ | |||
77 | 0x2510, 0x252c, 0x252e, /* 011 left down */ | |||
78 | 0x2512, 0x2530, 0x2532, /* 012 */ | |||
79 | 0x2578, 0x257e, 0x2501, /* 020 left */ | |||
80 | 0x2511, 0x252d, 0x252f, /* 021 left down */ | |||
81 | 0x2513, 0x2531, 0x2533, /* 022 */ | |||
82 | 0x2575, 0x2514, 0x2515, /* 100 up */ | |||
83 | 0x2502, 0x251c, 0x251d, /* 101 up down */ | |||
84 | 0x257d, 0x251f, 0x2522, /* 102 */ | |||
85 | 0x2518, 0x2534, 0x2536, /* 110 up left */ | |||
86 | 0x2524, 0x253c, 0x253e, /* 111 all */ | |||
87 | 0x2527, 0x2541, 0x2546, /* 112 */ | |||
88 | 0x2519, 0x2535, 0x2537, /* 120 up left */ | |||
89 | 0x2525, 0x253d, 0x253f, /* 121 all */ | |||
90 | 0x252a, 0x2545, 0x2548, /* 122 */ | |||
91 | 0x2579, 0x2516, 0x2517, /* 200 up */ | |||
92 | 0x257f, 0x251e, 0x2521, /* 201 up down */ | |||
93 | 0x2503, 0x2520, 0x2523, /* 202 */ | |||
94 | 0x251a, 0x2538, 0x253a, /* 210 up left */ | |||
95 | 0x2526, 0x2540, 0x2544, /* 211 all */ | |||
96 | 0x2528, 0x2542, 0x254a, /* 212 */ | |||
97 | 0x251b, 0x2539, 0x253b, /* 220 up left */ | |||
98 | 0x2529, 0x2543, 0x2547, /* 221 all */ | |||
99 | 0x252b, 0x2549, 0x254b, /* 222 */ | |||
100 | }; | |||
101 | ||||
102 | /* ASCII approximations for these code points, compatible with groff. */ | |||
103 | static const int borders_ascii[81] = { | |||
104 | ' ', '-', '=', /* 000 right */ | |||
105 | '|', '+', '+', /* 001 down */ | |||
106 | '|', '+', '+', /* 002 */ | |||
107 | '-', '-', '=', /* 010 left */ | |||
108 | '+', '+', '+', /* 011 left down */ | |||
109 | '+', '+', '+', /* 012 */ | |||
110 | '=', '=', '=', /* 020 left */ | |||
111 | '+', '+', '+', /* 021 left down */ | |||
112 | '+', '+', '+', /* 022 */ | |||
113 | '|', '+', '+', /* 100 up */ | |||
114 | '|', '+', '+', /* 101 up down */ | |||
115 | '|', '+', '+', /* 102 */ | |||
116 | '+', '+', '+', /* 110 up left */ | |||
117 | '+', '+', '+', /* 111 all */ | |||
118 | '+', '+', '+', /* 112 */ | |||
119 | '+', '+', '+', /* 120 up left */ | |||
120 | '+', '+', '+', /* 121 all */ | |||
121 | '+', '+', '+', /* 122 */ | |||
122 | '|', '+', '+', /* 200 up */ | |||
123 | '|', '+', '+', /* 201 up down */ | |||
124 | '|', '+', '+', /* 202 */ | |||
125 | '+', '+', '+', /* 210 up left */ | |||
126 | '+', '+', '+', /* 211 all */ | |||
127 | '+', '+', '+', /* 212 */ | |||
128 | '+', '+', '+', /* 220 up left */ | |||
129 | '+', '+', '+', /* 221 all */ | |||
130 | '+', '+', '+', /* 222 */ | |||
131 | }; | |||
132 | ||||
133 | /* Either of the above according to the selected output encoding. */ | |||
134 | static const int *borders_locale; | |||
135 | ||||
136 | ||||
137 | static size_t | |||
138 | term_tbl_sulen(const struct roffsu *su, void *arg) | |||
139 | { | |||
140 | int i; | |||
141 | ||||
142 | i = term_hen((const struct termp *)arg, su); | |||
143 | return i > 0 ? i : 0; | |||
144 | } | |||
145 | ||||
146 | static size_t | |||
147 | term_tbl_strlen(const char *p, void *arg) | |||
148 | { | |||
149 | return term_strlen((const struct termp *)arg, p); | |||
150 | } | |||
151 | ||||
152 | static size_t | |||
153 | term_tbl_len(size_t sz, void *arg) | |||
154 | { | |||
155 | return term_len((const struct termp *)arg, sz); | |||
156 | } | |||
157 | ||||
158 | ||||
159 | void | |||
160 | term_tbl(struct termp *tp, const struct tbl_span *sp) | |||
161 | { | |||
162 | const struct tbl_cell *cp, *cpn, *cpp, *cps; | |||
163 | const struct tbl_dat *dp; | |||
164 | static size_t offset; | |||
165 | size_t save_offset; | |||
166 | size_t coloff, tsz; | |||
167 | int hspans, ic, more; | |||
168 | int dvert, fc, horiz, lhori, rhori, uvert; | |||
169 | ||||
170 | /* Inhibit printing of spaces: we do padding ourselves. */ | |||
171 | ||||
172 | tp->flags |= TERMP_NOSPACE(1 << 1) | TERMP_NONOSPACE(1 << 2); | |||
173 | save_offset = tp->tcol->offset; | |||
174 | ||||
175 | /* | |||
176 | * The first time we're invoked for a given table block, | |||
177 | * calculate the table widths and decimal positions. | |||
178 | */ | |||
179 | ||||
180 | if (tp->tbl.cols == NULL((void *)0)) { | |||
| ||||
181 | borders_locale = tp->enc == TERMENC_UTF8 ? | |||
182 | borders_utf8 : borders_ascii; | |||
183 | ||||
184 | tp->tbl.len = term_tbl_len; | |||
185 | tp->tbl.slen = term_tbl_strlen; | |||
186 | tp->tbl.sulen = term_tbl_sulen; | |||
187 | tp->tbl.arg = tp; | |||
188 | ||||
189 | tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin); | |||
190 | ||||
191 | /* Center the table as a whole. */ | |||
192 | ||||
193 | offset = tp->tcol->offset; | |||
194 | if (sp->opts->opts & TBL_OPT_CENTRE(1 << 2)) { | |||
195 | tsz = sp->opts->opts & (TBL_OPT_BOX(1 << 1) | TBL_OPT_DBOX(1 << 3)) | |||
196 | ? 2 : !!sp->opts->lvert + !!sp->opts->rvert; | |||
197 | for (ic = 0; ic + 1 < sp->opts->cols; ic++) | |||
198 | tsz += tp->tbl.cols[ic].width + | |||
199 | tp->tbl.cols[ic].spacing; | |||
200 | if (sp->opts->cols) | |||
201 | tsz += tp->tbl.cols[sp->opts->cols - 1].width; | |||
202 | if (offset + tsz > tp->tcol->rmargin) | |||
203 | tsz -= 1; | |||
204 | offset = offset + tp->tcol->rmargin > tsz ? | |||
205 | (offset + tp->tcol->rmargin - tsz) / 2 : 0; | |||
206 | tp->tcol->offset = offset; | |||
207 | } | |||
208 | ||||
209 | /* Horizontal frame at the start of boxed tables. */ | |||
210 | ||||
211 | if (tp->enc == TERMENC_ASCII && | |||
212 | sp->opts->opts & TBL_OPT_DBOX(1 << 3)) | |||
213 | tbl_hrule(tp, NULL((void *)0), sp, sp, TBL_OPT_DBOX(1 << 3)); | |||
214 | if (sp->opts->opts & (TBL_OPT_DBOX(1 << 3) | TBL_OPT_BOX(1 << 1))) | |||
215 | tbl_hrule(tp, NULL((void *)0), sp, sp, TBL_OPT_BOX(1 << 1)); | |||
216 | } | |||
217 | ||||
218 | /* Set up the columns. */ | |||
219 | ||||
220 | tp->flags |= TERMP_MULTICOL(1 << 20); | |||
221 | tp->tcol->offset = offset; | |||
222 | horiz = 0; | |||
223 | switch (sp->pos) { | |||
224 | case TBL_SPAN_HORIZ: | |||
225 | case TBL_SPAN_DHORIZ: | |||
226 | horiz = 1; | |||
227 | term_setcol(tp, 1); | |||
228 | break; | |||
229 | case TBL_SPAN_DATA: | |||
230 | term_setcol(tp, sp->opts->cols + 2); | |||
231 | coloff = tp->tcol->offset; | |||
232 | ||||
233 | /* Set up a column for a left vertical frame. */ | |||
234 | ||||
235 | if (sp->opts->opts & (TBL_OPT_BOX(1 << 1) | TBL_OPT_DBOX(1 << 3)) || | |||
236 | sp->opts->lvert) | |||
237 | coloff++; | |||
238 | tp->tcol->rmargin = coloff; | |||
239 | ||||
240 | /* Set up the data columns. */ | |||
241 | ||||
242 | dp = sp->first; | |||
243 | hspans = 0; | |||
244 | for (ic = 0; ic < sp->opts->cols; ic++) { | |||
245 | if (hspans
| |||
246 | tp->tcol++; | |||
247 | tp->tcol->offset = coloff; | |||
248 | } | |||
249 | coloff += tp->tbl.cols[ic].width; | |||
250 | tp->tcol->rmargin = coloff; | |||
251 | if (ic + 1 < sp->opts->cols) | |||
252 | coloff += tp->tbl.cols[ic].spacing; | |||
253 | if (hspans
| |||
254 | hspans--; | |||
255 | continue; | |||
256 | } | |||
257 | if (dp != NULL((void *)0) && | |||
258 | (ic || sp->layout->first->pos != TBL_CELL_SPAN)) { | |||
259 | hspans = dp->hspans; | |||
260 | dp = dp->next; | |||
261 | } | |||
262 | } | |||
263 | ||||
264 | /* Set up a column for a right vertical frame. */ | |||
265 | ||||
266 | tp->tcol++; | |||
267 | tp->tcol->offset = coloff + 1; | |||
268 | tp->tcol->rmargin = tp->maxrmargin; | |||
269 | ||||
270 | /* Spans may have reduced the number of columns. */ | |||
271 | ||||
272 | tp->lasttcol = tp->tcol - tp->tcols; | |||
273 | ||||
274 | /* Fill the buffers for all data columns. */ | |||
275 | ||||
276 | tp->tcol = tp->tcols; | |||
277 | cp = cpn = sp->layout->first; | |||
278 | dp = sp->first; | |||
279 | hspans = 0; | |||
280 | for (ic = 0; ic < sp->opts->cols; ic++) { | |||
281 | if (cpn != NULL((void *)0)) { | |||
282 | cp = cpn; | |||
283 | cpn = cpn->next; | |||
284 | } | |||
285 | if (hspans
| |||
286 | hspans--; | |||
287 | continue; | |||
288 | } | |||
289 | tp->tcol++; | |||
290 | tp->col = 0; | |||
291 | tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic); | |||
292 | if (dp != NULL((void *)0) && | |||
293 | (ic || sp->layout->first->pos != TBL_CELL_SPAN)) { | |||
294 | hspans = dp->hspans; | |||
295 | dp = dp->next; | |||
296 | } | |||
297 | } | |||
298 | break; | |||
299 | } | |||
300 | ||||
301 | do { | |||
302 | /* Print the vertical frame at the start of each row. */ | |||
303 | ||||
304 | tp->tcol = tp->tcols; | |||
305 | uvert = dvert = sp->opts->opts & TBL_OPT_DBOX(1 << 3) ? 2 : | |||
306 | sp->opts->opts & TBL_OPT_BOX(1 << 1) ? 1 : 0; | |||
307 | if (sp->pos == TBL_SPAN_DATA && uvert < sp->layout->vert) | |||
308 | uvert = dvert = sp->layout->vert; | |||
309 | if (sp->next != NULL((void *)0) && sp->next->pos == TBL_SPAN_DATA && | |||
310 | dvert < sp->next->layout->vert) | |||
311 | dvert = sp->next->layout->vert; | |||
312 | if (sp->prev != NULL((void *)0) && uvert < sp->prev->layout->vert && | |||
313 | (horiz || (IS_HORIZ(sp->layout->first)((sp->layout->first)->pos == TBL_CELL_HORIZ || (sp-> layout->first)->pos == TBL_CELL_DHORIZ) && | |||
314 | !IS_HORIZ(sp->prev->layout->first)((sp->prev->layout->first)->pos == TBL_CELL_HORIZ || (sp->prev->layout->first)->pos == TBL_CELL_DHORIZ )))) | |||
315 | uvert = sp->prev->layout->vert; | |||
316 | rhori = sp->pos == TBL_SPAN_DHORIZ || | |||
317 | (sp->first != NULL((void *)0) && sp->first->pos == TBL_DATA_DHORIZ) || | |||
318 | sp->layout->first->pos == TBL_CELL_DHORIZ ? 2 : | |||
319 | sp->pos == TBL_SPAN_HORIZ || | |||
320 | (sp->first != NULL((void *)0) && sp->first->pos == TBL_DATA_HORIZ) || | |||
321 | sp->layout->first->pos == TBL_CELL_HORIZ ? 1 : 0; | |||
322 | fc = BUP(3 * 3 * 3) * uvert + BDOWN3 * dvert + BRIGHT1 * rhori; | |||
323 | if (uvert > 0 || dvert > 0 || (horiz && sp->opts->lvert)) { | |||
324 | (*tp->advance)(tp, tp->tcols->offset); | |||
325 | tp->viscol = tp->tcol->offset; | |||
326 | tbl_direct_border(tp, fc, 1); | |||
327 | } | |||
328 | ||||
329 | /* Print the data cells. */ | |||
330 | ||||
331 | more = 0; | |||
332 | if (horiz) | |||
333 | tbl_hrule(tp, sp->prev, sp, sp->next, 0); | |||
334 | else { | |||
335 | cp = sp->layout->first; | |||
336 | cpn = sp->next == NULL((void *)0) ? NULL((void *)0) : | |||
337 | sp->next->layout->first; | |||
338 | cpp = sp->prev == NULL((void *)0) ? NULL((void *)0) : | |||
339 | sp->prev->layout->first; | |||
340 | dp = sp->first; | |||
341 | hspans = 0; | |||
342 | for (ic = 0; ic < sp->opts->cols; ic++) { | |||
343 | ||||
344 | /* | |||
345 | * Figure out whether to print a | |||
346 | * vertical line after this cell | |||
347 | * and advance to next layout cell. | |||
348 | */ | |||
349 | ||||
350 | uvert = dvert = fc = 0; | |||
351 | if (cp != NULL((void *)0)) { | |||
352 | cps = cp; | |||
353 | while (cps->next != NULL((void *)0) && | |||
354 | cps->next->pos == TBL_CELL_SPAN) | |||
355 | cps = cps->next; | |||
356 | if (sp->pos == TBL_SPAN_DATA) | |||
357 | uvert = dvert = cps->vert; | |||
358 | switch (cp->pos) { | |||
359 | case TBL_CELL_HORIZ: | |||
360 | fc = BHORIZ((3 * 3) + 1); | |||
361 | break; | |||
362 | case TBL_CELL_DHORIZ: | |||
363 | fc = BHORIZ((3 * 3) + 1) * 2; | |||
364 | break; | |||
365 | default: | |||
366 | break; | |||
367 | } | |||
368 | } | |||
369 | if (cpp != NULL((void *)0)) { | |||
370 | if (uvert < cpp->vert && | |||
371 | cp != NULL((void *)0) && | |||
372 | ((IS_HORIZ(cp)((cp)->pos == TBL_CELL_HORIZ || (cp)->pos == TBL_CELL_DHORIZ ) && | |||
373 | !IS_HORIZ(cpp)((cpp)->pos == TBL_CELL_HORIZ || (cpp)->pos == TBL_CELL_DHORIZ )) || | |||
374 | (cp->next != NULL((void *)0) && | |||
375 | cpp->next != NULL((void *)0) && | |||
376 | IS_HORIZ(cp->next)((cp->next)->pos == TBL_CELL_HORIZ || (cp->next)-> pos == TBL_CELL_DHORIZ) && | |||
377 | !IS_HORIZ(cpp->next)((cpp->next)->pos == TBL_CELL_HORIZ || (cpp->next)-> pos == TBL_CELL_DHORIZ)))) | |||
378 | uvert = cpp->vert; | |||
379 | cpp = cpp->next; | |||
380 | } | |||
381 | if (sp->opts->opts & TBL_OPT_ALLBOX(1 << 0)) { | |||
382 | if (uvert == 0) | |||
383 | uvert = 1; | |||
384 | if (dvert == 0) | |||
385 | dvert = 1; | |||
386 | } | |||
387 | if (cpn != NULL((void *)0)) { | |||
388 | if (dvert == 0 || | |||
389 | (dvert < cpn->vert && | |||
390 | tp->enc == TERMENC_UTF8)) | |||
391 | dvert = cpn->vert; | |||
392 | cpn = cpn->next; | |||
393 | } | |||
394 | ||||
395 | lhori = (cp != NULL((void *)0) && | |||
396 | cp->pos == TBL_CELL_DHORIZ) || | |||
397 | (dp != NULL((void *)0) && | |||
398 | dp->pos == TBL_DATA_DHORIZ) ? 2 : | |||
399 | (cp != NULL((void *)0) && | |||
400 | cp->pos == TBL_CELL_HORIZ) || | |||
401 | (dp != NULL((void *)0) && | |||
402 | dp->pos == TBL_DATA_HORIZ) ? 1 : 0; | |||
403 | ||||
404 | /* | |||
405 | * Skip later cells in a span, | |||
406 | * figure out whether to start a span, | |||
407 | * and advance to next data cell. | |||
408 | */ | |||
409 | ||||
410 | if (hspans) { | |||
411 | hspans--; | |||
412 | cp = cp->next; | |||
413 | continue; | |||
414 | } | |||
415 | if (dp != NULL((void *)0) && (ic || | |||
416 | sp->layout->first->pos != TBL_CELL_SPAN)) { | |||
417 | hspans = dp->hspans; | |||
418 | dp = dp->next; | |||
419 | } | |||
420 | ||||
421 | /* | |||
422 | * Print one line of text in the cell | |||
423 | * and remember whether there is more. | |||
424 | */ | |||
425 | ||||
426 | tp->tcol++; | |||
427 | if (tp->tcol->col < tp->tcol->lastcol) | |||
428 | term_flushln(tp); | |||
429 | if (tp->tcol->col < tp->tcol->lastcol) | |||
430 | more = 1; | |||
431 | ||||
432 | /* | |||
433 | * Vertical frames between data cells, | |||
434 | * but not after the last column. | |||
435 | */ | |||
436 | ||||
437 | if (fc == 0 && | |||
438 | ((uvert == 0 && dvert == 0 && | |||
439 | cp != NULL((void *)0) && (cp->next == NULL((void *)0) || | |||
440 | !IS_HORIZ(cp->next)((cp->next)->pos == TBL_CELL_HORIZ || (cp->next)-> pos == TBL_CELL_DHORIZ))) || | |||
441 | tp->tcol + 1 == | |||
442 | tp->tcols + tp->lasttcol)) { | |||
443 | if (cp != NULL((void *)0)) | |||
444 | cp = cp->next; | |||
445 | continue; | |||
446 | } | |||
447 | ||||
448 | if (tp->viscol < tp->tcol->rmargin) { | |||
449 | (*tp->advance)(tp, tp->tcol->rmargin | |||
450 | - tp->viscol); | |||
451 | tp->viscol = tp->tcol->rmargin; | |||
452 | } | |||
453 | while (tp->viscol < tp->tcol->rmargin + | |||
454 | tp->tbl.cols[ic].spacing / 2) | |||
455 | tbl_direct_border(tp, | |||
456 | BHORIZ((3 * 3) + 1) * lhori, 1); | |||
457 | ||||
458 | if (tp->tcol + 1 == tp->tcols + tp->lasttcol) | |||
459 | continue; | |||
460 | ||||
461 | if (cp != NULL((void *)0)) | |||
462 | cp = cp->next; | |||
463 | ||||
464 | rhori = (cp != NULL((void *)0) && | |||
465 | cp->pos == TBL_CELL_DHORIZ) || | |||
466 | (dp != NULL((void *)0) && | |||
467 | dp->pos == TBL_DATA_DHORIZ) ? 2 : | |||
468 | (cp != NULL((void *)0) && | |||
469 | cp->pos == TBL_CELL_HORIZ) || | |||
470 | (dp != NULL((void *)0) && | |||
471 | dp->pos == TBL_DATA_HORIZ) ? 1 : 0; | |||
472 | ||||
473 | if (tp->tbl.cols[ic].spacing) | |||
474 | tbl_direct_border(tp, | |||
475 | BLEFT(3 * 3) * lhori + BRIGHT1 * rhori + | |||
476 | BUP(3 * 3 * 3) * uvert + BDOWN3 * dvert, 1); | |||
477 | ||||
478 | if (tp->enc == TERMENC_UTF8) | |||
479 | uvert = dvert = 0; | |||
480 | ||||
481 | if (tp->tbl.cols[ic].spacing > 2 && | |||
482 | (uvert > 1 || dvert > 1 || rhori)) | |||
483 | tbl_direct_border(tp, | |||
484 | BHORIZ((3 * 3) + 1) * rhori + | |||
485 | BUP(3 * 3 * 3) * (uvert > 1) + | |||
486 | BDOWN3 * (dvert > 1), 1); | |||
487 | } | |||
488 | } | |||
489 | ||||
490 | /* Print the vertical frame at the end of each row. */ | |||
491 | ||||
492 | uvert = dvert = sp->opts->opts & TBL_OPT_DBOX(1 << 3) ? 2 : | |||
493 | sp->opts->opts & TBL_OPT_BOX(1 << 1) ? 1 : 0; | |||
494 | if (sp->pos == TBL_SPAN_DATA && | |||
495 | uvert < sp->layout->last->vert && | |||
496 | sp->layout->last->col + 1 == sp->opts->cols) | |||
497 | uvert = dvert = sp->layout->last->vert; | |||
498 | if (sp->next != NULL((void *)0) && | |||
499 | dvert < sp->next->layout->last->vert && | |||
500 | sp->next->layout->last->col + 1 == sp->opts->cols) | |||
501 | dvert = sp->next->layout->last->vert; | |||
502 | if (sp->prev != NULL((void *)0) && | |||
503 | uvert < sp->prev->layout->last->vert && | |||
504 | sp->prev->layout->last->col + 1 == sp->opts->cols && | |||
505 | (horiz || (IS_HORIZ(sp->layout->last)((sp->layout->last)->pos == TBL_CELL_HORIZ || (sp-> layout->last)->pos == TBL_CELL_DHORIZ) && | |||
506 | !IS_HORIZ(sp->prev->layout->last)((sp->prev->layout->last)->pos == TBL_CELL_HORIZ || (sp->prev->layout->last)->pos == TBL_CELL_DHORIZ )))) | |||
507 | uvert = sp->prev->layout->last->vert; | |||
508 | lhori = sp->pos == TBL_SPAN_DHORIZ || | |||
509 | (sp->last != NULL((void *)0) && | |||
510 | sp->last->pos == TBL_DATA_DHORIZ && | |||
511 | sp->last->layout->col + 1 == sp->opts->cols) || | |||
512 | (sp->layout->last->pos == TBL_CELL_DHORIZ && | |||
513 | sp->layout->last->col + 1 == sp->opts->cols) ? 2 : | |||
514 | sp->pos == TBL_SPAN_HORIZ || | |||
515 | (sp->last != NULL((void *)0) && | |||
516 | sp->last->pos == TBL_DATA_HORIZ && | |||
517 | sp->last->layout->col + 1 == sp->opts->cols) || | |||
518 | (sp->layout->last->pos == TBL_CELL_HORIZ && | |||
519 | sp->layout->last->col + 1 == sp->opts->cols) ? 1 : 0; | |||
520 | fc = BUP(3 * 3 * 3) * uvert + BDOWN3 * dvert + BLEFT(3 * 3) * lhori; | |||
521 | if (uvert > 0 || dvert > 0 || (horiz && sp->opts->rvert)) { | |||
522 | if (horiz == 0 && (IS_HORIZ(sp->layout->last)((sp->layout->last)->pos == TBL_CELL_HORIZ || (sp-> layout->last)->pos == TBL_CELL_DHORIZ) == 0 || | |||
523 | sp->layout->last->col + 1 < sp->opts->cols)) { | |||
524 | tp->tcol++; | |||
525 | do { | |||
526 | tbl_direct_border(tp, | |||
527 | BHORIZ((3 * 3) + 1) * lhori, 1); | |||
528 | } while (tp->viscol < tp->tcol->offset); | |||
529 | } | |||
530 | tbl_direct_border(tp, fc, 1); | |||
531 | } | |||
532 | (*tp->endline)(tp); | |||
533 | tp->viscol = 0; | |||
534 | } while (more); | |||
535 | ||||
536 | /* | |||
537 | * Clean up after this row. If it is the last line | |||
538 | * of the table, print the box line and clean up | |||
539 | * column data; otherwise, print the allbox line. | |||
540 | */ | |||
541 | ||||
542 | term_setcol(tp, 1); | |||
543 | tp->flags &= ~TERMP_MULTICOL(1 << 20); | |||
544 | tp->tcol->rmargin = tp->maxrmargin; | |||
545 | if (sp->next == NULL((void *)0)) { | |||
546 | if (sp->opts->opts & (TBL_OPT_DBOX(1 << 3) | TBL_OPT_BOX(1 << 1))) { | |||
547 | tbl_hrule(tp, sp, sp, NULL((void *)0), TBL_OPT_BOX(1 << 1)); | |||
548 | tp->skipvsp = 1; | |||
549 | } | |||
550 | if (tp->enc == TERMENC_ASCII && | |||
551 | sp->opts->opts & TBL_OPT_DBOX(1 << 3)) { | |||
552 | tbl_hrule(tp, sp, sp, NULL((void *)0), TBL_OPT_DBOX(1 << 3)); | |||
553 | tp->skipvsp = 2; | |||
554 | } | |||
555 | assert(tp->tbl.cols)((tp->tbl.cols) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/tbl_term.c" , 555, __func__, "tp->tbl.cols")); | |||
556 | free(tp->tbl.cols); | |||
557 | tp->tbl.cols = NULL((void *)0); | |||
558 | } else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX(1 << 0) && | |||
559 | (sp->next == NULL((void *)0) || sp->next->pos == TBL_SPAN_DATA || | |||
560 | sp->next->next != NULL((void *)0))) | |||
561 | tbl_hrule(tp, sp, sp, sp->next, TBL_OPT_ALLBOX(1 << 0)); | |||
562 | ||||
563 | tp->tcol->offset = save_offset; | |||
564 | tp->flags &= ~TERMP_NONOSPACE(1 << 2); | |||
565 | } | |||
566 | ||||
567 | static void | |||
568 | tbl_hrule(struct termp *tp, const struct tbl_span *spp, | |||
569 | const struct tbl_span *sp, const struct tbl_span *spn, int flags) | |||
570 | { | |||
571 | const struct tbl_cell *cpp; /* Layout cell above this line. */ | |||
572 | const struct tbl_cell *cp; /* Layout cell in this line. */ | |||
573 | const struct tbl_cell *cpn; /* Layout cell below this line. */ | |||
574 | const struct tbl_dat *dpn; /* Data cell below this line. */ | |||
575 | const struct roffcol *col; /* Contains width and spacing. */ | |||
576 | int opts; /* For the table as a whole. */ | |||
577 | int bw; /* Box line width. */ | |||
578 | int hw; /* Horizontal line width. */ | |||
579 | int lw, rw; /* Left and right line widths. */ | |||
580 | int uw, dw; /* Vertical line widths. */ | |||
581 | ||||
582 | cpp = spp == NULL((void *)0) ? NULL((void *)0) : spp->layout->first; | |||
583 | cp = sp == NULL((void *)0) ? NULL((void *)0) : sp->layout->first; | |||
584 | cpn = spn == NULL((void *)0) ? NULL((void *)0) : spn->layout->first; | |||
585 | dpn = NULL((void *)0); | |||
586 | if (spn != NULL((void *)0)) { | |||
587 | if (spn->pos == TBL_SPAN_DATA) | |||
588 | dpn = spn->first; | |||
589 | else if (spn->next != NULL((void *)0)) | |||
590 | dpn = spn->next->first; | |||
591 | } | |||
592 | opts = sp->opts->opts; | |||
593 | bw = opts & TBL_OPT_DBOX(1 << 3) ? (tp->enc == TERMENC_UTF8 ? 2 : 1) : | |||
594 | opts & (TBL_OPT_BOX(1 << 1) | TBL_OPT_ALLBOX(1 << 0)) ? 1 : 0; | |||
595 | hw = flags == TBL_OPT_DBOX(1 << 3) || flags == TBL_OPT_BOX(1 << 1) ? bw : | |||
596 | sp->pos == TBL_SPAN_DHORIZ ? 2 : 1; | |||
597 | ||||
598 | /* Print the left end of the line. */ | |||
599 | ||||
600 | if (tp->viscol == 0) { | |||
601 | (*tp->advance)(tp, tp->tcols->offset); | |||
602 | tp->viscol = tp->tcols->offset; | |||
603 | } | |||
604 | if (flags != 0) | |||
605 | tbl_direct_border(tp, | |||
606 | (spp == NULL((void *)0) ? 0 : BUP(3 * 3 * 3) * bw) + | |||
607 | (spn == NULL((void *)0) ? 0 : BDOWN3 * bw) + | |||
608 | (spp == NULL((void *)0) || cpn == NULL((void *)0) || | |||
609 | cpn->pos != TBL_CELL_DOWN ? BRIGHT1 * hw : 0), 1); | |||
610 | ||||
611 | col = tp->tbl.cols; | |||
612 | for (;;) { | |||
613 | if (cp == NULL((void *)0)) | |||
614 | col++; | |||
615 | else | |||
616 | col = tp->tbl.cols + cp->col; | |||
617 | ||||
618 | /* Print the horizontal line inside this column. */ | |||
619 | ||||
620 | lw = cpp == NULL((void *)0) || cpn == NULL((void *)0) || | |||
621 | (cpn->pos != TBL_CELL_DOWN && | |||
622 | (dpn == NULL((void *)0) || dpn->string == NULL((void *)0) || | |||
623 | strcmp(dpn->string, "\\^") != 0)) | |||
624 | ? hw : 0; | |||
625 | tbl_direct_border(tp, BHORIZ((3 * 3) + 1) * lw, | |||
626 | col->width + col->spacing / 2); | |||
627 | ||||
628 | /* | |||
629 | * Figure out whether a vertical line is crossing | |||
630 | * at the end of this column, | |||
631 | * and advance to the next column. | |||
632 | */ | |||
633 | ||||
634 | uw = dw = 0; | |||
635 | if (cpp != NULL((void *)0)) { | |||
636 | if (flags != TBL_OPT_DBOX(1 << 3)) { | |||
637 | uw = cpp->vert; | |||
638 | if (uw == 0 && opts & TBL_OPT_ALLBOX(1 << 0)) | |||
639 | uw = 1; | |||
640 | } | |||
641 | cpp = cpp->next; | |||
642 | } else if (spp != NULL((void *)0) && opts & TBL_OPT_ALLBOX(1 << 0)) | |||
643 | uw = 1; | |||
644 | if (cp != NULL((void *)0)) | |||
645 | cp = cp->next; | |||
646 | if (cpn != NULL((void *)0)) { | |||
647 | if (flags != TBL_OPT_DBOX(1 << 3)) { | |||
648 | dw = cpn->vert; | |||
649 | if (dw == 0 && opts & TBL_OPT_ALLBOX(1 << 0)) | |||
650 | dw = 1; | |||
651 | } | |||
652 | cpn = cpn->next; | |||
653 | while (dpn != NULL((void *)0) && dpn->layout != cpn) | |||
654 | dpn = dpn->next; | |||
655 | } else if (spn != NULL((void *)0) && opts & TBL_OPT_ALLBOX(1 << 0)) | |||
656 | dw = 1; | |||
657 | if (col + 1 == tp->tbl.cols + sp->opts->cols) | |||
658 | break; | |||
659 | ||||
660 | /* Vertical lines do not cross spanned cells. */ | |||
661 | ||||
662 | if (cpp != NULL((void *)0) && cpp->pos == TBL_CELL_SPAN) | |||
663 | uw = 0; | |||
664 | if (cpn != NULL((void *)0) && cpn->pos == TBL_CELL_SPAN) | |||
665 | dw = 0; | |||
666 | ||||
667 | /* The horizontal line inside the next column. */ | |||
668 | ||||
669 | rw = cpp == NULL((void *)0) || cpn == NULL((void *)0) || | |||
670 | (cpn->pos != TBL_CELL_DOWN && | |||
671 | (dpn == NULL((void *)0) || dpn->string == NULL((void *)0) || | |||
672 | strcmp(dpn->string, "\\^") != 0)) | |||
673 | ? hw : 0; | |||
674 | ||||
675 | /* The line crossing at the end of this column. */ | |||
676 | ||||
677 | if (col->spacing) | |||
678 | tbl_direct_border(tp, BLEFT(3 * 3) * lw + | |||
679 | BRIGHT1 * rw + BUP(3 * 3 * 3) * uw + BDOWN3 * dw, 1); | |||
680 | ||||
681 | /* | |||
682 | * In ASCII output, a crossing may print two characters. | |||
683 | */ | |||
684 | ||||
685 | if (tp->enc != TERMENC_ASCII || (uw < 2 && dw < 2)) | |||
686 | uw = dw = 0; | |||
687 | if (col->spacing > 2) | |||
688 | tbl_direct_border(tp, | |||
689 | BHORIZ((3 * 3) + 1) * rw + BUP(3 * 3 * 3) * uw + BDOWN3 * dw, 1); | |||
690 | ||||
691 | /* Padding before the start of the next column. */ | |||
692 | ||||
693 | if (col->spacing > 4) | |||
694 | tbl_direct_border(tp, | |||
695 | BHORIZ((3 * 3) + 1) * rw, (col->spacing - 3) / 2); | |||
696 | } | |||
697 | ||||
698 | /* Print the right end of the line. */ | |||
699 | ||||
700 | if (flags != 0) { | |||
701 | tbl_direct_border(tp, | |||
702 | (spp == NULL((void *)0) ? 0 : BUP(3 * 3 * 3) * bw) + | |||
703 | (spn == NULL((void *)0) ? 0 : BDOWN3 * bw) + | |||
704 | (spp == NULL((void *)0) || spn == NULL((void *)0) || | |||
705 | spn->layout->last->pos != TBL_CELL_DOWN ? | |||
706 | BLEFT(3 * 3) * hw : 0), 1); | |||
707 | (*tp->endline)(tp); | |||
708 | tp->viscol = 0; | |||
709 | } | |||
710 | } | |||
711 | ||||
712 | static void | |||
713 | tbl_data(struct termp *tp, const struct tbl_opts *opts, | |||
714 | const struct tbl_cell *cp, const struct tbl_dat *dp, | |||
715 | const struct roffcol *col) | |||
716 | { | |||
717 | switch (cp->pos) { | |||
| ||||
718 | case TBL_CELL_HORIZ: | |||
719 | tbl_fill_border(tp, BHORIZ((3 * 3) + 1), col->width); | |||
720 | return; | |||
721 | case TBL_CELL_DHORIZ: | |||
722 | tbl_fill_border(tp, BHORIZ((3 * 3) + 1) * 2, col->width); | |||
723 | return; | |||
724 | default: | |||
725 | break; | |||
726 | } | |||
727 | ||||
728 | if (dp == NULL((void *)0)) | |||
729 | return; | |||
730 | ||||
731 | switch (dp->pos) { | |||
732 | case TBL_DATA_NONE: | |||
733 | return; | |||
734 | case TBL_DATA_HORIZ: | |||
735 | case TBL_DATA_NHORIZ: | |||
736 | tbl_fill_border(tp, BHORIZ((3 * 3) + 1), col->width); | |||
737 | return; | |||
738 | case TBL_DATA_NDHORIZ: | |||
739 | case TBL_DATA_DHORIZ: | |||
740 | tbl_fill_border(tp, BHORIZ((3 * 3) + 1) * 2, col->width); | |||
741 | return; | |||
742 | default: | |||
743 | break; | |||
744 | } | |||
745 | ||||
746 | switch (cp->pos) { | |||
747 | case TBL_CELL_LONG: | |||
748 | case TBL_CELL_CENTRE: | |||
749 | case TBL_CELL_LEFT: | |||
750 | case TBL_CELL_RIGHT: | |||
751 | tbl_literal(tp, dp, col); | |||
752 | break; | |||
753 | case TBL_CELL_NUMBER: | |||
754 | tbl_number(tp, opts, dp, col); | |||
755 | break; | |||
756 | case TBL_CELL_DOWN: | |||
757 | case TBL_CELL_SPAN: | |||
758 | break; | |||
759 | default: | |||
760 | abort(); | |||
761 | } | |||
762 | } | |||
763 | ||||
764 | static void | |||
765 | tbl_fill_string(struct termp *tp, const char *cp, size_t len) | |||
766 | { | |||
767 | size_t i, sz; | |||
768 | ||||
769 | sz = term_strlen(tp, cp); | |||
770 | for (i = 0; i < len; i += sz) | |||
771 | term_word(tp, cp); | |||
772 | } | |||
773 | ||||
774 | static void | |||
775 | tbl_fill_char(struct termp *tp, char c, size_t len) | |||
776 | { | |||
777 | char cp[2]; | |||
778 | ||||
779 | cp[0] = c; | |||
780 | cp[1] = '\0'; | |||
781 | tbl_fill_string(tp, cp, len); | |||
782 | } | |||
783 | ||||
784 | static void | |||
785 | tbl_fill_border(struct termp *tp, int c, size_t len) | |||
786 | { | |||
787 | char buf[12]; | |||
788 | ||||
789 | if ((c = borders_locale[c]) > 127) { | |||
790 | (void)snprintf(buf, sizeof(buf), "\\[u%04x]", c); | |||
791 | tbl_fill_string(tp, buf, len); | |||
792 | } else | |||
793 | tbl_fill_char(tp, c, len); | |||
794 | } | |||
795 | ||||
796 | static void | |||
797 | tbl_direct_border(struct termp *tp, int c, size_t len) | |||
798 | { | |||
799 | size_t i, sz; | |||
800 | ||||
801 | c = borders_locale[c]; | |||
802 | sz = (*tp->width)(tp, c); | |||
803 | for (i = 0; i < len; i += sz) { | |||
804 | (*tp->letter)(tp, c); | |||
805 | tp->viscol += sz; | |||
806 | } | |||
807 | } | |||
808 | ||||
809 | static void | |||
810 | tbl_literal(struct termp *tp, const struct tbl_dat *dp, | |||
811 | const struct roffcol *col) | |||
812 | { | |||
813 | size_t len, padl, padr, width; | |||
814 | int ic, hspans; | |||
815 | ||||
816 | assert(dp->string)((dp->string) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/tbl_term.c" , 816, __func__, "dp->string")); | |||
817 | len = term_strlen(tp, dp->string); | |||
818 | width = col->width; | |||
819 | ic = dp->layout->col; | |||
820 | hspans = dp->hspans; | |||
821 | while (hspans--) | |||
822 | width += tp->tbl.cols[++ic].width + 3; | |||
823 | ||||
824 | padr = width > len ? width - len : 0; | |||
825 | padl = 0; | |||
826 | ||||
827 | switch (dp->layout->pos) { | |||
828 | case TBL_CELL_LONG: | |||
829 | padl = term_len(tp, 1); | |||
830 | padr = padr > padl ? padr - padl : 0; | |||
831 | break; | |||
832 | case TBL_CELL_CENTRE: | |||
833 | if (2 > padr) | |||
834 | break; | |||
835 | padl = padr / 2; | |||
836 | padr -= padl; | |||
837 | break; | |||
838 | case TBL_CELL_RIGHT: | |||
839 | padl = padr; | |||
840 | padr = 0; | |||
841 | break; | |||
842 | default: | |||
843 | break; | |||
844 | } | |||
845 | ||||
846 | tbl_fill_char(tp, ASCII_NBRSP31, padl); | |||
847 | tbl_word(tp, dp); | |||
848 | tbl_fill_char(tp, ASCII_NBRSP31, padr); | |||
849 | } | |||
850 | ||||
851 | static void | |||
852 | tbl_number(struct termp *tp, const struct tbl_opts *opts, | |||
853 | const struct tbl_dat *dp, | |||
854 | const struct roffcol *col) | |||
855 | { | |||
856 | const char *cp, *lastdigit, *lastpoint; | |||
857 | size_t intsz, padl, totsz; | |||
858 | char buf[2]; | |||
859 | ||||
860 | /* | |||
861 | * Almost the same code as in tblcalc_number(): | |||
862 | * First find the position of the decimal point. | |||
863 | */ | |||
864 | ||||
865 | assert(dp->string)((dp->string) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/tbl_term.c" , 865, __func__, "dp->string")); | |||
866 | lastdigit = lastpoint = NULL((void *)0); | |||
867 | for (cp = dp->string; cp[0] != '\0'; cp++) { | |||
868 | if (cp[0] == '\\' && cp[1] == '&') { | |||
869 | lastdigit = lastpoint = cp; | |||
870 | break; | |||
871 | } else if (cp[0] == opts->decimal && | |||
872 | (isdigit((unsigned char)cp[1]) || | |||
873 | (cp > dp->string && isdigit((unsigned char)cp[-1])))) | |||
874 | lastpoint = cp; | |||
875 | else if (isdigit((unsigned char)cp[0])) | |||
876 | lastdigit = cp; | |||
877 | } | |||
878 | ||||
879 | /* Then measure both widths. */ | |||
880 | ||||
881 | padl = 0; | |||
882 | totsz = term_strlen(tp, dp->string); | |||
883 | if (lastdigit != NULL((void *)0)) { | |||
884 | if (lastpoint == NULL((void *)0)) | |||
885 | lastpoint = lastdigit + 1; | |||
886 | intsz = 0; | |||
887 | buf[1] = '\0'; | |||
888 | for (cp = dp->string; cp < lastpoint; cp++) { | |||
889 | buf[0] = cp[0]; | |||
890 | intsz += term_strlen(tp, buf); | |||
891 | } | |||
892 | ||||
893 | /* | |||
894 | * Pad left to match the decimal position, | |||
895 | * but avoid exceeding the total column width. | |||
896 | */ | |||
897 | ||||
898 | if (col->decimal > intsz && col->width > totsz) { | |||
899 | padl = col->decimal - intsz; | |||
900 | if (padl + totsz > col->width) | |||
901 | padl = col->width - totsz; | |||
902 | } | |||
903 | ||||
904 | /* If it is not a number, simply center the string. */ | |||
905 | ||||
906 | } else if (col->width > totsz) | |||
907 | padl = (col->width - totsz) / 2; | |||
908 | ||||
909 | tbl_fill_char(tp, ASCII_NBRSP31, padl); | |||
910 | tbl_word(tp, dp); | |||
911 | ||||
912 | /* Pad right to fill the column. */ | |||
913 | ||||
914 | if (col->width > padl + totsz) | |||
915 | tbl_fill_char(tp, ASCII_NBRSP31, col->width - padl - totsz); | |||
916 | } | |||
917 | ||||
918 | static void | |||
919 | tbl_word(struct termp *tp, const struct tbl_dat *dp) | |||
920 | { | |||
921 | int prev_font; | |||
922 | ||||
923 | prev_font = tp->fonti; | |||
924 | switch (dp->layout->font) { | |||
925 | case ESCAPE_FONTBI: | |||
926 | term_fontpush(tp, TERMFONT_BI); | |||
927 | break; | |||
928 | case ESCAPE_FONTBOLD: | |||
929 | case ESCAPE_FONTCB: | |||
930 | term_fontpush(tp, TERMFONT_BOLD); | |||
931 | break; | |||
932 | case ESCAPE_FONTITALIC: | |||
933 | case ESCAPE_FONTCI: | |||
934 | term_fontpush(tp, TERMFONT_UNDER); | |||
935 | break; | |||
936 | case ESCAPE_FONTROMAN: | |||
937 | case ESCAPE_FONTCR: | |||
938 | break; | |||
939 | default: | |||
940 | abort(); | |||
941 | } | |||
942 | ||||
943 | term_word(tp, dp->string); | |||
944 | ||||
945 | term_fontpopq(tp, prev_font); | |||
946 | } |