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