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