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