Fixes for SkiaSharp 2.8 (font fallback not working) + don't use deprecated functions

This commit is contained in:
Brad Robinson 2020-07-29 16:26:18 +10:00
Родитель 403b3591db
Коммит 8ff2b6d298
12 изменённых файлов: 202 добавлений и 117 удалений

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

@ -96,7 +96,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Include="SkiaSharp" Version="2.80.1" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

2
Sandbox/Form1.Designer.cs сгенерированный
Просмотреть файл

@ -35,7 +35,7 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 25F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.White;
this.ClientSize = new System.Drawing.Size(1402, 724);
this.ClientSize = new System.Drawing.Size(1576, 1025);
this.Name = "Form1";
this.Text = "RichTextKit Sandbox";
this.Load += new System.EventHandler(this.Form1_Load);

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

@ -100,7 +100,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Include="SkiaSharp" Version="2.80.1" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

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

@ -110,9 +110,6 @@ namespace SandboxDriver
break;
case 1:
_textBlock.AddText("\n\n", styleNormal);
//_textBlock.AddEllipsis();
/*
_textBlock.AddText("Hello Wor", styleNormal);
_textBlock.AddText("ld", styleRed);
_textBlock.AddText(". This is normal 18px. These are emojis: 🌐 🍪 🍕 🚀 🏴‍☠️", styleNormal);
@ -129,7 +126,6 @@ namespace SandboxDriver
_textBlock.AddText("हालाँकि प्रचलित रूप पूज", styleNormal);
_textBlock.AddText(", Han: ", styleNormal);
_textBlock.AddText("緳 踥踕", styleNormal);
*/
break;
case 2:

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

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net45</TargetFrameworks>
<TargetFrameworks>net462</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Include="SkiaSharp" Version="2.80.1" />
</ItemGroup>
<ItemGroup>

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

@ -8,10 +8,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SkiaSharp" Version="2.80.1" />
</ItemGroup>
<ItemGroup>

55
Topten.RichTextKit.sln Normal file
Просмотреть файл

@ -0,0 +1,55 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29728.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Topten.RichTextKit", "Topten.RichTextKit\Topten.RichTextKit.csproj", "{6472D685-FD75-4475-8DE2-53917B0BA189}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandbox", "Sandbox\Sandbox.csproj", "{A8440E53-0D1E-477B-81B3-7916ECAF2EF2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SandboxDriver", "SandboxDriver\SandboxDriver.csproj", "{76AE34F1-D8B7-4593-A9FA-A82CDB438A5C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Topten.RichTextKit.Test", "Topten.RichTextKit.Test\Topten.RichTextKit.Test.csproj", "{44AE4B31-855E-41C5-8AAE-B7E6227EE3E6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestBench", "TestBench\TestBench.csproj", "{79AC4303-DC30-480E-969B-E9320DD4A8CF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RichStringSandbox", "RichStringSandbox\RichStringSandbox.csproj", "{B11CCB1B-9DEA-44DE-BFFD-B12092C36500}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6472D685-FD75-4475-8DE2-53917B0BA189}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6472D685-FD75-4475-8DE2-53917B0BA189}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6472D685-FD75-4475-8DE2-53917B0BA189}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6472D685-FD75-4475-8DE2-53917B0BA189}.Release|Any CPU.Build.0 = Release|Any CPU
{A8440E53-0D1E-477B-81B3-7916ECAF2EF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8440E53-0D1E-477B-81B3-7916ECAF2EF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8440E53-0D1E-477B-81B3-7916ECAF2EF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8440E53-0D1E-477B-81B3-7916ECAF2EF2}.Release|Any CPU.Build.0 = Release|Any CPU
{76AE34F1-D8B7-4593-A9FA-A82CDB438A5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{76AE34F1-D8B7-4593-A9FA-A82CDB438A5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{76AE34F1-D8B7-4593-A9FA-A82CDB438A5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{76AE34F1-D8B7-4593-A9FA-A82CDB438A5C}.Release|Any CPU.Build.0 = Release|Any CPU
{44AE4B31-855E-41C5-8AAE-B7E6227EE3E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44AE4B31-855E-41C5-8AAE-B7E6227EE3E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44AE4B31-855E-41C5-8AAE-B7E6227EE3E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44AE4B31-855E-41C5-8AAE-B7E6227EE3E6}.Release|Any CPU.Build.0 = Release|Any CPU
{79AC4303-DC30-480E-969B-E9320DD4A8CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79AC4303-DC30-480E-969B-E9320DD4A8CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79AC4303-DC30-480E-969B-E9320DD4A8CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79AC4303-DC30-480E-969B-E9320DD4A8CF}.Release|Any CPU.Build.0 = Release|Any CPU
{B11CCB1B-9DEA-44DE-BFFD-B12092C36500}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B11CCB1B-9DEA-44DE-BFFD-B12092C36500}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B11CCB1B-9DEA-44DE-BFFD-B12092C36500}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B11CCB1B-9DEA-44DE-BFFD-B12092C36500}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {45C5DB84-4136-4F76-ADA4-9BC87F3C6A95}
EndGlobalSection
EndGlobal

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

@ -36,95 +36,96 @@ namespace Topten.RichTextKit
public static IEnumerable<Run> GetFontRuns(Slice<int> codePoints, SKTypeface typeface)
{
// Get the font manager - we'll use this to select font fallbacks
var fontManager = SKFontManager.Default;
int currentRunPos = 0;
SKTypeface currentRunTypeface = null;
int pos = 0;
List<Run> runs = new List<Run>();
// Get glyphs using the top-level typeface
var glyphs = new ushort[codePoints.Length];
var font = new SKFont(typeface);
font.GetGlyphs(codePoints.AsSpan(), glyphs);
unsafe
// Look for subspans that need font fallback (where glyphs are zero)
int runStart = 0;
for (int i = 0; i < codePoints.Length; i++)
{
fixed (int* pCodePoints = codePoints.Underlying)
// Do we need fallback for this character?
if (glyphs[i] == 0)
{
int* pch = pCodePoints + codePoints.Start;
int length = codePoints.Length;
while (pos < length)
// Check if there's a fallback available, if not, might as well continue with the current top-level typeface
var subSpanTypeface = fontManager.MatchCharacter(typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, codePoints[i]);
if (subSpanTypeface == null)
continue;
// We can do font fallback...
// Flush the current top-level run
if (i > runStart)
{
var RunFace = typeface;
int count = 0;
if (pch[pos] <= 32)
yield return new Run()
{
// Control characters and space always map to current typeface
count = 1;
RunFace = currentRunTypeface ?? typeface;
}
else
{
// Consume as many characters as possible using the requested type face
count = typeface.GetGlyphs((IntPtr)(pch + pos), length - pos, SKEncoding.Utf32, out var glyphs);
}
// Couldn't be mapped to current font, try to find a replacement
if (count == 0)
{
// Find fallback font
RunFace = fontManager.MatchCharacter(typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, pch[pos]);
count = 1;
if (RunFace == null)
{
RunFace = typeface;
count = 1;
}
else
{
// Consume as many as possible
count = RunFace.GetGlyphs((IntPtr)(pch + pos), length - pos, SKEncoding.Utf32, out var glyphs);
// But don't take control characters or spaces...
for (int i = 1; i < count; i++)
{
if (pch[pos] <= 32)
{
count = i;
break;
}
}
}
}
// Do we need to start a new Run?
if (currentRunTypeface != RunFace)
{
flushCurrentRun();
currentRunTypeface = RunFace;
currentRunPos = pos;
}
// Move on
pos += count;
Start = runStart,
Length = i - runStart,
Typeface = typeface,
};
}
// Count how many unmatched characters
var unmatchedStart = i;
var unmatchedEnd = i + 1;
while (unmatchedEnd < codePoints.Length && glyphs[unmatchedEnd] == 0)
unmatchedEnd++;
var unmatchedLength = unmatchedEnd - unmatchedStart;
// Match the missing characters
while (unmatchedLength > 0)
{
// Find the font fallback using the first character
subSpanTypeface = fontManager.MatchCharacter(typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, codePoints[unmatchedStart]);
if (subSpanTypeface == null)
{
unmatchedEnd = unmatchedStart;
break;
}
var subSpanFont = new SKFont(subSpanTypeface);
// Get the glyphs over the current unmatched range
subSpanFont.GetGlyphs(codePoints.SubSlice(unmatchedStart, unmatchedLength).AsSpan(), new Span<ushort>(glyphs, unmatchedStart, unmatchedLength));
// Count how many characters were matched
var fallbackStart = unmatchedStart;
var fallbackEnd = unmatchedStart + 1;
while (fallbackEnd < unmatchedEnd && glyphs[fallbackEnd] != 0)
fallbackEnd++;
var fallbackLength = fallbackEnd - fallbackStart;
// Yield this font fallback run
yield return new Run()
{
Start = fallbackStart,
Length = fallbackLength,
Typeface = subSpanTypeface,
};
// Continue selecting font fallbacks until the entire unmatched ranges has been matched
unmatchedStart += fallbackLength;
unmatchedLength -= fallbackLength;
}
// Move onto the next top level span
i = unmatchedEnd - 1; // account for i++ on for loop
runStart = unmatchedEnd;
}
}
// Flush the final Run
flushCurrentRun();
// Done
return runs;
void flushCurrentRun()
// Flush find run
if (codePoints.Length > runStart)
{
if (currentRunTypeface != null)
yield return new Run()
{
runs.Add(new Run()
{
Start = currentRunPos,
Length = pos - currentRunPos,
Typeface = currentRunTypeface,
});
}
Start = runStart,
Length = codePoints.Length - runStart,
Typeface = typeface,
};
}
}
}

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

@ -1,4 +1,5 @@
// RichTextKit
#define USE_SKTEXTBLOB
// RichTextKit
// Copyright © 2019-2020 Topten Software. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -13,6 +14,7 @@
// License for the specific language governing permissions and limitations
// under the License.
using HarfBuzzSharp;
using SkiaSharp;
using System;
using System.Collections.Generic;
@ -209,6 +211,8 @@ namespace Topten.RichTextKit
GlyphPositions[i].X += dx;
GlyphPositions[i].Y += dy;
}
_textBlob?.Dispose();
_textBlob = null;
}
/// <summary>
@ -362,6 +366,8 @@ namespace Topten.RichTextKit
this.Clusters = this.Clusters.SubSlice(0, glyphSplitPos);
this.Width = sliceLeftWidth;
this.Length = codePointSplitPos;
this._textBlob?.Dispose();
this._textBlob = null;
// Return the new run
return newRun;
@ -419,6 +425,8 @@ namespace Topten.RichTextKit
this.Clusters = this.Clusters.SubSlice(glyphSplitPos);
this.Width = sliceRightWidth;
this.Length = codePointSplitPos;
this._textBlob?.Dispose();
this._textBlob = null;
// Adjust code point positions
for (int i = 0; i < this.RelativeCodePointXCoords.Length; i++)
@ -555,10 +563,6 @@ namespace Topten.RichTextKit
// Setup SKPaint
paint.Color = Style.TextColor;
paint.TextEncoding = SKTextEncoding.GlyphId;
paint.Typeface = Typeface;
paint.TextSize = Style.FontSize * glyphScale;
paint.SubpixelText = true;
paint.IsAntialias = ctx.Options.IsAntialias;
paint.LcdRenderText = ctx.Options.LcdRenderText;
@ -569,22 +573,35 @@ namespace Topten.RichTextKit
// Get glyph positions
var glyphPositions = GlyphPositions.ToArray();
// Create the font
if (_font == null)
{
_font = new SKFont(this.Typeface, this.Style.FontSize * glyphScale);
_font.Subpixel = true;
}
// Create the SKTextBlob (if necessary)
if (_textBlob == null)
{
_textBlob = SKTextBlob.CreatePositioned(
(IntPtr)(pGlyphs + Glyphs.Start),
Glyphs.Length * sizeof(ushort),
SKTextEncoding.GlyphId,
_font,
GlyphPositions.AsSpan());
}
// Paint underline
if (Style.Underline != UnderlineStyle.None && RunKind == FontRunKind.Normal)
{
// Work out underline metrics
paint.TextSize = Style.FontSize;
float underlineYPos = Line.YCoord + Line.BaseLine + (paint.FontMetrics.UnderlinePosition ?? 0);
paint.StrokeWidth = paint.FontMetrics.UnderlineThickness ?? 0;
paint.TextSize = Style.FontSize * glyphScale;
float underlineYPos = Line.YCoord + Line.BaseLine + (_font.Metrics.UnderlinePosition ?? 0);
paint.StrokeWidth = _font.Metrics.UnderlineThickness ?? 0;
if (Style.Underline == UnderlineStyle.Gapped)
{
// Get intercept positions
var interceptPositions = paint.GetPositionedTextIntercepts(
(IntPtr)(pGlyphs + Glyphs.Start),
Glyphs.Length * sizeof(ushort),
glyphPositions, underlineYPos - paint.StrokeWidth / 2, underlineYPos + paint.StrokeWidth);
var interceptPositions = _textBlob.GetIntercepts(underlineYPos - paint.StrokeWidth / 2, underlineYPos + paint.StrokeWidth);
// Paint gapped underlinline
float x = XCoord;
@ -609,31 +626,38 @@ namespace Topten.RichTextKit
}
}
// Draw the text
ctx.Canvas.DrawPositionedText((IntPtr)(pGlyphs + Glyphs.Start), Glyphs.Length * sizeof(ushort), glyphPositions, paint);
ctx.Canvas.DrawText(_textBlob, 0, 0, paint);
}
}
// Paint strikethrough
if (Style.StrikeThrough != StrikeThroughStyle.None && RunKind == FontRunKind.Normal)
{
paint.StrokeWidth = paint.FontMetrics.StrikeoutThickness ?? 0;
paint.StrokeWidth = _font.Metrics.StrikeoutThickness ?? 0;
float strikeYPos = Line.YCoord + Line.BaseLine + (paint.FontMetrics.StrikeoutPosition ?? 0) + glyphVOffset;
ctx.Canvas.DrawLine(new SKPoint(XCoord, strikeYPos), new SKPoint(XCoord + Width, strikeYPos), paint);
}
}
}
SKTextBlob _textBlob;
SKFont _font;
void Reset()
{
RunKind = FontRunKind.Normal;
CodePointBuffer = null;
Style = null;
Typeface = null;
Line = null;
_textBlob = null;
_font = null;
}
internal static ThreadLocal<ObjectPool<FontRun>> Pool = new ThreadLocal<ObjectPool<FontRun>>(() => new ObjectPool<FontRun>()
{
Cleaner = (r) =>
{
r.RunKind = FontRunKind.Normal;
r.CodePointBuffer = null;
r.Style = null;
r.Typeface = null;
r.Line = null;
}
Cleaner = (r) => r.Reset()
});
}
}

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

@ -247,7 +247,7 @@ namespace Topten.RichTextKit
using (var buffer = new HarfBuzzSharp.Buffer())
{
// Setup buffer
buffer.AddUtf32(new ReadOnlySpan<int>(codePoints.Underlying, codePoints.Start, codePoints.Length), 0, -1);
buffer.AddUtf32(codePoints.AsSpan(), 0, -1);
// Setup directionality (if supplied)
switch (direction)

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

@ -3,7 +3,7 @@
<Import Project="../buildtools/Topten.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.0;net45</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netcoreapp2.0;net462</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
@ -22,8 +22,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Include="HarfBuzzSharp" Version="2.6.1" />
<PackageReference Include="SkiaSharp" Version="2.80.1" />
<PackageReference Include="HarfBuzzSharp" Version="2.6.1.6" />
</ItemGroup>
</Project>

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

@ -182,5 +182,11 @@ namespace Topten.RichTextKit.Utils
/// A shared empty slice of type T
/// </summary>
public static Slice<T> Empty => new Slice<T>();
/// <summary>
/// Get the slice as a Span
/// </summary>
/// <returns></returns>
public Span<T> AsSpan() => new Span<T>(_array, _start, _length);
}
}