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