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