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.
This commit is contained in:
parent
5761778246
commit
8f286c86ef
|
@ -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
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
/**** ****/
|
||||
|
|
10
docs/CHANGES
10
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 <davidbsaltzman@gmail.com>.
|
||||
|
||||
|
||||
======================================================================
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/*************************************************************************/
|
||||
/**** ****/
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) */
|
||||
|
|
|
@ -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_, \
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <freetype/tttables.h>
|
||||
#include <freetype/internal/ftobjs.h>
|
||||
#include <freetype/ftcolor.h>
|
||||
#include "freetype/fttypes.h"
|
||||
|
||||
#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
|
||||
#include <freetype/ftmm.h>
|
||||
|
@ -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 */
|
||||
|
|
|
@ -49,6 +49,10 @@
|
|||
#include <freetype/internal/services/svbdf.h>
|
||||
#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 */
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "ttcpal.c"
|
||||
#include "ttsvg.c"
|
||||
|
||||
#include "ttgpos.c"
|
||||
#include "ttkern.c"
|
||||
#include "ttload.c"
|
||||
#include "ttmtx.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;
|
||||
|
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue