Object pools for style runs, font runs and text lines
This commit is contained in:
Родитель
c65503fe06
Коммит
db07829b0c
|
@ -7,7 +7,8 @@
|
||||||
{
|
{
|
||||||
//BidiTest.Run();
|
//BidiTest.Run();
|
||||||
//BidiCharacterTest.Run();
|
//BidiCharacterTest.Run();
|
||||||
LineBreakTest.Run();
|
//LineBreakTest.Run();
|
||||||
|
TextBlockLoadTest.Run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Topten.RichTextKit;
|
||||||
|
|
||||||
|
namespace TestBench
|
||||||
|
{
|
||||||
|
public static class TextBlockLoadTest
|
||||||
|
{
|
||||||
|
public static bool Run()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Text Block Load Test");
|
||||||
|
Console.WriteLine("--------------------");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
string typefaceName = "Arial";
|
||||||
|
float Scale = 1.5f;
|
||||||
|
var styleSmall = new Style() { FontFamily = typefaceName, FontSize = 12 * Scale };
|
||||||
|
var styleScript = new Style() { FontFamily = "Segoe Script", FontSize = 18 * Scale };
|
||||||
|
var styleHeading = new Style() { FontFamily = typefaceName, FontSize = 24 * Scale, FontWeight = 700 };
|
||||||
|
var styleNormal = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, LineHeight = 1.0f };
|
||||||
|
var styleBold = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, FontWeight = 700 };
|
||||||
|
var styleUnderline = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, Underline = UnderlineStyle.Gapped };
|
||||||
|
var styleStrike = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, StrikeThrough = StrikeThroughStyle.Solid };
|
||||||
|
var styleSubScript = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, FontVariant = FontVariant.SubScript };
|
||||||
|
var styleSuperScript = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, FontVariant = FontVariant.SuperScript };
|
||||||
|
var styleItalic = new Style() { FontFamily = typefaceName, FontItalic = true, FontSize = 18 * Scale };
|
||||||
|
var styleBoldLarge = new Style() { FontFamily = typefaceName, FontSize = 28 * Scale, FontWeight = 700 };
|
||||||
|
var styleRed = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale/*, TextColor = new SKColor(0xFFFF0000) */};
|
||||||
|
var styleBlue = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale/*, TextColor = new SKColor(0xFF0000FF) */};
|
||||||
|
|
||||||
|
|
||||||
|
var tr = new TestResults();
|
||||||
|
var tb = new TextBlock();
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; i++)
|
||||||
|
{
|
||||||
|
tr.EnterTest();
|
||||||
|
|
||||||
|
tb.Clear();
|
||||||
|
tb.MaxWidth = 1000;
|
||||||
|
tb.AddText("Welcome to RichTextKit!\n", styleHeading);
|
||||||
|
tb.AddText("\nRichTextKit is a rich text layout, rendering and measurement library for SkiaSharp.\n\nIt supports normal, ", styleNormal);
|
||||||
|
tb.AddText("bold", styleBold);
|
||||||
|
tb.AddText(", ", styleNormal);
|
||||||
|
tb.AddText("italic", styleItalic);
|
||||||
|
tb.AddText(", ", styleNormal);
|
||||||
|
tb.AddText("underline", styleUnderline);
|
||||||
|
tb.AddText(" (including ", styleNormal);
|
||||||
|
tb.AddText("gaps over descenders", styleUnderline);
|
||||||
|
tb.AddText("), ", styleNormal);
|
||||||
|
tb.AddText("strikethrough", styleStrike);
|
||||||
|
tb.AddText(", superscript (E=mc", styleNormal);
|
||||||
|
tb.AddText("2", styleSuperScript);
|
||||||
|
tb.AddText("), subscript (H", styleNormal);
|
||||||
|
tb.AddText("2", styleSubScript);
|
||||||
|
tb.AddText("O), ", styleNormal);
|
||||||
|
tb.AddText("colored ", styleRed);
|
||||||
|
tb.AddText("text", styleBlue);
|
||||||
|
tb.AddText(" and ", styleNormal);
|
||||||
|
tb.AddText("mixed ", styleNormal);
|
||||||
|
tb.AddText("sizes", styleSmall);
|
||||||
|
tb.AddText(" and ", styleNormal);
|
||||||
|
tb.AddText("fonts", styleScript);
|
||||||
|
tb.AddText(".\n\n", styleNormal);
|
||||||
|
tb.AddText("Font fallback means emojis work: 🌐 🍪 🍕 🚀 and ", styleNormal);
|
||||||
|
tb.AddText("text shaping and bi-directional text support means complex scripts and languages like Arabic: مرحبا بالعالم, Japanese: ハローワールド, Chinese: 世界您好 and Hindi: हैलो वर्ल्ड are rendered correctly!\n\n", styleNormal);
|
||||||
|
tb.AddText("RichTextKit also supports left/center/right text alignment, word wrapping, truncation with ellipsis place-holder, text measurement, hit testing, painting a selection range, caret position & shape helpers.", styleNormal);
|
||||||
|
tb.Layout();
|
||||||
|
tr.LeaveTest();
|
||||||
|
tr.TestPassed(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.Dump();
|
||||||
|
return tr.AllPassed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -304,23 +304,21 @@ namespace Topten.RichTextKit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the other run
|
// Create the other run
|
||||||
var newRun = new FontRun()
|
var newRun = FontRun.Pool.Get();
|
||||||
{
|
newRun.StyleRun = this.StyleRun;
|
||||||
StyleRun = this.StyleRun,
|
newRun.CodePointBuffer = this.CodePointBuffer;
|
||||||
CodePointBuffer = this.CodePointBuffer,
|
newRun.Direction = this.Direction;
|
||||||
Direction = this.Direction,
|
newRun.Ascent = this.Ascent;
|
||||||
Ascent = this.Ascent,
|
newRun.Descent = this.Descent;
|
||||||
Descent = this.Descent,
|
newRun.Style = this.Style;
|
||||||
Style = this.Style,
|
newRun.Typeface = this.Typeface;
|
||||||
Typeface = this.Typeface,
|
newRun.Start = splitAtCodePoint;
|
||||||
Start = splitAtCodePoint,
|
newRun.Length = this.End - splitAtCodePoint;
|
||||||
Length = this.End - splitAtCodePoint,
|
newRun.Width = sliceRightWidth;
|
||||||
Width = sliceRightWidth,
|
newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
|
||||||
RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos),
|
newRun.GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos);
|
||||||
GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos),
|
newRun.Glyphs = this.Glyphs.SubSlice(glyphSplitPos);
|
||||||
Glyphs = this.Glyphs.SubSlice(glyphSplitPos),
|
newRun.Clusters = this.Clusters.SubSlice(glyphSplitPos);
|
||||||
Clusters = this.Clusters.SubSlice(glyphSplitPos),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Adjust code point positions
|
// Adjust code point positions
|
||||||
for (int i = 0; i < newRun.RelativeCodePointXCoords.Length; i++)
|
for (int i = 0; i < newRun.RelativeCodePointXCoords.Length; i++)
|
||||||
|
@ -375,23 +373,21 @@ namespace Topten.RichTextKit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the other run
|
// Create the other run
|
||||||
var newRun = new FontRun()
|
var newRun = FontRun.Pool.Get();
|
||||||
{
|
newRun.StyleRun = this.StyleRun;
|
||||||
StyleRun = this.StyleRun,
|
newRun.CodePointBuffer = this.CodePointBuffer;
|
||||||
CodePointBuffer = this.CodePointBuffer,
|
newRun.Direction = this.Direction;
|
||||||
Direction = this.Direction,
|
newRun.Ascent = this.Ascent;
|
||||||
Ascent = this.Ascent,
|
newRun.Descent = this.Descent;
|
||||||
Descent = this.Descent,
|
newRun.Style = this.Style;
|
||||||
Style = this.Style,
|
newRun.Typeface = this.Typeface;
|
||||||
Typeface = this.Typeface,
|
newRun.Start = splitAtCodePoint;
|
||||||
Start = splitAtCodePoint,
|
newRun.Length = this.End - splitAtCodePoint;
|
||||||
Length = this.End - splitAtCodePoint,
|
newRun.Width = sliceLeftWidth;
|
||||||
Width = sliceLeftWidth,
|
newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
|
||||||
RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos),
|
newRun.GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos);
|
||||||
GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos),
|
newRun.Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos);
|
||||||
Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos),
|
newRun.Clusters = this.Clusters.SubSlice(0, glyphSplitPos);
|
||||||
Clusters = this.Clusters.SubSlice(0, glyphSplitPos),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update this run
|
// Update this run
|
||||||
this.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(0, codePointSplitPos);
|
this.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(0, codePointSplitPos);
|
||||||
|
@ -592,5 +588,18 @@ namespace Topten.RichTextKit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
internal static ObjectPool<FontRun> Pool = new ObjectPool<FontRun>()
|
||||||
|
{
|
||||||
|
Cleaner = (r) =>
|
||||||
|
{
|
||||||
|
r.RunKind = FontRunKind.Normal;
|
||||||
|
r.CodePointBuffer = null;
|
||||||
|
r.Style = null;
|
||||||
|
r.Typeface = null;
|
||||||
|
r.Line = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,16 @@ namespace Topten.RichTextKit
|
||||||
/// The global list of code points
|
/// The global list of code points
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Buffer<int> CodePointBuffer;
|
internal Buffer<int> CodePointBuffer;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
internal static ObjectPool<StyleRun> Pool = new ObjectPool<StyleRun>()
|
||||||
|
{
|
||||||
|
Cleaner = (r) =>
|
||||||
|
{
|
||||||
|
r.CodePointBuffer = null;
|
||||||
|
r.Style = null;
|
||||||
|
r.TextBlock = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,9 +157,9 @@ namespace Topten.RichTextKit
|
||||||
{
|
{
|
||||||
// Reset everything
|
// Reset everything
|
||||||
_codePoints.Clear();
|
_codePoints.Clear();
|
||||||
_styleRuns.Clear();
|
StyleRun.Pool.ReturnAndClear(_styleRuns);
|
||||||
_fontRuns.Clear();
|
FontRun.Pool.ReturnAndClear(_fontRuns);
|
||||||
_lines.Clear();
|
TextLine.Pool.ReturnAndClear(_lines);
|
||||||
_textShapingBuffers.Clear();
|
_textShapingBuffers.Clear();
|
||||||
InvalidateLayout();
|
InvalidateLayout();
|
||||||
}
|
}
|
||||||
|
@ -179,14 +179,12 @@ namespace Topten.RichTextKit
|
||||||
var utf32 = _codePoints.Add(text);
|
var utf32 = _codePoints.Add(text);
|
||||||
|
|
||||||
// Create a run
|
// Create a run
|
||||||
var run = new StyleRun()
|
var run = StyleRun.Pool.Get();
|
||||||
{
|
run.TextBlock = this;
|
||||||
TextBlock = this,
|
run.CodePointBuffer = _codePoints;
|
||||||
CodePointBuffer = _codePoints,
|
run.Start = utf32.Start;
|
||||||
Start = utf32.Start,
|
run.Length = utf32.Length;
|
||||||
Length = utf32.Length,
|
run.Style = style;
|
||||||
Style = style,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add run
|
// Add run
|
||||||
_styleRuns.Add(run);
|
_styleRuns.Add(run);
|
||||||
|
@ -194,6 +192,8 @@ namespace Topten.RichTextKit
|
||||||
return run;
|
return run;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<StyleRun> _styledRunPool = new List<StyleRun>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add text to this paragraph
|
/// Add text to this paragraph
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -799,7 +799,8 @@ namespace Topten.RichTextKit
|
||||||
return _textAlignment;
|
return _textAlignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bidi _bidi = new Bidi();
|
// Use the shared Bidi algo instance
|
||||||
|
Bidi _bidi = Bidi.Instance;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Split into runs based on directionality and style switch points
|
/// Split into runs based on directionality and style switch points
|
||||||
|
@ -943,23 +944,22 @@ namespace Topten.RichTextKit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the run
|
// Create the run
|
||||||
return new FontRun()
|
var fontRun = FontRun.Pool.Get();
|
||||||
{
|
fontRun.StyleRun = styleRun;
|
||||||
StyleRun = styleRun,
|
fontRun.CodePointBuffer = _codePoints;
|
||||||
CodePointBuffer = _codePoints,
|
fontRun.Start = codePoints.Start;
|
||||||
Start = codePoints.Start,
|
fontRun.Length = codePoints.Length;
|
||||||
Length = codePoints.Length,
|
fontRun.Style = style;
|
||||||
Style = style,
|
fontRun.Direction = direction;
|
||||||
Direction = direction,
|
fontRun.Typeface = typeface;
|
||||||
Typeface = typeface,
|
fontRun.Glyphs = shaped.GlyphIndicies;
|
||||||
Glyphs = shaped.GlyphIndicies,
|
fontRun.GlyphPositions = shaped.GlyphPositions;
|
||||||
GlyphPositions = shaped.GlyphPositions,
|
fontRun.RelativeCodePointXCoords = shaped.CodePointXCoords;
|
||||||
RelativeCodePointXCoords = shaped.CodePointXCoords,
|
fontRun.Clusters = shaped.Clusters;
|
||||||
Clusters = shaped.Clusters,
|
fontRun.Ascent = shaped.Ascent;
|
||||||
Ascent = shaped.Ascent,
|
fontRun.Descent = shaped.Descent;
|
||||||
Descent = shaped.Descent,
|
fontRun.Width = shaped.EndXCoord.X;
|
||||||
Width = shaped.EndXCoord.X,
|
return fontRun;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1110,7 +1110,7 @@ namespace Topten.RichTextKit
|
||||||
void BuildLine(int frIndexStartOfLine, int frSplitIndex, int frTrailingWhiteSpaceIndex)
|
void BuildLine(int frIndexStartOfLine, int frSplitIndex, int frTrailingWhiteSpaceIndex)
|
||||||
{
|
{
|
||||||
// Create the line
|
// Create the line
|
||||||
var line = new TextLine();
|
var line = TextLine.Pool.Get();
|
||||||
line.TextBlock = this;
|
line.TextBlock = this;
|
||||||
line.YCoord = _measuredHeight;
|
line.YCoord = _measuredHeight;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Topten.RichTextKit.Utils;
|
||||||
|
|
||||||
namespace Topten.RichTextKit
|
namespace Topten.RichTextKit
|
||||||
{
|
{
|
||||||
|
@ -296,5 +297,15 @@ namespace Topten.RichTextKit
|
||||||
/// Internal List of runs
|
/// Internal List of runs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal List<FontRun> RunsInternal = new List<FontRun>();
|
internal List<FontRun> RunsInternal = new List<FontRun>();
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
internal static ObjectPool<TextLine> Pool = new ObjectPool<TextLine>()
|
||||||
|
{
|
||||||
|
Cleaner = (r) =>
|
||||||
|
{
|
||||||
|
r.TextBlock = null;
|
||||||
|
r.RunsInternal.Clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,6 +202,7 @@ namespace Topten.RichTextKit.Utils
|
||||||
return new ArraySliceEnumerator<T>(_data, 0, _length);
|
return new ArraySliceEnumerator<T>(_data, 0, _length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
public T[] SetMapping(Slice<T> data, Slice<int> mapping)
|
public T[] SetMapping(Slice<T> data, Slice<int> mapping)
|
||||||
{
|
{
|
||||||
Length = mapping.Length;
|
Length = mapping.Length;
|
||||||
|
@ -221,5 +222,6 @@ namespace Topten.RichTextKit.Utils
|
||||||
}
|
}
|
||||||
return _data;
|
return _data;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
//#define NO_POOLING
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Topten.RichTextKit.Utils
|
||||||
|
{
|
||||||
|
internal class ObjectPool<T> where T : class, new()
|
||||||
|
{
|
||||||
|
public ObjectPool()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Get()
|
||||||
|
{
|
||||||
|
#if NO_POOLING
|
||||||
|
return new T();
|
||||||
|
#else
|
||||||
|
int count = _pool.Count;
|
||||||
|
if (count == 0)
|
||||||
|
return new T();
|
||||||
|
|
||||||
|
var obj = _pool[count - 1];
|
||||||
|
_pool.RemoveAt(count - 1);
|
||||||
|
return obj;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Return(T obj)
|
||||||
|
{
|
||||||
|
#if NO_POOLING
|
||||||
|
#else
|
||||||
|
Cleaner?.Invoke(obj);
|
||||||
|
_pool.Add(obj);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Return(IEnumerable<T> objs)
|
||||||
|
{
|
||||||
|
#if NO_POOLING
|
||||||
|
#else
|
||||||
|
if (Cleaner != null)
|
||||||
|
{
|
||||||
|
foreach (var x in objs)
|
||||||
|
{
|
||||||
|
Cleaner(x);
|
||||||
|
}
|
||||||
|
_pool.AddRange(objs);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReturnAndClear(List<T> objs)
|
||||||
|
{
|
||||||
|
#if NO_POOLING
|
||||||
|
#else
|
||||||
|
if (Cleaner != null)
|
||||||
|
{
|
||||||
|
foreach (var x in objs)
|
||||||
|
{
|
||||||
|
Cleaner(x);
|
||||||
|
}
|
||||||
|
_pool.AddRange(objs);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
objs.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action<T> Cleaner;
|
||||||
|
|
||||||
|
List<T> _pool = new List<T>();
|
||||||
|
}
|
||||||
|
}
|
2
build.js
2
build.js
|
@ -31,7 +31,7 @@ if (bt.options.official)
|
||||||
bt.git_tag();
|
bt.git_tag();
|
||||||
|
|
||||||
// Push nuget package
|
// Push nuget package
|
||||||
//bt.nupush(`./build/Release/Topten.RichTextKit/*.${bt.options.version.build}.nupkg`, "http://nuget.toptensoftware.com/v3/index.json");
|
bt.nupush(`./build/Release/Topten.RichTextKit/*.${bt.options.version.build}.nupkg`, "http://nuget.toptensoftware.com/v3/index.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче