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