// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.Fonts.Unicode; using SixLabors.Fonts.WellKnownIds; using System; using System.Collections.Generic; using System.IO; namespace SixLabors.Fonts.Tables.General.CMap { /// /// Subtable format 14 specifies the Unicode Variation Sequences (UVSes) supported by the font. /// A Variation Sequence, according to the Unicode Standard, comprises a base character followed /// by a variation selector. For example, <U+82A6, U+E0101>. /// /// internal sealed class Format14SubTable : CMapSubTable { /// /// The dictionary mapping variation selector code points to their associated variation selector records. /// private readonly Dictionary variationSelectors; /// /// Initializes a new instance of the class. /// /// The dictionary of variation selector records keyed by selector code point. /// The platform identifier. /// The platform-specific encoding identifier. private Format14SubTable(Dictionary variationSelectors, PlatformIDs platform, ushort encoding) : base(platform, encoding, 5) => this.variationSelectors = variationSelectors; /// /// 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. /// The byte offset to the start of this format 14 subtable. /// An enumerable of instances, one per encoding record. public static IEnumerable Load( IEnumerable encodings, BigEndianBinaryReader reader, long offset) { // +-------------------+------------------------------------+------------------------------------------------------+ // | Type | Name | Description | // +===================+====================================+======================================================+ // | uint16 | format | Subtable format. Set to 14. | // +-------------------+------------------------------------+------------------------------------------------------+ // | uint32 | length | Byte length of this subtable (including this header) | // +-------------------+------------------------------------+------------------------------------------------------+ // | uint32 | numVarSelectorRecords | Number of variation Selector Records | // +-------------------+------------------------------------+------------------------------------------------------+ // | VariationSelector | varSelector[numVarSelectorRecords] | Array of VariationSelector records. | // +-------------------+------------------------------------+------------------------------------------------------+ uint length = reader.ReadUInt32(); uint numVarSelectorRecords = reader.ReadUInt32(); var variationSelectors = new Dictionary(); uint[] varSelectors = new uint[numVarSelectorRecords]; uint[] defaultUVSOffsets = new uint[numVarSelectorRecords]; uint[] nonDefaultUVSOffsets = new uint[numVarSelectorRecords]; for (int i = 0; i < numVarSelectorRecords; ++i) { // +----------+---------------------+----------------------------------------------------+ // | Type | Name | Description | // +==========+=====================+====================================================+ // | uint24 | varSelector | Variation selector | // +----------+---------------------+----------------------------------------------------+ // | Offset32 | defaultUVSOffset | Offset from the start of the format 14 subtable to | // | | | Default UVS Table. May be 0. | // +----------+---------------------+----------------------------------------------------+ // | Offset32 | nonDefaultUVSOffset | Offset from the start of the format 14 subtable to | // | | | Non-Default UVS Table. May be 0. | // +----------+---------------------+----------------------------------------------------+ varSelectors[i] = reader.ReadUInt24(); defaultUVSOffsets[i] = reader.ReadUInt32(); nonDefaultUVSOffsets[i] = reader.ReadUInt32(); } for (int i = 0; i < numVarSelectorRecords; ++i) { var selector = new VariationSelector(); if (defaultUVSOffsets[i] != 0) { // Default UVS table // +--------------+-------------------------------+-------------------------------------+ // | Type | Name | Description | // +==============+===============================+=====================================+ // | uint32 | numUnicodeValueRanges | Number of Unicode character ranges. | // +--------------+-------------------------------+-------------------------------------+ // | UnicodeRange | ranges[numUnicodeValueRanges] | Array of UnicodeRange records. | // +--------------+-------------------------------+-------------------------------------+ // UnicodeRange Record // +--------+-------------------+-------------------------------------------+ // | Type | Name | Description | // +========+===================+===========================================+ // | uint24 | startUnicodeValue | First value in this range | // +--------+-------------------+-------------------------------------------+ // | uint8 | additionalCount | Number of additional values in this range | // +--------+-------------------+-------------------------------------------+ reader.Seek(offset + defaultUVSOffsets[i], SeekOrigin.Begin); uint numUnicodeValueRanges = reader.ReadUInt32(); for (int n = 0; n < numUnicodeValueRanges; n++) { uint startCode = reader.ReadUInt24(); selector.DefaultStartCodes.Add(startCode); selector.DefaultEndCodes.Add(startCode + reader.ReadByte()); } } if (nonDefaultUVSOffsets[i] != 0) { // Non-Default UVS table // +------------+-----------------------------+------------------------------------+ // | Type | Name | Description | // +============+=============================+====================================+ // | uint32 | numUVSMappings | Number of UVS Mappings that follow | // +------------+-----------------------------+------------------------------------+ // | UVSMapping | uvsMappings[numUVSMappings] | Array of UVSMapping records. | // +------------+-----------------------------+------------------------------------+ // UVSMapping Record // +--------+--------------+-------------------------------+ // | Type | Name | Description | // +========+==============+===============================+ // | uint24 | unicodeValue | Base Unicode value of the UVS | // +--------+--------------+-------------------------------+ // | uint16 | glyphID | Glyph ID of the UVS | // +--------+--------------+-------------------------------+ reader.Seek(offset + nonDefaultUVSOffsets[i], SeekOrigin.Begin); uint numUVSMappings = reader.ReadUInt32(); for (int n = 0; n < numUVSMappings; n++) { uint unicodeValue = reader.ReadUInt24(); ushort glyphID = reader.ReadUInt16(); selector.UVSMappings.Add(unicodeValue, glyphID); } } variationSelectors.Add(varSelectors[i], selector); } foreach (EncodingRecord encoding in encodings) { yield return new Format14SubTable(variationSelectors, encoding.PlatformID, encoding.EncodingID); } } /// public override bool TryGetGlyphId(CodePoint codePoint, out ushort glyphId) { glyphId = 0; return false; } /// public override bool TryGetCodePoint(ushort glyphId, out CodePoint codePoint) { codePoint = default; return false; } /// public override IEnumerable GetAvailableCodePoints() => Array.Empty(); /// /// Resolves a glyph identifier for a base character and variation selector pair using Unicode Variation Sequences. /// /// The base character code point. /// The default glyph index for the base character. /// The variation selector code point. /// The resolved glyph identifier, or 0 if the next code point is not a variation selector. public ushort CharacterPairToGlyphId(CodePoint codePoint, ushort defaultGlyphIndex, CodePoint nextCodePoint) { // Only check codepoint if nextCodepoint is a variation selector if (this.variationSelectors.TryGetValue((uint)nextCodePoint.Value, out VariationSelector? sel)) { // If the sequence is a non-default UVS, return the mapped glyph if (sel.UVSMappings.TryGetValue((uint)codePoint.Value, out ushort ret)) { return ret; } // If the sequence is a default UVS, return the default glyph for (int i = 0; i < sel.DefaultStartCodes.Count; ++i) { if (codePoint.Value >= sel.DefaultStartCodes[i] && codePoint.Value < sel.DefaultEndCodes[i]) { return defaultGlyphIndex; } } // At this point we are neither a non-default UVS nor a default UVS, // but we know the nextCodepoint is a variation selector. Unicode says // this glyph should be invisible: "no visible rendering for the VS" // (http://unicode.org/faq/unsup_char.html#4) return defaultGlyphIndex; } // In all other cases, return 0 return 0; } /// /// Represents a variation selector record containing default UVS ranges and non-default UVS mappings. /// private class VariationSelector { /// /// Gets the list of start code points for default UVS ranges. /// public List DefaultStartCodes { get; } = []; /// /// Gets the list of end code points (exclusive) for default UVS ranges. /// public List DefaultEndCodes { get; } = []; /// /// Gets the dictionary mapping base character code points to glyph indices for non-default UVS mappings. /// public Dictionary UVSMappings { get; } = []; } } }