// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.IO;
namespace SixLabors.Fonts.Tables.AdvancedTypographic.Variations {
///
/// Implements reading the Font Variations Table `gvar`.
///
///
internal class GVarTable : Table
{
///
/// The table name identifier for the gvar table.
///
internal const string TableName = "gvar";
///
/// Initializes a new instance of the class.
///
/// The number of variation axes.
/// The number of glyphs in the font.
/// The shared tuple records array, indexed by tuple index then axis.
/// The array of per-glyph variation data.
public GVarTable(ushort axisCount, ushort glyphCount, float[,] sharedTuples, GlyphVariationData[] glyphVariations)
{
this.AxisCount = axisCount;
this.GlyphCount = glyphCount;
this.SharedTuples = sharedTuples;
this.GlyphVariations = glyphVariations;
}
///
/// Gets the number of variation axes in the font.
///
public ushort AxisCount { get; }
///
/// Gets the number of glyphs in this font.
///
public ushort GlyphCount { get; }
///
/// Gets the shared tuple records array. Each row contains normalized coordinates for one shared tuple, one value per axis.
///
public float[,] SharedTuples { get; }
///
/// Gets the array of per-glyph variation data tables.
///
public GlyphVariationData[] GlyphVariations { get; }
///
/// Loads the gvar table from the specified font reader.
///
/// The font reader.
/// The , or if the table is not present.
public static GVarTable? Load(FontReader reader)
{
if (!reader.TryGetReaderAtTablePosition(TableName, out BigEndianBinaryReader? binaryReader, out TableHeader? header))
{
return null;
}
using (binaryReader)
{
return Load(binaryReader, header);
}
}
///
/// Loads the gvar table from the specified binary reader.
///
/// The big-endian binary reader positioned at the start of the gvar table.
/// The table header providing the table length.
/// The .
public static GVarTable Load(BigEndianBinaryReader reader, TableHeader header)
{
// VariationsTable `gvar`
// +-----------------+----------------------------------------+-------------------------------------------------------------------------+
// | Type | Name | Description |
// +=================+========================================+=========================================================================+
// | uint16 | majorVersion | Major version number of the font variations table — set to 1. |
// +-----------------+----------------------------------------+-------------------------------------------------------------------------+
// | uint16 | minorVersion | Minor version number of the font variations table — set to 0. |
// +-----------------+----------------------------------------+-------------------------------------------------------------------------+
// | uint16 | axisCount | The number of variation axes in the font |
// | | | (the number of records in the axes array). |
// +-----------------+----------------------------------------+-------------------------------------------------------------------------+
// | uint16 | sharedTupleCount | The number of shared tuple records. Shared tuple records can |
// | | | be referenced within glyph variation data tables for multiple glyphs, |
// | | | as opposed to other tuple records stored directly within a glyph |
// | | | variation data table. |
// +-----------------+----------------------------------------+-------------------------------------------------------------------------+
// | Offset32 | sharedTuplesOffset | Offset from the start of this table to the shared tuple records. |
// +-----------------+----------------------------------------+-------------------------------------------------------------------------+
// | uint16 | glyphCount | The number of glyphs in this font. This must match the number of glyphs |
// | | | stored elsewhere in the font. |
// +-----------------+----------------------------------------+-------------------------------------------------------------------------+
// | uint16 | flags | Bit-field that gives the format of the offset array that follows. |
// | | | If bit 0 is clear, the offsets are uint16; if bit 0 is set, |
// | | | the offsets are uint32. |
// +-----------------+----------------------------------------+-------------------------------------------------------------------------+
// | Offset32 | glyphVariationDataArrayOffset | Offset from the start of this table to the array of GlyphVariationData |
// | | | tables. |
// +-----------------+----------------------------------------+-------------------------------------------------------------------------+
// | Offset16 or | glyphVariationDataOffsets[glyphCount+1]| Offsets from the start of the GlyphVariationData array to each |
// | Offset32 | | GlyphVariationData table. |
// +-----------------+----------------------------------------+-------------------------------------------------------------------------+
uint gvarTableLength = header.Length;
ushort major = reader.ReadUInt16();
ushort minor = reader.ReadUInt16();
ushort axisCount = reader.ReadUInt16();
ushort sharedTupleCount = reader.ReadUInt16();
uint sharedTuplesOffset = reader.ReadOffset32();
ushort glyphCount = reader.ReadUInt16();
ushort flags = reader.ReadUInt16();
bool is32BitOffset = (flags & 1) == 1;
uint glyphVariationDataArrayOffset = reader.ReadOffset32();
if (major != 1)
{
throw new NotSupportedException("Only version 1 of gvar table is supported");
}
// Read glyphVariationDataOffsets[glyphCount + 1] immediately after the header,
// as required by the spec and as done by FreeType.
int offsetCount = glyphCount + 1;
uint[] glyphVariationOffsets = new uint[offsetCount];
for (int i = 0; i < offsetCount; i++)
{
// If offsets are 16-bit, values are stored in units of 2 bytes.
glyphVariationOffsets[i] = is32BitOffset
? reader.ReadUInt32()
: (uint)(reader.ReadUInt16() * 2);
}
// Shared tuple records
float[,] sharedTuples = new float[sharedTupleCount, axisCount];
if (sharedTupleCount > 0 && axisCount > 0)
{
long tuplesPos = sharedTuplesOffset;
long tuplesLimit = glyphVariationDataArrayOffset;
long bytesPerTuple = (long)axisCount * 2;
long bytesAvailable = tuplesLimit - tuplesPos;
long maxTuples = bytesAvailable > 0
? bytesAvailable / bytesPerTuple
: 0;
int tuplesToRead = (int)Math.Min(sharedTupleCount, maxTuples);
reader.Seek(tuplesPos, SeekOrigin.Begin);
for (int i = 0; i < tuplesToRead; i++)
{
for (int j = 0; j < axisCount; j++)
{
sharedTuples[i, j] = reader.ReadF2Dot14();
}
}
// Any remaining tuples default to 0.0F.
}
// GlyphVariationData tables
long glyphDataBase = glyphVariationDataArrayOffset;
GlyphVariationData[] glyphVariations = new GlyphVariationData[glyphCount];
// Reader is positioned at table start
long gvarEnd = gvarTableLength;
GlyphVariationData empty = new([]);
for (int i = 0; i < glyphCount; i++)
{
long start = glyphDataBase + glyphVariationOffsets[i];
long end = glyphDataBase + glyphVariationOffsets[i + 1]; // spec gives glyphCount+1 offsets
// Validate range (must be within table and non-decreasing).
// Equal offsets mean the glyph has no variation data.
if (start == end || start < glyphDataBase || end < start || end > gvarEnd || start + 2 > gvarEnd)
{
glyphVariations[i] = empty;
continue;
}
glyphVariations[i] = GlyphVariationData.Load(reader, start, axisCount);
}
return new GVarTable(axisCount, glyphCount, sharedTuples, glyphVariations);
}
}
}