mirror of
https://github.com/Facepunch/sbox-public.git
synced 2025-12-23 22:48:07 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
726 lines
20 KiB
C#
726 lines
20 KiB
C#
|
|
// RichTextKit
|
|
// Copyright © 2019-2020 Topten Software. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this product except in compliance with the License. You may obtain
|
|
// a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
using SkiaSharp;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Topten.RichTextKit.Utils;
|
|
|
|
namespace Topten.RichTextKit
|
|
{
|
|
/// <summary>
|
|
/// Represents a block of formatted, laid out and measurable text
|
|
/// </summary>
|
|
public class StyledText
|
|
{
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
public StyledText()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a styled text block from unstyled text
|
|
/// </summary>
|
|
/// <param name="codePoints"></param>
|
|
public StyledText( Slice<int> codePoints )
|
|
{
|
|
AddText( codePoints, null );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear the content of this text block
|
|
/// </summary>
|
|
public virtual void Clear()
|
|
{
|
|
// Reset everything
|
|
_codePoints.Clear();
|
|
StyleRun.Pool.Value.ReturnAndClear( _styleRuns );
|
|
_hasTextDirectionOverrides = false;
|
|
OnChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The length of the added text in code points
|
|
/// </summary>
|
|
public int Length => _codePoints.Length;
|
|
|
|
/// <summary>
|
|
/// Get the code points of this text block
|
|
/// </summary>
|
|
public Utf32Buffer CodePoints => _codePoints;
|
|
|
|
/// <summary>
|
|
/// Get the text runs as added by AddText
|
|
/// </summary>
|
|
public IReadOnlyList<StyleRun> StyleRuns
|
|
{
|
|
get
|
|
{
|
|
return _styleRuns;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a code point index to a character index
|
|
/// </summary>
|
|
/// <param name="codePointIndex">The code point index to convert</param>
|
|
/// <returns>The converted index</returns>
|
|
public int CodePointToCharacterIndex( int codePointIndex )
|
|
{
|
|
return _codePoints.Utf32OffsetToUtf16Offset( codePointIndex );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a character index to a code point index
|
|
/// </summary>
|
|
/// <param name="characterIndex">The character index to convert</param>
|
|
/// <returns>The converted index</returns>
|
|
public int CharacterToCodePointIndex( int characterIndex )
|
|
{
|
|
return _codePoints.Utf16OffsetToUtf32Offset( characterIndex );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add text to this text block
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The added text will be internally coverted to UTF32.
|
|
///
|
|
/// Note that all text indicies returned by and accepted by this object will
|
|
/// be UTF32 "code point indicies". To convert between UTF16 character indicies
|
|
/// and UTF32 code point indicies use the <see cref="CodePointToCharacterIndex(int)"/>
|
|
/// and <see cref="CharacterToCodePointIndex(int)"/> methods
|
|
/// </remarks>
|
|
/// <param name="text">The text to add</param>
|
|
/// <param name="style">The style of the text</param>
|
|
public void AddText( ReadOnlySpan<char> text, IStyle style )
|
|
{
|
|
// Quit if redundant
|
|
if ( text.Length == 0 )
|
|
return;
|
|
|
|
// Add to buffer
|
|
var utf32 = _codePoints.Add( text );
|
|
|
|
// Create a run
|
|
var run = StyleRun.Pool.Value.Get();
|
|
run.CodePointBuffer = _codePoints;
|
|
run.Start = utf32.Start;
|
|
run.Length = utf32.Length;
|
|
run.Style = style;
|
|
if ( style != null )
|
|
_hasTextDirectionOverrides |= style.TextDirection != TextDirection.Auto;
|
|
|
|
// Add run
|
|
_styleRuns.Add( run );
|
|
|
|
// Need new layout
|
|
OnChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add text to this paragraph
|
|
/// </summary>
|
|
/// <param name="text">The text to add</param>
|
|
/// <param name="style">The style of the text</param>
|
|
public void AddText( Slice<int> text, IStyle style )
|
|
{
|
|
if ( text.Length == 0 )
|
|
return;
|
|
|
|
// Add to UTF-32 buffer
|
|
var utf32 = _codePoints.Add( text );
|
|
|
|
// Create a run
|
|
var run = StyleRun.Pool.Value.Get();
|
|
run.CodePointBuffer = _codePoints;
|
|
run.Start = utf32.Start;
|
|
run.Length = utf32.Length;
|
|
run.Style = style;
|
|
if ( style != null )
|
|
_hasTextDirectionOverrides |= style.TextDirection != TextDirection.Auto;
|
|
|
|
// Add run
|
|
_styleRuns.Add( run );
|
|
|
|
// Need new layout
|
|
OnChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add text to this text block
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The added text will be internally coverted to UTF32.
|
|
///
|
|
/// Note that all text indicies returned by and accepted by this object will
|
|
/// be UTF32 "code point indicies". To convert between UTF16 character indicies
|
|
/// and UTF32 code point indicies use the <see cref="CodePointToCharacterIndex(int)"/>
|
|
/// and <see cref="CharacterToCodePointIndex(int)"/> methods
|
|
/// </remarks>
|
|
/// <param name="text">The text to add</param>
|
|
/// <param name="style">The style of the text</param>
|
|
public void AddText( string text, IStyle style )
|
|
{
|
|
AddText( text.AsSpan(), style );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add all the text from another text block to this text block
|
|
/// </summary>
|
|
/// <param name="text">Text to add</param>
|
|
public void AddText( StyledText text )
|
|
{
|
|
foreach ( var sr in text.StyleRuns )
|
|
{
|
|
AddText( sr.CodePoints, sr.Style );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add all the text from another text block to this text block
|
|
/// </summary>
|
|
/// <param name="offset">The position at which to insert the text</param>
|
|
/// <param name="text">Text to add</param>
|
|
public void InsertText( int offset, StyledText text )
|
|
{
|
|
foreach ( var sr in text.StyleRuns )
|
|
{
|
|
InsertText( offset, sr.CodePoints, sr.Style );
|
|
offset += sr.CodePoints.Length;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add text to this text block
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If the style is null, the new text will acquire the style of the character
|
|
/// before the insertion point. If the text block is currently empty the style
|
|
/// must be supplied. If inserting at the start of a non-empty text block the
|
|
/// style will be that of the first existing style run
|
|
/// </remarks>
|
|
/// <param name="position">The position to insert the text</param>
|
|
/// <param name="text">The text to add</param>
|
|
/// <param name="style">The style of the text (optional)</param>
|
|
public void InsertText( int position, Slice<int> text, IStyle style = null )
|
|
{
|
|
// Redundant?
|
|
if ( text.Length == 0 )
|
|
return;
|
|
|
|
if ( style == null && _styleRuns.Count == 0 )
|
|
throw new InvalidOperationException( "Must supply style when inserting into an empty text block" );
|
|
|
|
// Add to UTF-32 buffer
|
|
var utf32 = _codePoints.Insert( position, text );
|
|
|
|
// Update style runs
|
|
FinishInsert( utf32, style );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add text to this text block
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If the style is null, the new text will acquire the style of the character
|
|
/// before the insertion point. If the text block is currently empty the style
|
|
/// must be supplied. If inserting at the start of a non-empty text block the
|
|
/// style will be that of the first existing style run
|
|
/// </remarks>
|
|
/// <param name="position">The position to insert the text</param>
|
|
/// <param name="text">The text to add</param>
|
|
/// <param name="style">The style of the text (optional)</param>
|
|
public void InsertText( int position, ReadOnlySpan<char> text, IStyle style = null )
|
|
{
|
|
// Redundant?
|
|
if ( text.Length == 0 )
|
|
return;
|
|
|
|
if ( style == null && _styleRuns.Count == 0 )
|
|
throw new InvalidOperationException( "Must supply style when inserting into an empty text block" );
|
|
|
|
// Add to UTF-32 buffer
|
|
var utf32 = _codePoints.Insert( position, text );
|
|
|
|
// Update style runs
|
|
FinishInsert( utf32, style );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add text to this text block
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If the style is null, the new text will acquire the style of the character
|
|
/// before the insertion point. If the text block is currently empty the style
|
|
/// must be supplied. If inserting at the start of a non-empty text block the
|
|
/// style will be that of the first existing style run
|
|
/// </remarks>
|
|
/// <param name="position">The position to insert the text</param>
|
|
/// <param name="text">The text to add</param>
|
|
/// <param name="style">The style of the text (optional)</param>
|
|
public void InsertText( int position, string text, IStyle style = null )
|
|
{
|
|
// Redundant?
|
|
if ( text.Length == 0 )
|
|
return;
|
|
|
|
if ( style == null && _styleRuns.Count == 0 )
|
|
throw new InvalidOperationException( "Must supply style when inserting into an empty text block" );
|
|
|
|
// Add to UTF-32 buffer
|
|
var utf32 = _codePoints.Insert( position, text );
|
|
|
|
// Update style runs
|
|
FinishInsert( utf32, style );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes text from this text block
|
|
/// </summary>
|
|
/// <param name="position">The code point index to delete from</param>
|
|
/// <param name="length">The number of code points to delete</param>
|
|
public void DeleteText( int position, int length )
|
|
{
|
|
if ( length == 0 )
|
|
return;
|
|
|
|
// Delete text from the code point buffer
|
|
_codePoints.Delete( position, length );
|
|
|
|
// Fix up style runs
|
|
for ( int i = 0; i < _styleRuns.Count; i++ )
|
|
{
|
|
// Get the run
|
|
var sr = _styleRuns[i];
|
|
|
|
// Ignore runs before the deleted range
|
|
if ( sr.End <= position )
|
|
continue;
|
|
|
|
// Runs that start before the deleted range
|
|
if ( sr.Start < position )
|
|
{
|
|
if ( sr.End <= position + length )
|
|
{
|
|
// Truncate runs the overlap with the start of the delete range
|
|
sr.Length = position - sr.Start;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// Shorten runs that completely cover the deleted range
|
|
sr.Length -= length;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Runs that start within the deleted range
|
|
if ( sr.Start < position + length )
|
|
{
|
|
if ( sr.End <= position + length )
|
|
{
|
|
// Delete runs that are completely within the deleted range
|
|
_styleRuns.RemoveAt( i );
|
|
StyleRun.Pool.Value.Return( sr );
|
|
i--;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// Runs that overlap the end of the deleted range, just
|
|
// keep the part past the deleted range
|
|
sr.Length = sr.End - (position + length);
|
|
sr.Start = position;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Run is after the deleted range, shuffle it back
|
|
sr.Start -= length;
|
|
}
|
|
|
|
// coalesc runs
|
|
CoalescStyleRuns();
|
|
|
|
// Need new layout
|
|
OnChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overwrites the styles of existing text in the text block
|
|
/// </summary>
|
|
/// <param name="position">The code point index of the start of the text</param>
|
|
/// <param name="length">The length of the text</param>
|
|
/// <param name="style">The new style to be applied</param>
|
|
public void ApplyStyle( int position, int length, IStyle style )
|
|
{
|
|
// Check args
|
|
if ( position < 0 || position + length > this.Length )
|
|
throw new ArgumentException( "Invalid range" );
|
|
if ( style == null )
|
|
throw new ArgumentNullException( nameof( style ) );
|
|
|
|
// Redundant?
|
|
if ( length == 0 )
|
|
return;
|
|
|
|
// Easy case when applying same style to entire text block
|
|
if ( position == 0 && length == this.Length )
|
|
{
|
|
// Remove excess runs
|
|
while ( _styleRuns.Count > 1 )
|
|
{
|
|
StyleRun.Pool.Value.Return( _styleRuns[1] );
|
|
_styleRuns.RemoveAt( 1 );
|
|
}
|
|
|
|
// Reconfigure the first
|
|
_styleRuns[0].Start = 0;
|
|
_styleRuns[0].Length = length;
|
|
_styleRuns[0].Style = style;
|
|
|
|
// Reset text direction overrides flag
|
|
_hasTextDirectionOverrides = style.TextDirection != TextDirection.Auto;
|
|
|
|
// Invalidate and done
|
|
OnChanged();
|
|
return;
|
|
}
|
|
|
|
// Get all intersecting runs
|
|
int newRunPos = -1;
|
|
foreach ( var subRun in _styleRuns.GetIntersectingRunsReverse( position, length ) )
|
|
{
|
|
if ( subRun.Partial )
|
|
{
|
|
var run = _styleRuns[subRun.Index];
|
|
|
|
|
|
if ( subRun.Offset == 0 )
|
|
{
|
|
// Overlaps start of existing run, keep end
|
|
run.Start += subRun.Length;
|
|
run.Length -= subRun.Length;
|
|
newRunPos = subRun.Index;
|
|
}
|
|
else if ( subRun.Offset + subRun.Length == run.Length )
|
|
{
|
|
// Overlaps end of existing run, keep start
|
|
run.Length = subRun.Offset;
|
|
newRunPos = subRun.Index + 1;
|
|
}
|
|
else
|
|
{
|
|
// Internal to existing run, keep start and end
|
|
|
|
// Create new run for end
|
|
var endRun = StyleRun.Pool.Value.Get();
|
|
endRun.CodePointBuffer = _codePoints;
|
|
endRun.Start = run.Start + subRun.Offset + subRun.Length;
|
|
endRun.Length = run.End - endRun.Start;
|
|
endRun.Style = run.Style;
|
|
_styleRuns.Insert( subRun.Index + 1, endRun );
|
|
|
|
// Shorten the existing run to keep start
|
|
run.Length = subRun.Offset;
|
|
|
|
newRunPos = subRun.Index + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove completely covered style runs
|
|
StyleRun.Pool.Value.Return( _styleRuns[subRun.Index] );
|
|
_styleRuns.RemoveAt( subRun.Index );
|
|
newRunPos = subRun.Index;
|
|
}
|
|
}
|
|
|
|
// Create style run for the new style
|
|
var newRun = StyleRun.Pool.Value.Get();
|
|
newRun.CodePointBuffer = _codePoints;
|
|
newRun.Start = position;
|
|
newRun.Length = length;
|
|
newRun.Style = style;
|
|
|
|
_hasTextDirectionOverrides |= style.TextDirection != TextDirection.Auto;
|
|
|
|
// Insert it
|
|
_styleRuns.Insert( newRunPos, newRun );
|
|
|
|
// Coalesc
|
|
CoalescStyleRuns();
|
|
|
|
// Need to redo layout
|
|
OnChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract text from this styled text block
|
|
/// </summary>
|
|
/// <param name="from">The code point offset to extract from</param>
|
|
/// <param name="length">The number of code points to extract</param>
|
|
/// <returns>A new text block with the RHS split part of the text</returns>
|
|
public StyledText Extract( int from, int length )
|
|
{
|
|
// Create a new text block with the same attributes as this one
|
|
var other = new StyledText();
|
|
|
|
// Copy text to the new paragraph
|
|
foreach ( var subRun in _styleRuns.GetInterectingRuns( from, length ) )
|
|
{
|
|
var sr = _styleRuns[subRun.Index];
|
|
other.AddText( sr.CodePoints.SubSlice( subRun.Offset, subRun.Length ), sr.Style );
|
|
}
|
|
|
|
return other;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the style of the text at a specified offset
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// When on a style run boundary, returns the style of the preceeding run
|
|
/// </remarks>
|
|
/// <param name="offset">The code point offset in the text</param>
|
|
/// <returns>An IStyle</returns>
|
|
public IStyle GetStyleAtOffset( int offset )
|
|
{
|
|
if ( Length == 0 || _styleRuns.Count == 0 )
|
|
return null;
|
|
|
|
if ( offset == 0 )
|
|
return _styleRuns[0].Style;
|
|
|
|
int runIndex = _styleRuns.BinarySearch( offset, ( sr, a ) =>
|
|
{
|
|
if ( a <= sr.Start )
|
|
return 1;
|
|
if ( a > sr.End )
|
|
return -1;
|
|
return 0;
|
|
} );
|
|
|
|
if ( runIndex < 0 )
|
|
runIndex = ~runIndex;
|
|
|
|
if ( runIndex >= _styleRuns.Count )
|
|
runIndex = _styleRuns.Count - 1;
|
|
|
|
return _styleRuns[runIndex].Style;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Completes the insertion of text by inserting it's style run
|
|
/// and updating the offsets of existing style runs.
|
|
/// </summary>
|
|
/// <param name="utf32">The utf32 slice that was inserted</param>
|
|
/// <param name="style">The style of the inserted text</param>
|
|
void FinishInsert( Slice<int> utf32, IStyle style )
|
|
{
|
|
// Update style runs
|
|
int newRunIndex = 0;
|
|
for ( int i = 0; i < _styleRuns.Count; i++ )
|
|
{
|
|
// Get the style run
|
|
var sr = _styleRuns[i];
|
|
|
|
// Before inserted text?
|
|
if ( sr.End < utf32.Start )
|
|
continue;
|
|
|
|
// Special case for inserting at very start of text block
|
|
// with no supplied style.
|
|
if ( sr.Start == 0 && utf32.Start == 0 && style == null )
|
|
{
|
|
sr.Length += utf32.Length;
|
|
continue;
|
|
}
|
|
|
|
// After inserted text?
|
|
if ( sr.Start >= utf32.Start )
|
|
{
|
|
sr.Start += utf32.Length;
|
|
continue;
|
|
}
|
|
|
|
// Inserting exactly at the end of a style run?
|
|
if ( sr.End == utf32.Start )
|
|
{
|
|
if ( style == null || style == sr.Style )
|
|
{
|
|
// Extend the existing run
|
|
sr.Length += utf32.Length;
|
|
|
|
// Force style to null to suppress later creation
|
|
// of a style run for it.
|
|
style = null;
|
|
}
|
|
else
|
|
{
|
|
// Remember this is where to insert the new
|
|
// style run
|
|
newRunIndex = i + 1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
Debug.Assert( sr.End > utf32.Start );
|
|
Debug.Assert( sr.Start < utf32.Start );
|
|
|
|
// Inserting inside an existing run
|
|
if ( style == null || style == sr.Style )
|
|
{
|
|
// Extend the existing style run to cover
|
|
// the newly inserted text with the same style
|
|
sr.Length += utf32.Length;
|
|
|
|
// Force style to null to suppress later creation
|
|
// of a style run for it.
|
|
style = null;
|
|
}
|
|
else
|
|
{
|
|
// Split this run and insert the new style run between
|
|
|
|
// Create the second part
|
|
var split = StyleRun.Pool.Value.Get();
|
|
split.CodePointBuffer = _codePoints;
|
|
split.Start = utf32.Start + utf32.Length;
|
|
split.Length = sr.End - utf32.Start;
|
|
split.Style = sr.Style;
|
|
_styleRuns.Insert( i + 1, split );
|
|
|
|
// Shorten this part
|
|
sr.Length = utf32.Start - sr.Start;
|
|
|
|
// Insert the newly styled run after this one
|
|
newRunIndex = i + 1;
|
|
|
|
// Skip the second part of the split in this for loop
|
|
// as we've already calculated it
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// Create a new style run
|
|
if ( style != null )
|
|
{
|
|
var run = StyleRun.Pool.Value.Get();
|
|
run.CodePointBuffer = _codePoints;
|
|
run.Start = utf32.Start;
|
|
run.Length = utf32.Length;
|
|
run.Style = style;
|
|
_hasTextDirectionOverrides |= style.TextDirection != TextDirection.Auto;
|
|
_styleRuns.Insert( newRunIndex, run );
|
|
}
|
|
|
|
// Coalesc if necessary
|
|
if ( (newRunIndex > 0 && _styleRuns[newRunIndex - 1].Style == style) ||
|
|
(newRunIndex + 1 < _styleRuns.Count && _styleRuns[newRunIndex + 1].Style == style) )
|
|
{
|
|
CoalescStyleRuns();
|
|
}
|
|
|
|
|
|
// Need new layout
|
|
OnChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Combines any consecutive style runs with the same style
|
|
/// into a single run
|
|
/// </summary>
|
|
void CoalescStyleRuns()
|
|
{
|
|
// Nothing to do if no style runs
|
|
if ( _styleRuns.Count == 0 )
|
|
{
|
|
_hasTextDirectionOverrides = false;
|
|
return;
|
|
}
|
|
|
|
// Since we're iterating the entire set of style runs, might as
|
|
// we recalculate this flag while we're at it
|
|
_hasTextDirectionOverrides = _styleRuns[0].Style.TextDirection != TextDirection.Auto;
|
|
|
|
// No need to coalesc a single run
|
|
if ( _styleRuns.Count == 1 )
|
|
return;
|
|
|
|
// Coalesc...
|
|
var prev = _styleRuns[0];
|
|
for ( int i = 1; i < _styleRuns.Count; i++ )
|
|
{
|
|
// Get the run
|
|
var run = _styleRuns[i];
|
|
|
|
// Update flag
|
|
_hasTextDirectionOverrides |= run.Style.TextDirection != TextDirection.Auto;
|
|
|
|
// Can run be coalesced?
|
|
if ( run.Style == prev.Style )
|
|
{
|
|
// Yes
|
|
prev.Length += run.Length;
|
|
StyleRun.Pool.Value.Return( run );
|
|
_styleRuns.RemoveAt( i );
|
|
i--;
|
|
}
|
|
else
|
|
{
|
|
// No, move on..
|
|
prev = run;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Called whenever the content of this styled text block changes
|
|
/// </summary>
|
|
protected virtual void OnChanged()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// All code points as supplied by user, accumulated into a single buffer
|
|
/// </summary>
|
|
protected Utf32Buffer _codePoints = new Utf32Buffer();
|
|
|
|
/// <summary>
|
|
/// A list of style runs, as supplied by user
|
|
/// </summary>
|
|
protected List<StyleRun> _styleRuns = new List<StyleRun>();
|
|
|
|
/// <summary>
|
|
/// Set to true if any style runs have a directionality override.
|
|
/// </summary>
|
|
protected bool _hasTextDirectionOverrides = false;
|
|
|
|
/// <inheritdoc />
|
|
public override string ToString()
|
|
{
|
|
return Utf32Utils.FromUtf32( CodePoints.AsSlice() );
|
|
}
|
|
}
|
|
}
|