1% mapfile.w
2%
3% Copyright 1996-2006 Han The Thanh <thanh@@pdftex.org>
4% Copyright 2006-2010 Taco Hoekwater <taco@@luatex.org>
5%
6% This file is part of LuaTeX.
7%
8% LuaTeX is free software; you can redistribute it and/or modify it under
9% the terms of the GNU General Public License as published by the Free
10% Software Foundation; either version 2 of the License, or (at your
11% option) any later version.
12%
13% LuaTeX is distributed in the hope that it will be useful, but WITHOUT
14% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15% FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
16% License for more details.
17%
18% You should have received a copy of the GNU General Public License along
19% with LuaTeX; if not, see <http://www.gnu.org/licenses/>.
20
21@ @c
22
23
24#include "ptexlib.h"
25#include <math.h>
26#include <kpathsea/c-auto.h>
27#include <kpathsea/c-memstr.h>
28#include <string.h>
29
30#define FM_BUF_SIZE     1024
31
32static FILE *fm_file;
33
34static unsigned char *fm_buffer = NULL;
35static int fm_size = 0;
36static int fm_curbyte = 0;
37
38#define fm_open(a)      (fm_file = fopen((char *)(a), FOPEN_RBIN_MODE))
39#define fm_read_file()  readbinfile(fm_file,&fm_buffer,&fm_size)
40#define fm_close()      xfclose(fm_file, cur_file_name)
41#define fm_getchar()    fm_buffer[fm_curbyte++]
42#define fm_eof()        (fm_curbyte>fm_size)
43#define is_cfg_comment(c) \
44    (c == 10 || c == '*' || c == '#' || c == ';' || c == '%')
45
46typedef enum { FM_DUPIGNORE, FM_REPLACE, FM_DELETE } updatemode;
47
48typedef struct mitem {
49    updatemode mode;            /* FM_DUPIGNORE or FM_REPLACE or FM_DELETE */
50    maptype type;               /* map file or map line */
51    char *line;                 /* pointer to map file name or map line */
52    int lineno;                 /* line number in map file */
53} mapitem;
54mapitem *mitem = NULL;
55
56#define read_field(r, q, buf) do {  \
57    q = buf;                        \
58    while (*r != ' ' && *r != '<' && *r != '"' && *r != '\0') \
59        *q++ = *r++;                \
60    *q = '\0';                      \
61    skip (r, ' ');                  \
62} while (0)
63
64#define set_field(F) do {           \
65    if (q > buf)                    \
66        fm->F = xstrdup(buf);       \
67    if (*r == '\0')                 \
68        goto done;                  \
69} while (0)
70
71fm_entry *new_fm_entry(void)
72{
73    fm_entry *fm;
74    fm = xtalloc(1, fm_entry);
75    fm->tfm_name = NULL;
76    fm->sfd_name = NULL;
77    fm->ps_name = NULL;
78    fm->fd_flags = FD_FLAGS_NOT_SET_IN_MAPLINE;
79    fm->ff_name = NULL;
80    fm->encname = NULL;
81    fm->type = 0;
82    fm->slant = 0;
83    fm->extend = 1000;
84    fm->pid = -1;
85    fm->eid = -1;
86    fm->subfont = NULL;
87    unset_slantset(fm);
88    unset_extendset(fm);
89    unset_inuse(fm);
90    return fm;
91}
92
93void delete_fm_entry(fm_entry * fm)
94{
95    xfree(fm->tfm_name);
96    xfree(fm->sfd_name);
97    xfree(fm->ps_name);
98    xfree(fm->ff_name);
99    xfree(fm);
100}
101
102static ff_entry *new_ff_entry(void)
103{
104    ff_entry *ff;
105    ff = xtalloc(1, ff_entry);
106    ff->ff_name = NULL;
107    ff->ff_path = NULL;
108    return ff;
109}
110
111static void delete_ff_entry(ff_entry * ff)
112{
113    xfree(ff->ff_name);
114    xfree(ff->ff_path);
115    xfree(ff);
116}
117
118/**********************************************************************/
119
120static struct avl_table *tfm_tree = NULL;
121static struct avl_table *ff_tree = NULL;
122static struct avl_table *encname_tree = NULL;
123
124/* AVL sort fm_entry into tfm_tree by tfm_name */
125
126static int comp_fm_entry_tfm(const void *pa, const void *pb, void *p)
127{
128    (void) p;
129    return strcmp(((const fm_entry *) pa)->tfm_name,
130                  ((const fm_entry *) pb)->tfm_name);
131}
132
133/* AVL sort ff_entry into ff_tree by ff_name */
134
135static int comp_ff_entry(const void *pa, const void *pb, void *p)
136{
137    (void) p;
138    return strcmp(((const ff_entry *) pa)->ff_name,
139                  ((const ff_entry *) pb)->ff_name);
140}
141
142static void create_avl_trees(void)
143{
144    assert(tfm_tree == NULL);
145    tfm_tree = avl_create(comp_fm_entry_tfm, NULL, &avl_xallocator);
146    assert(tfm_tree != NULL);
147    assert(ff_tree == NULL);
148    ff_tree = avl_create(comp_ff_entry, NULL, &avl_xallocator);
149    assert(ff_tree != NULL);
150    assert(encname_tree == NULL);
151    encname_tree = avl_create(comp_string_entry, NULL, &avl_xallocator);
152    assert(encname_tree != NULL);
153}
154
155int avl_do_entry(fm_entry * fm, int mode)
156{
157    fm_entry *p;
158    void *a;
159    void **aa;
160    int delete_new = 0;
161    if (tfm_tree == NULL)
162        create_avl_trees();
163    p = (fm_entry *) avl_find(tfm_tree, fm);
164    if (p != NULL) {
165        switch (mode) {
166        case FM_DUPIGNORE:
167            luatex_warn
168                ("fontmap entry for `%s' already exists, duplicates ignored",
169                 fm->tfm_name);
170            delete_new = 1;
171            break;
172        case FM_REPLACE:
173        case FM_DELETE:
174            if (is_inuse(p)) {
175                luatex_warn
176                    ("fontmap entry for `%s' has been used, replace/delete not allowed",
177                     fm->tfm_name);
178                delete_new = 1;
179            } else {
180                a = avl_delete(tfm_tree, p);
181                assert(a != NULL);
182                delete_fm_entry(p);
183            }
184            break;
185        default:
186            assert(0);
187        }
188    }
189    if ((mode == FM_DUPIGNORE || mode == FM_REPLACE) && delete_new == 0) {
190        aa = avl_probe(tfm_tree, fm);
191        assert(aa != NULL);
192    } else
193        delete_new = 1;
194    return delete_new;
195}
196
197/* add the encoding name to an AVL tree. this has nothing to do with writeenc.c */
198
199static char *add_encname(char *s)
200{
201    char *p;
202    void **aa;
203    assert(s != NULL);
204    assert(encname_tree != NULL);
205    if ((p = (char *) avl_find(encname_tree, s)) == NULL) {     /* encoding name not yet registered */
206        p = xstrdup(s);
207        aa = avl_probe(encname_tree, p);
208        assert(aa != NULL);
209    }
210    return p;
211}
212
213/**********************************************************************/
214/* consistency check for map entry, with warn flag */
215
216static int check_fm_entry(fm_entry * fm, boolean warn)
217{
218    int a = 0;
219    assert(fm != NULL);
220
221    if (is_fontfile(fm) && !is_included(fm)) {
222        if (warn)
223            luatex_warn
224                ("ambiguous entry for `%s': font file present but not included, "
225                 "will be treated as font file not present", fm->tfm_name);
226        xfree(fm->ff_name);
227        /* do not set variable |a| as this entry will be still accepted */
228    }
229
230    /* if both ps_name and font file are missing, drop this entry */
231    if (fm->ps_name == NULL && !is_fontfile(fm)) {
232        if (warn)
233            luatex_warn
234                ("invalid entry for `%s': both ps_name and font file missing",
235                 fm->tfm_name);
236        a += 1;
237    }
238
239    /* TrueType fonts cannot be reencoded without subsetting */
240    if (is_truetype(fm) && is_reencoded(fm) && !is_subsetted(fm)) {
241        if (warn)
242            luatex_warn
243                ("invalid entry for `%s': only subsetted TrueType font can be reencoded",
244                 fm->tfm_name);
245        a += 2;
246    }
247
248    /* the value of SlantFont and ExtendFont must be reasonable */
249    if (fm->slant < FONT_SLANT_MIN || fm->slant > FONT_SLANT_MAX) {
250        if (warn)
251            luatex_warn
252                ("invalid entry for `%s': too big value of SlantFont (%g)",
253                 fm->tfm_name, fm->slant / 1000.0);
254        a += 8;
255    }
256    if (fm->extend < FONT_EXTEND_MIN || fm->extend > FONT_EXTEND_MAX) {
257        if (warn)
258            luatex_warn
259                ("invalid entry for `%s': too big value of ExtendFont (%g)",
260                 fm->tfm_name, fm->extend / 1000.0);
261        a += 16;
262    }
263
264    /* subfonts must be used with subsetted non-reencoded TrueType fonts */
265    if (fm->pid != -1 &&
266        !(is_truetype(fm) && is_subsetted(fm) && !is_reencoded(fm))) {
267        if (warn)
268            luatex_warn
269                ("invalid entry for `%s': PidEid can be used only with subsetted non-reencoded TrueType fonts",
270                 fm->tfm_name);
271        a += 32;
272    }
273
274    return a;
275}
276
277/**********************************************************************/
278/* returns the font number if s is one of the 14 std. font names, -1 otherwise; speed-trimmed. */
279
280int check_std_t1font(char *s)
281{
282    static const char *std_t1font_names[] = {
283        "Courier",              /* 0:7 */
284        "Courier-Bold",         /* 1:12 */
285        "Courier-Oblique",      /* 2:15 */
286        "Courier-BoldOblique",  /* 3:19 */
287        "Helvetica",            /* 4:9 */
288        "Helvetica-Bold",       /* 5:14 */
289        "Helvetica-Oblique",    /* 6:17 */
290        "Helvetica-BoldOblique",        /* 7:21 */
291        "Symbol",               /* 8:6 */
292        "Times-Roman",          /* 9:11 */
293        "Times-Bold",           /* 10:10 */
294        "Times-Italic",         /* 11:12 */
295        "Times-BoldItalic",     /* 12:16 */
296        "ZapfDingbats"          /* 13:12 */
297    };
298    static const int index[] =
299        { -1, -1, -1, -1, -1, -1, 8, 0, -1, 4, 10, 9, -1, -1, 5, 2, 12, 6, -1,
300        3, -1, 7
301    };
302    size_t n;
303    int k = -1;
304    assert(s != NULL);
305    n = strlen(s);
306    if (n > 21)
307        return -1;
308    if (n == 12) {              /* three names have length 12 */
309        switch (*s) {
310        case 'C':
311            k = 1;              /* Courier-Bold */
312            break;
313        case 'T':
314            k = 11;             /* Times-Italic */
315            break;
316        case 'Z':
317            k = 13;             /* ZapfDingbats */
318            break;
319        default:
320            return -1;
321        }
322    } else
323        k = index[n];
324    if (k > -1 && !strcmp(std_t1font_names[k], s))
325        return k;
326    return -1;
327}
328
329/**********************************************************************/
330
331static void fm_scan_line(void)
332{
333    int a, b, c, j, u = 0, v = 0;
334    char cc;
335    float d;
336    fm_entry *fm;
337    char fm_line[FM_BUF_SIZE], buf[FM_BUF_SIZE];
338    char *p, *q, *s;
339    char *r = NULL;
340    switch (mitem->type) {
341    case MAPFILE:
342        p = fm_line;
343        while (!fm_eof()) {
344            if (fm_curbyte == fm_size) {
345                fm_curbyte++;
346                cc = 10;
347            } else {
348                cc = (char) fm_getchar();
349            }
350            append_char_to_buf(cc, p, fm_line, FM_BUF_SIZE);
351            if (cc == 10)
352                break;
353        }
354        *(--p) = '\0';
355        r = fm_line;
356        break;
357    case MAPLINE:
358        r = mitem->line;        /* work on string from makecstring() */
359        break;
360    default:
361        assert(0);
362    }
363    if (*r == '\0' || is_cfg_comment(*r))
364        return;
365    fm = new_fm_entry();
366    read_field(r, q, buf);
367    set_field(tfm_name);
368    if (!isdigit((unsigned char)*r)) {         /* 2nd field ps_name may not start with a digit */
369        read_field(r, q, buf);
370        set_field(ps_name);
371    }
372    if (isdigit((unsigned char)*r)) {          /* font descriptor /Flags given? */
373        for (s = r; isdigit((unsigned char)*s); s++);
374        if (*s == ' ' || *s == '"' || *s == '<' || *s == '\0') {        /* not e. g. 8r.enc */
375            fm->fd_flags = atoi(r);
376            while (isdigit((unsigned char)*r))
377                r++;
378        }
379    }
380    while (1) {                 /* loop through "specials", encoding, font file */
381        skip(r, ' ');
382        switch (*r) {
383        case '\0':
384            goto done;
385        case '"':              /* opening quote */
386            r++;
387            u = v = 0;
388            do {
389                skip(r, ' ');
390                if (sscanf(r, "%f %n", &d, &j) > 0) {
391                    s = r + j;  /* jump behind number, eat also blanks, if any */
392                    if (*(s - 1) == 'E' || *(s - 1) == 'e')
393                        s--;    /* e. g. 0.5ExtendFont: %f = 0.5E */
394                    if (str_prefix(s, "SlantFont")) {
395                        d *= (float) 1000.0;    /* correct rounding also for neg. numbers */
396                        fm->slant = (int) (d > 0 ? d + 0.5 : d - 0.5);
397                        set_slantset(fm);
398                        r = s + strlen("SlantFont");
399                    } else if (str_prefix(s, "ExtendFont")) {
400                        d *= (float) 1000.0;
401                        fm->extend = (int) (d > 0 ? d + 0.5 : d - 0.5);
402                        set_extendset(fm);
403                        r = s + strlen("ExtendFont");
404                    } else {    /* unknown name */
405                        for (r = s; *r != ' ' && *r != '"' && *r != '\0'; r++); /* jump over name */
406                        c = *r; /* remember char for temporary end of string */
407                        *r = '\0';
408                        luatex_warn
409                            ("invalid entry for `%s': unknown name `%s' ignored",
410                             fm->tfm_name, s);
411                        *r = (char) c;
412                    }
413                } else
414                    for (; *r != ' ' && *r != '"' && *r != '\0'; r++);
415            }
416            while (*r == ' ');
417            if (*r == '"')      /* closing quote */
418                r++;
419            else {
420                luatex_warn
421                    ("invalid entry for `%s': closing quote missing",
422                     fm->tfm_name);
423                goto bad_line;
424            }
425            break;
426        case 'P':              /* handle cases for subfonts like 'PidEid=3,1' */
427            if (sscanf(r, "PidEid=%i, %i %n", &a, &b, &c) >= 2) {
428                fm->pid = (short) a;
429                fm->eid = (short) b;
430                r += c;
431                break;
432            }
433        default:               /* encoding or font file specification */
434            a = b = 0;
435            if (*r == '<') {
436                a = *r++;
437                if (*r == '<' || *r == '[')
438                    b = *r++;
439            }
440            read_field(r, q, buf);
441            /* encoding, formats: '8r.enc' or '<8r.enc' or '<[8r.enc' */
442            if (strlen(buf) > 4 && strcasecmp(strend(buf) - 4, ".enc") == 0) {
443                fm->encname = add_encname(buf);
444                u = v = 0;      /* u, v used if intervening blank: "<< foo" */
445            } else if (strlen(buf) > 0) {       /* file name given */
446                /* font file, formats:
447                 * subsetting:    '<cmr10.pfa'
448                 * no subsetting: '<<cmr10.pfa'
449                 * no embedding:  'cmr10.pfa'
450                 */
451                if (a == '<' || u == '<') {
452                    set_included(fm);
453                    if ((a == '<' && b == 0) || (a == 0 && v == 0))
454                        set_subsetted(fm);
455                    /* otherwise b == '<' (or '[') => no subsetting */
456                }
457                set_field(ff_name);
458                u = v = 0;
459            } else {
460                u = a;
461                v = b;
462            }
463        }
464    }
465  done:
466    if (fm->ps_name != NULL && (check_std_t1font(fm->ps_name) >= 0))
467        set_std_t1font(fm);
468    if (is_fontfile(fm) && strlen(fm_fontfile(fm)) > 3) {
469        if (strcasecmp(strend(fm_fontfile(fm)) - 4, ".ttf") == 0)
470            set_truetype(fm);
471        else if (strcasecmp(strend(fm_fontfile(fm)) - 4, ".ttc") == 0)
472            set_truetype(fm);
473        else if (strcasecmp(strend(fm_fontfile(fm)) - 4, ".otf") == 0)
474            set_opentype(fm);
475        else
476            set_type1(fm);
477    } else
478        set_type1(fm);          /* assume a builtin font is Type1 */
479    if (check_fm_entry(fm, true) != 0)
480        goto bad_line;
481    /*
482       Until here the map line has been completely scanned without errors;
483       fm points to a valid, freshly filled-out fm_entry structure.
484       Now follows the actual work of registering/deleting.
485     */
486    if (handle_subfont_fm(fm, mitem->mode))     /* is this a subfont? */
487        return;
488    if (avl_do_entry(fm, mitem->mode) == 0)
489        return;
490  bad_line:
491    delete_fm_entry(fm);
492}
493
494/**********************************************************************/
495
496static void fm_read_info(void)
497{
498    int callback_id;
499    int file_opened = 0;
500
501    if (tfm_tree == NULL)
502        create_avl_trees();
503    if (mitem->line == NULL)    /* nothing to do */
504        return;
505    mitem->lineno = 1;
506    switch (mitem->type) {
507    case MAPFILE:
508        xfree(fm_buffer);
509        fm_curbyte = 0;
510        fm_size = 0;
511        cur_file_name = luatex_find_file(mitem->line, find_map_file_callback);
512        if (cur_file_name) {
513            callback_id = callback_defined(read_map_file_callback);
514            if (callback_id > 0) {
515                if (run_callback(callback_id, "S->bSd", cur_file_name,
516                                 &file_opened, &fm_buffer, &fm_size)) {
517                    if (file_opened) {
518                        if (fm_size > 0) {
519                            report_start_file(filetype_map,cur_file_name);
520                            while (!fm_eof()) {
521                                fm_scan_line();
522                                mitem->lineno++;
523                            }
524                            report_stop_file(filetype_map);
525                            fm_file = NULL;
526                        }
527                    } else {
528                        luatex_warn("cannot open font map file (%s)", cur_file_name);
529                    }
530                } else {
531                    luatex_warn("cannot open font map file (%s)", cur_file_name);
532                }
533            } else {
534                if (!fm_open(cur_file_name)) {
535                    luatex_warn("cannot open font map file (%s)", cur_file_name);
536                } else {
537                    fm_read_file();
538                    report_start_file(filetype_map,cur_file_name);
539                    while (!fm_eof()) {
540                        fm_scan_line();
541                        mitem->lineno++;
542                    }
543                    fm_close();
544                    report_stop_file(filetype_map);
545                    fm_file = NULL;
546                }
547            }
548            cur_file_name = NULL;
549        }
550        break;
551    case MAPLINE:
552        cur_file_name = NULL;   /* makes luatex_warn() shorter */
553        fm_scan_line();
554        break;
555    default:
556        assert(0);
557    }
558    mitem->line = NULL;         /* done with this line */
559    cur_file_name = NULL;
560    return;
561}
562
563/**********************************************************************/
564
565fm_entry *getfontmap(char *tfm_name)
566{
567    fm_entry *fm;
568    fm_entry tmp;
569    if (tfm_name == NULL)       /* wide, lua loaded fonts may not have a name */
570        return NULL;
571    if (tfm_tree == NULL)
572        fm_read_info();         /* only to read default map file */
573    tmp.tfm_name = tfm_name;    /* Look up for tfmname */
574    fm = (fm_entry *) avl_find(tfm_tree, &tmp);
575    if (fm == NULL)
576        return NULL;
577    set_inuse(fm);
578    return fm;
579}
580
581/**********************************************************************/
582/*
583 * Process map file given by its name or map line contents. Items not
584 * beginning with [+-=] flush default map file, if it has not yet been
585 * read. Leading blanks and blanks immediately following [+-=] are
586 * ignored.
587 */
588
589void process_map_item(char *s, int type)
590{
591    char *p;
592    int mode;
593    if (*s == ' ')
594        s++;                    /* ignore leading blank */
595    switch (*s) {
596    case '+':                  /* +mapfile.map, +mapline */
597        mode = FM_DUPIGNORE;    /* insert entry, if it is not duplicate */
598        s++;
599        break;
600    case '=':                  /* =mapfile.map, =mapline */
601        mode = FM_REPLACE;      /* try to replace earlier entry */
602        s++;
603        break;
604    case '-':                  /* -mapfile.map, -mapline */
605        mode = FM_DELETE;       /* try to delete entry */
606        s++;
607        break;
608    default:
609        mode = FM_DUPIGNORE;    /* like +, but also: */
610        mitem->line = NULL;     /* flush default map file name */
611    }
612    if (*s == ' ')
613        s++;                    /* ignore blank after [+-=] */
614    p = s;                      /* map item starts here */
615    switch (type) {
616    case MAPFILE:              /* remove blank at end */
617        while (*p != '\0' && *p != ' ')
618            p++;
619        *p = '\0';
620        break;
621    case MAPLINE:              /* blank at end allowed */
622        break;
623    default:
624        assert(0);
625    }
626    if (mitem->line != NULL)    /* read default map file first */
627        fm_read_info();
628    if (*s != '\0') {           /* only if real item to process */
629        mitem->mode = mode;
630        mitem->type = type;
631        mitem->line = s;
632        fm_read_info();
633    }
634}
635
636void pdfmapfile(int t)
637{
638    char *s = tokenlist_to_cstring(t, true, NULL);
639    process_map_item(s, MAPFILE);
640    free(s);
641}
642
643void pdfmapline(int t)
644{
645    char *s = tokenlist_to_cstring(t, true, NULL);
646    process_map_item(s, MAPLINE);
647    free(s);
648}
649
650void pdf_init_map_file(char *map_name)
651{
652    assert(mitem == NULL);
653    mitem = xtalloc(1, mapitem);
654    mitem->mode = FM_DUPIGNORE;
655    mitem->type = MAPFILE;
656    mitem->line = map_name;
657}
658
659/**********************************************************************/
660/*
661 * Early check whether a font file exists. Search tree ff_tree is used
662 * in 1st instance, as it may be faster than the kpse_find_file(), and
663 * kpse_find_file() is called only once per font file name + expansion
664 * parameter. This might help keeping speed, if many PDF pages with
665 * same fonts are to be embedded.
666 *
667 * The ff_tree contains only font files, which are actually needed,
668 * so this tree typically is much smaller than the tfm_tree.
669 */
670
671ff_entry *check_ff_exist(char *ff_name, boolean is_tt)
672{
673    ff_entry *ff;
674    ff_entry tmp;
675    void **aa;
676    int callback_id;
677    char *filepath = NULL;
678
679    assert(ff_name != NULL);
680    tmp.ff_name = ff_name;
681    ff = (ff_entry *) avl_find(ff_tree, &tmp);
682    if (ff == NULL) {           /* not yet in database */
683        ff = new_ff_entry();
684        ff->ff_name = xstrdup(ff_name);
685        if (is_tt) {
686            callback_id = callback_defined(find_truetype_file_callback);
687            if (callback_id > 0) {
688                run_callback(callback_id, "S->S", ff_name, &filepath);
689                if (filepath && strlen(filepath) == 0)
690                    filepath = NULL;
691                ff->ff_path = filepath;
692            } else {
693                ff->ff_path = kpse_find_file(ff_name, kpse_truetype_format, 0);
694            }
695        } else {
696            callback_id = callback_defined(find_type1_file_callback);
697            if (callback_id > 0) {
698                run_callback(callback_id, "S->S", ff_name, &filepath);
699                if (filepath && strlen(filepath) == 0)
700                    filepath = NULL;
701                ff->ff_path = filepath;
702            } else {
703                ff->ff_path = kpse_find_file(ff_name, kpse_type1_format, 0);
704            }
705        }
706        aa = avl_probe(ff_tree, ff);
707        assert(aa != NULL);
708    }
709    return ff;
710}
711
712/**********************************************************************/
713
714int is_subsetable(fm_entry * fm)
715{
716    assert(is_included(fm));
717    return is_subsetted(fm);
718}
719
720/**********************************************************************/
721/* cleaning up... */
722
723static void destroy_fm_entry_tfm(void *pa, void *pb)
724{
725    fm_entry *fm;
726    (void) pb;
727    fm = (fm_entry *) pa;
728    delete_fm_entry(fm);
729}
730
731static void destroy_ff_entry(void *pa, void *pb)
732{
733    ff_entry *ff;
734    (void) pb;
735    ff = (ff_entry *) pa;
736    delete_ff_entry(ff);
737}
738
739void fm_free(void)
740{
741    if (tfm_tree != NULL) {
742        avl_destroy(tfm_tree, destroy_fm_entry_tfm);
743        tfm_tree = NULL;
744    }
745    if (ff_tree != NULL) {
746        avl_destroy(ff_tree, destroy_ff_entry);
747        ff_tree = NULL;
748    }
749}
750