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

[truetype] Use the blend axis count for private VARC axes.

Static VARC fonts can have private variation axes without an 'fvar'
table or public MM metadata.  Use the blend's axis count when validating
'avar' and item variation stores, avoiding a null 'mmvar' dereference
when loading HVAR or VVAR for a VARC component.

* src/truetype/ttgxvar.c (ft_var_load_avar,
  tt_var_load_item_variation_store): Use blend->num_axis.

* tests/varc/make-hvvar-fonts.py: Generate HVAR and VVAR fixtures from
  the static VARC/gvar font using only Python's standard library.
* tests/meson.build: Generate the fixtures and pass them to the VARC test.
* tests/varc/main.c (test_font, main): Test generated fixture paths.

Tests: all five Meson regression tests pass.

Fixes issue #1474.

Assisted-by: OpenAI Codex
This commit is contained in:
Behdad Esfahbod
2026-10-18 15:33:50 -06:00
committed by Werner Lemberg
parent a5877f48ff
commit 0eca44e6e8
4 changed files with 103 additions and 5 deletions

View File

@@ -413,7 +413,7 @@
FT_TRACE2(( "loaded\n" ));
if ( axisCount != (FT_Long)blend->mmvar->num_axis )
if ( axisCount != (FT_Long)blend->num_axis )
{
FT_TRACE2(( "ft_var_load_avar:"
" number of axes in `avar' and `fvar'\n" ));
@@ -571,7 +571,7 @@
FT_READ_USHORT( region_count ) )
goto Exit;
if ( axis_count != (FT_Long)blend->mmvar->num_axis )
if ( axis_count != (FT_Long)blend->num_axis )
{
FT_TRACE2(( "tt_var_load_item_variation_store:"
" number of axes in item variation store\n" ));

View File

@@ -15,6 +15,12 @@ test('issue-1063',
###
varc_hvvar_fonts = custom_target('varc-hvvar-fonts',
input: files('data/varc-static-gvar.ttf'),
output: ['varc-static-hvar.ttf', 'varc-static-vvar.ttf'],
command: [python_exe, files('varc/make-hvvar-fonts.py'), '@INPUT@', '@OUTPUT@'],
)
test_varc = executable('varc',
files([ 'varc/main.c' ]),
dependencies: freetype_dep,
@@ -23,6 +29,8 @@ test_varc = executable('varc',
test('varc',
test_varc,
env: test_env,
depends: varc_hvvar_fonts,
args: [varc_hvvar_fonts[0], varc_hvvar_fonts[1]],
suite: 'regression')
###

View File

@@ -29,8 +29,11 @@ test_font( FT_Library library,
int ret = 1;
snprintf( filepath, sizeof ( filepath ), "%s/%s",
testdata_dir, test_font->filename );
if ( testdata_dir )
snprintf( filepath, sizeof ( filepath ), "%s/%s",
testdata_dir, test_font->filename );
else
snprintf( filepath, sizeof ( filepath ), "%s", test_font->filename );
if ( FT_New_Face( library, filepath, 0, &face ) )
{
@@ -244,7 +247,8 @@ Exit:
int
main( void )
main( int argc,
char** argv )
{
static const TestFont test_fonts[] =
{
@@ -276,6 +280,16 @@ main( void )
i++ )
ret |= test_font( library, testdata_dir, &test_fonts[i] );
/* Generated HVAR/VVAR fixtures retain the static gvar font's outline. */
for ( i = 1; i < (FT_UInt)argc; i++ )
{
TestFont font = { NULL, 1, 3, 50, 3, 0 };
font.filename = argv[i];
ret |= test_font( library, NULL, &font );
}
ret |= test_reset_without_axes( library, testdata_dir );
ret |= test_value_conditions( library, testdata_dir );
ret |= test_variation_store( library, testdata_dir );

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Add HVAR or VVAR to the static VARC/gvar fixture using only stdlib.
The component sets the private axis to a nonzero coordinate. Loading its
metrics must therefore parse an item variation store without an 'fvar'.
Distributed under the FreeType project license, LICENSE.TXT.
"""
from pathlib import Path
import struct
import sys
def checksum(data):
data += bytes(-len(data) % 4)
return sum(struct.unpack(">" + "I" * (len(data) // 4), data)) & 0xFFFFFFFF
def read_tables(data):
count, = struct.unpack_from(">H", data, 4)
tables = {}
for index in range(count):
tag, _, offset, size = struct.unpack_from(">4sIII", data, 12 + 16 * index)
tables[tag] = data[offset:offset + size]
return tables
def make_font(tables, vertical):
tables = tables.copy()
axis_count, = struct.unpack_from(">H", tables[b"gvar"], 4)
glyph_count, = struct.unpack_from(">H", tables[b"maxp"], 4)
assert b"fvar" not in tables and axis_count == 1
# One positive-axis region and one delta set per glyph, with implicit
# glyph-to-delta mapping. The component is glyph 2.
store = struct.pack(">HIHI", 1, 12, 1, 22)
store += struct.pack(">HHhhh", 1, 1, 0, 16384, 16384)
store += struct.pack(">4H", glyph_count, 1, 1, 0)
store += struct.pack(">" + "h" * glyph_count,
*(25 if i == 2 else 0 for i in range(glyph_count)))
if vertical:
tables[b"VVAR"] = struct.pack(">HH5I", 1, 0, 24, 0, 0, 0, 0) + store
vhea = bytearray(tables[b"hhea"])
struct.pack_into(">H", vhea, 10, 1000) # advanceHeightMax
struct.pack_into(">H", vhea, 34, glyph_count)
tables[b"vhea"] = bytes(vhea)
tables[b"vmtx"] = struct.pack(">Hh", 1000, 0) * glyph_count
else:
tables[b"HVAR"] = struct.pack(">HH4I", 1, 0, 20, 0, 0, 0) + store
head = bytearray(tables[b"head"])
struct.pack_into(">I", head, 8, 0)
tables[b"head"] = bytes(head)
count = len(tables)
power = 1 << (count.bit_length() - 1)
font = bytearray(struct.pack(">I4H", 0x10000, count, power * 16,
power.bit_length() - 1, count * 16 - power * 16))
font += bytes(16 * count)
for index, (tag, data) in enumerate(sorted(tables.items())):
offset = len(font)
struct.pack_into(">4sIII", font, 12 + 16 * index,
tag, checksum(data), offset, len(data))
font += data + bytes(-len(data) % 4)
if tag == b"head":
head_offset = offset
struct.pack_into(">I", font, head_offset + 8,
(0xB1B0AFBA - checksum(font)) & 0xFFFFFFFF)
return font
if __name__ == "__main__":
if len(sys.argv) != 4:
raise SystemExit("usage: make-hvvar-fonts.py input.ttf hvar.ttf vvar.ttf")
tables = read_tables(Path(sys.argv[1]).read_bytes())
for vertical, output in enumerate(sys.argv[2:]):
Path(output).write_bytes(make_font(tables, vertical))