]> git.cameronkatri.com Git - mandoc.git/blob - term.c
f8e7c395e2ba83d3a115d525b2955890615bb558
[mandoc.git] / term.c
1 /* $Id: term.c,v 1.142 2010/06/07 20:57:09 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include <sys/types.h>
22
23 #include <assert.h>
24 #include <ctype.h>
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30
31 #include "mandoc.h"
32 #include "chars.h"
33 #include "out.h"
34 #include "term.h"
35 #include "man.h"
36 #include "mdoc.h"
37 #include "main.h"
38
39 #define PS_CHAR_WIDTH 6
40 #define PS_CHAR_HEIGHT 12
41 #define PS_CHAR_TOPMARG (792 - 24)
42 #define PS_CHAR_TOP (PS_CHAR_TOPMARG - 36)
43 #define PS_CHAR_LEFT 36
44 #define PS_CHAR_BOTMARG 24
45 #define PS_CHAR_BOT (PS_CHAR_BOTMARG + 36)
46
47 static struct termp *alloc(char *, enum termenc, enum termtype);
48 static void term_free(struct termp *);
49 static void spec(struct termp *, const char *, size_t);
50 static void res(struct termp *, const char *, size_t);
51 static void buffera(struct termp *, const char *, size_t);
52 static void bufferc(struct termp *, char);
53 static void adjbuf(struct termp *p, size_t);
54 static void encode(struct termp *, const char *, size_t);
55 static void advance(struct termp *, size_t);
56 static void endline(struct termp *);
57 static void letter(struct termp *, char);
58 static void pageopen(struct termp *);
59
60
61 void *
62 ascii_alloc(char *outopts)
63 {
64
65 return(alloc(outopts, TERMENC_ASCII, TERMTYPE_CHAR));
66 }
67
68
69 void *
70 ps_alloc(void)
71 {
72
73 return(alloc(NULL, TERMENC_ASCII, TERMTYPE_PS));
74 }
75
76
77 void
78 terminal_free(void *arg)
79 {
80
81 term_free((struct termp *)arg);
82 }
83
84
85 static void
86 term_free(struct termp *p)
87 {
88
89 if (p->buf)
90 free(p->buf);
91 if (p->symtab)
92 chars_free(p->symtab);
93 free(p);
94 }
95
96
97 /*
98 * Push a single letter into our output engine.
99 */
100 static void
101 letter(struct termp *p, char c)
102 {
103
104 if (TERMTYPE_CHAR == p->type) {
105 /*
106 * If using the terminal device, just push the letter
107 * out into the screen.
108 */
109 putchar(c);
110 return;
111 }
112
113 if ( ! (PS_INLINE & p->psstate)) {
114 /*
115 * If we're not in a PostScript "word" context, then
116 * open one now at the current cursor.
117 */
118 printf("%zu %zu moveto\n", p->pscol, p->psrow);
119 putchar('(');
120 p->psstate |= PS_INLINE;
121 }
122
123 /*
124 * We need to escape these characters as per the PostScript
125 * specification. We would also escape non-graphable characters
126 * (like tabs), but none of them would get to this point and
127 * it's superfluous to abort() on them.
128 */
129
130 switch (c) {
131 case ('('):
132 /* FALLTHROUGH */
133 case (')'):
134 /* FALLTHROUGH */
135 case ('\\'):
136 putchar('\\');
137 break;
138 default:
139 break;
140 }
141
142 /* Write the character and adjust where we are on the page. */
143 putchar(c);
144 p->pscol += PS_CHAR_WIDTH;
145 }
146
147
148 /*
149 * Begin a "terminal" context. Since terminal encompasses PostScript,
150 * the actual terminal, etc., there are a few things we can do here.
151 */
152 void
153 term_begin(struct termp *p, term_margin head,
154 term_margin foot, const void *arg)
155 {
156
157 p->headf = head;
158 p->footf = foot;
159 p->argf = arg;
160
161 if (TERMTYPE_CHAR == p->type) {
162 /* Emit the header and be done. */
163 (*p->headf)(p, p->argf);
164 return;
165 }
166
167 /*
168 * Emit the standard PostScript prologue, set our initial page
169 * position, then run pageopen() on the initial page.
170 */
171
172 printf("%s\n", "%!PS");
173 printf("%s\n", "/Courier");
174 printf("%s\n", "10 selectfont");
175
176 p->pspage = 1;
177 p->psstate = 0;
178 pageopen(p);
179 }
180
181
182 /*
183 * Open a page. This is only used for -Tps at the moment. It opens a
184 * page context, printing the header and the footer. THE OUTPUT BUFFER
185 * MUST BE EMPTY. If it is not, output will ghost on the next line and
186 * we'll be all gross and out of state.
187 */
188 static void
189 pageopen(struct termp *p)
190 {
191
192 assert(TERMTYPE_PS == p->type);
193 assert(0 == p->psstate);
194
195 p->pscol = PS_CHAR_LEFT;
196 p->psrow = PS_CHAR_TOPMARG;
197 p->psstate |= PS_MARGINS;
198
199 (*p->headf)(p, p->argf);
200 endline(p);
201
202 p->psstate &= ~PS_MARGINS;
203 assert(0 == p->psstate);
204
205 p->pscol = PS_CHAR_LEFT;
206 p->psrow = PS_CHAR_BOTMARG;
207 p->psstate |= PS_MARGINS;
208
209 (*p->footf)(p, p->argf);
210 endline(p);
211
212 p->psstate &= ~PS_MARGINS;
213 assert(0 == p->psstate);
214
215 p->pscol = PS_CHAR_LEFT;
216 p->psrow = PS_CHAR_TOP;
217
218 }
219
220
221 void
222 term_end(struct termp *p)
223 {
224
225 if (TERMTYPE_CHAR == p->type) {
226 (*p->footf)(p, p->argf);
227 return;
228 }
229
230 printf("%s\n", "%%END");
231 }
232
233
234 static void
235 endline(struct termp *p)
236 {
237
238 if (TERMTYPE_CHAR == p->type) {
239 putchar('\n');
240 return;
241 }
242
243 if (PS_INLINE & p->psstate) {
244 printf(") show\n");
245 p->psstate &= ~PS_INLINE;
246 }
247
248 if (PS_MARGINS & p->psstate)
249 return;
250
251 p->pscol = PS_CHAR_LEFT;
252 if (p->psrow >= PS_CHAR_HEIGHT + PS_CHAR_BOT) {
253 p->psrow -= PS_CHAR_HEIGHT;
254 return;
255 }
256
257 /*
258 * XXX: can't run pageopen() until we're certain a flushln() has
259 * occured, else the buf will reopen in an awkward state on the
260 * next line.
261 */
262 printf("showpage\n");
263 p->psrow = PS_CHAR_TOP;
264 }
265
266
267 /*
268 * Advance the output engine by a certain amount of whitespace.
269 */
270 static void
271 advance(struct termp *p, size_t len)
272 {
273 size_t i;
274
275 if (TERMTYPE_CHAR == p->type) {
276 /* Just print whitespace on the terminal. */
277 for (i = 0; i < len; i++)
278 putchar(' ');
279 return;
280 }
281
282 if (PS_INLINE & p->psstate) {
283 /* Dump out any existing line scope. */
284 printf(") show\n");
285 p->psstate &= ~PS_INLINE;
286 }
287
288 p->pscol += len ? len * PS_CHAR_WIDTH : 0;
289 }
290
291
292 static struct termp *
293 alloc(char *outopts, enum termenc enc, enum termtype type)
294 {
295 struct termp *p;
296 const char *toks[2];
297 char *v;
298 size_t width;
299
300 toks[0] = "width";
301 toks[1] = NULL;
302
303 p = calloc(1, sizeof(struct termp));
304 if (NULL == p) {
305 perror(NULL);
306 exit(EXIT_FAILURE);
307 }
308
309 p->type = type;
310 p->tabwidth = 5;
311 p->enc = enc;
312
313 width = 80;
314
315 while (outopts && *outopts)
316 switch (getsubopt(&outopts, UNCONST(toks), &v)) {
317 case (0):
318 width = atoi(v);
319 break;
320 default:
321 break;
322 }
323
324 /* Enforce some lower boundary. */
325 if (width < 60)
326 width = 60;
327 p->defrmargin = width - 2;
328 return(p);
329 }
330
331
332 /*
333 * Flush a line of text. A "line" is loosely defined as being something
334 * that should be followed by a newline, regardless of whether it's
335 * broken apart by newlines getting there. A line can also be a
336 * fragment of a columnar list (`Bl -tag' or `Bl -column'), which does
337 * not have a trailing newline.
338 *
339 * The following flags may be specified:
340 *
341 * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
342 * offset value. This is useful when doing columnar lists where the
343 * prior column has right-padded.
344 *
345 * - TERMP_NOBREAK: this is the most important and is used when making
346 * columns. In short: don't print a newline and instead pad to the
347 * right margin. Used in conjunction with TERMP_NOLPAD.
348 *
349 * - TERMP_TWOSPACE: when padding, make sure there are at least two
350 * space characters of padding. Otherwise, rather break the line.
351 *
352 * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
353 * the line is overrun, and don't pad-right if it's underrun.
354 *
355 * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
356 * overruning, instead save the position and continue at that point
357 * when the next invocation.
358 *
359 * In-line line breaking:
360 *
361 * If TERMP_NOBREAK is specified and the line overruns the right
362 * margin, it will break and pad-right to the right margin after
363 * writing. If maxrmargin is violated, it will break and continue
364 * writing from the right-margin, which will lead to the above scenario
365 * upon exit. Otherwise, the line will break at the right margin.
366 */
367 void
368 term_flushln(struct termp *p)
369 {
370 int i; /* current input position in p->buf */
371 size_t vis; /* current visual position on output */
372 size_t vbl; /* number of blanks to prepend to output */
373 size_t vend; /* end of word visual position on output */
374 size_t bp; /* visual right border position */
375 int j; /* temporary loop index */
376 int jhy; /* last hyphen before line overflow */
377 size_t maxvis, mmax;
378
379 /*
380 * First, establish the maximum columns of "visible" content.
381 * This is usually the difference between the right-margin and
382 * an indentation, but can be, for tagged lists or columns, a
383 * small set of values.
384 */
385
386 assert(p->offset < p->rmargin);
387
388 maxvis = (int)(p->rmargin - p->offset) - p->overstep < 0 ?
389 /* LINTED */
390 0 : p->rmargin - p->offset - p->overstep;
391 mmax = (int)(p->maxrmargin - p->offset) - p->overstep < 0 ?
392 /* LINTED */
393 0 : p->maxrmargin - p->offset - p->overstep;
394
395 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
396
397 /*
398 * Indent the first line of a paragraph.
399 */
400 vbl = p->flags & TERMP_NOLPAD ? 0 : p->offset;
401
402 /*
403 * FIXME: if bp is zero, we still output the first word before
404 * breaking the line.
405 */
406
407 vis = vend = i = 0;
408 while (i < (int)p->col) {
409
410 /*
411 * Handle literal tab characters.
412 */
413 for (j = i; j < (int)p->col; j++) {
414 if ('\t' != p->buf[j])
415 break;
416 vend = (vis/p->tabwidth+1)*p->tabwidth;
417 vbl += vend - vis;
418 vis = vend;
419 }
420
421 /*
422 * Count up visible word characters. Control sequences
423 * (starting with the CSI) aren't counted. A space
424 * generates a non-printing word, which is valid (the
425 * space is printed according to regular spacing rules).
426 */
427
428 /* LINTED */
429 for (jhy = 0; j < (int)p->col; j++) {
430 if ((j && ' ' == p->buf[j]) || '\t' == p->buf[j])
431 break;
432 if (8 != p->buf[j]) {
433 if (vend > vis && vend < bp &&
434 ASCII_HYPH == p->buf[j])
435 jhy = j;
436 vend++;
437 } else
438 vend--;
439 }
440
441 /*
442 * Find out whether we would exceed the right margin.
443 * If so, break to the next line.
444 */
445 if (vend > bp && 0 == jhy && vis > 0) {
446 vend -= vis;
447 endline(p);
448 if (TERMP_NOBREAK & p->flags) {
449 p->viscol = p->rmargin;
450 advance(p, p->rmargin);
451 vend += p->rmargin - p->offset;
452 } else {
453 p->viscol = 0;
454 vbl = p->offset;
455 }
456
457 /* Remove the p->overstep width. */
458
459 bp += (int)/* LINTED */
460 p->overstep;
461 p->overstep = 0;
462 }
463
464 /*
465 * Skip leading tabs, they were handled above.
466 */
467 while (i < (int)p->col && '\t' == p->buf[i])
468 i++;
469
470 /* Write out the [remaining] word. */
471 for ( ; i < (int)p->col; i++) {
472 if (vend > bp && jhy > 0 && i > jhy)
473 break;
474 if ('\t' == p->buf[i])
475 break;
476 if (' ' == p->buf[i]) {
477 while (' ' == p->buf[i]) {
478 vbl++;
479 i++;
480 }
481 break;
482 }
483 if (ASCII_NBRSP == p->buf[i]) {
484 vbl++;
485 continue;
486 }
487
488 /*
489 * Now we definitely know there will be
490 * printable characters to output,
491 * so write preceding white space now.
492 */
493 if (vbl) {
494 advance(p, vbl);
495 p->viscol += vbl;
496 vbl = 0;
497 }
498
499 if (ASCII_HYPH == p->buf[i])
500 letter(p, '-');
501 else
502 letter(p, p->buf[i]);
503
504 p->viscol += 1;
505 }
506 vend += vbl;
507 vis = vend;
508 }
509
510 p->col = 0;
511 p->overstep = 0;
512
513 if ( ! (TERMP_NOBREAK & p->flags)) {
514 p->viscol = 0;
515 endline(p);
516 return;
517 }
518
519 if (TERMP_HANG & p->flags) {
520 /* We need one blank after the tag. */
521 p->overstep = /* LINTED */
522 vis - maxvis + 1;
523
524 /*
525 * Behave exactly the same way as groff:
526 * If we have overstepped the margin, temporarily move
527 * it to the right and flag the rest of the line to be
528 * shorter.
529 * If we landed right at the margin, be happy.
530 * If we are one step before the margin, temporarily
531 * move it one step LEFT and flag the rest of the line
532 * to be longer.
533 */
534 if (p->overstep >= -1) {
535 assert((int)maxvis + p->overstep >= 0);
536 /* LINTED */
537 maxvis += p->overstep;
538 } else
539 p->overstep = 0;
540
541 } else if (TERMP_DANGLE & p->flags)
542 return;
543
544 /* Right-pad. */
545 if (maxvis > vis + /* LINTED */
546 ((TERMP_TWOSPACE & p->flags) ? 1 : 0)) {
547 p->viscol += maxvis - vis;
548 advance(p, maxvis - vis);
549 vis += (maxvis - vis);
550 } else { /* ...or newline break. */
551 endline(p);
552 p->viscol = p->rmargin;
553 advance(p, p->rmargin);
554 }
555 }
556
557
558 /*
559 * A newline only breaks an existing line; it won't assert vertical
560 * space. All data in the output buffer is flushed prior to the newline
561 * assertion.
562 */
563 void
564 term_newln(struct termp *p)
565 {
566
567 p->flags |= TERMP_NOSPACE;
568 if (0 == p->col && 0 == p->viscol) {
569 p->flags &= ~TERMP_NOLPAD;
570 return;
571 }
572 term_flushln(p);
573 p->flags &= ~TERMP_NOLPAD;
574 }
575
576
577 /*
578 * Asserts a vertical space (a full, empty line-break between lines).
579 * Note that if used twice, this will cause two blank spaces and so on.
580 * All data in the output buffer is flushed prior to the newline
581 * assertion.
582 */
583 void
584 term_vspace(struct termp *p)
585 {
586
587 term_newln(p);
588 p->viscol = 0;
589 endline(p);
590 }
591
592
593 static void
594 spec(struct termp *p, const char *word, size_t len)
595 {
596 const char *rhs;
597 size_t sz;
598
599 rhs = chars_a2ascii(p->symtab, word, len, &sz);
600 if (rhs)
601 encode(p, rhs, sz);
602 }
603
604
605 static void
606 res(struct termp *p, const char *word, size_t len)
607 {
608 const char *rhs;
609 size_t sz;
610
611 rhs = chars_a2res(p->symtab, word, len, &sz);
612 if (rhs)
613 encode(p, rhs, sz);
614 }
615
616
617 void
618 term_fontlast(struct termp *p)
619 {
620 enum termfont f;
621
622 f = p->fontl;
623 p->fontl = p->fontq[p->fonti];
624 p->fontq[p->fonti] = f;
625 }
626
627
628 void
629 term_fontrepl(struct termp *p, enum termfont f)
630 {
631
632 p->fontl = p->fontq[p->fonti];
633 p->fontq[p->fonti] = f;
634 }
635
636
637 void
638 term_fontpush(struct termp *p, enum termfont f)
639 {
640
641 assert(p->fonti + 1 < 10);
642 p->fontl = p->fontq[p->fonti];
643 p->fontq[++p->fonti] = f;
644 }
645
646
647 const void *
648 term_fontq(struct termp *p)
649 {
650
651 return(&p->fontq[p->fonti]);
652 }
653
654
655 enum termfont
656 term_fonttop(struct termp *p)
657 {
658
659 return(p->fontq[p->fonti]);
660 }
661
662
663 void
664 term_fontpopq(struct termp *p, const void *key)
665 {
666
667 while (p->fonti >= 0 && key != &p->fontq[p->fonti])
668 p->fonti--;
669 assert(p->fonti >= 0);
670 }
671
672
673 void
674 term_fontpop(struct termp *p)
675 {
676
677 assert(p->fonti);
678 p->fonti--;
679 }
680
681
682 /*
683 * Handle pwords, partial words, which may be either a single word or a
684 * phrase that cannot be broken down (such as a literal string). This
685 * handles word styling.
686 */
687 void
688 term_word(struct termp *p, const char *word)
689 {
690 const char *sv, *seq;
691 int sz;
692 size_t ssz;
693 enum roffdeco deco;
694
695 sv = word;
696
697 if (word[0] && '\0' == word[1])
698 switch (word[0]) {
699 case('.'):
700 /* FALLTHROUGH */
701 case(','):
702 /* FALLTHROUGH */
703 case(';'):
704 /* FALLTHROUGH */
705 case(':'):
706 /* FALLTHROUGH */
707 case('?'):
708 /* FALLTHROUGH */
709 case('!'):
710 /* FALLTHROUGH */
711 case(')'):
712 /* FALLTHROUGH */
713 case(']'):
714 if ( ! (TERMP_IGNDELIM & p->flags))
715 p->flags |= TERMP_NOSPACE;
716 break;
717 default:
718 break;
719 }
720
721 if ( ! (TERMP_NOSPACE & p->flags)) {
722 bufferc(p, ' ');
723 if (TERMP_SENTENCE & p->flags)
724 bufferc(p, ' ');
725 }
726
727 if ( ! (p->flags & TERMP_NONOSPACE))
728 p->flags &= ~TERMP_NOSPACE;
729
730 p->flags &= ~TERMP_SENTENCE;
731
732 /* FIXME: use strcspn. */
733
734 while (*word) {
735 if ('\\' != *word) {
736 encode(p, word, 1);
737 word++;
738 continue;
739 }
740
741 seq = ++word;
742 sz = a2roffdeco(&deco, &seq, &ssz);
743
744 switch (deco) {
745 case (DECO_RESERVED):
746 res(p, seq, ssz);
747 break;
748 case (DECO_SPECIAL):
749 spec(p, seq, ssz);
750 break;
751 case (DECO_BOLD):
752 term_fontrepl(p, TERMFONT_BOLD);
753 break;
754 case (DECO_ITALIC):
755 term_fontrepl(p, TERMFONT_UNDER);
756 break;
757 case (DECO_ROMAN):
758 term_fontrepl(p, TERMFONT_NONE);
759 break;
760 case (DECO_PREVIOUS):
761 term_fontlast(p);
762 break;
763 default:
764 break;
765 }
766
767 word += sz;
768 if (DECO_NOSPACE == deco && '\0' == *word)
769 p->flags |= TERMP_NOSPACE;
770 }
771
772 /*
773 * Note that we don't process the pipe: the parser sees it as
774 * punctuation, but we don't in terms of typography.
775 */
776 if (sv[0] && 0 == sv[1])
777 switch (sv[0]) {
778 case('('):
779 /* FALLTHROUGH */
780 case('['):
781 p->flags |= TERMP_NOSPACE;
782 break;
783 default:
784 break;
785 }
786 }
787
788
789 static void
790 adjbuf(struct termp *p, size_t sz)
791 {
792
793 if (0 == p->maxcols)
794 p->maxcols = 1024;
795 while (sz >= p->maxcols)
796 p->maxcols <<= 2;
797
798 p->buf = realloc(p->buf, p->maxcols);
799 if (NULL == p->buf) {
800 perror(NULL);
801 exit(EXIT_FAILURE);
802 }
803 }
804
805
806 static void
807 buffera(struct termp *p, const char *word, size_t sz)
808 {
809
810 if (p->col + sz >= p->maxcols)
811 adjbuf(p, p->col + sz);
812
813 memcpy(&p->buf[(int)p->col], word, sz);
814 p->col += sz;
815 }
816
817
818 static void
819 bufferc(struct termp *p, char c)
820 {
821
822 if (p->col + 1 >= p->maxcols)
823 adjbuf(p, p->col + 1);
824
825 p->buf[(int)p->col++] = c;
826 }
827
828
829 static void
830 encode(struct termp *p, const char *word, size_t sz)
831 {
832 enum termfont f;
833 int i;
834
835 /*
836 * Encode and buffer a string of characters. If the current
837 * font mode is unset, buffer directly, else encode then buffer
838 * character by character.
839 */
840
841 if (TERMTYPE_PS == p->type) {
842 buffera(p, word, sz);
843 return;
844 } else if (TERMFONT_NONE == (f = term_fonttop(p))) {
845 buffera(p, word, sz);
846 return;
847 }
848
849 for (i = 0; i < (int)sz; i++) {
850 if ( ! isgraph((u_char)word[i])) {
851 bufferc(p, word[i]);
852 continue;
853 }
854
855 if (TERMFONT_UNDER == f)
856 bufferc(p, '_');
857 else
858 bufferc(p, word[i]);
859
860 bufferc(p, 8);
861 bufferc(p, word[i]);
862 }
863 }
864
865
866 size_t
867 term_vspan(const struct roffsu *su)
868 {
869 double r;
870
871 switch (su->unit) {
872 case (SCALE_CM):
873 r = su->scale * 2;
874 break;
875 case (SCALE_IN):
876 r = su->scale * 6;
877 break;
878 case (SCALE_PC):
879 r = su->scale;
880 break;
881 case (SCALE_PT):
882 r = su->scale / 8;
883 break;
884 case (SCALE_MM):
885 r = su->scale / 1000;
886 break;
887 case (SCALE_VS):
888 r = su->scale;
889 break;
890 default:
891 r = su->scale - 1;
892 break;
893 }
894
895 if (r < 0.0)
896 r = 0.0;
897 return(/* LINTED */(size_t)
898 r);
899 }
900
901
902 size_t
903 term_hspan(const struct roffsu *su)
904 {
905 double r;
906
907 /* XXX: CM, IN, and PT are approximations. */
908
909 switch (su->unit) {
910 case (SCALE_CM):
911 r = 4 * su->scale;
912 break;
913 case (SCALE_IN):
914 /* XXX: this is an approximation. */
915 r = 10 * su->scale;
916 break;
917 case (SCALE_PC):
918 r = (10 * su->scale) / 6;
919 break;
920 case (SCALE_PT):
921 r = (10 * su->scale) / 72;
922 break;
923 case (SCALE_MM):
924 r = su->scale / 1000; /* FIXME: double-check. */
925 break;
926 case (SCALE_VS):
927 r = su->scale * 2 - 1; /* FIXME: double-check. */
928 break;
929 default:
930 r = su->scale;
931 break;
932 }
933
934 if (r < 0.0)
935 r = 0.0;
936 return((size_t)/* LINTED */
937 r);
938 }
939
940