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

Add function to SkiaSharp.Harfbuzz for getting shaped text paths #2513

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
61 changes: 61 additions & 0 deletions source/SkiaSharp.HarfBuzz/SkiaSharp.HarfBuzz/FontExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,66 @@ public static void SetScale(this Font font, SKSizeI scale)

font.SetScale(scale.Width, scale.Height);
}

public static SKPath GetShapedTextPath(this SKFont font, string text, SKPoint p, SKTextAlign textAlign) =>
font.GetShapedTextPath(text, p.X, p.Y, textAlign);

public static SKPath GetShapedTextPath(this SKFont font, string text, float x, float y, SKTextAlign textAlign)
{
if (string.IsNullOrEmpty(text))
return new SKPath();

using var shaper = new SKShaper(font.Typeface);
return font.GetShapedTextPath(shaper, text, x, y, textAlign);
}

public static SKPath GetShapedTextPath(this SKFont font, SKShaper shaper, string text, float x, float y, SKTextAlign textAlign)
{
var returnPath = new SKPath();

if (string.IsNullOrEmpty(text))
return returnPath;

if (shaper == null)
throw new ArgumentNullException(nameof(shaper));

font.Typeface = shaper.Typeface;

// shape the text
var result = shaper.Shape(text, x, y, font);

// adjust alignment
var xOffset = 0.0f;
if (textAlign != SKTextAlign.Left)
{
var width = result.Width;
if (textAlign == SKTextAlign.Center)
width *= 0.5f;
xOffset -= width;
}

// generate a path for each glyph
for (var i = 0; i < result.Points.Length; i++)
{
// get the glyph path
using var glyphPath = font.GetGlyphPath((ushort)result.Codepoints[i]);

if (glyphPath.IsEmpty)
continue;

// translate the glyph path
var point = result.Points[i];
glyphPath.Transform(new SKMatrix(
1, 0, point.X + xOffset,
0, 1, point.Y,
0, 0, 1
));

// append the glyph path
returnPath.AddPath(glyphPath);
}

return returnPath;
}
}
}
89 changes: 72 additions & 17 deletions tests/Tests/SkiaSharp/SKShaperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,33 @@ public void DrawShapedTextExtensionMethodDraws()
}
}

[SkippableFact]
public void GetShapedTextPathExtensionMethodDraws()
{
using (var bitmap = new SKBitmap(new SKImageInfo(512, 512)))
using (var canvas = new SKCanvas(bitmap))
using (var tf = SKTypeface.FromFile(Path.Combine(PathToFonts, "content-font.ttf")))
using (var shaper = new SKShaper(tf))
using (var paint = new SKPaint { IsAntialias = true })
using (var font = new SKFont { Size = 64, Typeface = tf})
{
canvas.Clear(SKColors.White);

using var shapedPath = font.GetShapedTextPath(shaper, "متن", 100, 200, SKTextAlign.Left);
canvas.DrawPath(shapedPath, paint);

canvas.Flush();

Assert.Equal(SKColors.Black, bitmap.GetPixel(110, 210));
Assert.Equal(SKColors.Black, bitmap.GetPixel(127, 196));
Assert.Equal(SKColors.Black, bitmap.GetPixel(142, 197));
Assert.Equal(SKColors.Black, bitmap.GetPixel(155, 195));
Assert.Equal(SKColors.Black, bitmap.GetPixel(131, 181));
Assert.Equal(SKColors.White, bitmap.GetPixel(155, 190));
Assert.Equal(SKColors.White, bitmap.GetPixel(110, 200));
}
}

[SkippableFact]
public void CorrectlyShapesArabicScriptAtAnOffset()
{
Expand Down Expand Up @@ -112,7 +139,7 @@ public void TextAlignMovesTextPosition(SKTextAlign align, int offset)
var fontFile = Path.Combine(PathToFonts, "segoeui.ttf");
using var tf = SKTypeface.FromFile(fontFile);

using var bitmap = new SKBitmap(600, 200);
using var bitmap = new SKBitmap(600, 300);
using var canvas = new SKCanvas(bitmap);

canvas.Clear(SKColors.White);
Expand All @@ -127,28 +154,56 @@ public void TextAlignMovesTextPosition(SKTextAlign align, int offset)

canvas.DrawShapedText("SkiaSharp", 300, 100, align, font, paint);

AssertTextAlign(bitmap, offset, 0);
AssertTextAlign(bitmap, offset, 100);
}

[SkippableTheory]
[InlineData(SKTextAlign.Left, 300)]
[InlineData(SKTextAlign.Center, 162)]
[InlineData(SKTextAlign.Right, 23)]
public void TextAlignMovesTextPathPosition(SKTextAlign align, int offset)
{
var fontFile = Path.Combine(PathToFonts, "segoeui.ttf");
using var tf = SKTypeface.FromFile(fontFile);

using var bitmap = new SKBitmap(600, 300);
using var canvas = new SKCanvas(bitmap);

canvas.Clear(SKColors.White);

using var paint = new SKPaint();
paint.IsAntialias = true;
paint.Color = SKColors.Black;

using var font = new SKFont();
font.Typeface = tf;
font.Size = 64;

using var shapedPath = font.GetShapedTextPath("SkiaSharp", 300, 100, align);
canvas.DrawPath(shapedPath, paint);

AssertTextAlign(bitmap, offset, 100);
}

private static void AssertTextAlign(SKBitmap bitmap, int x, int y)
{
// [S]kia[S]har[p]

Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 6, y + 66));
Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 28, y + 87));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 28, y + 66));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 6, y + 87));

Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 120, y + 66));
Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 142, y + 87));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 142, y + 66));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 120, y + 87));

Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 246, y + 70));
Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 246, y + 113));
Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 271, y + 83));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 258, y + 83));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 258, y + 113));
Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 6, y - 34));
Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 28, y - 13));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 28, y - 34));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 6, y - 13));

Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 120, y - 34));
Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 142, y - 13));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 142, y - 34));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 120, y - 13));

Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 246, y - 30));
Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 246, y + 13));
Assert.Equal(SKColors.Black, bitmap.GetPixel(x + 271, y - 17));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 258, y - 17));
Assert.Equal(SKColors.White, bitmap.GetPixel(x + 258, y + 13));
}
}
}