netinet{,6}: Assert in{,6}_inithead() are only used for system routing tables.
[dragonfly.git] / contrib / mdocml / term.c
1 /*      $Id: term.c,v 1.226 2014/08/01 19:38:29 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010-2014 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 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include <sys/types.h>
23
24 #include <assert.h>
25 #include <ctype.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "mandoc.h"
31 #include "mandoc_aux.h"
32 #include "out.h"
33 #include "term.h"
34 #include "main.h"
35
36 static  size_t           cond_width(const struct termp *, int, int *);
37 static  void             adjbuf(struct termp *p, size_t);
38 static  void             bufferc(struct termp *, char);
39 static  void             encode(struct termp *, const char *, size_t);
40 static  void             encode1(struct termp *, int);
41
42
43 void
44 term_free(struct termp *p)
45 {
46
47         if (p->buf)
48                 free(p->buf);
49         if (p->symtab)
50                 mchars_free(p->symtab);
51
52         free(p);
53 }
54
55 void
56 term_begin(struct termp *p, term_margin head,
57                 term_margin foot, const void *arg)
58 {
59
60         p->headf = head;
61         p->footf = foot;
62         p->argf = arg;
63         (*p->begin)(p);
64 }
65
66 void
67 term_end(struct termp *p)
68 {
69
70         (*p->end)(p);
71 }
72
73 /*
74  * Flush a chunk of text.  By default, break the output line each time
75  * the right margin is reached, and continue output on the next line
76  * at the same offset as the chunk itself.  By default, also break the
77  * output line at the end of the chunk.
78  * The following flags may be specified:
79  *
80  *  - TERMP_NOBREAK: Do not break the output line at the right margin,
81  *    but only at the max right margin.  Also, do not break the output
82  *    line at the end of the chunk, such that the next call can pad to
83  *    the next column.  However, if less than p->trailspace blanks,
84  *    which can be 0, 1, or 2, remain to the right margin, the line
85  *    will be broken.
86  *  - TERMP_BRIND: If the chunk does not fit and the output line has
87  *    to be broken, start the next line at the right margin instead
88  *    of at the offset.  Used together with TERMP_NOBREAK for the tags
89  *    in various kinds of tagged lists.
90  *  - TERMP_DANGLE: Do not break the output line at the right margin,
91  *    append the next chunk after it even if this one is too long.
92  *    To be used together with TERMP_NOBREAK.
93  *  - TERMP_HANG: Like TERMP_DANGLE, and also suppress padding before
94  *    the next chunk if this column is not full.
95  */
96 void
97 term_flushln(struct termp *p)
98 {
99         size_t           i;     /* current input position in p->buf */
100         int              ntab;  /* number of tabs to prepend */
101         size_t           vis;   /* current visual position on output */
102         size_t           vbl;   /* number of blanks to prepend to output */
103         size_t           vend;  /* end of word visual position on output */
104         size_t           bp;    /* visual right border position */
105         size_t           dv;    /* temporary for visual pos calculations */
106         size_t           j;     /* temporary loop index for p->buf */
107         size_t           jhy;   /* last hyph before overflow w/r/t j */
108         size_t           maxvis; /* output position of visible boundary */
109         size_t           mmax; /* used in calculating bp */
110
111         /*
112          * First, establish the maximum columns of "visible" content.
113          * This is usually the difference between the right-margin and
114          * an indentation, but can be, for tagged lists or columns, a
115          * small set of values.
116          *
117          * The following unsigned-signed subtractions look strange,
118          * but they are actually correct.  If the int p->overstep
119          * is negative, it gets sign extended.  Subtracting that
120          * very large size_t effectively adds a small number to dv.
121          */
122         assert  (p->rmargin >= p->offset);
123         dv     = p->rmargin - p->offset;
124         maxvis = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
125         dv     = p->maxrmargin - p->offset;
126         mmax   = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
127
128         bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
129
130         /*
131          * Calculate the required amount of padding.
132          */
133         vbl = p->offset + p->overstep > p->viscol ?
134               p->offset + p->overstep - p->viscol : 0;
135
136         vis = vend = 0;
137         i = 0;
138
139         while (i < p->col) {
140                 /*
141                  * Handle literal tab characters: collapse all
142                  * subsequent tabs into a single huge set of spaces.
143                  */
144                 ntab = 0;
145                 while (i < p->col && '\t' == p->buf[i]) {
146                         vend = (vis / p->tabwidth + 1) * p->tabwidth;
147                         vbl += vend - vis;
148                         vis = vend;
149                         ntab++;
150                         i++;
151                 }
152
153                 /*
154                  * Count up visible word characters.  Control sequences
155                  * (starting with the CSI) aren't counted.  A space
156                  * generates a non-printing word, which is valid (the
157                  * space is printed according to regular spacing rules).
158                  */
159
160                 for (j = i, jhy = 0; j < p->col; j++) {
161                         if (' ' == p->buf[j] || '\t' == p->buf[j])
162                                 break;
163
164                         /* Back over the the last printed character. */
165                         if (8 == p->buf[j]) {
166                                 assert(j);
167                                 vend -= (*p->width)(p, p->buf[j - 1]);
168                                 continue;
169                         }
170
171                         /* Regular word. */
172                         /* Break at the hyphen point if we overrun. */
173                         if (vend > vis && vend < bp &&
174                             (ASCII_HYPH == p->buf[j] ||
175                              ASCII_BREAK == p->buf[j]))
176                                 jhy = j;
177
178                         /*
179                          * Hyphenation now decided, put back a real
180                          * hyphen such that we get the correct width.
181                          */
182                         if (ASCII_HYPH == p->buf[j])
183                                 p->buf[j] = '-';
184
185                         vend += (*p->width)(p, p->buf[j]);
186                 }
187
188                 /*
189                  * Find out whether we would exceed the right margin.
190                  * If so, break to the next line.
191                  */
192                 if (vend > bp && 0 == jhy && vis > 0) {
193                         vend -= vis;
194                         (*p->endline)(p);
195                         p->viscol = 0;
196                         if (TERMP_BRIND & p->flags) {
197                                 vbl = p->rmargin;
198                                 vend += p->rmargin - p->offset;
199                         } else
200                                 vbl = p->offset;
201
202                         /* use pending tabs on the new line */
203
204                         if (0 < ntab)
205                                 vbl += ntab * p->tabwidth;
206
207                         /*
208                          * Remove the p->overstep width.
209                          * Again, if p->overstep is negative,
210                          * sign extension does the right thing.
211                          */
212
213                         bp += (size_t)p->overstep;
214                         p->overstep = 0;
215                 }
216
217                 /* Write out the [remaining] word. */
218                 for ( ; i < p->col; i++) {
219                         if (vend > bp && jhy > 0 && i > jhy)
220                                 break;
221                         if ('\t' == p->buf[i])
222                                 break;
223                         if (' ' == p->buf[i]) {
224                                 j = i;
225                                 while (' ' == p->buf[i])
226                                         i++;
227                                 dv = (i - j) * (*p->width)(p, ' ');
228                                 vbl += dv;
229                                 vend += dv;
230                                 break;
231                         }
232                         if (ASCII_NBRSP == p->buf[i]) {
233                                 vbl += (*p->width)(p, ' ');
234                                 continue;
235                         }
236                         if (ASCII_BREAK == p->buf[i])
237                                 continue;
238
239                         /*
240                          * Now we definitely know there will be
241                          * printable characters to output,
242                          * so write preceding white space now.
243                          */
244                         if (vbl) {
245                                 (*p->advance)(p, vbl);
246                                 p->viscol += vbl;
247                                 vbl = 0;
248                         }
249
250                         (*p->letter)(p, p->buf[i]);
251                         if (8 == p->buf[i])
252                                 p->viscol -= (*p->width)(p, p->buf[i-1]);
253                         else
254                                 p->viscol += (*p->width)(p, p->buf[i]);
255                 }
256                 vis = vend;
257         }
258
259         /*
260          * If there was trailing white space, it was not printed;
261          * so reset the cursor position accordingly.
262          */
263         if (vis)
264                 vis -= vbl;
265
266         p->col = 0;
267         p->overstep = 0;
268
269         if ( ! (TERMP_NOBREAK & p->flags)) {
270                 p->viscol = 0;
271                 (*p->endline)(p);
272                 return;
273         }
274
275         if (TERMP_HANG & p->flags) {
276                 p->overstep = (int)(vis - maxvis +
277                     p->trailspace * (*p->width)(p, ' '));
278
279                 /*
280                  * If we have overstepped the margin, temporarily move
281                  * it to the right and flag the rest of the line to be
282                  * shorter.
283                  * If there is a request to keep the columns together,
284                  * allow negative overstep when the column is not full.
285                  */
286                 if (p->trailspace && p->overstep < 0)
287                         p->overstep = 0;
288                 return;
289
290         } else if (TERMP_DANGLE & p->flags)
291                 return;
292
293         /* If the column was overrun, break the line. */
294         if (maxvis < vis + p->trailspace * (*p->width)(p, ' ')) {
295                 (*p->endline)(p);
296                 p->viscol = 0;
297         }
298 }
299
300 /*
301  * A newline only breaks an existing line; it won't assert vertical
302  * space.  All data in the output buffer is flushed prior to the newline
303  * assertion.
304  */
305 void
306 term_newln(struct termp *p)
307 {
308
309         p->flags |= TERMP_NOSPACE;
310         if (p->col || p->viscol)
311                 term_flushln(p);
312 }
313
314 /*
315  * Asserts a vertical space (a full, empty line-break between lines).
316  * Note that if used twice, this will cause two blank spaces and so on.
317  * All data in the output buffer is flushed prior to the newline
318  * assertion.
319  */
320 void
321 term_vspace(struct termp *p)
322 {
323
324         term_newln(p);
325         p->viscol = 0;
326         if (0 < p->skipvsp)
327                 p->skipvsp--;
328         else
329                 (*p->endline)(p);
330 }
331
332 void
333 term_fontlast(struct termp *p)
334 {
335         enum termfont    f;
336
337         f = p->fontl;
338         p->fontl = p->fontq[p->fonti];
339         p->fontq[p->fonti] = f;
340 }
341
342 void
343 term_fontrepl(struct termp *p, enum termfont f)
344 {
345
346         p->fontl = p->fontq[p->fonti];
347         p->fontq[p->fonti] = f;
348 }
349
350 void
351 term_fontpush(struct termp *p, enum termfont f)
352 {
353
354         assert(p->fonti + 1 < 10);
355         p->fontl = p->fontq[p->fonti];
356         p->fontq[++p->fonti] = f;
357 }
358
359 const void *
360 term_fontq(struct termp *p)
361 {
362
363         return(&p->fontq[p->fonti]);
364 }
365
366 enum termfont
367 term_fonttop(struct termp *p)
368 {
369
370         return(p->fontq[p->fonti]);
371 }
372
373 void
374 term_fontpopq(struct termp *p, const void *key)
375 {
376
377         while (p->fonti >= 0 && key < (void *)(p->fontq + p->fonti))
378                 p->fonti--;
379         assert(p->fonti >= 0);
380 }
381
382 void
383 term_fontpop(struct termp *p)
384 {
385
386         assert(p->fonti);
387         p->fonti--;
388 }
389
390 /*
391  * Handle pwords, partial words, which may be either a single word or a
392  * phrase that cannot be broken down (such as a literal string).  This
393  * handles word styling.
394  */
395 void
396 term_word(struct termp *p, const char *word)
397 {
398         const char       nbrsp[2] = { ASCII_NBRSP, 0 };
399         const char      *seq, *cp;
400         char             c;
401         int              sz, uc;
402         size_t           ssz;
403         enum mandoc_esc  esc;
404
405         if ( ! (TERMP_NOSPACE & p->flags)) {
406                 if ( ! (TERMP_KEEP & p->flags)) {
407                         bufferc(p, ' ');
408                         if (TERMP_SENTENCE & p->flags)
409                                 bufferc(p, ' ');
410                 } else
411                         bufferc(p, ASCII_NBRSP);
412         }
413         if (TERMP_PREKEEP & p->flags)
414                 p->flags |= TERMP_KEEP;
415
416         if ( ! (p->flags & TERMP_NONOSPACE))
417                 p->flags &= ~TERMP_NOSPACE;
418         else
419                 p->flags |= TERMP_NOSPACE;
420
421         p->flags &= ~TERMP_SENTENCE;
422
423         while ('\0' != *word) {
424                 if ('\\' != *word) {
425                         if (TERMP_SKIPCHAR & p->flags) {
426                                 p->flags &= ~TERMP_SKIPCHAR;
427                                 word++;
428                                 continue;
429                         }
430                         if (TERMP_NBRWORD & p->flags) {
431                                 if (' ' == *word) {
432                                         encode(p, nbrsp, 1);
433                                         word++;
434                                         continue;
435                                 }
436                                 ssz = strcspn(word, "\\ ");
437                         } else
438                                 ssz = strcspn(word, "\\");
439                         encode(p, word, ssz);
440                         word += (int)ssz;
441                         continue;
442                 }
443
444                 word++;
445                 esc = mandoc_escape(&word, &seq, &sz);
446                 if (ESCAPE_ERROR == esc)
447                         continue;
448
449                 if (TERMENC_ASCII != p->enc)
450                         switch (esc) {
451                         case ESCAPE_UNICODE:
452                                 uc = mchars_num2uc(seq + 1, sz - 1);
453                                 if ('\0' == uc)
454                                         break;
455                                 encode1(p, uc);
456                                 continue;
457                         case ESCAPE_SPECIAL:
458                                 uc = mchars_spec2cp(p->symtab, seq, sz);
459                                 if (uc <= 0)
460                                         break;
461                                 encode1(p, uc);
462                                 continue;
463                         default:
464                                 break;
465                         }
466
467                 switch (esc) {
468                 case ESCAPE_UNICODE:
469                         encode1(p, '?');
470                         break;
471                 case ESCAPE_NUMBERED:
472                         c = mchars_num2char(seq, sz);
473                         if ('\0' != c)
474                                 encode(p, &c, 1);
475                         break;
476                 case ESCAPE_SPECIAL:
477                         cp = mchars_spec2str(p->symtab, seq, sz, &ssz);
478                         if (NULL != cp)
479                                 encode(p, cp, ssz);
480                         else if (1 == ssz)
481                                 encode(p, seq, sz);
482                         break;
483                 case ESCAPE_FONTBOLD:
484                         term_fontrepl(p, TERMFONT_BOLD);
485                         break;
486                 case ESCAPE_FONTITALIC:
487                         term_fontrepl(p, TERMFONT_UNDER);
488                         break;
489                 case ESCAPE_FONTBI:
490                         term_fontrepl(p, TERMFONT_BI);
491                         break;
492                 case ESCAPE_FONT:
493                         /* FALLTHROUGH */
494                 case ESCAPE_FONTROMAN:
495                         term_fontrepl(p, TERMFONT_NONE);
496                         break;
497                 case ESCAPE_FONTPREV:
498                         term_fontlast(p);
499                         break;
500                 case ESCAPE_NOSPACE:
501                         if (TERMP_SKIPCHAR & p->flags)
502                                 p->flags &= ~TERMP_SKIPCHAR;
503                         else if ('\0' == *word)
504                                 p->flags |= TERMP_NOSPACE;
505                         break;
506                 case ESCAPE_SKIPCHAR:
507                         p->flags |= TERMP_SKIPCHAR;
508                         break;
509                 default:
510                         break;
511                 }
512         }
513         p->flags &= ~TERMP_NBRWORD;
514 }
515
516 static void
517 adjbuf(struct termp *p, size_t sz)
518 {
519
520         if (0 == p->maxcols)
521                 p->maxcols = 1024;
522         while (sz >= p->maxcols)
523                 p->maxcols <<= 2;
524
525         p->buf = mandoc_reallocarray(p->buf, p->maxcols, sizeof(int));
526 }
527
528 static void
529 bufferc(struct termp *p, char c)
530 {
531
532         if (p->col + 1 >= p->maxcols)
533                 adjbuf(p, p->col + 1);
534
535         p->buf[p->col++] = c;
536 }
537
538 /*
539  * See encode().
540  * Do this for a single (probably unicode) value.
541  * Does not check for non-decorated glyphs.
542  */
543 static void
544 encode1(struct termp *p, int c)
545 {
546         enum termfont     f;
547
548         if (TERMP_SKIPCHAR & p->flags) {
549                 p->flags &= ~TERMP_SKIPCHAR;
550                 return;
551         }
552
553         if (p->col + 6 >= p->maxcols)
554                 adjbuf(p, p->col + 6);
555
556         f = term_fonttop(p);
557
558         if (TERMFONT_UNDER == f || TERMFONT_BI == f) {
559                 p->buf[p->col++] = '_';
560                 p->buf[p->col++] = 8;
561         }
562         if (TERMFONT_BOLD == f || TERMFONT_BI == f) {
563                 if (ASCII_HYPH == c)
564                         p->buf[p->col++] = '-';
565                 else
566                         p->buf[p->col++] = c;
567                 p->buf[p->col++] = 8;
568         }
569         p->buf[p->col++] = c;
570 }
571
572 static void
573 encode(struct termp *p, const char *word, size_t sz)
574 {
575         size_t            i;
576
577         if (TERMP_SKIPCHAR & p->flags) {
578                 p->flags &= ~TERMP_SKIPCHAR;
579                 return;
580         }
581
582         /*
583          * Encode and buffer a string of characters.  If the current
584          * font mode is unset, buffer directly, else encode then buffer
585          * character by character.
586          */
587
588         if (TERMFONT_NONE == term_fonttop(p)) {
589                 if (p->col + sz >= p->maxcols)
590                         adjbuf(p, p->col + sz);
591                 for (i = 0; i < sz; i++)
592                         p->buf[p->col++] = word[i];
593                 return;
594         }
595
596         /* Pre-buffer, assuming worst-case. */
597
598         if (p->col + 1 + (sz * 5) >= p->maxcols)
599                 adjbuf(p, p->col + 1 + (sz * 5));
600
601         for (i = 0; i < sz; i++) {
602                 if (ASCII_HYPH == word[i] ||
603                     isgraph((unsigned char)word[i]))
604                         encode1(p, word[i]);
605                 else
606                         p->buf[p->col++] = word[i];
607         }
608 }
609
610 void
611 term_setwidth(struct termp *p, const char *wstr)
612 {
613         struct roffsu    su;
614         size_t           width;
615         int              iop;
616
617         iop = 0;
618         width = 0;
619         if (NULL != wstr) {
620                 switch (*wstr) {
621                 case '+':
622                         iop = 1;
623                         wstr++;
624                         break;
625                 case '-':
626                         iop = -1;
627                         wstr++;
628                         break;
629                 default:
630                         break;
631                 }
632                 if (a2roffsu(wstr, &su, SCALE_MAX))
633                         width = term_hspan(p, &su);
634                 else
635                         iop = 0;
636         }
637         (*p->setwidth)(p, iop, width);
638 }
639
640 size_t
641 term_len(const struct termp *p, size_t sz)
642 {
643
644         return((*p->width)(p, ' ') * sz);
645 }
646
647 static size_t
648 cond_width(const struct termp *p, int c, int *skip)
649 {
650
651         if (*skip) {
652                 (*skip) = 0;
653                 return(0);
654         } else
655                 return((*p->width)(p, c));
656 }
657
658 size_t
659 term_strlen(const struct termp *p, const char *cp)
660 {
661         size_t           sz, rsz, i;
662         int              ssz, skip, c;
663         const char      *seq, *rhs;
664         enum mandoc_esc  esc;
665         static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
666                         ASCII_BREAK, '\0' };
667
668         /*
669          * Account for escaped sequences within string length
670          * calculations.  This follows the logic in term_word() as we
671          * must calculate the width of produced strings.
672          */
673
674         sz = 0;
675         skip = 0;
676         while ('\0' != *cp) {
677                 rsz = strcspn(cp, rej);
678                 for (i = 0; i < rsz; i++)
679                         sz += cond_width(p, *cp++, &skip);
680
681                 switch (*cp) {
682                 case '\\':
683                         cp++;
684                         esc = mandoc_escape(&cp, &seq, &ssz);
685                         if (ESCAPE_ERROR == esc)
686                                 continue;
687
688                         if (TERMENC_ASCII != p->enc)
689                                 switch (esc) {
690                                 case ESCAPE_UNICODE:
691                                         c = mchars_num2uc(seq + 1,
692                                             ssz - 1);
693                                         if ('\0' == c)
694                                                 break;
695                                         sz += cond_width(p, c, &skip);
696                                         continue;
697                                 case ESCAPE_SPECIAL:
698                                         c = mchars_spec2cp(p->symtab,
699                                             seq, ssz);
700                                         if (c <= 0)
701                                                 break;
702                                         sz += cond_width(p, c, &skip);
703                                         continue;
704                                 default:
705                                         break;
706                                 }
707
708                         rhs = NULL;
709
710                         switch (esc) {
711                         case ESCAPE_UNICODE:
712                                 sz += cond_width(p, '?', &skip);
713                                 break;
714                         case ESCAPE_NUMBERED:
715                                 c = mchars_num2char(seq, ssz);
716                                 if ('\0' != c)
717                                         sz += cond_width(p, c, &skip);
718                                 break;
719                         case ESCAPE_SPECIAL:
720                                 rhs = mchars_spec2str(p->symtab,
721                                     seq, ssz, &rsz);
722
723                                 if (ssz != 1 || rhs)
724                                         break;
725
726                                 rhs = seq;
727                                 rsz = ssz;
728                                 break;
729                         case ESCAPE_SKIPCHAR:
730                                 skip = 1;
731                                 break;
732                         default:
733                                 break;
734                         }
735
736                         if (NULL == rhs)
737                                 break;
738
739                         if (skip) {
740                                 skip = 0;
741                                 break;
742                         }
743
744                         for (i = 0; i < rsz; i++)
745                                 sz += (*p->width)(p, *rhs++);
746                         break;
747                 case ASCII_NBRSP:
748                         sz += cond_width(p, ' ', &skip);
749                         cp++;
750                         break;
751                 case ASCII_HYPH:
752                         sz += cond_width(p, '-', &skip);
753                         cp++;
754                         /* FALLTHROUGH */
755                 case ASCII_BREAK:
756                         break;
757                 default:
758                         break;
759                 }
760         }
761
762         return(sz);
763 }
764
765 size_t
766 term_vspan(const struct termp *p, const struct roffsu *su)
767 {
768         double           r;
769
770         switch (su->unit) {
771         case SCALE_CM:
772                 r = su->scale * 2.0;
773                 break;
774         case SCALE_IN:
775                 r = su->scale * 6.0;
776                 break;
777         case SCALE_PC:
778                 r = su->scale;
779                 break;
780         case SCALE_PT:
781                 r = su->scale / 8.0;
782                 break;
783         case SCALE_MM:
784                 r = su->scale / 1000.0;
785                 break;
786         case SCALE_VS:
787                 r = su->scale;
788                 break;
789         default:
790                 r = su->scale - 1.0;
791                 break;
792         }
793
794         if (r < 0.0)
795                 r = 0.0;
796         return((size_t)(r + 0.0005));
797 }
798
799 size_t
800 term_hspan(const struct termp *p, const struct roffsu *su)
801 {
802         double           v;
803
804         v = (*p->hspan)(p, su);
805         if (v < 0.0)
806                 v = 0.0;
807         return((size_t)(v + 0.0005));
808 }