blob: 33c45386c56d2e6eda01f0652eebcd19c9e3f4d1 [file] [log] [blame]
/****************************************************************************
*
* ftcsbits.c
*
* FreeType sbits manager (body).
*
* Copyright (C) 2000-2021 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 <freetype/ftcache.h>
#include "ftcsbits.h"
#include <freetype/internal/ftobjs.h>
#include <freetype/internal/ftdebug.h>
#include <freetype/fterrors.h>
#include "ftccback.h"
#include "ftcerror.h"
#undef FT_COMPONENT
#define FT_COMPONENT cache
/*************************************************************************/
/*************************************************************************/
/***** *****/
/***** SBIT CACHE NODES *****/
/***** *****/
/*************************************************************************/
/*************************************************************************/
static FT_Error
ftc_sbit_copy_bitmap( FTC_SBit sbit,
FT_Bitmap* bitmap,
FT_Memory memory )
{
FT_Error error;
FT_Int pitch = bitmap->pitch;
FT_ULong size;
if ( pitch < 0 )
pitch = -pitch;
size = (FT_ULong)pitch * bitmap->rows;
if ( !size )
return FT_Err_Ok;
if ( !FT_ALLOC( sbit->buffer, size ) )
FT_MEM_COPY( sbit->buffer, bitmap->buffer, size );
return error;
}
FT_LOCAL_DEF( void )
ftc_snode_free( FTC_Node ftcsnode,
FTC_Cache cache )
{
FTC_SNode snode = (FTC_SNode)ftcsnode;
FTC_SBit sbit = snode->sbits;
FT_UInt count = snode->count;
FT_Memory memory = cache->memory;
for ( ; count > 0; sbit++, count-- )
FT_FREE( sbit->buffer );
FTC_GNode_Done( FTC_GNODE( snode ), cache );
FT_FREE( snode );
}
FT_LOCAL_DEF( void )
FTC_SNode_Free( FTC_SNode snode,
FTC_Cache cache )
{
ftc_snode_free( FTC_NODE( snode ), cache );
}
/*
* This function tries to load a small bitmap within a given FTC_SNode.
* Note that it returns a non-zero error code _only_ in the case of
* out-of-memory condition. For all other errors (e.g., corresponding
* to a bad font file), this function will mark the sbit as `unavailable'
* and return a value of 0.
*
* You should also read the comment within the @ftc_snode_compare
* function below to see how out-of-memory is handled during a lookup.
*/
static FT_Error
ftc_snode_load( FTC_SNode snode,
FTC_Manager manager,
FT_UInt gindex,
FT_ULong *asize )
{
FT_Error error;
FTC_GNode gnode = FTC_GNODE( snode );
FTC_Family family = gnode->family;
FT_Face face;
FTC_SBit sbit;
FTC_SFamilyClass clazz;
if ( (FT_UInt)(gindex - gnode->gindex) >= snode->count )
{
FT_ERROR(( "ftc_snode_load: invalid glyph index" ));
return FT_THROW( Invalid_Argument );
}
sbit = snode->sbits + ( gindex - gnode->gindex );
clazz = (FTC_SFamilyClass)family->clazz;
sbit->buffer = NULL;
error = clazz->family_load_glyph( family, gindex, manager, &face );
if ( error )
goto BadGlyph;
{
FT_Int temp;
FT_GlyphSlot slot = face->glyph;
FT_Bitmap* bitmap = &slot->bitmap;
FT_Pos xadvance, yadvance; /* FT_GlyphSlot->advance.{x|y} */
if ( slot->format != FT_GLYPH_FORMAT_BITMAP )
{
FT_TRACE0(( "ftc_snode_load:"
" glyph loaded didn't return a bitmap\n" ));
goto BadGlyph;
}
/* Check whether our values fit into 8/16-bit containers! */
/* If this is not the case, our bitmap is too large */
/* and we will leave it as `missing' with sbit.buffer = 0 */
#define CHECK_CHAR( d ) ( temp = (FT_Char)d, (FT_Int) temp == (FT_Int) d )
#define CHECK_BYTE( d ) ( temp = (FT_Byte)d, (FT_UInt)temp == (FT_UInt)d )
#define CHECK_SHRT( d ) ( temp = (FT_Short)d, (FT_Int)temp == (FT_Int) d )
/* horizontal advance in pixels */
xadvance = ( slot->advance.x + 32 ) >> 6;
yadvance = ( slot->advance.y + 32 ) >> 6;
if ( !CHECK_BYTE( bitmap->rows ) ||
!CHECK_BYTE( bitmap->width ) ||
!CHECK_SHRT( bitmap->pitch ) ||
!CHECK_CHAR( slot->bitmap_left ) ||
!CHECK_CHAR( slot->bitmap_top ) ||
!CHECK_CHAR( xadvance ) ||
!CHECK_CHAR( yadvance ) )
{
FT_TRACE2(( "ftc_snode_load:"
" glyph too large for small bitmap cache\n"));
goto BadGlyph;
}
sbit->width = (FT_Byte)bitmap->width;
sbit->height = (FT_Byte)bitmap->rows;
sbit->pitch = (FT_Short)bitmap->pitch;
sbit->left = (FT_Char)slot->bitmap_left;
sbit->top = (FT_Char)slot->bitmap_top;
sbit->xadvance = (FT_Char)xadvance;
sbit->yadvance = (FT_Char)yadvance;
sbit->format = (FT_Byte)bitmap->pixel_mode;
sbit->max_grays = (FT_Byte)(bitmap->num_grays - 1);
if ( slot->internal->flags & FT_GLYPH_OWN_BITMAP )
{
/* take the bitmap ownership */
sbit->buffer = bitmap->buffer;
slot->internal->flags &= ~FT_GLYPH_OWN_BITMAP;
}
else
{
/* copy the bitmap into a new buffer -- ignore error */
error = ftc_sbit_copy_bitmap( sbit, bitmap, manager->memory );
}
/* now, compute size */
if ( asize )
*asize = (FT_ULong)FT_ABS( sbit->pitch ) * sbit->height;
} /* glyph loading successful */
/* ignore the errors that might have occurred -- */
/* we mark unloaded glyphs with `sbit.buffer == 0' */
/* and `width == 255', `height == 0' */
/* */
if ( error && FT_ERR_NEQ( error, Out_Of_Memory ) )
{
BadGlyph:
sbit->width = 255;
sbit->height = 0;
sbit->buffer = NULL;
error = FT_Err_Ok;
if ( asize )
*asize = 0;
}
return error;
}
FT_LOCAL_DEF( FT_Error )
FTC_SNode_New( FTC_SNode *psnode,
FTC_GQuery gquery,
FTC_Cache cache )
{
FT_Memory memory = cache->memory;
FT_Error error;
FTC_SNode snode = NULL;
FT_UInt gindex = gquery->gindex;
FTC_Family family = gquery->family;
FTC_SFamilyClass clazz = FTC_CACHE_SFAMILY_CLASS( cache );
FT_UInt total;
FT_UInt node_count;
total = clazz->family_get_count( family, cache->manager );
if ( total == 0 || gindex >= total )
{
error = FT_THROW( Invalid_Argument );
goto Exit;
}
if ( !FT_NEW( snode ) )
{
FT_UInt count, start;
start = gindex - ( gindex % FTC_SBIT_ITEMS_PER_NODE );
count = total - start;
if ( count > FTC_SBIT_ITEMS_PER_NODE )
count = FTC_SBIT_ITEMS_PER_NODE;
FTC_GNode_Init( FTC_GNODE( snode ), start, family );
snode->count = count;
for ( node_count = 0; node_count < count; node_count++ )
{
snode->sbits[node_count].width = 255;
}
error = ftc_snode_load( snode,
cache->manager,
gindex,
NULL );
if ( error )
{
FTC_SNode_Free( snode, cache );
snode = NULL;
}
}
Exit:
*psnode = snode;
return error;
}
FT_LOCAL_DEF( FT_Error )
ftc_snode_new( FTC_Node *ftcpsnode,
FT_Pointer ftcgquery,
FTC_Cache cache )
{
FTC_SNode *psnode = (FTC_SNode*)ftcpsnode;
FTC_GQuery gquery = (FTC_GQuery)ftcgquery;
return FTC_SNode_New( psnode, gquery, cache );
}
FT_LOCAL_DEF( FT_Offset )
ftc_snode_weight( FTC_Node ftcsnode,
FTC_Cache cache )
{
FTC_SNode snode = (FTC_SNode)ftcsnode;
FT_UInt count = snode->count;
FTC_SBit sbit = snode->sbits;
FT_Int pitch;
FT_Offset size;
FT_UNUSED( cache );
FT_ASSERT( snode->count <= FTC_SBIT_ITEMS_PER_NODE );
/* the node itself */
size = sizeof ( *snode );
for ( ; count > 0; count--, sbit++ )
{
if ( sbit->buffer )
{
pitch = sbit->pitch;
if ( pitch < 0 )
pitch = -pitch;
/* add the size of a given glyph image */
size += (FT_Offset)pitch * sbit->height;
}
}
return size;
}
#if 0
FT_LOCAL_DEF( FT_Offset )
FTC_SNode_Weight( FTC_SNode snode )
{
return ftc_snode_weight( FTC_NODE( snode ), NULL );
}
#endif /* 0 */
FT_LOCAL_DEF( FT_Bool )
ftc_snode_compare( FTC_Node ftcsnode,
FT_Pointer ftcgquery,
FTC_Cache cache,
FT_Bool* list_changed )
{
FTC_SNode snode = (FTC_SNode)ftcsnode;
FTC_GQuery gquery = (FTC_GQuery)ftcgquery;
FTC_GNode gnode = FTC_GNODE( snode );
FT_UInt gindex = gquery->gindex;
FT_Bool result;
if (list_changed)
*list_changed = FALSE;
result = FT_BOOL( gnode->family == gquery->family &&
(FT_UInt)( gindex - gnode->gindex ) < snode->count );
if ( result )
{
/* check if we need to load the glyph bitmap now */
FTC_SBit sbit = snode->sbits + ( gindex - gnode->gindex );
/*
* The following code illustrates what to do when you want to
* perform operations that may fail within a lookup function.
*
* Here, we want to load a small bitmap on-demand; we thus
* need to call the `ftc_snode_load' function which may return
* a non-zero error code only when we are out of memory (OOM).
*
* The correct thing to do is to use @FTC_CACHE_TRYLOOP and
* @FTC_CACHE_TRYLOOP_END in order to implement a retry loop
* that is capable of flushing the cache incrementally when
* an OOM errors occur.
*
* However, we need to `lock' the node before this operation to
* prevent it from being flushed within the loop.
*
* When we exit the loop, we unlock the node, then check the `error'
* variable. If it is non-zero, this means that the cache was
* completely flushed and that no usable memory was found to load
* the bitmap.
*
* We then prefer to return a value of 0 (i.e., NO MATCH). This
* ensures that the caller will try to allocate a new node.
* This operation consequently _fail_ and the lookup function
* returns the appropriate OOM error code.
*
* Note that `buffer == NULL && width == 255' is a hack used to
* tag `unavailable' bitmaps in the array. We should never try
* to load these.
*
*/
if ( !sbit->buffer && sbit->width == 255 )
{
FT_ULong size;
FT_Error error;
ftcsnode->ref_count++; /* lock node to prevent flushing */
/* in retry loop */
FTC_CACHE_TRYLOOP( cache )
{
error = ftc_snode_load( snode, cache->manager, gindex, &size );
}
FTC_CACHE_TRYLOOP_END( list_changed );
ftcsnode->ref_count--; /* unlock the node */
if ( error )
result = 0;
else
cache->manager->cur_weight += size;
}
}
return result;
}
#ifdef FTC_INLINE
FT_LOCAL_DEF( FT_Bool )
FTC_SNode_Compare( FTC_SNode snode,
FTC_GQuery gquery,
FTC_Cache cache,
FT_Bool* list_changed )
{
return ftc_snode_compare( FTC_NODE( snode ), gquery,
cache, list_changed );
}
#endif
/* END */