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