]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/mandoc/term.c
bhyvectl(8): Normalize the man page date
[FreeBSD/FreeBSD.git] / contrib / mandoc / term.c
1 /*      $Id: term.c,v 1.281 2019/06/03 20:23:41 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010-2019 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 AUTHORS DISCLAIM ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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 <ctype.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "mandoc.h"
30 #include "mandoc_aux.h"
31 #include "out.h"
32 #include "term.h"
33 #include "main.h"
34
35 static  size_t           cond_width(const struct termp *, int, int *);
36 static  void             adjbuf(struct termp_col *, size_t);
37 static  void             bufferc(struct termp *, char);
38 static  void             encode(struct termp *, const char *, size_t);
39 static  void             encode1(struct termp *, int);
40 static  void             endline(struct termp *);
41 static  void             term_field(struct termp *, size_t, size_t,
42                                 size_t, size_t);
43 static  void             term_fill(struct termp *, size_t *, size_t *,
44                                 size_t);
45
46
47 void
48 term_setcol(struct termp *p, size_t maxtcol)
49 {
50         if (maxtcol > p->maxtcol) {
51                 p->tcols = mandoc_recallocarray(p->tcols,
52                     p->maxtcol, maxtcol, sizeof(*p->tcols));
53                 p->maxtcol = maxtcol;
54         }
55         p->lasttcol = maxtcol - 1;
56         p->tcol = p->tcols;
57 }
58
59 void
60 term_free(struct termp *p)
61 {
62         for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++)
63                 free(p->tcol->buf);
64         free(p->tcols);
65         free(p->fontq);
66         free(p);
67 }
68
69 void
70 term_begin(struct termp *p, term_margin head,
71                 term_margin foot, const struct roff_meta *arg)
72 {
73
74         p->headf = head;
75         p->footf = foot;
76         p->argf = arg;
77         (*p->begin)(p);
78 }
79
80 void
81 term_end(struct termp *p)
82 {
83
84         (*p->end)(p);
85 }
86
87 /*
88  * Flush a chunk of text.  By default, break the output line each time
89  * the right margin is reached, and continue output on the next line
90  * at the same offset as the chunk itself.  By default, also break the
91  * output line at the end of the chunk.  There are many flags modifying
92  * this behaviour, see the comments in the body of the function.
93  */
94 void
95 term_flushln(struct termp *p)
96 {
97         size_t   vbl;      /* Number of blanks to prepend to the output. */
98         size_t   vbr;      /* Actual visual position of the end of field. */
99         size_t   vfield;   /* Desired visual field width. */
100         size_t   vtarget;  /* Desired visual position of the right margin. */
101         size_t   ic;       /* Character position in the input buffer. */
102         size_t   nbr;      /* Number of characters to print in this field. */
103
104         /*
105          * Normally, start writing at the left margin, but with the
106          * NOPAD flag, start writing at the current position instead.
107          */
108
109         vbl = (p->flags & TERMP_NOPAD) || p->tcol->offset < p->viscol ?
110             0 : p->tcol->offset - p->viscol;
111         if (p->minbl && vbl < p->minbl)
112                 vbl = p->minbl;
113
114         if ((p->flags & TERMP_MULTICOL) == 0)
115                 p->tcol->col = 0;
116
117         /* Loop over output lines. */
118
119         for (;;) {
120                 vfield = p->tcol->rmargin > p->viscol + vbl ?
121                     p->tcol->rmargin - p->viscol - vbl : 0;
122
123                 /*
124                  * Normally, break the line at the the right margin
125                  * of the field, but with the NOBREAK flag, only
126                  * break it at the max right margin of the screen,
127                  * and with the BRNEVER flag, never break it at all.
128                  */
129
130                 vtarget = p->flags & TERMP_BRNEVER ? SIZE_MAX :
131                     (p->flags & TERMP_NOBREAK) == 0 ? vfield :
132                     p->maxrmargin > p->viscol + vbl ?
133                     p->maxrmargin - p->viscol - vbl : 0;
134
135                 /*
136                  * Figure out how much text will fit in the field.
137                  * If there is whitespace only, print nothing.
138                  */
139
140                 term_fill(p, &nbr, &vbr, vtarget);
141                 if (nbr == 0)
142                         break;
143
144                 /*
145                  * With the CENTER or RIGHT flag, increase the indentation
146                  * to center the text between the left and right margins
147                  * or to adjust it to the right margin, respectively.
148                  */
149
150                 if (vbr < vtarget) {
151                         if (p->flags & TERMP_CENTER)
152                                 vbl += (vtarget - vbr) / 2;
153                         else if (p->flags & TERMP_RIGHT)
154                                 vbl += vtarget - vbr;
155                 }
156
157                 /* Finally, print the field content. */
158
159                 term_field(p, vbl, nbr, vbr, vtarget);
160
161                 /*
162                  * If there is no text left in the field, exit the loop.
163                  * If the BRTRSP flag is set, consider trailing
164                  * whitespace significant when deciding whether
165                  * the field fits or not.
166                  */
167
168                 for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
169                         switch (p->tcol->buf[ic]) {
170                         case '\t':
171                                 if (p->flags & TERMP_BRTRSP)
172                                         vbr = term_tab_next(vbr);
173                                 continue;
174                         case ' ':
175                                 if (p->flags & TERMP_BRTRSP)
176                                         vbr += (*p->width)(p, ' ');
177                                 continue;
178                         case '\n':
179                         case ASCII_BREAK:
180                                 continue;
181                         default:
182                                 break;
183                         }
184                         break;
185                 }
186                 if (ic == p->tcol->lastcol)
187                         break;
188
189                 /*
190                  * At the location of an automtic line break, input
191                  * space characters are consumed by the line break.
192                  */
193
194                 while (p->tcol->col < p->tcol->lastcol &&
195                     p->tcol->buf[p->tcol->col] == ' ')
196                         p->tcol->col++;
197
198                 /*
199                  * In multi-column mode, leave the rest of the text
200                  * in the buffer to be handled by a subsequent
201                  * invocation, such that the other columns of the
202                  * table can be handled first.
203                  * In single-column mode, simply break the line.
204                  */
205
206                 if (p->flags & TERMP_MULTICOL)
207                         return;
208
209                 endline(p);
210                 p->viscol = 0;
211
212                 /*
213                  * Normally, start the next line at the same indentation
214                  * as this one, but with the BRIND flag, start it at the
215                  * right margin instead.  This is used together with
216                  * NOBREAK for the tags in various kinds of tagged lists.
217                  */
218
219                 vbl = p->flags & TERMP_BRIND ?
220                     p->tcol->rmargin : p->tcol->offset;
221         }
222
223         /* Reset output state in preparation for the next field. */
224
225         p->col = p->tcol->col = p->tcol->lastcol = 0;
226         p->minbl = p->trailspace;
227         p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD);
228
229         if (p->flags & TERMP_MULTICOL)
230                 return;
231
232         /*
233          * The HANG flag means that the next field
234          * always follows on the same line.
235          * The NOBREAK flag means that the next field
236          * follows on the same line unless the field was overrun.
237          * Normally, break the line at the end of each field.
238          */
239
240         if ((p->flags & TERMP_HANG) == 0 &&
241             ((p->flags & TERMP_NOBREAK) == 0 ||
242              vbr + term_len(p, p->trailspace) > vfield))
243                 endline(p);
244 }
245
246 /*
247  * Store the number of input characters to print in this field in *nbr
248  * and their total visual width to print in *vbr.
249  * If there is only whitespace in the field, both remain zero.
250  * The desired visual width of the field is provided by vtarget.
251  * If the first word is longer, the field will be overrun.
252  */
253 static void
254 term_fill(struct termp *p, size_t *nbr, size_t *vbr, size_t vtarget)
255 {
256         size_t   ic;        /* Character position in the input buffer. */
257         size_t   vis;       /* Visual position of the current character. */
258         size_t   vn;        /* Visual position of the next character. */
259         int      breakline; /* Break at the end of this word. */
260         int      graph;     /* Last character was non-blank. */
261
262         *nbr = *vbr = vis = 0;
263         breakline = graph = 0;
264         for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
265                 switch (p->tcol->buf[ic]) {
266                 case '\b':  /* Escape \o (overstrike) or backspace markup. */
267                         assert(ic > 0);
268                         vis -= (*p->width)(p, p->tcol->buf[ic - 1]);
269                         continue;
270
271                 case '\t':  /* Normal ASCII whitespace. */
272                 case ' ':
273                 case ASCII_BREAK:  /* Escape \: (breakpoint). */
274                         switch (p->tcol->buf[ic]) {
275                         case '\t':
276                                 vn = term_tab_next(vis);
277                                 break;
278                         case ' ':
279                                 vn = vis + (*p->width)(p, ' ');
280                                 break;
281                         case ASCII_BREAK:
282                                 vn = vis;
283                                 break;
284                         default:
285                                 abort();
286                         }
287                         /* Can break at the end of a word. */
288                         if (breakline || vn > vtarget)
289                                 break;
290                         if (graph) {
291                                 *nbr = ic;
292                                 *vbr = vis;
293                                 graph = 0;
294                         }
295                         vis = vn;
296                         continue;
297
298                 case '\n':  /* Escape \p (break at the end of the word). */
299                         breakline = 1;
300                         continue;
301
302                 case ASCII_HYPH:  /* Breakable hyphen. */
303                         graph = 1;
304                         /*
305                          * We are about to decide whether to break the
306                          * line or not, so we no longer need this hyphen
307                          * to be marked as breakable.  Put back a real
308                          * hyphen such that we get the correct width.
309                          */
310                         p->tcol->buf[ic] = '-';
311                         vis += (*p->width)(p, '-');
312                         if (vis > vtarget) {
313                                 ic++;
314                                 break;
315                         }
316                         *nbr = ic + 1;
317                         *vbr = vis;
318                         continue;
319
320                 case ASCII_NBRSP:  /* Non-breakable space. */
321                         p->tcol->buf[ic] = ' ';
322                         /* FALLTHROUGH */
323                 default:  /* Printable character. */
324                         graph = 1;
325                         vis += (*p->width)(p, p->tcol->buf[ic]);
326                         if (vis > vtarget && *nbr > 0)
327                                 return;
328                         continue;
329                 }
330                 break;
331         }
332
333         /*
334          * If the last word extends to the end of the field without any
335          * trailing whitespace, the loop could not check yet whether it
336          * can remain on this line.  So do the check now.
337          */
338
339         if (graph && (vis <= vtarget || *nbr == 0)) {
340                 *nbr = ic;
341                 *vbr = vis;
342         }
343 }
344
345 /*
346  * Print the contents of one field
347  * with an indentation of        vbl      visual columns,
348  * an input string length of     nbr      characters,
349  * an output width of            vbr      visual columns,
350  * and a desired field width of  vtarget  visual columns.
351  */
352 static void
353 term_field(struct termp *p, size_t vbl, size_t nbr, size_t vbr, size_t vtarget)
354 {
355         size_t   ic;    /* Character position in the input buffer. */
356         size_t   vis;   /* Visual position of the current character. */
357         size_t   dv;    /* Visual width of the current character. */
358         size_t   vn;    /* Visual position of the next character. */
359
360         vis = 0;
361         for (ic = p->tcol->col; ic < nbr; ic++) {
362
363                 /*
364                  * To avoid the printing of trailing whitespace,
365                  * do not print whitespace right away, only count it.
366                  */
367
368                 switch (p->tcol->buf[ic]) {
369                 case '\n':
370                 case ASCII_BREAK:
371                         continue;
372                 case '\t':
373                         vn = term_tab_next(vis);
374                         vbl += vn - vis;
375                         vis = vn;
376                         continue;
377                 case ' ':
378                 case ASCII_NBRSP:
379                         dv = (*p->width)(p, ' ');
380                         vbl += dv;
381                         vis += dv;
382                         continue;
383                 default:
384                         break;
385                 }
386
387                 /*
388                  * We found a non-blank character to print,
389                  * so write preceding white space now.
390                  */
391
392                 if (vbl > 0) {
393                         (*p->advance)(p, vbl);
394                         p->viscol += vbl;
395                         vbl = 0;
396                 }
397
398                 /* Print the character and adjust the visual position. */
399
400                 (*p->letter)(p, p->tcol->buf[ic]);
401                 if (p->tcol->buf[ic] == '\b') {
402                         dv = (*p->width)(p, p->tcol->buf[ic - 1]);
403                         p->viscol -= dv;
404                         vis -= dv;
405                 } else {
406                         dv = (*p->width)(p, p->tcol->buf[ic]);
407                         p->viscol += dv;
408                         vis += dv;
409                 }
410         }
411         p->tcol->col = nbr;
412 }
413
414 static void
415 endline(struct termp *p)
416 {
417         if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) {
418                 p->mc = NULL;
419                 p->flags &= ~TERMP_ENDMC;
420         }
421         if (p->mc != NULL) {
422                 if (p->viscol && p->maxrmargin >= p->viscol)
423                         (*p->advance)(p, p->maxrmargin - p->viscol + 1);
424                 p->flags |= TERMP_NOBUF | TERMP_NOSPACE;
425                 term_word(p, p->mc);
426                 p->flags &= ~(TERMP_NOBUF | TERMP_NEWMC);
427         }
428         p->viscol = 0;
429         p->minbl = 0;
430         (*p->endline)(p);
431 }
432
433 /*
434  * A newline only breaks an existing line; it won't assert vertical
435  * space.  All data in the output buffer is flushed prior to the newline
436  * assertion.
437  */
438 void
439 term_newln(struct termp *p)
440 {
441
442         p->flags |= TERMP_NOSPACE;
443         if (p->tcol->lastcol || p->viscol)
444                 term_flushln(p);
445 }
446
447 /*
448  * Asserts a vertical space (a full, empty line-break between lines).
449  * Note that if used twice, this will cause two blank spaces and so on.
450  * All data in the output buffer is flushed prior to the newline
451  * assertion.
452  */
453 void
454 term_vspace(struct termp *p)
455 {
456
457         term_newln(p);
458         p->viscol = 0;
459         p->minbl = 0;
460         if (0 < p->skipvsp)
461                 p->skipvsp--;
462         else
463                 (*p->endline)(p);
464 }
465
466 /* Swap current and previous font; for \fP and .ft P */
467 void
468 term_fontlast(struct termp *p)
469 {
470         enum termfont    f;
471
472         f = p->fontl;
473         p->fontl = p->fontq[p->fonti];
474         p->fontq[p->fonti] = f;
475 }
476
477 /* Set font, save current, discard previous; for \f, .ft, .B etc. */
478 void
479 term_fontrepl(struct termp *p, enum termfont f)
480 {
481
482         p->fontl = p->fontq[p->fonti];
483         p->fontq[p->fonti] = f;
484 }
485
486 /* Set font, save previous. */
487 void
488 term_fontpush(struct termp *p, enum termfont f)
489 {
490
491         p->fontl = p->fontq[p->fonti];
492         if (++p->fonti == p->fontsz) {
493                 p->fontsz += 8;
494                 p->fontq = mandoc_reallocarray(p->fontq,
495                     p->fontsz, sizeof(*p->fontq));
496         }
497         p->fontq[p->fonti] = f;
498 }
499
500 /* Flush to make the saved pointer current again. */
501 void
502 term_fontpopq(struct termp *p, int i)
503 {
504
505         assert(i >= 0);
506         if (p->fonti > i)
507                 p->fonti = i;
508 }
509
510 /* Pop one font off the stack. */
511 void
512 term_fontpop(struct termp *p)
513 {
514
515         assert(p->fonti);
516         p->fonti--;
517 }
518
519 /*
520  * Handle pwords, partial words, which may be either a single word or a
521  * phrase that cannot be broken down (such as a literal string).  This
522  * handles word styling.
523  */
524 void
525 term_word(struct termp *p, const char *word)
526 {
527         struct roffsu    su;
528         const char       nbrsp[2] = { ASCII_NBRSP, 0 };
529         const char      *seq, *cp;
530         int              sz, uc;
531         size_t           csz, lsz, ssz;
532         enum mandoc_esc  esc;
533
534         if ((p->flags & TERMP_NOBUF) == 0) {
535                 if ((p->flags & TERMP_NOSPACE) == 0) {
536                         if ((p->flags & TERMP_KEEP) == 0) {
537                                 bufferc(p, ' ');
538                                 if (p->flags & TERMP_SENTENCE)
539                                         bufferc(p, ' ');
540                         } else
541                                 bufferc(p, ASCII_NBRSP);
542                 }
543                 if (p->flags & TERMP_PREKEEP)
544                         p->flags |= TERMP_KEEP;
545                 if (p->flags & TERMP_NONOSPACE)
546                         p->flags |= TERMP_NOSPACE;
547                 else
548                         p->flags &= ~TERMP_NOSPACE;
549                 p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
550                 p->skipvsp = 0;
551         }
552
553         while ('\0' != *word) {
554                 if ('\\' != *word) {
555                         if (TERMP_NBRWORD & p->flags) {
556                                 if (' ' == *word) {
557                                         encode(p, nbrsp, 1);
558                                         word++;
559                                         continue;
560                                 }
561                                 ssz = strcspn(word, "\\ ");
562                         } else
563                                 ssz = strcspn(word, "\\");
564                         encode(p, word, ssz);
565                         word += (int)ssz;
566                         continue;
567                 }
568
569                 word++;
570                 esc = mandoc_escape(&word, &seq, &sz);
571                 switch (esc) {
572                 case ESCAPE_UNICODE:
573                         uc = mchars_num2uc(seq + 1, sz - 1);
574                         break;
575                 case ESCAPE_NUMBERED:
576                         uc = mchars_num2char(seq, sz);
577                         if (uc < 0)
578                                 continue;
579                         break;
580                 case ESCAPE_SPECIAL:
581                         if (p->enc == TERMENC_ASCII) {
582                                 cp = mchars_spec2str(seq, sz, &ssz);
583                                 if (cp != NULL)
584                                         encode(p, cp, ssz);
585                         } else {
586                                 uc = mchars_spec2cp(seq, sz);
587                                 if (uc > 0)
588                                         encode1(p, uc);
589                         }
590                         continue;
591                 case ESCAPE_UNDEF:
592                         uc = *seq;
593                         break;
594                 case ESCAPE_FONTBOLD:
595                         term_fontrepl(p, TERMFONT_BOLD);
596                         continue;
597                 case ESCAPE_FONTITALIC:
598                         term_fontrepl(p, TERMFONT_UNDER);
599                         continue;
600                 case ESCAPE_FONTBI:
601                         term_fontrepl(p, TERMFONT_BI);
602                         continue;
603                 case ESCAPE_FONT:
604                 case ESCAPE_FONTCW:
605                 case ESCAPE_FONTROMAN:
606                         term_fontrepl(p, TERMFONT_NONE);
607                         continue;
608                 case ESCAPE_FONTPREV:
609                         term_fontlast(p);
610                         continue;
611                 case ESCAPE_BREAK:
612                         bufferc(p, '\n');
613                         continue;
614                 case ESCAPE_NOSPACE:
615                         if (p->flags & TERMP_BACKAFTER)
616                                 p->flags &= ~TERMP_BACKAFTER;
617                         else if (*word == '\0')
618                                 p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
619                         continue;
620                 case ESCAPE_DEVICE:
621                         if (p->type == TERMTYPE_PDF)
622                                 encode(p, "pdf", 3);
623                         else if (p->type == TERMTYPE_PS)
624                                 encode(p, "ps", 2);
625                         else if (p->enc == TERMENC_ASCII)
626                                 encode(p, "ascii", 5);
627                         else
628                                 encode(p, "utf8", 4);
629                         continue;
630                 case ESCAPE_HORIZ:
631                         if (*seq == '|') {
632                                 seq++;
633                                 uc = -p->col;
634                         } else
635                                 uc = 0;
636                         if (a2roffsu(seq, &su, SCALE_EM) == NULL)
637                                 continue;
638                         uc += term_hen(p, &su);
639                         if (uc > 0)
640                                 while (uc-- > 0)
641                                         bufferc(p, ASCII_NBRSP);
642                         else if (p->col > (size_t)(-uc))
643                                 p->col += uc;
644                         else {
645                                 uc += p->col;
646                                 p->col = 0;
647                                 if (p->tcol->offset > (size_t)(-uc)) {
648                                         p->ti += uc;
649                                         p->tcol->offset += uc;
650                                 } else {
651                                         p->ti -= p->tcol->offset;
652                                         p->tcol->offset = 0;
653                                 }
654                         }
655                         continue;
656                 case ESCAPE_HLINE:
657                         if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL)
658                                 continue;
659                         uc = term_hen(p, &su);
660                         if (uc <= 0) {
661                                 if (p->tcol->rmargin <= p->tcol->offset)
662                                         continue;
663                                 lsz = p->tcol->rmargin - p->tcol->offset;
664                         } else
665                                 lsz = uc;
666                         if (*cp == seq[-1])
667                                 uc = -1;
668                         else if (*cp == '\\') {
669                                 seq = cp + 1;
670                                 esc = mandoc_escape(&seq, &cp, &sz);
671                                 switch (esc) {
672                                 case ESCAPE_UNICODE:
673                                         uc = mchars_num2uc(cp + 1, sz - 1);
674                                         break;
675                                 case ESCAPE_NUMBERED:
676                                         uc = mchars_num2char(cp, sz);
677                                         break;
678                                 case ESCAPE_SPECIAL:
679                                         uc = mchars_spec2cp(cp, sz);
680                                         break;
681                                 case ESCAPE_UNDEF:
682                                         uc = *seq;
683                                         break;
684                                 default:
685                                         uc = -1;
686                                         break;
687                                 }
688                         } else
689                                 uc = *cp;
690                         if (uc < 0x20 || (uc > 0x7E && uc < 0xA0))
691                                 uc = '_';
692                         if (p->enc == TERMENC_ASCII) {
693                                 cp = ascii_uc2str(uc);
694                                 csz = term_strlen(p, cp);
695                                 ssz = strlen(cp);
696                         } else
697                                 csz = (*p->width)(p, uc);
698                         while (lsz >= csz) {
699                                 if (p->enc == TERMENC_ASCII)
700                                         encode(p, cp, ssz);
701                                 else
702                                         encode1(p, uc);
703                                 lsz -= csz;
704                         }
705                         continue;
706                 case ESCAPE_SKIPCHAR:
707                         p->flags |= TERMP_BACKAFTER;
708                         continue;
709                 case ESCAPE_OVERSTRIKE:
710                         cp = seq + sz;
711                         while (seq < cp) {
712                                 if (*seq == '\\') {
713                                         mandoc_escape(&seq, NULL, NULL);
714                                         continue;
715                                 }
716                                 encode1(p, *seq++);
717                                 if (seq < cp) {
718                                         if (p->flags & TERMP_BACKBEFORE)
719                                                 p->flags |= TERMP_BACKAFTER;
720                                         else
721                                                 p->flags |= TERMP_BACKBEFORE;
722                                 }
723                         }
724                         /* Trim trailing backspace/blank pair. */
725                         if (p->tcol->lastcol > 2 &&
726                             (p->tcol->buf[p->tcol->lastcol - 1] == ' ' ||
727                              p->tcol->buf[p->tcol->lastcol - 1] == '\t'))
728                                 p->tcol->lastcol -= 2;
729                         if (p->col > p->tcol->lastcol)
730                                 p->col = p->tcol->lastcol;
731                         continue;
732                 default:
733                         continue;
734                 }
735
736                 /*
737                  * Common handling for Unicode and numbered
738                  * character escape sequences.
739                  */
740
741                 if (p->enc == TERMENC_ASCII) {
742                         cp = ascii_uc2str(uc);
743                         encode(p, cp, strlen(cp));
744                 } else {
745                         if ((uc < 0x20 && uc != 0x09) ||
746                             (uc > 0x7E && uc < 0xA0))
747                                 uc = 0xFFFD;
748                         encode1(p, uc);
749                 }
750         }
751         p->flags &= ~TERMP_NBRWORD;
752 }
753
754 static void
755 adjbuf(struct termp_col *c, size_t sz)
756 {
757         if (c->maxcols == 0)
758                 c->maxcols = 1024;
759         while (c->maxcols <= sz)
760                 c->maxcols <<= 2;
761         c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf));
762 }
763
764 static void
765 bufferc(struct termp *p, char c)
766 {
767         if (p->flags & TERMP_NOBUF) {
768                 (*p->letter)(p, c);
769                 return;
770         }
771         if (p->col + 1 >= p->tcol->maxcols)
772                 adjbuf(p->tcol, p->col + 1);
773         if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
774                 p->tcol->buf[p->col] = c;
775         if (p->tcol->lastcol < ++p->col)
776                 p->tcol->lastcol = p->col;
777 }
778
779 /*
780  * See encode().
781  * Do this for a single (probably unicode) value.
782  * Does not check for non-decorated glyphs.
783  */
784 static void
785 encode1(struct termp *p, int c)
786 {
787         enum termfont     f;
788
789         if (p->flags & TERMP_NOBUF) {
790                 (*p->letter)(p, c);
791                 return;
792         }
793
794         if (p->col + 7 >= p->tcol->maxcols)
795                 adjbuf(p->tcol, p->col + 7);
796
797         f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ?
798             p->fontq[p->fonti] : TERMFONT_NONE;
799
800         if (p->flags & TERMP_BACKBEFORE) {
801                 if (p->tcol->buf[p->col - 1] == ' ' ||
802                     p->tcol->buf[p->col - 1] == '\t')
803                         p->col--;
804                 else
805                         p->tcol->buf[p->col++] = '\b';
806                 p->flags &= ~TERMP_BACKBEFORE;
807         }
808         if (f == TERMFONT_UNDER || f == TERMFONT_BI) {
809                 p->tcol->buf[p->col++] = '_';
810                 p->tcol->buf[p->col++] = '\b';
811         }
812         if (f == TERMFONT_BOLD || f == TERMFONT_BI) {
813                 if (c == ASCII_HYPH)
814                         p->tcol->buf[p->col++] = '-';
815                 else
816                         p->tcol->buf[p->col++] = c;
817                 p->tcol->buf[p->col++] = '\b';
818         }
819         if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
820                 p->tcol->buf[p->col] = c;
821         if (p->tcol->lastcol < ++p->col)
822                 p->tcol->lastcol = p->col;
823         if (p->flags & TERMP_BACKAFTER) {
824                 p->flags |= TERMP_BACKBEFORE;
825                 p->flags &= ~TERMP_BACKAFTER;
826         }
827 }
828
829 static void
830 encode(struct termp *p, const char *word, size_t sz)
831 {
832         size_t            i;
833
834         if (p->flags & TERMP_NOBUF) {
835                 for (i = 0; i < sz; i++)
836                         (*p->letter)(p, word[i]);
837                 return;
838         }
839
840         if (p->col + 2 + (sz * 5) >= p->tcol->maxcols)
841                 adjbuf(p->tcol, p->col + 2 + (sz * 5));
842
843         for (i = 0; i < sz; i++) {
844                 if (ASCII_HYPH == word[i] ||
845                     isgraph((unsigned char)word[i]))
846                         encode1(p, word[i]);
847                 else {
848                         if (p->tcol->lastcol <= p->col ||
849                             (word[i] != ' ' && word[i] != ASCII_NBRSP))
850                                 p->tcol->buf[p->col] = word[i];
851                         p->col++;
852
853                         /*
854                          * Postpone the effect of \z while handling
855                          * an overstrike sequence from ascii_uc2str().
856                          */
857
858                         if (word[i] == '\b' &&
859                             (p->flags & TERMP_BACKBEFORE)) {
860                                 p->flags &= ~TERMP_BACKBEFORE;
861                                 p->flags |= TERMP_BACKAFTER;
862                         }
863                 }
864         }
865         if (p->tcol->lastcol < p->col)
866                 p->tcol->lastcol = p->col;
867 }
868
869 void
870 term_setwidth(struct termp *p, const char *wstr)
871 {
872         struct roffsu    su;
873         int              iop, width;
874
875         iop = 0;
876         width = 0;
877         if (NULL != wstr) {
878                 switch (*wstr) {
879                 case '+':
880                         iop = 1;
881                         wstr++;
882                         break;
883                 case '-':
884                         iop = -1;
885                         wstr++;
886                         break;
887                 default:
888                         break;
889                 }
890                 if (a2roffsu(wstr, &su, SCALE_MAX) != NULL)
891                         width = term_hspan(p, &su);
892                 else
893                         iop = 0;
894         }
895         (*p->setwidth)(p, iop, width);
896 }
897
898 size_t
899 term_len(const struct termp *p, size_t sz)
900 {
901
902         return (*p->width)(p, ' ') * sz;
903 }
904
905 static size_t
906 cond_width(const struct termp *p, int c, int *skip)
907 {
908
909         if (*skip) {
910                 (*skip) = 0;
911                 return 0;
912         } else
913                 return (*p->width)(p, c);
914 }
915
916 size_t
917 term_strlen(const struct termp *p, const char *cp)
918 {
919         size_t           sz, rsz, i;
920         int              ssz, skip, uc;
921         const char      *seq, *rhs;
922         enum mandoc_esc  esc;
923         static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
924                         ASCII_BREAK, '\0' };
925
926         /*
927          * Account for escaped sequences within string length
928          * calculations.  This follows the logic in term_word() as we
929          * must calculate the width of produced strings.
930          */
931
932         sz = 0;
933         skip = 0;
934         while ('\0' != *cp) {
935                 rsz = strcspn(cp, rej);
936                 for (i = 0; i < rsz; i++)
937                         sz += cond_width(p, *cp++, &skip);
938
939                 switch (*cp) {
940                 case '\\':
941                         cp++;
942                         rhs = NULL;
943                         esc = mandoc_escape(&cp, &seq, &ssz);
944                         switch (esc) {
945                         case ESCAPE_UNICODE:
946                                 uc = mchars_num2uc(seq + 1, ssz - 1);
947                                 break;
948                         case ESCAPE_NUMBERED:
949                                 uc = mchars_num2char(seq, ssz);
950                                 if (uc < 0)
951                                         continue;
952                                 break;
953                         case ESCAPE_SPECIAL:
954                                 if (p->enc == TERMENC_ASCII) {
955                                         rhs = mchars_spec2str(seq, ssz, &rsz);
956                                         if (rhs != NULL)
957                                                 break;
958                                 } else {
959                                         uc = mchars_spec2cp(seq, ssz);
960                                         if (uc > 0)
961                                                 sz += cond_width(p, uc, &skip);
962                                 }
963                                 continue;
964                         case ESCAPE_UNDEF:
965                                 uc = *seq;
966                                 break;
967                         case ESCAPE_DEVICE:
968                                 if (p->type == TERMTYPE_PDF) {
969                                         rhs = "pdf";
970                                         rsz = 3;
971                                 } else if (p->type == TERMTYPE_PS) {
972                                         rhs = "ps";
973                                         rsz = 2;
974                                 } else if (p->enc == TERMENC_ASCII) {
975                                         rhs = "ascii";
976                                         rsz = 5;
977                                 } else {
978                                         rhs = "utf8";
979                                         rsz = 4;
980                                 }
981                                 break;
982                         case ESCAPE_SKIPCHAR:
983                                 skip = 1;
984                                 continue;
985                         case ESCAPE_OVERSTRIKE:
986                                 rsz = 0;
987                                 rhs = seq + ssz;
988                                 while (seq < rhs) {
989                                         if (*seq == '\\') {
990                                                 mandoc_escape(&seq, NULL, NULL);
991                                                 continue;
992                                         }
993                                         i = (*p->width)(p, *seq++);
994                                         if (rsz < i)
995                                                 rsz = i;
996                                 }
997                                 sz += rsz;
998                                 continue;
999                         default:
1000                                 continue;
1001                         }
1002
1003                         /*
1004                          * Common handling for Unicode and numbered
1005                          * character escape sequences.
1006                          */
1007
1008                         if (rhs == NULL) {
1009                                 if (p->enc == TERMENC_ASCII) {
1010                                         rhs = ascii_uc2str(uc);
1011                                         rsz = strlen(rhs);
1012                                 } else {
1013                                         if ((uc < 0x20 && uc != 0x09) ||
1014                                             (uc > 0x7E && uc < 0xA0))
1015                                                 uc = 0xFFFD;
1016                                         sz += cond_width(p, uc, &skip);
1017                                         continue;
1018                                 }
1019                         }
1020
1021                         if (skip) {
1022                                 skip = 0;
1023                                 break;
1024                         }
1025
1026                         /*
1027                          * Common handling for all escape sequences
1028                          * printing more than one character.
1029                          */
1030
1031                         for (i = 0; i < rsz; i++)
1032                                 sz += (*p->width)(p, *rhs++);
1033                         break;
1034                 case ASCII_NBRSP:
1035                         sz += cond_width(p, ' ', &skip);
1036                         cp++;
1037                         break;
1038                 case ASCII_HYPH:
1039                         sz += cond_width(p, '-', &skip);
1040                         cp++;
1041                         break;
1042                 default:
1043                         break;
1044                 }
1045         }
1046
1047         return sz;
1048 }
1049
1050 int
1051 term_vspan(const struct termp *p, const struct roffsu *su)
1052 {
1053         double           r;
1054         int              ri;
1055
1056         switch (su->unit) {
1057         case SCALE_BU:
1058                 r = su->scale / 40.0;
1059                 break;
1060         case SCALE_CM:
1061                 r = su->scale * 6.0 / 2.54;
1062                 break;
1063         case SCALE_FS:
1064                 r = su->scale * 65536.0 / 40.0;
1065                 break;
1066         case SCALE_IN:
1067                 r = su->scale * 6.0;
1068                 break;
1069         case SCALE_MM:
1070                 r = su->scale * 0.006;
1071                 break;
1072         case SCALE_PC:
1073                 r = su->scale;
1074                 break;
1075         case SCALE_PT:
1076                 r = su->scale / 12.0;
1077                 break;
1078         case SCALE_EN:
1079         case SCALE_EM:
1080                 r = su->scale * 0.6;
1081                 break;
1082         case SCALE_VS:
1083                 r = su->scale;
1084                 break;
1085         default:
1086                 abort();
1087         }
1088         ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
1089         return ri < 66 ? ri : 1;
1090 }
1091
1092 /*
1093  * Convert a scaling width to basic units, rounding towards 0.
1094  */
1095 int
1096 term_hspan(const struct termp *p, const struct roffsu *su)
1097 {
1098
1099         return (*p->hspan)(p, su);
1100 }
1101
1102 /*
1103  * Convert a scaling width to basic units, rounding to closest.
1104  */
1105 int
1106 term_hen(const struct termp *p, const struct roffsu *su)
1107 {
1108         int bu;
1109
1110         if ((bu = (*p->hspan)(p, su)) >= 0)
1111                 return (bu + 11) / 24;
1112         else
1113                 return -((-bu + 11) / 24);
1114 }