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