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