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