This commit is contained in:
Brad Robinson 2019-08-05 18:46:56 +10:00
Родитель 1181e9d957
Коммит 245245ad52
14 изменённых файлов: 594817 добавлений и 162 удалений

Двоичные данные
References/uax9.pdf Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -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);
}