This commit adds support for kerning from 'GPOS' tables, while maintaining support for basic 'kern' tables. `FT_HAS_KERNING` will be true for a font with either available and `FT_Get_Kerning` will still use the basic 'kern' table data if avilable, otherwise check the GPOS 'kern' feature. This feature is disabled by default; it can be enabled with the `TT_CONFIG_OPTION_GPOS_KERNING` flag. Only basic kerning (pair positioning with just an x advance) is supported from the GPOS layout features; support for that was added to make the existing `FT_Get_Kerning` API more consistently functional. FreeType does not intend to extend itself to further GPOS functionality though; a higher-level library like HarfBuzz can be used instead for that. * include/freetype/config/ftoption.h, include/devel/ftoption.h (TT_CONFIG_OPTION_GPOS_KERNING): New configuration option. * include/freetype/internal/fttrace.h: Add `ttgpos` trace handler. * include/freetype/internal/sfnt.h (SFNT_Interface): Add `load_gpos` and `get_gpos_kerning` fields. (FT_DEFINE_SFNT_INTERFACE): Updated. * include/freetype/internal/tttypes.h: Include `fttypes.h`. (TT_FaceRec) [TT_CONFIG_OPTION_GPOS_KERNING]: Add `gpos_table` and `gpos_kerning_available` fields. * src/sfnt/ttgpos.c, src/sfnt/ttgpos.h: New files. * src/sfnt/sfdriver.c [TT_CONFIG_OPTION_GPOS_KERNING]: Include `ttgpos.h`. (sfnt_interface): Updated. * src/sfnt/sfnt.c: Include `ttgpos.c`. * src/sfnt/sfobjs.c [TT_CONFIG_OPTION_GPOS_KERNING]: Include `ttgpos.h`. (sfnt_load_face) [TT_CONFIG_OPTION_GPOS_KERNING]: Load and free GPOS kerning data; check GPOS kerning availability. * src/truetype/ttdriver.c (tt_get_kerning): Use GPOS kerning if there's no 'kern' table.adjust
parent
5761778246
commit
8f286c86ef
13 changed files with 790 additions and 8 deletions
@ -0,0 +1,606 @@ |
||||
/****************************************************************************
|
||||
* |
||||
* 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: |
||||
{ |
||||
FT_UShort start_glyph_id = FT_PEEK_USHORT( class_def_table + 2 ); |
||||
FT_UShort glyph_count = FT_PEEK_USHORT( class_def_table + 4 ); |
||||
FT_Byte *class_value_array = class_def_table + 6; |
||||
|
||||
|
||||
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 */ |
@ -0,0 +1,53 @@ |
||||
/****************************************************************************
|
||||
* |
||||
* 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. |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
|
||||
#ifndef TTGPOS_H_ |
||||
#define TTGPOS_H_ |
||||
|
||||
|
||||
#include <freetype/internal/ftstream.h> |
||||
#include <freetype/internal/tttypes.h> |
||||
|
||||
|
||||
FT_BEGIN_HEADER |
||||
|
||||
|
||||
#ifdef TT_CONFIG_OPTION_GPOS_KERNING |
||||
|
||||
FT_LOCAL( FT_Error ) |
||||
tt_face_load_gpos( TT_Face face, |
||||
FT_Stream stream ); |
||||
|
||||
FT_LOCAL( void ) |
||||
tt_face_done_gpos( TT_Face face ); |
||||
|
||||
FT_LOCAL( FT_Int ) |
||||
tt_face_get_gpos_kerning( TT_Face face, |
||||
FT_UInt left_glyph, |
||||
FT_UInt right_glyph ); |
||||
|
||||
#endif /* TT_CONFIG_OPTION_GPOS_KERNING */ |
||||
|
||||
|
||||
FT_END_HEADER |
||||
|
||||
#endif /* TTGPOS_H_ */ |
||||
|
||||
|
||||
/* END */ |
Loading…
Reference in new issue