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