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