// 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 CVT Variations table cvar.
/// The cvar table provides variation data for the Control Value Table (CVT)
/// used by TrueType hinting instructions. It uses the same Tuple Variation Store
/// format as gvar, but with a single dimension of deltas (CVT values rather than X/Y coordinates).
///
///
internal class CVarTable : Table
{
///
/// The table name identifier for the cvar table.
///
internal const string TableName = "cvar";
///
/// Initializes a new instance of the class.
///
/// The array of tuple variations containing CVT deltas.
public CVarTable(CVarTupleVariation[] tupleVariations)
=> this.TupleVariations = tupleVariations;
///
/// Gets the tuple variations containing CVT deltas.
///
public CVarTupleVariation[] TupleVariations { get; }
///
/// Loads the cvar table from the font reader.
/// The axis count must be known from the fvar table before loading cvar.
///
/// The font reader.
/// The number of variation axes from fvar.
/// The loaded cvar table, or null if not present.
public static CVarTable? Load(FontReader reader, int axisCount)
{
if (!reader.TryGetReaderAtTablePosition(TableName, out BigEndianBinaryReader? binaryReader))
{
return null;
}
using (binaryReader)
{
return Load(binaryReader, axisCount);
}
}
///
/// Loads the cvar table from the specified binary reader.
///
/// The big-endian binary reader positioned at the start of the cvar table.
/// The number of variation axes from fvar.
/// The .
public static CVarTable Load(BigEndianBinaryReader reader, int axisCount)
{
// cvar — CVT Variations Table
// The cvar table uses the Tuple Variation Store format.
// +--------------------------+-------------------------------------------+--------------------------------------------------------------+
// | Type | Name | Description |
// +==========================+===========================================+==============================================================+
// | uint16 | majorVersion | Major version — set to 1. |
// +--------------------------+-------------------------------------------+--------------------------------------------------------------+
// | uint16 | minorVersion | Minor version — set to 0. |
// +--------------------------+-------------------------------------------+--------------------------------------------------------------+
// | uint16 | tupleVariationCount | Packed field: high 4 bits are flags, |
// | | | low 12 bits are the number of tuple variation tables. |
// +--------------------------+-------------------------------------------+--------------------------------------------------------------+
// | Offset16 | dataOffset | Offset from the start of the cvar table to the |
// | | | serialized data. |
// +--------------------------+-------------------------------------------+--------------------------------------------------------------+
// | TupleVariation | tupleVariationHeaders[tupleVariationCount]| Array of tuple variation headers. |
// +--------------------------+-------------------------------------------+--------------------------------------------------------------+
ushort majorVersion = reader.ReadUInt16();
ushort minorVersion = reader.ReadUInt16();
if (majorVersion != 1)
{
throw new NotSupportedException("Only version 1 of cvar table is supported");
}
ushort tupleVariationCount = reader.ReadUInt16();
bool hasSharedPointNumbers = (tupleVariationCount & GlyphVariationData.SharedPointNumbersMask) != 0;
int tupleCount = tupleVariationCount & GlyphVariationData.CountMask;
ushort dataOffset = reader.ReadOffset16();
// Read all tuple variation headers.
TupleVariation[] tupleVariations = new TupleVariation[tupleCount];
for (int i = 0; i < tupleCount; i++)
{
tupleVariations[i] = TupleVariation.Load(reader, axisCount);
}
// Seek to the serialized data.
reader.Seek(dataOffset, SeekOrigin.Begin);
// If shared point numbers flag is set, decode them from the start of the serialized data.
ushort[]? sharedPointNumbers = null;
if (hasSharedPointNumbers)
{
sharedPointNumbers = GlyphVariationData.DecodePackedPoints(reader);
}
// Decode each tuple's serialized data.
// Unlike gvar, cvar has only one set of deltas per tuple (CVT value adjustments).
CVarTupleVariation[] cvarTuples = new CVarTupleVariation[tupleCount];
for (int i = 0; i < tupleCount; i++)
{
TupleVariation header = tupleVariations[i];
long tupleDataStart = reader.BaseStream.Position;
// Determine which CVT indices this tuple applies to.
ushort[]? pointNumbers;
if (header.HasPrivatePointNumbers)
{
pointNumbers = GlyphVariationData.DecodePackedPoints(reader);
}
else
{
pointNumbers = sharedPointNumbers;
}
int nPoints = pointNumbers is { Length: > 0 } ? pointNumbers.Length : 0;
short[]? deltas = null;
if (nPoints > 0)
{
// cvar has only one set of deltas (not X/Y pairs like gvar).
deltas = GlyphVariationData.DecodePackedDeltas(reader, nPoints);
}
else
{
// All CVT entries are referenced. Store raw bytes for deferred decoding.
long bytesConsumed = reader.BaseStream.Position - tupleDataStart;
int remaining = header.VariationDataSize - (int)bytesConsumed;
if (remaining > 0)
{
cvarTuples[i] = new CVarTupleVariation(header, pointNumbers, null, reader.ReadBytes(remaining));
continue;
}
}
// Skip any remaining bytes for this tuple.
long consumed = reader.BaseStream.Position - tupleDataStart;
int skip = header.VariationDataSize - (int)consumed;
if (skip > 0)
{
reader.BaseStream.Position += skip;
}
cvarTuples[i] = new CVarTupleVariation(header, pointNumbers, deltas, null);
}
return new CVarTable(cvarTuples);
}
}
///
/// Represents a single tuple variation for the cvar table with its CVT index references and deltas.
/// Unlike gvar's which has X/Y delta pairs,
/// cvar tuples have a single set of deltas for CVT values.
///
internal class CVarTupleVariation
{
///
/// Initializes a new instance of the class.
///
/// The tuple variation header containing peak coordinates and flags.
/// The CVT indices this tuple applies to, or null/empty for all CVT entries.
/// The CVT deltas, or null if deferred.
/// The raw serialized delta data for deferred decoding, or null if already decoded.
public CVarTupleVariation(
TupleVariation tupleVariation,
ushort[]? pointNumbers,
short[]? deltas,
byte[]? rawDeltaData)
{
this.TupleVariation = tupleVariation;
this.PointNumbers = pointNumbers;
this.Deltas = deltas;
this.RawDeltaData = rawDeltaData;
}
///
/// Gets the tuple variation header containing peak coordinates and flags.
///
public TupleVariation TupleVariation { get; }
///
/// Gets the CVT indices this tuple applies to.
/// An empty array means all CVT entries are referenced.
///
public ushort[]? PointNumbers { get; }
///
/// Gets the CVT deltas for the referenced entries.
/// Null when deltas apply to all CVT entries and were deferred (see ).
///
public short[]? Deltas { get; }
///
/// Gets the raw serialized delta data for deferred decoding.
/// Used when point numbers indicate "all CVT entries" and the actual count
/// is not known until the CVT table size is available.
///
public byte[]? RawDeltaData { get; }
}
}