GNU standards require "file name" instead of "filename" in
[pspp-builds.git] / src / output / output.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20 #include <config.h>
21 #include "output.h"
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <errno.h>
25 #include <ctype.h>
26 #include <libpspp/alloc.h>
27 #include <data/file-name.h>
28 #include "htmlP.h"
29 #include "intprops.h"
30 #include <libpspp/misc.h>
31 #include <data/settings.h>
32 #include <libpspp/str.h>
33 #include "error.h"
34
35 #include "gettext.h"
36 #define _(msgid) gettext (msgid)
37
38 /* FIXME? Should the output configuration format be changed to
39    drivername:classname:devicetype:options, where devicetype is zero
40    or more of screen, printer, listing? */
41
42 /* FIXME: Have the reentrancy problems been solved? */
43
44 /* Where the output driver name came from. */
45 enum
46   {
47     OUTP_S_COMMAND_LINE,        /* Specified by the user. */
48     OUTP_S_INIT_FILE            /* `default' or the init file. */
49   };
50
51 /* Names the output drivers to be used. */
52 struct outp_names
53   {
54     char *name;                 /* Name of the output driver. */
55     int source;                 /* OUTP_S_* */
56     struct outp_names *next, *prev;
57   };
58
59 /* Defines an init file macro. */
60 struct outp_defn
61   {
62     char *key;
63     struct string value;
64     struct outp_defn *next, *prev;
65   };
66
67 static struct outp_defn *outp_macros;
68 static struct outp_names *outp_configure_vec;
69
70 /* A list of driver classes. */
71 struct outp_driver_class_list
72   {
73     struct outp_class *class;
74     struct outp_driver_class_list *next;
75   };
76
77 struct outp_driver_class_list *outp_class_list;
78 struct outp_driver *outp_driver_list;
79
80 char *outp_title;
81 char *outp_subtitle;
82
83 /* A set of OUTP_DEV_* bits indicating the devices that are
84    disabled. */
85 static int disabled_devices;
86
87 static void destroy_driver (struct outp_driver *);
88 static void configure_driver_line (struct string *);
89 static void configure_driver (const struct string *, const struct string *,
90                               const struct string *, const struct string *);
91
92 /* Add a class to the class list. */
93 static void
94 add_class (struct outp_class *class)
95 {
96   struct outp_driver_class_list *new_list = xmalloc (sizeof *new_list);
97
98   new_list->class = class;
99
100   if (!outp_class_list)
101     {
102       outp_class_list = new_list;
103       new_list->next = NULL;
104     }
105   else
106     {
107       new_list->next = outp_class_list;
108       outp_class_list = new_list;
109     }
110 }
111
112 /* Finds the outp_names in outp_configure_vec with name between BP and
113    EP exclusive. */
114 static struct outp_names *
115 search_names (char *bp, char *ep)
116 {
117   struct outp_names *n;
118
119   for (n = outp_configure_vec; n; n = n->next)
120     if ((int) strlen (n->name) == ep - bp && !memcmp (n->name, bp, ep - bp))
121       return n;
122   return NULL;
123 }
124
125 /* Deletes outp_names NAME from outp_configure_vec. */
126 static void
127 delete_name (struct outp_names * n)
128 {
129   free (n->name);
130   if (n->prev)
131     n->prev->next = n->next;
132   if (n->next)
133     n->next->prev = n->prev;
134   if (n == outp_configure_vec)
135     outp_configure_vec = n->next;
136   free (n);
137 }
138
139 /* Adds the name between BP and EP exclusive to list
140    outp_configure_vec with source SOURCE. */
141 static void
142 add_name (char *bp, char *ep, int source)
143 {
144   struct outp_names *n = xmalloc (sizeof *n);
145   n->name = xmalloc (ep - bp + 1);
146   memcpy (n->name, bp, ep - bp);
147   n->name[ep - bp] = 0;
148   n->source = source;
149   n->next = outp_configure_vec;
150   n->prev = NULL;
151   if (outp_configure_vec)
152     outp_configure_vec->prev = n;
153   outp_configure_vec = n;
154 }
155
156 /* Checks that outp_configure_vec is empty, bitches & clears it if it
157    isn't. */
158 static void
159 check_configure_vec (void)
160 {
161   struct outp_names *n;
162
163   for (n = outp_configure_vec; n; n = n->next)
164     if (n->source == OUTP_S_COMMAND_LINE)
165       error (0, 0, _("unknown output driver `%s'"), n->name);
166     else
167       error (0, 0, _("output driver `%s' referenced but never defined"),
168              n->name);
169   outp_configure_clear ();
170 }
171
172 /* Searches outp_configure_vec for the name between BP and EP
173    exclusive.  If found, it is deleted, then replaced by the names
174    given in EP+1, if any. */
175 static void
176 expand_name (char *bp, char *ep)
177 {
178   struct outp_names *n = search_names (bp, ep);
179   if (!n)
180     return;
181   delete_name (n);
182
183   bp = ep + 1;
184   for (;;)
185     {
186       while (isspace ((unsigned char) *bp))
187         bp++;
188       ep = bp;
189       while (*ep && !isspace ((unsigned char) *ep))
190         ep++;
191       if (bp == ep)
192         return;
193       if (!search_names (bp, ep))
194         add_name (bp, ep, OUTP_S_INIT_FILE);
195       bp = ep;
196     }
197 }
198
199 /* Looks for a macro with key KEY, and returns the corresponding value
200    if found, or NULL if not. */
201 static const char *
202 find_defn_value (const char *key)
203 {
204   static char buf[INT_STRLEN_BOUND (int) + 1];
205   struct outp_defn *d;
206
207   for (d = outp_macros; d; d = d->next)
208     if (!strcmp (key, d->key))
209       return ds_c_str(&d->value);
210   if (!strcmp (key, "viewwidth"))
211     {
212       sprintf (buf, "%d", get_viewwidth ());
213       return buf;
214     }
215   else if (!strcmp (key, "viewlength"))
216     {
217       sprintf (buf, "%d", get_viewlength ());
218       return buf;
219     }
220   else
221     return getenv (key);
222 }
223
224 /* Initializes global variables. */
225 void
226 outp_init (void)
227 {
228   extern struct outp_class ascii_class;
229   extern struct outp_class postscript_class;
230   extern struct outp_class html_class;
231
232   char def[] = "default";
233
234   add_class (&html_class);
235   add_class (&postscript_class);
236   add_class (&ascii_class);
237
238   add_name (def, &def[strlen (def)], OUTP_S_INIT_FILE);
239 }
240
241 /* Deletes all the output macros. */
242 static void
243 delete_macros (void)
244 {
245   struct outp_defn *d, *next;
246
247   for (d = outp_macros; d; d = next)
248     {
249       next = d->next;
250       free (d->key);
251       ds_destroy (&d->value);
252       free (d);
253     }
254 }
255
256 static void
257 init_default_drivers (void) 
258 {
259   struct string s;
260
261   error (0, 0, _("using default output driver configuration"));
262
263   ds_create (&s,
264              "list:ascii:listing:"
265              "length=66 width=79 output-file=\"pspp.list\"");
266   configure_driver_line (&s);
267   ds_destroy (&s);
268 }
269
270 /* Reads the initialization file; initializes
271    outp_driver_list. */
272 void
273 outp_read_devices (void)
274 {
275   int result = 0;
276
277   char *init_fn;
278
279   FILE *f = NULL;
280   struct string line;
281   int line_number;
282
283   init_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_INIT_FILE",
284                                                "devices"),
285                             fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
286                                                config_path),
287                             NULL);
288
289   ds_init (&line, 128);
290
291   if (init_fn == NULL)
292     {
293       error (0, 0, _("cannot find output initialization file "
294                      "(use `-vv' to view search path)"));
295       goto exit;
296     }
297
298   f = fopen (init_fn, "r");
299   if (f == NULL)
300     {
301       error (0, errno, _("cannot open \"%s\""), init_fn);
302       goto exit;
303     }
304
305   line_number = 0;
306   for (;;)
307     {
308       char *cp;
309
310       if (!ds_get_config_line (f, &line, &line_number))
311         {
312           if (ferror (f))
313             error (0, errno, _("reading \"%s\""), init_fn);
314           break;
315         }
316       for (cp = ds_c_str (&line); isspace ((unsigned char) *cp); cp++);
317       if (!strncmp ("define", cp, 6) && isspace ((unsigned char) cp[6]))
318         outp_configure_macro (&cp[7]);
319       else if (*cp)
320         {
321           char *ep;
322           for (ep = cp; *ep && *ep != ':' && *ep != '='; ep++);
323           if (*ep == '=')
324             expand_name (cp, ep);
325           else if (*ep == ':')
326             {
327               struct outp_names *n = search_names (cp, ep);
328               if (n)
329                 {
330                   configure_driver_line (&line);
331                   delete_name (n);
332                 }
333             }
334           else
335             error_at_line (0, 0, init_fn, line_number, _("syntax error"));
336         }
337     }
338   result = 1;
339
340   check_configure_vec ();
341
342 exit:
343   if (f && -1 == fclose (f))
344     error (0, errno, _("error closing \"%s\""), init_fn);
345   free (init_fn);
346   ds_destroy (&line);
347   delete_macros ();
348
349   if (result) 
350     {
351       if (outp_driver_list == NULL) 
352         error (0, 0, _("no active output drivers")); 
353     }
354   else
355     error (0, 0, _("error reading device definition file"));
356
357   if (!result || outp_driver_list == NULL)
358     init_default_drivers ();
359 }
360
361 /* Clear the list of drivers to configure. */
362 void
363 outp_configure_clear (void)
364 {
365   struct outp_names *n, *next;
366
367   for (n = outp_configure_vec; n; n = next)
368     {
369       next = n->next;
370       free (n->name);
371       free (n);
372     }
373   outp_configure_vec = NULL;
374 }
375
376 /* Adds the name BP to the list of drivers to configure into
377    outp_driver_list. */
378 void
379 outp_configure_add (char *bp)
380 {
381   char *ep = &bp[strlen (bp)];
382   if (!search_names (bp, ep))
383     add_name (bp, ep, OUTP_S_COMMAND_LINE);
384 }
385
386 /* Defines one configuration macro based on the text in BP, which
387    should be of the form `KEY=VALUE'. */
388 void
389 outp_configure_macro (char *bp)
390 {
391   struct outp_defn *d;
392   char *ep;
393
394   while (isspace ((unsigned char) *bp))
395     bp++;
396   ep = bp;
397   while (*ep && !isspace ((unsigned char) *ep) && *ep != '=')
398     ep++;
399
400   d = xmalloc (sizeof *d);
401   d->key = xmalloc (ep - bp + 1);
402   memcpy (d->key, bp, ep - bp);
403   d->key[ep - bp] = 0;
404
405   /* Earlier definitions for a particular KEY override later ones. */
406   if (find_defn_value (d->key))
407     {
408       free (d->key);
409       free (d);
410       return;
411     }
412   
413   if (*ep == '=')
414     ep++;
415   while (isspace ((unsigned char) *ep))
416     ep++;
417
418   ds_create(&d->value, ep);
419   fn_interp_vars(&d->value, find_defn_value);
420   d->next = outp_macros;
421   d->prev = NULL;
422   if (outp_macros)
423     outp_macros->prev = d;
424   outp_macros = d;
425 }
426
427 /* Destroys all the drivers in driver list *DL and sets *DL to
428    NULL. */
429 static void
430 destroy_list (struct outp_driver ** dl)
431 {
432   struct outp_driver *d, *next;
433
434   for (d = *dl; d; d = next)
435     {
436       destroy_driver (d);
437       next = d->next;
438       free (d);
439     }
440   *dl = NULL;
441 }
442
443 /* Closes all the output drivers. */
444 void
445 outp_done (void)
446 {
447   struct outp_driver_class_list *n = outp_class_list ; 
448   destroy_list (&outp_driver_list);
449
450   while (n) 
451     {
452       struct outp_driver_class_list *next = n->next;
453       free(n);
454       n = next;
455     }
456   outp_class_list = NULL;
457
458   free (outp_title);
459   outp_title = NULL;
460   
461   free (outp_subtitle);
462   outp_subtitle = NULL;
463 }
464
465 /* Display on stdout a list of all registered driver classes. */
466 void
467 outp_list_classes (void)
468 {
469   int width = get_viewwidth();
470   struct outp_driver_class_list *c;
471
472   printf (_("Driver classes:\n\t"));
473   width -= 8;
474   for (c = outp_class_list; c; c = c->next)
475     {
476       if ((int) strlen (c->class->name) + 1 > width)
477         {
478           printf ("\n\t");
479           width = get_viewwidth() - 8;
480         }
481       else
482         putc (' ', stdout);
483       fputs (c->class->name, stdout);
484     }
485   putc('\n', stdout);
486 }
487
488 /* Obtains a token from S starting at position *POS, which is
489    updated.  Errors are reported against the given DRIVER_NAME.
490    The token is stored in TOKEN.  Returns true if successful,
491    false on syntax error.
492
493    Caller is responsible for skipping leading spaces. */
494 static bool
495 get_option_token (const struct string *s, const char *driver_name,
496                   size_t *pos, struct string *token)
497 {
498   int c;
499   
500   ds_clear (token);
501   c = ds_at (s, *pos);
502   if (c == EOF)
503     {
504       error (0, 0, _("syntax error parsing options for \"%s\" driver"),
505              driver_name);
506       return false;
507     }
508   else if (c == '\'' || c == '"')
509     {
510       int quote = c;
511
512       ++*pos;
513       for (;;)
514         {
515           c = ds_at (s, (*pos)++);
516           if (c == quote)
517             break;
518           else if (c == EOF) 
519             {
520               error (0, 0,
521                      _("reached end of options inside quoted string "
522                        "parsing options for \"%s\" driver"),
523                      driver_name);
524               return false;
525             }
526           else if (c != '\\')
527             ds_putc (token, c);
528           else
529             {
530               int out;
531                   
532               switch (ds_at (s, *pos))
533                 {
534                 case '\'':
535                   out = '\'';
536                   break;
537                 case '"':
538                   out = '"';
539                   break;
540                 case '\\':
541                   out = '\\';
542                   break;
543                 case 'a':
544                   out = '\a';
545                   break;
546                 case 'b':
547                   out = '\b';
548                   break;
549                 case 'f':
550                   out = '\f';
551                   break;
552                 case 'n':
553                   out = '\n';
554                   break;
555                 case 'r':
556                   out = '\r';
557                   break;
558                 case 't':
559                   out = '\t';
560                   break;
561                 case 'v':
562                   out = '\v';
563                   break;
564                 case '0':
565                 case '1':
566                 case '2':
567                 case '3':
568                 case '4':
569                 case '5':
570                 case '6':
571                 case '7':
572                   out = c - '0';
573                   while (ds_at (s, *pos) >= '0' && ds_at (s, *pos) <= '7')
574                     out = c * 8 + ds_at (s, (*pos)++) - '0';
575                   break;
576                 case 'x':
577                 case 'X':
578                   out = 0;
579                   while (isxdigit (ds_at (s, *pos)))
580                     {
581                       c = ds_at (s, *pos);
582                       if (!isxdigit (c))
583                           break;
584                       (*pos)++;
585
586                       out *= 16;
587                       if (isdigit (c))
588                         out += c - '0';
589                       else
590                         out += tolower (c) - 'a' + 10;
591                     }
592                   break;
593                 default:
594                   error (0, 0, _("syntax error in string constant "
595                                  "parsing options for \"%s\" driver"),
596                          driver_name);
597                   return false;
598                 }
599               ds_putc (token, out);
600             }
601         }
602     }
603   else 
604     {
605       do
606         {
607           ds_putc (token, c);
608           c = ds_at (s, ++*pos);
609         }
610       while (c != EOF && c != '=' && !isspace (c));
611     }
612   
613   return 1;
614 }
615
616 bool
617 outp_parse_options (const struct string *options,
618                     bool (*callback) (struct outp_driver *, const char *key,
619                                       const struct string *value),
620                     struct outp_driver *driver)
621 {
622   struct string key = DS_INITIALIZER;
623   struct string value = DS_INITIALIZER;
624   size_t pos = 0;
625   bool ok = true;
626
627   do
628     {
629       pos += ds_span (options, pos, " \t");
630       if (ds_at (options, pos) == EOF)
631         break;
632       
633       if (!get_option_token (options, driver->name, &pos, &key))
634         break;
635
636       pos += ds_span (options, pos, " \t");
637       if (ds_at (options, pos) != '=')
638         {
639           error (0, 0, _("syntax error expecting `=' "
640                          "parsing options for driver \"%s\""),
641                  driver->name);
642           break;
643         }
644       pos++;
645       
646       pos += ds_span (options, pos, " \t");
647       if (!get_option_token (options, driver->name, &pos, &value))
648         break;
649
650       ok = callback (driver, ds_c_str (&key), &value);
651     }
652   while (ok);
653   
654   ds_destroy (&key);
655   ds_destroy (&value);
656
657   return ok;
658 }
659
660 /* Find the driver in outp_driver_list with name NAME. */
661 static struct outp_driver *
662 find_driver (char *name)
663 {
664   struct outp_driver *d;
665
666   for (d = outp_driver_list; d; d = d->next)
667     if (!strcmp (d->name, name))
668       return d;
669   return NULL;
670 }
671
672 /* String S is in format:
673    DRIVERNAME:CLASSNAME:DEVICETYPE:OPTIONS
674    Adds a driver to outp_driver_list pursuant to the specification
675    provided.  */
676 static void
677 configure_driver (const struct string *driver_name,
678                   const struct string *class_name,
679                   const struct string *device_type,
680                   const struct string *options)
681 {
682   struct outp_driver *d, *iter;
683   struct outp_driver_class_list *c;
684   int device;
685
686   /* Find class. */
687   for (c = outp_class_list; c; c = c->next)
688     if (!strcmp (c->class->name, ds_c_str (class_name)))
689       break;
690   if (c == NULL)
691     {
692       error (0, 0, _("unknown output driver class `%s'"),
693              ds_c_str (class_name));
694       return;
695     }
696   
697   /* Parse device type. */
698   device = 0;
699   if (device_type != NULL)
700     {
701       struct string token = DS_INITIALIZER;
702       size_t save_idx = 0;
703
704       while (ds_tokenize (device_type, &token, " \t\r\v", &save_idx)) 
705         {
706           const char *type = ds_c_str (&token);
707           if (!strcmp (type, "listing"))
708             device |= OUTP_DEV_LISTING;
709           else if (!strcmp (type, "screen"))
710             device |= OUTP_DEV_SCREEN;
711           else if (!strcmp (type, "printer"))
712             device |= OUTP_DEV_PRINTER;
713           else
714             error (0, 0, _("unknown device type `%s'"), type);
715         }
716       ds_destroy (&token);
717     }
718
719   /* Open the device. */
720   d = xmalloc (sizeof *d);
721   d->next = d->prev = NULL;
722   d->class = c->class;
723   d->name = xstrdup (ds_c_str (driver_name));
724   d->page_open = false;
725   d->device = OUTP_DEV_NONE;
726   d->cp_x = d->cp_y = 0;
727   d->ext = NULL;
728   d->prc = NULL;
729
730   /* Open driver. */
731   if (!d->class->open_driver (d, options))
732     {
733       error (0, 0, _("cannot initialize output driver `%s' of class `%s'"),
734              d->name, d->class->name);
735       free (d->name);
736       free (d);
737       return;
738     }
739
740   /* Find like-named driver and delete. */
741   iter = find_driver (d->name);
742   if (iter != NULL)
743     destroy_driver (iter);
744
745   /* Add to list. */
746   d->next = outp_driver_list;
747   d->prev = NULL;
748   if (outp_driver_list != NULL)
749     outp_driver_list->prev = d;
750   outp_driver_list = d;
751 }
752
753 /* String LINE is in format:
754    DRIVERNAME:CLASSNAME:DEVICETYPE:OPTIONS
755    Adds a driver to outp_driver_list pursuant to the specification
756    provided.  */
757 static void
758 configure_driver_line (struct string *line)
759 {
760   struct string tokens[4];
761   size_t save_idx;
762   size_t i;
763
764   fn_interp_vars (line, find_defn_value);
765
766   save_idx = 0;
767   for (i = 0; i < 4; i++) 
768     {
769       struct string *token = &tokens[i];
770       ds_init (token, 0);
771       ds_separate (line, token, i < 3 ? ":" : "", &save_idx);
772       ds_trim_spaces (token);
773     }
774
775   if (!ds_is_empty (&tokens[0]) && !ds_is_empty (&tokens[1]))
776     configure_driver (&tokens[0], &tokens[1], &tokens[2], &tokens[3]);
777   else
778     error (0, 0,
779            _("driver definition line missing driver name or class name"));
780
781   for (i = 0; i < 4; i++) 
782     ds_destroy (&tokens[i]);
783 }
784
785 /* Destroys output driver D. */
786 static void
787 destroy_driver (struct outp_driver *d)
788 {
789   outp_close_page (d);
790   if (d->class)
791     {
792       struct outp_driver_class_list *c;
793
794       d->class->close_driver (d);
795
796       for (c = outp_class_list; c; c = c->next)
797         if (c->class == d->class)
798           break;
799       assert (c != NULL);
800     }
801   free (d->name);
802
803   /* Remove this driver from the global driver list. */
804   if (d->prev)
805     d->prev->next = d->next;
806   if (d->next)
807     d->next->prev = d->prev;
808   if (d == outp_driver_list)
809     outp_driver_list = d->next;
810 }
811
812 /* Tries to match S as one of the keywords in TAB, with
813    corresponding information structure INFO.  Returns category
814    code and stores subcategory in *SUBCAT on success.  Returns -1
815    on failure. */
816 int
817 outp_match_keyword (const char *s, struct outp_option *tab, int *subcat)
818 {
819   for (; tab->keyword != NULL; tab++)
820     if (!strcmp (s, tab->keyword))
821       {
822         *subcat = tab->subcat;
823         return tab->cat;
824       }
825   return -1;
826 }
827
828 /* Encapsulate two characters in a single int. */
829 #define TWO_CHARS(A, B)                         \
830         ((A) + ((B)<<8))
831
832 /* Determines the size of a dimensional measurement and returns the
833    size in units of 1/72000".  Units if not specified explicitly are
834    inches for values under 50, millimeters otherwise.  Returns 0,
835    stores NULL to *TAIL on error; otherwise returns dimension, stores
836    address of next */
837 int
838 outp_evaluate_dimension (char *dimen, char **tail)
839 {
840   char *s = dimen;
841   char *ptail;
842   double value;
843
844   value = strtod (s, &ptail);
845   if (ptail == s)
846     goto lossage;
847   if (*ptail == '-')
848     {
849       double b, c;
850       s = &ptail[1];
851       b = strtod (s, &ptail);
852       if (b <= 0.0 || ptail == s)
853         goto lossage;
854       if (*ptail != '/')
855         goto lossage;
856       s = &ptail[1];
857       c = strtod (s, &ptail);
858       if (c <= 0.0 || ptail == s)
859         goto lossage;
860       s = ptail;
861       if (c == 0.0)
862         goto lossage;
863       if (value > 0)
864         value += b / c;
865       else
866         value -= b / c;
867     }
868   else if (*ptail == '/')
869     {
870       double b;
871       s = &ptail[1];
872       b = strtod (s, &ptail);
873       if (b <= 0.0 || ptail == s)
874         goto lossage;
875       s = ptail;
876       value /= b;
877     }
878   else
879     s = ptail;
880   if (*s == 0 || isspace ((unsigned char) *s))
881     {
882       if (value < 50.0)
883         value *= 72000;
884       else
885         value *= 72000 / 25.4;
886     }
887   else
888     {
889       double factor;
890
891       /* Standard TeX units are supported. */
892       if (*s == '"')
893         factor = 72000, s++;
894       else
895         switch (TWO_CHARS (s[0], s[1]))
896           {
897           case TWO_CHARS ('p', 't'):
898             factor = 72000 / 72.27;
899             break;
900           case TWO_CHARS ('p', 'c'):
901             factor = 72000 / 72.27 * 12.0;
902             break;
903           case TWO_CHARS ('i', 'n'):
904             factor = 72000;
905             break;
906           case TWO_CHARS ('b', 'p'):
907             factor = 72000 / 72.0;
908             break;
909           case TWO_CHARS ('c', 'm'):
910             factor = 72000 / 2.54;
911             break;
912           case TWO_CHARS ('m', 'm'):
913             factor = 72000 / 25.4;
914             break;
915           case TWO_CHARS ('d', 'd'):
916             factor = 72000 / 72.27 * 1.0700086;
917             break;
918           case TWO_CHARS ('c', 'c'):
919             factor = 72000 / 72.27 * 12.840104;
920             break;
921           case TWO_CHARS ('s', 'p'):
922             factor = 72000 / 72.27 / 65536.0;
923             break;
924           default:
925             error (0, 0,
926                    _("unit \"%s\" is unknown in dimension \"%s\""), s, dimen);
927             *tail = NULL;
928             return 0;
929           }
930       ptail += 2;
931       value *= factor;
932     }
933   if (value <= 0.0)
934     goto lossage;
935   if (tail)
936     *tail = ptail;
937   return value + 0.5;
938
939 lossage:
940   *tail = NULL;
941   error (0, 0, _("bad dimension \"%s\""), dimen);
942   return 0;
943 }
944
945 /* Stores the dimensions in 1/72000" units of paper identified by
946    SIZE, which is of form `HORZ x VERT' or `HORZ by VERT' where each
947    of HORZ and VERT are dimensions, into *H and *V.  Return nonzero on
948    success. */
949 static int
950 internal_get_paper_size (char *size, int *h, int *v)
951 {
952   char *tail;
953
954   while (isspace ((unsigned char) *size))
955     size++;
956   *h = outp_evaluate_dimension (size, &tail);
957   if (tail == NULL)
958     return 0;
959   while (isspace ((unsigned char) *tail))
960     tail++;
961   if (*tail == 'x')
962     tail++;
963   else if (*tail == 'b' && tail[1] == 'y')
964     tail += 2;
965   else
966     {
967       error (0, 0, _("`x' expected in paper size `%s'"), size);
968       return 0;
969     }
970   *v = outp_evaluate_dimension (tail, &tail);
971   if (tail == NULL)
972     return 0;
973   while (isspace ((unsigned char) *tail))
974     tail++;
975   if (*tail)
976     {
977       error (0, 0, _("trailing garbage `%s' on paper size `%s'"), tail, size);
978       return 0;
979     }
980   
981   return 1;
982 }
983
984 /* Stores the dimensions, in 1/72000" units, of paper identified by
985    SIZE into *H and *V.  SIZE may be a pair of dimensions of form `H x
986    V', or it may be a case-insensitive paper identifier, which is
987    looked up in the `papersize' configuration file.  Returns nonzero
988    on success.  May modify SIZE. */
989 /* Don't read further unless you've got a strong stomach. */
990 int
991 outp_get_paper_size (char *size, int *h, int *v)
992 {
993   struct paper_size
994     {
995       char *name;
996       int use;
997       int h, v;
998     };
999
1000   FILE *f;
1001   char *pprsz_fn;
1002
1003   struct string line;
1004   int line_number = 0;
1005
1006   int free_it = 0;
1007   int result = 0;
1008   char *ep;
1009
1010   while (isspace ((unsigned char) *size))
1011     size++;
1012   if (isdigit ((unsigned char) *size))
1013     return internal_get_paper_size (size, h, v);
1014   ep = size;
1015   while (*ep)
1016     ep++;
1017   while (isspace ((unsigned char) *ep) && ep >= size)
1018     ep--;
1019   if (ep == size)
1020     {
1021       error (0, 0, _("paper size name cannot be empty"));
1022       return 0;
1023     }
1024   
1025   ep++;
1026   if (*ep)
1027     *ep = 0;
1028
1029   pprsz_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_PAPERSIZE_FILE",
1030                                                 "papersize"),
1031                              fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
1032                                                 config_path),
1033                              NULL);
1034
1035   ds_init (&line, 128);
1036
1037   if (pprsz_fn == NULL)
1038     {
1039       error (0, 0, _("cannot find `papersize' configuration file"));
1040       goto exit;
1041     }
1042
1043   f = fopen (pprsz_fn, "r");
1044   if (!f)
1045     {
1046       error (0, errno, _("error opening \"%s\""), pprsz_fn);
1047       goto exit;
1048     }
1049
1050   for (;;)
1051     {
1052       char *cp, *bp, *ep;
1053
1054       if (!ds_get_config_line (f, &line, &line_number))
1055         {
1056           if (ferror (f))
1057             error (0, errno, _("error reading \"%s\""), pprsz_fn);
1058           break;
1059         }
1060       for (cp = ds_c_str (&line); isspace ((unsigned char) *cp); cp++);
1061       if (*cp == 0)
1062         continue;
1063       if (*cp != '"')
1064         goto lex_error;
1065       for (bp = ep = cp + 1; *ep && *ep != '"'; ep++);
1066       if (!*ep)
1067         goto lex_error;
1068       *ep = 0;
1069       if (0 != strcasecmp (bp, size))
1070         continue;
1071
1072       for (cp = ep + 1; isspace ((unsigned char) *cp); cp++);
1073       if (*cp == '=')
1074         {
1075           size = xmalloc (ep - bp + 1);
1076           strcpy (size, bp);
1077           free_it = 1;
1078           continue;
1079         }
1080       size = &ep[1];
1081       break;
1082
1083     lex_error:
1084       error_at_line (0, 0, pprsz_fn, line_number,
1085                      _("syntax error in paper size definition"));
1086     }
1087
1088   /* We found the one we want! */
1089   result = internal_get_paper_size (size, h, v);
1090
1091 exit:
1092   ds_destroy (&line);
1093   if (free_it)
1094     free (size);
1095
1096   if (!result)
1097     error (0, 0, _("error reading paper size definition file"));
1098   
1099   return result;
1100 }
1101
1102 /* If D is NULL, returns the first enabled driver if any, NULL if
1103    none.  Otherwise D must be the last driver returned by this
1104    function, in which case the next enabled driver is returned or NULL
1105    if that was the last. */
1106 struct outp_driver *
1107 outp_drivers (struct outp_driver *d)
1108 {
1109 #if DEBUGGING
1110   struct outp_driver *orig_d = d;
1111 #endif
1112
1113   for (;;)
1114     {
1115       if (d == NULL)
1116         d = outp_driver_list;
1117       else
1118         d = d->next;
1119
1120       if (d == NULL
1121           || (d->device == 0 || (d->device & disabled_devices) != d->device))
1122         break;
1123     }
1124
1125   return d;
1126 }
1127
1128 /* Enables (if ENABLE is nonzero) or disables (if ENABLE is zero) the
1129    device(s) given in mask DEVICE. */
1130 void
1131 outp_enable_device (int enable, int device)
1132 {
1133   if (enable)
1134     disabled_devices &= ~device;
1135   else
1136     disabled_devices |= device;
1137 }
1138
1139 /* Opens a page on driver D (if one is not open). */
1140 void
1141 outp_open_page (struct outp_driver *d) 
1142 {
1143   if (!d->page_open) 
1144     {
1145       d->cp_x = d->cp_y = 0;
1146
1147       d->page_open = true;
1148       if (d->class->open_page != NULL)
1149         d->class->open_page (d);
1150     }
1151 }
1152
1153 /* Closes the page on driver D (if one is open). */
1154 void
1155 outp_close_page (struct outp_driver *d) 
1156 {
1157   if (d->page_open) 
1158     {
1159       if (d->class->close_page != NULL)
1160         d->class->close_page (d);
1161       d->page_open = false;
1162     }
1163 }
1164
1165 /* Ejects the paper on device D, if a page is open and is not
1166    blank. */
1167 void
1168 outp_eject_page (struct outp_driver *d)
1169 {
1170   if (d->page_open && d->cp_y != 0)
1171     {
1172       outp_close_page (d);
1173       outp_open_page (d);
1174     }
1175 }
1176
1177 /* Returns the width of string S, in device units, when output on
1178    device D. */
1179 int
1180 outp_string_width (struct outp_driver *d, const char *s, enum outp_font font)
1181 {
1182   struct outp_text text;
1183   int width;
1184   
1185   text.font = font;
1186   text.justification = OUTP_LEFT;
1187   ls_init (&text.string, (char *) s, strlen (s));
1188   text.h = text.v = INT_MAX;
1189   d->class->text_metrics (d, &text, &width, NULL);
1190
1191   return width;
1192 }