Ready for testing
[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_ASCII:
982               (void) afm_font_encoding (font, AFM_ENCODING_ASCII, enc_flags);
983               break;
984
985             case ENC_ASCII_FISE:
986               /* First apply standard 7bit ASCII encoding. */
987               (void) afm_font_encoding (font, AFM_ENCODING_ASCII, enc_flags);
988
989               /* Then add those scand characters. */
990               for (i = 0; enc_7bit_ascii_fise[i].name; i++)
991                 (void) afm_font_encode (font, enc_7bit_ascii_fise[i].code,
992                                         enc_7bit_ascii_fise[i].name,
993                                         enc_flags);
994               break;
995
996             case ENC_ASCII_DKNO:
997               /* First apply standard 7bit ASCII encoding. */
998               (void) afm_font_encoding (font, AFM_ENCODING_ASCII, enc_flags);
999
1000               /* Then add those scand characters. */
1001               for (i = 0; enc_7bit_ascii_dkno[i].name; i++)
1002                 (void) afm_font_encode (font, enc_7bit_ascii_dkno[i].code,
1003                                         enc_7bit_ascii_dkno[i].name,
1004                                         enc_flags);
1005               break;
1006
1007             case ENC_IBMPC:
1008               (void) afm_font_encoding (font, AFM_ENCODING_IBMPC, enc_flags);
1009               break;
1010
1011             case ENC_MAC:
1012               (void) afm_font_encoding (font, AFM_ENCODING_MAC, enc_flags);
1013               break;
1014
1015             case ENC_VMS:
1016               (void) afm_font_encoding (font, AFM_ENCODING_VMS, enc_flags);
1017               break;
1018
1019             case ENC_HP8:
1020               (void) afm_font_encoding (font, AFM_ENCODING_HP8, enc_flags);
1021               break;
1022
1023             case ENC_KOI8:
1024               (void) afm_font_encoding (font, AFM_ENCODING_KOI8, enc_flags);
1025               break;
1026
1027             case ENC_PS:
1028               /* Let's use font's default encoding -- nothing here. */
1029               break;
1030             }
1031
1032           /* Put it to the AFM cache. */
1033           if (!strhash_put (afm_cache, Fname, strlen (Fname), font, NULL))
1034             font_cached = 0;
1035         }
1036
1037       font_info = (CachedFontInfo *) xcalloc (1, sizeof (*font_info));
1038       /* Read character widths and types. */
1039       for (i = 0; i < 256; i++)
1040         {
1041           AFMNumber w0x, w0y;
1042
1043           (void) afm_font_charwidth (font, Fpt.w, i, &w0x, &w0y);
1044           font_info->font_widths[i] = w0x;
1045
1046           if (font->encoding[i] == AFM_ENC_NONE)
1047             font_info->font_ctype[i] = ' ';
1048           else if (font->encoding[i] == AFM_ENC_NON_EXISTENT)
1049             font_info->font_ctype[i] = '.';
1050           else
1051             font_info->font_ctype[i] = '*';
1052         }
1053
1054       font_info->font_is_fixed
1055         = font->writing_direction_metrics[0].IsFixedPitch;
1056       font_info->font_bbox_lly = font->global_info.FontBBox_lly;
1057
1058       if (!font_cached)
1059         (void) afm_close_font (font);
1060
1061       /* Store font information to the AFM information cache. */
1062       if (!strhash_put (afm_info_cache, buffer_ptr (&fkey),
1063                         strlen (buffer_ptr (&fkey)), font_info, NULL))
1064         font_info_cached = 0;
1065     }
1066
1067   /* Select character widths and types. */
1068   memcpy (font_widths, font_info->font_widths, 256 * sizeof (double));
1069   memcpy (font_ctype, font_info->font_ctype, 256);
1070
1071   font_is_fixed = font_info->font_is_fixed;
1072   font_bbox_lly = font_info->font_bbox_lly;
1073
1074   if (!font_info_cached)
1075     xfree (font_info);
1076
1077   buffer_uninit (&fkey);
1078 }
1079
1080
1081 void
1082 download_font (char *name)
1083 {
1084   AFMError error;
1085   const char *prefix;
1086   struct stat stat_st;
1087   Buffer fname;
1088   unsigned char buf[4096];
1089   FILE *fp;
1090   int i;
1091   char *cp;
1092
1093   /* Get font prefix. */
1094   error = afm_font_prefix (afm, name, &prefix);
1095   if (error != AFM_SUCCESS)
1096     /* Font is unknown, nothing to download. */
1097     return;
1098
1099   /* Check if we have a font description file. */
1100
1101   buffer_init (&fname);
1102
1103   /* .pfa */
1104   buffer_append (&fname, prefix);
1105   buffer_append (&fname, ".pfa");
1106   if (stat (buffer_ptr (&fname), &stat_st) != 0)
1107     {
1108       /* .pfb */
1109       buffer_clear (&fname);
1110       buffer_append (&fname, prefix);
1111       buffer_append (&fname, ".pfb");
1112       if (stat (buffer_ptr (&fname), &stat_st) != 0)
1113         {
1114           /* Couldn't find font description file, nothing to download. */
1115           buffer_uninit (&fname);
1116           return;
1117         }
1118     }
1119
1120   /* Ok, fine.  Font was found. */
1121
1122   MESSAGE (1, (stderr, _("downloading font \"%s\"\n"), name));
1123   fp = fopen (buffer_ptr (&fname), "rb");
1124   if (fp == NULL)
1125     {
1126       MESSAGE (0, (stderr,
1127                    _("couldn't open font description file \"%s\": %s\n"),
1128                    buffer_ptr (&fname), strerror (errno)));
1129       buffer_uninit (&fname);
1130       return;
1131     }
1132   buffer_uninit (&fname);
1133
1134   /* Dump file. */
1135   fprintf (ofp, "%%%%BeginResource: font %s\n", name);
1136
1137   /* Check file type. */
1138   i = fgetc (fp);
1139   if (i == EOF)
1140     {
1141       /* Not much to do here. */
1142       ;
1143     }
1144   else if (i == 128)
1145     {
1146       int done = 0;
1147       unsigned int chunk;
1148       unsigned int to_read;
1149       int last_was_cr;
1150       int j;
1151
1152       /* IBM PC Format */
1153
1154       ungetc (i, fp);
1155
1156       while (!done)
1157         {
1158           /* Read 6-byte long header. */
1159           i = fread (buf, 1, 6, fp);
1160           if (i != 6)
1161             break;
1162
1163           chunk = buf[2] | (buf[3] << 8) | (buf[4] << 16) | (buf[5] << 24);
1164
1165           /* Check chunk type. */
1166           switch (buf[1])
1167             {
1168             case 1:             /* ASCII */
1169               last_was_cr = 0;
1170               while (chunk > 0)
1171                 {
1172                   to_read = sizeof (buf) < chunk ? sizeof (buf) : chunk;
1173                   i = fread (buf, 1, to_read, fp);
1174                   if (i == 0)
1175                     {
1176                       done = 1;
1177                       break;
1178                     }
1179
1180                   /* Check and fix Mac-newlines. */
1181                   for (j = 0; j < i; j++)
1182                     {
1183                       if (j == 0 && last_was_cr && buf[0] != '\n')
1184                         {
1185                           fputc ('\n', ofp);
1186                           fputc (buf[0], ofp);
1187                         }
1188                       else if (buf[j] == '\r' && j + 1 < i
1189                                && buf[j + 1] != '\n')
1190                         {
1191                           fputc ('\n', ofp);
1192                         }
1193                       else if (buf[j] != '\r')
1194                         fputc (buf[j], ofp);
1195                     }
1196
1197                   chunk -= i;
1198                   last_was_cr = (buf[i - 1] == '\r');
1199                 }
1200               break;
1201
1202             case 2:             /* binary data */
1203               while (chunk > 0)
1204                 {
1205                   to_read = sizeof (buf) < chunk ? sizeof (buf) : chunk;
1206                   i = fread (buf, 1, to_read, fp);
1207                   if (i == 0)
1208                     {
1209                       done = 1;
1210                       break;
1211                     }
1212
1213                   for (j = 0; j < i; j++)
1214                     {
1215                       fprintf (ofp, "%02X", buf[j]);
1216                       if ((j + 1) % 32 == 0)
1217                         fprintf (ofp, "\n");
1218                     }
1219                   chunk -= i;
1220                 }
1221               break;
1222
1223             case 3:             /* EOF */
1224               done = 1;
1225               break;
1226             }
1227
1228           /* Force a linebreak after each chunk. */
1229           fprintf (ofp, "\n");
1230         }
1231     }
1232   else
1233     {
1234       /* Plain ASCII. */
1235       ungetc (i, fp);
1236       while ((i = fread (buf, 1, sizeof (buf), fp)) != 0)
1237         fwrite (buf, 1, i, ofp);
1238     }
1239
1240   fprintf (ofp, "%%%%EndResource\n");
1241
1242   /* Remove font from needed resources. */
1243   (void) strhash_delete (res_fonts, name, strlen (name) + 1, (void **) &cp);
1244
1245   fclose (fp);
1246 }
1247
1248
1249 char *
1250 escape_string (char *string)
1251 {
1252   int i, j;
1253   int len;
1254   char *cp;
1255
1256   /* Count the length of the result string. */
1257   for (len = 0, i = 0; string[i]; i++)
1258     switch (string[i])
1259       {
1260       case '(':
1261       case ')':
1262       case '\\':
1263         len += 2;
1264         break;
1265
1266       default:
1267         len++;
1268       }
1269
1270   /* Create result. */
1271   cp = xmalloc (len + 1);
1272   if (cp == NULL)
1273       return NULL;
1274   for (i = 0, j = 0; string[i]; i++)
1275     switch (string[i])
1276       {
1277       case '(':
1278       case ')':
1279       case '\\':
1280         cp[j++] = '\\';
1281         /* FALLTHROUGH */
1282
1283       default:
1284         cp[j++] = string[i];
1285         break;
1286       }
1287   cp[j++] = '\0';
1288
1289   return cp;
1290 }
1291
1292 \f
1293
1294 /*
1295  * Help macros for the format_user_string() function.
1296  */
1297
1298 #define NEED_NBYTES(n)                          \
1299   do {                                          \
1300     if (rbufpos + (n) >= rbuflen)               \
1301       {                                         \
1302         rbuflen += (n) + 1024;                  \
1303         rbuf = xrealloc (rbuf, rbuflen);        \
1304       }                                         \
1305   } while (0)
1306
1307 #define APPEND_CH(ch)                           \
1308   do {                                          \
1309     int a;                                      \
1310     NEED_NBYTES (width);                        \
1311     if (width && justification < 0)             \
1312       rbuf[rbufpos++] = (ch);                   \
1313     for (a = 0; a < width - 1; a++)             \
1314       rbuf[rbufpos++] = ' ';                    \
1315     if (!width || justification > 0)            \
1316       rbuf[rbufpos++] = (ch);                   \
1317   } while (0)
1318
1319 #define APPEND_STR(str)                         \
1320   do {                                          \
1321     int len = strlen ((str));                   \
1322     int nspace;                                 \
1323                                                 \
1324     if (len > width)                            \
1325       nspace = 0;                               \
1326     else                                        \
1327       nspace = width - len;                     \
1328                                                 \
1329     NEED_NBYTES (nspace + len);                 \
1330     if (width && justification > 0)             \
1331       for (; nspace; nspace--)                  \
1332         rbuf[rbufpos++] = ' ';                  \
1333                                                 \
1334     memcpy (rbuf + rbufpos, str, len);          \
1335     rbufpos += len;                             \
1336                                                 \
1337     if (width && justification < 0)             \
1338       for (; nspace; nspace--)                  \
1339         rbuf[rbufpos++] = ' ';                  \
1340   } while (0)
1341
1342 char *
1343 format_user_string (char *context_name, char *str)
1344 {
1345   char *cp;
1346   char *rbuf = NULL;
1347   int rbuflen = 0;
1348   int rbufpos = 0;
1349   int i = 0;
1350   int j;
1351   char buf[512];
1352   char buf2[512];
1353   int width = 0;
1354   int justification = 1;
1355
1356   /* Format string. */
1357   for (i = 0; str[i] != '\0'; i++)
1358     {
1359       int type;
1360
1361       type = str[i];
1362
1363       if (type == '%' || type == '$')
1364         {
1365           i++;
1366           width = 0;
1367           justification = 1;
1368
1369           /* Get optional width and justification. */
1370           if (str[i] == '-')
1371             {
1372               i++;
1373               justification = -1;
1374             }
1375           while (isdigit (str[i]))
1376             width = width * 10 + str[i++] - '0';
1377
1378           /* Handle escapes. */
1379           if (type == '%')
1380             {
1381               /* General state related %-escapes. */
1382               switch (str[i])
1383                 {
1384                 case '%':       /* `%%' character `%' */
1385                   APPEND_CH ('%');
1386                   break;
1387
1388                 case 'c':       /* `%c' trailing component of pwd. */
1389                   if (!getcwd (buf, sizeof (buf)))
1390                     perror("getcwd");
1391                   cp = strrchr (buf, '/');
1392                   if (cp)
1393                     cp++;
1394                   else
1395                     cp = buf;
1396                   APPEND_STR (cp);
1397                   break;
1398
1399                 case 'C':       /* `%C' runtime in `hh:mm:ss' format */
1400                   sprintf (buf, "%02d:%02d:%02d", run_tm.tm_hour,
1401                            run_tm.tm_min, run_tm.tm_sec);
1402                   APPEND_STR (buf);
1403                   break;
1404
1405                 case 'd':       /* `%d' current working directory */
1406                   if (!getcwd (buf, sizeof (buf)))
1407                     perror("getcwd");
1408                   APPEND_STR (buf);
1409                   break;
1410
1411                 case 'D':
1412                   if (str[i + 1] == '{')
1413                     {
1414                       /* `%D{}' format run date with strftime() */
1415                       for (j = 0, i += 2;
1416                            j < sizeof (buf2) && str[i] && str[i] != '}';
1417                            i++, j++)
1418                         buf2[j] = str[i];
1419                       if (str[i] != '}')
1420                         FATAL ((stderr,
1421                                 _("%s: too long format for %%D{} escape"),
1422                                 context_name));
1423
1424                       buf2[j] = '\0';
1425                       strftime (buf, sizeof (buf), buf2, &run_tm);
1426                     }
1427                   else
1428                     {
1429                       /* `%D' run date in `yy-mm-dd' format */
1430                       sprintf (buf, "%02d-%02d-%02d", run_tm.tm_year % 100,
1431                                run_tm.tm_mon + 1, run_tm.tm_mday);
1432                     }
1433                   APPEND_STR (buf);
1434                   break;
1435
1436                 case 'E':       /* `%E' run date in `yy/mm/dd' format */
1437                   sprintf (buf, "%02d/%02d/%02d", run_tm.tm_year % 100,
1438                            run_tm.tm_mon + 1, run_tm.tm_mday);
1439                   APPEND_STR (buf);
1440                   break;
1441
1442                 case 'F':       /* `%F' run date in `dd.mm.yyyy' format */
1443                   sprintf (buf, "%d.%d.%d",
1444                            run_tm.tm_mday,
1445                            run_tm.tm_mon + 1,
1446                            run_tm.tm_year + 1900);
1447                   APPEND_STR (buf);
1448                   break;
1449
1450                 case 'H':       /* `%H' document title */
1451                   APPEND_STR (title);
1452                   break;
1453
1454                 case 'm':       /* `%m' the hostname up to the first `.' */
1455                   (void) gethostname (buf, sizeof (buf));
1456                   cp = strchr (buf, '.');
1457                   if (cp)
1458                     *cp = '\0';
1459                   APPEND_STR (buf);
1460                   break;
1461
1462                 case 'M':       /* `%M' the full hostname */
1463                   (void) gethostname (buf, sizeof (buf));
1464                   APPEND_STR (buf);
1465                   break;
1466
1467                 case 'n':       /* `%n' username */
1468                   APPEND_STR (passwd->pw_name);
1469                   break;
1470
1471                 case 'N':       /* `%N' pw_gecos up to the first `,' char */
1472                   strcpy (buf, passwd->pw_gecos);
1473                   cp = strchr (buf, ',');
1474                   if (cp)
1475                     *cp = '\0';
1476                   APPEND_STR (buf);
1477                   break;
1478
1479                 case 't':       /* `%t' runtime in 12-hour am/pm format */
1480                   sprintf (buf, "%d:%d%s",
1481                            run_tm.tm_hour > 12
1482                            ? run_tm.tm_hour - 12 : run_tm.tm_hour,
1483                            run_tm.tm_min,
1484                            run_tm.tm_hour > 12 ? "pm" : "am");
1485                   APPEND_STR (buf);
1486                   break;
1487
1488                 case 'T':       /* `%T' runtime in 24-hour format */
1489                   sprintf (buf, "%d:%d", run_tm.tm_hour, run_tm.tm_min);
1490                   APPEND_STR (buf);
1491                   break;
1492
1493                 case '*':       /* `%*' runtime in 24-hour format with secs */
1494                   sprintf (buf, "%d:%d:%d", run_tm.tm_hour, run_tm.tm_min,
1495                            run_tm.tm_sec);
1496                   APPEND_STR (buf);
1497                   break;
1498
1499                 case 'W':       /* `%W' run date in `mm/dd/yy' format */
1500                   sprintf (buf, "%02d/%02d/%02d", run_tm.tm_mon + 1,
1501                            run_tm.tm_mday, run_tm.tm_year % 100);
1502                   APPEND_STR (buf);
1503                   break;
1504
1505                 default:
1506                   FATAL ((stderr, _("%s: unknown `%%' escape `%c' (%d)"),
1507                           context_name, str[i], str[i]));
1508                   break;
1509                 }
1510             }
1511           else
1512             {
1513               /* Input file related $-escapes. */
1514               switch (str[i])
1515                 {
1516                 case '$':       /* `$$' character `$' */
1517                   APPEND_CH ('$');
1518                   break;
1519
1520                 case '%':       /* `$%' current page number */
1521                   if (slicing)
1522                     sprintf (buf, "%d%c", current_pagenum, slice - 1 + 'A');
1523                   else
1524                     sprintf (buf, "%d", current_pagenum);
1525                   APPEND_STR (buf);
1526                   break;
1527
1528                 case '=':       /* `$=' number of pages in this file */
1529                   APPEND_CH ('\001');
1530                   break;
1531
1532                 case 'p':       /* `$p' number of pages processed so far */
1533                   sprintf (buf, "%d", total_pages);
1534                   APPEND_STR (buf);
1535                   break;
1536
1537                 case '(':       /* $(ENVVAR)  */
1538                   for (j = 0, i++;
1539                        str[i] && str[i] != ')' && j < sizeof (buf) - 1;
1540                        i++)
1541                     buf[j++] = str[i];
1542
1543                   if (str[i] == '\0')
1544                     FATAL ((stderr, _("%s: no closing ')' for $() escape"),
1545                             context_name));
1546                   if (str[i] != ')')
1547                     FATAL ((stderr, _("%s: too long variable name for $() escape"),
1548                             context_name));
1549
1550                   buf[j] = '\0';
1551
1552                   cp = getenv (buf);
1553                   if (cp == NULL)
1554                     cp = "";
1555                   APPEND_STR (cp);
1556                   break;
1557
1558                 case 'C':       /* `$C' modtime in `hh:mm:ss' format */
1559                   sprintf (buf, "%02d:%02d:%02d", mod_tm.tm_hour,
1560                            mod_tm.tm_min, mod_tm.tm_sec);
1561                   APPEND_STR (buf);
1562                   break;
1563
1564                 case 'D':
1565                   if (str[i + 1] == '{')
1566                     {
1567                       /* `$D{}' format modification date with strftime() */
1568                       for (j = 0, i += 2;
1569                            j < sizeof (buf2) && str[i] && str[i] != '}';
1570                            i++, j++)
1571                         buf2[j] = str[i];
1572                       if (str[i] != '}')
1573                         FATAL ((stderr,
1574                                 _("%s: too long format for $D{} escape"),
1575                                 context_name));
1576
1577                       buf2[j] = '\0';
1578                       strftime (buf, sizeof (buf), buf2, &mod_tm);
1579                     }
1580                   else
1581                     {
1582                       /* `$D' mod date in `yy-mm-dd' format */
1583                       sprintf (buf, "%02d-%02d-%02d", mod_tm.tm_year % 100,
1584                                mod_tm.tm_mon + 1, mod_tm.tm_mday);
1585                     }
1586                   APPEND_STR (buf);
1587                   break;
1588
1589                 case 'E':       /* `$E' mod date in `yy/mm/dd' format */
1590                   sprintf (buf, "%02d/%02d/%02d", mod_tm.tm_year % 100,
1591                            mod_tm.tm_mon + 1, mod_tm.tm_mday);
1592                   APPEND_STR (buf);
1593                   break;
1594
1595                 case 'F':       /* `$F' run date in `dd.mm.yyyy' format */
1596                   sprintf (buf, "%d.%d.%d",
1597                            mod_tm.tm_mday,
1598                            mod_tm.tm_mon + 1,
1599                            mod_tm.tm_year + 1900);
1600                   APPEND_STR (buf);
1601                   break;
1602
1603                 case 't':       /* `$t' runtime in 12-hour am/pm format */
1604                   sprintf (buf, "%d:%d%s",
1605                            mod_tm.tm_hour > 12
1606                            ? mod_tm.tm_hour - 12 : mod_tm.tm_hour,
1607                            mod_tm.tm_min,
1608                            mod_tm.tm_hour > 12 ? "pm" : "am");
1609                   APPEND_STR (buf);
1610                   break;
1611
1612                 case 'T':       /* `$T' runtime in 24-hour format */
1613                   sprintf (buf, "%d:%d", mod_tm.tm_hour, mod_tm.tm_min);
1614                   APPEND_STR (buf);
1615                   break;
1616
1617                 case '*':       /* `$*' runtime in 24-hour format with secs */
1618                   sprintf (buf, "%d:%d:%d", mod_tm.tm_hour, mod_tm.tm_min,
1619                            mod_tm.tm_sec);
1620                   APPEND_STR (buf);
1621                   break;
1622
1623                 case 'v':       /* `$v': input file number */
1624                   sprintf (buf, "%d", input_filenum);
1625                   APPEND_STR (buf);
1626                   break;
1627
1628                 case 'V':       /* `$V': input file number in --toc format */
1629                   if (toc)
1630                     {
1631                       sprintf (buf, "%d-", input_filenum);
1632                       APPEND_STR (buf);
1633                     }
1634                   break;
1635
1636                 case 'W':       /* `$W' run date in `mm/dd/yy' format */
1637                   sprintf (buf, "%02d/%02d/%02d", mod_tm.tm_mon + 1,
1638                            mod_tm.tm_mday, mod_tm.tm_year % 100);
1639                   APPEND_STR (buf);
1640                   break;
1641
1642                 case 'N':       /* `$N' the full name of the printed file */
1643                   APPEND_STR (fname);
1644                   break;
1645
1646                 case 'n':       /* `$n' input file name without directory */
1647                   cp = strrchr (fname, '/');
1648                   if (cp)
1649                     cp++;
1650                   else
1651                     cp = fname;
1652                   APPEND_STR (cp);
1653                   break;
1654
1655                 case 'L':       /* `$L' number of lines in this file. */
1656                   /* This is valid only for TOC-strings. */
1657                   sprintf (buf, "%d", current_file_linenum - 1);
1658                   APPEND_STR (buf);
1659                   break;
1660
1661                 default:
1662                   FATAL ((stderr, _("%s: unknown `$' escape `%c' (%d)"),
1663                           context_name, str[i], str[i]));
1664                   break;
1665                 }
1666             }
1667           /* Reset width so the else-arm goes ok at the next round. */
1668           width = 0;
1669           justification = 1;
1670         }
1671       else
1672         APPEND_CH (str[i]);
1673     }
1674   APPEND_CH ('\0');
1675
1676   /* Escape PS specials. */
1677   cp = escape_string (rbuf);
1678   xfree (rbuf);
1679
1680   return cp;
1681 }
1682
1683 \f
1684 void
1685 parse_key_value_pair (StringHashPtr set, char *kv)
1686 {
1687   char *cp;
1688   Buffer key;
1689
1690   cp = strchr (kv, ':');
1691   if (cp == NULL)
1692     {
1693       if (strhash_delete (set, kv, strlen (kv) + 1, (void **) &cp))
1694         xfree (cp);
1695     }
1696   else
1697     {
1698       buffer_init (&key);
1699       buffer_append_len (&key, kv, cp - kv);
1700
1701       strhash_put (set, buffer_ptr (&key), strlen (buffer_ptr (&key)) + 1,
1702                    xstrdup (cp + 1), (void **) &cp);
1703       if (cp)
1704         xfree (cp);
1705
1706       buffer_uninit (&key);
1707     }
1708 }
1709
1710
1711 int
1712 count_key_value_set (StringHashPtr set)
1713 {
1714   int i = 0, got, j;
1715   char *cp;
1716   void *value;
1717
1718   for (got = strhash_get_first (set, &cp, &j, &value); got;
1719        got = strhash_get_next (set, &cp, &j, &value))
1720     i++;
1721
1722   return i;
1723 }
1724
1725
1726 int
1727 pathwalk (char *path, PathWalkProc proc, void *context)
1728 {
1729   char buf[512];
1730   char *cp;
1731   char *cp2;
1732   int len, i;
1733
1734   for (cp = path; cp; cp = strchr (cp, PATH_SEPARATOR))
1735     {
1736       if (cp != path)
1737         cp++;
1738
1739       cp2 = strchr (cp, PATH_SEPARATOR);
1740       if (cp2)
1741         len = cp2 - cp;
1742       else
1743         len = strlen (cp);
1744
1745       memcpy (buf, cp, len);
1746       buf[len] = '\0';
1747
1748       i = (*proc) (buf, context);
1749       if (i != 0)
1750         return i;
1751     }
1752
1753   return 0;
1754 }
1755
1756
1757 int
1758 file_lookup (char *path, void *context)
1759 {
1760   int len;
1761   FileLookupCtx *ctx = context;
1762   struct stat stat_st;
1763   int i;
1764
1765   MESSAGE (2, (stderr, "file_lookup(): %s/%s%s\t", path, ctx->name,
1766                ctx->suffix));
1767
1768   len = strlen (path);
1769   if (len && path[len - 1] == '/')
1770     len--;
1771
1772   buffer_clear (ctx->fullname);
1773   buffer_append_len (ctx->fullname, path, len);
1774   buffer_append (ctx->fullname, "/");
1775   buffer_append (ctx->fullname, ctx->name);
1776   buffer_append (ctx->fullname, ctx->suffix);
1777
1778   i = stat (buffer_ptr (ctx->fullname), &stat_st) == 0;
1779
1780   MESSAGE (2, (stderr, "#%c\n", i ? 't' : 'f'));
1781
1782   return i;
1783 }
1784
1785
1786 char *
1787 tilde_subst (char *fname)
1788 {
1789   char *cp;
1790   int i;
1791   struct passwd *pswd;
1792   Buffer buffer;
1793   char *result;
1794
1795   if (fname[0] != '~')
1796     return xstrdup (fname);
1797
1798   if (fname[1] == '/' || fname[1] == '\0')
1799     {
1800       /* The the user's home directory from the `HOME' environment
1801          variable. */
1802       cp = getenv ("HOME");
1803       if (cp == NULL)
1804         return xstrdup (fname);
1805
1806       buffer_init (&buffer);
1807       buffer_append (&buffer, cp);
1808       buffer_append (&buffer, fname + 1);
1809
1810       result = buffer_copy (&buffer);
1811       buffer_uninit (&buffer);
1812
1813       return result;
1814     }
1815
1816   /* Get user's login name. */
1817   for (i = 1; fname[i] && fname[i] != '/'; i++)
1818     ;
1819
1820   buffer_init (&buffer);
1821   buffer_append_len (&buffer, fname + 1, i - 1);
1822
1823   pswd = getpwnam (buffer_ptr (&buffer));
1824   buffer_uninit (&buffer);
1825
1826   if (pswd)
1827     {
1828       /* Found passwd entry. */
1829       buffer_init (&buffer);
1830       buffer_append (&buffer, pswd->pw_dir);
1831       buffer_append (&buffer, fname + i);
1832
1833       result = buffer_copy (&buffer);
1834       buffer_uninit (&buffer);
1835
1836       return result;
1837     }
1838
1839   /* No match found. */
1840   return xstrdup (fname);
1841 }
1842
1843
1844 double
1845 parse_float (char *string, int units, int horizontal)
1846 {
1847   double val;
1848   char *end;
1849
1850   val = strtod (string, &end);
1851   if (end == string)
1852   malformed_float:
1853     ERROR ((stderr, _("malformed float dimension: \"%s\""), string));
1854
1855   if (units)
1856     {
1857       switch (*end)
1858         {
1859         case 'c':
1860           val *= 72 / 2.54;
1861           break;
1862
1863         case 'p':
1864           break;
1865
1866         case 'i':
1867           val *= 72;
1868           break;
1869
1870         case '\0':
1871           /* FALLTHROUGH */
1872
1873         case 'l':
1874           if (horizontal)
1875             val *= FNT_CHAR_WIDTH ('m');
1876           else
1877             val *= LINESKIP;
1878           break;
1879
1880         default:
1881           goto malformed_float;
1882           break;
1883         }
1884     }
1885   else
1886     {
1887       if (*end != '\0')
1888         goto malformed_float;
1889     }
1890
1891   return val;
1892 }
1893
1894 \f
1895 /*
1896  * InputStream functions.
1897  */
1898
1899 int
1900 is_open (InputStream *is, FILE *fp, char *fname, char *input_filter)
1901 {
1902   /* Init stream variables. */
1903   is->data_in_buf = 0;
1904   is->bufpos = 0;
1905   is->nreads = 0;
1906   is->unget_ch = NULL;
1907   is->unget_pos = 0;
1908   is->unget_alloc = 0;
1909
1910   /* Input filter? */
1911   if (input_filter)
1912     {
1913       char *cmd = NULL;
1914       int cmdlen;
1915       int i, pos;
1916       char *cp;
1917
1918       is->is_pipe = 1;
1919
1920       if (fname == NULL)
1921         fname = input_filter_stdin;
1922
1923       /*
1924        * Count the initial command length, this will grow dynamically
1925        * when file specifier `%s' is encountered from <input_filter>.
1926        */
1927       cmdlen = strlen (input_filter) + 1;
1928       cmd = xmalloc (cmdlen);
1929
1930       /* Create filter command. */
1931       pos = 0;
1932       for (i = 0; input_filter[i]; i++)
1933         {
1934           if (input_filter[i] == '%')
1935             {
1936               switch (input_filter[i + 1])
1937                 {
1938                 case 's':
1939                   /* Expand cmd-buffer. */
1940                   if ((cp = shell_escape (fname)) != NULL)
1941                     {
1942                       cmdlen += strlen (cp);
1943                       cmd = xrealloc (cmd, cmdlen);
1944
1945                       /* Paste filename. */
1946                       strcpy (cmd + pos, cp);
1947                       pos += strlen (cp);
1948                       free (cp);
1949                     }
1950
1951                   i++;
1952                   break;
1953
1954                 case '%':
1955                   cmd[pos++] = '%';
1956                   i++;
1957                   break;
1958
1959                 default:
1960                   cmd[pos++] = input_filter[i];
1961                   break;
1962                 }
1963             }
1964           else
1965             cmd[pos++] = input_filter[i];
1966         }
1967       cmd[pos++] = '\0';
1968
1969       is->fp = popen (cmd, "r");
1970       xfree (cmd);
1971
1972       if (is->fp == NULL)
1973         {
1974           ERROR ((stderr,
1975                   _("couldn't open input filter \"%s\" for file \"%s\": %s"),
1976                   input_filter, fname ? fname : "(stdin)",
1977                   strerror (errno)));
1978           return 0;
1979         }
1980     }
1981   else
1982     {
1983       /* Just open the stream. */
1984       is->is_pipe = 0;
1985       if (fp)
1986         is->fp = fp;
1987       else
1988         {
1989           is->fp = fopen (fname, "rb");
1990           if (is->fp == NULL)
1991             {
1992               ERROR ((stderr, _("couldn't open input file \"%s\": %s"), fname,
1993                       strerror (errno)));
1994               return 0;
1995             }
1996         }
1997     }
1998
1999   return 1;
2000 }
2001
2002
2003 void
2004 is_close (InputStream *is)
2005 {
2006   if (is->is_pipe)
2007     pclose (is->fp);
2008   else
2009     fclose (is->fp);
2010
2011   if (is->unget_ch)
2012     xfree (is->unget_ch);
2013 }
2014
2015
2016 int
2017 is_getc (InputStream *is)
2018 {
2019   int ch;
2020
2021   if (is->unget_pos > 0)
2022     {
2023       ch = is->unget_ch[--is->unget_pos];
2024       return ch;
2025     }
2026
2027  retry:
2028
2029   /* Do we have any data left? */
2030   if (is->bufpos >= is->data_in_buf)
2031     {
2032       /* At the EOF? */
2033       if (is->nreads > 0 && is->data_in_buf < sizeof (is->buf))
2034         /* Yes. */
2035         return EOF;
2036
2037       /* Read more data. */
2038       is->data_in_buf = fread (is->buf, 1, sizeof (is->buf), is->fp);
2039       is->bufpos = 0;
2040       is->nreads++;
2041
2042       goto retry;
2043     }
2044
2045   return is->buf[is->bufpos++];
2046 }
2047
2048
2049 int
2050 is_ungetc (int ch, InputStream *is)
2051 {
2052   if (is->unget_pos >= is->unget_alloc)
2053     {
2054       is->unget_alloc += 1024;
2055       is->unget_ch = xrealloc (is->unget_ch, is->unget_alloc);
2056     }
2057
2058   is->unget_ch[is->unget_pos++] = ch;
2059
2060   return 1;
2061 }
2062
2063 \f
2064 /*
2065  * Buffer Functions.
2066  */
2067
2068 void
2069 buffer_init (Buffer *buffer)
2070 {
2071   buffer->allocated = 128;
2072   buffer->data = xmalloc (buffer->allocated);
2073   buffer->data[0] = '\0';
2074   buffer->len = 0;
2075 }
2076
2077
2078 void
2079 buffer_uninit (Buffer *buffer)
2080 {
2081   xfree (buffer->data);
2082 }
2083
2084
2085 Buffer *
2086 buffer_alloc ()
2087 {
2088   Buffer *buffer = (Buffer *) xcalloc (1, sizeof (Buffer));
2089
2090   buffer_init (buffer);
2091
2092   return buffer;
2093 }
2094
2095
2096 void
2097 buffer_free (Buffer *buffer)
2098 {
2099   buffer_uninit (buffer);
2100   xfree (buffer);
2101 }
2102
2103
2104 void
2105 buffer_append (Buffer *buffer, const char *data)
2106 {
2107   buffer_append_len (buffer, data, strlen (data));
2108 }
2109
2110
2111 void
2112 buffer_append_len (Buffer *buffer, const char *data, size_t len)
2113 {
2114   if (buffer->len + len + 1 >= buffer->allocated)
2115     {
2116       buffer->allocated = buffer->len + len + 1024;
2117       buffer->data = xrealloc (buffer->data, buffer->allocated);
2118     }
2119
2120   memcpy (buffer->data + buffer->len, data, len);
2121   buffer->len += len;
2122
2123   buffer->data[buffer->len] = '\0';
2124 }
2125
2126
2127 char *
2128 buffer_copy (Buffer *buffer)
2129 {
2130   char *copy = xmalloc (buffer->len + 1);
2131
2132   memcpy (copy, buffer->data, buffer->len + 1);
2133
2134   return copy;
2135 }
2136
2137
2138 void
2139 buffer_clear (Buffer *buffer)
2140 {
2141   buffer->len = 0;
2142   buffer->data[0] = '\0';
2143 }
2144
2145
2146 char *
2147 buffer_ptr (Buffer *buffer)
2148 {
2149   return buffer->data;
2150 }
2151
2152
2153 size_t
2154 buffer_len (Buffer *buffer)
2155 {
2156   return buffer->len;
2157 }
2158
2159 /*
2160  * Escapes the name of a file so that the shell groks it in 'single'
2161  * quotation marks.  The resulting pointer has to be free()ed when not
2162  * longer used.
2163 */
2164 char *
2165 shell_escape(const char *fn)
2166 {
2167   size_t len = 0;
2168   const char *inp;
2169   char *retval, *outp;
2170
2171   for(inp = fn; *inp; ++inp)
2172     switch(*inp)
2173     {
2174       case '\'': len += 4; break;
2175       default:   len += 1; break;
2176     }
2177
2178   outp = retval = malloc(len + 1);
2179   if(!outp)
2180     return NULL; /* perhaps one should do better error handling here */
2181   for(inp = fn; *inp; ++inp)
2182     switch(*inp)
2183     {
2184       case '\'': *outp++ = '\''; *outp++ = '\\'; *outp++ = '\'', *outp++ = '\''; break;
2185       default:   *outp++ = *inp; break;
2186     }
2187   *outp = 0;
2188
2189   return retval;
2190 }