freetype-commit
[Top][All Lists]
Advanced

[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;
   }



reply via email to

[Prev in Thread] Current Thread [Next in Thread]