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