565 lines
14 KiB
C
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 */
|