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