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