Merge branch 'varc' into 'master'

Draft: Add initial VARC (Variable Composites) table support

See merge request freetype/freetype!415
This commit is contained in:
Behdad Esfahbod 2026-04-13 14:21:03 +00:00
commit 96a1b56f89
13 changed files with 3676 additions and 3 deletions

View file

@ -772,6 +772,21 @@ FT_BEGIN_HEADER
/* #define TT_CONFIG_OPTION_NO_BORING_EXPANSION */
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_VARC` if you want to include support for
* variable composite glyphs (the 'VARC' table). This is part of the
* 'boring' OpenType specification expansions.
*
* https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md
*
* Variable composites enable more efficient encoding of variable glyphs
* by allowing glyphs to be composed from other glyphs with variable
* transformations and axis value overrides.
*/
#define TT_CONFIG_OPTION_VARC
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_BDF` if you want to include support for an

View file

@ -981,6 +981,10 @@ FT_BEGIN_HEADER
TT_Free_Table_Func free_svg;
TT_Load_Svg_Doc_Func load_svg_doc;
/* OpenType VARC Support */
TT_Load_Table_Func load_varc;
TT_Free_Table_Func free_varc;
} SFNT_Interface;
@ -1037,7 +1041,9 @@ FT_BEGIN_HEADER
get_name_id_, \
load_svg_, \
free_svg_, \
load_svg_doc_ ) \
load_svg_doc_, \
load_varc_, \
free_varc_ ) \
static const SFNT_Interface class_ = \
{ \
goto_table_, \
@ -1087,7 +1093,9 @@ FT_BEGIN_HEADER
get_name_id_, \
load_svg_, \
free_svg_, \
load_svg_doc_ \
load_svg_doc_, \
load_varc_, \
free_varc_ \
};

View file

@ -1595,6 +1595,13 @@ FT_BEGIN_HEADER
/* since 2.12 */
void* svg;
#ifdef TT_CONFIG_OPTION_VARC
/* since 2.14 */
void* varc;
void* varc_context; /* Active recursion context */
FT_Bool varc_loading_components; /* Skip VARC check for components */
#endif
#ifdef TT_CONFIG_OPTION_GPOS_KERNING
/* since 2.13.3 */
FT_Byte* gpos_table;

View file

@ -107,6 +107,7 @@ FT_BEGIN_HEADER
#define TTAG_VDMX FT_MAKE_TAG( 'V', 'D', 'M', 'X' )
#define TTAG_vhea FT_MAKE_TAG( 'v', 'h', 'e', 'a' )
#define TTAG_vmtx FT_MAKE_TAG( 'v', 'm', 't', 'x' )
#define TTAG_VARC FT_MAKE_TAG( 'V', 'A', 'R', 'C' )
#define TTAG_VVAR FT_MAKE_TAG( 'V', 'V', 'A', 'R' )
#define TTAG_wOFF FT_MAKE_TAG( 'w', 'O', 'F', 'F' )
#define TTAG_wOF2 FT_MAKE_TAG( 'w', 'O', 'F', '2' )

View file

@ -585,6 +585,17 @@
slot->glyph_index = 0;
FT_ZERO( &slot->metrics );
/* free outline if separately allocated (e.g., by VARC) */
if ( slot->outline.flags & FT_OUTLINE_OWNER )
{
FT_Memory memory = slot->face->memory;
FT_FREE( slot->outline.points );
FT_FREE( slot->outline.tags );
FT_FREE( slot->outline.contours );
}
FT_ZERO( &slot->outline );
slot->bitmap.width = 0;
@ -659,6 +670,14 @@
if ( clazz->done_slot )
clazz->done_slot( slot );
/* free outline if separately allocated (e.g., by VARC) */
if ( slot->outline.flags & FT_OUTLINE_OWNER )
{
FT_FREE( slot->outline.points );
FT_FREE( slot->outline.tags );
FT_FREE( slot->outline.contours );
}
/* free bitmap buffer if needed */
ft_glyphslot_free_bitmap( slot );

View file

@ -29,6 +29,10 @@
#include "cfferrs.h"
#ifdef TT_CONFIG_OPTION_VARC
#include "../sfnt/ttvarc.h"
#endif
#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
#define IS_DEFAULT_INSTANCE( _face ) \
( !( FT_IS_NAMED_INSTANCE( _face ) || \
@ -408,6 +412,28 @@
#endif /* FT_CONFIG_OPTION_SVG */
#ifdef TT_CONFIG_OPTION_VARC
/* check for VARC glyphs */
if ( face->varc && tt_face_has_varc_glyph( face, glyph_index ) )
{
FT_TRACE3(( "Loading VARC glyph\n" ));
error = tt_face_load_varc_glyph( face,
(FT_GlyphSlot)glyph,
glyph_index,
load_flags );
if ( !error )
{
FT_TRACE3(( "Successfully loaded VARC glyph\n" ));
return FT_Err_Ok;
}
FT_TRACE3(( "Failed to load VARC glyph, falling back to CFF\n" ));
}
#endif /* TT_CONFIG_OPTION_VARC */
/* top-level code ensures that FT_LOAD_NO_HINTING is set */
/* if FT_LOAD_NO_SCALE is active */
hinting = FT_BOOL( ( load_flags & FT_LOAD_NO_HINTING ) == 0 );

View file

@ -37,6 +37,7 @@ SFNT_DRV_SRC := $(SFNT_DIR)/pngshim.c \
$(SFNT_DIR)/ttcmap.c \
$(SFNT_DIR)/ttcolr.c \
$(SFNT_DIR)/ttsvg.c \
$(SFNT_DIR)/ttvarc.c \
$(SFNT_DIR)/ttcpal.c \
$(SFNT_DIR)/ttgpos.c \
$(SFNT_DIR)/ttkern.c \

View file

@ -40,6 +40,10 @@
#include "ttsvg.h"
#endif
#ifdef TT_CONFIG_OPTION_VARC
#include "ttvarc.h"
#endif
#ifdef TT_CONFIG_OPTION_POSTSCRIPT_NAMES
#include "ttpost.h"
#endif
@ -1254,6 +1258,12 @@
#define PUT_GPOS_KERNING( a ) a
#else
#define PUT_GPOS_KERNING( a ) NULL
#endif
#ifdef TT_CONFIG_OPTION_VARC
#define PUT_VARC_SUPPORT( a ) a
#else
#define PUT_VARC_SUPPORT( a ) NULL
#endif
FT_DEFINE_SFNT_INTERFACE(
@ -1353,8 +1363,13 @@
/* TT_Load_Table_Func load_svg */
PUT_SVG_SUPPORT( tt_face_free_svg ),
/* TT_Free_Table_Func free_svg */
PUT_SVG_SUPPORT( tt_face_load_svg_doc )
PUT_SVG_SUPPORT( tt_face_load_svg_doc ),
/* TT_Load_Svg_Doc_Func load_svg_doc */
PUT_VARC_SUPPORT( tt_face_load_varc ),
/* TT_Load_Table_Func load_varc */
PUT_VARC_SUPPORT( tt_face_free_varc )
/* TT_Free_Table_Func free_varc */
)

View file

@ -28,6 +28,7 @@
#include "ttcolr.c"
#include "ttcpal.c"
#include "ttsvg.c"
#include "ttvarc.c"
#include "ttgpos.c"
#include "ttkern.c"

View file

@ -1025,6 +1025,12 @@
LOAD_( colr );
}
#ifdef TT_CONFIG_OPTION_VARC
/* variable composite glyph support */
if ( sfnt->load_varc )
LOAD_( varc );
#endif
/* OpenType-SVG glyph support */
if ( sfnt->load_svg )
LOAD_( svg );
@ -1471,6 +1477,12 @@
sfnt->free_colr( face );
}
#ifdef TT_CONFIG_OPTION_VARC
/* free VARC data */
if ( sfnt->free_varc )
sfnt->free_varc( face );
#endif
#ifdef FT_CONFIG_OPTION_SVG
/* free SVG data */
if ( sfnt->free_svg )

3165
src/sfnt/ttvarc.c Normal file

File diff suppressed because it is too large Load diff

324
src/sfnt/ttvarc.h Normal file
View file

@ -0,0 +1,324 @@
/****************************************************************************
*
* ttvarc.h
*
* TrueType and OpenType VARC (Variable Composites) support (specification).
*
* Copyright (C) 2025 by
* Behdad Esfahbod and David Turner, Robert Wilhelm, and Werner Lemberg.
*
* 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 TTVARC_H_
#define TTVARC_H_
#include "ttload.h"
#include <freetype/ftcolor.h>
#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
#include <freetype/internal/ftmmtypes.h>
#endif
FT_BEGIN_HEADER
#ifdef TT_CONFIG_OPTION_VARC
/**************************************************************************
*
* VARC component flags from the specification
*
*/
/* Variable Component Flags - from VARC spec */
#define VARC_RESET_UNSPECIFIED_AXES 0x00000001U /* Bit 0 */
#define VARC_HAVE_AXES 0x00000002U /* Bit 1 */
#define VARC_AXES_HAVE_VARIATION 0x00000004U /* Bit 2 */
#define VARC_TRANSFORM_HAS_VARIATION 0x00000008U /* Bit 3 */
#define VARC_HAVE_TRANSLATE_X 0x00000010U /* Bit 4 */
#define VARC_HAVE_TRANSLATE_Y 0x00000020U /* Bit 5 */
#define VARC_HAVE_ROTATION 0x00000040U /* Bit 6 */
#define VARC_HAVE_CONDITION 0x00000080U /* Bit 7 */
#define VARC_HAVE_SCALE_X 0x00000100U /* Bit 8 */
#define VARC_HAVE_SCALE_Y 0x00000200U /* Bit 9 */
#define VARC_HAVE_TCENTER_X 0x00000400U /* Bit 10 */
#define VARC_HAVE_TCENTER_Y 0x00000800U /* Bit 11 */
#define VARC_GID_IS_24BIT 0x00001000U /* Bit 12 */
#define VARC_HAVE_SKEW_X 0x00002000U /* Bit 13 */
#define VARC_HAVE_SKEW_Y 0x00004000U /* Bit 14 */
/* Reserved flags - bits 15-31 must be zero */
#define VARC_RESERVED_MASK 0xFFFF8000U
/**************************************************************************
*
* @Struct:
* TT_VarcComponentRec
*
* @Description:
* Represents a single component within a VARC glyph.
*
* @Fields:
* flags ::
* Component flags indicating which optional fields are present.
*
* gid ::
* The glyph ID of the component to be included.
*
* condition_index ::
* Index into the condition list (if VARC_HAVE_CONDITION is set).
* Component is only rendered if condition evaluates to true.
*
* axis_indices_index ::
* Index into the axis indices list (if VARC_HAVE_AXES is set).
*
* num_axis_values ::
* Number of axis values in the axis_values array.
*
* axis_values ::
* Array of normalized axis coordinates (-1.0 to +1.0 in F2DOT14).
* These override the current design space location for this component.
*
* axis_values_var_index ::
* Variation store index for axis values (if VARC_AXES_HAVE_VARIATION).
*
* transform_var_index ::
* Variation store index for transform values (if VARC_TRANSFORM_HAS_VARIATION).
*
* translate_x ::
* translate_y ::
* Translation amounts in font units (26.6 fixed-point).
*
* rotation ::
* Rotation angle in radians, multiplied by pi (16.16 fixed-point).
* So 0x10000 represents pi radians (180 degrees).
*
* scale_x ::
* scale_y ::
* Scale factors (16.16 fixed-point, 1.0 = 0x10000).
*
* skew_x ::
* skew_y ::
* Skew angles in radians, multiplied by pi (16.16 fixed-point).
*
* tcenter_x ::
* tcenter_y ::
* Transformation center point in font units (26.6 fixed-point).
* Transformations are applied relative to this point.
*/
typedef struct TT_VarcComponentRec_
{
FT_UInt32 flags;
FT_UInt gid;
/* Conditional rendering */
FT_UInt32 condition_index;
/* Axis value overrides */
FT_UInt32 axis_indices_index;
FT_UInt num_axis_values;
FT_Fixed* axis_values; /* Normalized coordinates (F2DOT14) */
FT_Bool axis_values_on_heap; /* TRUE if axis_values needs FT_FREE */
FT_UInt32 axis_values_var_index;
/* Transform variation */
FT_UInt32 transform_var_index;
/* Transform components (all 26.6 or 16.16 fixed-point) */
FT_Pos translate_x;
FT_Pos translate_y;
FT_Fixed rotation; /* Radians * pi, 16.16 */
FT_Fixed scale_x;
FT_Fixed scale_y;
FT_Fixed skew_x;
FT_Fixed skew_y;
FT_Pos tcenter_x;
FT_Pos tcenter_y;
} TT_VarcComponentRec, *TT_VarcComponent;
/**************************************************************************
*
* @Struct:
* TT_VarcRec
*
* @Description:
* The VARC table structure, loaded from the font file.
*
* @Fields:
* version_major ::
* Major version of the VARC table (currently 1).
*
* version_minor ::
* Minor version of the VARC table (currently 0).
*
* coverage_offset ::
* Offset to the coverage table from start of VARC table.
*
* coverage ::
* Pointer to the coverage table data.
*
* multi_var_store_offset ::
* Offset to the MultiItemVariationStore.
*
* condition_list_offset ::
* Offset to the condition list.
*
* axis_indices_list_offset ::
* Offset to the axis indices list.
*
* var_composite_glyphs_offset ::
* Offset to the glyph records (CFF2-style index).
*
* var_store ::
* The loaded MultiItemVariationStore for variation deltas.
*
* table ::
* Pointer to the raw VARC table data.
*
* table_size ::
* Size of the VARC table in bytes.
*
* stream ::
* The stream from which the table was loaded.
*/
typedef struct TT_VarcRec_
{
FT_UShort version_major;
FT_UShort version_minor;
/* Table offsets */
FT_ULong coverage_offset;
FT_ULong multi_var_store_offset;
FT_ULong condition_list_offset;
FT_ULong axis_indices_list_offset;
FT_ULong var_composite_glyphs_offset;
/* Loaded data pointers */
FT_Byte* coverage;
FT_Byte* condition_list;
FT_Byte* axis_indices_list;
FT_Byte* var_composite_glyphs;
#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
/* MultiItemVariationStore - stores tuples of deltas, not single values */
FT_Byte* multi_var_store; /* Pointer to MultiItemVariationStore data */
FT_Bool var_store_loaded;
#endif
/* Raw table data */
void* table;
FT_ULong table_size;
} TT_VarcRec, *TT_Varc;
/**************************************************************************
*
* Maximum recursion depth for VARC glyph loading.
* This prevents stack overflow and infinite loops from malformed fonts.
*/
#define TT_VARC_MAX_NESTING_LEVEL 16
/**************************************************************************
*
* @Struct:
* TT_VarcContextRec
*
* @Description:
* Context structure for recursive VARC glyph loading.
* Tracks recursion depth and visited glyphs for cycle detection.
*
* @Fields:
* recursion_depth ::
* Current depth in the recursion stack.
*
* visited_glyphs ::
* Array of glyph IDs currently being processed.
* Used for cycle detection to prevent infinite loops.
*/
typedef struct TT_VarcContextRec_
{
FT_UInt recursion_depth;
FT_UInt visited_glyphs[TT_VARC_MAX_NESTING_LEVEL];
/* Parent transform to compose with child transforms */
FT_Matrix parent_matrix;
FT_Vector parent_delta;
FT_Bool has_parent_transform;
/* Font's original variation coordinates (before VARC processing) */
/* Used when RESET_UNSPECIFIED_AXES is not set at depth 1 */
FT_Fixed* font_coords;
FT_UInt num_font_coords;
/* Current variation coords for delta evaluation at this recursion level.
* Points to font_coords at depth 1, or parent's new_coords at depth > 1.
* Not owned - do not free. */
FT_Fixed* current_coords;
FT_UInt num_current_coords;
/* Reusable buffers to avoid per-component allocations */
FT_Fixed* axis_values_buffer;
FT_UInt axis_values_buffer_size;
FT_Fixed* deltas_buffer;
FT_UInt deltas_buffer_size;
FT_Fixed* coords_buffer;
FT_UInt coords_buffer_size;
FT_UInt* indices_buffer;
FT_UInt indices_buffer_size;
} TT_VarcContextRec, *TT_VarcContext;
/**************************************************************************
*
* VARC API functions
*
*/
/* Load the VARC table from the font stream */
FT_EXPORT( FT_Error )
tt_face_load_varc( TT_Face face,
FT_Stream stream );
/* Free the VARC table resources */
FT_EXPORT( void )
tt_face_free_varc( TT_Face face );
/* Check if a glyph has VARC data */
FT_EXPORT( FT_Bool )
tt_face_has_varc_glyph( TT_Face face,
FT_UInt glyph_index );
/* Load a VARC glyph into the glyph slot */
FT_EXPORT( FT_Error )
tt_face_load_varc_glyph( TT_Face face,
FT_GlyphSlot glyph_slot,
FT_UInt glyph_index,
FT_Int32 load_flags );
#endif /* TT_CONFIG_OPTION_VARC */
FT_END_HEADER
#endif /* TTVARC_H_ */
/* END */

View file

@ -34,6 +34,10 @@
#include "ttgxvar.h"
#endif
#ifdef TT_CONFIG_OPTION_VARC
#include "../sfnt/ttvarc.h"
#endif
#include "tterrors.h"
@ -2539,6 +2543,81 @@
#endif /* FT_CONFIG_OPTION_SVG */
#ifdef TT_CONFIG_OPTION_VARC
/* check for VARC glyphs */
if ( face->varc && tt_face_has_varc_glyph( face, glyph_index ) )
{
FT_TRACE3(( "Loading VARC glyph\n" ));
error = tt_face_load_varc_glyph( face,
(FT_GlyphSlot)glyph,
glyph_index,
load_flags );
if ( !error )
{
FT_Short left_bearing = 0;
FT_Short top_bearing = 0;
FT_UShort advance_width = 0;
FT_UShort advance_height = 0;
/* Get advance width from hmtx */
TT_Get_HMetrics( face, glyph_index,
&left_bearing,
&advance_width );
TT_Get_VMetrics( face, glyph_index,
0,
&top_bearing,
&advance_height );
glyph->linearHoriAdvance = advance_width;
glyph->linearVertAdvance = advance_height;
#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
{
FT_Int advance = (FT_Int)advance_width;
tt_hadvance_adjust( (FT_Face)face, glyph_index, &advance );
advance_width = (FT_UShort)advance;
}
#endif
{
FT_BBox bbox;
FT_Outline_Get_CBox( &glyph->outline, &bbox );
glyph->metrics.horiBearingX = bbox.xMin;
glyph->metrics.horiBearingY = bbox.yMax;
glyph->metrics.width = SUB_LONG( bbox.xMax, bbox.xMin );
glyph->metrics.height = SUB_LONG( bbox.yMax, bbox.yMin );
}
if ( load_flags & FT_LOAD_NO_SCALE )
{
glyph->metrics.horiAdvance = (FT_Pos)advance_width * 64;
glyph->metrics.vertAdvance = (FT_Pos)advance_height * 64;
}
else
{
glyph->metrics.horiAdvance = FT_MulFix( advance_width,
size->metrics->x_scale );
glyph->metrics.vertAdvance = FT_MulFix( advance_height,
size->metrics->y_scale );
}
FT_TRACE3(( "Successfully loaded VARC glyph\n" ));
goto Exit;
}
FT_TRACE3(( "Failed to load VARC glyph, falling back to glyf\n" ));
}
#endif /* TT_CONFIG_OPTION_VARC */
error = tt_loader_init( &loader, size, glyph, load_flags, FALSE );
if ( error )
goto Exit;