bidi2 wip
This commit is contained in:
Родитель
1181e9d957
Коммит
245245ad52
Двоичный файл не отображается.
|
@ -34,6 +34,7 @@
|
|||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace SandboxDriver
|
|||
{
|
||||
public class SandboxDriver
|
||||
{
|
||||
public int ContentModeCount = 9;
|
||||
public int ContentModeCount = 10;
|
||||
public int ContentMode = 0;
|
||||
public TextDirection BaseDirection = TextDirection.LTR;
|
||||
public TextAlignment TextAlignment = TextAlignment.Auto;
|
||||
|
@ -17,6 +17,8 @@ namespace SandboxDriver
|
|||
public bool UseMaxHeight = false;
|
||||
public bool ShowMeasuredSize = false;
|
||||
|
||||
TextBlock _textBlock = new TextBlock();
|
||||
|
||||
public void Render(SKCanvas canvas, float canvasWidth, float canvasHeight)
|
||||
{
|
||||
canvas.Clear(new SKColor(0xFFFFFFFF));
|
||||
|
@ -62,133 +64,132 @@ namespace SandboxDriver
|
|||
var styleBlue = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, TextColor = new SKColor(0xFF0000FF) };
|
||||
|
||||
|
||||
var tle = new TextBlock();
|
||||
tle.MaxWidth = width;
|
||||
tle.MaxHeight = height;
|
||||
tle.Clear();
|
||||
_textBlock.Clear();
|
||||
_textBlock.MaxWidth = width;
|
||||
_textBlock.MaxHeight = height;
|
||||
|
||||
tle.BaseDirection = BaseDirection;
|
||||
tle.Alignment = TextAlignment;
|
||||
tle.UseMSWordStyleRTLLayout = UseMSWordStyleRTLLayout;
|
||||
_textBlock.BaseDirection = BaseDirection;
|
||||
_textBlock.Alignment = TextAlignment;
|
||||
_textBlock.UseMSWordStyleRTLLayout = UseMSWordStyleRTLLayout;
|
||||
|
||||
switch (ContentMode)
|
||||
{
|
||||
case 0:
|
||||
tle.AddText("Welcome to RichTextKit!\n", styleHeading);
|
||||
tle.AddText("\nRichTextKit is a rich text layout, rendering and measurement library for SkiaSharp.\n\nIt supports normal, ", styleNormal);
|
||||
tle.AddText("bold", styleBold);
|
||||
tle.AddText(", ", styleNormal);
|
||||
tle.AddText("italic", styleItalic);
|
||||
tle.AddText(", ", styleNormal);
|
||||
tle.AddText("underline", styleUnderline);
|
||||
tle.AddText(" (including ", styleNormal);
|
||||
tle.AddText("gaps over descenders", styleUnderline);
|
||||
tle.AddText("), ", styleNormal);
|
||||
tle.AddText("strikethrough", styleStrike);
|
||||
tle.AddText(", superscript (E=mc", styleNormal);
|
||||
tle.AddText("2", styleSuperScript);
|
||||
tle.AddText("), subscript (H", styleNormal);
|
||||
tle.AddText("2", styleSubScript);
|
||||
tle.AddText("O), ", styleNormal);
|
||||
tle.AddText("colored ", styleRed);
|
||||
tle.AddText("text", styleBlue);
|
||||
tle.AddText(" and ", styleNormal);
|
||||
tle.AddText("mixed ", styleNormal);
|
||||
tle.AddText("sizes", styleSmall);
|
||||
tle.AddText(" and ", styleNormal);
|
||||
tle.AddText("fonts", styleScript);
|
||||
tle.AddText(".\n\n", styleNormal);
|
||||
tle.AddText("Font fallback means emojis work: 🌐 🍪 🍕 🚀 and ", styleNormal);
|
||||
tle.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);
|
||||
tle.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);
|
||||
_textBlock.AddText("Welcome to RichTextKit!\n", styleHeading);
|
||||
_textBlock.AddText("\nRichTextKit is a rich text layout, rendering and measurement library for SkiaSharp.\n\nIt supports normal, ", styleNormal);
|
||||
_textBlock.AddText("bold", styleBold);
|
||||
_textBlock.AddText(", ", styleNormal);
|
||||
_textBlock.AddText("italic", styleItalic);
|
||||
_textBlock.AddText(", ", styleNormal);
|
||||
_textBlock.AddText("underline", styleUnderline);
|
||||
_textBlock.AddText(" (including ", styleNormal);
|
||||
_textBlock.AddText("gaps over descenders", styleUnderline);
|
||||
_textBlock.AddText("), ", styleNormal);
|
||||
_textBlock.AddText("strikethrough", styleStrike);
|
||||
_textBlock.AddText(", superscript (E=mc", styleNormal);
|
||||
_textBlock.AddText("2", styleSuperScript);
|
||||
_textBlock.AddText("), subscript (H", styleNormal);
|
||||
_textBlock.AddText("2", styleSubScript);
|
||||
_textBlock.AddText("O), ", styleNormal);
|
||||
_textBlock.AddText("colored ", styleRed);
|
||||
_textBlock.AddText("text", styleBlue);
|
||||
_textBlock.AddText(" and ", styleNormal);
|
||||
_textBlock.AddText("mixed ", styleNormal);
|
||||
_textBlock.AddText("sizes", styleSmall);
|
||||
_textBlock.AddText(" and ", styleNormal);
|
||||
_textBlock.AddText("fonts", styleScript);
|
||||
_textBlock.AddText(".\n\n", styleNormal);
|
||||
_textBlock.AddText("Font fallback means emojis work: 🌐 🍪 🍕 🚀 and ", styleNormal);
|
||||
_textBlock.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);
|
||||
_textBlock.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);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
tle.AddText("Hello Wor", styleNormal);
|
||||
tle.AddText("ld", styleRed);
|
||||
tle.AddText(". This is normal 18px. These are emojis: 🌐 🍪 🍕 🚀 ", styleNormal);
|
||||
tle.AddText("This is ", styleNormal);
|
||||
tle.AddText("bold 28px", styleBoldLarge);
|
||||
tle.AddText(". ", styleNormal);
|
||||
tle.AddText("This is italic", styleItalic);
|
||||
tle.AddText(". This is ", styleNormal);
|
||||
tle.AddText("red", styleRed);
|
||||
tle.AddText(". This is Arabic: (", styleNormal);
|
||||
tle.AddText("تسجّل ", styleNormal);
|
||||
tle.AddText("يتكلّم", styleNormal);
|
||||
tle.AddText("), Hindi: ", styleNormal);
|
||||
tle.AddText("हालाँकि प्रचलित रूप पूज", styleNormal);
|
||||
tle.AddText(", Han: ", styleNormal);
|
||||
tle.AddText("緳 踥踕", styleNormal);
|
||||
_textBlock.AddText("Hello Wor", styleNormal);
|
||||
_textBlock.AddText("ld", styleRed);
|
||||
_textBlock.AddText(". This is normal 18px. These are emojis: 🌐 🍪 🍕 🚀 ", styleNormal);
|
||||
_textBlock.AddText("This is ", styleNormal);
|
||||
_textBlock.AddText("bold 28px", styleBoldLarge);
|
||||
_textBlock.AddText(". ", styleNormal);
|
||||
_textBlock.AddText("This is italic", styleItalic);
|
||||
_textBlock.AddText(". This is ", styleNormal);
|
||||
_textBlock.AddText("red", styleRed);
|
||||
_textBlock.AddText(". This is Arabic: (", styleNormal);
|
||||
_textBlock.AddText("تسجّل ", styleNormal);
|
||||
_textBlock.AddText("يتكلّم", styleNormal);
|
||||
_textBlock.AddText("), Hindi: ", styleNormal);
|
||||
_textBlock.AddText("हालाँकि प्रचलित रूप पूज", styleNormal);
|
||||
_textBlock.AddText(", Han: ", styleNormal);
|
||||
_textBlock.AddText("緳 踥踕", styleNormal);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
tle.AddText("Hello Wor", styleNormal);
|
||||
tle.AddText("ld", styleRed);
|
||||
tle.AddText(".\nThis is normal 18px.\nThese are emojis: 🌐 🍪 🍕 🚀\n", styleNormal);
|
||||
tle.AddText("This is ", styleNormal);
|
||||
tle.AddText("bold 28px", styleBoldLarge);
|
||||
tle.AddText(".\n", styleNormal);
|
||||
tle.AddText("This is italic", styleItalic);
|
||||
tle.AddText(".\nThis is ", styleNormal);
|
||||
tle.AddText("red", styleRed);
|
||||
_textBlock.AddText("Hello Wor", styleNormal);
|
||||
_textBlock.AddText("ld", styleRed);
|
||||
_textBlock.AddText(".\nThis is normal 18px.\nThese are emojis: 🌐 🍪 🍕 🚀\n", styleNormal);
|
||||
_textBlock.AddText("This is ", styleNormal);
|
||||
_textBlock.AddText("bold 28px", styleBoldLarge);
|
||||
_textBlock.AddText(".\n", styleNormal);
|
||||
_textBlock.AddText("This is italic", styleItalic);
|
||||
_textBlock.AddText(".\nThis is ", styleNormal);
|
||||
_textBlock.AddText("red", styleRed);
|
||||
/*
|
||||
tle.AddText(".\nThis is Arabic: (", styleNormal);
|
||||
tle.AddText("تسجّل ", styleNormal);
|
||||
tle.AddText("يتكلّم", styleNormal);
|
||||
tle.AddText("), Hindi: ", styleNormal);
|
||||
*/
|
||||
tle.AddText(".\nThis is Arabic: (تسجّل يتكلّم), Hindi: ", styleNormal);
|
||||
tle.AddText("हालाँकि प्रचलित रूप पूज", styleNormal);
|
||||
tle.AddText(", Han: ", styleNormal);
|
||||
tle.AddText("緳 踥踕", styleNormal);
|
||||
_textBlock.AddText(".\nThis is Arabic: (تسجّل يتكلّم), Hindi: ", styleNormal);
|
||||
_textBlock.AddText("हालाँकि प्रचलित रूप पूज", styleNormal);
|
||||
_textBlock.AddText(", Han: ", styleNormal);
|
||||
_textBlock.AddText("緳 踥踕", styleNormal);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
tle.AddText("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus semper, sapien vitae placerat sollicitudin, lorem diam aliquet quam, id finibus nisi quam eget lorem.\nDonec facilisis sem nec rhoncus elementum. Cras laoreet porttitor malesuada.\n\nVestibulum sed lacinia diam. Mauris a mollis enim. Cras in rhoncus mauris, at vulputate nisl. Sed nec lobortis dolor, hendrerit posuere quam. Vivamus malesuada sit amet nunc ac cursus. Praesent volutpat euismod efficitur. Nam eu ante.", styleNormal);
|
||||
_textBlock.AddText("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus semper, sapien vitae placerat sollicitudin, lorem diam aliquet quam, id finibus nisi quam eget lorem.\nDonec facilisis sem nec rhoncus elementum. Cras laoreet porttitor malesuada.\n\nVestibulum sed lacinia diam. Mauris a mollis enim. Cras in rhoncus mauris, at vulputate nisl. Sed nec lobortis dolor, hendrerit posuere quam. Vivamus malesuada sit amet nunc ac cursus. Praesent volutpat euismod efficitur. Nam eu ante.", styleNormal);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
tle.AddText("مرحبا بالعالم. هذا هو اختبار التفاف \nالخط للتأكد من أنه يعمل للغات من اليمين إلى اليسار.", styleNormal);
|
||||
_textBlock.AddText("مرحبا بالعالم. هذا هو اختبار التفاف \nالخط للتأكد من أنه يعمل للغات من اليمين إلى اليسار.", styleNormal);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
tle.AddText("مرحبا بالعالم. هذا هو اختبار التفاف الخط للتأكد من \u2066ACME Inc.\u2069 أنه يعمل للغات من اليمين إلى اليسار.", styleNormal);
|
||||
_textBlock.AddText("مرحبا بالعالم. هذا هو اختبار التفاف الخط للتأكد من \u2066ACME Inc.\u2069 أنه يعمل للغات من اليمين إلى اليسار.", styleNormal);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
tle.AddText("Subscript: H", styleNormal);
|
||||
tle.AddText("2", styleSubScript);
|
||||
tle.AddText("O Superscript: E=mc", styleNormal);
|
||||
tle.AddText("2", styleSuperScript);
|
||||
tle.AddText(" Key: C", styleNormal);
|
||||
tle.AddText("♯", styleSuperScript);
|
||||
tle.AddText(" B", styleNormal);
|
||||
tle.AddText("♭", styleSubScript);
|
||||
_textBlock.AddText("Subscript: H", styleNormal);
|
||||
_textBlock.AddText("2", styleSubScript);
|
||||
_textBlock.AddText("O Superscript: E=mc", styleNormal);
|
||||
_textBlock.AddText("2", styleSuperScript);
|
||||
_textBlock.AddText(" Key: C", styleNormal);
|
||||
_textBlock.AddText("♯", styleSuperScript);
|
||||
_textBlock.AddText(" B", styleNormal);
|
||||
_textBlock.AddText("♭", styleSubScript);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
tle.AddText("The quick brown fox jumps over the lazy dog.", styleUnderline);
|
||||
tle.AddText(" ", styleNormal);
|
||||
tle.AddText("Strike Through", styleStrike);
|
||||
tle.AddText(" something ends in wooooooooq", styleNormal);
|
||||
_textBlock.AddText("The quick brown fox jumps over the lazy dog.", styleUnderline);
|
||||
_textBlock.AddText(" ", styleNormal);
|
||||
_textBlock.AddText("Strike Through", styleStrike);
|
||||
_textBlock.AddText(" something ends in wooooooooq", styleNormal);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
tle.AddText("Apples and Bananas\r\n", styleNormal);
|
||||
tle.AddText("Pears\r\n", styleNormal);
|
||||
tle.AddText("Bananas\r\n", styleNormal);
|
||||
_textBlock.AddText("Apples and Bananas\r\n", styleNormal);
|
||||
_textBlock.AddText("Pears\r\n", styleNormal);
|
||||
_textBlock.AddText("Bananas\r\n", styleNormal);
|
||||
break;
|
||||
|
||||
case 9:
|
||||
tle.AddText("Hello World", styleNormal);
|
||||
_textBlock.AddText("Hello World", styleNormal);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
tle.Layout();
|
||||
_textBlock.Layout();
|
||||
var elapsed = sw.ElapsedMilliseconds;
|
||||
|
||||
var options = new TextPaintOptions()
|
||||
|
@ -200,14 +201,14 @@ namespace SandboxDriver
|
|||
CaretInfo? ci = null;
|
||||
if (_showHitTest)
|
||||
{
|
||||
htr = tle.HitTest(_hitTestX - margin, _hitTestY - margin);
|
||||
htr = _textBlock.HitTest(_hitTestX - margin, _hitTestY - margin);
|
||||
if (htr.Value.OverCodePointIndex >= 0)
|
||||
{
|
||||
options.SelectionStart = htr.Value.OverCodePointIndex;
|
||||
options.SelectionEnd = tle.CaretIndicies[tle.LookupCaretIndex(htr.Value.OverCodePointIndex) + 1];
|
||||
options.SelectionEnd = _textBlock.CaretIndicies[_textBlock.LookupCaretIndex(htr.Value.OverCodePointIndex) + 1];
|
||||
}
|
||||
|
||||
ci = tle.GetCaretInfo(htr.Value.ClosestCodePointIndex);
|
||||
ci = _textBlock.GetCaretInfo(htr.Value.ClosestCodePointIndex);
|
||||
}
|
||||
|
||||
if (ShowMeasuredSize)
|
||||
|
@ -218,20 +219,20 @@ namespace SandboxDriver
|
|||
IsStroke = false,
|
||||
})
|
||||
{
|
||||
var rect = new SKRect(margin + tle.MeasuredPadding.Left, margin, margin + tle.MeasuredWidth + tle.MeasuredPadding.Left, margin + tle.MeasuredHeight);
|
||||
var rect = new SKRect(margin + _textBlock.MeasuredPadding.Left, margin, margin + _textBlock.MeasuredWidth + _textBlock.MeasuredPadding.Left, margin + _textBlock.MeasuredHeight);
|
||||
canvas.DrawRect(rect, paint);
|
||||
}
|
||||
}
|
||||
|
||||
if (tle.MeasuredOverhang.Left > 0)
|
||||
if (_textBlock.MeasuredOverhang.Left > 0)
|
||||
{
|
||||
using (var paint = new SKPaint() { Color = new SKColor(0xFFf0f0f0), StrokeWidth = 1 })
|
||||
{
|
||||
canvas.DrawLine(new SKPoint(margin - tle.MeasuredOverhang.Left, 0), new SKPoint(margin - tle.MeasuredOverhang.Left, (float)canvasHeight), paint);
|
||||
canvas.DrawLine(new SKPoint(margin - _textBlock.MeasuredOverhang.Left, 0), new SKPoint(margin - _textBlock.MeasuredOverhang.Left, (float)canvasHeight), paint);
|
||||
}
|
||||
}
|
||||
|
||||
tle.Paint(canvas, new SKPoint(margin, margin), options);
|
||||
_textBlock.Paint(canvas, new SKPoint(margin, margin), options);
|
||||
|
||||
if (ci != null)
|
||||
{
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,145 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Topten.RichTextKit.Utils;
|
||||
using Xunit;
|
||||
|
||||
namespace Topten.RichTextKit.Test
|
||||
{
|
||||
public class BidiTest
|
||||
{
|
||||
[Fact]
|
||||
public void Test()
|
||||
{
|
||||
// Read the test file
|
||||
var location = System.IO.Path.GetDirectoryName(typeof(LineBreakTests).Assembly.Location);
|
||||
var lines = System.IO.File.ReadAllLines(System.IO.Path.Combine(location, "BidiTest.txt"));
|
||||
|
||||
// Process each line
|
||||
int testCount = 0;
|
||||
int passCount = 0;
|
||||
int[] levels = null;
|
||||
for (int lineNumber = 1; lineNumber < lines.Length + 1; lineNumber++)
|
||||
{
|
||||
// Get the line, remove comments
|
||||
var line = lines[lineNumber-1].Split("#")[0].Trim();
|
||||
|
||||
// Ignore blank/comment only lines
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
// Directive?
|
||||
if (line.StartsWith("@"))
|
||||
{
|
||||
if (line.StartsWith("@Levels:"))
|
||||
{
|
||||
levels = line.Substring(8).Trim().Split(' ').Where(x => x.Length > 0).Select(x =>
|
||||
{
|
||||
if (x == "x")
|
||||
return -1;
|
||||
else
|
||||
return int.Parse(x);
|
||||
}).ToArray();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split data line
|
||||
var parts = line.Split(";");
|
||||
Assert.Equal(2, parts.Length);
|
||||
|
||||
// Get the directions
|
||||
var directions = parts[0].Split(' ').Select(x => DirectionalityFromName(x)).ToArray();
|
||||
|
||||
// Get the bit set
|
||||
var bitset = Convert.ToInt32(parts[1].Trim(), 16);
|
||||
|
||||
var pairTypes = Enumerable.Repeat(PairedBracketType.n, directions.Length).ToArray();
|
||||
var pairValues = Enumerable.Repeat(0, directions.Length).ToArray();
|
||||
|
||||
for (int bit = 1; bit < 8; bit <<= 1)
|
||||
{
|
||||
if ((bitset & bit) == 0)
|
||||
continue;
|
||||
|
||||
byte paragraphEmbeddingLevel;
|
||||
switch (bit)
|
||||
{
|
||||
case 1:
|
||||
paragraphEmbeddingLevel = 2; // Auto
|
||||
break;
|
||||
case 2:
|
||||
paragraphEmbeddingLevel = 0; // LTR
|
||||
break;
|
||||
case 4:
|
||||
paragraphEmbeddingLevel = 1; // RTL
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
// Run the algorithm...
|
||||
var bidi = new Bidi(new Slice<Directionality>(directions), new Slice<PairedBracketType>(pairTypes), new Slice<int>(pairValues), paragraphEmbeddingLevel);
|
||||
|
||||
// Check the results match
|
||||
bool pass = true;
|
||||
if (bidi.ResultLevels.Length == directions.Length)
|
||||
{
|
||||
for (int i = 0; i < bidi.Result.Length; i++)
|
||||
{
|
||||
/*
|
||||
if (levels[i] == -1)
|
||||
continue;
|
||||
*/
|
||||
|
||||
if ((int)bidi.ResultLevels[i] != levels[i])
|
||||
{
|
||||
pass = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pass = false;
|
||||
}
|
||||
|
||||
if (pass)
|
||||
passCount++;
|
||||
else
|
||||
{
|
||||
int x = 3;
|
||||
}
|
||||
|
||||
testCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(testCount, passCount);
|
||||
}
|
||||
|
||||
Dictionary<string, Directionality> _dirnameMap;
|
||||
|
||||
Directionality DirectionalityFromName(string name)
|
||||
{
|
||||
if (_dirnameMap == null)
|
||||
{
|
||||
_dirnameMap = new Dictionary<string, Directionality>();
|
||||
for (var dir = Directionality.TYPE_MIN; dir <= Directionality.TYPE_MAX; dir++)
|
||||
{
|
||||
_dirnameMap[dir.ToString()] = dir;
|
||||
}
|
||||
}
|
||||
|
||||
return _dirnameMap[name];
|
||||
}
|
||||
|
||||
static bool IsHexDigit(char ch)
|
||||
{
|
||||
return char.IsDigit(ch) || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f');
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -19,6 +19,12 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="BidiCharacterTest.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="BidiTest.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="LineBreakTest.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
|
@ -109,7 +109,7 @@ namespace Topten.RichTextKit
|
|||
//
|
||||
|
||||
public Bidi(BidiData data) :
|
||||
this(data.Directionality, data.PairedBracketTypes, data.PairedBracketValues, data.ParagraphEmbeddingLevel)
|
||||
this(data.Directionality, data.PairedBracketTypes, data.PairedBracketValues, (byte)data.ParagraphEmbeddingLevel)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -182,6 +182,8 @@ namespace Topten.RichTextKit
|
|||
// Get the final computed directionality of each character
|
||||
public Directionality[] Result => _resultTypes;
|
||||
|
||||
public byte[] ResultLevels => _resultLevels;
|
||||
|
||||
public struct Run
|
||||
{
|
||||
public Directionality Direction;
|
||||
|
@ -873,7 +875,7 @@ namespace Topten.RichTextKit
|
|||
{
|
||||
// locate end of sequence
|
||||
int runstart = i;
|
||||
int runlimit = findRunLimit(runstart, length, new Directionality[] { Directionality.ET });
|
||||
int runlimit = findRunLimit(runstart, length, _dirsetET);
|
||||
|
||||
// check values at ends of sequence
|
||||
var t = runstart == 0 ? sos : types[runstart - 1];
|
||||
|
@ -927,6 +929,19 @@ namespace Topten.RichTextKit
|
|||
}
|
||||
}
|
||||
|
||||
static HashSet<Directionality> _dirsetET = new HashSet<Directionality>(new Directionality[] { Directionality.ET });
|
||||
|
||||
static HashSet<Directionality> _dirsetNeutral = new HashSet<Directionality>(new Directionality[] {
|
||||
Directionality.B,
|
||||
Directionality.S,
|
||||
Directionality.WS,
|
||||
Directionality.ON,
|
||||
Directionality.RLI,
|
||||
Directionality.LRI,
|
||||
Directionality.FSI,
|
||||
Directionality.PDI
|
||||
});
|
||||
|
||||
/*
|
||||
* 6) resolving neutral types Rules N1-N2.
|
||||
*/
|
||||
|
@ -957,16 +972,7 @@ namespace Topten.RichTextKit
|
|||
{
|
||||
// find bounds of run of neutrals
|
||||
int runstart = i;
|
||||
int runlimit = findRunLimit(runstart, length, new Directionality[] {
|
||||
Directionality.B,
|
||||
Directionality.S,
|
||||
Directionality.WS,
|
||||
Directionality.ON,
|
||||
Directionality.RLI,
|
||||
Directionality.LRI,
|
||||
Directionality.FSI,
|
||||
Directionality.PDI
|
||||
});
|
||||
int runlimit = findRunLimit(runstart, length, _dirsetNeutral);
|
||||
|
||||
// determine effective types at ends of run
|
||||
Directionality leadingType;
|
||||
|
@ -1096,23 +1102,28 @@ namespace Topten.RichTextKit
|
|||
* starting at index. This checks the value at index, and will return
|
||||
* index if that value is not in validSet.
|
||||
*/
|
||||
private int findRunLimit(int index, int limit, Directionality[] validSet)
|
||||
private int findRunLimit(int index, int limit, HashSet<Directionality> validSet)
|
||||
{
|
||||
while (index < limit && validSet.Contains(types[index]))
|
||||
index++;
|
||||
|
||||
return index;
|
||||
|
||||
/*
|
||||
loop: while (index < limit)
|
||||
{
|
||||
var t = types[index];
|
||||
for (int i = 0; i < validSet.Length; ++i)
|
||||
if (validSet.Contains(t))
|
||||
{
|
||||
if (t == validSet[i])
|
||||
{
|
||||
++index;
|
||||
goto loop;
|
||||
}
|
||||
++index;
|
||||
goto loop;
|
||||
}
|
||||
|
||||
// didn't find a match in validSet
|
||||
return index;
|
||||
}
|
||||
return limit;
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,374 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Topten.RichTextKit.Utils;
|
||||
|
||||
namespace Topten.RichTextKit
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of Unicode Bidirection Algorithm (UAX #9)
|
||||
/// https://unicode.org/reports/tr9/
|
||||
/// </summary>
|
||||
class Bidi2
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new instance of Bidi algorithm processor
|
||||
/// </summary>
|
||||
public Bidi2()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Processes Bidi Data
|
||||
/// </summary>
|
||||
/// <param name="data">The data to be processed</param>
|
||||
public void Process(BidiData data)
|
||||
{
|
||||
// Determine isolate pairs
|
||||
var _isolatePairs = FindIsolatePairs(data.Directionality);
|
||||
|
||||
// Determine the paragraph embedding level (if implicit)
|
||||
var _paragraphEmbeddingLevel = data.ParagraphEmbeddingLevel;
|
||||
if (_paragraphEmbeddingLevel == 2)
|
||||
{
|
||||
_paragraphEmbeddingLevel = DetermineParagraphEmbeddingLevel(data.Directionality, _isolatePairs);
|
||||
}
|
||||
|
||||
// Take a copy of the data to work with
|
||||
var resultTypes = new Slice<Directionality>(data.Directionality.ToArray());
|
||||
|
||||
// Determine explicit embedding levels (X1-X8)
|
||||
var resultLevels = DetermineExplicitEmbeddingLevels(resultTypes, _isolatePairs, _paragraphEmbeddingLevel);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determine explicit result levels
|
||||
/// </summary>
|
||||
/// <param name="data">The directionality data, will be modified</param>
|
||||
/// <param name="isolatePairs">The determined isolate pairs</param>
|
||||
/// <param name="paragraphEmbeddingLevel">The paragraph embedding level</param>
|
||||
/// <returns>Array of integer result levels</returns>
|
||||
private static int[] DetermineExplicitEmbeddingLevels(Slice<Directionality> data, BiDictionary<int, int> isolatePairs, int paragraphEmbeddingLevel)
|
||||
{
|
||||
// Work variables
|
||||
var stack = new Stack<Status>();
|
||||
int overflowIsolateCount = 0;
|
||||
int overflowEmbeddingCount = 0;
|
||||
int validIsolateCount = 0;
|
||||
|
||||
// Constants
|
||||
const int maxStackDepth = 125;
|
||||
|
||||
// Create result levels
|
||||
var resultLevels = new int[data.Length];
|
||||
for (int i = 0; i < resultLevels.Length; i++)
|
||||
{
|
||||
resultLevels[i] = paragraphEmbeddingLevel;
|
||||
}
|
||||
|
||||
// Rule X1 - setup initial state
|
||||
stack.Clear();
|
||||
stack.Push(new Status()
|
||||
{
|
||||
embeddingLevel = paragraphEmbeddingLevel,
|
||||
overrideStatus = Directionality.ON, // Neutral
|
||||
isolateStatus = false,
|
||||
});
|
||||
|
||||
|
||||
// Process all characters
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
switch (data[i])
|
||||
{
|
||||
case Directionality.RLE:
|
||||
{
|
||||
// Rule X2
|
||||
var newLevel = (stack.Peek().embeddingLevel + 1) | 1;
|
||||
if (newLevel <= maxStackDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0)
|
||||
{
|
||||
stack.Push(new Status()
|
||||
{
|
||||
embeddingLevel = newLevel,
|
||||
overrideStatus = Directionality.ON,
|
||||
isolateStatus = false,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (overflowIsolateCount == 0)
|
||||
overflowEmbeddingCount++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Directionality.LRE:
|
||||
{
|
||||
// Rule X3
|
||||
var newLevel = (stack.Peek().embeddingLevel + 2) & ~1;
|
||||
if (newLevel < maxStackDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0)
|
||||
{
|
||||
stack.Push(new Status()
|
||||
{
|
||||
embeddingLevel = newLevel,
|
||||
overrideStatus = Directionality.ON,
|
||||
isolateStatus = false,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (overflowIsolateCount == 0)
|
||||
overflowEmbeddingCount++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Directionality.RLO:
|
||||
{
|
||||
// Rule X4
|
||||
var newLevel = (stack.Peek().embeddingLevel + 1) | 1;
|
||||
if (newLevel <= maxStackDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0)
|
||||
{
|
||||
stack.Push(new Status()
|
||||
{
|
||||
embeddingLevel = newLevel,
|
||||
overrideStatus = Directionality.R,
|
||||
isolateStatus = false,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (overflowIsolateCount == 0)
|
||||
overflowEmbeddingCount++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Directionality.LRO:
|
||||
{
|
||||
// Rule X5
|
||||
var newLevel = (stack.Peek().embeddingLevel + 2) & ~1;
|
||||
if (newLevel <= maxStackDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0)
|
||||
{
|
||||
stack.Push(new Status()
|
||||
{
|
||||
embeddingLevel = newLevel,
|
||||
overrideStatus = Directionality.L,
|
||||
isolateStatus = false,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (overflowIsolateCount == 0)
|
||||
overflowEmbeddingCount++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Directionality.RLI:
|
||||
case Directionality.LRI:
|
||||
case Directionality.FSI:
|
||||
{
|
||||
// Rule X5a, X5b and X5c
|
||||
var resolvedIsolate = data[i];
|
||||
|
||||
if (resolvedIsolate == Directionality.FSI)
|
||||
{
|
||||
// Rule X5c
|
||||
if (DetermineParagraphEmbeddingLevel(data.SubSlice(i + 1), isolatePairs) == 1)
|
||||
resolvedIsolate = Directionality.RLI;
|
||||
else
|
||||
resolvedIsolate = Directionality.LRI;
|
||||
}
|
||||
|
||||
// Replace RLI's level with current embedding level
|
||||
var tos = stack.Peek();
|
||||
resultLevels[i] = tos.embeddingLevel;
|
||||
|
||||
// Apply override
|
||||
if (tos.overrideStatus != Directionality.ON)
|
||||
{
|
||||
data[i] = tos.overrideStatus;
|
||||
}
|
||||
|
||||
// Work out new level
|
||||
int newLevel;
|
||||
if (resolvedIsolate == Directionality.RLI)
|
||||
newLevel = (tos.embeddingLevel + 1) | 1;
|
||||
else
|
||||
newLevel = (tos.embeddingLevel + 2) & ~1;
|
||||
|
||||
// Valid?
|
||||
if (newLevel <= maxStackDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0)
|
||||
{
|
||||
validIsolateCount++;
|
||||
stack.Push(new Status()
|
||||
{
|
||||
embeddingLevel = newLevel,
|
||||
overrideStatus = Directionality.ON,
|
||||
isolateStatus = true,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
overflowIsolateCount++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Directionality.BN:
|
||||
{
|
||||
// Mentioned in rule X6 - "for all types besides ..., BN, ..."
|
||||
// no-op
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// Rule X6
|
||||
var tos = stack.Peek();
|
||||
resultLevels[i] = tos.embeddingLevel;
|
||||
if (tos.overrideStatus != Directionality.ON)
|
||||
{
|
||||
data[i] = tos.overrideStatus;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Directionality.PDI:
|
||||
{
|
||||
// Rule X6a
|
||||
if (overflowIsolateCount > 0)
|
||||
{
|
||||
overflowIsolateCount--;
|
||||
}
|
||||
else if (validIsolateCount != 0)
|
||||
{
|
||||
overflowEmbeddingCount = 0;
|
||||
while (!stack.Peek().isolateStatus)
|
||||
stack.Pop();
|
||||
stack.Pop();
|
||||
validIsolateCount--;
|
||||
}
|
||||
|
||||
var tos = stack.Peek();
|
||||
resultLevels[i] = tos.embeddingLevel;
|
||||
if (tos.overrideStatus != Directionality.ON)
|
||||
{
|
||||
data[i] = tos.overrideStatus;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Directionality.PDF:
|
||||
{
|
||||
// Rule X7
|
||||
if (overflowIsolateCount == 0)
|
||||
{
|
||||
if (overflowEmbeddingCount > 0)
|
||||
{
|
||||
overflowEmbeddingCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!stack.Peek().isolateStatus && stack.Count >= 2)
|
||||
{
|
||||
stack.Pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Directionality.B:
|
||||
{
|
||||
// Rule X8
|
||||
resultLevels[i] = paragraphEmbeddingLevel;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return resultLevels;
|
||||
}
|
||||
|
||||
class Status
|
||||
{
|
||||
public int embeddingLevel;
|
||||
public Directionality overrideStatus;
|
||||
public bool isolateStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a list of matching isolates for a directionality slice
|
||||
/// Implements BD9
|
||||
/// </summary>
|
||||
/// <param name="data">The directionality data to be processed</param>
|
||||
/// <returns>A BiDirectional dictionary mapping isolate start index => isolate end index</returns>
|
||||
static BiDictionary<int, int> FindIsolatePairs(Slice<Directionality> data)
|
||||
{
|
||||
var result = new BiDictionary<int, int>();
|
||||
var pendingOpens = new Stack<int>();
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
var t = data[i];
|
||||
if (t == Directionality.LRI || t == Directionality.RLI || t == Directionality.FSI)
|
||||
{
|
||||
pendingOpens.Push(i);
|
||||
}
|
||||
else if (t == Directionality.PDI)
|
||||
{
|
||||
if (pendingOpens.Count > 0)
|
||||
{
|
||||
result.Add(pendingOpens.Pop(), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static int DetermineParagraphEmbeddingLevel(Slice<Directionality> data, BiDictionary<int, int> isolatePairs)
|
||||
{
|
||||
// P2
|
||||
for (var i = 0; i < data.Length; ++i)
|
||||
{
|
||||
switch (data[i])
|
||||
{
|
||||
case Directionality.L:
|
||||
// P3
|
||||
return 0;
|
||||
|
||||
case Directionality.AL:
|
||||
case Directionality.R:
|
||||
// P3
|
||||
return 1;
|
||||
|
||||
case Directionality.FSI:
|
||||
case Directionality.LRI:
|
||||
case Directionality.RLI:
|
||||
|
||||
// Skip isolate pairs
|
||||
if (!isolatePairs.TryGetValue(i, out i))
|
||||
{
|
||||
// "or, if it has no matching PDI, the end of the paragraph."
|
||||
i = data.Length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// P3
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,16 +25,16 @@ namespace Topten.RichTextKit
|
|||
|
||||
List<int> _paragraphPositions = new List<int>();
|
||||
|
||||
byte _paragraphEmbeddingLevel;
|
||||
int _paragraphEmbeddingLevel;
|
||||
|
||||
public byte ParagraphEmbeddingLevel => _paragraphEmbeddingLevel;
|
||||
public int ParagraphEmbeddingLevel => _paragraphEmbeddingLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize with an array of Unicode code points
|
||||
/// </summary>
|
||||
/// <param name="codePoints">The unicode code points to be processed</param>
|
||||
/// <param name="paragraphEmbeddingLevel">The paragraph embedding level</param>
|
||||
public void Init(Slice<int> codePoints, byte paragraphEmbeddingLevel)
|
||||
public void Init(Slice<int> codePoints, int paragraphEmbeddingLevel)
|
||||
{
|
||||
// Set working buffer sizes
|
||||
_directionality.Length = codePoints.Length;
|
||||
|
|
|
@ -160,6 +160,7 @@ namespace Topten.RichTextKit
|
|||
_styleRuns.Clear();
|
||||
_fontRuns.Clear();
|
||||
_lines.Clear();
|
||||
_textShapingBuffers.Clear();
|
||||
InvalidateLayout();
|
||||
}
|
||||
|
||||
|
@ -725,6 +726,12 @@ namespace Topten.RichTextKit
|
|||
/// </summary>
|
||||
Utf32Buffer _codePoints = new Utf32Buffer();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Re-usable buffers for text shaping results
|
||||
/// </summary>
|
||||
TextShaper.ResultBufferSet _textShapingBuffers = new TextShaper.ResultBufferSet();
|
||||
|
||||
/// <summary>
|
||||
/// Reusable buffer for bidi data
|
||||
/// </summary>
|
||||
|
@ -922,7 +929,7 @@ namespace Topten.RichTextKit
|
|||
{
|
||||
// Shape the text
|
||||
var shaper = TextShaper.ForTypeface(typeface);
|
||||
var shaped = shaper.Shape(codePoints, style, direction, codePoints.Start);
|
||||
var shaped = shaper.Shape(_textShapingBuffers, codePoints, style, direction, codePoints.Start);
|
||||
|
||||
// Update minimum required left margin
|
||||
if (shaped.XMin < 0 && -shaped.XMin > _minLeftMargin)
|
||||
|
@ -940,10 +947,10 @@ namespace Topten.RichTextKit
|
|||
Style = style,
|
||||
Direction = direction,
|
||||
Typeface = typeface,
|
||||
Glyphs = new Slice<ushort>(shaped.GlyphIndicies),
|
||||
GlyphPositions = new Slice<SKPoint>(shaped.Points),
|
||||
RelativeCodePointXCoords = new Slice<float>(shaped.CodePointXCoords),
|
||||
Clusters = new Slice<int>(shaped.Clusters),
|
||||
Glyphs = shaped.GlyphIndicies,
|
||||
GlyphPositions = shaped.GlyphPositions,
|
||||
RelativeCodePointXCoords = shaped.CodePointXCoords,
|
||||
Clusters = shaped.Clusters,
|
||||
Ascent = shaped.Ascent,
|
||||
Descent = shaped.Descent,
|
||||
Width = shaped.EndXCoord.X,
|
||||
|
|
|
@ -88,6 +88,25 @@ namespace Topten.RichTextKit
|
|||
/// </summary>
|
||||
SKFontMetrics _fontMetrics;
|
||||
|
||||
/// <summary>
|
||||
/// A set of re-usable result buffers to store the result of text shaping operation
|
||||
/// </summary>
|
||||
public class ResultBufferSet
|
||||
{
|
||||
public void Clear()
|
||||
{
|
||||
GlyphIndicies.Clear();
|
||||
GlyphPositions.Clear();
|
||||
Clusters.Clear();
|
||||
CodePointXCoords.Clear();
|
||||
}
|
||||
|
||||
public Buffer<ushort> GlyphIndicies = new Buffer<ushort>();
|
||||
public Buffer<SKPoint> GlyphPositions = new Buffer<SKPoint>();
|
||||
public Buffer<int> Clusters = new Buffer<int>();
|
||||
public Buffer<float> CodePointXCoords = new Buffer<float>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returned as the result of a text shaping operation
|
||||
/// </summary>
|
||||
|
@ -96,18 +115,18 @@ namespace Topten.RichTextKit
|
|||
/// <summary>
|
||||
/// The glyph indicies of all glyphs required to render the shaped text
|
||||
/// </summary>
|
||||
public ushort[] GlyphIndicies;
|
||||
public Slice<ushort> GlyphIndicies;
|
||||
|
||||
/// <summary>
|
||||
/// The position of each glyph
|
||||
/// </summary>
|
||||
public SKPoint[] Points;
|
||||
public Slice<SKPoint> GlyphPositions;
|
||||
|
||||
/// <summary>
|
||||
/// One entry for each glyph, showing the code point index
|
||||
/// of the characters it was derived from
|
||||
/// </summary>
|
||||
public int[] Clusters;
|
||||
public Slice<int> Clusters;
|
||||
|
||||
/// <summary>
|
||||
/// The end position of the rendered text
|
||||
|
@ -117,7 +136,7 @@ namespace Topten.RichTextKit
|
|||
/// <summary>
|
||||
/// The X-Position of each passed code point
|
||||
/// </summary>
|
||||
public float[] CodePointXCoords;
|
||||
public Slice<float> CodePointXCoords;
|
||||
|
||||
/// <summary>
|
||||
/// The ascent of the font
|
||||
|
@ -147,12 +166,13 @@ namespace Topten.RichTextKit
|
|||
/// <summary>
|
||||
/// Shape an array of utf-32 code points
|
||||
/// </summary>
|
||||
/// <param name="bufferSet">A re-usable text shaping buffer set that results will be allocated from</param>
|
||||
/// <param name="codePoints">The utf-32 code points to be shaped</param>
|
||||
/// <param name="style">The user style for the text</param>
|
||||
/// <param name="direction">LTR or RTL direction</param>
|
||||
/// <param name="clusterAdjustment">A value to add to all reported cluster numbers</param>
|
||||
/// <returns>A TextShaper.Result representing the shaped text</returns>
|
||||
public Result Shape(Slice<int> codePoints, IStyle style, TextDirection direction, int clusterAdjustment = 0)
|
||||
public Result Shape(ResultBufferSet bufferSet, Slice<int> codePoints, IStyle style, TextDirection direction, int clusterAdjustment = 0)
|
||||
{
|
||||
using (var buffer = new HarfBuzzSharp.Buffer())
|
||||
{
|
||||
|
@ -186,13 +206,6 @@ namespace Topten.RichTextKit
|
|||
// Shape it
|
||||
_font.Shape(buffer);
|
||||
|
||||
// Create results
|
||||
var r = new Result();
|
||||
r.GlyphIndicies = buffer.GlyphInfos.Select(x => (ushort)x.Codepoint).ToArray();
|
||||
r.Clusters = buffer.GlyphInfos.Select(x => (int)x.Cluster + clusterAdjustment).ToArray();
|
||||
r.CodePointXCoords = new float[codePoints.Length];
|
||||
r.Points = new SKPoint[buffer.Length];
|
||||
|
||||
// RTL?
|
||||
bool rtl = buffer.Direction == Direction.RightToLeft;
|
||||
|
||||
|
@ -210,12 +223,23 @@ namespace Topten.RichTextKit
|
|||
glyphVOffset += style.FontSize * 0.1f;
|
||||
}
|
||||
|
||||
// Create results and get buffes
|
||||
var r = new Result();
|
||||
r.GlyphIndicies = bufferSet.GlyphIndicies.Add((int)buffer.Length, false);
|
||||
r.GlyphPositions = bufferSet.GlyphPositions.Add((int)buffer.Length, false);
|
||||
r.Clusters = bufferSet.Clusters.Add((int)buffer.Length, false);
|
||||
r.CodePointXCoords = bufferSet.CodePointXCoords.Add(codePoints.Length, false);
|
||||
|
||||
// Convert points
|
||||
var gp = buffer.GlyphPositions;
|
||||
var gi = buffer.GlyphInfos;
|
||||
float cursorX = 0;
|
||||
float cursorY = 0;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
r.GlyphIndicies[i] = (ushort)gi[i].Codepoint;
|
||||
r.Clusters[i] = (int)gi[i].Cluster + clusterAdjustment;
|
||||
|
||||
// Update code point positions
|
||||
if (!rtl)
|
||||
{
|
||||
|
@ -232,7 +256,7 @@ namespace Topten.RichTextKit
|
|||
var pos = gp[i];
|
||||
|
||||
// Update glyph position
|
||||
r.Points[i] = new SKPoint(
|
||||
r.GlyphPositions[i] = new SKPoint(
|
||||
cursorX + pos.XOffset * glyphScale,
|
||||
cursorY - pos.YOffset * glyphScale + glyphVOffset
|
||||
);
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Topten.RichTextKit.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple bi-directional dictionary
|
||||
/// </summary>
|
||||
/// <typeparam name="T1">Key type</typeparam>
|
||||
/// <typeparam name="T2">Value type</typeparam>
|
||||
class BiDictionary<T1, T2>
|
||||
{
|
||||
public Dictionary<T1, T2> Forward = new Dictionary<T1, T2>();
|
||||
public Dictionary<T2, T1> Reverse = new Dictionary<T2, T1>();
|
||||
|
||||
public void Add(T1 key, T2 value)
|
||||
{
|
||||
Forward.Add(key, value);
|
||||
Reverse.Add(value, key);
|
||||
}
|
||||
|
||||
public bool TryGetValue(T1 key, out T2 value)
|
||||
{
|
||||
return Forward.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public bool TryGetKey(T2 value, out T1 key)
|
||||
{
|
||||
return Reverse.TryGetValue(value, out key);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,16 +49,58 @@ namespace Topten.RichTextKit.Utils
|
|||
get => _length;
|
||||
set
|
||||
{
|
||||
if (value > _data.Length)
|
||||
// Now grow buffer
|
||||
if (!GrowBuffer(value))
|
||||
{
|
||||
var newData = new T[value];
|
||||
Array.Copy(_data, 0, newData, 0, _data.Length);
|
||||
_data = newData;
|
||||
// If the length is increasing, but we didn't re-size the buffer
|
||||
// then we need to clear the new elements.
|
||||
if (value > _length)
|
||||
{
|
||||
Array.Clear(_data, _length, value - _length);
|
||||
}
|
||||
}
|
||||
|
||||
// Store new length
|
||||
_length = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the buffer has sufficient capacity
|
||||
/// </summary>
|
||||
/// <param name="requiredLength"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
bool GrowBuffer(int requiredLength)
|
||||
{
|
||||
if (requiredLength <= _data.Length)
|
||||
return false;
|
||||
|
||||
// Work out new length
|
||||
int newLength;
|
||||
if (_data.Length < 1048576)
|
||||
{
|
||||
// Below 1MB, grow by doubling...
|
||||
newLength = _data.Length * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise grow by 1mb at a time...
|
||||
newLength = _data.Length + 1048576;
|
||||
}
|
||||
|
||||
// Make sure we're allocating enough
|
||||
if (newLength < requiredLength)
|
||||
newLength = requiredLength;
|
||||
|
||||
// Allocate new buffer, only copying _length, not Data.Length
|
||||
var newData = new T[requiredLength];
|
||||
Array.Copy(_data, 0, newData, 0, _length);
|
||||
_data = newData;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the buffer, keeping the internally allocated array.
|
||||
/// </summary>
|
||||
|
@ -75,25 +117,18 @@ namespace Topten.RichTextKit.Utils
|
|||
/// <returns>A slice representing the allocated elements.</returns>
|
||||
public Slice<T> Add(int length, bool clear = true)
|
||||
{
|
||||
// Save position
|
||||
int pos = Length;
|
||||
|
||||
// Grow internal buffer?
|
||||
if (_length + length > _data.Length)
|
||||
{
|
||||
var newData = new T[_length + length + 1024];
|
||||
Array.Copy(_data, 0, newData, 0, _length);
|
||||
_data = newData;
|
||||
}
|
||||
GrowBuffer(_length + length);
|
||||
_length += length;
|
||||
|
||||
// Clear it?
|
||||
if (clear)
|
||||
Array.Clear(_data, _length, length);
|
||||
Array.Clear(_data, pos, length);
|
||||
|
||||
// Capture where it was placed
|
||||
var pos = _length;
|
||||
|
||||
// Update length
|
||||
_length += length;
|
||||
|
||||
// Return position
|
||||
// Return subslice
|
||||
return SubSlice(pos, length);
|
||||
}
|
||||
|
||||
|
@ -104,23 +139,15 @@ namespace Topten.RichTextKit.Utils
|
|||
/// <returns>A slice representing the added elements.</returns>
|
||||
public Slice<T> Add(Slice<T> slice)
|
||||
{
|
||||
// Grow internal buffer?
|
||||
if (_length + slice.Length > _data.Length)
|
||||
{
|
||||
var newData = new T[_length + slice.Length + 1024];
|
||||
Array.Copy(_data, 0, newData, 0, _length);
|
||||
_data = newData;
|
||||
}
|
||||
|
||||
// Copy in the slice
|
||||
Array.Copy(slice.Underlying, slice.Start, _data, _length, slice.Length);
|
||||
|
||||
// Capture where it was placed
|
||||
var pos = _length;
|
||||
|
||||
// Update length
|
||||
// Grow internal buffer?
|
||||
GrowBuffer(_length + slice.Length);
|
||||
_length += slice.Length;
|
||||
|
||||
// Copy in the slice
|
||||
Array.Copy(slice.Underlying, slice.Start, _data, pos, slice.Length);
|
||||
|
||||
// Return position
|
||||
return SubSlice(pos, slice.Length);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче