]> www.fi.muni.cz Git - evince.git/blob - backend/dvi/mdvi-lib/tfmfile.c
backends: Fix several security issues in the dvi-backend.
[evince.git] / backend / dvi / mdvi-lib / tfmfile.c
1 /* tfmfile.c -- readers for TFM, AFM, OTFM-0 and OTFM-1 files */
2 /*
3  * Copyright (C) 2000, Matias Atria
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU 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 02110-1301, USA.
18  */
19
20 #include <config.h>
21 #include <stdio.h> /* tex-file.h needs this */
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <string.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27
28 #include "mdvi.h"
29 #include "private.h"
30
31 #ifdef WITH_AFM_FILES
32 #undef TRUE
33 #undef FALSE
34 #include "afmparse.h"
35 #endif
36
37 typedef struct tfmpool {
38         struct tfmpool *next;
39         struct tfmpool *prev;
40         char    *short_name;
41         int     links;
42         TFMInfo tfminfo;
43 } TFMPool;
44
45 static ListHead tfmpool = {NULL, NULL, 0};
46 static DviHashTable tfmhash;
47
48 #define TFM_HASH_SIZE   31
49
50 #ifdef WORD_LITTLE_ENDIAN
51 static inline void swap_array(Uint32 *ptr, int n)
52 {
53         Uint32 i;
54         
55         while(n-- > 0) {
56                 i = *ptr;
57                 *ptr++ = ((i & 0xff000000) >> 24)
58                        | ((i & 0x00ff0000) >> 8)
59                        | ((i & 0x0000ff00) << 8)
60                        | ((i & 0x000000ff) << 24);
61         }
62 }
63 #endif
64
65 #ifdef WITH_AFM_FILES
66
67 static int      __PROTO(ofm_load_file(const char *filename, TFMInfo *info));
68
69 /* reading of AFM files */
70 /* macro to convert between AFM and TFM units */
71 #define AFM2TFM(x)      FROUND((double)(x) * 0x100000 / 1000)
72 int     afm_load_file(const char *filename, TFMInfo *info)
73 {
74         /* the information we want is:
75          *   - tfmwidth
76          *   - width and heights
77          *   - character origins
78          */
79         FontInfo *fi = NULL;
80         int     status;
81         CharMetricInfo *cm;
82         FILE    *in;
83         
84         in = fopen(filename, "rb");
85         if(in == NULL)
86                 return -1;
87         status = afm_parse_file(in, &fi, P_GM);
88         fclose(in);
89
90         if(status != ok) {
91                 mdvi_error(_("%s: Error reading AFM data\n"), filename);
92                 return -1;
93         }
94         
95         /* aim high */
96         info->chars = xnalloc(TFMChar, 256);
97         info->loc = 256;
98         info->hic = 0;
99         info->design = 0xa00000; /* fake -- 10pt */
100         info->checksum = 0; /* no checksum */
101         info->type = DviFontAFM;
102         mdvi_strncpy(info->coding, fi->gfi->encodingScheme, 63);
103         mdvi_strncpy(info->family, fi->gfi->familyName, 63);
104
105         /* now get the data */
106         for(cm = fi->cmi; cm < fi->cmi + fi->numOfChars; cm++) {
107                 int     code;
108                 TFMChar *ch;
109                 
110                 code = cm->code;
111                 if(code < 0 || code > 255)
112                         continue; /* ignore it */
113                 ch = &info->chars[code];
114                 ch->present = 1;
115                 if(code < info->loc)
116                         info->loc = code;
117                 if(code > info->hic)
118                         info->hic = code;
119                 ch->advance = AFM2TFM(cm->wx);
120                 /* this is the `leftSideBearing' */
121                 ch->left   = AFM2TFM(cm->charBBox.llx);
122                 /* this is the height (ascent - descent) -- the sign is to follow
123                  * TeX conventions, as opposed to Adobe's ones */
124                 ch->depth  = -AFM2TFM(cm->charBBox.lly);
125                 /* this is the width (rightSideBearing - leftSideBearing) */
126                 ch->right  = AFM2TFM(cm->charBBox.urx);
127                 /* this is the `ascent' */
128                 ch->height = AFM2TFM(cm->charBBox.ury);
129         }
130
131         /* we don't need this anymore */
132         afm_free_fontinfo(fi);
133
134         /* optimize storage */
135         if(info->loc > 0 || info->hic < 256) {
136                 memmove(&info->chars[0],
137                         &info->chars[info->loc],
138                         (info->hic - info->loc + 1) * sizeof(TFMChar));
139                 info->chars = mdvi_realloc(info->chars,
140                         (info->hic - info->loc + 1) * sizeof(TFMChar));
141         }
142
143         /* we're done */
144         return 0;
145 }
146
147 #endif /* WITH_AFM_FILES */
148
149 int     tfm_load_file(const char *filename, TFMInfo *info)
150 {
151         int     lf, lh, bc, ec, nw, nh, nd, ne;
152         int     i, n;
153         Uchar   *tfm;
154         Uchar   *ptr;
155         struct stat st;
156         int     size;
157         FILE    *in;
158         Int32   *cb;
159         Int32   *charinfo;
160         Int32   *widths;
161         Int32   *heights;
162         Int32   *depths;
163         Uint32  checksum;
164
165         in = fopen(filename, "rb");
166         if(in == NULL)
167                 return -1;
168         tfm = NULL;
169
170         DEBUG((DBG_FONTS, "(mt) reading TFM file `%s'\n",
171                 filename));
172         /* We read the entire TFM file into core */
173         if(fstat(fileno(in), &st) < 0)
174                 return -1;
175         /* according to the spec, TFM files are smaller than 16K */
176         if(st.st_size == 0 || st.st_size >= 16384)
177                 goto bad_tfm;
178
179         /* allocate a word-aligned buffer to hold the file */
180         size = 4 * ROUND(st.st_size, 4);
181         if(size != st.st_size)
182                 mdvi_warning(_("Warning: TFM file `%s' has suspicious size\n"), 
183                              filename);
184         tfm = (Uchar *)mdvi_malloc(size);
185         if(fread(tfm, st.st_size, 1, in) != 1)
186                 goto error;
187         /* we don't need this anymore */
188         fclose(in);
189         in = NULL;
190
191         /* not a checksum, but serves a similar purpose */
192         checksum = 0;
193         
194         ptr = tfm;
195         /* get the counters */
196         lf = muget2(ptr);
197         lh = muget2(ptr); checksum += 6 + lh;
198         bc = muget2(ptr); 
199         ec = muget2(ptr); checksum += ec - bc + 1;
200         nw = muget2(ptr); checksum += nw;
201         nh = muget2(ptr); checksum += nh;
202         nd = muget2(ptr); checksum += nd;
203         checksum += muget2(ptr); /* skip italics correction count */
204         checksum += muget2(ptr); /* skip lig/kern table size */
205         checksum += muget2(ptr); /* skip kern table size */
206         ne = muget2(ptr); checksum += ne;
207         checksum += muget2(ptr); /* skip # of font parameters */
208
209         size = ec - bc + 1;
210         cb = (Int32 *)tfm; cb += 6 + lh;
211         charinfo    = cb;  cb += size;
212         widths      = cb;  cb += nw;
213         heights     = cb;  cb += nh;
214         depths      = cb;
215
216         if(widths[0] || heights[0] || depths[0] || 
217            checksum != lf || bc - 1 > ec || ec > 255 || ne > 256)
218                 goto bad_tfm;
219
220         /* from this point on, no error checking is done */
221
222         /* now we're at the header */
223         /* get the checksum */
224         info->checksum = muget4(ptr);
225         /* get the design size */
226         info->design = muget4(ptr);
227         /* get the coding scheme */
228         if(lh > 2) {
229                 /* get the coding scheme */
230                 i = n = msget1(ptr);
231                 if(n < 0 || n > 39) {
232                         mdvi_warning(_("%s: font coding scheme truncated to 40 bytes\n"),
233                                      filename);
234                         n = 39;
235                 }
236                 memcpy(info->coding, ptr, n);
237                 info->coding[n] = 0;
238                 ptr += i;
239         } else
240                 strcpy(info->coding, "FontSpecific");
241         /* get the font family */
242         if(lh > 12) {
243                 n = msget1(ptr);
244                 if(n > 0) {
245                         i = Max(n, 63);
246                         memcpy(info->family, ptr, i);
247                         info->family[i] = 0;
248                 } else
249                         strcpy(info->family, "unspecified");
250                 ptr += n;
251         }
252         /* now we don't read from `ptr' anymore */
253         
254         info->loc = bc;
255         info->hic = ec;
256         info->type = DviFontTFM;
257
258         /* allocate characters */
259         info->chars = xnalloc(TFMChar, size);
260
261
262 #ifdef WORD_LITTLE_ENDIAN
263         /* byte-swap the three arrays at once (they are consecutive in memory) */
264         swap_array((Uint32 *)widths, nw + nh + nd);
265 #endif
266
267         /* get the relevant data */
268         ptr = (Uchar *)charinfo;
269         for(i = bc; i <= ec; ptr += 3, i++) {
270                 int     ndx;
271
272                 ndx = (int)*ptr; ptr++;
273                 info->chars[i-bc].advance = widths[ndx];
274                 /* TFM files lack this information */
275                 info->chars[i-bc].left = 0;
276                 info->chars[i-bc].right = widths[ndx];
277                 info->chars[i-bc].present = (ndx != 0);
278                 if(ndx) {
279                         ndx = ((*ptr >> 4) & 0xf);
280                         info->chars[i-bc].height = heights[ndx];
281                         ndx = (*ptr & 0xf);
282                         info->chars[i-bc].depth = depths[ndx];
283                 }
284         }
285
286         /* free everything */
287         mdvi_free(tfm);
288         
289         return 0;
290
291 bad_tfm:
292         mdvi_error(_("%s: File corrupted, or not a TFM file\n"), filename);
293 error:
294         if(tfm) mdvi_free(tfm);
295         if(in)  fclose(in);
296         return -1;      
297 }
298
299 static int ofm1_load_file(FILE *in, TFMInfo *info)
300 {
301         int     lf, lh, bc, ec, nw, nh, nd;
302         int     nco, ncw, npc;
303         int     i;
304         int     n;
305         int     size;
306         Int32   *tfm;
307         Int32   *widths;
308         Int32   *heights;
309         Int32   *depths;
310         TFMChar *tch;
311         TFMChar *end;
312         
313         lf = fuget4(in);
314         lh = fuget4(in);
315         bc = fuget4(in);
316         ec = fuget4(in);
317         nw = fuget4(in);
318         nh = fuget4(in);
319         nd = fuget4(in);
320         fuget4(in); /* italics */
321         fuget4(in); /* lig-kern */
322         fuget4(in); /* kern */
323         fuget4(in); /* extensible recipe */
324         fuget4(in); /* parameters */
325         fuget4(in); /* direction */
326         nco = fuget4(in);
327         ncw = fuget4(in);
328         npc = fuget4(in);
329
330         /* get the checksum */
331         info->checksum = fuget4(in);
332         /* the design size */
333         info->design = fuget4(in);
334         /* get the coding scheme */
335         if(lh > 2) {
336                 /* get the coding scheme */
337                 i = n = fsget1(in);
338                 if(n < 0 || n > 39)
339                         n = 39;
340                 fread(info->coding, 39, 1, in);
341                 info->coding[n] = 0;
342         } else
343                 strcpy(info->coding, "FontSpecific");
344         /* get the font family */
345         if(lh > 12) {
346                 n = fsget1(in);
347                 if(n > 0) {
348                         i = Max(n, 63);
349                         fread(info->family, i, 1, in);
350                         info->family[i] = 0;
351                 } else
352                         strcpy(info->family, "unspecified");
353         }
354         tfm = NULL;
355
356         /* jump to the beginning of the char-info table */
357         fseek(in, 4L*nco, SEEK_SET);
358
359         size = ec - bc + 1;     
360         info->loc = bc;
361         info->hic = ec;
362         info->chars = xnalloc(TFMChar, size);
363         end = info->chars + size;
364         
365         for(tch = info->chars, i = 0; i < ncw; i++) {
366                 TFMChar ch;
367                 int     nr;
368                 
369                 /* in the characters we store the actual indices */
370                 ch.advance = fuget2(in);
371                 ch.height  = fuget1(in);
372                 ch.depth   = fuget1(in);
373                 /* skip 2nd word */
374                 fuget4(in);
375                 /* get # of repeats */
376                 nr = fuget2(in);
377                 /* skip parameters */
378                 fseek(in, (long)npc * 2, SEEK_CUR);
379                 /* if npc is odd, skip padding */
380                 if(npc & 1) fuget2(in);
381
382                 /* now repeat the character */
383                 while(nr-- >= 0 && tch < end)
384                         memcpy(tch++, &ch, sizeof(TFMChar));
385                 if(tch == end)
386                         goto bad_tfm;
387         }       
388
389         /* I wish we were done, but we aren't */
390
391         /* get the widths, heights and depths */
392         size = nw + nh + nd;
393         tfm = xnalloc(Int32, size);
394         /* read them in one sweep */
395         if(fread(tfm, 4, size, in) != size) {
396                 mdvi_free(tfm);
397                 goto bad_tfm;
398         }
399
400         /* byte-swap things if necessary */
401 #ifdef WORD_LITTLE_ENDIAN
402         swap_array((Uint32 *)tfm, size);
403 #endif
404         widths  = tfm;
405         heights = widths + nw;
406         depths  = heights + nh;
407
408         if(widths[0] || heights[0] || depths[0])
409                 goto bad_tfm;
410         
411         /* now fix the characters */
412         size = ec - bc + 1;
413         for(tch = info->chars; tch < end; tch++) {
414                 tch->present = (tch->advance != 0);
415                 tch->advance = widths[tch->advance];
416                 tch->height  = heights[tch->height];
417                 tch->depth   = depths[tch->depth];
418                 tch->left    = 0;
419                 tch->right   = tch->advance;
420         }
421
422         /* NOW we're done */
423         mdvi_free(tfm);
424         return 0;
425
426 bad_tfm:
427         if(tfm) mdvi_free(tfm);
428         return -1;
429 }
430
431 /* we don't read OFM files into memory, because they can potentially be large */
432 static int      ofm_load_file(const char *filename, TFMInfo *info)
433 {
434         int     lf, lh, bc, ec, nw, nh, nd;
435         int     i, n;
436         Int32   *tfm;
437         Uchar   *ptr;
438         int     size;
439         FILE    *in;
440         Int32   *cb;
441         Int32   *charinfo;
442         Int32   *widths;
443         Int32   *heights;
444         Int32   *depths;
445         Uint32  checksum;
446         int     olevel;
447         int     nwords;
448
449         in = fopen(filename, "rb");
450         if(in == NULL)
451                 return -1;
452
453         /* not a checksum, but serves a similar purpose */
454         checksum = 0;
455         
456         /* get the counters */
457         /* get file level */
458         olevel = fsget2(in);
459         if(olevel != 0)
460                 goto bad_tfm;
461         olevel = fsget2(in);
462         if(olevel != 0) {
463                 DEBUG((DBG_FONTS, "(mt) reading Level-1 OFM file `%s'\n", 
464                         filename));
465                 /* we handle level-1 files separately */
466                 if(ofm1_load_file(in, info) < 0)
467                         goto bad_tfm;
468                 return 0;
469         }
470
471         DEBUG((DBG_FONTS, "(mt) reading Level-0 OFM file `%s'\n", filename));
472         nwords = 14;
473         lf = fuget4(in); checksum  = nwords;
474         lh = fuget4(in); checksum += lh;
475         bc = fuget4(in); 
476         ec = fuget4(in); checksum += 2 * (ec - bc + 1);
477         nw = fuget4(in); checksum += nw;
478         nh = fuget4(in); checksum += nh;
479         nd = fuget4(in); checksum += nd;
480         checksum +=   fuget4(in); /* skip italics correction count */
481         checksum += 2*fuget4(in); /* skip lig/kern table size */
482         checksum +=   fuget4(in); /* skip kern table size */
483         checksum += 2*fuget4(in); /* skip extensible recipe count */
484         checksum +=   fuget4(in); /* skip # of font parameters */
485
486         /* I have found several .ofm files that seem to have the 
487          * font-direction word missing, so we try to detect that here */
488         if(checksum == lf + 1) {
489                 DEBUG((DBG_FONTS, "(mt) font direction missing in `%s'\n",
490                         filename));
491                 checksum--;
492                 nwords--;
493         } else {
494                 /* skip font direction */
495                 fuget4(in);
496         }
497
498         if(checksum != lf || bc > ec + 1 || ec > 65535)
499                 goto bad_tfm;
500
501         /* now we're at the header */
502
503         /* get the checksum */
504         info->checksum = fuget4(in);
505         /* get the design size */
506         info->design = fuget4(in);
507
508         /* get the coding scheme */
509         if(lh > 2) {
510                 /* get the coding scheme */
511                 i = n = fsget1(in);
512                 if(n < 0 || n > 39) {
513                         mdvi_warning(_("%s: font coding scheme truncated to 40 bytes\n"),
514                                      filename);
515                         n = 39;
516                 }
517                 fread(info->coding, 39, 1, in);
518                 info->coding[n] = 0;
519         } else
520                 strcpy(info->coding, "FontSpecific");
521         /* get the font family */
522         if(lh > 12) {
523                 n = fsget1(in);
524                 if(n > 0) {
525                         i = Max(n, 63);
526                         fread(info->family, i, 1, in);
527                         info->family[i] = 0;
528                 } else
529                         strcpy(info->family, "unspecified");
530         }
531
532         /* now skip anything else in the header */
533         fseek(in, 4L*(nwords + lh), SEEK_SET);
534         /* and read everything at once */
535         size = 2*(ec - bc + 1) + nw + nh + nd;
536         tfm = xnalloc(Int32, size * sizeof(Int32));
537         if(fread(tfm, 4, size, in) != size) {
538                 mdvi_free(tfm);
539                 goto bad_tfm;
540         }
541         /* byte-swap all the tables at once */
542 #ifdef WORD_LITTLE_ENDIAN
543         swap_array((Uint32 *)tfm, size);
544 #endif
545         cb = tfm;
546         charinfo = cb; cb += 2*(ec - bc + 1);
547         widths   = cb; cb += nw;
548         heights  = cb; cb += nh;
549         depths   = cb;
550
551         if(widths[0] || heights[0] || depths[0]) {
552                 mdvi_free(tfm);
553                 goto bad_tfm;
554         }
555
556         /* from this point on, no error checking is done */
557         
558         /* we don't need this anymore */
559         fclose(in);
560
561         /* now we don't read from `ptr' anymore */
562         
563         info->loc = bc;
564         info->hic = ec;
565         info->type = DviFontTFM;
566
567         /* allocate characters */
568         info->chars = xnalloc(TFMChar, size);
569
570         /* get the relevant data */
571         ptr = (Uchar *)charinfo;
572         for(i = bc; i <= ec; ptr += 4, i++) {
573                 int     ndx;
574
575                 ndx = muget2(ptr);
576                 info->chars[i-bc].advance = widths[ndx];
577                 /* TFM files lack this information */
578                 info->chars[i-bc].left = 0;
579                 info->chars[i-bc].right = widths[ndx];
580                 info->chars[i-bc].present = (ndx != 0);
581                 ndx = muget1(ptr);
582                 info->chars[i-bc].height = heights[ndx];
583                 ndx = muget1(ptr);
584                 info->chars[i-bc].depth = depths[ndx];
585         }
586
587         mdvi_free(tfm);
588         return 0;
589
590 bad_tfm:
591         mdvi_error(_("%s: File corrupted, or not a TFM file\n"), filename);
592         fclose(in);
593         return -1;      
594 }
595
596 char    *lookup_font_metrics(const char *name, int *type)
597 {
598         char    *file;
599         
600         switch(*type) {
601 #ifndef WITH_AFM_FILES
602                 case DviFontAny:
603 #endif
604                 case DviFontTFM:
605                         file = kpse_find_tfm(name);
606                         *type = DviFontTFM;
607                         break;
608                 case DviFontOFM: {
609                         file = kpse_find_ofm(name);
610                         /* we may have gotten a TFM back */
611                         if(file != NULL) {
612                                 const char *ext = file_extension(file);
613                                 if(ext && STREQ(ext, "tfm"))
614                                         *type = DviFontTFM;
615                         }
616                         break;
617                 }
618 #ifdef WITH_AFM_FILES
619                 case DviFontAFM:
620                         file = kpse_find_file(name, kpse_afm_format, 0);
621                         break;  
622                 case DviFontAny:
623                         file = kpse_find_file(name, kpse_afm_format, 0);
624                         *type = DviFontAFM;
625                         if(file == NULL) {
626                                 file = kpse_find_tfm(name);
627                                 *type = DviFontTFM;
628                         }
629                         break;
630 #endif
631                 default:
632                         return NULL;
633         }
634
635         return file;
636 }
637
638 /* 
639  * The next two functions are just wrappers for the font metric loaders, 
640  * and use the pool of TFM data
641  */
642
643 /* this is how we interpret arguments:
644  *  - if filename is NULL, we look for files of the given type,
645  *    unless type is DviFontAny, in which case we try all the
646  *    types we know of.
647  *  - if filename is not NULL, we look at `type' to decide
648  *    how to read the file. If type is DviFontAny, we just
649  *    return an error.
650  */
651 TFMInfo *get_font_metrics(const char *short_name, int type, const char *filename)
652 {
653         TFMPool *tfm = NULL;
654         int     status;
655         char    *file;
656         
657         if(tfmpool.count) {
658                 tfm = (TFMPool *)mdvi_hash_lookup(&tfmhash, 
659                         MDVI_KEY(short_name));
660                 if(tfm != NULL) {
661                         DEBUG((DBG_FONTS, "(mt) reusing metric file `%s' (%d links)\n",
662                                 short_name, tfm->links));
663                         tfm->links++;
664                         return &tfm->tfminfo;
665                 }
666         }
667
668         file = filename ? (char *)filename : lookup_font_metrics(short_name, &type);
669         if(file == NULL)
670                 return NULL;
671
672         tfm = xalloc(TFMPool);
673         DEBUG((DBG_FONTS, "(mt) loading font metric data from `%s'\n", file, file));
674         switch(type) {
675         case DviFontTFM:
676                 status = tfm_load_file(file, &tfm->tfminfo);
677                 break;
678         case DviFontOFM:
679                 status = ofm_load_file(file, &tfm->tfminfo);
680                 break;
681 #ifdef WITH_AFM_FILES
682         case DviFontAFM:
683                 status = afm_load_file(file, &tfm->tfminfo);
684                 break;
685 #endif
686         default:
687                 status = -1;
688                 break;
689         }
690         if(file != filename)
691                 mdvi_free(file);
692         if(status < 0) {
693                 mdvi_free(tfm);
694                 return NULL;
695         }
696         tfm->short_name = mdvi_strdup(short_name);
697         
698         /* add it to the pool */
699         if(tfmpool.count == 0)
700                 mdvi_hash_create(&tfmhash, TFM_HASH_SIZE);
701         mdvi_hash_add(&tfmhash, MDVI_KEY(tfm->short_name), 
702                 tfm, MDVI_HASH_UNCHECKED);
703         listh_prepend(&tfmpool, LIST(tfm));
704         tfm->links = 1;
705
706         return &tfm->tfminfo;
707 }
708
709 void    free_font_metrics(TFMInfo *info)
710 {
711         TFMPool *tfm;
712
713         if(tfmpool.count == 0)
714                 return;
715         /* get the entry -- can't use the hash table for this, because
716          * we don't have the short name */
717         for(tfm = (TFMPool *)tfmpool.head; tfm; tfm = tfm->next)
718                 if(info == &tfm->tfminfo)
719                         break;
720         if(tfm == NULL)
721                 return;
722         if(--tfm->links > 0) {
723                 DEBUG((DBG_FONTS, "(mt) %s not removed, still in use\n",        
724                         tfm->short_name));
725                 return;
726         }
727         mdvi_hash_remove_ptr(&tfmhash, MDVI_KEY(tfm->short_name));
728
729         DEBUG((DBG_FONTS, "(mt) removing unused TFM data for `%s'\n", tfm->short_name));
730         listh_remove(&tfmpool, LIST(tfm));
731         mdvi_free(tfm->short_name);
732         mdvi_free(tfm->tfminfo.chars);
733         mdvi_free(tfm); 
734 }
735
736 void    flush_font_metrics(void)
737 {
738         TFMPool *ptr;
739         
740         for(; (ptr = (TFMPool *)tfmpool.head); ) {
741                 tfmpool.head = LIST(ptr->next);
742                 
743                 mdvi_free(ptr->short_name);
744                 mdvi_free(ptr->tfminfo.chars);
745                 mdvi_free(ptr);
746         }
747         mdvi_hash_reset(&tfmhash, 0);
748 }