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