Files
freetype/lib/extend/ftxkern.c
2023-08-27 18:03:45 +02:00

565 lines
14 KiB
C

/*******************************************************************
*
* ftxkern.c 1.0
*
* Kerning support extension.
*
* Copyright 1996-1999 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.
*
*
* The kerning support is currently part of the engine extensions.
*
******************************************************************/
#include "ftxkern.h"
#include "ttextend.h"
#include "tttypes.h"
#include "ttdebug.h"
#include "ttmemory.h"
#include "ttfile.h"
#include "ttobjs.h"
#include "ttload.h" /* For the macros */
#include "tttags.h"
/* Required by the tracing mode */
#undef TT_COMPONENT
#define TT_COMPONENT trace_any
#define KERNING_ID Build_Extension_ID( 'k', 'e', 'r', 'n' )
/*******************************************************************
*
* Function : SubTable_Load_0
*
* Description : Loads a format 0 kerning subtable data.
*
* Input : kern0 pointer to the kerning subtable
*
* Output : error code
*
* Notes : - Assumes that the stream is already `used'
*
* - the file cursor must be set by the caller
*
* - in case of error, the function _must_ destroy
* the data it allocates!
*
******************************************************************/
static TT_Error Subtable_Load_0( TT_Kern_0* kern0,
PFace input )
{
DEFINE_LOAD_LOCALS( input->stream );
UShort num_pairs, n;
if ( ACCESS_Frame( 8L ) )
return error;
num_pairs = GET_UShort();
kern0->nPairs = 0;
kern0->searchRange = GET_UShort();
kern0->entrySelector = GET_UShort();
kern0->rangeShift = GET_UShort();
/* we only set kern0->nPairs if the subtable has been loaded */
FORGET_Frame();
if ( ALLOC_ARRAY( kern0->pairs, num_pairs, TT_Kern_0_Pair ) )
return error;
if ( ACCESS_Frame( num_pairs * 6L ) )
goto Fail;
for ( n = 0; n < num_pairs; n++ )
{
kern0->pairs[n].left = GET_UShort();
kern0->pairs[n].right = GET_UShort();
kern0->pairs[n].value = GET_UShort();
if ( kern0->pairs[n].left >= input->numGlyphs ||
kern0->pairs[n].right >= input->numGlyphs )
{
FORGET_Frame();
error = TT_Err_Invalid_Kerning_Table;
goto Fail;
}
}
FORGET_Frame();
/* we're ok, set the pairs count */
kern0->nPairs = num_pairs;
return TT_Err_Ok;
Fail:
FREE( kern0->pairs );
return error;
}
/*******************************************************************
*
* Function : SubTable_Load_2
*
* Description : Loads a format 2 kerning subtable data.
*
* Input : kern2 pointer to the kerning subtable
* length subtable length. This is required as
* the subheader doesn't give any indication
* of the size of the `array' table.
*
* Output : error code
*
* Notes : - Assumes that the stream is already `used'
*
* - the file cursor must be set by the caller
*
* - in case of error, the function _must_ destroy
* the data it allocates!
*
******************************************************************/
static TT_Error Subtable_Load_2( TT_Kern_2* kern2,
PFace input )
{
DEFINE_LOAD_LOCALS( input->stream );
Long table_base;
UShort left_offset, right_offset, array_offset;
ULong array_size;
UShort left_max, right_max, n;
/* record the table offset */
table_base = FILE_Pos();
if ( ACCESS_Frame( 8L ) )
return error;
kern2->rowWidth = GET_UShort();
left_offset = GET_UShort();
right_offset = GET_UShort();
array_offset = GET_UShort();
FORGET_Frame();
/* first load left and right glyph classes */
if ( FILE_Seek( table_base + left_offset ) ||
ACCESS_Frame( 4L ) )
return error;
kern2->leftClass.firstGlyph = GET_UShort();
kern2->leftClass.nGlyphs = GET_UShort();
FORGET_Frame();
if ( ALLOC_ARRAY( kern2->leftClass.classes,
kern2->leftClass.nGlyphs,
UShort ) )
return error;
/* load left offsets */
if ( ACCESS_Frame( kern2->leftClass.nGlyphs * 2L ) )
goto Fail_Left;
for ( n = 0; n < kern2->leftClass.nGlyphs; n++ )
kern2->leftClass.classes[n] = GET_UShort();
FORGET_Frame();
/* right class */
if ( FILE_Seek( table_base + right_offset ) ||
ACCESS_Frame( 4L ) )
goto Fail_Left;
kern2->rightClass.firstGlyph = GET_UShort();
kern2->rightClass.nGlyphs = GET_UShort();
FORGET_Frame();
if ( ALLOC_ARRAY( kern2->rightClass.classes,
kern2->rightClass.nGlyphs,
UShort ) )
goto Fail_Left;
/* load right offsets */
if ( ACCESS_Frame( kern2->rightClass.nGlyphs * 2L ) )
goto Fail_Right;
for ( n = 0; n < kern2->rightClass.nGlyphs; n++ )
kern2->rightClass.classes[n] = GET_UShort();
FORGET_Frame();
/* Now load the kerning array. We don't have its size, we */
/* must compute it from what we know. */
/* We thus compute the maximum left and right offsets and */
/* add them to get the array size. */
left_max = right_max = 0;
for ( n = 0; n < kern2->leftClass.nGlyphs; n++ )
left_max = MAX( left_max, kern2->leftClass.classes[n] );
for ( n = 0; n < kern2->rightClass.nGlyphs; n++ )
right_max = MAX( right_max, kern2->leftClass.classes[n] );
array_size = left_max + right_max + 2;
if ( ALLOC( kern2->array, array_size ) )
goto Fail_Right;
if ( ACCESS_Frame( array_size ) )
goto Fail_Array;
for ( n = 0; n < array_size/2; n++ )
kern2->array[n] = GET_Short();
FORGET_Frame();
/* we're good now */
return TT_Err_Ok;
Fail_Array:
FREE( kern2->array );
Fail_Right:
FREE( kern2->rightClass.classes );
kern2->rightClass.nGlyphs = 0;
Fail_Left:
FREE( kern2->leftClass.classes );
kern2->leftClass.nGlyphs = 0;
return error;
}
/*******************************************************************
*
* Function : Kerning_Create
*
* Description : Creates the kerning directory if a face is
* loaded. The tables however are loaded on
* demand to save space.
*
* Input : face pointer to the parent face object
* kern pointer to the extension's kerning field
*
* Output : error code
*
* Notes : as in all constructors, the memory allocated isn't
* released in case of failure. Rather, the task is left
* to the destructor (which is called if an error
* occurs during the loading of a face).
*
******************************************************************/
static TT_Error Kerning_Create( void* ext,
PFace face )
{
DEFINE_LOAD_LOCALS( face->stream );
TT_Kerning* kern = (TT_Kerning*)ext;
UShort num_tables;
Long table;
TT_Kern_Subtable* sub;
/* by convention */
if ( !kern )
return TT_Err_Ok;
/* Now load the kerning directory. We're called from the face */
/* constructor. We thus need not use the stream. */
kern->version = 0;
kern->nTables = 0;
kern->tables = NULL;
table = TT_LookUp_Table( face, TTAG_kern );
if ( table < 0 )
return TT_Err_Ok; /* The table is optional */
if ( FILE_Seek( face->dirTables[table].Offset ) ||
ACCESS_Frame( 4L ) )
return error;
kern->version = GET_UShort();
num_tables = GET_UShort();
FORGET_Frame();
/* we don't set kern->nTables until we have allocated the array */
if ( ALLOC_ARRAY( kern->tables, num_tables, TT_Kern_Subtable ) )
return error;
kern->nTables = num_tables;
/* now load the directory entries, but do _not_ load the tables ! */
sub = kern->tables;
for ( table = 0; table < num_tables; table++ )
{
if ( ACCESS_Frame( 6L ) )
return error;
sub->loaded = FALSE; /* redundant, but good to see */
sub->version = GET_UShort();
sub->length = GET_UShort() - 6; /* substract header length */
sub->format = GET_Byte();
sub->coverage = GET_Byte();
FORGET_Frame();
sub->offset = FILE_Pos();
/* now skip to the next table */
if ( FILE_Skip( sub->length ) )
return error;
sub++;
}
/* that's fine, leave now */
return TT_Err_Ok;
}
/*******************************************************************
*
* Function : Kerning_Destroy
*
* Description : Destroys all kerning information.
*
* Input : kern pointer to the extension's kerning field
*
* Output : error code
*
* Notes : This function is a destructor; it must be able
* to destroy partially built tables.
*
******************************************************************/
static TT_Error Kerning_Destroy( void* ext,
PFace face )
{
TT_Kerning* kern = (TT_Kerning*)ext;
TT_Kern_Subtable* sub;
UShort n;
/* by convention */
if ( !kern )
return TT_Err_Ok;
if ( kern->nTables == 0 )
return TT_Err_Ok; /* no tables to release */
/* scan the table directory and release loaded entries */
sub = kern->tables;
for ( n = 0; n < kern->nTables; n++ )
{
if ( sub->loaded )
{
switch ( sub->format )
{
case 0:
FREE( sub->t.kern0.pairs );
sub->t.kern0.nPairs = 0;
sub->t.kern0.searchRange = 0;
sub->t.kern0.entrySelector = 0;
sub->t.kern0.rangeShift = 0;
break;
case 2:
FREE( sub->t.kern2.leftClass.classes );
sub->t.kern2.leftClass.firstGlyph = 0;
sub->t.kern2.leftClass.nGlyphs = 0;
FREE( sub->t.kern2.rightClass.classes );
sub->t.kern2.rightClass.firstGlyph = 0;
sub->t.kern2.rightClass.nGlyphs = 0;
FREE( sub->t.kern2.array );
sub->t.kern2.rowWidth = 0;
break;
default:
; /* invalid subtable format - do nothing */
}
sub->loaded = FALSE;
sub->version = 0;
sub->offset = 0;
sub->length = 0;
sub->coverage = 0;
sub->format = 0;
}
sub++;
}
FREE( kern->tables );
kern->nTables = 0;
return TT_Err_Ok;
}
/*******************************************************************
*
* Function : TT_Get_Kerning_Directory
*
* Description : Returns a given face's kerning directory.
*
* Input : face handle to the face object
* directory pointer to client's target directory
*
* Output : error code
*
* Notes : The kerning table directory is loaded with the face
* through the extension constructor. However, the kerning
* tables themselves are only loaded on demand, as they
* may represent a lot of data, unneeded by most uses of
* the engine.
*
******************************************************************/
EXPORT_FUNC
TT_Error TT_Get_Kerning_Directory( TT_Face face,
TT_Kerning* directory )
{
PFace faze = HANDLE_Face( face );
TT_Error error;
TT_Kerning* kerning;
if ( !faze )
return TT_Err_Invalid_Face_Handle;
/* copy directory header */
error = TT_Extension_Get( faze, KERNING_ID, (void**)&kerning );
if ( !error )
*directory = *kerning;
return error;
}
/*******************************************************************
*
* Function : TT_Load_Kerning_Table
*
* Description : Loads a kerning table intro memory.
*
* Input : face face handle
* kern_index index in the face's kerning directory
*
* Output : error code
*
* Notes :
*
******************************************************************/
EXPORT_FUNC
TT_Error TT_Load_Kerning_Table( TT_Face face,
TT_UShort kern_index )
{
TT_Error error;
TT_Stream stream;
TT_Kerning* kern;
TT_Kern_Subtable* sub;
PFace faze = HANDLE_Face( face );
if ( !faze )
return TT_Err_Invalid_Face_Handle;
error = TT_Extension_Get( faze, KERNING_ID, (void**)&kern );
if ( error )
return error;
if ( kern->nTables == 0 )
return TT_Err_Table_Missing;
if ( kern_index >= kern->nTables )
return TT_Err_Invalid_Argument;
sub = kern->tables + kern_index;
if ( sub->format != 0 && sub->format != 2 )
return TT_Err_Invalid_Kerning_Table_Format;
/* now access stream */
if ( USE_Stream( faze->stream, stream ) )
return error;
if ( FILE_Seek( sub->offset ) )
goto Fail;
if ( sub->format == 0 )
error = Subtable_Load_0( &sub->t.kern0, faze );
else if ( sub->format == 2 )
error = Subtable_Load_2( &sub->t.kern2, faze );
if ( !error )
sub->loaded = TRUE;
Fail:
/* release stream */
DONE_Stream( stream );
return error;
}
EXPORT_FUNC
TT_Error TT_Init_Kerning_Extension( TT_Engine engine )
{
PEngine_Instance _engine = HANDLE_Engine( engine );
TT_Error error;
if ( !_engine )
return TT_Err_Invalid_Engine;
error = TT_Register_Extension( _engine,
KERNING_ID,
sizeof ( TT_Kerning ),
Kerning_Create,
Kerning_Destroy );
return error;
}
/* END */