// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Collections.Generic;
using System.Numerics;
namespace SixLabors.Fonts.Tables.General.Kern {
///
/// Represents the OpenType 'kern' table, which contains kerning pair adjustments
/// for positioning glyphs within a font.
///
///
internal sealed class KerningTable : Table
{
///
/// The table tag name identifying the 'kern' table.
///
internal const string TableName = "kern";
///
/// The array of kerning subtables contained in this table.
///
private readonly KerningSubTable[] kerningSubTable;
///
/// Initializes a new instance of the class.
///
/// The array of kerning subtables.
public KerningTable(KerningSubTable[] kerningSubTable)
=> this.kerningSubTable = kerningSubTable;
///
/// Gets the number of kerning subtables.
///
public int Count => this.kerningSubTable.Length;
///
/// Loads the from the specified font reader.
/// Returns an empty table if the 'kern' table is not present.
///
/// The font reader to read the table from.
/// The loaded .
public static KerningTable Load(FontReader fontReader)
{
if (!fontReader.TryGetReaderAtTablePosition(TableName, out BigEndianBinaryReader? binaryReader))
{
// this table is optional.
return new KerningTable([]);
}
using (binaryReader)
{
// Move to start of table.
return Load(binaryReader);
}
}
///
/// Loads the from the specified binary reader.
///
/// The binary reader positioned at the start of the kern table data.
/// The loaded .
public static KerningTable Load(BigEndianBinaryReader reader)
{
// +--------+---------+-------------------------------------------+
// | Type | Field | Description |
// +========+=========+===========================================+
// | uint16 | version | Table version number(0) |
// +--------+---------+-------------------------------------------+
// | uint16 | nTables | Number of subtables in the kerning table. |
// +--------+---------+-------------------------------------------+
ushort version = reader.ReadUInt16();
ushort subTableCount = reader.ReadUInt16();
List tables = new(subTableCount);
for (int i = 0; i < subTableCount; i++)
{
KerningSubTable? t = KerningSubTable.Load(reader); // returns null for unknown/supported table format
if (t != null)
{
tables.Add(t);
}
}
return new KerningTable([.. tables]);
}
///
/// Updates glyph positions by applying kerning adjustments for the specified glyph pair.
///
/// The font metrics used for position calculations.
/// The glyph positioning collection to update.
/// The index of the left glyph in the collection.
/// The index of the right glyph in the collection.
public void UpdatePositions(FontMetrics fontMetrics, GlyphPositioningCollection collection, int left, int right)
{
if (this.Count == 0 || collection.Count == 0)
{
return;
}
GlyphShapingData current = collection[left];
if (current.IsKerned)
{
// Already kerned via previous processing.
return;
}
ushort currentId = current.GlyphId;
ushort nextId = collection[right].GlyphId;
if (this.TryGetKerningOffset(currentId, nextId, out Vector2 result))
{
collection.Advance(fontMetrics, left, currentId, (short)result.X, (short)result.Y);
current.IsKerned = true;
}
}
///
/// Attempts to get the accumulated kerning offset for the specified pair of glyph indices
/// by iterating through all kerning subtables.
///
/// The glyph index of the current (left) glyph.
/// The glyph index of the next (right) glyph.
/// When this method returns, contains the accumulated kerning offset vector.
/// if any kerning was applied; otherwise, .
public bool TryGetKerningOffset(ushort current, ushort next, out Vector2 result)
{
result = Vector2.Zero;
if (this.Count == 0 || current == 0 || next == 0)
{
return false;
}
bool kerned = false;
foreach (KerningSubTable sub in this.kerningSubTable)
{
kerned |= sub.TryApplyOffset(current, next, ref result);
}
return kerned;
}
}
}