From 8f286c86ef9157ababd3294a5fb6a419756fd9d8 Mon Sep 17 00:00:00 2001 From: David Saltzman Date: Fri, 19 Jan 2024 16:42:48 -0800 Subject: [PATCH] Add support for kerning from 'GPOS' tables. 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. --- devel/ftoption.h | 16 + docs/CHANGES | 10 + include/freetype/config/ftoption.h | 16 + include/freetype/freetype.h | 33 +- include/freetype/internal/fttrace.h | 1 + include/freetype/internal/sfnt.h | 7 + include/freetype/internal/tttypes.h | 6 + src/sfnt/sfdriver.c | 15 + src/sfnt/sfnt.c | 1 + src/sfnt/sfobjs.c | 19 +- src/sfnt/ttgpos.c | 606 ++++++++++++++++++++++++++++ src/sfnt/ttgpos.h | 53 +++ src/truetype/ttdriver.c | 15 +- 13 files changed, 790 insertions(+), 8 deletions(-) create mode 100644 src/sfnt/ttgpos.c create mode 100644 src/sfnt/ttgpos.h diff --git a/devel/ftoption.h b/devel/ftoption.h index da56abc5a..7b702d444 100644 --- a/devel/ftoption.h +++ b/devel/ftoption.h @@ -757,6 +757,22 @@ FT_BEGIN_HEADER #endif + /************************************************************************** + * + * Option `TT_CONFIG_OPTION_GPOS_KERNING` enables a basic GPOS kerning + * implementation (for TrueType fonts only). With this defined, FreeType + * is able to get kerning pair data from the GPOS 'kern' feature as well as + * legacy 'kern' tables; without this defined, FreeType will only be able + * to use legacy 'kern' tables. + * + * Note that FreeType does not support more advanced GPOS layout features; + * even the 'kern' feature implemented here doesn't handle more + * sophisticated kerning variants. Use a higher-level library like + * HarfBuzz instead for that. + */ +#define TT_CONFIG_OPTION_GPOS_KERNING + + /*************************************************************************/ /*************************************************************************/ /**** ****/ diff --git a/docs/CHANGES b/docs/CHANGES index ba40d8d92..7ccfcad99 100644 --- a/docs/CHANGES +++ b/docs/CHANGES @@ -11,6 +11,16 @@ CHANGES BETWEEN 2.13.2 and 2.13.3 (202Y-Mmm-DD) large performance improvement. The rendering speed has increased and even doubled for very complex glyphs. + - If the new configuration option `TT_CONFIG_OPTION_GPOS_KERNING` is + defined, `FT_Get_Kerning` understands rudimentary GPOS kerning + (for TrueType fonts only). This is not enabled by default since + its usage is very limited, mainly for legacy applications that + have to support TrueType fonts automatically converted from 'kern' + tables to GPOS kerning. If you need proper (GPOS) kerning support + please use a higher-level library like HarfBuzz. + + Code contributed by David Saltzman . + ====================================================================== diff --git a/include/freetype/config/ftoption.h b/include/freetype/config/ftoption.h index 1976b33af..415f3d379 100644 --- a/include/freetype/config/ftoption.h +++ b/include/freetype/config/ftoption.h @@ -757,6 +757,22 @@ FT_BEGIN_HEADER #endif + /************************************************************************** + * + * Option `TT_CONFIG_OPTION_GPOS_KERNING` enables a basic GPOS kerning + * implementation (for TrueType fonts only). With this defined, FreeType + * is able to get kerning pair data from the GPOS 'kern' feature as well as + * legacy 'kern' tables; without this defined, FreeType will only be able + * to use legacy 'kern' tables. + * + * Note that FreeType does not support more advanced GPOS layout features; + * even the 'kern' feature implemented here doesn't handle more + * sophisticated kerning variants. Use a higher-level library like + * HarfBuzz instead for that. + */ +/* #define TT_CONFIG_OPTION_GPOS_KERNING */ + + /*************************************************************************/ /*************************************************************************/ /**** ****/ diff --git a/include/freetype/freetype.h b/include/freetype/freetype.h index 92acf3794..9632fbc21 100644 --- a/include/freetype/freetype.h +++ b/include/freetype/freetype.h @@ -1322,9 +1322,13 @@ FT_BEGIN_HEADER * FT_FACE_FLAG_KERNING :: * The face contains kerning information. If set, the kerning distance * can be retrieved using the function @FT_Get_Kerning. Otherwise the - * function always returns the vector (0,0). Note that FreeType - * doesn't handle kerning data from the SFNT 'GPOS' table (as present - * in many OpenType fonts). + * function always returns the vector (0,0). + * + * Note that for TrueType fonts only, FreeType supports both the 'kern' + * table and the basic, pair-wise kerning feature from the 'GPOS' table + * (with `TT_CONFIG_OPTION_GPOS_KERNING` enabled), though FreeType does + * not support the more advanced GPOS layout features; use a library + * like HarfBuzz for those instead. * * FT_FACE_FLAG_FAST_GLYPHS :: * THIS FLAG IS DEPRECATED. DO NOT USE OR TEST IT. @@ -4058,9 +4062,26 @@ FT_BEGIN_HEADER * out of the scope of this API function -- they can be implemented * through format-specific interfaces. * - * Kerning for OpenType fonts implemented in a 'GPOS' table is not - * supported; use @FT_HAS_KERNING to find out whether a font has data - * that can be extracted with `FT_Get_Kerning`. + * Note that, for TrueType fonts only, this can extract data from both + * the 'kern' table and the basic, pair-wise kerning feature from the + * GPOS table (with `TT_CONFIG_OPTION_GPOS_KERNING` enabled), though + * FreeType does not support the more advanced GPOS layout features; use + * a library like HarfBuzz for those instead. If a font has both a + * 'kern' table and kern features of a GPOS table, the 'kern' table will + * be used. + * + * Also note for right-to-left scripts, the functionality may differ for + * fonts with GPOS tables vs. 'kern' tables. For GPOS, right-to-left + * fonts typically use both a placement offset and an advance for pair + * positioning, which this API does not support, so it would output + * kerning values of zero; though if the right-to-left font used only + * advances in GPOS pair positioning, then this API could output kerning + * values for it, but it would use `left_glyph` to mean the first glyph + * for that case. Whereas 'kern' tables are always advance-only and + * always store the left glyph first. + * + * Use @FT_HAS_KERNING to find out whether a font has data that can be + * extracted with `FT_Get_Kerning`. */ FT_EXPORT( FT_Error ) FT_Get_Kerning( FT_Face face, diff --git a/include/freetype/internal/fttrace.h b/include/freetype/internal/fttrace.h index 319fe56fd..f97446956 100644 --- a/include/freetype/internal/fttrace.h +++ b/include/freetype/internal/fttrace.h @@ -64,6 +64,7 @@ FT_TRACE_DEF( ttbdf ) /* TrueType embedded BDF (ttbdf.c) */ FT_TRACE_DEF( ttcmap ) /* charmap handler (ttcmap.c) */ FT_TRACE_DEF( ttcolr ) /* glyph layer table (ttcolr.c) */ FT_TRACE_DEF( ttcpal ) /* color palette table (ttcpal.c) */ +FT_TRACE_DEF( ttgpos ) /* GPOS handler (ttgpos.c) */ FT_TRACE_DEF( ttsvg ) /* OpenType SVG table (ttsvg.c) */ FT_TRACE_DEF( ttkern ) /* kerning handler (ttkern.c) */ FT_TRACE_DEF( ttload ) /* basic TrueType tables (ttload.c) */ diff --git a/include/freetype/internal/sfnt.h b/include/freetype/internal/sfnt.h index a2d4e15ba..5cbc32b25 100644 --- a/include/freetype/internal/sfnt.h +++ b/include/freetype/internal/sfnt.h @@ -924,6 +924,7 @@ FT_BEGIN_HEADER /* this field was called `load_kerning' up to version 2.1.10 */ TT_Load_Table_Func load_kern; + TT_Load_Table_Func load_gpos; TT_Load_Table_Func load_gasp; TT_Load_Table_Func load_pclt; @@ -944,6 +945,8 @@ FT_BEGIN_HEADER /* new elements introduced after version 2.1.10 */ + TT_Face_GetKerningFunc get_gpos_kerning; + /* load the font directory, i.e., the offset table and */ /* the table directory */ TT_Load_Table_Func load_font_dir; @@ -1002,6 +1005,7 @@ FT_BEGIN_HEADER load_name_, \ free_name_, \ load_kern_, \ + load_gpos_, \ load_gasp_, \ load_pclt_, \ load_bhed_, \ @@ -1009,6 +1013,7 @@ FT_BEGIN_HEADER get_psname_, \ free_psnames_, \ get_kerning_, \ + get_gpos_kerning_, \ load_font_dir_, \ load_hmtx_, \ load_eblc_, \ @@ -1050,6 +1055,7 @@ FT_BEGIN_HEADER load_name_, \ free_name_, \ load_kern_, \ + load_gpos_, \ load_gasp_, \ load_pclt_, \ load_bhed_, \ @@ -1057,6 +1063,7 @@ FT_BEGIN_HEADER get_psname_, \ free_psnames_, \ get_kerning_, \ + get_gpos_kerning_, \ load_font_dir_, \ load_hmtx_, \ load_eblc_, \ diff --git a/include/freetype/internal/tttypes.h b/include/freetype/internal/tttypes.h index b9788c783..ccc29887e 100644 --- a/include/freetype/internal/tttypes.h +++ b/include/freetype/internal/tttypes.h @@ -24,6 +24,7 @@ #include #include #include +#include "freetype/fttypes.h" #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT #include @@ -1581,6 +1582,11 @@ FT_BEGIN_HEADER FT_UInt32 kern_avail_bits; FT_UInt32 kern_order_bits; +#ifdef TT_CONFIG_OPTION_GPOS_KERNING + FT_Byte* gpos_table; + FT_Bool gpos_kerning_available; +#endif + #ifdef TT_CONFIG_OPTION_BDF TT_BDFRec bdf; #endif /* TT_CONFIG_OPTION_BDF */ diff --git a/src/sfnt/sfdriver.c b/src/sfnt/sfdriver.c index 0925940b0..7f0e4199a 100644 --- a/src/sfnt/sfdriver.c +++ b/src/sfnt/sfdriver.c @@ -49,6 +49,10 @@ #include #endif +#ifdef TT_CONFIG_OPTION_GPOS_KERNING +#include "ttgpos.h" +#endif + #include "ttcmap.h" #include "ttkern.h" #include "ttmtx.h" @@ -1249,6 +1253,12 @@ #define PUT_PS_NAMES( a ) a #else #define PUT_PS_NAMES( a ) NULL +#endif + +#ifdef TT_CONFIG_OPTION_GPOS_KERNING +#define PUT_GPOS_KERNING( a ) a +#else +#define PUT_GPOS_KERNING( a ) NULL #endif FT_DEFINE_SFNT_INTERFACE( @@ -1274,6 +1284,8 @@ tt_face_free_name, /* TT_Free_Table_Func free_name */ tt_face_load_kern, /* TT_Load_Table_Func load_kern */ + PUT_GPOS_KERNING( tt_face_load_gpos ), + /* TT_Load_Table_Func load_gpos */ tt_face_load_gasp, /* TT_Load_Table_Func load_gasp */ tt_face_load_pclt, /* TT_Load_Table_Func load_init */ @@ -1292,6 +1304,9 @@ /* since version 2.1.8 */ tt_face_get_kerning, /* TT_Face_GetKerningFunc get_kerning */ + PUT_GPOS_KERNING( tt_face_get_gpos_kerning ), + /* TT_Face_GetKerningFunc get_gpos_kerning */ + /* since version 2.2 */ tt_face_load_font_dir, /* TT_Load_Table_Func load_font_dir */ tt_face_load_hmtx, /* TT_Load_Metrics_Func load_hmtx */ diff --git a/src/sfnt/sfnt.c b/src/sfnt/sfnt.c index 8e4f08a90..79da3b0c3 100644 --- a/src/sfnt/sfnt.c +++ b/src/sfnt/sfnt.c @@ -29,6 +29,7 @@ #include "ttcpal.c" #include "ttsvg.c" +#include "ttgpos.c" #include "ttkern.c" #include "ttload.c" #include "ttmtx.c" diff --git a/src/sfnt/sfobjs.c b/src/sfnt/sfobjs.c index f5d66ef84..be4336ecf 100644 --- a/src/sfnt/sfobjs.c +++ b/src/sfnt/sfobjs.c @@ -40,6 +40,10 @@ #include "ttbdf.h" #endif +#ifdef TT_CONFIG_OPTION_GPOS_KERNING +#include "ttgpos.h" +#endif + /************************************************************************** * @@ -1026,6 +1030,10 @@ LOAD_( gasp ); LOAD_( kern ); +#ifdef TT_CONFIG_OPTION_GPOS_KERNING + LOAD_( gpos ); +#endif + face->root.num_glyphs = face->max_profile.numGlyphs; /* Bit 8 of the `fsSelection' field in the `OS/2' table denotes */ @@ -1119,7 +1127,11 @@ flags |= FT_FACE_FLAG_VERTICAL; /* kerning available ? */ - if ( TT_FACE_HAS_KERNING( face ) ) + if ( TT_FACE_HAS_KERNING( face ) +#ifdef TT_CONFIG_OPTION_GPOS_KERNING + || face->gpos_kerning_available +#endif + ) flags |= FT_FACE_FLAG_KERNING; #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT @@ -1470,6 +1482,11 @@ /* freeing the kerning table */ tt_face_done_kern( face ); +#ifdef TT_CONFIG_OPTION_GPOS_KERNING + /* freeing the GPOS table */ + tt_face_done_gpos( face ); +#endif + /* freeing the collection table */ FT_FREE( face->ttc_header.offsets ); face->ttc_header.count = 0; diff --git a/src/sfnt/ttgpos.c b/src/sfnt/ttgpos.c new file mode 100644 index 000000000..8c7ca172b --- /dev/null +++ b/src/sfnt/ttgpos.c @@ -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 +#include +#include +#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 */ diff --git a/src/sfnt/ttgpos.h b/src/sfnt/ttgpos.h new file mode 100644 index 000000000..570e9e3d7 --- /dev/null +++ b/src/sfnt/ttgpos.h @@ -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 +#include + + +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 */ diff --git a/src/truetype/ttdriver.c b/src/truetype/ttdriver.c index d1496fec7..6b0acda37 100644 --- a/src/truetype/ttdriver.c +++ b/src/truetype/ttdriver.c @@ -217,7 +217,20 @@ kerning->y = 0; if ( sfnt ) - kerning->x = sfnt->get_kerning( ttface, left_glyph, right_glyph ); + { + /* Use 'kern' table if available since that can be faster; otherwise */ + /* use GPOS kerning pairs if available. */ + if ( ttface->kern_avail_bits != 0 ) + kerning->x = sfnt->get_kerning( ttface, + left_glyph, + right_glyph ); +#ifdef TT_CONFIG_OPTION_GPOS_KERNING + else if ( ttface->gpos_kerning_available ) + kerning->x = sfnt->get_gpos_kerning( ttface, + left_glyph, + right_glyph ); +#endif + } return 0; }