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