2754ec73701e124088b97810cb6f2c723508ce52
[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     const struct outp_class *class;
74     struct outp_driver_class_list *next;
75   };
76
77 static struct outp_driver_class_list *outp_class_list;
78 static 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 substring);
89 static void configure_driver (const struct substring, const struct substring,
90                               const struct substring, const struct substring);
91
92 /* Add a class to the class list. */
93 static void
94 add_class (const 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_cstr (&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
231   char def[] = "default";
232
233   add_class (&html_class);
234   add_class (&postscript_class);
235   add_class (&ascii_class);
236
237   add_name (def, &def[strlen (def)], OUTP_S_INIT_FILE);
238 }
239
240 /* Deletes all the output macros. */
241 static void
242 delete_macros (void)
243 {
244   struct outp_defn *d, *next;
245
246   for (d = outp_macros; d; d = next)
247     {
248       next = d->next;
249       free (d->key);
250       ds_destroy (&d->value);
251       free (d);
252     }
253 }
254
255 static void
256 init_default_drivers (void) 
257 {
258   error (0, 0, _("using default output driver configuration"));
259   configure_driver (ss_cstr ("list"),
260                     ss_cstr ("ascii"),
261                     ss_cstr ("listing"),
262                     ss_cstr ("length=66 width=79 output-file=\"pspp.list\""));
263 }
264
265 /* Reads the initialization file; initializes
266    outp_driver_list. */
267 void
268 outp_read_devices (void)
269 {
270   int result = 0;
271
272   char *init_fn;
273
274   FILE *f = NULL;
275   struct string line;
276   int line_number;
277
278   init_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_INIT_FILE",
279                                                "devices"),
280                             fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
281                                                config_path));
282   
283   ds_init_empty (&line);
284
285   if (init_fn == NULL)
286     {
287       error (0, 0, _("cannot find output initialization file "
288                      "(use `-vv' to view search path)"));
289       goto exit;
290     }
291
292   f = fopen (init_fn, "r");
293   if (f == NULL)
294     {
295       error (0, errno, _("cannot open \"%s\""), init_fn);
296       goto exit;
297     }
298
299   line_number = 0;
300   for (;;)
301     {
302       char *cp;
303
304       if (!ds_read_config_line (&line, &line_number, f))
305         {
306           if (ferror (f))
307             error (0, errno, _("reading \"%s\""), init_fn);
308           break;
309         }
310       for (cp = ds_cstr (&line); isspace ((unsigned char) *cp); cp++);
311       if (!strncmp ("define", cp, 6) && isspace ((unsigned char) cp[6]))
312         outp_configure_macro (&cp[7]);
313       else if (*cp)
314         {
315           char *ep;
316           for (ep = cp; *ep && *ep != ':' && *ep != '='; ep++);
317           if (*ep == '=')
318             expand_name (cp, ep);
319           else if (*ep == ':')
320             {
321               struct outp_names *n = search_names (cp, ep);
322               if (n)
323                 {
324                   configure_driver_line (ds_ss (&line));
325                   delete_name (n);
326                 }
327             }
328           else
329             error_at_line (0, 0, init_fn, line_number, _("syntax error"));
330         }
331     }
332   result = 1;
333
334   check_configure_vec ();
335
336 exit:
337   if (f && -1 == fclose (f))
338     error (0, errno, _("error closing \"%s\""), init_fn);
339   free (init_fn);
340   ds_destroy (&line);
341   delete_macros ();
342
343   if (result) 
344     {
345       if (outp_driver_list == NULL) 
346         error (0, 0, _("no active output drivers")); 
347     }
348   else
349     error (0, 0, _("error reading device definition file"));
350
351   if (!result || outp_driver_list == NULL)
352     init_default_drivers ();
353 }
354
355 /* Clear the list of drivers to configure. */
356 void
357 outp_configure_clear (void)
358 {
359   struct outp_names *n, *next;
360
361   for (n = outp_configure_vec; n; n = next)
362     {
363       next = n->next;
364       free (n->name);
365       free (n);
366     }
367   outp_configure_vec = NULL;
368 }
369
370 /* Adds the name BP to the list of drivers to configure into
371    outp_driver_list. */
372 void
373 outp_configure_add (char *bp)
374 {
375   char *ep = &bp[strlen (bp)];
376   if (!search_names (bp, ep))
377     add_name (bp, ep, OUTP_S_COMMAND_LINE);
378 }
379
380 /* Defines one configuration macro based on the text in BP, which
381    should be of the form `KEY=VALUE'. */
382 void
383 outp_configure_macro (char *bp)
384 {
385   struct outp_defn *d;
386   char *ep;
387
388   while (isspace ((unsigned char) *bp))
389     bp++;
390   ep = bp;
391   while (*ep && !isspace ((unsigned char) *ep) && *ep != '=')
392     ep++;
393
394   d = xmalloc (sizeof *d);
395   d->key = xmalloc (ep - bp + 1);
396   memcpy (d->key, bp, ep - bp);
397   d->key[ep - bp] = 0;
398
399   /* Earlier definitions for a particular KEY override later ones. */
400   if (find_defn_value (d->key))
401     {
402       free (d->key);
403       free (d);
404       return;
405     }
406   
407   if (*ep == '=')
408     ep++;
409   while (isspace ((unsigned char) *ep))
410     ep++;
411
412   ds_init_cstr (&d->value, ep);
413   fn_interp_vars (ds_ss (&d->value), find_defn_value, &d->value);
414   d->next = outp_macros;
415   d->prev = NULL;
416   if (outp_macros)
417     outp_macros->prev = d;
418   outp_macros = d;
419 }
420
421 /* Destroys all the drivers in driver list *DL and sets *DL to
422    NULL. */
423 static void
424 destroy_list (struct outp_driver ** dl)
425 {
426   struct outp_driver *d, *next;
427
428   for (d = *dl; d; d = next)
429     {
430       destroy_driver (d);
431       next = d->next;
432       free (d);
433     }
434   *dl = NULL;
435 }
436
437 /* Closes all the output drivers. */
438 void
439 outp_done (void)
440 {
441   struct outp_driver_class_list *n = outp_class_list ; 
442   destroy_list (&outp_driver_list);
443
444   while (n) 
445     {
446       struct outp_driver_class_list *next = n->next;
447       free(n);
448       n = next;
449     }
450   outp_class_list = NULL;
451
452   free (outp_title);
453   outp_title = NULL;
454   
455   free (outp_subtitle);
456   outp_subtitle = NULL;
457 }
458
459 /* Display on stdout a list of all registered driver classes. */
460 void
461 outp_list_classes (void)
462 {
463   int width = get_viewwidth();
464   struct outp_driver_class_list *c;
465
466   printf (_("Driver classes:\n\t"));
467   width -= 8;
468   for (c = outp_class_list; c; c = c->next)
469     {
470       if ((int) strlen (c->class->name) + 1 > width)
471         {
472           printf ("\n\t");
473           width = get_viewwidth() - 8;
474         }
475       else
476         putc (' ', stdout);
477       fputs (c->class->name, stdout);
478     }
479   putc('\n', stdout);
480 }
481
482 /* Obtains a token from S and advances its position.  Errors are
483    reported against the given DRIVER_NAME.
484    The token is stored in TOKEN.  Returns true if successful,
485    false on syntax error.
486
487    Caller is responsible for skipping leading spaces. */
488 static bool
489 get_option_token (struct substring *s, const char *driver_name,
490                   struct string *token)
491 {
492   int c;
493   
494   ds_clear (token);
495   c = ss_get_char (s);
496   if (c == EOF)
497     {
498       error (0, 0, _("syntax error parsing options for \"%s\" driver"),
499              driver_name);
500       return false;
501     }
502   else if (c == '\'' || c == '"')
503     {
504       int quote = c;
505
506       for (;;)
507         {
508           c = ss_get_char (s);
509           if (c == quote)
510             break;
511           else if (c == EOF) 
512             {
513               error (0, 0,
514                      _("reached end of options inside quoted string "
515                        "parsing options for \"%s\" driver"),
516                      driver_name);
517               return false;
518             }
519           else if (c != '\\')
520             ds_put_char (token, c);
521           else
522             {
523               int out;
524
525               c = ss_get_char (s);
526               switch (c)
527                 {
528                 case '\'':
529                   out = '\'';
530                   break;
531                 case '"':
532                   out = '"';
533                   break;
534                 case '\\':
535                   out = '\\';
536                   break;
537                 case 'a':
538                   out = '\a';
539                   break;
540                 case 'b':
541                   out = '\b';
542                   break;
543                 case 'f':
544                   out = '\f';
545                   break;
546                 case 'n':
547                   out = '\n';
548                   break;
549                 case 'r':
550                   out = '\r';
551                   break;
552                 case 't':
553                   out = '\t';
554                   break;
555                 case 'v':
556                   out = '\v';
557                   break;
558                 case '0':
559                 case '1':
560                 case '2':
561                 case '3':
562                 case '4':
563                 case '5':
564                 case '6':
565                 case '7':
566                   out = c - '0';
567                   while (ss_first (*s) >= '0' && ss_first (*s) <= '7')
568                     out = c * 8 + (ss_get_char (s) - '0');
569                   break;
570                 case 'x':
571                 case 'X':
572                   out = 0;
573                   while (isxdigit (ss_first (*s)))
574                     {
575                       c = ss_get_char (s);
576                       out *= 16;
577                       if (isdigit (c))
578                         out += c - '0';
579                       else
580                         out += tolower (c) - 'a' + 10;
581                     }
582                   break;
583                 default:
584                   error (0, 0, _("syntax error in string constant "
585                                  "parsing options for \"%s\" driver"),
586                          driver_name);
587                   return false;
588                 }
589               ds_put_char (token, out);
590             }
591         }
592     }
593   else 
594     {
595       for (;;)
596         {
597           ds_put_char (token, c);
598
599           c = ss_first (*s);
600           if (c == EOF || c == '=' || isspace (c))
601             break;
602           ss_advance (s, 1);
603         }
604     }
605   
606   return 1;
607 }
608
609 bool
610 outp_parse_options (struct substring options,
611                     bool (*callback) (struct outp_driver *, const char *key,
612                                       const struct string *value),
613                     struct outp_driver *driver)
614 {
615   struct string key = DS_EMPTY_INITIALIZER;
616   struct string value = DS_EMPTY_INITIALIZER;
617   struct substring left = options;
618   bool ok = true;
619
620   do
621     {
622       ss_ltrim (&left, ss_cstr (CC_SPACES));
623       if (ss_is_empty (left))
624         break;
625       
626       if (!get_option_token (&left, driver->name, &key))
627         break;
628
629       ss_ltrim (&left, ss_cstr (CC_SPACES));
630       if (!ss_match_char (&left, '='))
631         {
632           error (0, 0, _("syntax error expecting `=' "
633                          "parsing options for driver \"%s\""),
634                  driver->name);
635           break;
636         }
637
638       ss_ltrim (&left, ss_cstr (CC_SPACES));
639       if (!get_option_token (&left, driver->name, &value))
640         break;
641
642       ok = callback (driver, ds_cstr (&key), &value);
643     }
644   while (ok);
645   
646   ds_destroy (&key);
647   ds_destroy (&value);
648
649   return ok;
650 }
651
652 /* Find the driver in outp_driver_list with name NAME. */
653 static struct outp_driver *
654 find_driver (char *name)
655 {
656   struct outp_driver *d;
657
658   for (d = outp_driver_list; d; d = d->next)
659     if (!strcmp (d->name, name))
660       return d;
661   return NULL;
662 }
663
664 /* Adds a driver to outp_driver_list pursuant to the
665    specification provided.  */
666 static void
667 configure_driver (struct substring driver_name, struct substring class_name,
668                   struct substring device_type, struct substring options)
669 {
670   struct outp_driver *d, *iter;
671   struct outp_driver_class_list *c;
672
673   struct substring token;
674   size_t save_idx = 0;
675   int device;
676
677   /* Find class. */
678   for (c = outp_class_list; c; c = c->next)
679     if (!ss_compare (ss_cstr (c->class->name), class_name))
680       break;
681   if (c == NULL)
682     {
683       error (0, 0, _("unknown output driver class `%.*s'"),
684              (int) ss_length (class_name), ss_data (class_name));
685       return;
686     }
687   
688   /* Parse device type. */
689   device = 0;
690   while (ss_tokenize (device_type, ss_cstr (CC_SPACES), &save_idx, &token))
691     if (!ss_compare (token, ss_cstr ("listing")))
692       device |= OUTP_DEV_LISTING;
693     else if (!ss_compare (token, ss_cstr ("screen")))
694       device |= OUTP_DEV_SCREEN;
695     else if (!ss_compare (token, ss_cstr ("printer")))
696       device |= OUTP_DEV_PRINTER;
697     else
698       error (0, 0, _("unknown device type `%.*s'"),
699              (int) ss_length (token), ss_data (token));
700
701   /* Open the device. */
702   d = xmalloc (sizeof *d);
703   d->next = d->prev = NULL;
704   d->class = c->class;
705   d->name = ss_xstrdup (driver_name);
706   d->page_open = false;
707   d->device = OUTP_DEV_NONE;
708   d->cp_x = d->cp_y = 0;
709   d->ext = NULL;
710   d->prc = NULL;
711
712   /* Open driver. */
713   if (!d->class->open_driver (d, options))
714     {
715       error (0, 0, _("cannot initialize output driver `%s' of class `%s'"),
716              d->name, d->class->name);
717       free (d->name);
718       free (d);
719       return;
720     }
721
722   /* Find like-named driver and delete. */
723   iter = find_driver (d->name);
724   if (iter != NULL)
725     destroy_driver (iter);
726
727   /* Add to list. */
728   d->next = outp_driver_list;
729   d->prev = NULL;
730   if (outp_driver_list != NULL)
731     outp_driver_list->prev = d;
732   outp_driver_list = d;
733 }
734
735 /* String LINE is in format:
736    DRIVERNAME:CLASSNAME:DEVICETYPE:OPTIONS
737    Adds a driver to outp_driver_list pursuant to the specification
738    provided.  */
739 static void
740 configure_driver_line (struct substring line_)
741 {
742   struct string line = DS_EMPTY_INITIALIZER;
743   struct substring tokens[4];
744   size_t save_idx;
745   size_t i;
746
747   fn_interp_vars (line_, find_defn_value, &line);
748
749   save_idx = 0;
750   for (i = 0; i < 4; i++) 
751     {
752       struct substring *token = &tokens[i];
753       ds_separate (&line, ss_cstr (i < 3 ? ":" : ""), &save_idx, token);
754       ss_trim (token, ss_cstr (CC_SPACES));
755     }
756
757   if (!ss_is_empty (tokens[0]) && !ss_is_empty (tokens[1]))
758     configure_driver (tokens[0], tokens[1], tokens[2], tokens[3]);
759   else
760     error (0, 0,
761            _("driver definition line missing driver name or class name"));
762
763   ds_destroy (&line);
764 }
765
766 /* Destroys output driver D. */
767 static void
768 destroy_driver (struct outp_driver *d)
769 {
770   outp_close_page (d);
771   if (d->class)
772     {
773       struct outp_driver_class_list *c;
774
775       d->class->close_driver (d);
776
777       for (c = outp_class_list; c; c = c->next)
778         if (c->class == d->class)
779           break;
780       assert (c != NULL);
781     }
782   free (d->name);
783
784   /* Remove this driver from the global driver list. */
785   if (d->prev)
786     d->prev->next = d->next;
787   if (d->next)
788     d->next->prev = d->prev;
789   if (d == outp_driver_list)
790     outp_driver_list = d->next;
791 }
792
793 /* Tries to match S as one of the keywords in TAB, with
794    corresponding information structure INFO.  Returns category
795    code and stores subcategory in *SUBCAT on success.  Returns -1
796    on failure. */
797 int
798 outp_match_keyword (const char *s, const struct outp_option *tab, int *subcat)
799 {
800   for (; tab->keyword != NULL; tab++)
801     if (!strcmp (s, tab->keyword))
802       {
803         *subcat = tab->subcat;
804         return tab->cat;
805       }
806   return -1;
807 }
808
809 /* Encapsulate two characters in a single int. */
810 #define TWO_CHARS(A, B)                         \
811         ((A) + ((B)<<8))
812
813 /* Determines the size of a dimensional measurement and returns the
814    size in units of 1/72000".  Units if not specified explicitly are
815    inches for values under 50, millimeters otherwise.  Returns 0,
816    stores NULL to *TAIL on error; otherwise returns dimension, stores
817    address of next */
818 int
819 outp_evaluate_dimension (char *dimen, char **tail)
820 {
821   char *s = dimen;
822   char *ptail;
823   double value;
824
825   value = strtod (s, &ptail);
826   if (ptail == s)
827     goto lossage;
828   if (*ptail == '-')
829     {
830       double b, c;
831       s = &ptail[1];
832       b = strtod (s, &ptail);
833       if (b <= 0.0 || ptail == s)
834         goto lossage;
835       if (*ptail != '/')
836         goto lossage;
837       s = &ptail[1];
838       c = strtod (s, &ptail);
839       if (c <= 0.0 || ptail == s)
840         goto lossage;
841       s = ptail;
842       if (c == 0.0)
843         goto lossage;
844       if (value > 0)
845         value += b / c;
846       else
847         value -= b / c;
848     }
849   else if (*ptail == '/')
850     {
851       double b;
852       s = &ptail[1];
853       b = strtod (s, &ptail);
854       if (b <= 0.0 || ptail == s)
855         goto lossage;
856       s = ptail;
857       value /= b;
858     }
859   else
860     s = ptail;
861   if (*s == 0 || isspace ((unsigned char) *s))
862     {
863       if (value < 50.0)
864         value *= 72000;
865       else
866         value *= 72000 / 25.4;
867     }
868   else
869     {
870       double factor;
871
872       /* Standard TeX units are supported. */
873       if (*s == '"')
874         factor = 72000, s++;
875       else
876         switch (TWO_CHARS (s[0], s[1]))
877           {
878           case TWO_CHARS ('p', 't'):
879             factor = 72000 / 72.27;
880             break;
881           case TWO_CHARS ('p', 'c'):
882             factor = 72000 / 72.27 * 12.0;
883             break;
884           case TWO_CHARS ('i', 'n'):
885             factor = 72000;
886             break;
887           case TWO_CHARS ('b', 'p'):
888             factor = 72000 / 72.0;
889             break;
890           case TWO_CHARS ('c', 'm'):
891             factor = 72000 / 2.54;
892             break;
893           case TWO_CHARS ('m', 'm'):
894             factor = 72000 / 25.4;
895             break;
896           case TWO_CHARS ('d', 'd'):
897             factor = 72000 / 72.27 * 1.0700086;
898             break;
899           case TWO_CHARS ('c', 'c'):
900             factor = 72000 / 72.27 * 12.840104;
901             break;
902           case TWO_CHARS ('s', 'p'):
903             factor = 72000 / 72.27 / 65536.0;
904             break;
905           default:
906             error (0, 0,
907                    _("unit \"%s\" is unknown in dimension \"%s\""), s, dimen);
908             *tail = NULL;
909             return 0;
910           }
911       ptail += 2;
912       value *= factor;
913     }
914   if (value <= 0.0)
915     goto lossage;
916   if (tail)
917     *tail = ptail;
918   return value + 0.5;
919
920 lossage:
921   *tail = NULL;
922   error (0, 0, _("bad dimension \"%s\""), dimen);
923   return 0;
924 }
925
926 /* Stores the dimensions in 1/72000" units of paper identified by
927    SIZE, which is of form `HORZ x VERT' or `HORZ by VERT' where each
928    of HORZ and VERT are dimensions, into *H and *V.  Return true on
929    success. */
930 static bool
931 internal_get_paper_size (char *size, int *h, int *v)
932 {
933   char *tail;
934
935   while (isspace ((unsigned char) *size))
936     size++;
937   *h = outp_evaluate_dimension (size, &tail);
938   if (tail == NULL)
939     return false;
940   while (isspace ((unsigned char) *tail))
941     tail++;
942   if (*tail == 'x')
943     tail++;
944   else if (*tail == 'b' && tail[1] == 'y')
945     tail += 2;
946   else
947     {
948       error (0, 0, _("`x' expected in paper size `%s'"), size);
949       return false;
950     }
951   *v = outp_evaluate_dimension (tail, &tail);
952   if (tail == NULL)
953     return 0;
954   while (isspace ((unsigned char) *tail))
955     tail++;
956   if (*tail)
957     {
958       error (0, 0, _("trailing garbage `%s' on paper size `%s'"), tail, size);
959       return false;
960     }
961   
962   return true;
963 }
964
965 /* Stores the dimensions, in 1/72000" units, of paper identified by
966    SIZE into *H and *V.  SIZE may be a pair of dimensions of form `H x
967    V', or it may be a case-insensitive paper identifier, which is
968    looked up in the `papersize' configuration file.  Returns true
969    on success.  May modify SIZE. */
970 /* Don't read further unless you've got a strong stomach. */
971 bool
972 outp_get_paper_size (char *size, int *h, int *v)
973 {
974   struct paper_size
975     {
976       char *name;
977       int use;
978       int h, v;
979     };
980
981   FILE *f;
982   char *pprsz_fn;
983
984   struct string line;
985   int line_number = 0;
986
987   bool free_it = false;
988   bool result = false;
989   char *ep;
990
991   while (isspace ((unsigned char) *size))
992     size++;
993   if (isdigit ((unsigned char) *size))
994     return internal_get_paper_size (size, h, v);
995   ep = size;
996   while (*ep)
997     ep++;
998   while (isspace ((unsigned char) *ep) && ep >= size)
999     ep--;
1000   if (ep == size)
1001     {
1002       error (0, 0, _("paper size name cannot be empty"));
1003       return 0;
1004     }
1005   
1006   ep++;
1007   if (*ep)
1008     *ep = 0;
1009
1010   pprsz_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_PAPERSIZE_FILE",
1011                                                 "papersize"),
1012                              fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
1013                                                 config_path));
1014   
1015   ds_init_empty (&line);
1016
1017   if (pprsz_fn == NULL)
1018     {
1019       error (0, 0, _("cannot find `papersize' configuration file"));
1020       goto exit;
1021     }
1022
1023   f = fopen (pprsz_fn, "r");
1024   if (!f)
1025     {
1026       error (0, errno, _("error opening \"%s\""), pprsz_fn);
1027       goto exit;
1028     }
1029
1030   for (;;)
1031     {
1032       struct substring p, name;
1033
1034       if (!ds_read_config_line (&line, &line_number, f))
1035         {
1036           if (ferror (f))
1037             error (0, errno, _("error reading \"%s\""), pprsz_fn);
1038           break;
1039         }
1040
1041       p = ds_ss (&line);
1042       ss_ltrim (&p, ss_cstr (CC_SPACES));
1043       if (!ss_match_char (&p, '"') || !ss_get_until (&p, '"', &name))
1044         goto lex_error;
1045       if (ss_compare (name, ss_cstr (size)))
1046         continue;
1047
1048       ss_ltrim (&p, ss_cstr (CC_SPACES));
1049       if (ss_match_char (&p, '='))
1050         {
1051           if (free_it)
1052             free (size);
1053           ss_trim (&p, ss_cstr (CC_SPACES));
1054           size = ss_xstrdup (p);
1055           free_it = true;
1056           continue;
1057         }
1058       size = ss_data (p);
1059       break;
1060
1061     lex_error:
1062       error_at_line (0, 0, pprsz_fn, line_number,
1063                      _("syntax error in paper size definition"));
1064     }
1065
1066   /* We found the one we want! */
1067   result = internal_get_paper_size (size, h, v);
1068
1069 exit:
1070   ds_destroy (&line);
1071   if (free_it)
1072     free (size);
1073
1074   if (!result)
1075     error (0, 0, _("error reading paper size definition file"));
1076   
1077   return result;
1078 }
1079
1080 /* If D is NULL, returns the first enabled driver if any, NULL if
1081    none.  Otherwise D must be the last driver returned by this
1082    function, in which case the next enabled driver is returned or NULL
1083    if that was the last. */
1084 struct outp_driver *
1085 outp_drivers (struct outp_driver *d)
1086 {
1087   for (;;)
1088     {
1089       if (d == NULL)
1090         d = outp_driver_list;
1091       else
1092         d = d->next;
1093
1094       if (d == NULL
1095           || (d->device == 0 || (d->device & disabled_devices) != d->device))
1096         break;
1097     }
1098
1099   return d;
1100 }
1101
1102 /* Enables (if ENABLE is nonzero) or disables (if ENABLE is zero) the
1103    device(s) given in mask DEVICE. */
1104 void
1105 outp_enable_device (int enable, int device)
1106 {
1107   if (enable)
1108     disabled_devices &= ~device;
1109   else
1110     disabled_devices |= device;
1111 }
1112
1113 /* Opens a page on driver D (if one is not open). */
1114 void
1115 outp_open_page (struct outp_driver *d) 
1116 {
1117   if (!d->page_open) 
1118     {
1119       d->cp_x = d->cp_y = 0;
1120
1121       d->page_open = true;
1122       if (d->class->open_page != NULL)
1123         d->class->open_page (d);
1124     }
1125 }
1126
1127 /* Closes the page on driver D (if one is open). */
1128 void
1129 outp_close_page (struct outp_driver *d) 
1130 {
1131   if (d->page_open) 
1132     {
1133       if (d->class->close_page != NULL)
1134         d->class->close_page (d);
1135       d->page_open = false;
1136     }
1137 }
1138
1139 /* Ejects the page on device D, if a page is open and non-blank,
1140    and opens a new page.  */
1141 void
1142 outp_eject_page (struct outp_driver *d)
1143 {
1144   if (d->page_open && d->cp_y != 0)
1145     outp_close_page (d);
1146   outp_open_page (d);
1147 }
1148
1149 /* Returns the width of string S, in device units, when output on
1150    device D. */
1151 int
1152 outp_string_width (struct outp_driver *d, const char *s, enum outp_font font)
1153 {
1154   struct outp_text text;
1155   int width;
1156   
1157   text.font = font;
1158   text.justification = OUTP_LEFT;
1159   text.string = ss_cstr (s);
1160   text.h = text.v = INT_MAX;
1161   d->class->text_metrics (d, &text, &width, NULL);
1162
1163   return width;
1164 }