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