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