Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

サロゲートペア(絵文字など)に対応 #744

Merged
merged 1 commit into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
<PackageVersion Include="FluentIcons.FluentAvalonia" Version="1.1.216" />
<PackageVersion Include="FluentTextTable" Version="1.0.0" />
<PackageVersion Include="GitVersion.MsBuild" Version="5.12.0" />
<PackageVersion Include="HarfBuzzSharp" Version="2.8.2.5" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2.5" />
<PackageVersion Include="ILGPU" Version="1.5.1" />
<PackageVersion Include="Kokuban" Version="0.2.0" />
<PackageVersion Include="Kurukuru" Version="1.4.2" />
Expand Down Expand Up @@ -64,6 +66,7 @@
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="Sharprompt" Version="2.4.5" />
<PackageVersion Include="SkiaSharp" Version="2.88.5" />
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.5" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.5" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.Interactive" Version="6.0.1" />
Expand Down
3 changes: 3 additions & 0 deletions src/Beutl.Engine/Beutl.Engine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="HarfBuzzSharp" />
<PackageReference Include="HarfBuzzSharp.NativeAssets.Linux" />
<PackageReference Include="ILGPU" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
<PackageReference Include="NAudio.Core" />
Expand All @@ -25,6 +27,7 @@
<PackageReference Include="OpenCvSharp4.runtime.win" />
<PackageReference Include="OpenTK.OpenAL" />
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" />
<PackageReference Include="SkiaSharp.HarfBuzz" />
<PackageReference Include="Vortice.XAudio2" />
<PackageReference Include="SkiaSharp" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" />
Expand Down
100 changes: 77 additions & 23 deletions src/Beutl.Engine/Graphics/ImmediateCanvas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Beutl.Threading;

using SkiaSharp;
using SkiaSharp.HarfBuzz;

namespace Beutl.Graphics;

Expand Down Expand Up @@ -327,45 +328,98 @@ public void DrawRectangle(Rect rect, IBrush? fill, IPen? pen)
}
}

private static SKPath CreateSKPathFromText(ReadOnlySpan<ushort> glyphs, ReadOnlySpan<SKPoint> positions, SKFont font)
{
var path = new SKPath();

for (int i = 0; i < glyphs.Length; i++)
{
ushort glyph = glyphs[i];
SKPoint point = positions[i];

using SKPath glyphPath = font.GetGlyphPath(glyph);
path.AddPath(glyphPath, point.X, point.Y);
}

return path;
}

private void DrawTextStroke(FormattedText text, IPen pen, SKTextBlob textBlob,
ReadOnlySpan<ushort> glyphs, ReadOnlySpan<SKPoint> positions, SKFont font)
{
ConfigureStrokePaint(new(text.Bounds), pen!);
if (pen.StrokeAlignment == StrokeAlignment.Center)
{
_canvas.DrawText(textBlob, 0, 0, _sharedStrokePaint);
}
else
{
using SKPath path = CreateSKPathFromText(glyphs, positions, font);

switch (pen!.StrokeAlignment)
{
case StrokeAlignment.Inside:
_canvas.Save();
_canvas.ClipPath(path, SKClipOperation.Intersect, true);
_canvas.DrawText(textBlob, 0, 0, _sharedStrokePaint);
_canvas.Restore();
break;

case StrokeAlignment.Outside:
_canvas.Save();
_canvas.ClipPath(path, SKClipOperation.Difference, true);
_canvas.DrawText(textBlob, 0, 0, _sharedStrokePaint);
_canvas.Restore();
break;
}
}
}

public void DrawText(FormattedText text, IBrush? fill, IPen? pen)
{
VerifyAccess();

// SKPathに変換
var typeface = new Typeface(text.Font, text.Style, text.Weight);
SKTypeface sktypeface = typeface.ToSkia();
_sharedFillPaint.Reset();
_sharedFillPaint.TextSize = text.Size;
_sharedFillPaint.Typeface = sktypeface;

#if DEBUG
Span<char> sc = new char[1];
#else
Span<char> sc = stackalloc char[1];
#endif
float prevRight = 0;
using var path = new SKPath();
using var shaper = new SKShaper(sktypeface);
using var buffer = new HarfBuzzSharp.Buffer();
buffer.AddUtf16(text.Text.AsSpan());
buffer.GuessSegmentProperties();

SKShaper.Result result = shaper.Shape(buffer, _sharedFillPaint);

// create the text blob
using var builder = new SKTextBlobBuilder();
using SKFont font = _sharedFillPaint.ToFont();
SKPositionedRunBuffer run = builder.AllocatePositionedRun(font, result.Codepoints.Length);

foreach (char item in text.Text.AsSpan())
// copy the glyphs
Span<ushort> glyphs = run.GetGlyphSpan();
Span<SKPoint> positions = run.GetPositionSpan();
for (int i = 0; i < result.Codepoints.Length; i++)
{
sc[0] = item;
var bounds = default(SKRect);
float w = _sharedFillPaint.MeasureText(sc, ref bounds);
glyphs[i] = (ushort)result.Codepoints[i];
SKPoint point = result.Points[i];
point.X += i * text.Spacing;
positions[i] = point;
}

using SKPath skPath = _sharedFillPaint.GetTextPath(
sc,
(bounds.Width / 2) - bounds.MidX,
0);
// build
using SKTextBlob textBlob = builder.Build();

path.AddPath(skPath, prevRight + bounds.Left, 0);
// draw filled
ConfigureFillPaint(text.Bounds, fill);
_canvas.DrawText(textBlob, 0, 0, _sharedFillPaint);

prevRight += text.Spacing;
prevRight += w;
// draw stroke
if (pen != null && pen.Thickness != 0)
{
DrawTextStroke(text, pen, textBlob, glyphs, positions, font);
}

// キャンバスに描画
Size size = text.Bounds;
DrawSKPath(path, false, fill, pen);
}

internal void DrawSKPath(SKPath skPath, bool strokeOnly, IBrush? fill, IPen? pen)
Expand Down
53 changes: 39 additions & 14 deletions src/Beutl.Engine/Media/TextFormatting/FormattedText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Beutl.Graphics;

using SkiaSharp;
using SkiaSharp.HarfBuzz;

namespace Beutl.Media.TextFormatting;

Expand Down Expand Up @@ -73,7 +74,7 @@ public StringSpan Text
public bool BeginOnNewLine { get; set; } = false;

public IBrush? Brush { get; set; }

public IPen? Pen { get; set; }

public FontMetrics Metrics => MeasureAndSetField().Metrics;
Expand All @@ -89,22 +90,39 @@ internal Point AddToSKPath(SKPath path, Point point)
Typeface = typeface
};

Size size = Bounds;
Span<char> buffer = stackalloc char[1];
using var shaper = new SKShaper(typeface);
using var buffer = new HarfBuzzSharp.Buffer();
buffer.AddUtf16(Text.AsSpan());
buffer.GuessSegmentProperties();

foreach (char item in Text.AsSpan())
{
buffer[0] = item;
var bounds = default(SKRect);
float width = paint.MeasureText(buffer, ref bounds);
SKShaper.Result result = shaper.Shape(buffer, paint);

SKPath skPath = paint.GetTextPath(buffer, (bounds.Width / 2) - bounds.MidX, 0);
// create the text blob
using var builder = new SKTextBlobBuilder();
using SKFont font = paint.ToFont();
SKPositionedRunBuffer run = builder.AllocatePositionedRun(font, result.Codepoints.Length);

// copy the glyphs
Span<ushort> glyphs = run.GetGlyphSpan();
Span<SKPoint> positions = run.GetPositionSpan();
for (int i = 0; i < result.Codepoints.Length; i++)
{
glyphs[i] = (ushort)result.Codepoints[i];
SKPoint p = result.Points[i];
p.X += i * Spacing;
positions[i] = p;
}

path.AddPath(skPath, point.X + bounds.Left, point.Y);
// build
using SKTextBlob textBlob = builder.Build();

skPath.Dispose();
for (int i = 0; i < glyphs.Length; i++)
{
ushort glyph = glyphs[i];
SKPoint p = positions[i] + point.ToSKPoint();

point += new Point(Spacing + width, 0);
using SKPath glyphPath = font.GetGlyphPath(glyph);
path.AddPath(glyphPath, p.X, p.Y);
}

return point;
Expand All @@ -119,10 +137,17 @@ internal Point AddToSKPath(SKPath path, Point point)
Typeface = typeface
};

using var shaper = new SKShaper(typeface);
using var buffer = new HarfBuzzSharp.Buffer();
buffer.AddUtf16(Text.AsSpan());
buffer.GuessSegmentProperties();

SKShaper.Result result = shaper.Shape(buffer, paint);

FontMetrics fontMetrics = paint.FontMetrics.ToFontMetrics();
float w = paint.MeasureText(Text.AsSpan());
float w = result.Width;
var size = new Size(
w + (Text.Length - 1) * Spacing,
w + (buffer.Length - 1) * Spacing,
fontMetrics.Descent - fontMetrics.Ascent);

return (fontMetrics, size);
Expand Down