Hi all, During the new year holiday, I was trying to introduce per-site limiter of memory allocation to memory debugger module in FreeType2. I think it might be useful for the application programmers to reproduce the out-of-memory error by specific scenario. If you're interested in, please give me comment about the current design.
The patch is attached: mps20110107-ft2_limit-alloc-per-site.diff -------------------------------------------------------------------- * What is this? Current FreeType2 memory debugger provides 2 interfaces to restrict the memory allocator; FT2_ALLOC_TOTAL_MAX (historical summation of allocation, free() is not considered) and FT2_ALLOC_COUNT_MAX (how many times allocation is requested). Their restriction is for whole of FT2 library, so it is difficult to make an OOM error occur at specific site. During the debug for Savannah bug#31923, I wanted to make OOM error in ftc_sbit_copy_bitmap(). Attached patch introduces 2 interfaces to define the per-site limiter of memory allocation; FT2_ALLOC_TOTAL_MAX_SITE and FT2_ALLOC_CUR_MAX_SITE. FT2_ALLOC_TOTAL_MAX_SITE is a per-site version of FT2_ALLOC_TOTAL_MAX. FT2_ALLOC_CUR_MAX_SITE is a modified version of FT2_TOTAL_MAX_SITE to consider the effect of free(). * Basic syntax of the environmental variables to control per-site limiter As FT2_DEBUG is a space-separated list, new interfaces are same. A limiter for a specific site is described by a pair of site-specifier and the max buffer size allocated by the site. <limiter> ::= <a_site>,<max_buffer_size> <site> ::= <pathname>:<line_number> <pathname> ::= free string <line_number> ::= 32-bit integer <max_buffer_size> ::= 32-bit integer and FT2_ALLOC_{TOTAL,CUR}_MAX_SITE should be set as a space-separated list of the limiters. <limiters> ::= <limiter> | <limiter> <limiters> For example, FT2_ALLOC_CUR_MAX_SITE="/foo/bah/aaa.c:10,1000 /foo/bah/bbb.c:21,10000" * More easier syntax is required? The site specifier is embedded by existing memory debugger. You can the summary report when you set FT2_DEBUG_MEMORY environment, like this: FreeType Memory Dump: current=0 max=1360517 total=3047988 count=0 block block sizes sizes sizes source count high sum highsum max location ------------------------------------------------- 0 6879 0 385224 56 /home/mpsuzuki/redhat/BUILD/freetype2-current/freetype2/src/base/ftobjs.c:302 0 6878 0 385168 56 /home/mpsuzuki/redhat/BUILD/freetype2-current/freetype2/src/cache/ftcsbits.c:57 0 6878 0 357656 52 /home/mpsuzuki/redhat/BUILD/freetype2-current/freetype2/src/base/ftglyph.c:290 0 6878 0 220096 32 /home/mpsuzuki/redhat/BUILD/freetype2-current/freetype2/src/cache/ftcimage.c:67 0 430 0 123840 288 /home/mpsuzuki/redhat/BUILD/freetype2-current/freetype2/src/cache/ftcsbits.c:232 ... Setting FT2_ALLOC_CUR_MAX_SITE with absolute pathname like "/home/mpsuzuki/redhat/BUILD/freetype2-current/freetype2/src/base/ftobjs.c:302,1000 ..." is lengthy. I want to specify by basename like "ftobjs.c:302,1000 ...". But FreeType2 is a cross-platform library assuming only C89 and we should not expect the function like basename(). ft2demos has already it, slightly modified version is written in another patch; mps20110107-ft2_basename.diff. Here if libgen.h is available, native basename() is used. Also raw slash in resource fork accessor is replaced by new macro PLATFORM_DIR_SEPARATOR. * Future issues Some people may request a feature to define the limiter with more fine-tuned scenario, like "per specific stackframe". For example, if there is a generic function calling allocater and the function is called by many places, the site specification by the position where the allocator is invoked is not enough to set fine-tuned timebomb. I was trying to add a feature to define a limiter by the range of code, catching all allocation from the range. The patch was unfinished, if you're interested in, please refer the patch uploaded on Savannah bug#31923. Regards, mpsuzuki
diff --git a/include/freetype/config/ftstdlib.h b/include/freetype/config/ftstdlib.h index 30ec14e..db6b265 100644 --- a/include/freetype/config/ftstdlib.h +++ b/include/freetype/config/ftstdlib.h @@ -81,6 +81,7 @@ #define ft_memmove memmove #define ft_memset memset #define ft_strcat strcat +#define ft_strchr strchr #define ft_strcmp strcmp #define ft_strcpy strcpy #define ft_strlen strlen diff --git a/include/freetype/internal/fttrace.h b/include/freetype/internal/fttrace.h index e9b383a..5d2decc 100644 --- a/include/freetype/internal/fttrace.h +++ b/include/freetype/internal/fttrace.h @@ -24,6 +24,7 @@ FT_TRACE_DEF( any ) /* base components */ FT_TRACE_DEF( calc ) /* calculations (ftcalc.c) */ FT_TRACE_DEF( memory ) /* memory manager (ftobjs.c) */ +FT_TRACE_DEF( dbgmem ) /* memory debugger (ftdbgmem.c) */ FT_TRACE_DEF( stream ) /* stream manager (ftstream.c) */ FT_TRACE_DEF( io ) /* i/o interface (ftsystem.c) */ FT_TRACE_DEF( list ) /* list management (ftlist.c) */ diff --git a/src/base/ftdbgmem.c b/src/base/ftdbgmem.c index 12fed04..bb04f68 100644 --- a/src/base/ftdbgmem.c +++ b/src/base/ftdbgmem.c @@ -25,6 +25,10 @@ #include FT_TYPES_H +#undef FT_COMPONENT +#define FT_COMPONENT trace_dbgmem + + #ifdef FT_DEBUG_MEMORY #define KEEPALIVE /* `Keep alive' means that freed blocks aren't released @@ -505,6 +509,65 @@ } + /* + * ft_mem_get_val_for_source( source, env_var_name ) + * + * Get a numerical value from named environmental variable + * for the site specified by FT_MemSource. For the syntax + * of the environmental variable, see docs/DEBUG. + * + */ + static int + ft_mem_get_env_val_for_source( FT_MemSource source, + const char* env_var_name ) + { + char *file_name = (char*)source->file_name; + char *c, *c0; + + + /* return if specified environment is unset */ + c = c0 = getenv( env_var_name ); + if ( !c ) + return -1; + + /* return if basename not found anymore */ + while ( NULL != ( c = ft_strstr( c, file_name ) ) ) + { + /* matched token is 1st or after separator space? */ + if ( c != c0 && *(c-1) != ' ' ) + goto NextToken; + + /* check ':', a separator between pathname & line number */ + c = c + ft_strlen( file_name ); + if ( ':' != *c || !ft_isdigit( c[1] ) ) + goto NextToken; + c++; + + /* check the line number */ + if ( atoi( c ) != source->line_no ) + goto NextToken; + + /* line number matched, skip digits */ + while ( ft_isdigit( *c ) ) + c++; + + /* check ',', a separator between line number & value */ + if ( ',' != *c || !ft_isdigit( c[1] ) ) + goto NextToken; + c++; + return atoi( c ); + + NextToken: + c = ft_strchr( c, ' ' ); + if ( !c ) /* no token anymore */ + break; + + c++; + } + return -1; + } + + static void ft_mem_table_set( FT_MemTable table, FT_Byte* address, @@ -676,17 +739,87 @@ } + static FT_Bool + ft_mem_check_alloc_limiter_for_source( FT_MemSource source, + FT_Long size ) + { + int mem_limit_site_total = -1; /* negative means unlimited */ + int mem_limit_site_cur = -1; /* negative means unlimited */ + FT_Bool exceeds_limit_site_total = FALSE; + FT_Bool exceeds_limit_site_cur = FALSE; + long req_all = source->all_size + size; + long req_cur = source->cur_size + size; +#define FT_HAS_LIMITER( key ) \ + ( mem_limit_ ## key ## _total >= 0 || mem_limit_ ## key ## _cur >= 0 ) +#define FT_HAS_MULTI_LIMITERS( key ) \ + ( mem_limit_ ## key ## _total >= 0 && mem_limit_ ## key ## _cur >= 0 ) +#define FT_TRACE6_DEFINED_LIMIT( req, limit ) \ + { \ + if ( limit >= 0 ) \ + FT_TRACE6(( "%ld =< %ld", req, limit )); \ + } + + mem_limit_site_total = + ft_mem_get_env_val_for_source( source, + "FT2_ALLOC_TOTAL_MAX_SITE" ); + mem_limit_site_cur = + ft_mem_get_env_val_for_source( source, + "FT2_ALLOC_CUR_MAX_SITE" ); + if ( FT_HAS_LIMITER( site ) ) + FT_TRACE6(( "ft_mem_table_set() invoked by %s:%lu, limit:( ", + source->file_name, source->line_no )); + FT_TRACE6_DEFINED_LIMIT( req_all, mem_limit_site_total ); + if ( FT_HAS_MULTI_LIMITERS( site ) ) + FT_TRACE6(( " && " )); + FT_TRACE6_DEFINED_LIMIT( req_cur, mem_limit_site_cur ); + if ( FT_HAS_LIMITER( site ) ) + FT_TRACE6((" )\n")); + + if ( mem_limit_site_total >= 0 && mem_limit_site_total > req_all ) + exceeds_limit_site_total = TRUE; + + if ( mem_limit_site_cur >= 0 && mem_limit_site_cur > req_cur ) + exceeds_limit_site_cur = TRUE; + + if ( exceeds_limit_site_total || exceeds_limit_site_cur ) + { + FT_TRACE6(( "ft_mem_table_set() returns NULL to %s:%lu," + " allocation request exceeds %s-limit (%lu > %lu)\n", + source->file_name, source->line_no, + ( exceeds_limit_site_total ? "site-total" : + "site-current" ), + ( exceeds_limit_site_total ? req_all : + req_cur ), + ( exceeds_limit_site_total ? mem_limit_site_total : + mem_limit_site_cur ) + )); + return FALSE; + } + return TRUE; + +#undef FT_HAS_LIMITER +#undef FT_HAS_MULTI_LIMITERS +#undef FT_TRACE6_DEFINED_LIMIT + } + + extern FT_Pointer ft_mem_debug_alloc( FT_Memory memory, FT_Long size ) { - FT_MemTable table = (FT_MemTable)memory->user; - FT_Byte* block; + FT_MemTable table = (FT_MemTable)memory->user; + FT_MemSource source = NULL; + FT_Byte* block; if ( size <= 0 ) ft_mem_debug_panic( "negative block size allocation (%ld)", size ); + source = ft_mem_table_get_source( table ); + if ( source ) + if ( !ft_mem_check_alloc_limiter_for_source( source, size ) ) + return NULL; + /* return NULL if the maximum number of allocations was reached */ if ( table->bound_count && table->alloc_count >= table->alloc_count_max ) diff --git a/src/base/ftrfork.c b/src/base/ftrfork.c index 6df2def..b01ecb9 100644 --- a/src/base/ftrfork.c +++ b/src/base/ftrfork.c @@ -25,6 +25,7 @@ #include <ft2build.h> +#include FT_CONFIG_CONFIG_H #include FT_INTERNAL_DEBUG_H #include FT_INTERNAL_STREAM_H #include FT_INTERNAL_RFORK_H diff --git a/src/cache/ftccache.c b/src/cache/ftccache.c index a5a915e..18af754 100644 --- a/src/cache/ftccache.c +++ b/src/cache/ftccache.c @@ -473,6 +473,29 @@ } + FT_LOCAL_DEF( FT_Size ) + ftc_get_list_length( FTC_Cache cache, + FT_PtrDist hash ) + { + FT_Size len = 0; + FTC_Node* bucket; + FTC_Node* pnode; + FT_UFast idx; + + + idx = hash & cache->mask; + if ( idx < cache->p ) + idx = hash & ( cache->mask * 2 + 1 ); + + bucket = cache->buckets + idx; + pnode = bucket; + for ( ; pnode && *pnode; pnode = &((*pnode)->link) ) + len++; + + return len; + } + + #ifndef FTC_INLINE FT_LOCAL_DEF( FT_Error ) diff --git a/src/cache/ftccache.h b/src/cache/ftccache.h index 10830a9..176f766 100644 --- a/src/cache/ftccache.h +++ b/src/cache/ftccache.h @@ -196,6 +196,10 @@ FT_BEGIN_HEADER FTC_Cache_RemoveFaceID( FTC_Cache cache, FTC_FaceID face_id ); + FT_LOCAL( FT_Size ) + ftc_get_list_length( FTC_Cache cache, + FT_PtrDist hash ); + #ifdef FTC_INLINE @@ -208,6 +212,12 @@ FT_BEGIN_HEADER FT_UFast _idx; \ \ \ + FT_TRACE6(("FTC_CACHE_LOOKUP_CMP() lookup object for ")); \ + FT_TRACE6(("hash=0x%08x in cache ", _hash )); \ + FT_TRACE6(("mask=0x%08x p=0x%08x ", _cache->mask, _cache->p )); \ + FT_TRACE7(("list length=%d", ftc_get_list_length( _cache, _hash ) )); \ + FT_TRACE6(("\n")); \ + \ error = FTC_Err_Ok; \ node = NULL; \ _idx = _hash & _cache->mask; \ @@ -227,6 +237,8 @@ FT_BEGIN_HEADER _pnode = &_node->link; \ } \ \ + FT_TRACE6(("(_bucket,_pnode)=(%p,%p)\n", _bucket, _pnode)); \ + \ if ( _node != *_bucket ) \ { \ *_pnode = _node->link; \ diff --git a/src/cache/ftcmanag.c b/src/cache/ftcmanag.c index 548ebe9..fcf1c43 100644 --- a/src/cache/ftcmanag.c +++ b/src/cache/ftcmanag.c @@ -622,6 +622,24 @@ } + static int + ft_get_length_nodes_list( FTC_Node node0 ) + { + FTC_Node node = node0; + int len = 0; + + + while ( node ) + { + len ++; + node = node->link; + if ( node == node0 ) + return len; + } + return len; + } + + FT_LOCAL_DEF( FT_UInt ) FTC_Manager_FlushN( FTC_Manager manager, FT_UInt count ) @@ -631,9 +649,17 @@ FT_UInt result; + FT_TRACE2(( "FTC_Manager_FlushN() tries to" + " free %d nodes from list length=%d\n", + count, + ft_get_length_nodes_list( manager->nodes_list ) )); + /* try to remove `count' nodes from the list */ if ( first == NULL ) /* empty list! */ + { + FT_TRACE2(("FTC_Manager_FlushN() cannot change empty list\n" )); return 0; + } /* go to last node - it's a circular list */ node = FTC_NODE__PREV(first); @@ -654,6 +680,10 @@ node = prev; } + FT_TRACE2(( "FTC_Manager_FlushN() freed %d nodes," + " list length=%d\n", + result, + ft_get_length_nodes_list( manager->nodes_list ) )); return result; } diff --git a/src/cache/ftcsbits.c b/src/cache/ftcsbits.c index 0386bdd..907868c 100644 --- a/src/cache/ftcsbits.c +++ b/src/cache/ftcsbits.c @@ -125,6 +125,11 @@ sbit->buffer = 0; error = clazz->family_load_glyph( family, gindex, manager, &face ); + FT_TRACE2(( "ftc_snode_load() got %s from " + "family_load_glyph() err=0x%02x%s\n", + error ? "error" : "ok", + error, + error ? " fallback to BadGlyph" : "" )); if ( error ) goto BadGlyph; @@ -200,6 +205,8 @@ *asize = 0; } + FT_TRACE2(( "ftc_snode_load() load glyph for gid=%d, err=0x%02x\n", + gindex, error)); return error; } @@ -333,6 +340,7 @@ FT_Bool result; + FT_TRACE2(( "ftc_snode_compare() work for gindex=%d\n", gindex )); result = FT_BOOL( gnode->family == gquery->family && (FT_UInt)( gindex - gnode->gindex ) < snode->count ); if ( result ) @@ -397,6 +405,8 @@ } } + FT_TRACE2(( "ftc_snode_compare() returns %s\n", + result ? "TRUE" : "FALSE" )); return result; }
diff --git a/builds/unix/configure.raw b/builds/unix/configure.raw index cb10ef4..6e4c1c2 100644 --- a/builds/unix/configure.raw +++ b/builds/unix/configure.raw @@ -111,7 +111,7 @@ esac # checks for header files AC_HEADER_STDC -AC_CHECK_HEADERS([fcntl.h unistd.h]) +AC_CHECK_HEADERS([fcntl.h unistd.h libgen.h]) # checks for typedefs, structures, and compiler characteristics diff --git a/builds/unix/ftconfig.in b/builds/unix/ftconfig.in index 4c81187..3bb68fb 100644 --- a/builds/unix/ftconfig.in +++ b/builds/unix/ftconfig.in @@ -60,6 +60,7 @@ FT_BEGIN_HEADER #undef HAVE_UNISTD_H #undef HAVE_FCNTL_H #undef HAVE_STDINT_H +#undef HAVE_LIBGEN_H /* There are systems (like the Texas Instruments 'C54x) where a `char' */ @@ -167,6 +168,26 @@ FT_BEGIN_HEADER #endif + /*************************************************************************/ + /* */ + /* Pathname separator used by resource-fork accessor & ft_basename() */ + /* */ + +#ifdef PLATFORM_DIR_SEPARATOR + /* explicitly defined, do nothing */ +#elif defined( __DOS__ ) || defined( __OS2__ ) || defined( _WIN32 ) || \ + defined( __SYMBIAN32__ ) +#define PLATFORM_DIR_SEPARATOR "\\" +#elif defined( FT_MACINTOSH ) && !defined( __MACH__ ) +#define PLATFORM_DIR_SEPARATOR ":" +#elif defined( __riscos__ ) || defined( VMS ) +#define PLATFORM_DIR_SEPARATOR "." +#else +#define PLATFORM_DIR_SEPARATOR "/" +#endif + + + /* Fix compiler warning with sgi compiler */ #if defined( __sgi ) && !defined( __GNUC__ ) #if defined( _COMPILER_VERSION ) && ( _COMPILER_VERSION >= 730 ) diff --git a/include/freetype/config/ftconfig.h b/include/freetype/config/ftconfig.h index cbe30f2..cd0fa27 100644 --- a/include/freetype/config/ftconfig.h +++ b/include/freetype/config/ftconfig.h @@ -147,6 +147,25 @@ FT_BEGIN_HEADER /*************************************************************************/ /* */ + /* Pathname separator used by resource-fork accessor & ft_basename() */ + /* */ + +#ifdef PLATFORM_DIR_SEPARATOR + /* explicitly defined, do nothing */ +#elif defined( __DOS__ ) || defined( __OS2__ ) || defined( _WIN32 ) || \ + defined( __SYMBIAN32__ ) +#define PLATFORM_DIR_SEPARATOR "\\" +#elif defined( FT_MACINTOSH ) && !defined( __MACH__ ) +#define PLATFORM_DIR_SEPARATOR ":" +#elif defined( __riscos__ ) || defined( VMS ) +#define PLATFORM_DIR_SEPARATOR "." +#else +#define PLATFORM_DIR_SEPARATOR "/" +#endif + + + /*************************************************************************/ + /* */ /* <Section> */ /* basic_types */ /* */ diff --git a/include/freetype/internal/ftobjs.h b/include/freetype/internal/ftobjs.h index 670eb78..4cffb13 100644 --- a/include/freetype/internal/ftobjs.h +++ b/include/freetype/internal/ftobjs.h @@ -108,6 +108,14 @@ FT_BEGIN_HEADER #define ft_isalpha( x ) ( ft_isupper( x ) || ft_islower( x ) ) #define ft_isalnum( x ) ( ft_isdigit( x ) || ft_isalpha( x ) ) +#ifdef HAVE_LIBGEN_H +#include <libgen.h> +#define ft_basename( p ) basename( ( p ) ) +#else + FT_BASE( char* ) + ft_basename( char* path ); +#endif + /*************************************************************************/ /*************************************************************************/ diff --git a/src/base/ftdbgmem.c b/src/base/ftdbgmem.c index bb04f68..538c086 100644 --- a/src/base/ftdbgmem.c +++ b/src/base/ftdbgmem.c @@ -521,7 +521,7 @@ ft_mem_get_env_val_for_source( FT_MemSource source, const char* env_var_name ) { - char *file_name = (char*)source->file_name; + char *file_name = ft_basename( (char*)source->file_name ); char *c, *c0; @@ -534,7 +534,12 @@ while ( NULL != ( c = ft_strstr( c, file_name ) ) ) { /* matched token is 1st or after separator space? */ - if ( c != c0 && *(c-1) != ' ' ) + if ( c != c0 && *(c-1) != ' ' && +#ifndef macintosh + *(c-1) != '/' && *(c-1) != '\\' ) +#else + *(c-1) != ':' ) +#endif goto NextToken; /* check ':', a separator between pathname & line number */ @@ -767,7 +772,8 @@ "FT2_ALLOC_CUR_MAX_SITE" ); if ( FT_HAS_LIMITER( site ) ) FT_TRACE6(( "ft_mem_table_set() invoked by %s:%lu, limit:( ", - source->file_name, source->line_no )); + ft_basename( (char*)source->file_name ), + source->line_no )); FT_TRACE6_DEFINED_LIMIT( req_all, mem_limit_site_total ); if ( FT_HAS_MULTI_LIMITERS( site ) ) FT_TRACE6(( " && " )); @@ -785,7 +791,8 @@ { FT_TRACE6(( "ft_mem_table_set() returns NULL to %s:%lu," " allocation request exceeds %s-limit (%lu > %lu)\n", - source->file_name, source->line_no, + ft_basename( (char*)source->file_name ), + source->line_no, ( exceeds_limit_site_total ? "site-total" : "site-current" ), ( exceeds_limit_site_total ? req_all : diff --git a/src/base/ftobjs.c b/src/base/ftobjs.c index 6b01f43..afe2995 100644 --- a/src/base/ftobjs.c +++ b/src/base/ftobjs.c @@ -1226,6 +1226,27 @@ } +#ifndef HAVE_LIBGEN_H + FT_BASE_DEF( char* ) + ft_basename( char* pathname ) + { + char* c; + + + c = pathname + ft_strlen( pathname ); + + /* skip trailing separator */ + while ( pathname < c && *c == PLATFORM_DIR_SEPARATOR[0] ) + c--; + + while ( pathname < c && *(c-1) != PLATFORM_DIR_SEPARATOR[0] ) + c--; + + return c; + } +#endif + + #ifdef FT_CONFIG_OPTION_MAC_FONTS /* The behavior here is very similar to that in base/ftmac.c, but it */ diff --git a/src/base/ftrfork.c b/src/base/ftrfork.c index b01ecb9..01be277 100644 --- a/src/base/ftrfork.c +++ b/src/base/ftrfork.c @@ -544,7 +544,8 @@ return error; FT_MEM_COPY( newpath, base_file_name, base_file_len ); - FT_MEM_COPY( newpath + base_file_len, "/rsrc", 6 ); + FT_MEM_COPY( newpath + base_file_len, + PLATFORM_DIR_SEPARATOR "rsrc", 6 ); *result_file_name = newpath; *result_offset = 0; @@ -580,7 +581,9 @@ return error; FT_MEM_COPY( newpath, base_file_name, base_file_len ); - FT_MEM_COPY( newpath + base_file_len, "/..namedfork/rsrc", 18 ); + FT_MEM_COPY( newpath + base_file_len, + PLATFORM_DIR_SEPARATOR "..namedfork" + PLATFORM_DIR_SEPARATOR "rsrc", 18 ); *result_file_name = newpath; *result_offset = 0; @@ -605,7 +608,8 @@ memory = library->memory; newpath = raccess_make_file_name( memory, base_file_name, - "resource.frk/" ); + "resource.frk" + PLATFORM_DIR_SEPARATOR ); if ( !newpath ) return FT_Err_Out_Of_Memory; @@ -631,7 +635,9 @@ memory = library->memory; - newpath = raccess_make_file_name( memory, base_file_name, ".resource/" ); + newpath = raccess_make_file_name( memory, base_file_name, + ".resource" + PLATFORM_DIR_SEPARATOR ); if ( !newpath ) return FT_Err_Out_Of_Memory; @@ -690,7 +696,8 @@ memory = library->memory; newpath = raccess_make_file_name( memory, base_file_name, - ".AppleDouble/" ); + ".AppleDouble" + PLATFORM_DIR_SEPARATOR ); if ( !newpath ) return FT_Err_Out_Of_Memory; @@ -815,7 +822,7 @@ if ( FT_ALLOC( new_name, new_length + 1 ) ) return NULL; - tmp = ft_strrchr( original_name, '/' ); + tmp = ft_strrchr( original_name, PLATFORM_DIR_SEPARATOR[0] ); if ( tmp ) { ft_strncpy( new_name, original_name, tmp - original_name + 1 );
_______________________________________________ Freetype-devel mailing list Freetype-devel@nongnu.org http://lists.nongnu.org/mailman/listinfo/freetype-devel