Fri Dec 12 23:54:37 2003 Ben Pfaff <blp@gnu.org>
[pspp-builds.git] / src / groff-font.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 #include <config.h>
21 #include <assert.h>
22 #include <stdio.h>
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <limits.h>
26 #include <stdarg.h>
27 #include "alloc.h"
28 #include "error.h"
29 #include "filename.h"
30 #include "font.h"
31 #include "hash.h"
32 #include "pool.h"
33 #include "str.h"
34 #include "version.h"
35
36 int font_number_to_index (int);
37
38 int space_index;
39
40 static int font_msg (int, const char *,...)
41      __attribute__ ((format (printf, 2, 3)));
42 static void scan_badchars (char *, int);
43 static void dup_char_metric (struct font_desc * font, int dest, int src);
44 static void add_char_metric (struct font_desc * font, struct char_metrics *metrics,
45                              int code);
46 static void add_kern (struct font_desc * font, int ch1, int ch2, int adjust);
47
48 /* Typical whitespace characters for tokenizing. */
49 static const char whitespace[] = " \t\n\r\v";
50
51 void
52 groff_init (void)
53 {
54   space_index = font_char_name_to_index ("space");
55 }
56
57 /* Some notes on the groff_font(8) manpage:
58
59    DESC file format: A typical PostScript `res' would be 72000, with
60    `hor' and `vert' set to 1 to indicate that all those positions are
61    valid.  `sizescale' of 1000 would indicate that a scaled point is
62    1/1000 of a point (which is 1/72000 of an inch, the same as the
63    number of machine units per inch indicated on `res').  `unitwidth'
64    of 1000 would indicate that font files are set up for fonts with
65    point size of 1000 scaled points, which would equal 1/72 inch or 1
66    point (this would tell Groff's postprocessor that it needs to scale
67    the font 12 times larger to get a 12-point font). */
68
69 /* Reads a Groff font description file and converts it to a usable
70    binary format in memory.  Installs the binary format in the global
71    font table.  See groff_font(8) for a description of the font
72    description format supported.  Returns nonzero on success. */
73 struct font_desc *
74 groff_read_font (const char *fn)
75 {
76   struct char_metrics *metrics;
77
78   /* Pool created for font, font being created, font file. */
79   struct pool *font_pool = NULL;
80   struct font_desc *font = NULL;
81   FILE *f = NULL;
82
83   /* Current line, size of line buffer, length of line. */
84   char *line = NULL;
85   size_t size;
86   int len;
87
88   /* Tokenization saved pointer. */
89   char *sp;
90   
91   /* First token on line. */
92   char *key;
93
94   /* 0=kernpairs section, 1=charset section. */
95   int charset;
96
97   /* Index for previous line. */
98   int prev_index = -1;
99
100   /* Current location in file, used for error reporting. */
101   struct file_locator where;
102
103 #if unix
104   fn = fn_tilde_expand (fn);
105 #endif
106
107   msg (VM (1), _("%s: Opening Groff font file..."), fn);
108
109   where.filename = fn;
110   where.line_number = 1;
111   err_push_file_locator (&where);
112
113   f = fopen (fn, "r");
114   if (!f)
115     goto file_lossage;
116
117   font_pool = pool_create ();
118   font = pool_alloc (font_pool, sizeof *font);
119   font->owner = font_pool;
120   font->name = NULL;
121   font->internal_name = NULL;
122   font->encoding = NULL;
123   font->space_width = 0;
124   font->slant = 0.0;
125   font->ligatures = 0;
126   font->special = 0;
127   font->deref = NULL;
128   font->deref_size = 0;
129   font->metric = NULL;
130   font->metric_size = 0;
131   font->metric_used = 0;
132   font->kern = NULL;
133   font->kern_size = 8;
134   font->kern_used = 0;
135   font->kern_max_used = 0;
136
137   /* Parses first section of font file. */
138   for (;;)
139     {
140       /* Location of '#' in line. */
141       char *p;
142
143       len = getline (&line, &size, f);
144       if (len == -1)
145         break;
146       
147       scan_badchars (line, len);
148       p = strchr (line, '#');
149       if (p)
150         *p = '\0';              /* Reject comments. */
151
152       key = strtok_r (line, whitespace, &sp);
153       if (!key)
154         goto next_iteration;
155
156       if (!strcmp (key, "internalname"))
157         {
158           font->internal_name = strtok_r (NULL, whitespace, &sp);
159           if (font->internal_name == NULL)
160             {
161               font_msg (SE, _("Missing font name."));
162               goto lose;
163             }
164           font->internal_name = pool_strdup (font_pool, font->internal_name);
165         }
166       else if (!strcmp (key, "encoding"))
167         {
168           font->encoding = strtok_r (NULL, whitespace, &sp);
169           if (font->encoding == NULL)
170             {
171               font_msg (SE, _("Missing encoding filename."));
172               goto lose;
173             }
174           font->encoding = pool_strdup (font_pool, font->encoding);
175         }
176       else if (!strcmp (key, "spacewidth"))
177         {
178           char *n = strtok_r (NULL, whitespace, &sp);
179           char *tail;
180           if (n)
181             font->space_width = strtol (n, &tail, 10);
182           if (n == NULL || tail == n)
183             {
184               font_msg (SE, _("Bad spacewidth value."));
185               goto lose;
186             }
187         }
188       else if (!strcmp (key, "slant"))
189         {
190           char *n = strtok_r (NULL, whitespace, &sp);
191           char *tail;
192           if (n)
193             font->slant = strtod (n, &tail);
194           if (n == NULL || tail == n)
195             {
196               font_msg (SE, _("Bad slant value."));
197               goto lose;
198             }
199         }
200       else if (!strcmp (key, "ligatures"))
201         {
202           char *lig;
203
204           for (;;)
205             {
206               lig = strtok_r (NULL, whitespace, &sp);
207               if (!lig || !strcmp (lig, "0"))
208                 break;
209               else if (!strcmp (lig, "ff"))
210                 font->ligatures |= LIG_ff;
211               else if (!strcmp (lig, "ffi"))
212                 font->ligatures |= LIG_ffi;
213               else if (!strcmp (lig, "ffl"))
214                 font->ligatures |= LIG_ffl;
215               else if (!strcmp (lig, "fi"))
216                 font->ligatures |= LIG_fi;
217               else if (!strcmp (lig, "fl"))
218                 font->ligatures |= LIG_fl;
219               else
220                 {
221                   font_msg (SE, _("Unknown ligature `%s'."), lig);
222                   goto lose;
223                 }
224             }
225         }
226       else if (!strcmp (key, "special"))
227         font->special = 1;
228       else if (!strcmp (key, "charset") || !strcmp (key, "kernpairs"))
229         break;
230
231       where.line_number++;
232     }
233   if (ferror (f))
234     goto file_lossage;
235
236   /* Parses second section of font file (metrics & kerning data). */
237   do
238     {
239       key = strtok_r (line, whitespace, &sp);
240       if (!key)
241         goto next_iteration;
242
243       if (!strcmp (key, "charset"))
244         charset = 1;
245       else if (!strcmp (key, "kernpairs"))
246         charset = 0;
247       else if (charset)
248         {
249           struct char_metrics *metrics = pool_alloc (font_pool,
250                                                      sizeof *metrics);
251           char *m, *type, *code, *tail;
252
253           m = strtok_r (NULL, whitespace, &sp);
254           if (!m)
255             {
256               font_msg (SE, _("Unexpected end of line reading character "
257                               "set."));
258               goto lose;
259             }
260           if (!strcmp (m, "\""))
261             {
262               if (!prev_index)
263                 {
264                   font_msg (SE, _("Can't use ditto mark for first character."));
265                   goto lose;
266                 }
267               if (!strcmp (key, "---"))
268                 {
269                   font_msg (SE, _("Can't ditto into an unnamed character."));
270                   goto lose;
271                 }
272               dup_char_metric (font, font_char_name_to_index (key), prev_index);
273               where.line_number++;
274               goto next_iteration;
275             }
276
277           if (m)
278             {
279               metrics->code = metrics->width
280                 = metrics->height = metrics->depth = 0;
281             }
282           
283           if (m == NULL || 1 > sscanf (m, "%d,%d,%d", &metrics->width,
284                                        &metrics->height, &metrics->depth))
285             {
286               font_msg (SE, _("Missing metrics for character `%s'."), key);
287               goto lose;
288             }
289
290           type = strtok_r (NULL, whitespace, &sp);
291           if (type)
292             metrics->type = strtol (type, &tail, 10);
293           if (!type || tail == type)
294             {
295               font_msg (SE, _("Missing type for character `%s'."), key);
296               goto lose;
297             }
298
299           code = strtok_r (NULL, whitespace, &sp);
300           if (code)
301             metrics->code = strtol (code, &tail, 0);
302           if (tail == code)
303             {
304               font_msg (SE, _("Missing code for character `%s'."), key);
305               goto lose;
306             }
307
308           if (strcmp (key, "---"))
309             prev_index = font_char_name_to_index (key);
310           else
311             prev_index = font_number_to_index (metrics->code);
312           add_char_metric (font, metrics, prev_index);
313         }
314       else
315         {
316           char *c1 = key;
317           char *c2 = strtok_r (NULL, whitespace, &sp);
318           char *n, *tail;
319           int adjust;
320
321           if (c2 == NULL)
322             {
323               font_msg (SE, _("Malformed kernpair."));
324               goto lose;
325             }
326
327           n = strtok_r (NULL, whitespace, &sp);
328           if (!n)
329             {
330               font_msg (SE, _("Unexpected end of line reading kernpairs."));
331               goto lose;
332             }
333           adjust = strtol (n, &tail, 10);
334           if (tail == n || *tail)
335             {
336               font_msg (SE, _("Bad kern value."));
337               goto lose;
338             }
339           add_kern (font, font_char_name_to_index (c1),
340                     font_char_name_to_index (c2), adjust);
341         }
342
343     next_iteration:
344       where.line_number++;
345
346       len = getline (&line, &size, f);
347     }
348   while (len != -1);
349   
350   if (ferror (f))
351     goto file_lossage;
352   if (fclose (f) == EOF)
353     {
354       f = NULL;
355       goto file_lossage;
356     }
357   free (line);
358 #if unix
359   free ((char *) fn);
360 #endif
361
362   /* Get font ascent and descent. */
363   metrics = font_get_char_metrics (font, font_char_name_to_index ("d"));
364   font->ascent = metrics ? metrics->height : 0;
365   metrics = font_get_char_metrics (font, font_char_name_to_index ("p"));
366   font->descent = metrics ? metrics->depth : 0;
367
368   msg (VM (2), _("Font read successfully with internal name %s."),
369        font->internal_name == NULL ? "<none>" : font->internal_name);
370   
371   err_pop_file_locator (&where);
372
373   return font;
374
375   /* Come here on a file error. */
376 file_lossage:
377   msg (ME, "%s: %s", fn, strerror (errno));
378
379   /* Come here on any error. */
380 lose:
381   if (f != NULL)
382     fclose (f);
383   pool_destroy (font_pool);
384 #if unix
385   free ((char *) fn);
386 #endif
387   err_pop_file_locator (&where);
388
389   msg (VM (1), _("Error reading font."));
390   return NULL;
391 }
392
393 /* Prints a font error on stderr. */
394 static int
395 font_msg (int class, const char *format,...)
396 {
397   va_list args;
398
399   va_start (args, format);
400   tmsg (class, format, args, _("installation error: Groff font error: "));
401   va_end (args);
402
403   return 0;
404 }
405
406 /* Scans string LINE of length LEN (not incl. null terminator) for bad
407    characters, converts to spaces; reports warnings on file FN. */
408 static void
409 scan_badchars (char *line, int len)
410 {
411   unsigned char *cp = line;
412
413   /* Same bad characters as Groff. */
414   static unsigned char badchars[32] =
415   {
416     0x01, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
417     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
418     0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
419     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
420   };
421
422   for (; len--; cp++)
423     if (badchars[*cp >> 3] & (1 << (*cp & 7)))
424       {
425         font_msg (SE, _("Bad character \\%3o."), *cp);
426         *cp = ' ';
427       }
428 }
429 \f
430 /* Character name hashing. */
431
432 /* Associates a character index with a character name. */
433 struct index_hash
434   {
435     char *name;
436     int index;
437   };
438
439 /* Character index hash table. */
440 static struct
441   {
442     int size;                   /* Size of table (must be power of 2). */
443     int used;                   /* Number of full entries. */
444     int next_index;             /* Next index to allocate. */
445     struct index_hash *tab;     /* Hash table proper. */
446     struct pool *ar;            /* Pool for names. */
447   }
448 hash;
449
450 /* Searches for NAME in the global character code table, returns the
451    index if found; otherwise inserts NAME and returns the new
452    index. */
453 int
454 font_char_name_to_index (const char *name)
455 {
456   int i;
457
458   if (name[0] == ' ')
459     return space_index;
460   if (name[0] == '\0' || name[1] == '\0')
461     return name[0];
462   if (0 == strncmp (name, "char", 4))
463     {
464       char *tail;
465       int x = strtol (name + 4, &tail, 10);
466       if (tail != name + 4 && *tail == 0 && x >= 0 && x <= 255)
467         return x;
468     }
469
470   if (!hash.tab)
471     {
472       hash.size = 128;
473       hash.used = 0;
474       hash.next_index = 256;
475       hash.tab = xmalloc (sizeof *hash.tab * hash.size);
476       hash.ar = pool_create ();
477       for (i = 0; i < hash.size; i++)
478         hash.tab[i].name = NULL;
479     }
480
481   for (i = hsh_hash_string (name) & (hash.size - 1); hash.tab[i].name; )
482     {
483       if (!strcmp (hash.tab[i].name, name))
484         return hash.tab[i].index;
485       if (++i >= hash.size)
486         i = 0;
487     }
488
489   hash.used++;
490   if (hash.used >= hash.size / 2)
491     {
492       struct index_hash *old_tab = hash.tab;
493       int old_size = hash.size;
494       int i, j;
495
496       hash.size *= 2;
497       hash.tab = xmalloc (sizeof *hash.tab * hash.size);
498       for (i = 0; i < hash.size; i++)
499         hash.tab[i].name = NULL;
500       for (i = 0; i < old_size; i++)
501         if (old_tab[i].name)
502           {
503             for (j = hsh_hash_string (old_tab[i].name) & (hash.size - 1);
504                  hash.tab[j].name;)
505               if (++j >= hash.size)
506                 j = 0;
507             hash.tab[j] = old_tab[i];
508           }
509       free (old_tab);
510     }
511
512   hash.tab[i].name = pool_strdup (hash.ar, name);
513   hash.tab[i].index = hash.next_index;
514   return hash.next_index++;
515 }
516
517 /* Returns an index for a character that has only a code, not a
518    name. */
519 int
520 font_number_to_index (int x)
521 {
522   char name[INT_DIGITS + 2];
523
524   /* Note that space is the only character that can't appear in a
525      character name.  That makes it an excellent choice for a name
526      that won't conflict. */
527   sprintf (name, " %d", x);
528   return font_char_name_to_index (name);
529 }
530 \f
531 /* Font character metric entries. */
532
533 /* Ensures room for at least MIN_SIZE metric indexes in deref of
534    FONT. */
535 static void
536 check_deref_space (struct font_desc *font, int min_size)
537 {
538   if (min_size >= font->deref_size)
539     {
540       int i = font->deref_size;
541
542       font->deref_size = min_size + 16;
543       if (font->deref_size < 256)
544         font->deref_size = 256;
545       font->deref = pool_realloc (font->owner, font->deref,
546                                   sizeof *font->deref * font->deref_size);
547       for (; i < font->deref_size; i++)
548         font->deref[i] = -1;
549     }
550 }
551
552 /* Inserts METRICS for character with code CODE into FONT. */
553 static void
554 add_char_metric (struct font_desc *font, struct char_metrics *metrics, int code)
555 {
556   check_deref_space (font, code);
557   if (font->metric_used >= font->metric_size)
558     {
559       font->metric_size += 64;
560       font->metric = pool_realloc (font->owner, font->metric,
561                                    sizeof *font->metric * font->metric_size);
562     }
563   font->metric[font->metric_used] = metrics;
564   font->deref[code] = font->metric_used++;
565 }
566
567 /* Copies metric in FONT from character with code SRC to character
568    with code DEST. */
569 static void
570 dup_char_metric (struct font_desc *font, int dest, int src)
571 {
572   check_deref_space (font, dest);
573   assert (font->deref[src] != -1);
574   font->deref[dest] = font->deref[src];
575 }
576 \f
577 /* Kerning. */
578
579 /* Returns a hash value for characters with codes CH1 and CH2. */
580 #define hash_kern(CH1, CH2)                     \
581         ((unsigned) (((CH1) << 16) ^ (CH2)))
582
583 /* Adds an ADJUST-size kern to FONT between characters with codes CH1
584    and CH2. */
585 static void
586 add_kern (struct font_desc *font, int ch1, int ch2, int adjust)
587 {
588   int i;
589
590   if (font->kern_used >= font->kern_max_used)
591     {
592       struct kern_pair *old_kern = font->kern;
593       int old_kern_size = font->kern_size;
594       int j;
595
596       font->kern_size *= 2;
597       font->kern_max_used = font->kern_size / 2;
598       font->kern = pool_malloc (font->owner,
599                                 sizeof *font->kern * font->kern_size);
600       for (i = 0; i < font->kern_size; i++)
601         font->kern[i].ch1 = -1;
602
603       for (i = 0; i < old_kern_size; i++)
604         {
605           if (old_kern[i].ch1 == -1)
606             continue;
607
608           j = hash_kern (old_kern[i].ch1, old_kern[i].ch2) % font->kern_size;
609           while (font->kern[j].ch1 != -1)
610             if (0 == j--)
611               j = font->kern_size - 1;
612           font->kern[j] = old_kern[i];
613         }
614       if (old_kern)
615         pool_free (font->owner, old_kern);
616     }
617
618   for (i = hash_kern (ch1, ch2) % font->kern_size; font->kern[i].ch1 != -1;)
619     if (0 == i--)
620       i = font->kern_size - 1;
621   font->kern[i].ch1 = ch1;
622   font->kern[i].ch2 = ch2;
623   font->kern[i].adjust = adjust;
624   font->kern_used++;
625 }
626
627 /* Finds a font file corresponding to font NAME for device DEV. */
628 static char *
629 find_font_file (const char *dev, const char *name)
630 {
631   char *basename = xmalloc (3 + strlen (dev) + 1 + strlen (name) + 1);
632   char *cp;
633   char *filename;
634   char *path;
635
636   cp = stpcpy (basename, "dev");
637   cp = stpcpy (cp, dev);
638   *cp++ = DIR_SEPARATOR;
639   strcpy (cp, name);
640
641   /* Search order:
642      1. $STAT_GROFF_FONT_PATH
643      2. $GROFF_FONT_PATH
644      3. GROFF_FONT_PATH from pref.h
645      4. config_path
646    */
647   if ((path = getenv ("STAT_GROFF_FONT_PATH")) != NULL
648       && (filename = fn_search_path (basename, path, NULL)) != NULL)
649     goto win;
650
651   if ((path = getenv ("GROFF_FONT_PATH")) != NULL
652       && (filename = fn_search_path (basename, path, NULL)) != NULL)
653     goto win;
654
655   if ((filename = fn_search_path (basename, groff_font_path, NULL)) != NULL)
656     goto win;
657
658   if ((filename = fn_search_path (basename, config_path, NULL)) != NULL)
659     goto win;
660
661   msg (IE, _("Groff font error: Cannot find \"%s\"."), basename);
662
663 win:
664   free (basename);
665   return filename;
666 }
667
668 /* Finds a font for device DEV with name NAME, reads it with
669    groff_read_font(), and returns the resultant font. */
670 struct font_desc *
671 groff_find_font (const char *dev, const char *name)
672 {
673   char *filename = find_font_file (dev, name);
674   struct font_desc *fd;
675
676   if (!filename)
677     return NULL;
678   fd = groff_read_font (filename);
679   free (filename);
680   return fd;
681 }
682
683 /* Reads a DESC file for device DEV and sets the appropriate fields in
684    output driver *DRIVER, which must be previously allocated.  Returns
685    nonzero on success. */
686 int
687 groff_read_DESC (const char *dev_name, struct groff_device_info * dev)
688 {
689   char *filename;               /* Full name of DESC file. */
690   FILE *f;                      /* DESC file. */
691
692   char *line = NULL;            /* Current line. */
693   int line_len;                 /* Number of chars in current line. */
694   size_t line_size = 0;         /* Number of chars allocated for line. */
695
696   char *token;                  /* strtok()'d token inside line. */
697
698   unsigned found = 0;           /* Bitmask showing what settings
699                                    have been encountered. */
700
701   int m_sizes = 0;              /* Number of int[2] items that
702                                    can fit in driver->sizes. */
703
704   char *sp;                     /* Tokenization string pointer. */
705   struct file_locator where;
706
707   int i;
708
709   dev->horiz = 1;
710   dev->vert = 1;
711   dev->size_scale = 1;
712   dev->n_sizes = 0;
713   dev->sizes = NULL;
714   dev->family = NULL;
715   for (i = 0; i < 4; i++)
716     dev->font_name[i] = NULL;
717
718   filename = find_font_file (dev_name, "DESC");
719   if (!filename)
720     return 0;
721
722   where.filename = filename;
723   where.line_number = 0;
724   err_push_file_locator (&where);
725
726   msg (VM (1), _("%s: Opening Groff description file..."), filename);
727   f = fopen (filename, "r");
728   if (!f)
729     goto file_lossage;
730
731   while ((line_len = getline (&line, &line_size, f)) != -1)
732     {
733       where.line_number++;
734
735       token = strtok_r (line, whitespace, &sp);
736       if (!token)
737         continue;
738
739       if (!strcmp (token, "sizes"))
740         {
741           if (found & 0x10000)
742             font_msg (SW, _("Multiple `sizes' declarations."));
743           for (;;)
744             {
745               char *tail;
746               int lower, upper;
747
748               for (;;)
749                 {
750                   token = strtok_r (NULL, whitespace, &sp);
751                   if (token)
752                     break;
753
754                   where.line_number++;
755                   if ((line_len = getline (&line, &line_size, f)) != -1)
756                     {
757                       if (ferror (f))
758                         goto file_lossage;
759                       font_msg (SE, _("Unexpected end of file.  "
760                                 "Missing 0 terminator to `sizes' command?"));
761                       goto lossage;
762                     }
763                 }
764
765               if (!strcmp (token, "0"))
766                 break;
767
768               errno = 0;
769               if (0 == (lower = strtol (token, &tail, 0)) || errno == ERANGE)
770                 {
771                   font_msg (SE, _("Bad argument to `sizes'."));
772                   goto lossage;
773                 }
774               if (*tail == '-')
775                 {
776                   if (0 == (upper = strtol (&tail[1], &tail, 0)) || errno == ERANGE)
777                     {
778                       font_msg (SE, _("Bad argument to `sizes'."));
779                       goto lossage;
780                     }
781                   if (lower < upper)
782                     {
783                       font_msg (SE, _("Bad range in argument to `sizes'."));
784                       goto lossage;
785                     }
786                 }
787               else
788                 upper = lower;
789               if (*tail)
790                 {
791                   font_msg (SE, _("Bad argument to `sizes'."));
792                   goto lossage;
793                 }
794
795               if (dev->n_sizes + 2 >= m_sizes)
796                 {
797                   m_sizes += 1;
798                   dev->sizes = xrealloc (dev->sizes,
799                                          m_sizes * sizeof *dev->sizes);
800                 }
801               dev->sizes[dev->n_sizes++][0] = lower;
802               dev->sizes[dev->n_sizes][1] = upper;
803
804               found |= 0x10000;
805             }
806         }
807       else if (!strcmp (token, "family"))
808         {
809           token = strtok_r (NULL, whitespace, &sp);
810           if (!token)
811             {
812               font_msg (SE, _("Family name expected."));
813               goto lossage;
814             }
815           if (found & 0x20000)
816             {
817               font_msg (SE, _("This command already specified."));
818               goto lossage;
819             }
820           dev->family = xstrdup (token);
821         }
822       else if (!strcmp (token, "charset"))
823         break;
824       else
825         {
826           static const char *id[]
827             = {"res", "hor", "vert", "sizescale", "unitwidth", NULL};
828           const char **cp;
829           int value;
830
831           for (cp = id; *cp; cp++)
832             if (!strcmp (token, *cp))
833               break;
834           if (*cp == NULL)
835             continue;           /* completely ignore unrecognized lines */
836           if (found & (1 << (cp - id)))
837             font_msg (SW, _("%s: Device characteristic already defined."), *cp);
838
839           token = strtok_r (NULL, whitespace, &sp);
840           errno = 0;
841           if (!token || (value = strtol (token, NULL, 0)) <= 0 || errno == ERANGE)
842             {
843               font_msg (SE, _("%s: Invalid numeric format."), *cp);
844               goto lossage;
845             }
846           found |= (1 << (cp - id));
847           switch (cp - id)
848             {
849             case 0:
850               dev->res = value;
851               break;
852             case 1:
853               dev->horiz = value;
854               break;
855             case 2:
856               dev->vert = value;
857               break;
858             case 3:
859               dev->size_scale = value;
860               break;
861             case 4:
862               dev->unit_width = value;
863               break;
864             default:
865               assert (0);
866             }
867         }
868     }
869   if (ferror (f))
870     goto file_lossage;
871   if ((found & 0x10011) != 0x10011)
872     {
873       font_msg (SE, _("Missing `res', `unitwidth', and/or `sizes' line(s)."));
874       goto lossage;
875     }
876
877   /* Font name = family name + suffix. */
878   {
879     static const char *suffix[4] =
880       {"R", "I", "B", "BI"};    /* match OUTP_F_* */
881     int len;                    /* length of family name */
882     int i;
883
884     if (!dev->family)
885       dev->family = xstrdup ("");
886     len = strlen (dev->family);
887     for (i = 0; i < 4; i++)
888       {
889         char *cp;
890         dev->font_name[i] = xmalloc (len + strlen (suffix[i]) + 1);
891         cp = stpcpy (dev->font_name[i], dev->family);
892         strcpy (cp, suffix[i]);
893       }
894   }
895
896   dev->sizes[dev->n_sizes][0] = 0;
897   dev->sizes[dev->n_sizes][1] = 0;
898
899   msg (VM (2), _("Description file read successfully."));
900   
901   err_pop_file_locator (&where);
902   free (filename);
903   free (line);
904   return 1;
905
906   /* Come here on a file error. */
907 file_lossage:
908   msg (ME, "%s: %s", filename, strerror (errno));
909
910   /* Come here on any error. */
911 lossage:
912   fclose (f);
913   free (line);
914   free (dev->family);
915   dev->family = NULL;
916   free (filename);
917   free (dev->sizes);
918   dev->sizes = NULL;
919   dev->n_sizes = 0;
920 #if 0                           /* at the moment, no errors can come here when dev->font_name[*] are
921                                    nonzero. */
922   for (i = 0; i < 4; i++)
923     {
924       free (dev->font_name[i]);
925       dev->font_name[i] = NULL;
926     }
927 #endif
928
929   err_pop_file_locator (&where);
930   
931   msg (VM (1), _("Error reading description file."));
932   
933   return 0;
934 }
935
936 /* Finds character with index CH (as returned by name_to_index() or
937    number_to_index()) in font FONT and returns the associated metrics.
938    Nonexistent characters have width 0. */
939 struct char_metrics *
940 font_get_char_metrics (const struct font_desc *font, int ch)
941 {
942   short index;
943
944   if (ch < 0 || ch >= font->deref_size)
945     return 0;
946
947   index = font->deref[ch];
948   if (index == -1)
949     return 0;
950
951   return font->metric[index];
952 }
953
954 /* Finds kernpair consisting of CH1 and CH2, in that order, in font
955    FONT and returns the associated kerning adjustment. */
956 int
957 font_get_kern_adjust (const struct font_desc *font, int ch1, int ch2)
958 {
959   unsigned i;
960
961   if (!font->kern)
962     return 0;
963   for (i = hash_kern (ch1, ch2) % font->kern_size; font->kern[i].ch1 != -1;)
964     {
965       if (font->kern[i].ch1 == ch1 && font->kern[i].ch2 == ch2)
966         return font->kern[i].adjust;
967       if (0 == i--)
968         i = font->kern_size - 1;
969     }
970   return 0;
971 }
972
973 /* Returns a twelve-point fixed-pitch font that can be used as a
974    last-resort fallback. */
975 struct font_desc *
976 default_font (void)
977 {
978   struct pool *font_pool;
979   static struct font_desc *font;
980
981   if (font)
982     return font;
983   font_pool = pool_create ();
984   font = pool_alloc (font_pool, sizeof *font);
985   font->owner = font_pool;
986   font->name = NULL;
987   font->internal_name = pool_strdup (font_pool, _("<<fallback>>"));
988   font->encoding = pool_strdup (font_pool, "text.enc");
989   font->space_width = 12000;
990   font->slant = 0.0;
991   font->ligatures = 0;
992   font->special = 0;
993   font->ascent = 8000;
994   font->descent = 4000;
995   font->deref = NULL;
996   font->deref_size = 0;
997   font->metric = NULL;
998   font->metric_size = 0;
999   font->metric_used = 0;
1000   font->kern = NULL;
1001   font->kern_size = 8;
1002   font->kern_used = 0;
1003   font->kern_max_used = 0;
1004   return font;
1005 }