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