// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Collections.Generic; namespace SixLabors.Fonts.Unicode { /// /// Represents a unicode string and all associated attributes /// for each character required for the Bidi algorithm /// internal class BidiData { private ArrayBuilder types; private ArrayBuilder pairedBracketTypes; private ArrayBuilder pairedBracketValues; private ArrayBuilder savedTypes; private ArrayBuilder savedPairedBracketTypes; private ArrayBuilder tempLevelBuffer; private readonly List paragraphPositions = new(); public sbyte ParagraphEmbeddingLevel { get; private set; } public bool HasBrackets { get; private set; } public bool HasEmbeddings { get; private set; } public bool HasIsolates { get; private set; } /// /// Gets the length of the data held by the BidiData /// public int Length => this.types.Length; /// /// Gets the bidi character type of each code point /// public ArraySlice Types { get; private set; } /// /// Gets the paired bracket type for each code point /// public ArraySlice PairedBracketTypes { get; private set; } /// /// Gets the paired bracket value for code point /// /// /// The paired bracket values are the code points /// of each character where the opening code point /// is replaced with the closing code point for easier /// matching. Also, bracket code points are mapped /// to their canonical equivalents /// public ArraySlice PairedBracketValues { get; private set; } /// /// Initialize with a text value. /// /// The text to process. /// The paragraph embedding level public void Init(ReadOnlySpan text, sbyte paragraphEmbeddingLevel) { // Set working buffer sizes // TODO: This allocates more than it should for some arrays. int length = CodePoint.GetCodePointCount(text); this.types.Length = length; this.pairedBracketTypes.Length = length; this.pairedBracketValues.Length = length; this.paragraphPositions.Clear(); this.ParagraphEmbeddingLevel = paragraphEmbeddingLevel; // Resolve the BidiCharacterType, paired bracket type and paired // bracket values for all code points this.HasBrackets = false; this.HasEmbeddings = false; this.HasIsolates = false; int i = 0; var codePointEnumerator = new SpanCodePointEnumerator(text); while (codePointEnumerator.MoveNext()) { CodePoint codePoint = codePointEnumerator.Current; BidiClass bidi = CodePoint.GetBidiClass(codePoint); // Look up BidiCharacterType BidiCharacterType dir = bidi.CharacterType; this.types[i] = dir; switch (dir) { case BidiCharacterType.LeftToRightEmbedding: case BidiCharacterType.LeftToRightOverride: case BidiCharacterType.RightToLeftEmbedding: case BidiCharacterType.RightToLeftOverride: case BidiCharacterType.PopDirectionalFormat: this.HasEmbeddings = true; break; case BidiCharacterType.LeftToRightIsolate: case BidiCharacterType.RightToLeftIsolate: case BidiCharacterType.FirstStrongIsolate: case BidiCharacterType.PopDirectionalIsolate: this.HasIsolates = true; break; } // Lookup paired bracket types BidiPairedBracketType pbt = bidi.PairedBracketType; this.pairedBracketTypes[i] = pbt; if (pbt == BidiPairedBracketType.Open) { // Opening bracket types can never have a null pairing. bidi.TryGetPairedBracket(out CodePoint paired); this.pairedBracketValues[i] = CodePoint.GetCanonicalType(paired).Value; this.HasBrackets = true; } else if (pbt == BidiPairedBracketType.Close) { this.pairedBracketValues[i] = CodePoint.GetCanonicalType(codePoint).Value; this.HasBrackets = true; } i++; } // Create slices on work buffers this.Types = this.types.AsSlice(); this.PairedBracketTypes = this.pairedBracketTypes.AsSlice(); this.PairedBracketValues = this.pairedBracketValues.AsSlice(); } /// /// Save the Types and PairedBracketTypes of this bididata /// /// /// This is used when processing embedded style runs with /// BidiCharacterType overrides. TextLayout saves the data, /// overrides the style runs to neutral, processes the bidi /// data for the entire paragraph and then restores this data /// before processing the embedded runs. /// public void SaveTypes() { // Capture the types data this.savedTypes.Clear(); this.savedTypes.Add(this.types.AsSlice()); this.savedPairedBracketTypes.Clear(); this.savedPairedBracketTypes.Add(this.pairedBracketTypes.AsSlice()); } /// /// Restore the data saved by SaveTypes /// public void RestoreTypes() { this.types.Clear(); this.types.Add(this.savedTypes.AsSlice()); this.pairedBracketTypes.Clear(); this.pairedBracketTypes.Add(this.savedPairedBracketTypes.AsSlice()); } /// /// Gets a temporary level buffer. Used by TextLayout when /// resolving style runs with different BidiCharacterType. /// /// Length of the required ExpandableBuffer /// An uninitialized level ExpandableBuffer public ArraySlice GetTempLevelBuffer(int length) { this.tempLevelBuffer.Clear(); return this.tempLevelBuffer.Add(length, false); } } }