// 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; } } }