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