1
0
mirror of https://github.com/freetype/freetype.git synced 2026-10-23 11:08:54 +03:00

[truetype] Update the VARC variation-store binary format.

Spec: https://github.com/harfbuzz/boring-expansion-spec/pull/175
Offset-base erratum: https://github.com/harfbuzz/boring-expansion-spec/issues/177

* src/truetype/ttvarc.c (tt_varc_evaluate_region, tt_varc_get_item_deltas):
  Read the sparse-region count as uint32, resolve each sparse axis through a
  region-relative Offset32, and follow the Offset32 to the delta-set INDEX.
  Bound the new offset fields and count arrays before dereferencing them.

* tests/varc/main.c (test_variation_store): New function to check
  translated outlines at default, midpoint, and peak coordinates.
  (main): Updated.

* tests/data/varc-null-condition.ttf, tests/data/varc-value-conditions.ttf:
  Updated to migrate the existing affected fixture without changing its
  logical contents or non-VARC tables.
* tests/data/varc-variation-store.ttf: New test file.

Tests: full Meson regression suite (3 passed).

Assisted-by: OpenAI Codex
This commit is contained in:
Behdad Esfahbod (بهداد اسفهبد)
2026-10-03 10:20:05 +02:00
committed by Werner Lemberg
parent d1f916a65e
commit 42e28aa7ad
5 changed files with 129 additions and 40 deletions

View File

@@ -1070,9 +1070,10 @@
if ( !coords || coord_count == 0 )
return scalar; /* no coords available, return 1.0 */
/* `SparseVarRegionList` region format:
/* `SparseVariationRegion` table:
*
* regionAxisCount (u16) - number of non-default axes
* axisOffsets (u32[regionAxisCount]) - relative to this region
* for each sparse axis:
* axisIndex (u16)
* startCoord (F2DOT14)
@@ -1080,13 +1081,16 @@
* endCoord (F2DOT14)
*/
if ( p + 2 > limit )
if ( limit - p < 2 )
return 0;
/* read sparse axis count (u16) */
axis_count = ( (FT_UInt)p[0] << 8 ) | p[1];
p += 2;
if ( axis_count > (FT_ULong)( limit - p ) / 4 )
return 0;
/* process each axis range */
for ( i = 0; i < axis_count; i++ )
{
@@ -1094,32 +1098,38 @@
FT_Short min_val, peak_val, max_val;
FT_Fixed coord;
FT_Fixed axis_scalar;
FT_UInt32 axis_offset;
FT_Byte* axis;
FT_Fixed min_16;
FT_Fixed peak_16;
FT_Fixed max_16;
/* read axis index (u16) */
if ( p + 8 > limit )
axis_offset = ( (FT_UInt32)p[0] << 24 ) |
( (FT_UInt32)p[1] << 16 ) |
( (FT_UInt32)p[2] << 8 ) |
(FT_UInt32)p[3];
p += 4;
if ( axis_offset > (FT_ULong)( limit - region_data ) ||
(FT_ULong)( limit - region_data ) - axis_offset < 8 )
return 0;
axis_index = ( (FT_UInt16)p[0] << 8 ) | p[1];
p += 2;
axis = region_data + axis_offset;
axis_index = ( (FT_UInt16)axis[0] << 8 ) | axis[1];
axis += 2;
/* read min, peak, max (F2DOT14) - each is 2 bytes, */
/* advance after each read */
min_val = (FT_Short)( ( (FT_UShort)p[0] << 8 ) |
(FT_UShort)p[1] );
p += 2;
/* fixed: read from p[0] after advancing */
peak_val = (FT_Short)( ( (FT_UShort)p[0] << 8 ) |
(FT_UShort)p[1] );
p += 2;
/* fixed: read from p[0] after advancing */
max_val = (FT_Short)( ( (FT_UShort)p[0] << 8 ) |
(FT_UShort)p[1] );
p += 2;
min_val = (FT_Short)( ( (FT_UShort)axis[0] << 8 ) |
(FT_UShort)axis[1] );
axis += 2;
peak_val = (FT_Short)( ( (FT_UShort)axis[0] << 8 ) |
(FT_UShort)axis[1] );
axis += 2;
max_val = (FT_Short)( ( (FT_UShort)axis[0] << 8 ) |
(FT_UShort)axis[1] );
/* get coordinate for this axis */
if ( axis_index >= coord_count )
@@ -1385,15 +1395,18 @@
FT_Byte mvd_format;
FT_UInt16 region_count;
FT_Byte* delta_sets;
FT_UInt32 delta_sets_offset;
FT_Byte* tuple_data;
FT_UInt tuple_size;
FT_Int64 stack_accumulators[VARC_STACK_DELTA_COUNT];
/* deltas * regions */
FT_Int32 stack_all_deltas[VARC_STACK_DELTA_COUNT * 16];
FT_Int64* accumulators = NULL;
FT_Byte* regions_data = NULL;
FT_Byte* regions_limit = NULL;
FT_Int64* accumulators = NULL;
FT_Byte* regions_data = NULL;
FT_Byte* regions_limit = NULL;
FT_UInt32 region_list_count = 0;
FT_UInt total_deltas;
FT_Int32* all_deltas = NULL;
FT_UInt bytes_consumed = 0;
@@ -1439,8 +1452,6 @@
(FT_UInt32)p[3];
p += 4;
FT_UNUSED( regions_offset ); /* TODO: Use for region evaluation */
/* read dataSets count (u16) */
data_sets_count = ( (FT_UInt16)p[0] << 8 ) | p[1];
p += 2;
@@ -1467,7 +1478,7 @@
mvd_data = mvs_data + data_set_offset;
if ( mvd_data + 3 > table_limit )
if ( table_limit - mvd_data < 7 )
return FT_THROW( Invalid_Table );
/* parse `MultiVarData` header */
@@ -1482,14 +1493,23 @@
/* read `regionIndices` count (u16) */
region_count = ( (FT_UInt16)p[0] << 8 ) | p[1];
p += 2;
/* skip `regionIndices` array */
if ( region_count > (FT_ULong)( table_limit - p ) / 2 )
return FT_THROW( Invalid_Table );
p += region_count * 2;
if ( p > table_limit )
if ( table_limit - p < 4 )
return FT_THROW( Invalid_Table );
/* Now `p` points to `deltaSets` (INDEX2) */
delta_sets = p;
delta_sets_offset = ( (FT_UInt32)p[0] << 24 ) |
( (FT_UInt32)p[1] << 16 ) |
( (FT_UInt32)p[2] << 8 ) |
(FT_UInt32)p[3];
if ( !delta_sets_offset ||
delta_sets_offset > (FT_ULong)( table_limit - mvd_data ) ||
(FT_ULong)( table_limit - mvd_data ) - delta_sets_offset < 4 )
return FT_THROW( Invalid_Table );
delta_sets = mvd_data + delta_sets_offset;
/* read tuple from INDEX2 at inner index */
error = tt_varc_read_cff2_index_entry( delta_sets, table_limit, inner,
@@ -1525,18 +1545,37 @@
}
/* parse `SparseVarRegionList` to get region definitions */
if ( regions_offset > 0 && regions_offset < varc->table_size )
if ( regions_offset > 0 &&
regions_offset <= (FT_ULong)( table_limit - mvs_data ) )
{
regions_data = mvs_data + regions_offset;
regions_limit = table_limit;
/* read and skip region count (u16) */
if ( regions_data + 2 <= regions_limit )
regions_data += 2;
/* read region count (u32) */
if ( regions_limit - regions_data >= 4 )
{
region_list_count = ( (FT_UInt32)regions_data[0] << 24 ) |
( (FT_UInt32)regions_data[1] << 16 ) |
( (FT_UInt32)regions_data[2] << 8 ) |
(FT_UInt32)regions_data[3];
regions_data += 4;
if ( region_list_count >
(FT_ULong)( regions_limit - regions_data ) / 4 )
regions_data = NULL;
}
else
regions_data = NULL;
}
if ( !regions_data )
{
if ( accumulators != stack_accumulators )
FT_FREE( accumulators );
error = FT_THROW( Invalid_Table );
goto Cleanup;
}
/* read ALL deltas from the flat tuple */
/* (region_count * num_deltas values) */
@@ -1604,23 +1643,27 @@
{
/* SparseVarRegionList format:
*
* u16: regionCount
* u32: regionCount
* u32[regionCount]: array of offsets (from start of list)
* to each region
*/
/* back to start (before count) */
FT_Byte* region_list_start = regions_data - 2;
FT_Byte* region_offset_ptr = regions_data + region_index * 4;
FT_Byte* region_list_start = regions_data - 4;
FT_Byte* region_offset_ptr;
if ( region_offset_ptr + 4 <= regions_limit )
if ( region_index < region_list_count &&
region_index < (FT_ULong)( regions_limit - regions_data ) / 4 )
{
FT_UInt32 region_offset =
( (FT_UInt32)region_offset_ptr[0] << 24 ) |
( (FT_UInt32)region_offset_ptr[1] << 16 ) |
( (FT_UInt32)region_offset_ptr[2] << 8 ) |
(FT_UInt32)region_offset_ptr[3];
FT_UInt32 region_offset;
region_offset_ptr = regions_data + region_index * 4;
region_offset = ( (FT_UInt32)region_offset_ptr[0] << 24 ) |
( (FT_UInt32)region_offset_ptr[1] << 16 ) |
( (FT_UInt32)region_offset_ptr[2] << 8 ) |
(FT_UInt32)region_offset_ptr[3];
if ( region_offset <

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -198,6 +198,51 @@ Exit:
}
static int
test_variation_store( FT_Library library,
const char* testdata_dir )
{
FT_Face face = NULL;
char filepath[FILENAME_MAX];
FT_UInt i;
int ret = 1;
/* A triangle translated by 100, plus a TEST-axis delta of 100. */
snprintf( filepath, sizeof ( filepath ), "%s/varc-variation-store.ttf",
testdata_dir );
if ( FT_New_Face( library, filepath, 0, &face ) )
goto Exit;
for ( i = 0; i < 3; i++ )
{
FT_Fixed coordinate = (FT_Fixed)i * 0x8000L;
FT_BBox box;
if ( FT_Set_Var_Design_Coordinates( face, 1, &coordinate ) ||
FT_Load_Glyph( face, 2, FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING ) )
goto Exit;
FT_Outline_Get_CBox( &face->glyph->outline, &box );
if ( box.xMin != 100 + (FT_Pos)i * 50 ||
box.xMax != 200 + (FT_Pos)i * 50 ||
box.yMin != 0 ||
box.yMax != 100 )
{
fprintf( stderr, "Unexpected VARC variation-store outline\n" );
goto Exit;
}
}
ret = 0;
Exit:
FT_Done_Face( face );
return ret;
}
int
main( void )
{
@@ -233,6 +278,7 @@ main( void )
ret |= test_reset_without_axes( library, testdata_dir );
ret |= test_value_conditions( library, testdata_dir );
ret |= test_variation_store( library, testdata_dir );
FT_Done_FreeType( library );
return ret;