2024-01-20 01:42:48 +01:00
|
|
|
/****************************************************************************
|
|
|
|
*
|
|
|
|
* ttgpos.c
|
|
|
|
*
|
|
|
|
* Load the TrueType GPOS table. The only GPOS layout feature this
|
|
|
|
* currently supports is kerning, from x advances in the pair adjustment
|
|
|
|
* layout feature.
|
|
|
|
*
|
|
|
|
* Parts of the implementation were adapted from:
|
|
|
|
* https://github.com/nothings/stb/blob/master/stb_truetype.h
|
|
|
|
*
|
|
|
|
* GPOS spec reference available at:
|
|
|
|
* https://learn.microsoft.com/en-us/typography/opentype/spec/gpos
|
|
|
|
*
|
|
|
|
* Copyright (C) 2024 by
|
|
|
|
* David Saltzman
|
|
|
|
*
|
|
|
|
* 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/internal/ftdebug.h>
|
|
|
|
#include <freetype/internal/ftstream.h>
|
|
|
|
#include <freetype/tttags.h>
|
|
|
|
#include "freetype/fttypes.h"
|
|
|
|
#include "freetype/internal/ftobjs.h"
|
|
|
|
#include "ttgpos.h"
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* The macro FT_COMPONENT is used in trace mode. It is an implicit
|
|
|
|
* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
|
|
|
|
* messages during execution.
|
|
|
|
*/
|
|
|
|
#undef FT_COMPONENT
|
|
|
|
#define FT_COMPONENT ttgpos
|
|
|
|
|
|
|
|
|
|
|
|
typedef enum coverage_table_format_type_
|
|
|
|
{
|
|
|
|
COVERAGE_TABLE_FORMAT_LIST = 1,
|
|
|
|
COVERAGE_TABLE_FORMAT_RANGE = 2
|
|
|
|
|
|
|
|
} coverage_table_format_type;
|
|
|
|
|
|
|
|
typedef enum class_def_table_format_type_
|
|
|
|
{
|
|
|
|
CLASS_DEF_TABLE_FORMAT_ARRAY = 1,
|
|
|
|
CLASS_DEF_TABLE_FORMAT_RANGE_GROUPS = 2
|
|
|
|
|
|
|
|
} class_def_table_format_type;
|
|
|
|
|
|
|
|
typedef enum gpos_lookup_type_
|
|
|
|
{
|
|
|
|
GPOS_LOOKUP_TYPE_SINGLE_ADJUSTMENT = 1,
|
|
|
|
GPOS_LOOKUP_TYPE_PAIR_ADJUSTMENT = 2,
|
|
|
|
GPOS_LOOKUP_TYPE_CURSIVE_ATTACHMENT = 3,
|
|
|
|
GPOS_LOOKUP_TYPE_MARK_TO_BASE_ATTACHMENT = 4,
|
|
|
|
GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE_ATTACHMENT = 5,
|
|
|
|
GPOS_LOOKUP_TYPE_MARK_TO_MARK_ATTACHMENT = 6,
|
|
|
|
GPOS_LOOKUP_TYPE_CONTEXT_POSITIONING = 7,
|
|
|
|
GPOS_LOOKUP_TYPE_CHAINED_CONTEXT_POSITIONING = 8,
|
|
|
|
GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING = 9
|
|
|
|
|
|
|
|
} gpos_lookup_type;
|
|
|
|
|
|
|
|
typedef enum gpos_pair_adjustment_format_
|
|
|
|
{
|
|
|
|
GPOS_PAIR_ADJUSTMENT_FORMAT_GLYPH_PAIR = 1,
|
|
|
|
GPOS_PAIR_ADJUSTMENT_FORMAT_CLASS_PAIR = 2
|
|
|
|
|
|
|
|
} gpos_pair_adjustment_format;
|
|
|
|
|
|
|
|
typedef enum gpos_value_format_bitmask_
|
|
|
|
{
|
|
|
|
GPOS_VALUE_FORMAT_NONE = 0x0000,
|
|
|
|
GPOS_VALUE_FORMAT_X_PLACEMENT = 0x0001,
|
|
|
|
GPOS_VALUE_FORMAT_Y_PLACEMENT = 0x0002,
|
|
|
|
GPOS_VALUE_FORMAT_X_ADVANCE = 0x0004,
|
|
|
|
GPOS_VALUE_FORMAT_Y_ADVANCE = 0x0008,
|
|
|
|
GPOS_VALUE_FORMAT_X_PLACEMENT_DEVICE = 0x0010,
|
|
|
|
GPOS_VALUE_FORMAT_Y_PLACEMENT_DEVICE = 0x0020,
|
|
|
|
GPOS_VALUE_FORMAT_X_ADVANCE_DEVICE = 0x0040,
|
|
|
|
GPOS_VALUE_FORMAT_Y_ADVANCE_DEVICE = 0x0080
|
|
|
|
|
|
|
|
} gpos_value_format_bitmask;
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct TT_GPOS_Subtable_Iterator_Context_
|
|
|
|
{
|
|
|
|
/* Iteration state. */
|
|
|
|
FT_Byte* current_lookup_table;
|
|
|
|
gpos_lookup_type current_lookup_type;
|
|
|
|
FT_UShort subtable_count;
|
|
|
|
FT_Byte* subtable_offsets;
|
|
|
|
FT_UInt subtable_idx;
|
|
|
|
|
|
|
|
/* Element for the current iteration. */
|
|
|
|
FT_Byte* subtable;
|
|
|
|
gpos_lookup_type subtable_type;
|
|
|
|
|
|
|
|
} TT_GPOS_Subtable_Iterator_Context;
|
|
|
|
|
|
|
|
|
|
|
|
/* Initialize a subtable iterator for a given lookup list index. */
|
|
|
|
static void
|
|
|
|
tt_gpos_subtable_iterator_init(
|
|
|
|
TT_GPOS_Subtable_Iterator_Context* context,
|
|
|
|
FT_Byte* gpos_table,
|
|
|
|
FT_ULong lookup_list_idx )
|
|
|
|
{
|
|
|
|
FT_Byte* lookup_list = gpos_table + FT_PEEK_USHORT( gpos_table + 8 );
|
|
|
|
FT_UInt16 lookup_count = FT_PEEK_USHORT( lookup_list );
|
|
|
|
|
|
|
|
|
|
|
|
if ( lookup_list_idx < lookup_count )
|
|
|
|
{
|
|
|
|
context->current_lookup_table =
|
|
|
|
lookup_list + FT_PEEK_USHORT( lookup_list + 2 + 2 * lookup_list_idx );
|
|
|
|
context->current_lookup_type =
|
|
|
|
(gpos_lookup_type)FT_PEEK_USHORT( context->current_lookup_table );
|
|
|
|
context->subtable_count =
|
|
|
|
FT_PEEK_USHORT( context->current_lookup_table + 4 );
|
|
|
|
context->subtable_offsets = context->current_lookup_table + 6;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
context->current_lookup_table = NULL;
|
|
|
|
context->current_lookup_type = 0;
|
|
|
|
context->subtable_count = 0;
|
|
|
|
context->subtable_offsets = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
context->subtable_idx = 0;
|
|
|
|
context->subtable = NULL;
|
|
|
|
context->subtable_type = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Get the next subtable. Return whether there was a next one. */
|
|
|
|
static FT_Bool
|
|
|
|
tt_gpos_subtable_iterator_next(
|
|
|
|
TT_GPOS_Subtable_Iterator_Context* context )
|
|
|
|
{
|
|
|
|
if ( context->subtable_idx < context->subtable_count )
|
|
|
|
{
|
|
|
|
FT_UShort subtable_offset =
|
|
|
|
FT_PEEK_USHORT( context->subtable_offsets +
|
|
|
|
2 * context->subtable_idx );
|
|
|
|
|
|
|
|
|
|
|
|
context->subtable = context->current_lookup_table + subtable_offset;
|
|
|
|
|
|
|
|
if ( context->current_lookup_type ==
|
|
|
|
GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING )
|
|
|
|
{
|
|
|
|
/* Update type and subtable based on extension positioning header. */
|
|
|
|
context->subtable_type =
|
|
|
|
(gpos_lookup_type)FT_PEEK_USHORT( context->subtable + 2 );
|
|
|
|
context->subtable += FT_PEEK_ULONG( context->subtable + 4 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
context->subtable_type = context->current_lookup_type;
|
|
|
|
|
|
|
|
context->subtable_idx++;
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static FT_Int
|
|
|
|
tt_gpos_get_coverage_index( FT_Byte *coverage_table,
|
|
|
|
FT_UInt glyph )
|
|
|
|
{
|
|
|
|
coverage_table_format_type coverage_format =
|
|
|
|
(coverage_table_format_type)FT_PEEK_USHORT( coverage_table );
|
|
|
|
|
|
|
|
|
|
|
|
switch ( coverage_format )
|
|
|
|
{
|
|
|
|
case COVERAGE_TABLE_FORMAT_LIST:
|
|
|
|
{
|
|
|
|
FT_UShort glyph_count = FT_PEEK_USHORT( coverage_table + 2 );
|
|
|
|
|
|
|
|
FT_Int l = 0;
|
|
|
|
FT_Int r = glyph_count - 1;
|
|
|
|
FT_Int m;
|
|
|
|
|
|
|
|
FT_Int straw;
|
|
|
|
FT_Int needle = glyph;
|
|
|
|
|
|
|
|
|
|
|
|
/* Binary search. */
|
|
|
|
while ( l <= r )
|
|
|
|
{
|
|
|
|
FT_Byte *glyph_array = coverage_table + 4;
|
|
|
|
FT_UShort glyph_id;
|
|
|
|
|
|
|
|
|
|
|
|
m = ( l + r ) >> 1;
|
|
|
|
glyph_id = FT_PEEK_USHORT( glyph_array + 2 * m );
|
|
|
|
straw = glyph_id;
|
|
|
|
|
|
|
|
if ( needle < straw )
|
|
|
|
r = m - 1;
|
|
|
|
else if ( needle > straw )
|
|
|
|
l = m + 1;
|
|
|
|
else
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case COVERAGE_TABLE_FORMAT_RANGE:
|
|
|
|
{
|
|
|
|
FT_UShort range_count = FT_PEEK_USHORT( coverage_table + 2 );
|
|
|
|
FT_Byte *range_array = coverage_table + 4;
|
|
|
|
|
|
|
|
FT_Int l = 0;
|
|
|
|
FT_Int r = range_count - 1;
|
|
|
|
FT_Int m;
|
|
|
|
|
|
|
|
FT_Int straw_start;
|
|
|
|
FT_Int straw_end;
|
|
|
|
FT_Int needle = glyph;
|
|
|
|
|
|
|
|
|
|
|
|
/* Binary search. */
|
|
|
|
while ( l <= r )
|
|
|
|
{
|
|
|
|
FT_Byte *range_record;
|
|
|
|
|
|
|
|
|
|
|
|
m = ( l + r ) >> 1;
|
|
|
|
range_record = range_array + 6 * m;
|
|
|
|
straw_start = FT_PEEK_USHORT( range_record );
|
|
|
|
straw_end = FT_PEEK_USHORT( range_record + 2 );
|
|
|
|
|
|
|
|
if ( needle < straw_start )
|
|
|
|
r = m - 1;
|
|
|
|
else if ( needle > straw_end )
|
|
|
|
l = m + 1;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
FT_UShort start_coverage_index =
|
|
|
|
FT_PEEK_USHORT( range_record + 4 );
|
|
|
|
|
|
|
|
|
|
|
|
return start_coverage_index + glyph - straw_start;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -1; /* unsupported */
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static FT_Int
|
|
|
|
tt_gpos_get_glyph_class( FT_Byte *class_def_table,
|
|
|
|
FT_UInt glyph )
|
|
|
|
{
|
|
|
|
class_def_table_format_type class_def_format =
|
|
|
|
(class_def_table_format_type)FT_PEEK_USHORT( class_def_table );
|
|
|
|
|
|
|
|
|
|
|
|
switch ( class_def_format )
|
|
|
|
{
|
|
|
|
case CLASS_DEF_TABLE_FORMAT_ARRAY:
|
|
|
|
{
|
2024-01-27 16:47:10 +01:00
|
|
|
FT_UInt start_glyph_id = FT_PEEK_USHORT( class_def_table + 2 );
|
|
|
|
FT_UInt glyph_count = FT_PEEK_USHORT( class_def_table + 4 );
|
|
|
|
FT_Byte *class_value_array = class_def_table + 6;
|
2024-01-20 01:42:48 +01:00
|
|
|
|
|
|
|
|
|
|
|
if ( glyph >= start_glyph_id &&
|
|
|
|
glyph < start_glyph_id + glyph_count )
|
|
|
|
return (FT_Int)FT_PEEK_USHORT( class_value_array +
|
|
|
|
2 * ( glyph - start_glyph_id ) );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case CLASS_DEF_TABLE_FORMAT_RANGE_GROUPS:
|
|
|
|
{
|
|
|
|
FT_UShort class_range_count = FT_PEEK_USHORT( class_def_table + 2 );
|
|
|
|
FT_Byte *class_range_records = class_def_table + 4;
|
|
|
|
|
|
|
|
FT_Int l = 0;
|
|
|
|
FT_Int r = class_range_count - 1;
|
|
|
|
FT_Int m;
|
|
|
|
|
|
|
|
FT_Int straw_start;
|
|
|
|
FT_Int straw_end;
|
|
|
|
FT_Int needle = glyph;
|
|
|
|
|
|
|
|
|
|
|
|
while ( l <= r )
|
|
|
|
{
|
|
|
|
FT_Byte *class_range_record;
|
|
|
|
|
|
|
|
|
|
|
|
m = ( l + r ) >> 1;
|
|
|
|
class_range_record = class_range_records + 6 * m;
|
|
|
|
straw_start = FT_PEEK_USHORT( class_range_record );
|
|
|
|
straw_end = FT_PEEK_USHORT( class_range_record + 2 );
|
|
|
|
|
|
|
|
if ( needle < straw_start )
|
|
|
|
r = m - 1;
|
|
|
|
else if ( needle > straw_end )
|
|
|
|
l = m + 1;
|
|
|
|
else
|
|
|
|
return (FT_Int)FT_PEEK_USHORT( class_range_record + 4 );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -1; /* Unsupported definition type, return an error. */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* "All glyphs not assigned to a class fall into class 0." */
|
|
|
|
/* (OpenType spec) */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FT_LOCAL_DEF( FT_Error )
|
|
|
|
tt_face_load_gpos( TT_Face face,
|
|
|
|
FT_Stream stream )
|
|
|
|
{
|
|
|
|
FT_Error error;
|
|
|
|
FT_ULong table_size;
|
|
|
|
|
|
|
|
|
|
|
|
/* The GPOS table is optional; exit silently if it is missing. */
|
|
|
|
error = face->goto_table( face, TTAG_GPOS, stream, &table_size );
|
|
|
|
if ( error )
|
|
|
|
goto Exit;
|
|
|
|
|
|
|
|
if ( table_size < 4 ) /* the case of a malformed table */
|
|
|
|
{
|
|
|
|
FT_ERROR(( "tt_face_load_gpos:"
|
|
|
|
" GPOS table is too small - ignored\n" ));
|
|
|
|
error = FT_THROW( Table_Missing );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( FT_FRAME_EXTRACT( table_size, face->gpos_table ) )
|
|
|
|
{
|
|
|
|
FT_ERROR(( "tt_face_load_gpos:"
|
|
|
|
" could not extract GPOS table\n" ));
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
face->gpos_kerning_available = FALSE;
|
|
|
|
|
|
|
|
if ( face->gpos_table )
|
|
|
|
{
|
|
|
|
FT_Byte* feature_list = face->gpos_table +
|
|
|
|
FT_PEEK_USHORT( face->gpos_table + 6 );
|
|
|
|
FT_UInt16 feature_count = FT_PEEK_USHORT( feature_list );
|
|
|
|
FT_Byte* feature_records = feature_list + 2;
|
|
|
|
|
|
|
|
FT_UInt idx;
|
|
|
|
|
|
|
|
|
|
|
|
for ( idx = 0; idx < feature_count; idx++, feature_records += 6 )
|
|
|
|
{
|
|
|
|
FT_ULong feature_tag = FT_PEEK_ULONG( feature_records );
|
|
|
|
|
|
|
|
|
|
|
|
if ( feature_tag == TTAG_kern )
|
|
|
|
{
|
|
|
|
face->gpos_kerning_available = TRUE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Exit:
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FT_LOCAL_DEF( void )
|
|
|
|
tt_face_done_gpos( TT_Face face )
|
|
|
|
{
|
|
|
|
FT_Stream stream = face->root.stream;
|
|
|
|
|
|
|
|
|
|
|
|
FT_FRAME_RELEASE( face->gpos_table );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FT_LOCAL_DEF( FT_Int )
|
|
|
|
tt_face_get_gpos_kerning( TT_Face face,
|
|
|
|
FT_UInt left_glyph,
|
|
|
|
FT_UInt right_glyph )
|
|
|
|
{
|
|
|
|
FT_Byte* feature_list;
|
|
|
|
FT_UInt16 feature_count;
|
|
|
|
FT_Byte* feature_records;
|
|
|
|
FT_UInt feature_idx;
|
|
|
|
|
|
|
|
|
|
|
|
if ( !face->gpos_kerning_available )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
feature_list = face->gpos_table +
|
|
|
|
FT_PEEK_USHORT( face->gpos_table + 6 );
|
|
|
|
feature_count = FT_PEEK_USHORT( feature_list );
|
|
|
|
feature_records = feature_list + 2;
|
|
|
|
|
|
|
|
for ( feature_idx = 0;
|
|
|
|
feature_idx < feature_count;
|
|
|
|
feature_idx++, feature_records += 6 )
|
|
|
|
{
|
|
|
|
FT_ULong feature_tag = FT_PEEK_ULONG( feature_records );
|
|
|
|
FT_Byte* feature_table;
|
|
|
|
FT_UInt16 lookup_idx_count;
|
|
|
|
FT_UInt16 lookup_idx;
|
|
|
|
|
|
|
|
|
|
|
|
if ( feature_tag != TTAG_kern )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
feature_table = feature_list + FT_PEEK_USHORT( feature_records + 4 );
|
|
|
|
lookup_idx_count = FT_PEEK_USHORT( feature_table + 2 );
|
|
|
|
|
|
|
|
for ( lookup_idx = 0; lookup_idx < lookup_idx_count; lookup_idx++ )
|
|
|
|
{
|
|
|
|
FT_UInt16 lookup_list_idx =
|
|
|
|
FT_PEEK_USHORT( feature_table + 4 + 2 * lookup_idx );
|
|
|
|
TT_GPOS_Subtable_Iterator_Context subtable_iter;
|
|
|
|
|
|
|
|
|
|
|
|
tt_gpos_subtable_iterator_init( &subtable_iter,
|
|
|
|
face->gpos_table,
|
|
|
|
lookup_list_idx );
|
|
|
|
|
|
|
|
while ( tt_gpos_subtable_iterator_next( &subtable_iter ) )
|
|
|
|
{
|
|
|
|
FT_Byte* subtable;
|
|
|
|
|
|
|
|
gpos_value_format_bitmask value_format_1;
|
|
|
|
gpos_value_format_bitmask value_format_2;
|
|
|
|
gpos_pair_adjustment_format format;
|
|
|
|
|
|
|
|
FT_UShort coverage_offset;
|
|
|
|
FT_Int coverage_index;
|
|
|
|
|
|
|
|
|
|
|
|
if ( subtable_iter.subtable_type !=
|
|
|
|
GPOS_LOOKUP_TYPE_PAIR_ADJUSTMENT )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
subtable = subtable_iter.subtable;
|
|
|
|
|
|
|
|
value_format_1 =
|
|
|
|
(gpos_value_format_bitmask)FT_PEEK_USHORT( subtable + 4 );
|
|
|
|
value_format_2 =
|
|
|
|
(gpos_value_format_bitmask)FT_PEEK_USHORT( subtable + 6 );
|
|
|
|
|
|
|
|
if ( !( value_format_1 == GPOS_VALUE_FORMAT_X_ADVANCE &&
|
|
|
|
value_format_2 == GPOS_VALUE_FORMAT_NONE ) )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
format = (gpos_pair_adjustment_format)FT_PEEK_USHORT( subtable );
|
|
|
|
|
|
|
|
coverage_offset = FT_PEEK_USHORT( subtable + 2 );
|
|
|
|
coverage_index =
|
|
|
|
tt_gpos_get_coverage_index( subtable + coverage_offset,
|
|
|
|
left_glyph );
|
|
|
|
|
|
|
|
if ( coverage_index == -1 )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch ( format )
|
|
|
|
{
|
|
|
|
case GPOS_PAIR_ADJUSTMENT_FORMAT_GLYPH_PAIR:
|
|
|
|
{
|
|
|
|
FT_Int l, r, m;
|
|
|
|
FT_Int straw, needle;
|
|
|
|
|
|
|
|
FT_Int value_record_pair_size_in_bytes = 2;
|
|
|
|
|
|
|
|
FT_UShort pair_set_count = FT_PEEK_USHORT( subtable + 8 );
|
|
|
|
FT_UShort pair_pos_offset;
|
|
|
|
|
|
|
|
FT_Byte* pair_value_table;
|
|
|
|
FT_UShort pair_value_count;
|
|
|
|
FT_Byte* pair_value_array;
|
|
|
|
|
|
|
|
|
|
|
|
if ( coverage_index >= pair_set_count )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pair_pos_offset =
|
|
|
|
FT_PEEK_USHORT( subtable + 10 + 2 * coverage_index );
|
|
|
|
|
|
|
|
pair_value_table = subtable + pair_pos_offset;
|
|
|
|
pair_value_count = FT_PEEK_USHORT( pair_value_table );
|
|
|
|
pair_value_array = pair_value_table + 2;
|
|
|
|
|
|
|
|
needle = right_glyph;
|
|
|
|
r = pair_value_count - 1;
|
|
|
|
l = 0;
|
|
|
|
|
|
|
|
/* Binary search. */
|
|
|
|
while ( l <= r )
|
|
|
|
{
|
|
|
|
FT_UShort second_glyph;
|
|
|
|
FT_Byte* pair_value;
|
|
|
|
|
|
|
|
|
|
|
|
m = ( l + r ) >> 1;
|
|
|
|
pair_value = pair_value_array +
|
|
|
|
( 2 + value_record_pair_size_in_bytes ) * m;
|
|
|
|
second_glyph = FT_PEEK_USHORT( pair_value );
|
|
|
|
straw = second_glyph;
|
|
|
|
|
|
|
|
if ( needle < straw )
|
|
|
|
r = m - 1;
|
|
|
|
else if ( needle > straw )
|
|
|
|
l = m + 1;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
FT_Short x_advance = FT_PEEK_SHORT( pair_value + 2 );
|
|
|
|
|
|
|
|
|
|
|
|
return x_advance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case GPOS_PAIR_ADJUSTMENT_FORMAT_CLASS_PAIR:
|
|
|
|
{
|
|
|
|
FT_UShort class_def1_offset = FT_PEEK_USHORT( subtable + 8 );
|
|
|
|
FT_UShort class_def2_offset = FT_PEEK_USHORT( subtable + 10 );
|
|
|
|
|
|
|
|
FT_Int left_glyph_class =
|
|
|
|
tt_gpos_get_glyph_class( subtable + class_def1_offset,
|
|
|
|
left_glyph );
|
|
|
|
FT_Int right_glyph_class =
|
|
|
|
tt_gpos_get_glyph_class( subtable + class_def2_offset,
|
|
|
|
right_glyph );
|
|
|
|
|
|
|
|
FT_UShort class1_count = FT_PEEK_USHORT( subtable + 12 );
|
|
|
|
FT_UShort class2_count = FT_PEEK_USHORT( subtable + 14 );
|
|
|
|
|
|
|
|
FT_Byte *class1_records, *class2_records;
|
|
|
|
FT_Short x_advance;
|
|
|
|
|
|
|
|
|
|
|
|
if ( left_glyph_class < 0 ||
|
|
|
|
left_glyph_class >= class1_count )
|
|
|
|
return 0; /* malformed */
|
|
|
|
if ( right_glyph_class < 0 ||
|
|
|
|
right_glyph_class >= class2_count )
|
|
|
|
return 0; /* malformed */
|
|
|
|
|
|
|
|
if ( right_glyph_class == 0 )
|
|
|
|
continue; /* right glyph not found in this table */
|
|
|
|
|
|
|
|
class1_records = subtable + 16;
|
|
|
|
class2_records =
|
|
|
|
class1_records + 2 * ( left_glyph_class * class2_count );
|
|
|
|
|
|
|
|
x_advance =
|
|
|
|
FT_PEEK_SHORT( class2_records + 2 * right_glyph_class );
|
|
|
|
|
|
|
|
return x_advance;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else /* !TT_CONFIG_OPTION_GPOS_KERNING */
|
|
|
|
|
|
|
|
/* ANSI C doesn't like empty source files */
|
|
|
|
typedef int tt_gpos_dummy_;
|
|
|
|
|
|
|
|
#endif /* !TT_CONFIG_OPTION_GPOS_KERNING */
|
|
|
|
|
|
|
|
|
|
|
|
/* END */
|