summaryrefslogtreecommitdiff
path: root/Build/source/texk/web2c/luatexdir/font/mapfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/web2c/luatexdir/font/mapfile.c')
-rw-r--r--Build/source/texk/web2c/luatexdir/font/mapfile.c770
1 files changed, 770 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/luatexdir/font/mapfile.c b/Build/source/texk/web2c/luatexdir/font/mapfile.c
new file mode 100644
index 00000000000..5ea1e7ca0a2
--- /dev/null
+++ b/Build/source/texk/web2c/luatexdir/font/mapfile.c
@@ -0,0 +1,770 @@
+/*
+
+Copyright 1996-2006 Han The Thanh <thanh@pdftex.org>
+Copyright 2006-2010 Taco Hoekwater <taco@luatex.org>
+
+This file is part of LuaTeX.
+
+LuaTeX is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+LuaTeX is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of the GNU General Public License along
+with LuaTeX; if not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "ptexlib.h"
+#include <math.h>
+#include <kpathsea/c-auto.h>
+#include <kpathsea/c-memstr.h>
+#include <string.h>
+
+#define FM_BUF_SIZE 1024
+
+static FILE *fm_file;
+
+static unsigned char *fm_buffer = NULL;
+static int fm_size = 0;
+static int fm_curbyte = 0;
+
+#define fm_open(a) (fm_file = fopen((char *)(a), FOPEN_RBIN_MODE))
+#define fm_read_file() readbinfile(fm_file,&fm_buffer,&fm_size)
+#define fm_close() xfclose(fm_file, cur_file_name)
+#define fm_getchar() fm_buffer[fm_curbyte++]
+#define fm_eof() (fm_curbyte>fm_size)
+#define is_cfg_comment(c) (c == 10 || c == '*' || c == '#' || c == ';' || c == '%')
+
+typedef enum { FM_DUPIGNORE, FM_REPLACE, FM_DELETE } updatemode;
+
+typedef struct mitem {
+ /*tex |FM_DUPIGNORE| or |FM_REPLACE| or |FM_DELETE| */
+ updatemode mode;
+ /*tex map file or map line */
+ maptype type;
+ /*tex pointer to map file name or map line */
+ char *line;
+ /*tex line number in map file */
+ int lineno;
+} mapitem;
+
+mapitem *mitem = NULL;
+
+#define read_field(r, q, buf) do { \
+ q = buf; \
+ while (*r != ' ' && *r != '<' && *r != '"' && *r != '\0') \
+ *q++ = *r++; \
+ *q = '\0'; \
+ skip_char(r, ' '); \
+} while (0)
+
+#define set_field(F) do { \
+ if (q > buf) \
+ fm->F = xstrdup(buf); \
+ if (*r == '\0') \
+ goto done; \
+} while (0)
+
+fm_entry *new_fm_entry(void)
+{
+ fm_entry *fm;
+ fm = xtalloc(1, fm_entry);
+ fm->tfm_name = NULL;
+ fm->ps_name = NULL;
+ fm->fd_flags = FD_FLAGS_NOT_SET_IN_MAPLINE;
+ fm->ff_name = NULL;
+ fm->encname = NULL;
+ fm->type = 0;
+ fm->slant = 0;
+ fm->extend = 1000;
+ unset_slantset(fm);
+ unset_extendset(fm);
+ unset_inuse(fm);
+ return fm;
+}
+
+void delete_fm_entry(fm_entry * fm)
+{
+ xfree(fm->tfm_name);
+ xfree(fm->ps_name);
+ xfree(fm->ff_name);
+ xfree(fm);
+}
+
+static ff_entry *new_ff_entry(void)
+{
+ ff_entry *ff;
+ ff = xtalloc(1, ff_entry);
+ ff->ff_name = NULL;
+ ff->ff_path = NULL;
+ return ff;
+}
+
+static void delete_ff_entry(ff_entry * ff)
+{
+ xfree(ff->ff_name);
+ xfree(ff->ff_path);
+ xfree(ff);
+}
+
+static struct avl_table *tfm_tree = NULL;
+static struct avl_table *ff_tree = NULL;
+static struct avl_table *encname_tree = NULL;
+
+/*tex
+
+ We sort |fm_entry| into |tfm_tree| by |tfm_name|:
+
+*/
+
+static int comp_fm_entry_tfm(const void *pa, const void *pb, void *p)
+{
+ (void) p;
+ return strcmp(((const fm_entry *) pa)->tfm_name, ((const fm_entry *) pb)->tfm_name);
+}
+
+/* We sort |ff_entry| into |ff_tree| by |ff_name|: */
+
+static int comp_ff_entry(const void *pa, const void *pb, void *p)
+{
+ (void) p;
+ return strcmp(((const ff_entry *) pa)->ff_name, ((const ff_entry *) pb)->ff_name);
+}
+
+static void create_avl_trees(void)
+{
+ tfm_tree = avl_create(comp_fm_entry_tfm, NULL, &avl_xallocator);
+ ff_tree = avl_create(comp_ff_entry, NULL, &avl_xallocator);
+ encname_tree = avl_create(comp_string_entry, NULL, &avl_xallocator);
+}
+
+int avl_do_entry(fm_entry * fm, int mode)
+{
+ fm_entry *p;
+ void *a;
+ void **aa;
+ int delete_new = 0;
+ if (tfm_tree == NULL)
+ create_avl_trees();
+ p = (fm_entry *) avl_find(tfm_tree, fm);
+ if (p != NULL) {
+ switch (mode) {
+ case FM_DUPIGNORE:
+ formatted_warning("map file", "entry for '%s' already exists, duplicates ignored", fm->tfm_name);
+ delete_new = 1;
+ break;
+ case FM_REPLACE:
+ case FM_DELETE:
+ if (is_inuse(p)) {
+ formatted_warning("map file", "entry for '%s' has been used, replace/delete not allowed", fm->tfm_name);
+ delete_new = 1;
+ } else {
+ a = avl_delete(tfm_tree, p);
+ assert(a != NULL);
+ delete_fm_entry(p);
+ }
+ break;
+ default:
+ formatted_error("map file", "something bad happened",0);
+ }
+ }
+ if ((mode == FM_DUPIGNORE || mode == FM_REPLACE) && delete_new == 0) {
+ aa = avl_probe(tfm_tree, fm);
+ if (aa == NULL) {
+ /*tex Is this a problem? */
+ }
+ } else
+ delete_new = 1;
+ return delete_new;
+}
+
+/*tex
+
+ Add the encoding name to an AVL tree. This has nothing to do with |writeenc.c|.
+
+*/
+
+static char *add_encname(char *s)
+{
+ char *p;
+ void **aa;
+ if ((p = (char *) avl_find(encname_tree, s)) == NULL) {
+ /*tex The encoding name has not yet been registered. */
+ p = xstrdup(s);
+ aa = avl_probe(encname_tree, p);
+ if (aa == NULL) {
+ /*tex Is this a problem? */
+ }
+ }
+ return p;
+}
+
+/*tex
+
+ A consistency check for map entry, with warn flag.
+
+*/
+
+static int check_fm_entry(fm_entry * fm, boolean warn)
+{
+ int a = 0;
+ if (is_fontfile(fm) && !is_included(fm)) {
+ if (warn)
+ formatted_warning("map file",
+ "ambiguous entry for '%s': font file present but not included, "
+ "will be treated as font file not present", fm->tfm_name);
+ xfree(fm->ff_name);
+ /*tex Do not set variable |a| as this entry will be still accepted. */
+ }
+ /*tex If both ps_name and font file are missing, drop this entry. */
+ if (fm->ps_name == NULL && !is_fontfile(fm)) {
+ if (warn)
+ formatted_warning("map file", "invalid entry for '%s': both ps_name and font file missing", fm->tfm_name);
+ a += 1;
+ }
+ /*tex \TRUETYPE\ fonts cannot be reencoded without subsetting. */
+ if (is_truetype(fm) && is_reencoded(fm) && !is_subsetted(fm)) {
+ if (warn)
+ formatted_warning("map file", "invalid entry for '%s': only subsetted TrueType font can be reencoded", fm->tfm_name);
+ a += 2;
+ }
+ /*tex The value of |SlantFont| and |ExtendFont| must be reasonable. */
+ if (fm->slant < FONT_SLANT_MIN || fm->slant > FONT_SLANT_MAX) {
+ if (warn)
+ formatted_warning("map file", "invalid entry for '%s': value '%g' is to large for SlantFont",
+ fm->tfm_name, fm->slant / 1000.0);
+ a += 8;
+ }
+ if (fm->extend < FONT_EXTEND_MIN || fm->extend > FONT_EXTEND_MAX) {
+ if (warn)
+ formatted_warning("map file", "invalid entry for '%s': value '%g' is too large for ExtendFont",
+ fm->tfm_name, fm->extend / 1000.0);
+ a += 16;
+ }
+ return a;
+}
+
+/*tex
+
+ Returns the font number if s is one of the 14 std. font names, -1 otherwise.
+ A bit speed trimmed. Using these base fonts is oldfashioned and doesn't
+ happen in a decent \LUATEX\ produced file.
+
+*/
+
+int check_std_t1font(char *s)
+{
+ static const char *std_t1font_names[] = {
+ "Courier", /* 0:7 */
+ "Courier-Bold", /* 1:12 */
+ "Courier-Oblique", /* 2:15 */
+ "Courier-BoldOblique", /* 3:19 */
+ "Helvetica", /* 4:9 */
+ "Helvetica-Bold", /* 5:14 */
+ "Helvetica-Oblique", /* 6:17 */
+ "Helvetica-BoldOblique", /* 7:21 */
+ "Symbol", /* 8:6 */
+ "Times-Roman", /* 9:11 */
+ "Times-Bold", /* 10:10 */
+ "Times-Italic", /* 11:12 */
+ "Times-BoldItalic", /* 12:16 */
+ "ZapfDingbats" /* 13:12 */
+ };
+ static const int index[] = {
+ -1, -1, -1, -1, -1, -1, 8, 0, -1, 4, 10,
+ 9, -1, -1, 5, 2, 12, 6, -1, 3, -1, 7
+ };
+ size_t n;
+ int k = -1;
+ n = strlen(s);
+ if (n > 21)
+ return -1;
+ if (n == 12) {
+ /*tex three names have length 12 */
+ switch (*s) {
+ case 'C':
+ /*tex Courier-Bold */
+ k = 1;
+ break;
+ case 'T':
+ /*tex Times-Italic */
+ k = 11;
+ break;
+ case 'Z':
+ /*tex ZapfDingbats */
+ k = 13;
+ break;
+ default:
+ return -1;
+ }
+ } else
+ k = index[n];
+ if (k > -1 && !strcmp(std_t1font_names[k], s))
+ return k;
+ return -1;
+}
+
+static void fm_scan_line(void)
+{
+ int a, b, c, j, u = 0, v = 0;
+ char cc;
+ float d;
+ fm_entry *fm;
+ char fm_line[FM_BUF_SIZE], buf[FM_BUF_SIZE];
+ char *p, *q, *s;
+ char *r = NULL;
+ switch (mitem->type) {
+ case MAPFILE:
+ p = fm_line;
+ while (!fm_eof()) {
+ if (fm_curbyte == fm_size) {
+ fm_curbyte++;
+ cc = 10;
+ } else {
+ cc = (char) fm_getchar();
+ }
+ append_char_to_buf(cc, p, fm_line, FM_BUF_SIZE);
+ if (cc == 10)
+ break;
+ }
+ *(--p) = '\0';
+ r = fm_line;
+ break;
+ case MAPLINE:
+ /*tex Work on a string from |makecstring|. */
+ r = mitem->line;
+ break;
+ default:
+ assert(0);
+ }
+ if (*r == '\0' || is_cfg_comment(*r))
+ return;
+ fm = new_fm_entry();
+ read_field(r, q, buf);
+ set_field(tfm_name);
+ if (!isdigit((unsigned char)*r)) {
+ /*tex The 2nd field |ps_name| may not start with a digit. */
+ read_field(r, q, buf);
+ set_field(ps_name);
+ }
+ if (isdigit((unsigned char)*r)) {
+ /*tex Is the font descriptor |/Flags| given? */
+ for (s = r; isdigit((unsigned char)*s); s++);
+ if (*s == ' ' || *s == '"' || *s == '<' || *s == '\0') {
+ /*tex not e.g.\ |8r.enc| */
+ fm->fd_flags = atoi(r);
+ while (isdigit((unsigned char)*r))
+ r++;
+ }
+ }
+ /*tex Loop through specials, encoding, font file:*/
+ while (1) {
+ skip_char(r, ' ');
+ switch (*r) {
+ case '\0':
+ goto done;
+ case '"':
+ /*tex The pening quote. */
+ r++;
+ u = v = 0;
+ do {
+ skip_char(r, ' ');
+ if (sscanf(r, "%f %n", &d, &j) > 0) {
+ /*tex Jump behind number, eat also blanks, if any. */
+ s = r + j;
+ if (*(s - 1) == 'E' || *(s - 1) == 'e') {
+ /*tex e.g.\ |0.5ExtendFont|: |%f = 0.5E| */
+ s--;
+ }
+ if (str_prefix(s, "SlantFont")) {
+ /*tex Correct rounding also for negative numbers. */
+ d *= (float) 1000.0;
+ fm->slant = (int) (d > 0 ? d + 0.5 : d - 0.5);
+ set_slantset(fm);
+ r = s + strlen("SlantFont");
+ } else if (str_prefix(s, "ExtendFont")) {
+ d *= (float) 1000.0;
+ fm->extend = (int) (d > 0 ? d + 0.5 : d - 0.5);
+ set_extendset(fm);
+ r = s + strlen("ExtendFont");
+ } else {
+ /*tex unknown name, jump over it */
+ for (r = s; *r != ' ' && *r != '"' && *r != '\0'; r++);
+ c = *r;
+ *r = '\0';
+ formatted_warning("map file", "invalid entry for '%s': unknown name '%s' ignored", fm->tfm_name, s);
+ *r = (char) c;
+ }
+ } else
+ for (; *r != ' ' && *r != '"' && *r != '\0'; r++);
+ }
+ while (*r == ' ');
+ if (*r == '"') {
+ /*tex The closing quote. */
+ r++;
+ } else {
+ formatted_warning("map file", "invalid entry for '%s': closing quote missing", fm->tfm_name);
+ goto bad_line;
+ }
+ break;
+ case 'P':
+ /*tex Handle cases for sub fonts like |PidEid=3,1| */
+ formatted_warning("map file", "invalid entry for '%s': subfonts are not supported", fm->tfm_name);
+ goto bad_line;
+ break;
+ default:
+ /*tex Encoding or font file specification. */
+ a = b = 0;
+ if (*r == '<') {
+ a = *r++;
+ if (*r == '<' || *r == '[')
+ b = *r++;
+ }
+ read_field(r, q, buf);
+ /*tex Encoding, Formats: |8r.enc| or |<8r.enc| or |<[8r.enc| */
+ if (strlen(buf) > 4 && strcasecmp(strend(buf) - 4, ".enc") == 0) {
+ /*tex |u|, |v| used if intervening blank: |<< foo| */
+ fm->encname = add_encname(buf);
+ u = v = 0;
+ } else if (strlen(buf) > 0) {
+ /*tex
+
+ We get the file name given where possible formats are:
+
+ \starttabulate[|||]
+ \NC subsetting \NC \tpe {<cmr10.pfa} \NC \NR
+ \NC no subsetting \NC \tpe {<<cmr10.pfa} \NC \NR
+ \NC no embedding \NC \tpe {cmr10.pfa} \NC \NR
+ \stoptabulate
+
+ */
+ if (a == '<' || u == '<') {
+ set_included(fm);
+ if ((a == '<' && b == 0) || (a == 0 && v == 0)) {
+ set_subsetted(fm);
+ } else {
+ /*tex
+
+ Otherwise |b| equals |<| or |[| which means that we have no subsetting.
+ */
+ }
+ }
+ set_field(ff_name);
+ u = v = 0;
+ } else {
+ u = a;
+ v = b;
+ }
+ }
+ }
+ done:
+ if (fm->ps_name != NULL && (check_std_t1font(fm->ps_name) >= 0))
+ set_std_t1font(fm);
+ if (is_fontfile(fm) && strlen(fm_fontfile(fm)) > 3) {
+ if (strcasecmp(strend(fm_fontfile(fm)) - 4, ".ttf") == 0)
+ set_truetype(fm);
+ else if (strcasecmp(strend(fm_fontfile(fm)) - 4, ".ttc") == 0)
+ set_truetype(fm);
+ else if (strcasecmp(strend(fm_fontfile(fm)) - 4, ".otf") == 0)
+ set_opentype(fm);
+ else
+ set_type1(fm);
+ } else {
+ /*tex Assume a builtin font is \TYPEONE\: */
+ set_type1(fm);
+ }
+ if (check_fm_entry(fm, true) != 0)
+ goto bad_line;
+ /*tex
+
+ Until here the map line has been completely scanned without errors; |fm|
+ points to a valid, freshly filled-out |fm_entry| structure. Now follows
+ the actual work of registering or deleting.
+
+ */
+ if (avl_do_entry(fm, mitem->mode) == 0)
+ return;
+ bad_line:
+ delete_fm_entry(fm);
+}
+
+static void fm_read_info(void)
+{
+ int callback_id;
+ int file_opened = 0;
+
+ if (tfm_tree == NULL)
+ create_avl_trees();
+ if (mitem->line == NULL) {
+ /*tex There is nothing to do. */
+ return;
+ }
+ mitem->lineno = 1;
+ switch (mitem->type) {
+ case MAPFILE:
+ xfree(fm_buffer);
+ fm_curbyte = 0;
+ fm_size = 0;
+ cur_file_name = luatex_find_file(mitem->line, find_map_file_callback);
+ if (cur_file_name) {
+ callback_id = callback_defined(read_map_file_callback);
+ if (callback_id > 0) {
+ if (run_callback(callback_id, "S->bSd", cur_file_name,
+ &file_opened, &fm_buffer, &fm_size)) {
+ if (file_opened) {
+ if (fm_size > 0) {
+ report_start_file(filetype_map,cur_file_name);
+ while (!fm_eof()) {
+ fm_scan_line();
+ mitem->lineno++;
+ }
+ report_stop_file(filetype_map);
+ fm_file = NULL;
+ }
+ } else {
+ formatted_warning("map file", "cannot open font map file '%s'", cur_file_name);
+ }
+ } else {
+ formatted_warning("map file", "cannot open font map file '%s'", cur_file_name);
+ }
+ } else {
+ if (!fm_open(cur_file_name)) {
+ formatted_warning("map file", "cannot open font map file '%s'", cur_file_name);
+ } else {
+ fm_read_file();
+ report_start_file(filetype_map,cur_file_name);
+ while (!fm_eof()) {
+ fm_scan_line();
+ mitem->lineno++;
+ }
+ fm_close();
+ report_stop_file(filetype_map);
+ fm_file = NULL;
+ }
+ }
+ cur_file_name = NULL;
+ }
+ break;
+ case MAPLINE:
+ cur_file_name = NULL;
+ fm_scan_line();
+ break;
+ default:
+ assert(0);
+ }
+ /*tex Done with this line: */
+ mitem->line = NULL;
+ cur_file_name = NULL;
+ return;
+}
+
+fm_entry *getfontmap(char *tfm_name)
+{
+ fm_entry *fm;
+ fm_entry tmp;
+ if (tfm_name == NULL) {
+ /*tex Wide \LUA\ loaded fonts may not have a name. */
+ return NULL;
+ }
+ if (tfm_tree == NULL) {
+ /*tex Only read the default map file. */
+ fm_read_info();
+ }
+ /*tex Look up the name. */
+ tmp.tfm_name = tfm_name;
+ fm = (fm_entry *) avl_find(tfm_tree, &tmp);
+ if (fm == NULL)
+ return NULL;
+ set_inuse(fm);
+ return fm;
+}
+
+/*tex
+
+ Process map file given by its name or map line contents. Items not beginning
+ with [+-=] flush default map file, if it has not yet been read. Leading
+ blanks and blanks immediately following [+-=] are ignored.
+
+*/
+
+void process_map_item(char *s, int type)
+{
+ char *p;
+ int mode;
+ if (*s == ' ') {
+ /*tex Ignore leading blanks: */
+ s++;
+ }
+ switch (*s) {
+ case '+':
+ /* +mapfile.map, +mapline: insert an entry, if it is not duplicate */
+ mode = FM_DUPIGNORE;
+ s++;
+ break;
+ case '=':
+ /* =mapfile.map, =mapline: try to replace an existing entry */
+ mode = FM_REPLACE;
+ s++;
+ break;
+ case '-':
+ /* -mapfile.map, -mapline: try to delete an entry */
+ mode = FM_DELETE;
+ s++;
+ break;
+ default:
+ /* like +, but also: flush the default map file name */
+ mode = FM_DUPIGNORE;
+ mitem->line = NULL;
+ }
+ if (*s == ' ') {
+ /*tex Ignore a blank after |[+-=]| */
+ s++;
+ }
+ /*tex The map item starts here. */
+ p = s;
+ switch (type) {
+ case MAPFILE:
+ /*tex Remove blank at the end. */
+ while (*p != '\0' && *p != ' ')
+ p++;
+ *p = '\0';
+ break;
+ case MAPLINE:
+ /*tex A blank at end is allowed. */
+ break;
+ default:
+ assert(0);
+ }
+ if (mitem->line != NULL) {
+ /*tex Read default map file first */
+ fm_read_info();
+ }
+ if (*s != '\0') {
+ /*tex Only if real item to process. */
+ mitem->mode = mode;
+ mitem->type = type;
+ mitem->line = s;
+ fm_read_info();
+ }
+}
+
+void pdfmapfile(int t)
+{
+ char *s = tokenlist_to_cstring(t, true, NULL);
+ process_map_item(s, MAPFILE);
+ free(s);
+}
+
+void pdfmapline(int t)
+{
+ char *s = tokenlist_to_cstring(t, true, NULL);
+ process_map_item(s, MAPLINE);
+ free(s);
+}
+
+void pdf_init_map_file(const char *map_name)
+{
+ assert(mitem == NULL);
+ mitem = xtalloc(1, mapitem);
+ mitem->mode = FM_DUPIGNORE;
+ mitem->type = MAPFILE;
+ mitem->line = xstrdup(map_name);
+}
+
+/*tex
+
+ An early check whether a font file exists. Search tree |ff_tree| is used in
+ 1st instance, as it may be faster than the |kpse_find_file|, and
+ |kpse_find_file| is called only once per font file name plus expansion
+ parameter. This might help keeping speed, if many \PDF\ pages with same fonts
+ are to be embedded (not that we deal with that fragile approach any longer in
+ \LUATEX).
+
+ The |ff_tree| contains only font files, which are actually needed, so this tree
+ typically is much smaller than the tfm_tree.
+
+*/
+
+ff_entry *check_ff_exist(char *ff_name, boolean is_tt)
+{
+ ff_entry *ff;
+ ff_entry tmp;
+ void **aa;
+ int callback_id;
+ char *filepath = NULL;
+ tmp.ff_name = ff_name;
+ ff = (ff_entry *) avl_find(ff_tree, &tmp);
+ if (ff == NULL) {
+ /*tex The name is not yet in the database. */
+ ff = new_ff_entry();
+ ff->ff_name = xstrdup(ff_name);
+ if (is_tt) {
+ callback_id = callback_defined(find_truetype_file_callback);
+ if (callback_id > 0) {
+ run_callback(callback_id, "S->S", ff_name, &filepath);
+ if (filepath && strlen(filepath) == 0)
+ filepath = NULL;
+ ff->ff_path = filepath;
+ } else {
+ ff->ff_path = kpse_find_file(ff_name, kpse_truetype_format, 0);
+ }
+ } else {
+ callback_id = callback_defined(find_type1_file_callback);
+ if (callback_id > 0) {
+ run_callback(callback_id, "S->S", ff_name, &filepath);
+ if (filepath && strlen(filepath) == 0)
+ filepath = NULL;
+ ff->ff_path = filepath;
+ } else {
+ ff->ff_path = kpse_find_file(ff_name, kpse_type1_format, 0);
+ }
+ }
+ aa = avl_probe(ff_tree, ff);
+ if (aa == NULL) {
+ /*tex Is this a problem? */
+ }
+ }
+ return ff;
+}
+
+int is_subsetable(fm_entry * fm)
+{
+ assert(is_included(fm));
+ return is_subsetted(fm);
+}
+
+/*tex Cleaning up: */
+
+static void destroy_fm_entry_tfm(void *pa, void *pb)
+{
+ fm_entry *fm;
+ (void) pb;
+ fm = (fm_entry *) pa;
+ delete_fm_entry(fm);
+}
+
+static void destroy_ff_entry(void *pa, void *pb)
+{
+ ff_entry *ff;
+ (void) pb;
+ ff = (ff_entry *) pa;
+ delete_ff_entry(ff);
+}
+
+void fm_free(void)
+{
+ if (tfm_tree != NULL) {
+ avl_destroy(tfm_tree, destroy_fm_entry_tfm);
+ tfm_tree = NULL;
+ }
+ if (ff_tree != NULL) {
+ avl_destroy(ff_tree, destroy_ff_entry);
+ ff_tree = NULL;
+ }
+}