Werner Lemberg pushed to branch master at FreeType / FreeType
Commits:
-
8f286c86
by David Saltzman at 2024-01-27T10:55:04+01:00
13 changed files:
- devel/ftoption.h
- docs/CHANGES
- include/freetype/config/ftoption.h
- include/freetype/freetype.h
- include/freetype/internal/fttrace.h
- include/freetype/internal/sfnt.h
- include/freetype/internal/tttypes.h
- src/sfnt/sfdriver.c
- src/sfnt/sfnt.c
- src/sfnt/sfobjs.c
- + src/sfnt/ttgpos.c
- + src/sfnt/ttgpos.h
- src/truetype/ttdriver.c
Changes:
... | ... | @@ -757,6 +757,22 @@ FT_BEGIN_HEADER |
757 | 757 | #endif
|
758 | 758 | |
759 | 759 | |
760 | + /**************************************************************************
|
|
761 | + *
|
|
762 | + * Option `TT_CONFIG_OPTION_GPOS_KERNING` enables a basic GPOS kerning
|
|
763 | + * implementation (for TrueType fonts only). With this defined, FreeType
|
|
764 | + * is able to get kerning pair data from the GPOS 'kern' feature as well as
|
|
765 | + * legacy 'kern' tables; without this defined, FreeType will only be able
|
|
766 | + * to use legacy 'kern' tables.
|
|
767 | + *
|
|
768 | + * Note that FreeType does not support more advanced GPOS layout features;
|
|
769 | + * even the 'kern' feature implemented here doesn't handle more
|
|
770 | + * sophisticated kerning variants. Use a higher-level library like
|
|
771 | + * HarfBuzz instead for that.
|
|
772 | + */
|
|
773 | +#define TT_CONFIG_OPTION_GPOS_KERNING
|
|
774 | + |
|
775 | + |
|
760 | 776 | /*************************************************************************/
|
761 | 777 | /*************************************************************************/
|
762 | 778 | /**** ****/
|
... | ... | @@ -11,6 +11,16 @@ CHANGES BETWEEN 2.13.2 and 2.13.3 (202Y-Mmm-DD) |
11 | 11 | large performance improvement. The rendering speed has increased
|
12 | 12 | and even doubled for very complex glyphs.
|
13 | 13 | |
14 | + - If the new configuration option `TT_CONFIG_OPTION_GPOS_KERNING` is
|
|
15 | + defined, `FT_Get_Kerning` understands rudimentary GPOS kerning
|
|
16 | + (for TrueType fonts only). This is not enabled by default since
|
|
17 | + its usage is very limited, mainly for legacy applications that
|
|
18 | + have to support TrueType fonts automatically converted from 'kern'
|
|
19 | + tables to GPOS kerning. If you need proper (GPOS) kerning support
|
|
20 | + please use a higher-level library like HarfBuzz.
|
|
21 | + |
|
22 | + Code contributed by David Saltzman <davidbsaltzman@gmail.com>.
|
|
23 | + |
|
14 | 24 | |
15 | 25 | ======================================================================
|
16 | 26 |
... | ... | @@ -757,6 +757,22 @@ FT_BEGIN_HEADER |
757 | 757 | #endif
|
758 | 758 | |
759 | 759 | |
760 | + /**************************************************************************
|
|
761 | + *
|
|
762 | + * Option `TT_CONFIG_OPTION_GPOS_KERNING` enables a basic GPOS kerning
|
|
763 | + * implementation (for TrueType fonts only). With this defined, FreeType
|
|
764 | + * is able to get kerning pair data from the GPOS 'kern' feature as well as
|
|
765 | + * legacy 'kern' tables; without this defined, FreeType will only be able
|
|
766 | + * to use legacy 'kern' tables.
|
|
767 | + *
|
|
768 | + * Note that FreeType does not support more advanced GPOS layout features;
|
|
769 | + * even the 'kern' feature implemented here doesn't handle more
|
|
770 | + * sophisticated kerning variants. Use a higher-level library like
|
|
771 | + * HarfBuzz instead for that.
|
|
772 | + */
|
|
773 | +/* #define TT_CONFIG_OPTION_GPOS_KERNING */
|
|
774 | + |
|
775 | + |
|
760 | 776 | /*************************************************************************/
|
761 | 777 | /*************************************************************************/
|
762 | 778 | /**** ****/
|
... | ... | @@ -1322,9 +1322,13 @@ FT_BEGIN_HEADER |
1322 | 1322 | * FT_FACE_FLAG_KERNING ::
|
1323 | 1323 | * The face contains kerning information. If set, the kerning distance
|
1324 | 1324 | * can be retrieved using the function @FT_Get_Kerning. Otherwise the
|
1325 | - * function always returns the vector (0,0). Note that FreeType
|
|
1326 | - * doesn't handle kerning data from the SFNT 'GPOS' table (as present
|
|
1327 | - * in many OpenType fonts).
|
|
1325 | + * function always returns the vector (0,0).
|
|
1326 | + *
|
|
1327 | + * Note that for TrueType fonts only, FreeType supports both the 'kern'
|
|
1328 | + * table and the basic, pair-wise kerning feature from the 'GPOS' table
|
|
1329 | + * (with `TT_CONFIG_OPTION_GPOS_KERNING` enabled), though FreeType does
|
|
1330 | + * not support the more advanced GPOS layout features; use a library
|
|
1331 | + * like HarfBuzz for those instead.
|
|
1328 | 1332 | *
|
1329 | 1333 | * FT_FACE_FLAG_FAST_GLYPHS ::
|
1330 | 1334 | * THIS FLAG IS DEPRECATED. DO NOT USE OR TEST IT.
|
... | ... | @@ -4058,9 +4062,26 @@ FT_BEGIN_HEADER |
4058 | 4062 | * out of the scope of this API function -- they can be implemented
|
4059 | 4063 | * through format-specific interfaces.
|
4060 | 4064 | *
|
4061 | - * Kerning for OpenType fonts implemented in a 'GPOS' table is not
|
|
4062 | - * supported; use @FT_HAS_KERNING to find out whether a font has data
|
|
4063 | - * that can be extracted with `FT_Get_Kerning`.
|
|
4065 | + * Note that, for TrueType fonts only, this can extract data from both
|
|
4066 | + * the 'kern' table and the basic, pair-wise kerning feature from the
|
|
4067 | + * GPOS table (with `TT_CONFIG_OPTION_GPOS_KERNING` enabled), though
|
|
4068 | + * FreeType does not support the more advanced GPOS layout features; use
|
|
4069 | + * a library like HarfBuzz for those instead. If a font has both a
|
|
4070 | + * 'kern' table and kern features of a GPOS table, the 'kern' table will
|
|
4071 | + * be used.
|
|
4072 | + *
|
|
4073 | + * Also note for right-to-left scripts, the functionality may differ for
|
|
4074 | + * fonts with GPOS tables vs. 'kern' tables. For GPOS, right-to-left
|
|
4075 | + * fonts typically use both a placement offset and an advance for pair
|
|
4076 | + * positioning, which this API does not support, so it would output
|
|
4077 | + * kerning values of zero; though if the right-to-left font used only
|
|
4078 | + * advances in GPOS pair positioning, then this API could output kerning
|
|
4079 | + * values for it, but it would use `left_glyph` to mean the first glyph
|
|
4080 | + * for that case. Whereas 'kern' tables are always advance-only and
|
|
4081 | + * always store the left glyph first.
|
|
4082 | + *
|
|
4083 | + * Use @FT_HAS_KERNING to find out whether a font has data that can be
|
|
4084 | + * extracted with `FT_Get_Kerning`.
|
|
4064 | 4085 | */
|
4065 | 4086 | FT_EXPORT( FT_Error )
|
4066 | 4087 | FT_Get_Kerning( FT_Face face,
|
... | ... | @@ -64,6 +64,7 @@ FT_TRACE_DEF( ttbdf ) /* TrueType embedded BDF (ttbdf.c) */ |
64 | 64 | FT_TRACE_DEF( ttcmap ) /* charmap handler (ttcmap.c) */
|
65 | 65 | FT_TRACE_DEF( ttcolr ) /* glyph layer table (ttcolr.c) */
|
66 | 66 | FT_TRACE_DEF( ttcpal ) /* color palette table (ttcpal.c) */
|
67 | +FT_TRACE_DEF( ttgpos ) /* GPOS handler (ttgpos.c) */
|
|
67 | 68 | FT_TRACE_DEF( ttsvg ) /* OpenType SVG table (ttsvg.c) */
|
68 | 69 | FT_TRACE_DEF( ttkern ) /* kerning handler (ttkern.c) */
|
69 | 70 | FT_TRACE_DEF( ttload ) /* basic TrueType tables (ttload.c) */
|
... | ... | @@ -924,6 +924,7 @@ FT_BEGIN_HEADER |
924 | 924 | /* this field was called `load_kerning' up to version 2.1.10 */
|
925 | 925 | TT_Load_Table_Func load_kern;
|
926 | 926 | |
927 | + TT_Load_Table_Func load_gpos;
|
|
927 | 928 | TT_Load_Table_Func load_gasp;
|
928 | 929 | TT_Load_Table_Func load_pclt;
|
929 | 930 | |
... | ... | @@ -944,6 +945,8 @@ FT_BEGIN_HEADER |
944 | 945 | |
945 | 946 | /* new elements introduced after version 2.1.10 */
|
946 | 947 | |
948 | + TT_Face_GetKerningFunc get_gpos_kerning;
|
|
949 | + |
|
947 | 950 | /* load the font directory, i.e., the offset table and */
|
948 | 951 | /* the table directory */
|
949 | 952 | TT_Load_Table_Func load_font_dir;
|
... | ... | @@ -1002,6 +1005,7 @@ FT_BEGIN_HEADER |
1002 | 1005 | load_name_, \
|
1003 | 1006 | free_name_, \
|
1004 | 1007 | load_kern_, \
|
1008 | + load_gpos_, \
|
|
1005 | 1009 | load_gasp_, \
|
1006 | 1010 | load_pclt_, \
|
1007 | 1011 | load_bhed_, \
|
... | ... | @@ -1009,6 +1013,7 @@ FT_BEGIN_HEADER |
1009 | 1013 | get_psname_, \
|
1010 | 1014 | free_psnames_, \
|
1011 | 1015 | get_kerning_, \
|
1016 | + get_gpos_kerning_, \
|
|
1012 | 1017 | load_font_dir_, \
|
1013 | 1018 | load_hmtx_, \
|
1014 | 1019 | load_eblc_, \
|
... | ... | @@ -1050,6 +1055,7 @@ FT_BEGIN_HEADER |
1050 | 1055 | load_name_, \
|
1051 | 1056 | free_name_, \
|
1052 | 1057 | load_kern_, \
|
1058 | + load_gpos_, \
|
|
1053 | 1059 | load_gasp_, \
|
1054 | 1060 | load_pclt_, \
|
1055 | 1061 | load_bhed_, \
|
... | ... | @@ -1057,6 +1063,7 @@ FT_BEGIN_HEADER |
1057 | 1063 | get_psname_, \
|
1058 | 1064 | free_psnames_, \
|
1059 | 1065 | get_kerning_, \
|
1066 | + get_gpos_kerning_, \
|
|
1060 | 1067 | load_font_dir_, \
|
1061 | 1068 | load_hmtx_, \
|
1062 | 1069 | load_eblc_, \
|
... | ... | @@ -24,6 +24,7 @@ |
24 | 24 | #include <freetype/tttables.h>
|
25 | 25 | #include <freetype/internal/ftobjs.h>
|
26 | 26 | #include <freetype/ftcolor.h>
|
27 | +#include "freetype/fttypes.h"
|
|
27 | 28 | |
28 | 29 | #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
|
29 | 30 | #include <freetype/ftmm.h>
|
... | ... | @@ -1581,6 +1582,11 @@ FT_BEGIN_HEADER |
1581 | 1582 | FT_UInt32 kern_avail_bits;
|
1582 | 1583 | FT_UInt32 kern_order_bits;
|
1583 | 1584 | |
1585 | +#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
1586 | + FT_Byte* gpos_table;
|
|
1587 | + FT_Bool gpos_kerning_available;
|
|
1588 | +#endif
|
|
1589 | + |
|
1584 | 1590 | #ifdef TT_CONFIG_OPTION_BDF
|
1585 | 1591 | TT_BDFRec bdf;
|
1586 | 1592 | #endif /* TT_CONFIG_OPTION_BDF */
|
... | ... | @@ -49,6 +49,10 @@ |
49 | 49 | #include <freetype/internal/services/svbdf.h>
|
50 | 50 | #endif
|
51 | 51 | |
52 | +#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
53 | +#include "ttgpos.h"
|
|
54 | +#endif
|
|
55 | + |
|
52 | 56 | #include "ttcmap.h"
|
53 | 57 | #include "ttkern.h"
|
54 | 58 | #include "ttmtx.h"
|
... | ... | @@ -1249,6 +1253,12 @@ |
1249 | 1253 | #define PUT_PS_NAMES( a ) a
|
1250 | 1254 | #else
|
1251 | 1255 | #define PUT_PS_NAMES( a ) NULL
|
1256 | +#endif
|
|
1257 | + |
|
1258 | +#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
1259 | +#define PUT_GPOS_KERNING( a ) a
|
|
1260 | +#else
|
|
1261 | +#define PUT_GPOS_KERNING( a ) NULL
|
|
1252 | 1262 | #endif
|
1253 | 1263 | |
1254 | 1264 | FT_DEFINE_SFNT_INTERFACE(
|
... | ... | @@ -1274,6 +1284,8 @@ |
1274 | 1284 | tt_face_free_name, /* TT_Free_Table_Func free_name */
|
1275 | 1285 | |
1276 | 1286 | tt_face_load_kern, /* TT_Load_Table_Func load_kern */
|
1287 | + PUT_GPOS_KERNING( tt_face_load_gpos ),
|
|
1288 | + /* TT_Load_Table_Func load_gpos */
|
|
1277 | 1289 | tt_face_load_gasp, /* TT_Load_Table_Func load_gasp */
|
1278 | 1290 | tt_face_load_pclt, /* TT_Load_Table_Func load_init */
|
1279 | 1291 | |
... | ... | @@ -1292,6 +1304,9 @@ |
1292 | 1304 | /* since version 2.1.8 */
|
1293 | 1305 | tt_face_get_kerning, /* TT_Face_GetKerningFunc get_kerning */
|
1294 | 1306 | |
1307 | + PUT_GPOS_KERNING( tt_face_get_gpos_kerning ),
|
|
1308 | + /* TT_Face_GetKerningFunc get_gpos_kerning */
|
|
1309 | + |
|
1295 | 1310 | /* since version 2.2 */
|
1296 | 1311 | tt_face_load_font_dir, /* TT_Load_Table_Func load_font_dir */
|
1297 | 1312 | tt_face_load_hmtx, /* TT_Load_Metrics_Func load_hmtx */
|
... | ... | @@ -29,6 +29,7 @@ |
29 | 29 | #include "ttcpal.c"
|
30 | 30 | #include "ttsvg.c"
|
31 | 31 | |
32 | +#include "ttgpos.c"
|
|
32 | 33 | #include "ttkern.c"
|
33 | 34 | #include "ttload.c"
|
34 | 35 | #include "ttmtx.c"
|
... | ... | @@ -40,6 +40,10 @@ |
40 | 40 | #include "ttbdf.h"
|
41 | 41 | #endif
|
42 | 42 | |
43 | +#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
44 | +#include "ttgpos.h"
|
|
45 | +#endif
|
|
46 | + |
|
43 | 47 | |
44 | 48 | /**************************************************************************
|
45 | 49 | *
|
... | ... | @@ -1026,6 +1030,10 @@ |
1026 | 1030 | LOAD_( gasp );
|
1027 | 1031 | LOAD_( kern );
|
1028 | 1032 | |
1033 | +#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
1034 | + LOAD_( gpos );
|
|
1035 | +#endif
|
|
1036 | + |
|
1029 | 1037 | face->root.num_glyphs = face->max_profile.numGlyphs;
|
1030 | 1038 | |
1031 | 1039 | /* Bit 8 of the `fsSelection' field in the `OS/2' table denotes */
|
... | ... | @@ -1119,7 +1127,11 @@ |
1119 | 1127 | flags |= FT_FACE_FLAG_VERTICAL;
|
1120 | 1128 | |
1121 | 1129 | /* kerning available ? */
|
1122 | - if ( TT_FACE_HAS_KERNING( face ) )
|
|
1130 | + if ( TT_FACE_HAS_KERNING( face )
|
|
1131 | +#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
1132 | + || face->gpos_kerning_available
|
|
1133 | +#endif
|
|
1134 | + )
|
|
1123 | 1135 | flags |= FT_FACE_FLAG_KERNING;
|
1124 | 1136 | |
1125 | 1137 | #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
|
... | ... | @@ -1470,6 +1482,11 @@ |
1470 | 1482 | /* freeing the kerning table */
|
1471 | 1483 | tt_face_done_kern( face );
|
1472 | 1484 | |
1485 | +#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
1486 | + /* freeing the GPOS table */
|
|
1487 | + tt_face_done_gpos( face );
|
|
1488 | +#endif
|
|
1489 | + |
|
1473 | 1490 | /* freeing the collection table */
|
1474 | 1491 | FT_FREE( face->ttc_header.offsets );
|
1475 | 1492 | face->ttc_header.count = 0;
|
1 | +/****************************************************************************
|
|
2 | + *
|
|
3 | + * ttgpos.c
|
|
4 | + *
|
|
5 | + * Load the TrueType GPOS table. The only GPOS layout feature this
|
|
6 | + * currently supports is kerning, from x advances in the pair adjustment
|
|
7 | + * layout feature.
|
|
8 | + *
|
|
9 | + * Parts of the implementation were adapted from:
|
|
10 | + * https://github.com/nothings/stb/blob/master/stb_truetype.h
|
|
11 | + *
|
|
12 | + * GPOS spec reference available at:
|
|
13 | + * https://learn.microsoft.com/en-us/typography/opentype/spec/gpos
|
|
14 | + *
|
|
15 | + * Copyright (C) 2024 by
|
|
16 | + * David Saltzman
|
|
17 | + *
|
|
18 | + * This file is part of the FreeType project, and may only be used,
|
|
19 | + * modified, and distributed under the terms of the FreeType project
|
|
20 | + * license, LICENSE.TXT. By continuing to use, modify, or distribute
|
|
21 | + * this file you indicate that you have read the license and
|
|
22 | + * understand and accept it fully.
|
|
23 | + */
|
|
24 | + |
|
25 | +#include <freetype/internal/ftdebug.h>
|
|
26 | +#include <freetype/internal/ftstream.h>
|
|
27 | +#include <freetype/tttags.h>
|
|
28 | +#include "freetype/fttypes.h"
|
|
29 | +#include "freetype/internal/ftobjs.h"
|
|
30 | +#include "ttgpos.h"
|
|
31 | + |
|
32 | + |
|
33 | +#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
34 | + |
|
35 | + /**************************************************************************
|
|
36 | + *
|
|
37 | + * The macro FT_COMPONENT is used in trace mode. It is an implicit
|
|
38 | + * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
|
|
39 | + * messages during execution.
|
|
40 | + */
|
|
41 | +#undef FT_COMPONENT
|
|
42 | +#define FT_COMPONENT ttgpos
|
|
43 | + |
|
44 | + |
|
45 | + typedef enum coverage_table_format_type_
|
|
46 | + {
|
|
47 | + COVERAGE_TABLE_FORMAT_LIST = 1,
|
|
48 | + COVERAGE_TABLE_FORMAT_RANGE = 2
|
|
49 | + |
|
50 | + } coverage_table_format_type;
|
|
51 | + |
|
52 | + typedef enum class_def_table_format_type_
|
|
53 | + {
|
|
54 | + CLASS_DEF_TABLE_FORMAT_ARRAY = 1,
|
|
55 | + CLASS_DEF_TABLE_FORMAT_RANGE_GROUPS = 2
|
|
56 | + |
|
57 | + } class_def_table_format_type;
|
|
58 | + |
|
59 | + typedef enum gpos_lookup_type_
|
|
60 | + {
|
|
61 | + GPOS_LOOKUP_TYPE_SINGLE_ADJUSTMENT = 1,
|
|
62 | + GPOS_LOOKUP_TYPE_PAIR_ADJUSTMENT = 2,
|
|
63 | + GPOS_LOOKUP_TYPE_CURSIVE_ATTACHMENT = 3,
|
|
64 | + GPOS_LOOKUP_TYPE_MARK_TO_BASE_ATTACHMENT = 4,
|
|
65 | + GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE_ATTACHMENT = 5,
|
|
66 | + GPOS_LOOKUP_TYPE_MARK_TO_MARK_ATTACHMENT = 6,
|
|
67 | + GPOS_LOOKUP_TYPE_CONTEXT_POSITIONING = 7,
|
|
68 | + GPOS_LOOKUP_TYPE_CHAINED_CONTEXT_POSITIONING = 8,
|
|
69 | + GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING = 9
|
|
70 | + |
|
71 | + } gpos_lookup_type;
|
|
72 | + |
|
73 | + typedef enum gpos_pair_adjustment_format_
|
|
74 | + {
|
|
75 | + GPOS_PAIR_ADJUSTMENT_FORMAT_GLYPH_PAIR = 1,
|
|
76 | + GPOS_PAIR_ADJUSTMENT_FORMAT_CLASS_PAIR = 2
|
|
77 | + |
|
78 | + } gpos_pair_adjustment_format;
|
|
79 | + |
|
80 | + typedef enum gpos_value_format_bitmask_
|
|
81 | + {
|
|
82 | + GPOS_VALUE_FORMAT_NONE = 0x0000,
|
|
83 | + GPOS_VALUE_FORMAT_X_PLACEMENT = 0x0001,
|
|
84 | + GPOS_VALUE_FORMAT_Y_PLACEMENT = 0x0002,
|
|
85 | + GPOS_VALUE_FORMAT_X_ADVANCE = 0x0004,
|
|
86 | + GPOS_VALUE_FORMAT_Y_ADVANCE = 0x0008,
|
|
87 | + GPOS_VALUE_FORMAT_X_PLACEMENT_DEVICE = 0x0010,
|
|
88 | + GPOS_VALUE_FORMAT_Y_PLACEMENT_DEVICE = 0x0020,
|
|
89 | + GPOS_VALUE_FORMAT_X_ADVANCE_DEVICE = 0x0040,
|
|
90 | + GPOS_VALUE_FORMAT_Y_ADVANCE_DEVICE = 0x0080
|
|
91 | + |
|
92 | + } gpos_value_format_bitmask;
|
|
93 | + |
|
94 | + |
|
95 | + typedef struct TT_GPOS_Subtable_Iterator_Context_
|
|
96 | + {
|
|
97 | + /* Iteration state. */
|
|
98 | + FT_Byte* current_lookup_table;
|
|
99 | + gpos_lookup_type current_lookup_type;
|
|
100 | + FT_UShort subtable_count;
|
|
101 | + FT_Byte* subtable_offsets;
|
|
102 | + FT_UInt subtable_idx;
|
|
103 | + |
|
104 | + /* Element for the current iteration. */
|
|
105 | + FT_Byte* subtable;
|
|
106 | + gpos_lookup_type subtable_type;
|
|
107 | + |
|
108 | + } TT_GPOS_Subtable_Iterator_Context;
|
|
109 | + |
|
110 | + |
|
111 | + /* Initialize a subtable iterator for a given lookup list index. */
|
|
112 | + static void
|
|
113 | + tt_gpos_subtable_iterator_init(
|
|
114 | + TT_GPOS_Subtable_Iterator_Context* context,
|
|
115 | + FT_Byte* gpos_table,
|
|
116 | + FT_ULong lookup_list_idx )
|
|
117 | + {
|
|
118 | + FT_Byte* lookup_list = gpos_table + FT_PEEK_USHORT( gpos_table + 8 );
|
|
119 | + FT_UInt16 lookup_count = FT_PEEK_USHORT( lookup_list );
|
|
120 | + |
|
121 | + |
|
122 | + if ( lookup_list_idx < lookup_count )
|
|
123 | + {
|
|
124 | + context->current_lookup_table =
|
|
125 | + lookup_list + FT_PEEK_USHORT( lookup_list + 2 + 2 * lookup_list_idx );
|
|
126 | + context->current_lookup_type =
|
|
127 | + (gpos_lookup_type)FT_PEEK_USHORT( context->current_lookup_table );
|
|
128 | + context->subtable_count =
|
|
129 | + FT_PEEK_USHORT( context->current_lookup_table + 4 );
|
|
130 | + context->subtable_offsets = context->current_lookup_table + 6;
|
|
131 | + }
|
|
132 | + else
|
|
133 | + {
|
|
134 | + context->current_lookup_table = NULL;
|
|
135 | + context->current_lookup_type = 0;
|
|
136 | + context->subtable_count = 0;
|
|
137 | + context->subtable_offsets = NULL;
|
|
138 | + }
|
|
139 | + |
|
140 | + context->subtable_idx = 0;
|
|
141 | + context->subtable = NULL;
|
|
142 | + context->subtable_type = 0;
|
|
143 | + }
|
|
144 | + |
|
145 | + |
|
146 | + /* Get the next subtable. Return whether there was a next one. */
|
|
147 | + static FT_Bool
|
|
148 | + tt_gpos_subtable_iterator_next(
|
|
149 | + TT_GPOS_Subtable_Iterator_Context* context )
|
|
150 | + {
|
|
151 | + if ( context->subtable_idx < context->subtable_count )
|
|
152 | + {
|
|
153 | + FT_UShort subtable_offset =
|
|
154 | + FT_PEEK_USHORT( context->subtable_offsets +
|
|
155 | + 2 * context->subtable_idx );
|
|
156 | + |
|
157 | + |
|
158 | + context->subtable = context->current_lookup_table + subtable_offset;
|
|
159 | + |
|
160 | + if ( context->current_lookup_type ==
|
|
161 | + GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING )
|
|
162 | + {
|
|
163 | + /* Update type and subtable based on extension positioning header. */
|
|
164 | + context->subtable_type =
|
|
165 | + (gpos_lookup_type)FT_PEEK_USHORT( context->subtable + 2 );
|
|
166 | + context->subtable += FT_PEEK_ULONG( context->subtable + 4 );
|
|
167 | + }
|
|
168 | + else
|
|
169 | + context->subtable_type = context->current_lookup_type;
|
|
170 | + |
|
171 | + context->subtable_idx++;
|
|
172 | + return TRUE;
|
|
173 | + }
|
|
174 | + |
|
175 | + return FALSE;
|
|
176 | + }
|
|
177 | + |
|
178 | + |
|
179 | + static FT_Int
|
|
180 | + tt_gpos_get_coverage_index( FT_Byte *coverage_table,
|
|
181 | + FT_UInt glyph )
|
|
182 | + {
|
|
183 | + coverage_table_format_type coverage_format =
|
|
184 | + (coverage_table_format_type)FT_PEEK_USHORT( coverage_table );
|
|
185 | + |
|
186 | + |
|
187 | + switch ( coverage_format )
|
|
188 | + {
|
|
189 | + case COVERAGE_TABLE_FORMAT_LIST:
|
|
190 | + {
|
|
191 | + FT_UShort glyph_count = FT_PEEK_USHORT( coverage_table + 2 );
|
|
192 | + |
|
193 | + FT_Int l = 0;
|
|
194 | + FT_Int r = glyph_count - 1;
|
|
195 | + FT_Int m;
|
|
196 | + |
|
197 | + FT_Int straw;
|
|
198 | + FT_Int needle = glyph;
|
|
199 | + |
|
200 | + |
|
201 | + /* Binary search. */
|
|
202 | + while ( l <= r )
|
|
203 | + {
|
|
204 | + FT_Byte *glyph_array = coverage_table + 4;
|
|
205 | + FT_UShort glyph_id;
|
|
206 | + |
|
207 | + |
|
208 | + m = ( l + r ) >> 1;
|
|
209 | + glyph_id = FT_PEEK_USHORT( glyph_array + 2 * m );
|
|
210 | + straw = glyph_id;
|
|
211 | + |
|
212 | + if ( needle < straw )
|
|
213 | + r = m - 1;
|
|
214 | + else if ( needle > straw )
|
|
215 | + l = m + 1;
|
|
216 | + else
|
|
217 | + return m;
|
|
218 | + }
|
|
219 | + break;
|
|
220 | + }
|
|
221 | + |
|
222 | + case COVERAGE_TABLE_FORMAT_RANGE:
|
|
223 | + {
|
|
224 | + FT_UShort range_count = FT_PEEK_USHORT( coverage_table + 2 );
|
|
225 | + FT_Byte *range_array = coverage_table + 4;
|
|
226 | + |
|
227 | + FT_Int l = 0;
|
|
228 | + FT_Int r = range_count - 1;
|
|
229 | + FT_Int m;
|
|
230 | + |
|
231 | + FT_Int straw_start;
|
|
232 | + FT_Int straw_end;
|
|
233 | + FT_Int needle = glyph;
|
|
234 | + |
|
235 | + |
|
236 | + /* Binary search. */
|
|
237 | + while ( l <= r )
|
|
238 | + {
|
|
239 | + FT_Byte *range_record;
|
|
240 | + |
|
241 | + |
|
242 | + m = ( l + r ) >> 1;
|
|
243 | + range_record = range_array + 6 * m;
|
|
244 | + straw_start = FT_PEEK_USHORT( range_record );
|
|
245 | + straw_end = FT_PEEK_USHORT( range_record + 2 );
|
|
246 | + |
|
247 | + if ( needle < straw_start )
|
|
248 | + r = m - 1;
|
|
249 | + else if ( needle > straw_end )
|
|
250 | + l = m + 1;
|
|
251 | + else
|
|
252 | + {
|
|
253 | + FT_UShort start_coverage_index =
|
|
254 | + FT_PEEK_USHORT( range_record + 4 );
|
|
255 | + |
|
256 | + |
|
257 | + return start_coverage_index + glyph - straw_start;
|
|
258 | + }
|
|
259 | + }
|
|
260 | + break;
|
|
261 | + }
|
|
262 | + |
|
263 | + default:
|
|
264 | + return -1; /* unsupported */
|
|
265 | + }
|
|
266 | + |
|
267 | + return -1;
|
|
268 | + }
|
|
269 | + |
|
270 | + |
|
271 | + static FT_Int
|
|
272 | + tt_gpos_get_glyph_class( FT_Byte *class_def_table,
|
|
273 | + FT_UInt glyph )
|
|
274 | + {
|
|
275 | + class_def_table_format_type class_def_format =
|
|
276 | + (class_def_table_format_type)FT_PEEK_USHORT( class_def_table );
|
|
277 | + |
|
278 | + |
|
279 | + switch ( class_def_format )
|
|
280 | + {
|
|
281 | + case CLASS_DEF_TABLE_FORMAT_ARRAY:
|
|
282 | + {
|
|
283 | + FT_UShort start_glyph_id = FT_PEEK_USHORT( class_def_table + 2 );
|
|
284 | + FT_UShort glyph_count = FT_PEEK_USHORT( class_def_table + 4 );
|
|
285 | + FT_Byte *class_value_array = class_def_table + 6;
|
|
286 | + |
|
287 | + |
|
288 | + if ( glyph >= start_glyph_id &&
|
|
289 | + glyph < start_glyph_id + glyph_count )
|
|
290 | + return (FT_Int)FT_PEEK_USHORT( class_value_array +
|
|
291 | + 2 * ( glyph - start_glyph_id ) );
|
|
292 | + break;
|
|
293 | + }
|
|
294 | + |
|
295 | + case CLASS_DEF_TABLE_FORMAT_RANGE_GROUPS:
|
|
296 | + {
|
|
297 | + FT_UShort class_range_count = FT_PEEK_USHORT( class_def_table + 2 );
|
|
298 | + FT_Byte *class_range_records = class_def_table + 4;
|
|
299 | + |
|
300 | + FT_Int l = 0;
|
|
301 | + FT_Int r = class_range_count - 1;
|
|
302 | + FT_Int m;
|
|
303 | + |
|
304 | + FT_Int straw_start;
|
|
305 | + FT_Int straw_end;
|
|
306 | + FT_Int needle = glyph;
|
|
307 | + |
|
308 | + |
|
309 | + while ( l <= r )
|
|
310 | + {
|
|
311 | + FT_Byte *class_range_record;
|
|
312 | + |
|
313 | + |
|
314 | + m = ( l + r ) >> 1;
|
|
315 | + class_range_record = class_range_records + 6 * m;
|
|
316 | + straw_start = FT_PEEK_USHORT( class_range_record );
|
|
317 | + straw_end = FT_PEEK_USHORT( class_range_record + 2 );
|
|
318 | + |
|
319 | + if ( needle < straw_start )
|
|
320 | + r = m - 1;
|
|
321 | + else if ( needle > straw_end )
|
|
322 | + l = m + 1;
|
|
323 | + else
|
|
324 | + return (FT_Int)FT_PEEK_USHORT( class_range_record + 4 );
|
|
325 | + }
|
|
326 | + break;
|
|
327 | + }
|
|
328 | + |
|
329 | + default:
|
|
330 | + return -1; /* Unsupported definition type, return an error. */
|
|
331 | + }
|
|
332 | + |
|
333 | + /* "All glyphs not assigned to a class fall into class 0." */
|
|
334 | + /* (OpenType spec) */
|
|
335 | + return 0;
|
|
336 | + }
|
|
337 | + |
|
338 | + |
|
339 | + FT_LOCAL_DEF( FT_Error )
|
|
340 | + tt_face_load_gpos( TT_Face face,
|
|
341 | + FT_Stream stream )
|
|
342 | + {
|
|
343 | + FT_Error error;
|
|
344 | + FT_ULong table_size;
|
|
345 | + |
|
346 | + |
|
347 | + /* The GPOS table is optional; exit silently if it is missing. */
|
|
348 | + error = face->goto_table( face, TTAG_GPOS, stream, &table_size );
|
|
349 | + if ( error )
|
|
350 | + goto Exit;
|
|
351 | + |
|
352 | + if ( table_size < 4 ) /* the case of a malformed table */
|
|
353 | + {
|
|
354 | + FT_ERROR(( "tt_face_load_gpos:"
|
|
355 | + " GPOS table is too small - ignored\n" ));
|
|
356 | + error = FT_THROW( Table_Missing );
|
|
357 | + goto Exit;
|
|
358 | + }
|
|
359 | + |
|
360 | + if ( FT_FRAME_EXTRACT( table_size, face->gpos_table ) )
|
|
361 | + {
|
|
362 | + FT_ERROR(( "tt_face_load_gpos:"
|
|
363 | + " could not extract GPOS table\n" ));
|
|
364 | + goto Exit;
|
|
365 | + }
|
|
366 | + |
|
367 | + face->gpos_kerning_available = FALSE;
|
|
368 | + |
|
369 | + if ( face->gpos_table )
|
|
370 | + {
|
|
371 | + FT_Byte* feature_list = face->gpos_table +
|
|
372 | + FT_PEEK_USHORT( face->gpos_table + 6 );
|
|
373 | + FT_UInt16 feature_count = FT_PEEK_USHORT( feature_list );
|
|
374 | + FT_Byte* feature_records = feature_list + 2;
|
|
375 | + |
|
376 | + FT_UInt idx;
|
|
377 | + |
|
378 | + |
|
379 | + for ( idx = 0; idx < feature_count; idx++, feature_records += 6 )
|
|
380 | + {
|
|
381 | + FT_ULong feature_tag = FT_PEEK_ULONG( feature_records );
|
|
382 | + |
|
383 | + |
|
384 | + if ( feature_tag == TTAG_kern )
|
|
385 | + {
|
|
386 | + face->gpos_kerning_available = TRUE;
|
|
387 | + break;
|
|
388 | + }
|
|
389 | + }
|
|
390 | + }
|
|
391 | + |
|
392 | + Exit:
|
|
393 | + return error;
|
|
394 | + }
|
|
395 | + |
|
396 | + |
|
397 | + FT_LOCAL_DEF( void )
|
|
398 | + tt_face_done_gpos( TT_Face face )
|
|
399 | + {
|
|
400 | + FT_Stream stream = face->root.stream;
|
|
401 | + |
|
402 | + |
|
403 | + FT_FRAME_RELEASE( face->gpos_table );
|
|
404 | + }
|
|
405 | + |
|
406 | + |
|
407 | + FT_LOCAL_DEF( FT_Int )
|
|
408 | + tt_face_get_gpos_kerning( TT_Face face,
|
|
409 | + FT_UInt left_glyph,
|
|
410 | + FT_UInt right_glyph )
|
|
411 | + {
|
|
412 | + FT_Byte* feature_list;
|
|
413 | + FT_UInt16 feature_count;
|
|
414 | + FT_Byte* feature_records;
|
|
415 | + FT_UInt feature_idx;
|
|
416 | + |
|
417 | + |
|
418 | + if ( !face->gpos_kerning_available )
|
|
419 | + return 0;
|
|
420 | + |
|
421 | + feature_list = face->gpos_table +
|
|
422 | + FT_PEEK_USHORT( face->gpos_table + 6 );
|
|
423 | + feature_count = FT_PEEK_USHORT( feature_list );
|
|
424 | + feature_records = feature_list + 2;
|
|
425 | + |
|
426 | + for ( feature_idx = 0;
|
|
427 | + feature_idx < feature_count;
|
|
428 | + feature_idx++, feature_records += 6 )
|
|
429 | + {
|
|
430 | + FT_ULong feature_tag = FT_PEEK_ULONG( feature_records );
|
|
431 | + FT_Byte* feature_table;
|
|
432 | + FT_UInt16 lookup_idx_count;
|
|
433 | + FT_UInt16 lookup_idx;
|
|
434 | + |
|
435 | + |
|
436 | + if ( feature_tag != TTAG_kern )
|
|
437 | + continue;
|
|
438 | + |
|
439 | + feature_table = feature_list + FT_PEEK_USHORT( feature_records + 4 );
|
|
440 | + lookup_idx_count = FT_PEEK_USHORT( feature_table + 2 );
|
|
441 | + |
|
442 | + for ( lookup_idx = 0; lookup_idx < lookup_idx_count; lookup_idx++ )
|
|
443 | + {
|
|
444 | + FT_UInt16 lookup_list_idx =
|
|
445 | + FT_PEEK_USHORT( feature_table + 4 + 2 * lookup_idx );
|
|
446 | + TT_GPOS_Subtable_Iterator_Context subtable_iter;
|
|
447 | + |
|
448 | + |
|
449 | + tt_gpos_subtable_iterator_init( &subtable_iter,
|
|
450 | + face->gpos_table,
|
|
451 | + lookup_list_idx );
|
|
452 | + |
|
453 | + while ( tt_gpos_subtable_iterator_next( &subtable_iter ) )
|
|
454 | + {
|
|
455 | + FT_Byte* subtable;
|
|
456 | + |
|
457 | + gpos_value_format_bitmask value_format_1;
|
|
458 | + gpos_value_format_bitmask value_format_2;
|
|
459 | + gpos_pair_adjustment_format format;
|
|
460 | + |
|
461 | + FT_UShort coverage_offset;
|
|
462 | + FT_Int coverage_index;
|
|
463 | + |
|
464 | + |
|
465 | + if ( subtable_iter.subtable_type !=
|
|
466 | + GPOS_LOOKUP_TYPE_PAIR_ADJUSTMENT )
|
|
467 | + continue;
|
|
468 | + |
|
469 | + subtable = subtable_iter.subtable;
|
|
470 | + |
|
471 | + value_format_1 =
|
|
472 | + (gpos_value_format_bitmask)FT_PEEK_USHORT( subtable + 4 );
|
|
473 | + value_format_2 =
|
|
474 | + (gpos_value_format_bitmask)FT_PEEK_USHORT( subtable + 6 );
|
|
475 | + |
|
476 | + if ( !( value_format_1 == GPOS_VALUE_FORMAT_X_ADVANCE &&
|
|
477 | + value_format_2 == GPOS_VALUE_FORMAT_NONE ) )
|
|
478 | + continue;
|
|
479 | + |
|
480 | + format = (gpos_pair_adjustment_format)FT_PEEK_USHORT( subtable );
|
|
481 | + |
|
482 | + coverage_offset = FT_PEEK_USHORT( subtable + 2 );
|
|
483 | + coverage_index =
|
|
484 | + tt_gpos_get_coverage_index( subtable + coverage_offset,
|
|
485 | + left_glyph );
|
|
486 | + |
|
487 | + if ( coverage_index == -1 )
|
|
488 | + continue;
|
|
489 | + |
|
490 | + switch ( format )
|
|
491 | + {
|
|
492 | + case GPOS_PAIR_ADJUSTMENT_FORMAT_GLYPH_PAIR:
|
|
493 | + {
|
|
494 | + FT_Int l, r, m;
|
|
495 | + FT_Int straw, needle;
|
|
496 | + |
|
497 | + FT_Int value_record_pair_size_in_bytes = 2;
|
|
498 | + |
|
499 | + FT_UShort pair_set_count = FT_PEEK_USHORT( subtable + 8 );
|
|
500 | + FT_UShort pair_pos_offset;
|
|
501 | + |
|
502 | + FT_Byte* pair_value_table;
|
|
503 | + FT_UShort pair_value_count;
|
|
504 | + FT_Byte* pair_value_array;
|
|
505 | + |
|
506 | + |
|
507 | + if ( coverage_index >= pair_set_count )
|
|
508 | + return 0;
|
|
509 | + |
|
510 | + pair_pos_offset =
|
|
511 | + FT_PEEK_USHORT( subtable + 10 + 2 * coverage_index );
|
|
512 | + |
|
513 | + pair_value_table = subtable + pair_pos_offset;
|
|
514 | + pair_value_count = FT_PEEK_USHORT( pair_value_table );
|
|
515 | + pair_value_array = pair_value_table + 2;
|
|
516 | + |
|
517 | + needle = right_glyph;
|
|
518 | + r = pair_value_count - 1;
|
|
519 | + l = 0;
|
|
520 | + |
|
521 | + /* Binary search. */
|
|
522 | + while ( l <= r )
|
|
523 | + {
|
|
524 | + FT_UShort second_glyph;
|
|
525 | + FT_Byte* pair_value;
|
|
526 | + |
|
527 | + |
|
528 | + m = ( l + r ) >> 1;
|
|
529 | + pair_value = pair_value_array +
|
|
530 | + ( 2 + value_record_pair_size_in_bytes ) * m;
|
|
531 | + second_glyph = FT_PEEK_USHORT( pair_value );
|
|
532 | + straw = second_glyph;
|
|
533 | + |
|
534 | + if ( needle < straw )
|
|
535 | + r = m - 1;
|
|
536 | + else if ( needle > straw )
|
|
537 | + l = m + 1;
|
|
538 | + else
|
|
539 | + {
|
|
540 | + FT_Short x_advance = FT_PEEK_SHORT( pair_value + 2 );
|
|
541 | + |
|
542 | + |
|
543 | + return x_advance;
|
|
544 | + }
|
|
545 | + }
|
|
546 | + break;
|
|
547 | + }
|
|
548 | + |
|
549 | + case GPOS_PAIR_ADJUSTMENT_FORMAT_CLASS_PAIR:
|
|
550 | + {
|
|
551 | + FT_UShort class_def1_offset = FT_PEEK_USHORT( subtable + 8 );
|
|
552 | + FT_UShort class_def2_offset = FT_PEEK_USHORT( subtable + 10 );
|
|
553 | + |
|
554 | + FT_Int left_glyph_class =
|
|
555 | + tt_gpos_get_glyph_class( subtable + class_def1_offset,
|
|
556 | + left_glyph );
|
|
557 | + FT_Int right_glyph_class =
|
|
558 | + tt_gpos_get_glyph_class( subtable + class_def2_offset,
|
|
559 | + right_glyph );
|
|
560 | + |
|
561 | + FT_UShort class1_count = FT_PEEK_USHORT( subtable + 12 );
|
|
562 | + FT_UShort class2_count = FT_PEEK_USHORT( subtable + 14 );
|
|
563 | + |
|
564 | + FT_Byte *class1_records, *class2_records;
|
|
565 | + FT_Short x_advance;
|
|
566 | + |
|
567 | + |
|
568 | + if ( left_glyph_class < 0 ||
|
|
569 | + left_glyph_class >= class1_count )
|
|
570 | + return 0; /* malformed */
|
|
571 | + if ( right_glyph_class < 0 ||
|
|
572 | + right_glyph_class >= class2_count )
|
|
573 | + return 0; /* malformed */
|
|
574 | + |
|
575 | + if ( right_glyph_class == 0 )
|
|
576 | + continue; /* right glyph not found in this table */
|
|
577 | + |
|
578 | + class1_records = subtable + 16;
|
|
579 | + class2_records =
|
|
580 | + class1_records + 2 * ( left_glyph_class * class2_count );
|
|
581 | + |
|
582 | + x_advance =
|
|
583 | + FT_PEEK_SHORT( class2_records + 2 * right_glyph_class );
|
|
584 | + |
|
585 | + return x_advance;
|
|
586 | + }
|
|
587 | + |
|
588 | + default:
|
|
589 | + return 0;
|
|
590 | + }
|
|
591 | + }
|
|
592 | + }
|
|
593 | + }
|
|
594 | + |
|
595 | + return 0;
|
|
596 | + }
|
|
597 | + |
|
598 | +#else /* !TT_CONFIG_OPTION_GPOS_KERNING */
|
|
599 | + |
|
600 | + /* ANSI C doesn't like empty source files */
|
|
601 | + typedef int tt_gpos_dummy_;
|
|
602 | + |
|
603 | +#endif /* !TT_CONFIG_OPTION_GPOS_KERNING */
|
|
604 | + |
|
605 | + |
|
606 | +/* END */ |
1 | +/****************************************************************************
|
|
2 | + *
|
|
3 | + * ttgpos.c
|
|
4 | + *
|
|
5 | + * Load the TrueType GPOS table. The only GPOS layout feature this
|
|
6 | + * currently supports is kerning, from x advances in the pair adjustment
|
|
7 | + * layout feature.
|
|
8 | + *
|
|
9 | + * Copyright (C) 2024 by
|
|
10 | + * David Saltzman
|
|
11 | + *
|
|
12 | + * This file is part of the FreeType project, and may only be used,
|
|
13 | + * modified, and distributed under the terms of the FreeType project
|
|
14 | + * license, LICENSE.TXT. By continuing to use, modify, or distribute
|
|
15 | + * this file you indicate that you have read the license and
|
|
16 | + * understand and accept it fully.
|
|
17 | + */
|
|
18 | + |
|
19 | + |
|
20 | +#ifndef TTGPOS_H_
|
|
21 | +#define TTGPOS_H_
|
|
22 | + |
|
23 | + |
|
24 | +#include <freetype/internal/ftstream.h>
|
|
25 | +#include <freetype/internal/tttypes.h>
|
|
26 | + |
|
27 | + |
|
28 | +FT_BEGIN_HEADER
|
|
29 | + |
|
30 | + |
|
31 | +#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
32 | + |
|
33 | + FT_LOCAL( FT_Error )
|
|
34 | + tt_face_load_gpos( TT_Face face,
|
|
35 | + FT_Stream stream );
|
|
36 | + |
|
37 | + FT_LOCAL( void )
|
|
38 | + tt_face_done_gpos( TT_Face face );
|
|
39 | + |
|
40 | + FT_LOCAL( FT_Int )
|
|
41 | + tt_face_get_gpos_kerning( TT_Face face,
|
|
42 | + FT_UInt left_glyph,
|
|
43 | + FT_UInt right_glyph );
|
|
44 | + |
|
45 | +#endif /* TT_CONFIG_OPTION_GPOS_KERNING */
|
|
46 | + |
|
47 | + |
|
48 | +FT_END_HEADER
|
|
49 | + |
|
50 | +#endif /* TTGPOS_H_ */
|
|
51 | + |
|
52 | + |
|
53 | +/* END */ |
... | ... | @@ -217,7 +217,20 @@ |
217 | 217 | kerning->y = 0;
|
218 | 218 | |
219 | 219 | if ( sfnt )
|
220 | - kerning->x = sfnt->get_kerning( ttface, left_glyph, right_glyph );
|
|
220 | + {
|
|
221 | + /* Use 'kern' table if available since that can be faster; otherwise */
|
|
222 | + /* use GPOS kerning pairs if available. */
|
|
223 | + if ( ttface->kern_avail_bits != 0 )
|
|
224 | + kerning->x = sfnt->get_kerning( ttface,
|
|
225 | + left_glyph,
|
|
226 | + right_glyph );
|
|
227 | +#ifdef TT_CONFIG_OPTION_GPOS_KERNING
|
|
228 | + else if ( ttface->gpos_kerning_available )
|
|
229 | + kerning->x = sfnt->get_gpos_kerning( ttface,
|
|
230 | + left_glyph,
|
|
231 | + right_glyph );
|
|
232 | +#endif
|
|
233 | + }
|
|
221 | 234 | |
222 | 235 | return 0;
|
223 | 236 | }
|