Browse Source

Merge pull request #9952 from Mikolaytis/BidiPooling

[Text] Bidi pooling
pull/9968/head
Benedikt Stebner 4 years ago
committed by GitHub
parent
commit
d6e6324110
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  2. 31
      src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs
  3. 12
      src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs
  4. 20
      src/Avalonia.Base/Utilities/ArrayBuilder.cs
  5. 24
      tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs

25
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -158,7 +158,7 @@ namespace Avalonia.Media.TextFormatting
{
var flowDirection = paragraphProperties.FlowDirection;
var shapedRuns = new List<TextRun>();
var biDiData = new BidiData((sbyte)flowDirection);
using var biDiData = new BidiData((sbyte)flowDirection);
foreach (var textRun in textRuns)
{
@ -176,7 +176,7 @@ namespace Avalonia.Media.TextFormatting
}
}
var biDi = new BidiAlgorithm();
using var biDi = new BidiAlgorithm();
biDi.Process(biDiData);
@ -187,10 +187,7 @@ namespace Avalonia.Media.TextFormatting
var processedRuns = new List<TextRun>(textRuns.Count);
foreach (var coalescedRuns in CoalesceLevels(textRuns, biDi.ResolvedLevels))
{
processedRuns.AddRange(coalescedRuns);
}
CoalesceLevels(textRuns, biDi.ResolvedLevels, processedRuns);
for (var index = 0; index < processedRuns.Count; index++)
{
@ -282,12 +279,14 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
/// <param name="textCharacters">The text characters to form <see cref="UnshapedTextRun"/> from.</param>
/// <param name="levels">The bidi levels.</param>
/// <param name="processedRuns"></param>
/// <returns></returns>
private static IEnumerable<IReadOnlyList<TextRun>> CoalesceLevels(IReadOnlyList<TextRun> textCharacters, ArraySlice<sbyte> levels)
private static void CoalesceLevels(IReadOnlyList<TextRun> textCharacters, ArraySlice<sbyte> levels,
List<TextRun> processedRuns)
{
if (levels.Length == 0)
{
yield break;
return;
}
var levelIndex = 0;
@ -306,7 +305,7 @@ namespace Avalonia.Media.TextFormatting
{
var drawableRun = textCharacters[i];
yield return new[] { drawableRun };
processedRuns.Add(drawableRun);
levelIndex += drawableRun.Length;
@ -329,7 +328,7 @@ namespace Avalonia.Media.TextFormatting
if (j == runText.Length)
{
yield return currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties);
processedRuns.AddRange(currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties));
runLevel = levels[levelIndex];
@ -342,7 +341,7 @@ namespace Avalonia.Media.TextFormatting
}
// End of this run
yield return currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties);
processedRuns.AddRange(currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties));
runText = runText.Skip(j);
@ -355,10 +354,10 @@ namespace Avalonia.Media.TextFormatting
if (currentRun is null || runText.IsEmpty)
{
yield break;
return;
}
yield return currentRun.GetShapeableCharacters(runText, runLevel, ref previousProperties);
processedRuns.AddRange(currentRun.GetShapeableCharacters(runText, runLevel, ref previousProperties));
}
/// <summary>

31
src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Avalonia.Collections.Pooled;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
@ -27,7 +28,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// as much as possible.
/// </para>
/// </remarks>
internal sealed class BidiAlgorithm
internal struct BidiAlgorithm : IDisposable
{
/// <summary>
/// The original BiDiClass classes as provided by the caller
@ -66,7 +67,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// The forward mapping maps the start index to the end index.
/// The reverse mapping maps the end index to the start index.
/// </remarks>
private readonly BidiDictionary<int, int> _isolatePairs = new BidiDictionary<int, int>();
private BidiDictionary<int, int>? _isolatePairs;
/// <summary>
/// The working BiDi classes
@ -118,7 +119,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// <summary>
/// A stack of pending isolate openings used by FindIsolatePairs()
/// </summary>
private readonly Stack<int> _pendingIsolateOpenings = new Stack<int>();
private Stack<int>? _pendingIsolateOpenings;
/// <summary>
/// The level of the isolating run currently being processed
@ -184,7 +185,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// <summary>
/// Initializes a new instance of the <see cref="BidiAlgorithm"/> class.
/// </summary>
internal BidiAlgorithm()
public BidiAlgorithm()
{
}
@ -227,7 +228,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
ArraySlice<sbyte>? outLevels)
{
// Reset state
_isolatePairs.Clear();
_isolatePairs?.Clear();
_workingClassesBuffer.Clear();
_levelRuns.Clear();
_resolvedLevelsBuffer.Clear();
@ -323,7 +324,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
// Skip isolate pairs
// (Because we're working with a slice, we need to adjust the indices
// we're using for the isolatePairs map)
if (_isolatePairs.TryGetValue(data.Start + i, out i))
if (_isolatePairs?.TryGetValue(data.Start + i, out i) == true)
{
i -= data.Start;
}
@ -358,7 +359,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
_hasIsolates = false;
// BD9...
_pendingIsolateOpenings.Clear();
_pendingIsolateOpenings?.Clear();
for (var i = 0; i < _originalClasses.Length; i++)
{
@ -370,14 +371,16 @@ namespace Avalonia.Media.TextFormatting.Unicode
case BidiClass.RightToLeftIsolate:
case BidiClass.FirstStrongIsolate:
{
_pendingIsolateOpenings ??= new Stack<int>();
_pendingIsolateOpenings.Push(i);
_hasIsolates = true;
break;
}
case BidiClass.PopDirectionalIsolate:
{
if (_pendingIsolateOpenings.Count > 0)
if (_pendingIsolateOpenings?.Count > 0)
{
_isolatePairs ??= new BidiDictionary<int, int>();
_isolatePairs.Add(_pendingIsolateOpenings.Pop(), i);
}
@ -498,7 +501,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
if (resolvedIsolate == BidiClass.FirstStrongIsolate)
{
if (!_isolatePairs.TryGetValue(i, out var endOfIsolate))
if (_isolatePairs == null || !_isolatePairs.TryGetValue(i, out var endOfIsolate))
{
endOfIsolate = _originalClasses.Length;
}
@ -829,7 +832,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
var lastCharacterIndex = _isolatedRunMapping[_isolatedRunMapping.Length - 1];
var lastType = _originalClasses[lastCharacterIndex];
if ((lastType == BidiClass.LeftToRightIsolate || lastType == BidiClass.RightToLeftIsolate || lastType == BidiClass.FirstStrongIsolate) &&
_isolatePairs.TryGetValue(lastCharacterIndex, out var nextRunIndex))
_isolatePairs?.TryGetValue(lastCharacterIndex, out var nextRunIndex) == true)
{
// Find the continuing run index
runIndex = FindRunForIndex(nextRunIndex);
@ -1714,5 +1717,13 @@ namespace Avalonia.Media.TextFormatting.Unicode
public BidiClass Eos { get; }
}
public void Dispose()
{
_workingClassesBuffer.Dispose();
_resolvedLevelsBuffer.Dispose();
_x9Map.Dispose();
_isolatedRunMapping.Dispose();
}
}
}

12
src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs

@ -11,7 +11,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// Represents a unicode string and all associated attributes
/// for each character required for the bidirectional Unicode algorithm
/// </summary>
internal class BidiData
internal struct BidiData : IDisposable
{
private ArrayBuilder<BidiClass> _classes;
private ArrayBuilder<BidiPairedBracketType> _pairedBracketTypes;
@ -181,5 +181,15 @@ namespace Avalonia.Media.TextFormatting.Unicode
return _tempLevelBuffer.Add(length, false);
}
public void Dispose()
{
_classes.Dispose();
_pairedBracketTypes.Dispose();
_pairedBracketValues.Dispose();
_savedClasses.Dispose();
_savedPairedBracketTypes.Dispose();
_tempLevelBuffer.Dispose();
}
}
}

20
src/Avalonia.Base/Utilities/ArrayBuilder.cs

@ -3,6 +3,7 @@
// Ported from: https://github.com/SixLabors/Fonts/
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace Avalonia.Utilities
@ -11,7 +12,7 @@ namespace Avalonia.Utilities
/// A helper type for avoiding allocations while building arrays.
/// </summary>
/// <typeparam name="T">The type of item contained in the array.</typeparam>
internal struct ArrayBuilder<T>
internal struct ArrayBuilder<T> : IDisposable
where T : struct
{
private const int DefaultCapacity = 4;
@ -135,7 +136,7 @@ namespace Avalonia.Utilities
}
// Same expansion algorithm as List<T>.
var newCapacity = length == 0 ? DefaultCapacity : (uint)length * 2u;
var newCapacity = length == 0 ? DefaultCapacity : length * 2;
if (newCapacity > MaxCoreClrArrayLength)
{
@ -144,14 +145,15 @@ namespace Avalonia.Utilities
if (newCapacity < min)
{
newCapacity = (uint)min;
newCapacity = min;
}
var array = new T[newCapacity];
var array = ArrayPool<T>.Shared.Rent(newCapacity);
if (_size > 0)
{
Array.Copy(_data!, array, _size);
ArrayPool<T>.Shared.Return(_data!);
}
_data = array;
@ -180,5 +182,13 @@ namespace Avalonia.Utilities
/// <returns>The <see cref="ArraySlice{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArraySlice<T> AsSlice(int start, int length) => new ArraySlice<T>(_data!, start, length);
public void Dispose()
{
if (_data != null)
{
ArrayPool<T>.Shared.Return(_data);
}
}
}
}

24
tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs

@ -1,4 +1,5 @@
using System;
using System.Linq;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.UnitTests;
@ -10,9 +11,18 @@ namespace Avalonia.Benchmarks.Text;
public class HugeTextLayout : IDisposable
{
private readonly IDisposable _app;
private string[] _manySmallStrings;
private static Random _rand = new Random();
private static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&?%$@";
return new string(Enumerable.Repeat(chars, length).Select(s => s[_rand.Next(s.Length)]).ToArray());
}
public HugeTextLayout()
{
_manySmallStrings = Enumerable.Range(0, 1000).Select(x => RandomString(_rand.Next(2, 15))).ToArray();
_app = UnitTestApplication.Start(
TestServices.StyledWindow.With(
renderInterface: new NullRenderingPlatform(),
@ -29,11 +39,13 @@ One should, however, not forget that concentration of violations of the strategi
In a loose sense concentration of the center of the critical thinking provides a deep insight into the emergency planning. The comparison is quite a powerful matter.
Resulting from review or analysis of threats and opportunities, we can presume that either significant improvement or basics of planning and scheduling reveals the patterns of the final draft. Therefore, the concept of the crucial component can be treated as the only solution.
One should, nevertheless, consider that the exceptional results of the diverse sources of information gives an overview of the production cycle. It should rather be regarded as an integral part of the direct access to key resources.
Admitting that the possibility of achieving the results of the constructive criticism, as far as the strategic management is questionable, cannot rely only on the critical thinking. It may reveal how the systems approach partially the comprehensive project management. We must be ready for outline design stage and network development investigation of every contradiction between the effective time management and the efficient decision the network development. Everyone understands what it takes to the draft analysis and prior decisions and early design solutions. In any case, we can systematically change the mechanism of the sources and influences of the continuing financing doctrine. This could exceedingly be a result of a task analysis the hardware maintenance. The real reason of the strategic planning seemingly the influence on eventual productivity. Everyone understands what it takes to the well-known practice. Therefore, the concept of the productivity boost can be treated as the only solution the driving factor. It may reveal how the matters of peculiar interest slowly the goals and objectives or the diverse sources of information the positive influence of any major outcomes complete failure of the supposed theory.
Admitting that the possibility of achieving the results of the constructive criticism, as far as the strategic management is questionable, cannot rely only on the critical thinking. It may reveal how the systems approach partially the comprehensive project management. We must be ready for outline design stage and network development investigation of every contradiction between the effective time management and the efficient decision the network development.
Everyone understands what it takes to the draft analysis and prior decisions and early design solutions. In any case, we can systematically change the mechanism of the sources and influences of the continuing financing doctrine. This could exceedingly be a result of a task analysis the hardware maintenance. The real reason of the strategic planning seemingly the influence on eventual productivity. Everyone understands what it takes to the well-known practice. Therefore, the concept of the productivity boost can be treated as the only solution the driving factor.
It may reveal how the matters of peculiar interest slowly the goals and objectives or the diverse sources of information the positive influence of any major outcomes complete failure of the supposed theory.
In respect that the structure of the sufficient amount poses problems and challenges for both the set of related commands and controls and the ability bias.";
[Benchmark]
public TextLayout BuildTextLayout() => new TextLayout(Text, Typeface.Default, 12d, Brushes.Black);
public TextLayout BuildTextLayout() => MakeLayout(Text);
private const string Emojis = @"๐Ÿ˜€ ๐Ÿ˜ ๐Ÿ˜‚ ๐Ÿคฃ ๐Ÿ˜ƒ ๐Ÿ˜„ ๐Ÿ˜… ๐Ÿ˜† ๐Ÿ˜‰ ๐Ÿ˜Š ๐Ÿ˜‹ ๐Ÿ˜Ž ๐Ÿ˜ ๐Ÿ˜˜ ๐Ÿฅฐ ๐Ÿ˜— ๐Ÿ˜™ ๐Ÿ˜š โ˜บ๏ธ ๐Ÿ™‚ ๐Ÿค— ๐Ÿคฉ ๐Ÿค” ๐Ÿคจ ๐Ÿ˜ ๐Ÿ˜‘ ๐Ÿ˜ถ ๐Ÿ™„ ๐Ÿ˜ ๐Ÿ˜ฃ ๐Ÿ˜ฅ ๐Ÿ˜ฎ ๐Ÿค ๐Ÿ˜ฏ ๐Ÿ˜ช ๐Ÿ˜ซ ๐Ÿ˜ด ๐Ÿ˜Œ ๐Ÿ˜› ๐Ÿ˜œ ๐Ÿ˜ ๐Ÿคค ๐Ÿ˜’ ๐Ÿ˜“ ๐Ÿ˜” ๐Ÿ˜• ๐Ÿ™ƒ ๐Ÿค‘ ๐Ÿ˜ฒ โ˜น๏ธ ๐Ÿ™ ๐Ÿ˜– ๐Ÿ˜ž ๐Ÿ˜Ÿ ๐Ÿ˜ค ๐Ÿ˜ข ๐Ÿ˜ญ ๐Ÿ˜ฆ ๐Ÿ˜ง ๐Ÿ˜จ ๐Ÿ˜ฉ ๐Ÿคฏ ๐Ÿ˜ฌ ๐Ÿ˜ฐ ๐Ÿ˜ฑ ๐Ÿฅต ๐Ÿฅถ ๐Ÿ˜ณ ๐Ÿคช ๐Ÿ˜ต ๐Ÿ˜ก ๐Ÿ˜  ๐Ÿคฌ ๐Ÿ˜ท ๐Ÿค’ ๐Ÿค• ๐Ÿคข ๐Ÿคฎ ๐Ÿคง ๐Ÿ˜‡ ๐Ÿค  ๐Ÿคก ๐Ÿฅณ ๐Ÿฅด ๐Ÿฅบ ๐Ÿคฅ ๐Ÿคซ ๐Ÿคญ ๐Ÿง ๐Ÿค“ ๐Ÿ˜ˆ ๐Ÿ‘ฟ ๐Ÿ‘น ๐Ÿ‘บ ๐Ÿ’€ ๐Ÿ‘ป ๐Ÿ‘ฝ ๐Ÿค– ๐Ÿ’ฉ ๐Ÿ˜บ ๐Ÿ˜ธ ๐Ÿ˜น ๐Ÿ˜ป ๐Ÿ˜ผ ๐Ÿ˜ฝ ๐Ÿ™€ ๐Ÿ˜ฟ ๐Ÿ˜พ
๐Ÿ‘ถ ๐Ÿ‘ง ๐Ÿง’ ๐Ÿ‘ฆ ๐Ÿ‘ฉ ๐Ÿง‘ ๐Ÿ‘จ ๐Ÿ‘ต ๐Ÿง“ ๐Ÿ‘ด ๐Ÿ‘ฒ ๐Ÿ‘ณโ€โ™€๏ธ ๐Ÿ‘ณโ€โ™‚๏ธ ๐Ÿง• ๐Ÿง” ๐Ÿ‘ฑโ€โ™‚๏ธ ๐Ÿ‘ฑโ€โ™€๏ธ ๐Ÿ‘จโ€๐Ÿฆฐ ๐Ÿ‘ฉโ€๐Ÿฆฐ ๐Ÿ‘จโ€๐Ÿฆฑ ๐Ÿ‘ฉโ€๐Ÿฆฑ ๐Ÿ‘จโ€๐Ÿฆฒ ๐Ÿ‘ฉโ€๐Ÿฆฒ ๐Ÿ‘จโ€๐Ÿฆณ ๐Ÿ‘ฉโ€๐Ÿฆณ ๐Ÿฆธโ€โ™€๏ธ ๐Ÿฆธโ€โ™‚๏ธ ๐Ÿฆนโ€โ™€๏ธ ๐Ÿฆนโ€โ™‚๏ธ ๐Ÿ‘ฎโ€โ™€๏ธ ๐Ÿ‘ฎโ€โ™‚๏ธ ๐Ÿ‘ทโ€โ™€๏ธ ๐Ÿ‘ทโ€โ™‚๏ธ ๐Ÿ’‚โ€โ™€๏ธ ๐Ÿ’‚โ€โ™‚๏ธ ๐Ÿ•ต๏ธโ€โ™€๏ธ ๐Ÿ•ต๏ธโ€โ™‚๏ธ ๐Ÿ‘ฉโ€โš•๏ธ ๐Ÿ‘จโ€โš•๏ธ ๐Ÿ‘ฉโ€๐ŸŒพ ๐Ÿ‘จโ€๐ŸŒพ ๐Ÿ‘ฉโ€๐Ÿณ ๐Ÿ‘จโ€๐Ÿณ ๐Ÿ‘ฉโ€๐ŸŽ“ ๐Ÿ‘จโ€๐ŸŽ“ ๐Ÿ‘ฉโ€๐ŸŽค ๐Ÿ‘จโ€๐ŸŽค ๐Ÿ‘ฉโ€๐Ÿซ ๐Ÿ‘จโ€๐Ÿซ ๐Ÿ‘ฉโ€๐Ÿญ ๐Ÿ‘จโ€๐Ÿญ ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿ‘จโ€๐Ÿ’ป ๐Ÿ‘ฉโ€๐Ÿ’ผ ๐Ÿ‘จโ€๐Ÿ’ผ ๐Ÿ‘ฉโ€๐Ÿ”ง ๐Ÿ‘จโ€๐Ÿ”ง ๐Ÿ‘ฉโ€๐Ÿ”ฌ ๐Ÿ‘จโ€๐Ÿ”ฌ ๐Ÿ‘ฉโ€๐ŸŽจ ๐Ÿ‘จโ€๐ŸŽจ ๐Ÿ‘ฉโ€๐Ÿš’ ๐Ÿ‘จโ€๐Ÿš’ ๐Ÿ‘ฉโ€โœˆ๏ธ ๐Ÿ‘จโ€โœˆ๏ธ ๐Ÿ‘ฉโ€๐Ÿš€ ๐Ÿ‘จโ€๐Ÿš€ ๐Ÿ‘ฉโ€โš–๏ธ ๐Ÿ‘จโ€โš–๏ธ ๐Ÿ‘ฐ ๐Ÿคต ๐Ÿ‘ธ ๐Ÿคด ๐Ÿคถ ๐ŸŽ… ๐Ÿง™โ€โ™€๏ธ ๐Ÿง™โ€โ™‚๏ธ ๐Ÿงโ€โ™€๏ธ ๐Ÿงโ€โ™‚๏ธ ๐Ÿง›โ€โ™€๏ธ ๐Ÿง›โ€โ™‚๏ธ ๐ŸงŸโ€โ™€๏ธ ๐ŸงŸโ€โ™‚๏ธ ๐Ÿงžโ€โ™€๏ธ ๐Ÿงžโ€โ™‚๏ธ ๐Ÿงœโ€โ™€๏ธ ๐Ÿงœโ€โ™‚๏ธ ๐Ÿงšโ€โ™€๏ธ ๐Ÿงšโ€โ™‚๏ธ ๐Ÿ‘ผ ๐Ÿคฐ ๐Ÿคฑ ๐Ÿ™‡โ€โ™€๏ธ ๐Ÿ™‡โ€โ™‚๏ธ ๐Ÿ’โ€โ™€๏ธ ๐Ÿ’โ€โ™‚๏ธ ๐Ÿ™…โ€โ™€๏ธ ๐Ÿ™…โ€โ™‚๏ธ ๐Ÿ™†โ€โ™€๏ธ ๐Ÿ™†โ€โ™‚๏ธ ๐Ÿ™‹โ€โ™€๏ธ ๐Ÿ™‹โ€โ™‚๏ธ ๐Ÿคฆโ€โ™€๏ธ ๐Ÿคฆโ€โ™‚๏ธ ๐Ÿคทโ€โ™€๏ธ ๐Ÿคทโ€โ™‚๏ธ ๐Ÿ™Žโ€โ™€๏ธ ๐Ÿ™Žโ€โ™‚๏ธ ๐Ÿ™โ€โ™€๏ธ ๐Ÿ™โ€โ™‚๏ธ ๐Ÿ’‡โ€โ™€๏ธ ๐Ÿ’‡โ€โ™‚๏ธ ๐Ÿ’†โ€โ™€๏ธ ๐Ÿ’†โ€โ™‚๏ธ ๐Ÿง–โ€โ™€๏ธ ๐Ÿง–โ€โ™‚๏ธ ๐Ÿ’… ๐Ÿคณ ๐Ÿ’ƒ ๐Ÿ•บ ๐Ÿ‘ฏโ€โ™€๏ธ ๐Ÿ‘ฏโ€โ™‚๏ธ ๐Ÿ•ด ๐Ÿšถโ€โ™€๏ธ ๐Ÿšถโ€โ™‚๏ธ ๐Ÿƒโ€โ™€๏ธ ๐Ÿƒโ€โ™‚๏ธ ๐Ÿ‘ซ ๐Ÿ‘ญ ๐Ÿ‘ฌ ๐Ÿ’‘ ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ ๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ ๐Ÿ’ ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ ๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ ๐Ÿ‘ช ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿคฒ ๐Ÿ‘ ๐Ÿ™Œ ๐Ÿ‘ ๐Ÿค ๐Ÿ‘ ๐Ÿ‘Ž ๐Ÿ‘Š โœŠ ๐Ÿค› ๐Ÿคœ ๐Ÿคž โœŒ๏ธ ๐ŸคŸ ๐Ÿค˜ ๐Ÿ‘Œ ๐Ÿ‘ˆ ๐Ÿ‘‰ ๐Ÿ‘† ๐Ÿ‘‡ โ˜๏ธ โœ‹ ๐Ÿคš ๐Ÿ– ๐Ÿ–– ๐Ÿ‘‹ ๐Ÿค™ ๐Ÿ’ช ๐Ÿฆต ๐Ÿฆถ ๐Ÿ–• โœ๏ธ ๐Ÿ™ ๐Ÿ’ ๐Ÿ’„ ๐Ÿ’‹ ๐Ÿ‘„ ๐Ÿ‘… ๐Ÿ‘‚ ๐Ÿ‘ƒ ๐Ÿ‘ฃ ๐Ÿ‘ ๐Ÿ‘€ ๐Ÿง  ๐Ÿฆด ๐Ÿฆท ๐Ÿ—ฃ ๐Ÿ‘ค ๐Ÿ‘ฅ
@ -53,7 +65,13 @@ In respect that the structure of the sufficient amount poses problems and challe
๐Ÿฅฑ ๐Ÿค ๐Ÿฆพ ๐Ÿฆฟ ๐Ÿฆป ๐Ÿง ๐Ÿงโ€โ™‚๏ธ ๐Ÿงโ€โ™€๏ธ ๐Ÿง ๐Ÿงโ€โ™‚๏ธ ๐Ÿงโ€โ™€๏ธ ๐ŸงŽ ๐ŸงŽโ€โ™‚๏ธ ๐ŸงŽโ€โ™€๏ธ ๐Ÿ‘จโ€๐Ÿฆฏ ๐Ÿ‘ฉโ€๐Ÿฆฏ ๐Ÿ‘จโ€๐Ÿฆผ ๐Ÿ‘ฉโ€๐Ÿฆผ ๐Ÿ‘จโ€๐Ÿฆฝ ๐Ÿ‘ฉโ€๐Ÿฆฝ ๐Ÿฆง ๐Ÿฆฎ ๐Ÿ•โ€๐Ÿฆบ ๐Ÿฆฅ ๐Ÿฆฆ ๐Ÿฆจ ๐Ÿฆฉ ๐Ÿง„ ๐Ÿง… ๐Ÿง‡ ๐Ÿง† ๐Ÿงˆ ๐Ÿฆช ๐Ÿงƒ ๐Ÿง‰ ๐ŸงŠ ๐Ÿ›• ๐Ÿฆฝ ๐Ÿฆผ ๐Ÿ›บ ๐Ÿช‚ ๐Ÿช ๐Ÿคฟ ๐Ÿช€ ๐Ÿช ๐Ÿฆบ ๐Ÿฅป ๐Ÿฉฑ ๐Ÿฉฒ ๐Ÿฉณ ๐Ÿฉฐ ๐Ÿช• ๐Ÿช” ๐Ÿช“ ๐Ÿฆฏ ๐Ÿฉธ ๐Ÿฉน ๐Ÿฉบ ๐Ÿช‘ ๐Ÿช’ ๐ŸคŽ ๐Ÿค ๐ŸŸ  ๐ŸŸก ๐ŸŸข ๐ŸŸฃ ๐ŸŸค ๐ŸŸฅ ๐ŸŸง ๐ŸŸจ ๐ŸŸฉ ๐ŸŸฆ ๐ŸŸช ๐ŸŸซ";
[Benchmark]
public TextLayout BuildEmojisTextLayout() => new TextLayout(Emojis, Typeface.Default, 12d, Brushes.Black);
public TextLayout BuildEmojisTextLayout() => MakeLayout(Emojis);
[Benchmark]
public TextLayout[] BuildManySmallTexts() => _manySmallStrings.Select(MakeLayout).ToArray();
private static TextLayout MakeLayout(string str)
=> new TextLayout(str, Typeface.Default, 12d, Brushes.Black, maxWidth:120);
public void Dispose()
{

Loadingโ€ฆ
Cancel
Save