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