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