// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.Fonts.Unicode;
using SixLabors.Fonts.WellKnownIds;
using System.Collections.Generic;
using System.Linq;
namespace SixLabors.Fonts.Tables.General.CMap {
///
/// Format 12 is a segmented coverage subtable used for character codes beyond the BMP (U+0000 to U+10FFFF).
/// It uses 32-bit character codes and groups of sequential mappings.
///
///
internal sealed class Format12SubTable : CMapSubTable
{
///
/// Initializes a new instance of the class.
///
/// The language code for this subtable.
/// The platform identifier.
/// The platform-specific encoding identifier.
/// The array of sequential map groups.
public Format12SubTable(uint language, PlatformIDs platform, ushort encoding, SequentialMapGroup[] groups)
: base(platform, encoding, 4)
{
this.Language = language;
this.SequentialMapGroups = groups;
}
///
/// Gets the array of sequential map groups defining character-to-glyph mappings.
///
public SequentialMapGroup[] SequentialMapGroups { get; }
///
/// Gets the language code for this subtable.
///
public uint Language { get; }
///
public override bool TryGetGlyphId(CodePoint codePoint, out ushort glyphId)
{
int charAsInt = codePoint.Value;
for (int i = 0; i < this.SequentialMapGroups.Length; i++)
{
ref SequentialMapGroup seg = ref this.SequentialMapGroups[i];
if (charAsInt >= seg.StartCodePoint && charAsInt <= seg.EndCodePoint)
{
glyphId = (ushort)(charAsInt - seg.StartCodePoint + seg.StartGlyphId);
return true;
}
}
glyphId = 0;
return false;
}
///
public override bool TryGetCodePoint(ushort glyphId, out CodePoint codePoint)
{
for (int i = 0; i < this.SequentialMapGroups.Length; i++)
{
ref SequentialMapGroup seg = ref this.SequentialMapGroups[i];
if (glyphId >= seg.StartGlyphId && glyphId <= seg.StartGlyphId + seg.EndCodePoint - seg.StartCodePoint)
{
// Reverse the calculation:
// Forward: glyphId = (codePoint - StartCodePoint) + StartGlyphId
// Reverse: codePoint = (glyphId - StartGlyphId) + StartCodePoint
codePoint = new CodePoint(glyphId - seg.StartGlyphId + seg.StartCodePoint);
return true;
}
}
codePoint = default;
return false;
}
///
public override IEnumerable GetAvailableCodePoints()
=> this.SequentialMapGroups.SelectMany(segment =>
{
int start = (int)segment.StartCodePoint;
int end = (int)segment.EndCodePoint;
return Enumerable.Range(start, end - start + 1);
});
///
/// Loads one or more instances from the specified encoding records and reader.
///
/// The encoding records that share this subtable.
/// The binary reader positioned after the format field.
/// An enumerable of instances, one per encoding record.
public static IEnumerable Load(IEnumerable encodings, BigEndianBinaryReader reader)
{
// 'cmap' Subtable Format 4:
// Type | Name | Description
// -------------------|-------------------|------------------------------------------------------------------------
// uint16 | format | Subtable format; set to 12.
// uint16 | reserved | Reserved; set to 0
// uint32 | length | Byte length of this subtable(including the header)
// uint32 | language | For requirements on use of the language field, see "Use of the language field in 'cmap' subtables" in this document.
// uint32 | numGroups | Number of groupings which follow
// SequentialMapGroup | groups[numGroups] | Array of SequentialMapGroup records.
// format has already been read by this point skip it
ushort reserved = reader.ReadUInt16();
uint length = reader.ReadUInt32();
uint language = reader.ReadUInt32();
uint numGroups = reader.ReadUInt32();
var groups = new SequentialMapGroup[numGroups];
for (var i = 0; i < numGroups; i++)
{
groups[i] = SequentialMapGroup.Load(reader);
}
foreach (EncodingRecord encoding in encodings)
{
yield return new Format12SubTable(language, encoding.PlatformID, encoding.EncodingID, groups);
}
}
///
/// Represents a sequential map group record that maps a contiguous range of character codes
/// to a contiguous range of glyph indices.
///
internal readonly struct SequentialMapGroup
{
///
/// The first character code in this group.
///
public readonly uint StartCodePoint;
///
/// The last character code in this group (inclusive).
///
public readonly uint EndCodePoint;
///
/// The glyph index corresponding to the starting character code.
///
public readonly uint StartGlyphId;
///
/// Initializes a new instance of the struct.
///
/// The first character code in this group.
/// The last character code in this group.
/// The glyph index corresponding to the starting character code.
public SequentialMapGroup(uint startCodePoint, uint endCodePoint, uint startGlyph)
{
this.StartCodePoint = startCodePoint;
this.EndCodePoint = endCodePoint;
this.StartGlyphId = startGlyph;
}
///
/// Loads a from the specified reader.
///
/// The binary reader positioned at the sequential map group data.
/// The parsed .
public static SequentialMapGroup Load(BigEndianBinaryReader reader)
{
var startCodePoint = reader.ReadUInt32();
var endCodePoint = reader.ReadUInt32();
var startGlyph = reader.ReadUInt32();
return new SequentialMapGroup(startCodePoint, endCodePoint, startGlyph);
}
}
}
}