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