Object pools for style runs, font runs and text lines
This commit is contained in:
Родитель
c65503fe06
Коммит
db07829b0c
|
@ -7,7 +7,8 @@
|
|||
{
|
||||
//BidiTest.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
|
||||
var newRun = new FontRun()
|
||||
{
|
||||
StyleRun = this.StyleRun,
|
||||
CodePointBuffer = this.CodePointBuffer,
|
||||
Direction = this.Direction,
|
||||
Ascent = this.Ascent,
|
||||
Descent = this.Descent,
|
||||
Style = this.Style,
|
||||
Typeface = this.Typeface,
|
||||
Start = splitAtCodePoint,
|
||||
Length = this.End - splitAtCodePoint,
|
||||
Width = sliceRightWidth,
|
||||
RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos),
|
||||
GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos),
|
||||
Glyphs = this.Glyphs.SubSlice(glyphSplitPos),
|
||||
Clusters = this.Clusters.SubSlice(glyphSplitPos),
|
||||
};
|
||||
var newRun = FontRun.Pool.Get();
|
||||
newRun.StyleRun = this.StyleRun;
|
||||
newRun.CodePointBuffer = this.CodePointBuffer;
|
||||
newRun.Direction = this.Direction;
|
||||
newRun.Ascent = this.Ascent;
|
||||
newRun.Descent = this.Descent;
|
||||
newRun.Style = this.Style;
|
||||
newRun.Typeface = this.Typeface;
|
||||
newRun.Start = splitAtCodePoint;
|
||||
newRun.Length = this.End - splitAtCodePoint;
|
||||
newRun.Width = sliceRightWidth;
|
||||
newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
|
||||
newRun.GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos);
|
||||
newRun.Glyphs = this.Glyphs.SubSlice(glyphSplitPos);
|
||||
newRun.Clusters = this.Clusters.SubSlice(glyphSplitPos);
|
||||
|
||||
// Adjust code point positions
|
||||
for (int i = 0; i < newRun.RelativeCodePointXCoords.Length; i++)
|
||||
|
@ -375,23 +373,21 @@ namespace Topten.RichTextKit
|
|||
}
|
||||
|
||||
// Create the other run
|
||||
var newRun = new FontRun()
|
||||
{
|
||||
StyleRun = this.StyleRun,
|
||||
CodePointBuffer = this.CodePointBuffer,
|
||||
Direction = this.Direction,
|
||||
Ascent = this.Ascent,
|
||||
Descent = this.Descent,
|
||||
Style = this.Style,
|
||||
Typeface = this.Typeface,
|
||||
Start = splitAtCodePoint,
|
||||
Length = this.End - splitAtCodePoint,
|
||||
Width = sliceLeftWidth,
|
||||
RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos),
|
||||
GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos),
|
||||
Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos),
|
||||
Clusters = this.Clusters.SubSlice(0, glyphSplitPos),
|
||||
};
|
||||
var newRun = FontRun.Pool.Get();
|
||||
newRun.StyleRun = this.StyleRun;
|
||||
newRun.CodePointBuffer = this.CodePointBuffer;
|
||||
newRun.Direction = this.Direction;
|
||||
newRun.Ascent = this.Ascent;
|
||||
newRun.Descent = this.Descent;
|
||||
newRun.Style = this.Style;
|
||||
newRun.Typeface = this.Typeface;
|
||||
newRun.Start = splitAtCodePoint;
|
||||
newRun.Length = this.End - splitAtCodePoint;
|
||||
newRun.Width = sliceLeftWidth;
|
||||
newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
|
||||
newRun.GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos);
|
||||
newRun.Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos);
|
||||
newRun.Clusters = this.Clusters.SubSlice(0, glyphSplitPos);
|
||||
|
||||
// Update this run
|
||||
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
|
||||
/// </summary>
|
||||
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
|
||||
_codePoints.Clear();
|
||||
_styleRuns.Clear();
|
||||
_fontRuns.Clear();
|
||||
_lines.Clear();
|
||||
StyleRun.Pool.ReturnAndClear(_styleRuns);
|
||||
FontRun.Pool.ReturnAndClear(_fontRuns);
|
||||
TextLine.Pool.ReturnAndClear(_lines);
|
||||
_textShapingBuffers.Clear();
|
||||
InvalidateLayout();
|
||||
}
|
||||
|
@ -179,14 +179,12 @@ namespace Topten.RichTextKit
|
|||
var utf32 = _codePoints.Add(text);
|
||||
|
||||
// Create a run
|
||||
var run = new StyleRun()
|
||||
{
|
||||
TextBlock = this,
|
||||
CodePointBuffer = _codePoints,
|
||||
Start = utf32.Start,
|
||||
Length = utf32.Length,
|
||||
Style = style,
|
||||
};
|
||||
var run = StyleRun.Pool.Get();
|
||||
run.TextBlock = this;
|
||||
run.CodePointBuffer = _codePoints;
|
||||
run.Start = utf32.Start;
|
||||
run.Length = utf32.Length;
|
||||
run.Style = style;
|
||||
|
||||
// Add run
|
||||
_styleRuns.Add(run);
|
||||
|
@ -194,6 +192,8 @@ namespace Topten.RichTextKit
|
|||
return run;
|
||||
}
|
||||
|
||||
List<StyleRun> _styledRunPool = new List<StyleRun>();
|
||||
|
||||
/// <summary>
|
||||
/// Add text to this paragraph
|
||||
/// </summary>
|
||||
|
@ -799,7 +799,8 @@ namespace Topten.RichTextKit
|
|||
return _textAlignment;
|
||||
}
|
||||
|
||||
Bidi _bidi = new Bidi();
|
||||
// Use the shared Bidi algo instance
|
||||
Bidi _bidi = Bidi.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Split into runs based on directionality and style switch points
|
||||
|
@ -943,23 +944,22 @@ namespace Topten.RichTextKit
|
|||
}
|
||||
|
||||
// Create the run
|
||||
return new FontRun()
|
||||
{
|
||||
StyleRun = styleRun,
|
||||
CodePointBuffer = _codePoints,
|
||||
Start = codePoints.Start,
|
||||
Length = codePoints.Length,
|
||||
Style = style,
|
||||
Direction = direction,
|
||||
Typeface = typeface,
|
||||
Glyphs = shaped.GlyphIndicies,
|
||||
GlyphPositions = shaped.GlyphPositions,
|
||||
RelativeCodePointXCoords = shaped.CodePointXCoords,
|
||||
Clusters = shaped.Clusters,
|
||||
Ascent = shaped.Ascent,
|
||||
Descent = shaped.Descent,
|
||||
Width = shaped.EndXCoord.X,
|
||||
};
|
||||
var fontRun = FontRun.Pool.Get();
|
||||
fontRun.StyleRun = styleRun;
|
||||
fontRun.CodePointBuffer = _codePoints;
|
||||
fontRun.Start = codePoints.Start;
|
||||
fontRun.Length = codePoints.Length;
|
||||
fontRun.Style = style;
|
||||
fontRun.Direction = direction;
|
||||
fontRun.Typeface = typeface;
|
||||
fontRun.Glyphs = shaped.GlyphIndicies;
|
||||
fontRun.GlyphPositions = shaped.GlyphPositions;
|
||||
fontRun.RelativeCodePointXCoords = shaped.CodePointXCoords;
|
||||
fontRun.Clusters = shaped.Clusters;
|
||||
fontRun.Ascent = shaped.Ascent;
|
||||
fontRun.Descent = shaped.Descent;
|
||||
fontRun.Width = shaped.EndXCoord.X;
|
||||
return fontRun;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1110,7 +1110,7 @@ namespace Topten.RichTextKit
|
|||
void BuildLine(int frIndexStartOfLine, int frSplitIndex, int frTrailingWhiteSpaceIndex)
|
||||
{
|
||||
// Create the line
|
||||
var line = new TextLine();
|
||||
var line = TextLine.Pool.Get();
|
||||
line.TextBlock = this;
|
||||
line.YCoord = _measuredHeight;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Topten.RichTextKit.Utils;
|
||||
|
||||
namespace Topten.RichTextKit
|
||||
{
|
||||
|
@ -296,5 +297,15 @@ namespace Topten.RichTextKit
|
|||
/// Internal List of runs
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
public T[] SetMapping(Slice<T> data, Slice<int> mapping)
|
||||
{
|
||||
Length = mapping.Length;
|
||||
|
@ -221,5 +222,6 @@ namespace Topten.RichTextKit.Utils
|
|||
}
|
||||
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();
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче