Skip to content

Commit

Permalink
サロゲートペア(絵文字など)に対応
Browse files Browse the repository at this point in the history
  • Loading branch information
yuto-trd committed Sep 21, 2023
1 parent c2a5b52 commit 4f4da9b
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 37 deletions.
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

0 comments on commit 4f4da9b

Please sign in to comment.