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