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