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