DO IF, LOOP cleanup.
[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   struct error error;
396   va_list args;
397
398   error.class = class;
399   err_location (&error.where);
400   error.title = _("installation error: Groff font error: ");
401
402   va_start (args, format);
403   err_vmsg (&error, format, args);
404   va_end (args);
405
406   return 0;
407 }
408
409 /* Scans string LINE of length LEN (not incl. null terminator) for bad
410    characters, converts to spaces; reports warnings on file FN. */
411 static void
412 scan_badchars (char *line, int len)
413 {
414   char *cp = line;
415
416   /* Same bad characters as Groff. */
417   static unsigned char badchars[32] =
418   {
419     0x01, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
420     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
421     0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
422     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
423   };
424
425   for (; len--; cp++) 
426     {
427       int c = (unsigned char) *cp;
428       if (badchars[c >> 3] & (1 << (c & 7)))
429         {
430           font_msg (SE, _("Bad character \\%3o."), *cp);
431           *cp = ' ';
432         } 
433     }
434 }
435 \f
436 /* Character name hashing. */
437
438 /* Associates a character index with a character name. */
439 struct index_hash
440   {
441     char *name;
442     int index;
443   };
444
445 /* Character index hash table. */
446 static struct
447   {
448     int size;                   /* Size of table (must be power of 2). */
449     int used;                   /* Number of full entries. */
450     int next_index;             /* Next index to allocate. */
451     struct index_hash *tab;     /* Hash table proper. */
452     struct pool *ar;            /* Pool for names. */
453   }
454 hash;
455
456 void
457 groff_init (void)
458 {
459   space_index = font_char_name_to_index ("space");
460 }
461
462 void
463 groff_done (void)
464 {
465   free (hash.tab) ;
466   pool_destroy(hash.ar);
467 }
468
469
470 /* Searches for NAME in the global character code table, returns the
471    index if found; otherwise inserts NAME and returns the new
472    index. */
473 int
474 font_char_name_to_index (const char *name)
475 {
476   int i;
477
478   if (name[0] == ' ')
479     return space_index;
480   if (name[0] == '\0' || name[1] == '\0')
481     return name[0];
482   if (0 == strncmp (name, "char", 4))
483     {
484       char *tail;
485       int x = strtol (name + 4, &tail, 10);
486       if (tail != name + 4 && *tail == 0 && x >= 0 && x <= 255)
487         return x;
488     }
489
490   if (!hash.tab)
491     {
492       hash.size = 128;
493       hash.used = 0;
494       hash.next_index = 256;
495       hash.tab = xnmalloc (hash.size, sizeof *hash.tab);
496       hash.ar = pool_create ();
497       for (i = 0; i < hash.size; i++)
498         hash.tab[i].name = NULL;
499     }
500
501   for (i = hsh_hash_string (name) & (hash.size - 1); hash.tab[i].name; )
502     {
503       if (!strcmp (hash.tab[i].name, name))
504         return hash.tab[i].index;
505       if (++i >= hash.size)
506         i = 0;
507     }
508
509   hash.used++;
510   if (hash.used >= hash.size / 2)
511     {
512       struct index_hash *old_tab = hash.tab;
513       int old_size = hash.size;
514       int i, j;
515
516       hash.size *= 2;
517       hash.tab = xnmalloc (hash.size, sizeof *hash.tab);
518       for (i = 0; i < hash.size; i++)
519         hash.tab[i].name = NULL;
520       for (i = 0; i < old_size; i++)
521         if (old_tab[i].name)
522           {
523             for (j = hsh_hash_string (old_tab[i].name) & (hash.size - 1);
524                  hash.tab[j].name;)
525               if (++j >= hash.size)
526                 j = 0;
527             hash.tab[j] = old_tab[i];
528           }
529       free (old_tab);
530     }
531
532   hash.tab[i].name = pool_strdup (hash.ar, name);
533   hash.tab[i].index = hash.next_index;
534   return hash.next_index++;
535 }
536
537 /* Returns an index for a character that has only a code, not a
538    name. */
539 int
540 font_number_to_index (int x)
541 {
542   char name[INT_DIGITS + 2];
543
544   /* Note that space is the only character that can't appear in a
545      character name.  That makes it an excellent choice for a name
546      that won't conflict. */
547   sprintf (name, " %d", x);
548   return font_char_name_to_index (name);
549 }
550 \f
551 /* Font character metric entries. */
552
553 /* Ensures room for at least MIN_SIZE metric indexes in deref of
554    FONT. */
555 static void
556 check_deref_space (struct font_desc *font, int min_size)
557 {
558   if (min_size >= font->deref_size)
559     {
560       int i = font->deref_size;
561
562       font->deref_size = min_size + 16;
563       if (font->deref_size < 256)
564         font->deref_size = 256;
565       font->deref = pool_nrealloc (font->owner, font->deref,
566                                    font->deref_size, sizeof *font->deref);
567       for (; i < font->deref_size; i++)
568         font->deref[i] = -1;
569     }
570 }
571
572 /* Inserts METRICS for character with code CODE into FONT. */
573 static void
574 add_char_metric (struct font_desc *font, struct char_metrics *metrics, int code)
575 {
576   check_deref_space (font, code);
577   if (font->metric_used >= font->metric_size)
578     {
579       font->metric_size += 64;
580       font->metric = pool_nrealloc (font->owner, font->metric,
581                                     font->metric_size, sizeof *font->metric);
582     }
583   font->metric[font->metric_used] = metrics;
584   font->deref[code] = font->metric_used++;
585 }
586
587 /* Copies metric in FONT from character with code SRC to character
588    with code DEST. */
589 static void
590 dup_char_metric (struct font_desc *font, int dest, int src)
591 {
592   check_deref_space (font, dest);
593   assert (font->deref[src] != -1);
594   font->deref[dest] = font->deref[src];
595 }
596 \f
597 /* Kerning. */
598
599 /* Returns a hash value for characters with codes CH1 and CH2. */
600 #define hash_kern(CH1, CH2)                     \
601         ((unsigned) (((CH1) << 16) ^ (CH2)))
602
603 /* Adds an ADJUST-size kern to FONT between characters with codes CH1
604    and CH2. */
605 static void
606 add_kern (struct font_desc *font, int ch1, int ch2, int adjust)
607 {
608   int i;
609
610   if (font->kern_used >= font->kern_max_used)
611     {
612       struct kern_pair *old_kern = font->kern;
613       int old_kern_size = font->kern_size;
614       int j;
615
616       font->kern_size *= 2;
617       font->kern_max_used = font->kern_size / 2;
618       font->kern = pool_nmalloc (font->owner,
619                                  font->kern_size, sizeof *font->kern);
620       for (i = 0; i < font->kern_size; i++)
621         font->kern[i].ch1 = -1;
622
623       if (old_kern)
624         {
625           for (i = 0; i < old_kern_size; i++)
626             {
627               if (old_kern[i].ch1 == -1)
628                 continue;
629
630               j = (hash_kern (old_kern[i].ch1, old_kern[i].ch2)
631                    & (font->kern_size - 1));
632               while (font->kern[j].ch1 != -1)
633                 if (0 == j--)
634                   j = font->kern_size - 1;
635               font->kern[j] = old_kern[i];
636             }
637           pool_free (font->owner, old_kern);
638         }
639     }
640
641   for (i = hash_kern (ch1, ch2) & (font->kern_size - 1);
642        font->kern[i].ch1 != -1; )
643     if (0 == i--)
644       i = font->kern_size - 1;
645   font->kern[i].ch1 = ch1;
646   font->kern[i].ch2 = ch2;
647   font->kern[i].adjust = adjust;
648   font->kern_used++;
649 }
650
651 /* Finds a font file corresponding to font NAME for device DEV. */
652 static char *
653 find_font_file (const char *dev, const char *name)
654 {
655   char *basename = xmalloc (3 + strlen (dev) + 1 + strlen (name) + 1);
656   char *cp;
657   char *filename;
658   char *path;
659
660   cp = stpcpy (basename, "dev");
661   cp = stpcpy (cp, dev);
662   *cp++ = DIR_SEPARATOR;
663   strcpy (cp, name);
664
665   /* Search order:
666      1. $STAT_GROFF_FONT_PATH
667      2. $GROFF_FONT_PATH
668      3. GROFF_FONT_PATH from pref.h
669      4. config_path
670    */
671   if ((path = getenv ("STAT_GROFF_FONT_PATH")) != NULL
672       && (filename = fn_search_path (basename, path, NULL)) != NULL)
673     goto win;
674
675   if ((path = getenv ("GROFF_FONT_PATH")) != NULL
676       && (filename = fn_search_path (basename, path, NULL)) != NULL)
677     goto win;
678
679   if ((filename = fn_search_path (basename, groff_font_path, NULL)) != NULL)
680     goto win;
681
682   if ((filename = fn_search_path (basename, config_path, NULL)) != NULL)
683     goto win;
684
685   msg (IE, _("Groff font error: Cannot find \"%s\"."), basename);
686
687 win:
688   free (basename);
689   return filename;
690 }
691
692 /* Finds a font for device DEV with name NAME, reads it with
693    groff_read_font(), and returns the resultant font. */
694 struct font_desc *
695 groff_find_font (const char *dev, const char *name)
696 {
697   char *filename = find_font_file (dev, name);
698   struct font_desc *fd;
699
700   if (!filename)
701     return NULL;
702   fd = groff_read_font (filename);
703   free (filename);
704   return fd;
705 }
706
707 /* Reads a DESC file for device DEV and sets the appropriate fields in
708    output driver *DRIVER, which must be previously allocated.  Returns
709    nonzero on success. */
710 int
711 groff_read_DESC (const char *dev_name, struct groff_device_info * dev)
712 {
713   char *filename;               /* Full name of DESC file. */
714   FILE *f;                      /* DESC file. */
715
716   char *line = NULL;            /* Current line. */
717   int line_len;                 /* Number of chars in current line. */
718   size_t line_size = 0;         /* Number of chars allocated for line. */
719
720   char *token;                  /* strtok()'d token inside line. */
721
722   unsigned found = 0;           /* Bitmask showing what settings
723                                    have been encountered. */
724
725   int m_sizes = 0;              /* Number of int[2] items that
726                                    can fit in driver->sizes. */
727
728   char *sp;                     /* Tokenization string pointer. */
729   struct file_locator where;
730
731   int i;
732
733   dev->horiz = 1;
734   dev->vert = 1;
735   dev->size_scale = 1;
736   dev->n_sizes = 0;
737   dev->sizes = NULL;
738   dev->family = NULL;
739   for (i = 0; i < 4; i++)
740     dev->font_name[i] = NULL;
741
742   filename = find_font_file (dev_name, "DESC");
743   if (!filename)
744     return 0;
745
746   where.filename = filename;
747   where.line_number = 0;
748   err_push_file_locator (&where);
749
750   msg (VM (1), _("%s: Opening Groff description file..."), filename);
751   f = fopen (filename, "r");
752   if (!f)
753     goto file_lossage;
754
755   while ((line_len = getline (&line, &line_size, f)) != -1)
756     {
757       where.line_number++;
758
759       token = strtok_r (line, whitespace, &sp);
760       if (!token)
761         continue;
762
763       if (!strcmp (token, "sizes"))
764         {
765           if (found & 0x10000)
766             font_msg (SW, _("Multiple `sizes' declarations."));
767           for (;;)
768             {
769               char *tail;
770               int lower, upper;
771
772               for (;;)
773                 {
774                   token = strtok_r (NULL, whitespace, &sp);
775                   if (token)
776                     break;
777
778                   where.line_number++;
779                   if ((line_len = getline (&line, &line_size, f)) != -1)
780                     {
781                       if (ferror (f))
782                         goto file_lossage;
783                       font_msg (SE, _("Unexpected end of file.  "
784                                 "Missing 0 terminator to `sizes' command?"));
785                       goto lossage;
786                     }
787                 }
788
789               if (!strcmp (token, "0"))
790                 break;
791
792               errno = 0;
793               if (0 == (lower = strtol (token, &tail, 0)) || errno == ERANGE)
794                 {
795                   font_msg (SE, _("Bad argument to `sizes'."));
796                   goto lossage;
797                 }
798               if (*tail == '-')
799                 {
800                   if (0 == (upper = strtol (&tail[1], &tail, 0)) || errno == ERANGE)
801                     {
802                       font_msg (SE, _("Bad argument to `sizes'."));
803                       goto lossage;
804                     }
805                   if (lower < upper)
806                     {
807                       font_msg (SE, _("Bad range in argument to `sizes'."));
808                       goto lossage;
809                     }
810                 }
811               else
812                 upper = lower;
813               if (*tail)
814                 {
815                   font_msg (SE, _("Bad argument to `sizes'."));
816                   goto lossage;
817                 }
818
819               if (dev->n_sizes + 2 >= m_sizes)
820                 {
821                   m_sizes += 1;
822                   dev->sizes = xnrealloc (dev->sizes,
823                                           m_sizes, sizeof *dev->sizes);
824                 }
825               dev->sizes[dev->n_sizes++][0] = lower;
826               dev->sizes[dev->n_sizes][1] = upper;
827
828               found |= 0x10000;
829             }
830         }
831       else if (!strcmp (token, "family"))
832         {
833           token = strtok_r (NULL, whitespace, &sp);
834           if (!token)
835             {
836               font_msg (SE, _("Family name expected."));
837               goto lossage;
838             }
839           if (found & 0x20000)
840             {
841               font_msg (SE, _("This command already specified."));
842               goto lossage;
843             }
844           dev->family = xstrdup (token);
845         }
846       else if (!strcmp (token, "charset"))
847         break;
848       else
849         {
850           static const char *id[]
851             = {"res", "hor", "vert", "sizescale", "unitwidth", NULL};
852           const char **cp;
853           int value;
854
855           for (cp = id; *cp; cp++)
856             if (!strcmp (token, *cp))
857               break;
858           if (*cp == NULL)
859             continue;           /* completely ignore unrecognized lines */
860           if (found & (1 << (cp - id)))
861             font_msg (SW, _("%s: Device characteristic already defined."), *cp);
862
863           token = strtok_r (NULL, whitespace, &sp);
864           errno = 0;
865           if (!token || (value = strtol (token, NULL, 0)) <= 0 || errno == ERANGE)
866             {
867               font_msg (SE, _("%s: Invalid numeric format."), *cp);
868               goto lossage;
869             }
870           found |= (1 << (cp - id));
871           switch (cp - id)
872             {
873             case 0:
874               dev->res = value;
875               break;
876             case 1:
877               dev->horiz = value;
878               break;
879             case 2:
880               dev->vert = value;
881               break;
882             case 3:
883               dev->size_scale = value;
884               break;
885             case 4:
886               dev->unit_width = value;
887               break;
888             default:
889               assert (0);
890             }
891         }
892     }
893   if (ferror (f))
894     goto file_lossage;
895   if ((found & 0x10011) != 0x10011)
896     {
897       font_msg (SE, _("Missing `res', `unitwidth', and/or `sizes' line(s)."));
898       goto lossage;
899     }
900
901   /* Font name = family name + suffix. */
902   {
903     static const char *suffix[4] =
904       {"R", "I", "B", "BI"};    /* match OUTP_F_* */
905     int len;                    /* length of family name */
906     int i;
907
908     if (!dev->family)
909       dev->family = xstrdup ("");
910     len = strlen (dev->family);
911     for (i = 0; i < 4; i++)
912       {
913         char *cp;
914         dev->font_name[i] = xmalloc (len + strlen (suffix[i]) + 1);
915         cp = stpcpy (dev->font_name[i], dev->family);
916         strcpy (cp, suffix[i]);
917       }
918   }
919
920   dev->sizes[dev->n_sizes][0] = 0;
921   dev->sizes[dev->n_sizes][1] = 0;
922
923   msg (VM (2), _("Description file read successfully."));
924   
925   err_pop_file_locator (&where);
926   free (filename);
927   free (line);
928   return 1;
929
930   /* Come here on a file error. */
931 file_lossage:
932   msg (ME, "%s: %s", filename, strerror (errno));
933
934   /* Come here on any error. */
935 lossage:
936   fclose (f);
937   free (line);
938   free (dev->family);
939   dev->family = NULL;
940   free (filename);
941   free (dev->sizes);
942   dev->sizes = NULL;
943   dev->n_sizes = 0;
944 #if 0                           /* at the moment, no errors can come here when dev->font_name[*] are
945                                    nonzero. */
946   for (i = 0; i < 4; i++)
947     {
948       free (dev->font_name[i]);
949       dev->font_name[i] = NULL;
950     }
951 #endif
952
953   err_pop_file_locator (&where);
954   
955   msg (VM (1), _("Error reading description file."));
956   
957   return 0;
958 }
959
960 /* Finds character with index CH (as returned by name_to_index() or
961    number_to_index()) in font FONT and returns the associated metrics.
962    Nonexistent characters have width 0. */
963 struct char_metrics *
964 font_get_char_metrics (const struct font_desc *font, int ch)
965 {
966   short index;
967
968   if (ch < 0 || ch >= font->deref_size)
969     return 0;
970
971   index = font->deref[ch];
972   if (index == -1)
973     return 0;
974
975   return font->metric[index];
976 }
977
978 /* Finds kernpair consisting of CH1 and CH2, in that order, in font
979    FONT and returns the associated kerning adjustment. */
980 int
981 font_get_kern_adjust (const struct font_desc *font, int ch1, int ch2)
982 {
983   unsigned i;
984
985   if (!font->kern)
986     return 0;
987   for (i = hash_kern (ch1, ch2) & (font->kern_size - 1);
988        font->kern[i].ch1 != -1;)
989     {
990       if (font->kern[i].ch1 == ch1 && font->kern[i].ch2 == ch2)
991         return font->kern[i].adjust;
992       if (0 == i--)
993         i = font->kern_size - 1;
994     }
995   return 0;
996 }
997
998 /* Returns a twelve-point fixed-pitch font that can be used as a
999    last-resort fallback. */
1000 struct font_desc *
1001 default_font (void)
1002 {
1003   struct pool *font_pool;
1004   static struct font_desc *font;
1005
1006   if (font)
1007     return font;
1008   font_pool = pool_create ();
1009   font = pool_alloc (font_pool, sizeof *font);
1010   font->owner = font_pool;
1011   font->name = NULL;
1012   font->internal_name = pool_strdup (font_pool, _("<<fallback>>"));
1013   font->encoding = pool_strdup (font_pool, "text.enc");
1014   font->space_width = 12000;
1015   font->slant = 0.0;
1016   font->ligatures = 0;
1017   font->special = 0;
1018   font->ascent = 8000;
1019   font->descent = 4000;
1020   font->deref = NULL;
1021   font->deref_size = 0;
1022   font->metric = NULL;
1023   font->metric_size = 0;
1024   font->metric_used = 0;
1025   font->kern = NULL;
1026   font->kern_size = 8;
1027   font->kern_used = 0;
1028   font->kern_max_used = 0;
1029   return font;
1030 }