Fix assertion for proper Huffman merge pattern: 0 == 1 modulo 1.
[pspp] / 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 "font.h"
22 #include "error.h"
23 #include <stdio.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <limits.h>
27 #include <stdarg.h>
28 #include "alloc.h"
29 #include "error.h"
30 #include "filename.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      PRINTF_FORMAT (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 = 0;
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 #ifdef 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 #ifdef 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 #ifdef 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       if (old_kern)
604         {
605           for (i = 0; i < old_kern_size; i++)
606             {
607               if (old_kern[i].ch1 == -1)
608                 continue;
609
610               j = (hash_kern (old_kern[i].ch1, old_kern[i].ch2)
611                    & (font->kern_size - 1));
612               while (font->kern[j].ch1 != -1)
613                 if (0 == j--)
614                   j = font->kern_size - 1;
615               font->kern[j] = old_kern[i];
616             }
617           pool_free (font->owner, old_kern);
618         }
619     }
620
621   for (i = hash_kern (ch1, ch2) & (font->kern_size - 1);
622        font->kern[i].ch1 != -1; )
623     if (0 == i--)
624       i = font->kern_size - 1;
625   font->kern[i].ch1 = ch1;
626   font->kern[i].ch2 = ch2;
627   font->kern[i].adjust = adjust;
628   font->kern_used++;
629 }
630
631 /* Finds a font file corresponding to font NAME for device DEV. */
632 static char *
633 find_font_file (const char *dev, const char *name)
634 {
635   char *basename = xmalloc (3 + strlen (dev) + 1 + strlen (name) + 1);
636   char *cp;
637   char *filename;
638   char *path;
639
640   cp = stpcpy (basename, "dev");
641   cp = stpcpy (cp, dev);
642   *cp++ = DIR_SEPARATOR;
643   strcpy (cp, name);
644
645   /* Search order:
646      1. $STAT_GROFF_FONT_PATH
647      2. $GROFF_FONT_PATH
648      3. GROFF_FONT_PATH from pref.h
649      4. config_path
650    */
651   if ((path = getenv ("STAT_GROFF_FONT_PATH")) != NULL
652       && (filename = fn_search_path (basename, path, NULL)) != NULL)
653     goto win;
654
655   if ((path = getenv ("GROFF_FONT_PATH")) != NULL
656       && (filename = fn_search_path (basename, path, NULL)) != NULL)
657     goto win;
658
659   if ((filename = fn_search_path (basename, groff_font_path, NULL)) != NULL)
660     goto win;
661
662   if ((filename = fn_search_path (basename, config_path, NULL)) != NULL)
663     goto win;
664
665   msg (IE, _("Groff font error: Cannot find \"%s\"."), basename);
666
667 win:
668   free (basename);
669   return filename;
670 }
671
672 /* Finds a font for device DEV with name NAME, reads it with
673    groff_read_font(), and returns the resultant font. */
674 struct font_desc *
675 groff_find_font (const char *dev, const char *name)
676 {
677   char *filename = find_font_file (dev, name);
678   struct font_desc *fd;
679
680   if (!filename)
681     return NULL;
682   fd = groff_read_font (filename);
683   free (filename);
684   return fd;
685 }
686
687 /* Reads a DESC file for device DEV and sets the appropriate fields in
688    output driver *DRIVER, which must be previously allocated.  Returns
689    nonzero on success. */
690 int
691 groff_read_DESC (const char *dev_name, struct groff_device_info * dev)
692 {
693   char *filename;               /* Full name of DESC file. */
694   FILE *f;                      /* DESC file. */
695
696   char *line = NULL;            /* Current line. */
697   int line_len;                 /* Number of chars in current line. */
698   size_t line_size = 0;         /* Number of chars allocated for line. */
699
700   char *token;                  /* strtok()'d token inside line. */
701
702   unsigned found = 0;           /* Bitmask showing what settings
703                                    have been encountered. */
704
705   int m_sizes = 0;              /* Number of int[2] items that
706                                    can fit in driver->sizes. */
707
708   char *sp;                     /* Tokenization string pointer. */
709   struct file_locator where;
710
711   int i;
712
713   dev->horiz = 1;
714   dev->vert = 1;
715   dev->size_scale = 1;
716   dev->n_sizes = 0;
717   dev->sizes = NULL;
718   dev->family = NULL;
719   for (i = 0; i < 4; i++)
720     dev->font_name[i] = NULL;
721
722   filename = find_font_file (dev_name, "DESC");
723   if (!filename)
724     return 0;
725
726   where.filename = filename;
727   where.line_number = 0;
728   err_push_file_locator (&where);
729
730   msg (VM (1), _("%s: Opening Groff description file..."), filename);
731   f = fopen (filename, "r");
732   if (!f)
733     goto file_lossage;
734
735   while ((line_len = getline (&line, &line_size, f)) != -1)
736     {
737       where.line_number++;
738
739       token = strtok_r (line, whitespace, &sp);
740       if (!token)
741         continue;
742
743       if (!strcmp (token, "sizes"))
744         {
745           if (found & 0x10000)
746             font_msg (SW, _("Multiple `sizes' declarations."));
747           for (;;)
748             {
749               char *tail;
750               int lower, upper;
751
752               for (;;)
753                 {
754                   token = strtok_r (NULL, whitespace, &sp);
755                   if (token)
756                     break;
757
758                   where.line_number++;
759                   if ((line_len = getline (&line, &line_size, f)) != -1)
760                     {
761                       if (ferror (f))
762                         goto file_lossage;
763                       font_msg (SE, _("Unexpected end of file.  "
764                                 "Missing 0 terminator to `sizes' command?"));
765                       goto lossage;
766                     }
767                 }
768
769               if (!strcmp (token, "0"))
770                 break;
771
772               errno = 0;
773               if (0 == (lower = strtol (token, &tail, 0)) || errno == ERANGE)
774                 {
775                   font_msg (SE, _("Bad argument to `sizes'."));
776                   goto lossage;
777                 }
778               if (*tail == '-')
779                 {
780                   if (0 == (upper = strtol (&tail[1], &tail, 0)) || errno == ERANGE)
781                     {
782                       font_msg (SE, _("Bad argument to `sizes'."));
783                       goto lossage;
784                     }
785                   if (lower < upper)
786                     {
787                       font_msg (SE, _("Bad range in argument to `sizes'."));
788                       goto lossage;
789                     }
790                 }
791               else
792                 upper = lower;
793               if (*tail)
794                 {
795                   font_msg (SE, _("Bad argument to `sizes'."));
796                   goto lossage;
797                 }
798
799               if (dev->n_sizes + 2 >= m_sizes)
800                 {
801                   m_sizes += 1;
802                   dev->sizes = xrealloc (dev->sizes,
803                                          m_sizes * sizeof *dev->sizes);
804                 }
805               dev->sizes[dev->n_sizes++][0] = lower;
806               dev->sizes[dev->n_sizes][1] = upper;
807
808               found |= 0x10000;
809             }
810         }
811       else if (!strcmp (token, "family"))
812         {
813           token = strtok_r (NULL, whitespace, &sp);
814           if (!token)
815             {
816               font_msg (SE, _("Family name expected."));
817               goto lossage;
818             }
819           if (found & 0x20000)
820             {
821               font_msg (SE, _("This command already specified."));
822               goto lossage;
823             }
824           dev->family = xstrdup (token);
825         }
826       else if (!strcmp (token, "charset"))
827         break;
828       else
829         {
830           static const char *id[]
831             = {"res", "hor", "vert", "sizescale", "unitwidth", NULL};
832           const char **cp;
833           int value;
834
835           for (cp = id; *cp; cp++)
836             if (!strcmp (token, *cp))
837               break;
838           if (*cp == NULL)
839             continue;           /* completely ignore unrecognized lines */
840           if (found & (1 << (cp - id)))
841             font_msg (SW, _("%s: Device characteristic already defined."), *cp);
842
843           token = strtok_r (NULL, whitespace, &sp);
844           errno = 0;
845           if (!token || (value = strtol (token, NULL, 0)) <= 0 || errno == ERANGE)
846             {
847               font_msg (SE, _("%s: Invalid numeric format."), *cp);
848               goto lossage;
849             }
850           found |= (1 << (cp - id));
851           switch (cp - id)
852             {
853             case 0:
854               dev->res = value;
855               break;
856             case 1:
857               dev->horiz = value;
858               break;
859             case 2:
860               dev->vert = value;
861               break;
862             case 3:
863               dev->size_scale = value;
864               break;
865             case 4:
866               dev->unit_width = value;
867               break;
868             default:
869               assert (0);
870             }
871         }
872     }
873   if (ferror (f))
874     goto file_lossage;
875   if ((found & 0x10011) != 0x10011)
876     {
877       font_msg (SE, _("Missing `res', `unitwidth', and/or `sizes' line(s)."));
878       goto lossage;
879     }
880
881   /* Font name = family name + suffix. */
882   {
883     static const char *suffix[4] =
884       {"R", "I", "B", "BI"};    /* match OUTP_F_* */
885     int len;                    /* length of family name */
886     int i;
887
888     if (!dev->family)
889       dev->family = xstrdup ("");
890     len = strlen (dev->family);
891     for (i = 0; i < 4; i++)
892       {
893         char *cp;
894         dev->font_name[i] = xmalloc (len + strlen (suffix[i]) + 1);
895         cp = stpcpy (dev->font_name[i], dev->family);
896         strcpy (cp, suffix[i]);
897       }
898   }
899
900   dev->sizes[dev->n_sizes][0] = 0;
901   dev->sizes[dev->n_sizes][1] = 0;
902
903   msg (VM (2), _("Description file read successfully."));
904   
905   err_pop_file_locator (&where);
906   free (filename);
907   free (line);
908   return 1;
909
910   /* Come here on a file error. */
911 file_lossage:
912   msg (ME, "%s: %s", filename, strerror (errno));
913
914   /* Come here on any error. */
915 lossage:
916   fclose (f);
917   free (line);
918   free (dev->family);
919   dev->family = NULL;
920   free (filename);
921   free (dev->sizes);
922   dev->sizes = NULL;
923   dev->n_sizes = 0;
924 #if 0                           /* at the moment, no errors can come here when dev->font_name[*] are
925                                    nonzero. */
926   for (i = 0; i < 4; i++)
927     {
928       free (dev->font_name[i]);
929       dev->font_name[i] = NULL;
930     }
931 #endif
932
933   err_pop_file_locator (&where);
934   
935   msg (VM (1), _("Error reading description file."));
936   
937   return 0;
938 }
939
940 /* Finds character with index CH (as returned by name_to_index() or
941    number_to_index()) in font FONT and returns the associated metrics.
942    Nonexistent characters have width 0. */
943 struct char_metrics *
944 font_get_char_metrics (const struct font_desc *font, int ch)
945 {
946   short index;
947
948   if (ch < 0 || ch >= font->deref_size)
949     return 0;
950
951   index = font->deref[ch];
952   if (index == -1)
953     return 0;
954
955   return font->metric[index];
956 }
957
958 /* Finds kernpair consisting of CH1 and CH2, in that order, in font
959    FONT and returns the associated kerning adjustment. */
960 int
961 font_get_kern_adjust (const struct font_desc *font, int ch1, int ch2)
962 {
963   unsigned i;
964
965   if (!font->kern)
966     return 0;
967   for (i = hash_kern (ch1, ch2) & (font->kern_size - 1);
968        font->kern[i].ch1 != -1;)
969     {
970       if (font->kern[i].ch1 == ch1 && font->kern[i].ch2 == ch2)
971         return font->kern[i].adjust;
972       if (0 == i--)
973         i = font->kern_size - 1;
974     }
975   return 0;
976 }
977
978 /* Returns a twelve-point fixed-pitch font that can be used as a
979    last-resort fallback. */
980 struct font_desc *
981 default_font (void)
982 {
983   struct pool *font_pool;
984   static struct font_desc *font;
985
986   if (font)
987     return font;
988   font_pool = pool_create ();
989   font = pool_alloc (font_pool, sizeof *font);
990   font->owner = font_pool;
991   font->name = NULL;
992   font->internal_name = pool_strdup (font_pool, _("<<fallback>>"));
993   font->encoding = pool_strdup (font_pool, "text.enc");
994   font->space_width = 12000;
995   font->slant = 0.0;
996   font->ligatures = 0;
997   font->special = 0;
998   font->ascent = 8000;
999   font->descent = 4000;
1000   font->deref = NULL;
1001   font->deref_size = 0;
1002   font->metric = NULL;
1003   font->metric_size = 0;
1004   font->metric_used = 0;
1005   font->kern = NULL;
1006   font->kern_size = 8;
1007   font->kern_used = 0;
1008   font->kern_max_used = 0;
1009   return font;
1010 }