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