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