// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; namespace SixLabors.Fonts.Tables.AdvancedTypographic.Variations { /// /// Implements reading the font variations table `MVAR`. /// The MVAR table is used in variable fonts to provide variations for global font metric values /// such as ascender, descender, line gap, caret metrics, and other font-wide measurements. /// /// internal class MVarTable : Table { /// /// The table name identifier for the MVAR table. /// internal const string TableName = "MVAR"; /// /// Initializes a new instance of the class. /// /// The item variation store containing delta data. /// The array of metric value records. public MVarTable(ItemVariationStore itemVariationStore, MetricValueRecord[] valueRecords) { this.ItemVariationStore = itemVariationStore; this.ValueRecords = valueRecords; } /// /// Gets the item variation store containing the variation delta data. /// public ItemVariationStore ItemVariationStore { get; } /// /// Gets the array of metric value records, sorted by tag for binary search. /// public MetricValueRecord[] ValueRecords { get; } /// /// Loads the MVAR table from the specified font reader. /// /// The font reader. /// The , or if the table is not present. public static MVarTable? Load(FontReader reader) { if (!reader.TryGetReaderAtTablePosition(TableName, out BigEndianBinaryReader? binaryReader)) { return null; } using (binaryReader) { return Load(binaryReader); } } /// /// Loads the MVAR table from the specified binary reader. /// /// The big-endian binary reader positioned at the start of the MVAR table. /// The . public static MVarTable Load(BigEndianBinaryReader reader) { // MVAR — Metrics Variations Table // +--------------------------+------------------------------------------+----------------------------------------------------+ // | Type | Name | Description | // +==========================+==========================================+====================================================+ // | uint16 | majorVersion | Major version — set to 1. | // +--------------------------+------------------------------------------+----------------------------------------------------+ // | uint16 | minorVersion | Minor version — set to 0. | // +--------------------------+------------------------------------------+----------------------------------------------------+ // | uint16 | reserved | Not used; set to 0. | // +--------------------------+------------------------------------------+----------------------------------------------------+ // | uint16 | valueRecordSize | Size in bytes of each value record. | // +--------------------------+------------------------------------------+----------------------------------------------------+ // | uint16 | valueRecordCount | Number of value records. | // +--------------------------+------------------------------------------+----------------------------------------------------+ // | Offset16 | itemVariationStoreOffset | Offset to ItemVariationStore. | // +--------------------------+------------------------------------------+----------------------------------------------------+ // | ValueRecord[] | valueRecords[valueRecordCount] | Array of value records. | // +--------------------------+------------------------------------------+----------------------------------------------------+ ushort majorVersion = reader.ReadUInt16(); ushort minorVersion = reader.ReadUInt16(); ushort reserved = reader.ReadUInt16(); ushort valueRecordSize = reader.ReadUInt16(); ushort valueRecordCount = reader.ReadUInt16(); ushort itemVariationStoreOffset = reader.ReadOffset16(); if (majorVersion != 1) { throw new NotSupportedException("Only version 1 of MVAR table is supported"); } // Read the value records. Each is typically 8 bytes (Tag + outerIndex + innerIndex). MetricValueRecord[] valueRecords = new MetricValueRecord[valueRecordCount]; for (int i = 0; i < valueRecordCount; i++) { long recordStart = reader.BaseStream.Position; uint tag = reader.ReadUInt32(); ushort outerIndex = reader.ReadUInt16(); ushort innerIndex = reader.ReadUInt16(); valueRecords[i] = new MetricValueRecord(tag, outerIndex, innerIndex); // Skip any extra bytes if valueRecordSize > 8 (future compatibility). long consumed = reader.BaseStream.Position - recordStart; if (consumed < valueRecordSize) { reader.BaseStream.Position += valueRecordSize - consumed; } } // Load the ItemVariationStore. ItemVariationStore itemVariationStore = ItemVariationStore.Load(reader, itemVariationStoreOffset); return new MVarTable(itemVariationStore, valueRecords); } /// /// Finds the value record for the given tag using binary search. /// Returns true if found, with the outer and inner indices set. /// /// The 4-byte metric tag to look up. /// The outer index into the ItemVariationStore. /// The inner index into the ItemVariationStore. /// True if the tag was found; false otherwise. public bool TryGetIndices(Tag tag, out ushort outerIndex, out ushort innerIndex) { // ValueRecords are sorted by tag per the spec, so binary search is valid. int lo = 0; int hi = this.ValueRecords.Length - 1; while (lo <= hi) { int mid = lo + ((hi - lo) >> 1); Tag midTag = this.ValueRecords[mid].Tag; if (midTag == tag) { outerIndex = this.ValueRecords[mid].DeltaSetOuterIndex; innerIndex = this.ValueRecords[mid].DeltaSetInnerIndex; return true; } if (midTag.Value < tag.Value) { lo = mid + 1; } else { hi = mid - 1; } } outerIndex = 0; innerIndex = 0; return false; } } }