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