/***************************************************************************/ /* */ /* ftcmanag.c */ /* */ /* FreeType Cache Manager (body). */ /* */ /* Copyright 2000-2001, 2002 by */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, LICENSE.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ #include #include FT_CACHE_H #include FT_CACHE_MANAGER_H #include FT_CACHE_INTERNAL_LRU_H #include FT_INTERNAL_OBJECTS_H #include FT_INTERNAL_DEBUG_H #include FT_SIZES_H #include "ftcerror.h" #undef FT_COMPONENT #define FT_COMPONENT trace_cache #define FTC_LRU_GET_MANAGER( lru ) ( (FTC_Manager)(lru)->user_data ) /*************************************************************************/ /*************************************************************************/ /***** *****/ /***** FACE LRU IMPLEMENTATION *****/ /***** *****/ /*************************************************************************/ /*************************************************************************/ typedef struct FTC_FaceNodeRec_* FTC_FaceNode; typedef struct FTC_SizeNodeRec_* FTC_SizeNode; typedef struct FTC_FaceNodeRec_ { FT_LruNodeRec lru; FT_Face face; } FTC_FaceNodeRec; typedef struct FTC_SizeNodeRec_ { FT_LruNodeRec lru; FT_Size size; } FTC_SizeNodeRec; FT_CALLBACK_DEF( FT_Error ) ftc_face_node_init( FTC_FaceNode node, FTC_FaceID face_id, FTC_Manager manager ) { FT_Error error; error = manager->request_face( face_id, manager->library, manager->request_data, &node->face ); if ( !error ) { /* destroy initial size object; it will be re-created later */ if ( node->face->size ) FT_Done_Size( node->face->size ); } return error; } /* helper function for ftc_face_node_done() */ FT_CALLBACK_DEF( FT_Bool ) ftc_size_node_select( FTC_SizeNode node, FT_Face face ) { return FT_BOOL( node->size->face == face ); } FT_CALLBACK_DEF( void ) ftc_face_node_done( FTC_FaceNode node, FTC_Manager manager ) { FT_Face face = node->face; /* we must begin by removing all sizes for the target face */ /* from the manager's list */ FT_LruList_Remove_Selection( manager->sizes_list, (FT_LruNode_SelectFunc)ftc_size_node_select, face ); /* all right, we can discard the face now */ FT_Done_Face( face ); node->face = NULL; } FT_CALLBACK_TABLE_DEF const FT_LruList_ClassRec ftc_face_list_class = { sizeof ( FT_LruListRec ), (FT_LruList_InitFunc)0, (FT_LruList_DoneFunc)0, sizeof ( FTC_FaceNodeRec ), (FT_LruNode_InitFunc) ftc_face_node_init, (FT_LruNode_DoneFunc) ftc_face_node_done, (FT_LruNode_FlushFunc) 0, /* no flushing needed */ (FT_LruNode_CompareFunc)0, /* direct comparison of FTC_FaceID handles */ }; /* documentation is in ftcache.h */ FT_EXPORT_DEF( FT_Error ) FTC_Manager_Lookup_Face( FTC_Manager manager, FTC_FaceID face_id, FT_Face *aface ) { FT_Error error; FTC_FaceNode node; if ( aface == NULL ) return FTC_Err_Bad_Argument; *aface = NULL; if ( !manager ) return FTC_Err_Invalid_Cache_Handle; error = FT_LruList_Lookup( manager->faces_list, (FT_LruKey)face_id, (FT_LruNode*)&node ); if ( !error ) *aface = node->face; return error; } /*************************************************************************/ /*************************************************************************/ /***** *****/ /***** SIZES LRU IMPLEMENTATION *****/ /***** *****/ /*************************************************************************/ /*************************************************************************/ typedef struct FTC_SizeQueryRec_ { FT_Face face; FT_UInt width; FT_UInt height; } FTC_SizeQueryRec, *FTC_SizeQuery; FT_CALLBACK_DEF( FT_Error ) ftc_size_node_init( FTC_SizeNode node, FTC_SizeQuery query ) { FT_Face face = query->face; FT_Size size; FT_Error error; node->size = NULL; error = FT_New_Size( face, &size ); if ( !error ) { FT_Activate_Size( size ); error = FT_Set_Pixel_Sizes( query->face, query->width, query->height ); if ( error ) FT_Done_Size( size ); else node->size = size; } return error; } FT_CALLBACK_DEF( void ) ftc_size_node_done( FTC_SizeNode node ) { if ( node->size ) { FT_Done_Size( node->size ); node->size = NULL; } } FT_CALLBACK_DEF( FT_Error ) ftc_size_node_flush( FTC_SizeNode node, FTC_SizeQuery query ) { FT_Size size = node->size; FT_Error error; if ( size->face == query->face ) { FT_Activate_Size( size ); error = FT_Set_Pixel_Sizes( query->face, query->width, query->height ); if ( error ) { FT_Done_Size( size ); node->size = NULL; } } else { FT_Done_Size( size ); node->size = NULL; error = ftc_size_node_init( node, query ); } return error; } FT_CALLBACK_DEF( FT_Bool ) ftc_size_node_compare( FTC_SizeNode node, FTC_SizeQuery query ) { FT_Size size = node->size; return FT_BOOL( size->face == query->face && (FT_UInt)size->metrics.x_ppem == query->width && (FT_UInt)size->metrics.y_ppem == query->height ); } FT_CALLBACK_TABLE_DEF const FT_LruList_ClassRec ftc_size_list_class = { sizeof ( FT_LruListRec ), (FT_LruList_InitFunc)0, (FT_LruList_DoneFunc)0, sizeof ( FTC_SizeNodeRec ), (FT_LruNode_InitFunc) ftc_size_node_init, (FT_LruNode_DoneFunc) ftc_size_node_done, (FT_LruNode_FlushFunc) ftc_size_node_flush, (FT_LruNode_CompareFunc)ftc_size_node_compare }; /* documentation is in ftcache.h */ FT_EXPORT_DEF( FT_Error ) FTC_Manager_Lookup_Size( FTC_Manager manager, FTC_Font font, FT_Face *aface, FT_Size *asize ) { FT_Error error; /* check for valid `manager' delayed to FTC_Manager_Lookup_Face() */ if ( aface ) *aface = 0; if ( asize ) *asize = 0; error = FTC_Manager_Lookup_Face( manager, font->face_id, aface ); if ( !error ) { FTC_SizeQueryRec query; FTC_SizeNode node; query.face = *aface; query.width = font->pix_width; query.height = font->pix_height; error = FT_LruList_Lookup( manager->sizes_list, (FT_LruKey)&query, (FT_LruNode*)&node ); if ( !error ) { /* select the size as the current one for this face */ FT_Activate_Size( node->size ); if ( asize ) *asize = node->size; } } return error; } /*************************************************************************/ /*************************************************************************/ /***** *****/ /***** SET TABLE MANAGEMENT *****/ /***** *****/ /*************************************************************************/ /*************************************************************************/ static void ftc_family_table_init( FTC_FamilyTable table ) { table->count = 0; table->size = 0; table->entries = NULL; table->free = FTC_FAMILY_ENTRY_NONE; } static void ftc_family_table_done( FTC_FamilyTable table, FT_Memory memory ) { FT_FREE( table->entries ); table->free = 0; table->count = 0; table->size = 0; } FT_EXPORT_DEF( FT_Error ) ftc_family_table_alloc( FTC_FamilyTable table, FT_Memory memory, FTC_FamilyEntry *aentry ) { FTC_FamilyEntry entry; FT_Error error = 0; /* re-allocate table size when needed */ if ( table->free == FTC_FAMILY_ENTRY_NONE && table->count >= table->size ) { FT_UInt old_size = table->size; FT_UInt new_size, idx; if ( old_size == 0 ) new_size = 8; else { new_size = old_size * 2; /* check for (unlikely) overflow */ if ( new_size < old_size ) new_size = 65534; } if ( FT_RENEW_ARRAY( table->entries, old_size, new_size ) ) return error; table->size = new_size; entry = table->entries + old_size; table->free = old_size; for ( idx = old_size; idx + 1 < new_size; idx++, entry++ ) { entry->link = idx + 1; entry->index = idx; } entry->link = FTC_FAMILY_ENTRY_NONE; entry->index = idx; } if ( table->free != FTC_FAMILY_ENTRY_NONE ) { entry = table->entries + table->free; table->free = entry->link; } else if ( table->count < table->size ) { entry = table->entries + table->count++; } else { FT_ERROR(( "ftc_family_table_alloc: internal bug!" )); return FTC_Err_Invalid_Argument; } entry->link = FTC_FAMILY_ENTRY_NONE; table->count++; *aentry = entry; return error; } FT_EXPORT_DEF( void ) ftc_family_table_free( FTC_FamilyTable table, FT_UInt idx ) { /* simply add it to the linked list of free entries */ if ( idx < table->count ) { FTC_FamilyEntry entry = table->entries + idx; if ( entry->link != FTC_FAMILY_ENTRY_NONE ) FT_ERROR(( "ftc_family_table_free: internal bug!\n" )); else { entry->link = table->free; table->free = entry->index; table->count--; } } } /*************************************************************************/ /*************************************************************************/ /***** *****/ /***** CACHE MANAGER ROUTINES *****/ /***** *****/ /*************************************************************************/ /*************************************************************************/ /* documentation is in ftcache.h */ FT_EXPORT_DEF( FT_Error ) FTC_Manager_New( FT_Library library, FT_UInt max_faces, FT_UInt max_sizes, FT_ULong max_bytes, FTC_Face_Requester requester, FT_Pointer req_data, FTC_Manager *amanager ) { FT_Error error; FT_Memory memory; FTC_Manager manager = 0; if ( !library ) return FTC_Err_Invalid_Library_Handle; memory = library->memory; if ( FT_NEW( manager ) ) goto Exit; if ( max_faces == 0 ) max_faces = FTC_MAX_FACES_DEFAULT; if ( max_sizes == 0 ) max_sizes = FTC_MAX_SIZES_DEFAULT; if ( max_bytes == 0 ) max_bytes = FTC_MAX_BYTES_DEFAULT; error = FT_LruList_New( &ftc_face_list_class, max_faces, manager, memory, &manager->faces_list ); if ( error ) goto Exit; error = FT_LruList_New( &ftc_size_list_class, max_sizes, manager, memory, &manager->sizes_list ); if ( error ) goto Exit; manager->library = library; manager->max_weight = max_bytes; manager->cur_weight = 0; manager->request_face = requester; manager->request_data = req_data; ftc_family_table_init( &manager->families ); *amanager = manager; Exit: if ( error && manager ) { FT_LruList_Destroy( manager->faces_list ); FT_LruList_Destroy( manager->sizes_list ); FT_FREE( manager ); } return error; } /* documentation is in ftcache.h */ FT_EXPORT_DEF( void ) FTC_Manager_Done( FTC_Manager manager ) { FT_Memory memory; FT_UInt idx; if ( !manager || !manager->library ) return; memory = manager->library->memory; /* now discard all caches */ for (idx = 0; idx < FTC_MAX_CACHES; idx++ ) { FTC_Cache cache = manager->caches[idx]; if ( cache ) { cache->clazz->cache_done( cache ); FT_FREE( cache ); manager->caches[idx] = 0; } } /* discard families table */ ftc_family_table_done( &manager->families, memory ); /* discard faces and sizes */ FT_LruList_Destroy( manager->faces_list ); manager->faces_list = 0; FT_LruList_Destroy( manager->sizes_list ); manager->sizes_list = 0; FT_FREE( manager ); } /* documentation is in ftcache.h */ FT_EXPORT_DEF( void ) FTC_Manager_Reset( FTC_Manager manager ) { if ( manager ) { FT_LruList_Reset( manager->sizes_list ); FT_LruList_Reset( manager->faces_list ); } /* XXX: FIXME: flush the caches? */ } #ifdef FT_DEBUG_ERROR FT_EXPORT_DEF( void ) FTC_Manager_Check( FTC_Manager manager ) { FTC_Node node, first; first = manager->nodes_list; /* check node weights */ if ( first ) { FT_ULong weight = 0; node = first; do { FTC_FamilyEntry entry = manager->families.entries + node->fam_index; FTC_Cache cache; if ( (FT_UInt)node->fam_index >= manager->families.count || entry->link != FTC_FAMILY_ENTRY_NONE ) FT_ERROR(( "FTC_Manager_Check: invalid node (family index = %ld\n", node->fam_index )); else { cache = entry->cache; weight += cache->clazz->node_weight( node, cache ); } node = node->mru_next; } while ( node != first ); if ( weight != manager->cur_weight ) FT_ERROR(( "FTC_Manager_Check: invalid weight %ld instead of %ld\n", manager->cur_weight, weight )); } /* check circular list */ if ( first ) { FT_UFast count = 0; node = first; do { count++; node = node->mru_next; } while ( node != first ); if ( count != manager->num_nodes ) FT_ERROR(( "FTC_Manager_Check: invalid cache node count %d instead of %d\n", manager->num_nodes, count )); } } #endif /* FT_DEBUG_ERROR */ /* `Compress' the manager's data, i.e., get rid of old cache nodes */ /* that are not referenced anymore in order to limit the total */ /* memory used by the cache. */ /* documentation is in ftcmanag.h */ FT_EXPORT_DEF( void ) FTC_Manager_Compress( FTC_Manager manager ) { FTC_Node node, first; if ( !manager ) return; first = manager->nodes_list; #ifdef FT_DEBUG_ERROR FTC_Manager_Check( manager ); FT_ERROR(( "compressing, weight = %ld, max = %ld, nodes = %d\n", manager->cur_weight, manager->max_weight, manager->num_nodes )); #endif if ( manager->cur_weight < manager->max_weight || first == NULL ) return; /* go to last node - it's a circular list */ node = first->mru_prev; do { FTC_Node prev = node->mru_prev; prev = ( node == first ) ? NULL : node->mru_prev; if ( node->ref_count <= 0 ) ftc_node_destroy( node, manager ); node = prev; } while ( node && manager->cur_weight > manager->max_weight ); } /* documentation is in ftcmanag.h */ FT_EXPORT_DEF( FT_Error ) FTC_Manager_Register_Cache( FTC_Manager manager, FTC_Cache_Class clazz, FTC_Cache *acache ) { FT_Error error = FTC_Err_Invalid_Argument; FTC_Cache cache = NULL; if ( manager && clazz && acache ) { FT_Memory memory = manager->library->memory; FT_UInt idx = 0; /* check for an empty cache slot in the manager's table */ for ( idx = 0; idx < FTC_MAX_CACHES; idx++ ) { if ( manager->caches[idx] == 0 ) break; } /* return an error if there are too many registered caches */ if ( idx >= FTC_MAX_CACHES ) { error = FTC_Err_Too_Many_Caches; FT_ERROR(( "FTC_Manager_Register_Cache:" )); FT_ERROR(( " too many registered caches\n" )); goto Exit; } if ( !FT_ALLOC( cache, clazz->cache_size ) ) { cache->manager = manager; cache->memory = memory; cache->clazz = clazz; /* THIS IS VERY IMPORTANT! IT WILL WRETCH THE MANAGER */ /* IF IT IS NOT SET CORRECTLY */ cache->cache_index = idx; if ( clazz->cache_init ) { error = clazz->cache_init( cache ); if ( error ) { if ( clazz->cache_done ) clazz->cache_done( cache ); FT_FREE( cache ); goto Exit; } } manager->caches[idx] = cache; } } Exit: *acache = cache; return error; } /* documentation is in ftcmanag.h */ FT_EXPORT_DEF( void ) FTC_Node_Unref( FTC_Node node, FTC_Manager manager ) { if ( node && (FT_UInt)node->fam_index < manager->families.count && manager->families.entries[node->fam_index].cache ) { node->ref_count--; } } /* END */