[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[freetype2] master 8f286c86e: Add support for kerning from 'GPOS' tables
From: |
Werner Lemberg |
Subject: |
[freetype2] master 8f286c86e: Add support for kerning from 'GPOS' tables. |
Date: |
Sat, 27 Jan 2024 04:55:59 -0500 (EST) |
branch: master
commit 8f286c86ef9157ababd3294a5fb6a419756fd9d8
Author: David Saltzman <davidbsaltzman@gmail.com>
Commit: Werner Lemberg <wl@gnu.org>
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(-)
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 <davidbsaltzman@gmail.com>.
+
======================================================================
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 <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 */
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 <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 */
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 <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 */
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 <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 */
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;
}
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [freetype2] master 8f286c86e: Add support for kerning from 'GPOS' tables.,
Werner Lemberg <=