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