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