Respect $HOME when searching for ~/.enscriptrc
[enscript.git] / src / util.c
1 /*
2  * Help utilities.
3  * Copyright (c) 1995-1999 Markku Rossi.
4  *
5  * Author: Markku Rossi <mtr@iki.fi>
6  */
7
8 /*
9  * This file is part of GNU Enscript.
10  *
11  * Enscript is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * Enscript is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with Enscript.  If not, see <http://www.gnu.org/licenses/>.
23  */
24
25 #include "gsint.h"
26
27 /*
28  * Types and definitions.
29  */
30
31 #define CFG_FATAL(body)                                         \
32   do {                                                          \
33     fprintf (stderr, "%s:%s:%d: ", program, fname, line);       \
34     fprintf body;                                               \
35     fprintf (stderr, "\n");                                     \
36     fflush (stderr);                                            \
37     exit (1);                                                   \
38   } while (0)
39
40
41 /*
42  * Static variables.
43  */
44
45 /*
46  * 7bit ASCII fi(nland), se (sweden) scand encodings (additions to 7bit ASCII
47  * enc).
48  */
49 static struct
50 {
51   int code;
52   char *name;
53 } enc_7bit_ascii_fise[] =
54 {
55   {'{',         "adieresis"},
56   {'|',         "odieresis"},
57   {'}',         "aring"},
58   {'[',         "Adieresis"},
59   {'\\',        "Odieresis"},
60   {']',         "Aring"},
61   {0, NULL},
62 };
63
64 /*
65  * 7bit ASCII dk (denmark), no(rway) scand encodings (additions to 7bit ASCII
66  * enc).
67  */
68 static struct
69 {
70   int code;
71   char *name;
72 } enc_7bit_ascii_dkno[] =
73 {
74   {'{',         "ae"},
75   {'|',         "oslash"},
76   {'}',         "aring"},
77   {'[',         "AE"},
78   {'\\',        "Oslash"},
79   {']',         "Aring"},
80   {0, NULL},
81 };
82
83
84 /*
85  * Global functions.
86  */
87
88 #define GET_TOKEN(from) (strtok ((from), " \t\n"))
89 #define GET_LINE_TOKEN(from) (strtok ((from), "\n"))
90
91 #define CHECK_TOKEN()                                                   \
92   if (token2 == NULL)                                                   \
93     CFG_FATAL ((stderr, _("missing argument: %s"), token));
94
95 int
96 read_config (char *path, char *file)
97 {
98   FILE *fp;
99   Buffer fname;
100   char buf[4096];
101   char *token, *token2;
102   int line = 0;
103
104   buffer_init (&fname);
105   buffer_append (&fname, path);
106   buffer_append (&fname, "/");
107   buffer_append (&fname, file);
108
109   fp = fopen (buffer_ptr (&fname), "r");
110
111   buffer_uninit (&fname);
112
113   if (fp == NULL)
114     return 0;
115
116   while (fgets (buf, sizeof (buf), fp))
117     {
118       line++;
119
120       if (buf[0] == '#')
121         continue;
122
123       token = GET_TOKEN (buf);
124       if (token == NULL)
125         /* Empty line. */
126         continue;
127
128       if (MATCH (token, "AcceptCompositeCharacters:"))
129         {
130           token2 = GET_TOKEN (NULL);
131           CHECK_TOKEN ();
132           accept_composites = atoi (token2);
133         }
134       else if (MATCH (token, "AFMPath:"))
135         {
136           token2 = GET_TOKEN (NULL);
137           CHECK_TOKEN ();
138           xfree (afm_path);
139           afm_path = xstrdup (token2);
140         }
141       else if (MATCH (token, "AppendCtrlD:"))
142         {
143           token2 = GET_TOKEN (NULL);
144           CHECK_TOKEN ();
145           append_ctrl_D = atoi (token2);
146         }
147       else if (MATCH (token, "Clean7Bit:"))
148         {
149           token2 = GET_TOKEN (NULL);
150           CHECK_TOKEN ();
151           clean_7bit = atoi (token2);
152         }
153       else if (MATCH (token, "DefaultEncoding:"))
154         {
155           token2 = GET_TOKEN (NULL);
156           CHECK_TOKEN ();
157           xfree (encoding_name);
158           encoding_name = xstrdup (token2);
159         }
160       else if (MATCH (token, "DefaultFancyHeader:"))
161         {
162           token2 = GET_TOKEN (NULL);
163           CHECK_TOKEN ();
164           xfree (fancy_header_default);
165           fancy_header_default = xstrdup (token2);
166         }
167       else if (MATCH (token, "DefaultMedia:"))
168         {
169           token2 = GET_TOKEN (NULL);
170           CHECK_TOKEN ();
171           xfree (media_name);
172           media_name = xstrdup (token2);
173         }
174       else if (MATCH (token, "DefaultOutputMethod:"))
175         {
176           token2 = GET_TOKEN (NULL);
177           CHECK_TOKEN ();
178           if (MATCH (token2, "printer"))
179             output_file = OUTPUT_FILE_NONE;
180           else if (MATCH (token2, "stdout"))
181             output_file = OUTPUT_FILE_STDOUT;
182           else
183             CFG_FATAL ((stderr, _("illegal value \"%s\" for option %s"),
184                         token2, token));
185         }
186       else if (MATCH (token, "DownloadFont:"))
187         {
188           token2 = GET_TOKEN (NULL);
189           CHECK_TOKEN ();
190           strhash_put (download_fonts, token2, strlen (token2) + 1, NULL,
191                        NULL);
192         }
193       else if (MATCH (token, "EscapeChar:"))
194         {
195           token2 = GET_TOKEN (NULL);
196           CHECK_TOKEN ();
197           escape_char = atoi (token2);
198           if (escape_char < 0 || escape_char > 255)
199             CFG_FATAL ((stderr, _("invalid value \"%s\" for option %s"),
200                         token2, token));
201         }
202       else if (MATCH (token, "FormFeedType:"))
203         {
204           token2 = GET_TOKEN (NULL);
205           CHECK_TOKEN ();
206           if (MATCH (token2, "column"))
207             formfeed_type = FORMFEED_COLUMN;
208           else if (MATCH (token2, "page"))
209             formfeed_type = FORMFEED_PAGE;
210           else
211             CFG_FATAL ((stderr, _("illegal value \"%s\" for option %s"),
212                         token2, token));
213         }
214       else if (MATCH (token, "GeneratePageSize:"))
215         {
216           token2 = GET_TOKEN (NULL);
217           CHECK_TOKEN ();
218           generate_PageSize = atoi (token2);
219         }
220       else if (MATCH (token, "HighlightBarGray:"))
221         {
222           token2 = GET_TOKEN (NULL);
223           CHECK_TOKEN ();
224           highlight_bar_gray = atof (token2);
225         }
226       else if (MATCH (token, "HighlightBars:"))
227         {
228           token2 = GET_TOKEN (NULL);
229           CHECK_TOKEN ();
230           highlight_bars = atoi (token2);
231         }
232       else if (MATCH (token, "LibraryPath:"))
233         {
234           token2 = GET_TOKEN (NULL);
235           CHECK_TOKEN ();
236           xfree (libpath);
237           libpath = xstrdup (token2);
238         }
239       else if (MATCH (token, "MarkWrappedLines:"))
240         {
241           token2 = GET_TOKEN (NULL);
242           CHECK_TOKEN ();
243           xfree (mark_wrapped_lines_style_name);
244           mark_wrapped_lines_style_name = xstrdup (token2);
245         }
246       else if (MATCH (token, "Media:"))
247         {
248           char *name;
249           int w, h, llx, lly, urx, ury;
250
251           token2 = GET_TOKEN (NULL);
252           CHECK_TOKEN ();
253           name = token2;
254
255           token2 = GET_TOKEN (NULL);
256           CHECK_TOKEN ();
257           w = atoi (token2);
258
259           token2 = GET_TOKEN (NULL);
260           CHECK_TOKEN ();
261           h = atoi (token2);
262
263           token2 = GET_TOKEN (NULL);
264           CHECK_TOKEN ();
265           llx = atoi (token2);
266
267           token2 = GET_TOKEN (NULL);
268           CHECK_TOKEN ();
269           lly = atoi (token2);
270
271           token2 = GET_TOKEN (NULL);
272           CHECK_TOKEN ();
273           urx = atoi (token2);
274
275           token2 = GET_TOKEN (NULL);
276           CHECK_TOKEN ();
277           ury = atoi (token2);
278
279           add_media (name, w, h, llx, lly, urx, ury);
280         }
281       else if (MATCH (token, "NoJobHeaderSwitch:"))
282         {
283           token2 = GET_LINE_TOKEN (NULL);
284           CHECK_TOKEN ();
285           xfree (no_job_header_switch);
286           no_job_header_switch = xstrdup (token2);
287         }
288       else if (MATCH (token, "NonPrintableFormat:"))
289         {
290           token2 = GET_TOKEN (NULL);
291           CHECK_TOKEN ();
292           xfree (npf_name);
293           npf_name = xstrdup (token2);
294         }
295       else if (MATCH (token, "OutputFirstLine:"))
296         {
297           token2 = GET_LINE_TOKEN (NULL);
298           CHECK_TOKEN ();
299           xfree (output_first_line);
300           output_first_line = xstrdup (token2);
301         }
302       else if (MATCH (token, "PageLabelFormat:"))
303         {
304           token2 = GET_TOKEN (NULL);
305           CHECK_TOKEN ();
306           xfree (page_label_format);
307           page_label_format = xstrdup (token2);
308         }
309       else if (MATCH (token, "PagePrefeed:"))
310         {
311           token2 = GET_TOKEN (NULL);
312           CHECK_TOKEN ();
313           page_prefeed = atoi (token2);
314         }
315       else if (MATCH (token, "PostScriptLevel:"))
316         {
317           token2 = GET_TOKEN (NULL);
318           CHECK_TOKEN ();
319           pslevel = atoi (token2);
320         }
321       else if (MATCH (token, "Printer:"))
322         {
323           token2 = GET_TOKEN (NULL);
324           CHECK_TOKEN ();
325           xfree (printer);
326           printer = xstrdup (token2);
327         }
328       else if (MATCH (token, "QueueParam:"))
329         {
330           token2 = GET_LINE_TOKEN (NULL);
331           CHECK_TOKEN ();
332           xfree (queue_param);
333           queue_param = xstrdup (token2);
334         }
335       else if (MATCH (token, "SetPageDevice:"))
336         {
337           token2 = GET_LINE_TOKEN (NULL);
338           CHECK_TOKEN ();
339           parse_key_value_pair (pagedevice, token2);
340         }
341       else if (MATCH (token, "Spooler:"))
342         {
343           token2 = GET_TOKEN (NULL);
344           CHECK_TOKEN ();
345           xfree (spooler_command);
346           spooler_command = xstrdup (token2);
347         }
348       else if (MATCH (token, "StatesBinary:"))
349         {
350           token2 = GET_TOKEN (NULL);
351           CHECK_TOKEN ();
352           xfree (states_binary);
353           states_binary = xstrdup (token2);
354         }
355       else if (MATCH (token, "StatesColor:"))
356         {
357           token2 = GET_TOKEN (NULL);
358           CHECK_TOKEN ();
359           states_color = atoi (token2);
360         }
361       else if (MATCH (token, "StatesConfigFile:"))
362         {
363           token2 = GET_LINE_TOKEN (NULL);
364           CHECK_TOKEN ();
365           xfree (states_config_file);
366           states_config_file = xstrdup (token2);
367         }
368       else if (MATCH (token, "StatesHighlightStyle:"))
369         {
370           token2 = GET_TOKEN (NULL);
371           CHECK_TOKEN ();
372           xfree (states_highlight_style);
373           states_highlight_style = xstrdup (token2);
374         }
375       else if (MATCH (token, "StatesPath:"))
376         {
377           token2 = GET_LINE_TOKEN (NULL);
378           CHECK_TOKEN ();
379           xfree (states_path);
380           states_path = xstrdup (token2);
381         }
382       else if (MATCH (token, "StatusDict:"))
383         {
384           token2 = GET_TOKEN (NULL);
385           CHECK_TOKEN ();
386           parse_key_value_pair (statusdict, token2);
387         }
388       else if (MATCH (token, "TOCFormat:"))
389         {
390           token2 = GET_LINE_TOKEN (NULL);
391           CHECK_TOKEN ();
392           toc_fmt_string = xstrdup (token2);
393         }
394       else if (MATCH (token, "Underlay:"))
395         {
396           token2 = GET_LINE_TOKEN (NULL);
397           CHECK_TOKEN ();
398           underlay = xmalloc (strlen (token2) + 1);
399           strcpy (underlay, token2);
400         }
401       else if (MATCH (token, "UnderlayAngle:"))
402         {
403           token2 = GET_TOKEN (NULL);
404           CHECK_TOKEN ();
405           ul_angle = atof (token2);
406           ul_angle_p = 1;
407         }
408       else if (MATCH (token, "UnderlayFont:"))
409         {
410           token2 = GET_TOKEN (NULL);
411           CHECK_TOKEN ();
412           if (!parse_font_spec (token2, &ul_font, &ul_ptsize, NULL))
413             CFG_FATAL ((stderr, _("malformed font spec: %s"), token2));
414         }
415       else if (MATCH (token, "UnderlayGray:"))
416         {
417           token2 = GET_TOKEN (NULL);
418           CHECK_TOKEN ();
419           ul_gray = atof (token2);
420         }
421       else if (MATCH (token, "UnderlayPosition:"))
422         {
423           token2 = GET_TOKEN (NULL);
424           CHECK_TOKEN ();
425           xfree (ul_position);
426           ul_position = xstrdup (token2);
427           ul_position_p = 1;
428         }
429       else if (MATCH (token, "UnderlayStyle:"))
430         {
431           token2 = GET_TOKEN (NULL);
432           CHECK_TOKEN ();
433           xfree (ul_style_str);
434           ul_style_str = xstrdup (token2);
435         }
436       else
437         CFG_FATAL ((stderr, _("illegal option: %s"), token));
438     }
439   return 1;
440 }
441
442
443 void
444 add_media (char *name, int w, int h, int llx, int lly, int urx, int ury)
445 {
446   MediaEntry *entry;
447
448   MESSAGE (2,
449            (stderr,
450             "add_media: name=%s, w=%d, h=%d, llx=%d, lly=%d, urx=%d, ury=%d\n",
451             name, w, h, llx, lly, urx, ury));
452
453   entry = xcalloc (1, sizeof (*entry));
454   entry->name = xmalloc (strlen (name) + 1);
455
456   strcpy (entry->name, name);
457   entry->w = w;
458   entry->h = h;
459   entry->llx = llx;
460   entry->lly = lly;
461   entry->urx = urx;
462   entry->ury = ury;
463
464   entry->next = media_names;
465   media_names = entry;
466 }
467
468
469 void
470 do_list_missing_characters (int *array)
471 {
472   int i;
473   int count = 0;
474
475   for (i = 0; i < 256; i++)
476     if (array[i])
477       {
478         fprintf (stderr, "%3d ", i);
479         count++;
480         if (count % 15 == 0)
481           fprintf (stderr, "\n");
482       }
483
484   if (count % 15 != 0)
485     fprintf (stderr, "\n");
486 }
487
488
489 int
490 file_existsp (char *name, char *suffix)
491 {
492   FileLookupCtx ctx;
493   int result;
494
495   ctx.name = name;
496   ctx.suffix =  suffix ? suffix : "";
497   ctx.fullname = buffer_alloc ();
498
499   result = pathwalk (libpath, file_lookup, &ctx);
500
501   buffer_free (ctx.fullname);
502
503   return result;
504 }
505
506
507 int
508 paste_file (char *name, char *suffix)
509 {
510   char buf[512];
511   char resources[512];
512   FILE *fp;
513   FileLookupCtx ctx;
514   int pending_comment = 0;
515   int line = 0;
516
517   ctx.name = name;
518   ctx.suffix = suffix ? suffix : "";
519   ctx.fullname = buffer_alloc ();
520
521   if (!pathwalk (libpath, file_lookup, &ctx))
522     {
523       buffer_free (ctx.fullname);
524       return 0;
525     }
526   fp = fopen (buffer_ptr (ctx.fullname), "r");
527   if (fp == NULL)
528     {
529       buffer_free (ctx.fullname);
530       return 0;
531     }
532
533   /* Find the end of the header. */
534 #define HDR_TAG "% -- code follows this line --"
535   while ((fgets (buf, sizeof (buf), fp)))
536     {
537       line++;
538       if (strncmp (buf, HDR_TAG, strlen (HDR_TAG)) == 0)
539         break;
540     }
541
542   /* Dump rest of file. */
543   while ((fgets (buf, sizeof (buf), fp)))
544     {
545       line++;
546
547       /*
548        * Document needed resources?
549        */
550 #define RESOURCE_DSC    "%%DocumentNeededResources:"
551 #define CONT_DSC        "%%+"
552       if (strncmp (buf, RESOURCE_DSC, strlen (RESOURCE_DSC)) == 0)
553         {
554           char *cp, *cp2;
555
556           strcpy (resources, buf + strlen (RESOURCE_DSC));
557           pending_comment = 1;
558
559         parse_resources:
560           /* Register needed resources. */
561           cp = GET_TOKEN (resources);
562           if (cp == NULL)
563             /* Get the next line. */
564             continue;
565
566           if (MATCH (cp, "font"))
567             {
568               for (cp = GET_TOKEN (NULL); cp; cp = GET_TOKEN (NULL))
569                 /* Is this font already known? */
570                 if (!strhash_get (res_fonts, cp, strlen (cp) + 1,
571                                   (void **) &cp2))
572                   {
573                     /* Not it is not,  we must include this resource. */
574                     fprintf (ofp, "%%%%IncludeResource: font %s\n", cp);
575
576                     /*
577                      * And register that this resource is needed in
578                      * this document.
579                      */
580                     strhash_put (res_fonts, cp, strlen (cp) + 1, NULL, NULL);
581                   }
582
583               /* Do not pass this DSC row to the output. */
584               continue;
585             }
586           else
587             /* Unknown resource, ignore. */
588             continue;
589         }
590       else if (pending_comment
591                && strncmp (buf, CONT_DSC, strlen (CONT_DSC)) == 0)
592         {
593           strcpy (resources, buf + strlen (CONT_DSC));
594           goto parse_resources;
595         }
596       else
597         pending_comment = 0;
598
599       /*
600        * `%Format' directive?
601        */
602 #define DIRECTIVE_FORMAT "%Format:"
603       if (strncmp (buf, DIRECTIVE_FORMAT, strlen (DIRECTIVE_FORMAT)) == 0)
604         {
605           int i, j;
606           char name[256];
607           char *cp, *cp2;
608           errno = 0;
609
610           /* Skip the leading whitespace. */
611           for (i = strlen (DIRECTIVE_FORMAT); buf[i] && isspace (buf[i]); i++)
612             ;
613           if (!buf[i])
614             FATAL ((stderr, _("%s:%d: %%Format: no name"),
615                     buffer_ptr (ctx.fullname), line));
616
617           /* Copy name. */
618           for (j = 0;
619                j < sizeof (name) - 1 && buf[i] && !isspace (buf[i]);
620                i++)
621             name[j++] = buf[i];
622           name[j] = '\0';
623
624           if (j >= sizeof (name) - 1)
625             FATAL ((stderr, _("%s:%d: %%Format: too long name, maxlen=%d"),
626                     buffer_ptr (ctx.fullname), line, sizeof (name) - 1));
627
628           /* Find the start of the format string. */
629           for (; buf[i] && isspace (buf[i]); i++)
630             ;
631
632           /* Find the end. */
633           j = strlen (buf);
634           for (j--; isspace (buf[j]) && j > i; j--)
635             ;
636           j++;
637
638           MESSAGE (2, (stderr, "%%Format: %s %.*s\n", name, j - i, buf + i));
639
640           cp = xmalloc (j - i + 1);
641           memcpy (cp, buf + i, j - i);
642           cp[j - i] = '\0';
643
644           strhash_put (user_strings, name, strlen (name) + 1, cp,
645                        (void **) &cp2);
646           if (cp2)
647             FATAL ((stderr,
648                     _("%s:%d: %%Format: name \"%s\" is already defined"),
649                     buffer_ptr (ctx.fullname), line, name));
650
651           /* All done with the `%Format' directive. */
652           continue;
653         }
654
655       /*
656        * `%HeaderHeight' directive?
657        */
658 #define DIRECTIVE_HEADERHEIGHT "%HeaderHeight:"
659       if (strncmp (buf, DIRECTIVE_HEADERHEIGHT,
660                    strlen (DIRECTIVE_HEADERHEIGHT)) == 0)
661           {
662             int i;
663
664             /* Find the start of the pts argument. */
665             for (i = strlen (DIRECTIVE_HEADERHEIGHT);
666                  buf[i] && !isspace (buf[i]); i++)
667               ;
668             if (!buf[i])
669               FATAL ((stderr, _("%s:%d: %%HeaderHeight: no argument"),
670                       buffer_ptr (ctx.fullname), line));
671
672             d_header_h = atoi (buf + i);
673             MESSAGE (2, (stderr, "%%HeaderHeight: %d\n", d_header_h));
674             continue;
675           }
676
677       /*
678        * `%FooterHeight' directive?
679        */
680 #define DIRECTIVE_FOOTERHEIGHT "%FooterHeight:"
681       if (strncmp (buf, DIRECTIVE_FOOTERHEIGHT,
682                    strlen (DIRECTIVE_FOOTERHEIGHT)) == 0)
683         {
684           int i;
685
686           /* Find the start of the pts argument. */
687           for (i = strlen (DIRECTIVE_FOOTERHEIGHT);
688                buf[i] && !isspace (buf[i]); i++)
689             ;
690           if (!buf[i])
691             FATAL ((stderr, _("%s:%d: %%FooterHeight: no argument"),
692                     buffer_ptr (ctx.fullname), line));
693
694           d_footer_h = atoi (buf + i);
695           MESSAGE (2, (stderr, "%%FooterHeight: %d\n", d_footer_h));
696           continue;
697         }
698
699       /* Nothing special, just copy it to the output. */
700       fputs (buf, ofp);
701     }
702
703   fclose (fp);
704   buffer_free (ctx.fullname);
705
706   return 1;
707 }
708
709
710 int
711 parse_font_spec (char *spec_a, char **name_return, FontPoint *size_return,
712                  InputEncoding *encoding_return)
713 {
714   int i, j;
715   char *cp, *cp2;
716   char *spec;
717   char *encp;
718
719   spec = xstrdup (spec_a);
720
721   /* Check for the `namesize:encoding' format. */
722   encp = strrchr (spec, ':');
723   if (encp)
724     {
725       *encp = '\0';
726       encp++;
727     }
728
729   /* The `name@ptsize' format? */
730   cp = strchr (spec, '@');
731   if (cp)
732     {
733       i = cp - spec;
734       if (cp[1] == '\0')
735         {
736           /* No ptsize after '@'. */
737           xfree (spec);
738           return 0;
739         }
740       cp++;
741     }
742   else
743     {
744       /* The old `nameptsize' format. */
745       i = strlen (spec) - 1;
746       if (i <= 0 || !ISNUMBERDIGIT (spec[i]))
747         {
748           xfree (spec);
749           return 0;
750         }
751
752       for (i--; i >= 0 && ISNUMBERDIGIT (spec[i]); i--)
753         ;
754       if (i < 0)
755         {
756           xfree (spec);
757           return 0;
758         }
759       if (spec[i] == '/')
760         {
761           /* We accept one slash for the `pt/pt' format. */
762           for (i--; i >= 0 && ISNUMBERDIGIT (spec[i]); i--)
763             ;
764           if (i < 0)
765             {
766               xfree (spec);
767               return 0;
768             }
769         }
770       i++;
771
772       /* Now, <i> points to the end of the name.  Let's set the <cp>
773          to the beginning of the point size and share a little code
774          with the other format. */
775       cp = spec + i;
776     }
777
778   /* Check the font point size. */
779   cp2 = strchr (cp, '/');
780   if (cp2)
781     {
782       *cp2++ = '\0';
783       size_return->w = atof (cp);
784       size_return->h = atof (cp2);
785     }
786   else
787     size_return->w = size_return->h = atof (cp);
788
789   /* Extract the font name. */
790   *name_return = (char *) xcalloc (1, i + 1);
791   strncpy (*name_return, spec, i);
792
793   /* Check the input encoding. */
794   if (encp)
795     {
796       int found = 0;
797
798       if (encoding_return == NULL)
799         {
800           /* We don't allow it here. */
801           xfree (spec);
802           return 0;
803         }
804
805       for (i = 0; !found && encodings[i].names[0]; i++)
806         for (j = 0; j < 3; j++)
807           if (encodings[i].names[j] != NULL && MATCH (encodings[i].names[j],
808                                                       encp))
809             {
810               /* Found a match. */
811               *encoding_return = encodings[i].encoding;
812               encp = encodings[i].names[0];
813               found = 1;
814               break;
815             }
816
817       if (!found)
818         {
819           xfree (spec);
820           return 0;
821         }
822     }
823   else
824     {
825       /* The spec didn't contain the encoding part.  Use our global default. */
826       encp = encoding_name;
827       if (encoding_return)
828         *encoding_return = encoding;
829     }
830   xfree (spec);
831
832   MESSAGE (2, (stderr,
833                "parse_font_spec(): name=%.*s, size=%g/%g, encoding=%s\n", i,
834                *name_return, size_return->w, size_return->h,
835                encp));
836
837   if (size_return->w < 0.0 && size_return->h < 0.0)
838     MESSAGE (0, (stderr, _("%s: warning: font size is negative\n"), program));
839   else if (size_return->w < 0.0)
840     MESSAGE (0, (stderr, _("%s: warning: font width is negative\n"), program));
841   else if (size_return->h < 0.0)
842     MESSAGE (0, (stderr, _("%s: warning: font height is negative\n"),
843                  program));
844
845   return 1;
846 }
847
848
849 void
850 read_font_info (void)
851 {
852   CachedFontInfo *font_info;
853   AFMFont font;
854   int font_info_cached = 1;
855   int font_cached = 1;
856   int i;
857   unsigned int enc_flags = 0;
858   char buf[256];
859   Buffer fkey;
860
861   MESSAGE (2, (stderr, _("reading AFM info for font \"%s\"\n"), Fname));
862
863   if (accept_composites)
864     enc_flags = AFM_ENCODE_ACCEPT_COMPOSITES;
865
866   /* Open font */
867
868   buffer_init (&fkey);
869
870   buffer_append (&fkey, Fname);
871   sprintf (buf, "@%f:%d", Fpt.w, encoding);
872   buffer_append (&fkey, buf);
873
874   if (!strhash_get (afm_info_cache, buffer_ptr (&fkey),
875                     strlen (buffer_ptr (&fkey)), (void **) &font_info))
876     {
877       AFMError error;
878
879       /* Couldn't find it from our cache, open open AFM file. */
880       if (!strhash_get (afm_cache, Fname, strlen (Fname), (void **) &font))
881         {
882           /* AFM file was not cached, open it from disk. */
883           error = afm_open_font (afm, AFM_I_COMPOSITES, Fname, &font);
884           if (error != AFM_SUCCESS)
885             {
886 #define COUR "Courier"
887               /*
888                * Do not report failures for "Courier*" fonts because
889                * AFM library's default font will fix them.
890                */
891               if (strncmp (Fname, COUR, strlen (COUR)) != 0)
892                 MESSAGE (0,
893                          (stderr,
894                           _("couldn't open AFM file for font \"%s\", using default\n"),
895                           Fname));
896               error = afm_open_default_font (afm, &font);
897               if (error != AFM_SUCCESS)
898                 {
899                   afm_error_to_string (error, buf);
900                   FATAL ((stderr,
901                           _("couldn't open AFM file for the default font: %s"),
902                           buf));
903                 }
904             }
905
906           /* Apply encoding. */
907           switch (encoding)
908             {
909             case ENC_ISO_8859_1:
910               (void) afm_font_encoding (font, AFM_ENCODING_ISO_8859_1,
911                                         enc_flags);
912               break;
913
914             case ENC_ISO_8859_2:
915               (void) afm_font_encoding (font, AFM_ENCODING_ISO_8859_2,
916                                         enc_flags);
917               break;
918
919             case ENC_ISO_8859_3:
920               (void) afm_font_encoding (font, AFM_ENCODING_ISO_8859_3,
921                                         enc_flags);
922               break;
923
924             case ENC_ISO_8859_4:
925               (void) afm_font_encoding (font, AFM_ENCODING_ISO_8859_4,
926                                         enc_flags);
927               break;
928
929             case ENC_ISO_8859_5:
930               (void) afm_font_encoding (font, AFM_ENCODING_ISO_8859_5,
931                                         enc_flags);
932               break;
933
934             case ENC_ISO_8859_7:
935               (void) afm_font_encoding (font, AFM_ENCODING_ISO_8859_7,
936                                         enc_flags);
937               break;
938
939             case ENC_ISO_8859_9:
940               (void) afm_font_encoding (font, AFM_ENCODING_ISO_8859_9,
941                                         enc_flags);
942               break;
943
944             case ENC_ISO_8859_10:
945               (void) afm_font_encoding (font, AFM_ENCODING_ISO_8859_10,
946                                         enc_flags);
947               break;
948
949             case ENC_ASCII:
950               (void) afm_font_encoding (font, AFM_ENCODING_ASCII, enc_flags);
951               break;
952
953             case ENC_ASCII_FISE:
954               /* First apply standard 7bit ASCII encoding. */
955               (void) afm_font_encoding (font, AFM_ENCODING_ASCII, enc_flags);
956
957               /* Then add those scand characters. */
958               for (i = 0; enc_7bit_ascii_fise[i].name; i++)
959                 (void) afm_font_encode (font, enc_7bit_ascii_fise[i].code,
960                                         enc_7bit_ascii_fise[i].name,
961                                         enc_flags);
962               break;
963
964             case ENC_ASCII_DKNO:
965               /* First apply standard 7bit ASCII encoding. */
966               (void) afm_font_encoding (font, AFM_ENCODING_ASCII, enc_flags);
967
968               /* Then add those scand characters. */
969               for (i = 0; enc_7bit_ascii_dkno[i].name; i++)
970                 (void) afm_font_encode (font, enc_7bit_ascii_dkno[i].code,
971                                         enc_7bit_ascii_dkno[i].name,
972                                         enc_flags);
973               break;
974
975             case ENC_IBMPC:
976               (void) afm_font_encoding (font, AFM_ENCODING_IBMPC, enc_flags);
977               break;
978
979             case ENC_MAC:
980               (void) afm_font_encoding (font, AFM_ENCODING_MAC, enc_flags);
981               break;
982
983             case ENC_VMS:
984               (void) afm_font_encoding (font, AFM_ENCODING_VMS, enc_flags);
985               break;
986
987             case ENC_HP8:
988               (void) afm_font_encoding (font, AFM_ENCODING_HP8, enc_flags);
989               break;
990
991             case ENC_KOI8:
992               (void) afm_font_encoding (font, AFM_ENCODING_KOI8, enc_flags);
993               break;
994
995             case ENC_PS:
996               /* Let's use font's default encoding -- nothing here. */
997               break;
998             }
999
1000           /* Put it to the AFM cache. */
1001           if (!strhash_put (afm_cache, Fname, strlen (Fname), font, NULL))
1002             font_cached = 0;
1003         }
1004
1005       font_info = (CachedFontInfo *) xcalloc (1, sizeof (*font_info));
1006       /* Read character widths and types. */
1007       for (i = 0; i < 256; i++)
1008         {
1009           AFMNumber w0x, w0y;
1010
1011           (void) afm_font_charwidth (font, Fpt.w, i, &w0x, &w0y);
1012           font_info->font_widths[i] = w0x;
1013
1014           if (font->encoding[i] == AFM_ENC_NONE)
1015             font_info->font_ctype[i] = ' ';
1016           else if (font->encoding[i] == AFM_ENC_NON_EXISTENT)
1017             font_info->font_ctype[i] = '.';
1018           else
1019             font_info->font_ctype[i] = '*';
1020         }
1021
1022       font_info->font_is_fixed
1023         = font->writing_direction_metrics[0].IsFixedPitch;
1024       font_info->font_bbox_lly = font->global_info.FontBBox_lly;
1025
1026       if (!font_cached)
1027         (void) afm_close_font (font);
1028
1029       /* Store font information to the AFM information cache. */
1030       if (!strhash_put (afm_info_cache, buffer_ptr (&fkey),
1031                         strlen (buffer_ptr (&fkey)), font_info, NULL))
1032         font_info_cached = 0;
1033     }
1034
1035   /* Select character widths and types. */
1036   memcpy (font_widths, font_info->font_widths, 256 * sizeof (double));
1037   memcpy (font_ctype, font_info->font_ctype, 256);
1038
1039   font_is_fixed = font_info->font_is_fixed;
1040   font_bbox_lly = font_info->font_bbox_lly;
1041
1042   if (!font_info_cached)
1043     xfree (font_info);
1044
1045   buffer_uninit (&fkey);
1046 }
1047
1048
1049 void
1050 download_font (char *name)
1051 {
1052   AFMError error;
1053   const char *prefix;
1054   struct stat stat_st;
1055   Buffer fname;
1056   unsigned char buf[4096];
1057   FILE *fp;
1058   int i;
1059   char *cp;
1060
1061   /* Get font prefix. */
1062   error = afm_font_prefix (afm, name, &prefix);
1063   if (error != AFM_SUCCESS)
1064     /* Font is unknown, nothing to download. */
1065     return;
1066
1067   /* Check if we have a font description file. */
1068
1069   buffer_init (&fname);
1070
1071   /* .pfa */
1072   buffer_append (&fname, prefix);
1073   buffer_append (&fname, ".pfa");
1074   if (stat (buffer_ptr (&fname), &stat_st) != 0)
1075     {
1076       /* .pfb */
1077       buffer_clear (&fname);
1078       buffer_append (&fname, prefix);
1079       buffer_append (&fname, ".pfb");
1080       if (stat (buffer_ptr (&fname), &stat_st) != 0)
1081         {
1082           /* Couldn't find font description file, nothing to download. */
1083           buffer_uninit (&fname);
1084           return;
1085         }
1086     }
1087
1088   /* Ok, fine.  Font was found. */
1089
1090   MESSAGE (1, (stderr, _("downloading font \"%s\"\n"), name));
1091   fp = fopen (buffer_ptr (&fname), "rb");
1092   if (fp == NULL)
1093     {
1094       MESSAGE (0, (stderr,
1095                    _("couldn't open font description file \"%s\": %s\n"),
1096                    buffer_ptr (&fname), strerror (errno)));
1097       buffer_uninit (&fname);
1098       return;
1099     }
1100   buffer_uninit (&fname);
1101
1102   /* Dump file. */
1103   fprintf (ofp, "%%%%BeginResource: font %s\n", name);
1104
1105   /* Check file type. */
1106   i = fgetc (fp);
1107   if (i == EOF)
1108     {
1109       /* Not much to do here. */
1110       ;
1111     }
1112   else if (i == 128)
1113     {
1114       int done = 0;
1115       unsigned int chunk;
1116       unsigned int to_read;
1117       int last_was_cr;
1118       int j;
1119
1120       /* IBM PC Format */
1121
1122       ungetc (i, fp);
1123
1124       while (!done)
1125         {
1126           /* Read 6-byte long header. */
1127           i = fread (buf, 1, 6, fp);
1128           if (i != 6)
1129             break;
1130
1131           chunk = buf[2] | (buf[3] << 8) | (buf[4] << 16) | (buf[5] << 24);
1132
1133           /* Check chunk type. */
1134           switch (buf[1])
1135             {
1136             case 1:             /* ASCII */
1137               last_was_cr = 0;
1138               while (chunk > 0)
1139                 {
1140                   to_read = sizeof (buf) < chunk ? sizeof (buf) : chunk;
1141                   i = fread (buf, 1, to_read, fp);
1142                   if (i == 0)
1143                     {
1144                       done = 1;
1145                       break;
1146                     }
1147
1148                   /* Check and fix Mac-newlines. */
1149                   for (j = 0; j < i; j++)
1150                     {
1151                       if (j == 0 && last_was_cr && buf[0] != '\n')
1152                         {
1153                           fputc ('\n', ofp);
1154                           fputc (buf[0], ofp);
1155                         }
1156                       else if (buf[j] == '\r' && j + 1 < i
1157                                && buf[j + 1] != '\n')
1158                         {
1159                           fputc ('\n', ofp);
1160                         }
1161                       else if (buf[j] != '\r')
1162                         fputc (buf[j], ofp);
1163                     }
1164
1165                   chunk -= i;
1166                   last_was_cr = (buf[i - 1] == '\r');
1167                 }
1168               break;
1169
1170             case 2:             /* binary data */
1171               while (chunk > 0)
1172                 {
1173                   to_read = sizeof (buf) < chunk ? sizeof (buf) : chunk;
1174                   i = fread (buf, 1, to_read, fp);
1175                   if (i == 0)
1176                     {
1177                       done = 1;
1178                       break;
1179                     }
1180
1181                   for (j = 0; j < i; j++)
1182                     {
1183                       fprintf (ofp, "%02X", buf[j]);
1184                       if ((j + 1) % 32 == 0)
1185                         fprintf (ofp, "\n");
1186                     }
1187                   chunk -= i;
1188                 }
1189               break;
1190
1191             case 3:             /* EOF */
1192               done = 1;
1193               break;
1194             }
1195
1196           /* Force a linebreak after each chunk. */
1197           fprintf (ofp, "\n");
1198         }
1199     }
1200   else
1201     {
1202       /* Plain ASCII. */
1203       ungetc (i, fp);
1204       while ((i = fread (buf, 1, sizeof (buf), fp)) != 0)
1205         fwrite (buf, 1, i, ofp);
1206     }
1207
1208   fprintf (ofp, "%%%%EndResource\n");
1209
1210   /* Remove font from needed resources. */
1211   (void) strhash_delete (res_fonts, name, strlen (name) + 1, (void **) &cp);
1212
1213   fclose (fp);
1214 }
1215
1216
1217 char *
1218 escape_string (char *string)
1219 {
1220   int i, j;
1221   int len;
1222   char *cp;
1223
1224   /* Count the length of the result string. */
1225   for (len = 0, i = 0; string[i]; i++)
1226     switch (string[i])
1227       {
1228       case '(':
1229       case ')':
1230       case '\\':
1231         len += 2;
1232         break;
1233
1234       default:
1235         len++;
1236       }
1237
1238   /* Create result. */
1239   cp = xmalloc (len + 1);
1240   for (i = 0, j = 0; string[i]; i++)
1241     switch (string[i])
1242       {
1243       case '(':
1244       case ')':
1245       case '\\':
1246         cp[j++] = '\\';
1247         /* FALLTHROUGH */
1248
1249       default:
1250         cp[j++] = string[i];
1251         break;
1252       }
1253   cp[j++] = '\0';
1254
1255   return cp;
1256 }
1257
1258 \f
1259
1260 /*
1261  * Help macros for the format_user_string() function.
1262  */
1263
1264 #define NEED_NBYTES(n)                          \
1265   do {                                          \
1266     if (rbufpos + (n) >= rbuflen)               \
1267       {                                         \
1268         rbuflen += (n) + 1024;                  \
1269         rbuf = xrealloc (rbuf, rbuflen);        \
1270       }                                         \
1271   } while (0)
1272
1273 #define APPEND_CH(ch)                           \
1274   do {                                          \
1275     int a;                                      \
1276     NEED_NBYTES (width);                        \
1277     if (width && justification < 0)             \
1278       rbuf[rbufpos++] = (ch);                   \
1279     for (a = 0; a < width - 1; a++)             \
1280       rbuf[rbufpos++] = ' ';                    \
1281     if (!width || justification > 0)            \
1282       rbuf[rbufpos++] = (ch);                   \
1283   } while (0)
1284
1285 #define APPEND_STR(str)                         \
1286   do {                                          \
1287     int len = strlen ((str));                   \
1288     int nspace;                                 \
1289                                                 \
1290     if (len > width)                            \
1291       nspace = 0;                               \
1292     else                                        \
1293       nspace = width - len;                     \
1294                                                 \
1295     NEED_NBYTES (nspace + len);                 \
1296     if (width && justification > 0)             \
1297       for (; nspace; nspace--)                  \
1298         rbuf[rbufpos++] = ' ';                  \
1299                                                 \
1300     memcpy (rbuf + rbufpos, str, len);          \
1301     rbufpos += len;                             \
1302                                                 \
1303     if (width && justification < 0)             \
1304       for (; nspace; nspace--)                  \
1305         rbuf[rbufpos++] = ' ';                  \
1306   } while (0)
1307
1308 char *
1309 format_user_string (char *context_name, char *str)
1310 {
1311   char *cp;
1312   char *rbuf = NULL;
1313   int rbuflen = 0;
1314   int rbufpos = 0;
1315   int i = 0;
1316   int j;
1317   char buf[512];
1318   char buf2[512];
1319   int width = 0;
1320   int justification = 1;
1321
1322   /* Format string. */
1323   for (i = 0; str[i] != '\0'; i++)
1324     {
1325       int type;
1326
1327       type = str[i];
1328
1329       if (type == '%' || type == '$')
1330         {
1331           i++;
1332           width = 0;
1333           justification = 1;
1334
1335           /* Get optional width and justification. */
1336           if (str[i] == '-')
1337             {
1338               i++;
1339               justification = -1;
1340             }
1341           while (isdigit (str[i]))
1342             width = width * 10 + str[i++] - '0';
1343
1344           /* Handle escapes. */
1345           if (type == '%')
1346             {
1347               /* General state related %-escapes. */
1348               switch (str[i])
1349                 {
1350                 case '%':       /* `%%' character `%' */
1351                   APPEND_CH ('%');
1352                   break;
1353
1354                 case 'c':       /* `%c' trailing component of pwd. */
1355                   getcwd (buf, sizeof (buf));
1356                   cp = strrchr (buf, '/');
1357                   if (cp)
1358                     cp++;
1359                   else
1360                     cp = buf;
1361                   APPEND_STR (cp);
1362                   break;
1363
1364                 case 'C':       /* `%C' runtime in `hh:mm:ss' format */
1365                   sprintf (buf, "%02d:%02d:%02d", run_tm.tm_hour,
1366                            run_tm.tm_min, run_tm.tm_sec);
1367                   APPEND_STR (buf);
1368                   break;
1369
1370                 case 'd':       /* `%d' current working directory */
1371                   getcwd (buf, sizeof (buf));
1372                   APPEND_STR (buf);
1373                   break;
1374
1375                 case 'D':
1376                   if (str[i + 1] == '{')
1377                     {
1378                       /* `%D{}' format run date with strftime() */
1379                       for (j = 0, i += 2;
1380                            j < sizeof (buf2) && str[i] && str[i] != '}';
1381                            i++, j++)
1382                         buf2[j] = str[i];
1383                       if (str[i] != '}')
1384                         FATAL ((stderr,
1385                                 _("%s: too long format for %%D{} escape"),
1386                                 context_name));
1387
1388                       buf2[j] = '\0';
1389                       strftime (buf, sizeof (buf), buf2, &run_tm);
1390                     }
1391                   else
1392                     {
1393                       /* `%D' run date in `yy-mm-dd' format */
1394                       sprintf (buf, "%02d-%02d-%02d", run_tm.tm_year % 100,
1395                                run_tm.tm_mon + 1, run_tm.tm_mday);
1396                     }
1397                   APPEND_STR (buf);
1398                   break;
1399
1400                 case 'E':       /* `%E' run date in `yy/mm/dd' format */
1401                   sprintf (buf, "%02d/%02d/%02d", run_tm.tm_year % 100,
1402                            run_tm.tm_mon + 1, run_tm.tm_mday);
1403                   APPEND_STR (buf);
1404                   break;
1405
1406                 case 'F':       /* `%F' run date in `dd.mm.yyyy' format */
1407                   sprintf (buf, "%d.%d.%d",
1408                            run_tm.tm_mday,
1409                            run_tm.tm_mon + 1,
1410                            run_tm.tm_year + 1900);
1411                   APPEND_STR (buf);
1412                   break;
1413
1414                 case 'H':       /* `%H' document title */
1415                   APPEND_STR (title);
1416                   break;
1417
1418                 case 'm':       /* `%m' the hostname up to the first `.' */
1419                   (void) gethostname (buf, sizeof (buf));
1420                   cp = strchr (buf, '.');
1421                   if (cp)
1422                     *cp = '\0';
1423                   APPEND_STR (buf);
1424                   break;
1425
1426                 case 'M':       /* `%M' the full hostname */
1427                   (void) gethostname (buf, sizeof (buf));
1428                   APPEND_STR (buf);
1429                   break;
1430
1431                 case 'n':       /* `%n' username */
1432                   APPEND_STR (passwd->pw_name);
1433                   break;
1434
1435                 case 'N':       /* `%N' pw_gecos up to the first `,' char */
1436                   strcpy (buf, passwd->pw_gecos);
1437                   cp = strchr (buf, ',');
1438                   if (cp)
1439                     *cp = '\0';
1440                   APPEND_STR (buf);
1441                   break;
1442
1443                 case 't':       /* `%t' runtime in 12-hour am/pm format */
1444                   sprintf (buf, "%d:%d%s",
1445                            run_tm.tm_hour > 12
1446                            ? run_tm.tm_hour - 12 : run_tm.tm_hour,
1447                            run_tm.tm_min,
1448                            run_tm.tm_hour > 12 ? "pm" : "am");
1449                   APPEND_STR (buf);
1450                   break;
1451
1452                 case 'T':       /* `%T' runtime in 24-hour format */
1453                   sprintf (buf, "%d:%d", run_tm.tm_hour, run_tm.tm_min);
1454                   APPEND_STR (buf);
1455                   break;
1456
1457                 case '*':       /* `%*' runtime in 24-hour format with secs */
1458                   sprintf (buf, "%d:%d:%d", run_tm.tm_hour, run_tm.tm_min,
1459                            run_tm.tm_sec);
1460                   APPEND_STR (buf);
1461                   break;
1462
1463                 case 'W':       /* `%W' run date in `mm/dd/yy' format */
1464                   sprintf (buf, "%02d/%02d/%02d", run_tm.tm_mon + 1,
1465                            run_tm.tm_mday, run_tm.tm_year % 100);
1466                   APPEND_STR (buf);
1467                   break;
1468
1469                 default:
1470                   FATAL ((stderr, _("%s: unknown `%%' escape `%c' (%d)"),
1471                           context_name, str[i], str[i]));
1472                   break;
1473                 }
1474             }
1475           else
1476             {
1477               /* Input file related $-escapes. */
1478               switch (str[i])
1479                 {
1480                 case '$':       /* `$$' character `$' */
1481                   APPEND_CH ('$');
1482                   break;
1483
1484                 case '%':       /* `$%' current page number */
1485                   if (slicing)
1486                     sprintf (buf, "%d%c", current_pagenum, slice - 1 + 'A');
1487                   else
1488                     sprintf (buf, "%d", current_pagenum);
1489                   APPEND_STR (buf);
1490                   break;
1491
1492                 case '=':       /* `$=' number of pages in this file */
1493                   APPEND_CH ('\001');
1494                   break;
1495
1496                 case 'p':       /* `$p' number of pages processed so far */
1497                   sprintf (buf, "%d", total_pages);
1498                   APPEND_STR (buf);
1499                   break;
1500
1501                 case '(':       /* $(ENVVAR)  */
1502                   for (j = 0, i++;
1503                        str[i] && str[i] != ')' && j < sizeof (buf) - 1;
1504                        i++)
1505                     buf[j++] = str[i];
1506
1507                   if (str[i] == '\0')
1508                     FATAL ((stderr, _("%s: no closing ')' for $() escape"),
1509                             context_name));
1510                   if (str[i] != ')')
1511                     FATAL ((stderr, _("%s: too long variable name for $() escape"),
1512                             context_name));
1513
1514                   buf[j] = '\0';
1515
1516                   cp = getenv (buf);
1517                   if (cp == NULL)
1518                     cp = "";
1519                   APPEND_STR (cp);
1520                   break;
1521
1522                 case 'C':       /* `$C' modtime in `hh:mm:ss' format */
1523                   sprintf (buf, "%02d:%02d:%02d", mod_tm.tm_hour,
1524                            mod_tm.tm_min, mod_tm.tm_sec);
1525                   APPEND_STR (buf);
1526                   break;
1527
1528                 case 'D':
1529                   if (str[i + 1] == '{')
1530                     {
1531                       /* `$D{}' format modification date with strftime() */
1532                       for (j = 0, i += 2;
1533                            j < sizeof (buf2) && str[i] && str[i] != '}';
1534                            i++, j++)
1535                         buf2[j] = str[i];
1536                       if (str[i] != '}')
1537                         FATAL ((stderr,
1538                                 _("%s: too long format for $D{} escape"),
1539                                 context_name));
1540
1541                       buf2[j] = '\0';
1542                       strftime (buf, sizeof (buf), buf2, &mod_tm);
1543                     }
1544                   else
1545                     {
1546                       /* `$D' mod date in `yy-mm-dd' format */
1547                       sprintf (buf, "%02d-%02d-%02d", mod_tm.tm_year % 100,
1548                                mod_tm.tm_mon + 1, mod_tm.tm_mday);
1549                     }
1550                   APPEND_STR (buf);
1551                   break;
1552
1553                 case 'E':       /* `$E' mod date in `yy/mm/dd' format */
1554                   sprintf (buf, "%02d/%02d/%02d", mod_tm.tm_year % 100,
1555                            mod_tm.tm_mon + 1, mod_tm.tm_mday);
1556                   APPEND_STR (buf);
1557                   break;
1558
1559                 case 'F':       /* `$F' run date in `dd.mm.yyyy' format */
1560                   sprintf (buf, "%d.%d.%d",
1561                            mod_tm.tm_mday,
1562                            mod_tm.tm_mon + 1,
1563                            mod_tm.tm_year + 1900);
1564                   APPEND_STR (buf);
1565                   break;
1566
1567                 case 't':       /* `$t' runtime in 12-hour am/pm format */
1568                   sprintf (buf, "%d:%d%s",
1569                            mod_tm.tm_hour > 12
1570                            ? mod_tm.tm_hour - 12 : mod_tm.tm_hour,
1571                            mod_tm.tm_min,
1572                            mod_tm.tm_hour > 12 ? "pm" : "am");
1573                   APPEND_STR (buf);
1574                   break;
1575
1576                 case 'T':       /* `$T' runtime in 24-hour format */
1577                   sprintf (buf, "%d:%d", mod_tm.tm_hour, mod_tm.tm_min);
1578                   APPEND_STR (buf);
1579                   break;
1580
1581                 case '*':       /* `$*' runtime in 24-hour format with secs */
1582                   sprintf (buf, "%d:%d:%d", mod_tm.tm_hour, mod_tm.tm_min,
1583                            mod_tm.tm_sec);
1584                   APPEND_STR (buf);
1585                   break;
1586
1587                 case 'v':       /* `$v': input file number */
1588                   sprintf (buf, "%d", input_filenum);
1589                   APPEND_STR (buf);
1590                   break;
1591
1592                 case 'V':       /* `$V': input file number in --toc format */
1593                   if (toc)
1594                     {
1595                       sprintf (buf, "%d-", input_filenum);
1596                       APPEND_STR (buf);
1597                     }
1598                   break;
1599
1600                 case 'W':       /* `$W' run date in `mm/dd/yy' format */
1601                   sprintf (buf, "%02d/%02d/%02d", mod_tm.tm_mon + 1,
1602                            mod_tm.tm_mday, mod_tm.tm_year % 100);
1603                   APPEND_STR (buf);
1604                   break;
1605
1606                 case 'N':       /* `$N' the full name of the printed file */
1607                   APPEND_STR (fname);
1608                   break;
1609
1610                 case 'n':       /* `$n' input file name without directory */
1611                   cp = strrchr (fname, '/');
1612                   if (cp)
1613                     cp++;
1614                   else
1615                     cp = fname;
1616                   APPEND_STR (cp);
1617                   break;
1618
1619                 case 'L':       /* `$L' number of lines in this file. */
1620                   /* This is valid only for TOC-strings. */
1621                   sprintf (buf, "%d", current_file_linenum - 1);
1622                   APPEND_STR (buf);
1623                   break;
1624
1625                 default:
1626                   FATAL ((stderr, _("%s: unknown `$' escape `%c' (%d)"),
1627                           context_name, str[i], str[i]));
1628                   break;
1629                 }
1630             }
1631           /* Reset width so the else-arm goes ok at the next round. */
1632           width = 0;
1633           justification = 1;
1634         }
1635       else
1636         APPEND_CH (str[i]);
1637     }
1638   APPEND_CH ('\0');
1639
1640   /* Escape PS specials. */
1641   cp = escape_string (rbuf);
1642   xfree (rbuf);
1643
1644   return cp;
1645 }
1646
1647 \f
1648 void
1649 parse_key_value_pair (StringHashPtr set, char *kv)
1650 {
1651   char *cp;
1652   Buffer key;
1653
1654   cp = strchr (kv, ':');
1655   if (cp == NULL)
1656     {
1657       if (strhash_delete (set, kv, strlen (kv) + 1, (void **) &cp))
1658         xfree (cp);
1659     }
1660   else
1661     {
1662       buffer_init (&key);
1663       buffer_append_len (&key, kv, cp - kv);
1664
1665       strhash_put (set, buffer_ptr (&key), strlen (buffer_ptr (&key)) + 1,
1666                    xstrdup (cp + 1), (void **) &cp);
1667       if (cp)
1668         xfree (cp);
1669
1670       buffer_uninit (&key);
1671     }
1672 }
1673
1674
1675 int
1676 count_key_value_set (StringHashPtr set)
1677 {
1678   int i = 0, got, j;
1679   char *cp;
1680   void *value;
1681
1682   for (got = strhash_get_first (set, &cp, &j, &value); got;
1683        got = strhash_get_next (set, &cp, &j, &value))
1684     i++;
1685
1686   return i;
1687 }
1688
1689
1690 int
1691 pathwalk (char *path, PathWalkProc proc, void *context)
1692 {
1693   char buf[512];
1694   char *cp;
1695   char *cp2;
1696   int len, i;
1697
1698   for (cp = path; cp; cp = strchr (cp, PATH_SEPARATOR))
1699     {
1700       if (cp != path)
1701         cp++;
1702
1703       cp2 = strchr (cp, PATH_SEPARATOR);
1704       if (cp2)
1705         len = cp2 - cp;
1706       else
1707         len = strlen (cp);
1708
1709       memcpy (buf, cp, len);
1710       buf[len] = '\0';
1711
1712       i = (*proc) (buf, context);
1713       if (i != 0)
1714         return i;
1715     }
1716
1717   return 0;
1718 }
1719
1720
1721 int
1722 file_lookup (char *path, void *context)
1723 {
1724   int len;
1725   FileLookupCtx *ctx = context;
1726   struct stat stat_st;
1727   int i;
1728
1729   MESSAGE (2, (stderr, "file_lookup(): %s/%s%s\t", path, ctx->name,
1730                ctx->suffix));
1731
1732   len = strlen (path);
1733   if (len && path[len - 1] == '/')
1734     len--;
1735
1736   buffer_clear (ctx->fullname);
1737   buffer_append_len (ctx->fullname, path, len);
1738   buffer_append (ctx->fullname, "/");
1739   buffer_append (ctx->fullname, ctx->name);
1740   buffer_append (ctx->fullname, ctx->suffix);
1741
1742   i = stat (buffer_ptr (ctx->fullname), &stat_st) == 0;
1743
1744   MESSAGE (2, (stderr, "#%c\n", i ? 't' : 'f'));
1745
1746   return i;
1747 }
1748
1749
1750 char *
1751 tilde_subst (char *fname)
1752 {
1753   char *cp;
1754   int i;
1755   struct passwd *pswd;
1756   Buffer buffer;
1757   char *result;
1758
1759   if (fname[0] != '~')
1760     return xstrdup (fname);
1761
1762   if (fname[1] == '/' || fname[1] == '\0')
1763     {
1764       /* The the user's home directory from the `HOME' environment
1765          variable. */
1766       cp = getenv ("HOME");
1767       if (cp == NULL)
1768         return xstrdup (fname);
1769
1770       buffer_init (&buffer);
1771       buffer_append (&buffer, cp);
1772       buffer_append (&buffer, fname + 1);
1773
1774       result = buffer_copy (&buffer);
1775       buffer_uninit (&buffer);
1776
1777       return result;
1778     }
1779
1780   /* Get user's login name. */
1781   for (i = 1; fname[i] && fname[i] != '/'; i++)
1782     ;
1783
1784   buffer_init (&buffer);
1785   buffer_append_len (&buffer, fname + 1, i - 1);
1786
1787   pswd = getpwnam (buffer_ptr (&buffer));
1788   buffer_uninit (&buffer);
1789
1790   if (pswd)
1791     {
1792       /* Found passwd entry. */
1793       buffer_init (&buffer);
1794       buffer_append (&buffer, pswd->pw_dir);
1795       buffer_append (&buffer, fname + i);
1796
1797       result = buffer_copy (&buffer);
1798       buffer_uninit (&buffer);
1799
1800       return result;
1801     }
1802
1803   /* No match found. */
1804   return xstrdup (fname);
1805 }
1806
1807
1808 double
1809 parse_float (char *string, int units, int horizontal)
1810 {
1811   double val;
1812   char *end;
1813
1814   val = strtod (string, &end);
1815   if (end == string)
1816   malformed_float:
1817     ERROR ((stderr, _("malformed float dimension: \"%s\""), string));
1818
1819   if (units)
1820     {
1821       switch (*end)
1822         {
1823         case 'c':
1824           val *= 72 / 2.54;
1825           break;
1826
1827         case 'p':
1828           break;
1829
1830         case 'i':
1831           val *= 72;
1832           break;
1833
1834         case '\0':
1835           /* FALLTHROUGH */
1836
1837         case 'l':
1838           if (horizontal)
1839             val *= CHAR_WIDTH ('m');
1840           else
1841             val *= LINESKIP;
1842           break;
1843
1844         default:
1845           goto malformed_float;
1846           break;
1847         }
1848     }
1849   else
1850     {
1851       if (*end != '\0')
1852         goto malformed_float;
1853     }
1854
1855   return val;
1856 }
1857
1858 \f
1859 /*
1860  * InputStream functions.
1861  */
1862
1863 int
1864 is_open (InputStream *is, FILE *fp, char *fname, char *input_filter)
1865 {
1866   /* Init stream variables. */
1867   is->data_in_buf = 0;
1868   is->bufpos = 0;
1869   is->nreads = 0;
1870   is->unget_ch = NULL;
1871   is->unget_pos = 0;
1872   is->unget_alloc = 0;
1873
1874   /* Input filter? */
1875   if (input_filter)
1876     {
1877       char *cmd = NULL;
1878       int cmdlen;
1879       int i, pos;
1880
1881       is->is_pipe = 1;
1882
1883       if (fname == NULL)
1884         fname = input_filter_stdin;
1885
1886       /*
1887        * Count the initial command length, this will grow dynamically
1888        * when file specifier `%s' is encountered from <input_filter>.
1889        */
1890       cmdlen = strlen (input_filter) + 1;
1891       cmd = xmalloc (cmdlen);
1892
1893       /* Create filter command. */
1894       pos = 0;
1895       for (i = 0; input_filter[i]; i++)
1896         {
1897           if (input_filter[i] == '%')
1898             {
1899               switch (input_filter[i + 1])
1900                 {
1901                 case 's':
1902                   /* Expand cmd-buffer. */
1903                   cmdlen += strlen (fname);
1904                   cmd = xrealloc (cmd, cmdlen);
1905
1906                   /* Paste filename. */
1907                   strcpy (cmd + pos, fname);
1908                   pos += strlen (fname);
1909
1910                   i++;
1911                   break;
1912
1913                 case '%':
1914                   cmd[pos++] = '%';
1915                   i++;
1916                   break;
1917
1918                 default:
1919                   cmd[pos++] = input_filter[i];
1920                   break;
1921                 }
1922             }
1923           else
1924             cmd[pos++] = input_filter[i];
1925         }
1926       cmd[pos++] = '\0';
1927
1928       is->fp = popen (cmd, "r");
1929       xfree (cmd);
1930
1931       if (is->fp == NULL)
1932         {
1933           ERROR ((stderr,
1934                   _("couldn't open input filter \"%s\" for file \"%s\": %s"),
1935                   input_filter, fname ? fname : "(stdin)",
1936                   strerror (errno)));
1937           return 0;
1938         }
1939     }
1940   else
1941     {
1942       /* Just open the stream. */
1943       is->is_pipe = 0;
1944       if (fp)
1945         is->fp = fp;
1946       else
1947         {
1948           is->fp = fopen (fname, "rb");
1949           if (is->fp == NULL)
1950             {
1951               ERROR ((stderr, _("couldn't open input file \"%s\": %s"), fname,
1952                       strerror (errno)));
1953               return 0;
1954             }
1955         }
1956     }
1957
1958   return 1;
1959 }
1960
1961
1962 void
1963 is_close (InputStream *is)
1964 {
1965   if (is->is_pipe)
1966     pclose (is->fp);
1967   else
1968     fclose (is->fp);
1969
1970   if (is->unget_ch)
1971     xfree (is->unget_ch);
1972 }
1973
1974
1975 int
1976 is_getc (InputStream *is)
1977 {
1978   int ch;
1979
1980   if (is->unget_pos > 0)
1981     {
1982       ch = is->unget_ch[--is->unget_pos];
1983       return ch;
1984     }
1985
1986  retry:
1987
1988   /* Do we have any data left? */
1989   if (is->bufpos >= is->data_in_buf)
1990     {
1991       /* At the EOF? */
1992       if (is->nreads > 0 && is->data_in_buf < sizeof (is->buf))
1993         /* Yes. */
1994         return EOF;
1995
1996       /* Read more data. */
1997       is->data_in_buf = fread (is->buf, 1, sizeof (is->buf), is->fp);
1998       is->bufpos = 0;
1999       is->nreads++;
2000
2001       goto retry;
2002     }
2003
2004   return is->buf[is->bufpos++];
2005 }
2006
2007
2008 int
2009 is_ungetc (int ch, InputStream *is)
2010 {
2011   if (is->unget_pos >= is->unget_alloc)
2012     {
2013       is->unget_alloc += 1024;
2014       is->unget_ch = xrealloc (is->unget_ch, is->unget_alloc);
2015     }
2016
2017   is->unget_ch[is->unget_pos++] = ch;
2018
2019   return 1;
2020 }
2021
2022 \f
2023 /*
2024  * Buffer Functions.
2025  */
2026
2027 void
2028 buffer_init (Buffer *buffer)
2029 {
2030   buffer->allocated = 128;
2031   buffer->data = xmalloc (buffer->allocated);
2032   buffer->data[0] = '\0';
2033   buffer->len = 0;
2034 }
2035
2036
2037 void
2038 buffer_uninit (Buffer *buffer)
2039 {
2040   xfree (buffer->data);
2041 }
2042
2043
2044 Buffer *
2045 buffer_alloc ()
2046 {
2047   Buffer *buffer = (Buffer *) xcalloc (1, sizeof (Buffer));
2048
2049   buffer_init (buffer);
2050
2051   return buffer;
2052 }
2053
2054
2055 void
2056 buffer_free (Buffer *buffer)
2057 {
2058   buffer_uninit (buffer);
2059   xfree (buffer);
2060 }
2061
2062
2063 void
2064 buffer_append (Buffer *buffer, const char *data)
2065 {
2066   buffer_append_len (buffer, data, strlen (data));
2067 }
2068
2069
2070 void
2071 buffer_append_len (Buffer *buffer, const char *data, size_t len)
2072 {
2073   if (buffer->len + len + 1 >= buffer->allocated)
2074     {
2075       buffer->allocated = buffer->len + len + 1024;
2076       buffer->data = xrealloc (buffer->data, buffer->allocated);
2077     }
2078
2079   memcpy (buffer->data + buffer->len, data, len);
2080   buffer->len += len;
2081
2082   buffer->data[buffer->len] = '\0';
2083 }
2084
2085
2086 char *
2087 buffer_copy (Buffer *buffer)
2088 {
2089   char *copy = xmalloc (buffer->len + 1);
2090
2091   memcpy (copy, buffer->data, buffer->len + 1);
2092
2093   return copy;
2094 }
2095
2096
2097 void
2098 buffer_clear (Buffer *buffer)
2099 {
2100   buffer->len = 0;
2101   buffer->data[0] = '\0';
2102 }
2103
2104
2105 char *
2106 buffer_ptr (Buffer *buffer)
2107 {
2108   return buffer->data;
2109 }
2110
2111
2112 size_t
2113 buffer_len (Buffer *buffer)
2114 {
2115   return buffer->len;
2116 }