checkin of 0.3.0
[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 = 0;
134   font->kern_size_p = hsh_next_prime (64);
135   font->kern_used = 0;
136   font->kern_max_used = 0;
137
138   /* Parses first section of font file. */
139   for (;;)
140     {
141       /* Location of '#' in line. */
142       char *p;
143
144       len = getline (&line, &size, f);
145       if (len == -1)
146         break;
147       
148       scan_badchars (line, len);
149       p = strchr (line, '#');
150       if (p)
151         *p = '\0';              /* Reject comments. */
152
153       key = strtok_r (line, whitespace, &sp);
154       if (!key)
155         goto next_iteration;
156
157       if (!strcmp (key, "internalname"))
158         {
159           font->internal_name = strtok_r (NULL, whitespace, &sp);
160           if (font->internal_name == NULL)
161             {
162               font_msg (SE, _("Missing font name."));
163               goto lose;
164             }
165           font->internal_name = pool_strdup (font_pool, font->internal_name);
166         }
167       else if (!strcmp (key, "encoding"))
168         {
169           font->encoding = strtok_r (NULL, whitespace, &sp);
170           if (font->encoding == NULL)
171             {
172               font_msg (SE, _("Missing encoding filename."));
173               goto lose;
174             }
175           font->encoding = pool_strdup (font_pool, font->encoding);
176         }
177       else if (!strcmp (key, "spacewidth"))
178         {
179           char *n = strtok_r (NULL, whitespace, &sp);
180           char *tail;
181           if (n)
182             font->space_width = strtol (n, &tail, 10);
183           if (n == NULL || tail == n)
184             {
185               font_msg (SE, _("Bad spacewidth value."));
186               goto lose;
187             }
188         }
189       else if (!strcmp (key, "slant"))
190         {
191           char *n = strtok_r (NULL, whitespace, &sp);
192           char *tail;
193           if (n)
194             font->slant = strtod (n, &tail);
195           if (n == NULL || tail == n)
196             {
197               font_msg (SE, _("Bad slant value."));
198               goto lose;
199             }
200         }
201       else if (!strcmp (key, "ligatures"))
202         {
203           char *lig;
204
205           for (;;)
206             {
207               lig = strtok_r (NULL, whitespace, &sp);
208               if (!lig || !strcmp (lig, "0"))
209                 break;
210               else if (!strcmp (lig, "ff"))
211                 font->ligatures |= LIG_ff;
212               else if (!strcmp (lig, "ffi"))
213                 font->ligatures |= LIG_ffi;
214               else if (!strcmp (lig, "ffl"))
215                 font->ligatures |= LIG_ffl;
216               else if (!strcmp (lig, "fi"))
217                 font->ligatures |= LIG_fi;
218               else if (!strcmp (lig, "fl"))
219                 font->ligatures |= LIG_fl;
220               else
221                 {
222                   font_msg (SE, _("Unknown ligature `%s'."), lig);
223                   goto lose;
224                 }
225             }
226         }
227       else if (!strcmp (key, "special"))
228         font->special = 1;
229       else if (!strcmp (key, "charset") || !strcmp (key, "kernpairs"))
230         break;
231
232       where.line_number++;
233     }
234   if (ferror (f))
235     goto file_lossage;
236
237   /* Parses second section of font file (metrics & kerning data). */
238   do
239     {
240       key = strtok_r (line, whitespace, &sp);
241       if (!key)
242         goto next_iteration;
243
244       if (!strcmp (key, "charset"))
245         charset = 1;
246       else if (!strcmp (key, "kernpairs"))
247         charset = 0;
248       else if (charset)
249         {
250           struct char_metrics *metrics = pool_alloc (font_pool,
251                                                      sizeof *metrics);
252           char *m, *type, *code, *tail;
253
254           m = strtok_r (NULL, whitespace, &sp);
255           if (!m)
256             {
257               font_msg (SE, _("Unexpected end of line reading character "
258                               "set."));
259               goto lose;
260             }
261           if (!strcmp (m, "\""))
262             {
263               if (!prev_index)
264                 {
265                   font_msg (SE, _("Can't use ditto mark for first character."));
266                   goto lose;
267                 }
268               if (!strcmp (key, "---"))
269                 {
270                   font_msg (SE, _("Can't ditto into an unnamed character."));
271                   goto lose;
272                 }
273               dup_char_metric (font, font_char_name_to_index (key), prev_index);
274               where.line_number++;
275               goto next_iteration;
276             }
277
278           if (m)
279             {
280               metrics->code = metrics->width
281                 = metrics->height = metrics->depth = 0;
282             }
283           
284           if (m == NULL || 1 > sscanf (m, "%d,%d,%d", &metrics->width,
285                                        &metrics->height, &metrics->depth))
286             {
287               font_msg (SE, _("Missing metrics for character `%s'."), key);
288               goto lose;
289             }
290
291           type = strtok_r (NULL, whitespace, &sp);
292           if (type)
293             metrics->type = strtol (type, &tail, 10);
294           if (!type || tail == type)
295             {
296               font_msg (SE, _("Missing type for character `%s'."), key);
297               goto lose;
298             }
299
300           code = strtok_r (NULL, whitespace, &sp);
301           if (code)
302             metrics->code = strtol (code, &tail, 0);
303           if (tail == code)
304             {
305               font_msg (SE, _("Missing code for character `%s'."), key);
306               goto lose;
307             }
308
309           if (strcmp (key, "---"))
310             prev_index = font_char_name_to_index (key);
311           else
312             prev_index = font_number_to_index (metrics->code);
313           add_char_metric (font, metrics, prev_index);
314         }
315       else
316         {
317           char *c1 = key;
318           char *c2 = strtok_r (NULL, whitespace, &sp);
319           char *n, *tail;
320           int adjust;
321
322           if (c2 == NULL)
323             {
324               font_msg (SE, _("Malformed kernpair."));
325               goto lose;
326             }
327
328           n = strtok_r (NULL, whitespace, &sp);
329           if (!n)
330             {
331               font_msg (SE, _("Unexpected end of line reading kernpairs."));
332               goto lose;
333             }
334           adjust = strtol (n, &tail, 10);
335           if (tail == n || *tail)
336             {
337               font_msg (SE, _("Bad kern value."));
338               goto lose;
339             }
340           add_kern (font, font_char_name_to_index (c1),
341                     font_char_name_to_index (c2), adjust);
342         }
343
344     next_iteration:
345       where.line_number++;
346
347       len = getline (&line, &size, f);
348     }
349   while (len != -1);
350   
351   if (ferror (f))
352     goto file_lossage;
353   if (fclose (f) == EOF)
354     {
355       f = NULL;
356       goto file_lossage;
357     }
358   free (line);
359 #if unix
360   free ((char *) fn);
361 #endif
362
363   /* Get font ascent and descent. */
364   metrics = font_get_char_metrics (font, font_char_name_to_index ("d"));
365   font->ascent = metrics ? metrics->height : 0;
366   metrics = font_get_char_metrics (font, font_char_name_to_index ("p"));
367   font->descent = metrics ? metrics->depth : 0;
368
369   msg (VM (2), _("Font read successfully with internal name %s."),
370        font->internal_name == NULL ? "<none>" : font->internal_name);
371   
372   err_pop_file_locator (&where);
373
374   return font;
375
376   /* Come here on a file error. */
377 file_lossage:
378   msg (ME, "%s: %s", fn, strerror (errno));
379
380   /* Come here on any error. */
381 lose:
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. */
443     int *size_p;                /* Next larger table size. */
444     int used;                   /* Number of full entries. */
445     int max_used;               /* # used entries where we enlarge & rehash. */
446     int next_index;             /* Next index to allocate. */
447     struct index_hash *tab;     /* Hash table proper. */
448     struct pool *ar;            /* Pool for names. */
449   }
450 hash;
451
452 /* Searches for NAME in the global character code table, returns the
453    index if found; otherwise inserts NAME and returns the new
454    index. */
455 int
456 font_char_name_to_index (const char *name)
457 {
458   int i;
459
460   if (name[0] == ' ')
461     return space_index;
462   if (name[0] == '\0' || name[1] == '\0')
463     return name[0];
464   if (0 == strncmp (name, "char", 4))
465     {
466       char *tail;
467       int x = strtol (name + 4, &tail, 10);
468       if (tail != name + 4 && *tail == 0 && x >= 0 && x <= 255)
469         return x;
470     }
471
472   if (!hash.tab)
473     {
474       hash.size_p = hsh_next_prime (128);
475       hash.size = *hash.size_p++;
476       hash.used = 0;
477       hash.max_used = hash.size / 2;
478       hash.next_index = 256;
479       hash.tab = xmalloc (sizeof *hash.tab * hash.size);
480       hash.ar = pool_create ();
481       for (i = 0; i < hash.size; i++)
482         hash.tab[i].name = NULL;
483     }
484
485   for (i = hashpjw (name) % hash.size; hash.tab[i].name;)
486     {
487       if (!strcmp (hash.tab[i].name, name))
488         return hash.tab[i].index;
489       if (++i >= hash.size)
490         i = 0;
491     }
492
493   hash.used++;
494   if (hash.used >= hash.max_used)
495     {
496       struct index_hash *old_tab = hash.tab;
497       int old_size = hash.size;
498       int i, j;
499
500       hash.size = *hash.size_p++;
501       hash.max_used = hash.size / 2;
502       hash.tab = xmalloc (sizeof *hash.tab * hash.size);
503       for (i = 0; i < hash.size; i++)
504         hash.tab[i].name = NULL;
505       for (i = 0; i < old_size; i++)
506         if (old_tab[i].name)
507           {
508             for (j = hashpjw (old_tab[i].name) % hash.size; hash.tab[j].name;)
509               if (++j >= hash.size)
510                 j = 0;
511             hash.tab[j] = old_tab[i];
512           }
513       free (old_tab);
514     }
515
516   hash.tab[i].name = pool_strdup (hash.ar, name);
517   hash.tab[i].index = hash.next_index;
518   return hash.next_index++;
519 }
520
521 /* Returns an index for a character that has only a code, not a
522    name. */
523 int
524 font_number_to_index (int x)
525 {
526   char name[INT_DIGITS + 2];
527
528   /* Note that space is the only character that can't appear in a
529      character name.  That makes it an excellent choice for a name
530      that won't conflict. */
531   sprintf (name, " %d", x);
532   return font_char_name_to_index (name);
533 }
534 \f
535 /* Font character metric entries. */
536
537 /* Ensures room for at least MIN_SIZE metric indexes in deref of
538    FONT. */
539 static void
540 check_deref_space (struct font_desc *font, int min_size)
541 {
542   if (min_size >= font->deref_size)
543     {
544       int i = font->deref_size;
545
546       font->deref_size = min_size + 16;
547       if (font->deref_size < 256)
548         font->deref_size = 256;
549       font->deref = pool_realloc (font->owner, font->deref,
550                                   sizeof *font->deref * font->deref_size);
551       for (; i < font->deref_size; i++)
552         font->deref[i] = -1;
553     }
554 }
555
556 /* Inserts METRICS for character with code CODE into FONT. */
557 static void
558 add_char_metric (struct font_desc *font, struct char_metrics *metrics, int code)
559 {
560   check_deref_space (font, code);
561   if (font->metric_used >= font->metric_size)
562     {
563       font->metric_size += 64;
564       font->metric = pool_realloc (font->owner, font->metric,
565                                    sizeof *font->metric * font->metric_size);
566     }
567   font->metric[font->metric_used] = metrics;
568   font->deref[code] = font->metric_used++;
569 }
570
571 /* Copies metric in FONT from character with code SRC to character
572    with code DEST. */
573 static void
574 dup_char_metric (struct font_desc *font, int dest, int src)
575 {
576   check_deref_space (font, dest);
577   assert (font->deref[src] != -1);
578   font->deref[dest] = font->deref[src];
579 }
580 \f
581 /* Kerning. */
582
583 /* Returns a hash value for characters with codes CH1 and CH2. */
584 #define hash_kern(CH1, CH2)                     \
585         ((unsigned) (((CH1) << 16) ^ (CH2)))
586
587 /* Adds an ADJUST-size kern to FONT between characters with codes CH1
588    and CH2. */
589 static void
590 add_kern (struct font_desc *font, int ch1, int ch2, int adjust)
591 {
592   int i;
593
594   if (font->kern_used >= font->kern_max_used)
595     {
596       struct kern_pair *old_kern = font->kern;
597       int old_kern_size = font->kern_size;
598       int j;
599
600       font->kern_size = *font->kern_size_p++;
601       font->kern_max_used = font->kern_size / 2;
602       font->kern = pool_malloc (font->owner,
603                                 sizeof *font->kern * font->kern_size);
604       for (i = 0; i < font->kern_size; i++)
605         font->kern[i].ch1 = -1;
606
607       for (i = 0; i < old_kern_size; i++)
608         {
609           if (old_kern[i].ch1 == -1)
610             continue;
611
612           j = hash_kern (old_kern[i].ch1, old_kern[i].ch2) % font->kern_size;
613           while (font->kern[j].ch1 != -1)
614             if (0 == j--)
615               j = font->kern_size - 1;
616           font->kern[j] = old_kern[i];
617         }
618       if (old_kern)
619         pool_free (font->owner, old_kern);
620     }
621
622   for (i = hash_kern (ch1, ch2) % font->kern_size; 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; font->kern[i].ch1 != -1;)
968     {
969       if (font->kern[i].ch1 == ch1 && font->kern[i].ch2 == ch2)
970         return font->kern[i].adjust;
971       if (0 == i--)
972         i = font->kern_size - 1;
973     }
974   return 0;
975 }
976
977 /* Returns a twelve-point fixed-pitch font that can be used as a
978    last-resort fallback. */
979 struct font_desc *
980 default_font (void)
981 {
982   struct pool *font_pool;
983   static struct font_desc *font;
984
985   if (font)
986     return font;
987   font_pool = pool_create ();
988   font = pool_alloc (font_pool, sizeof *font);
989   font->owner = font_pool;
990   font->name = NULL;
991   font->internal_name = pool_strdup (font_pool, _("<<fallback>>"));
992   font->encoding = pool_strdup (font_pool, "text.enc");
993   font->space_width = 12000;
994   font->slant = 0.0;
995   font->ligatures = 0;
996   font->special = 0;
997   font->ascent = 8000;
998   font->descent = 4000;
999   font->deref = NULL;
1000   font->deref_size = 0;
1001   font->metric = NULL;
1002   font->metric_size = 0;
1003   font->metric_used = 0;
1004   font->kern = NULL;
1005   font->kern_size = 0;
1006   font->kern_size_p = hsh_next_prime (64);
1007   font->kern_used = 0;
1008   font->kern_max_used = 0;
1009   return font;
1010 }