public MyShaper(SKTypeface typeface) { if (typeface == null) { throw new ArgumentNullException(nameof(typeface)); } ; Typeface = typeface; int index; using (var blob = Typeface.OpenStream(out index).ToHarfBuzzBlob()) using (var face = new Face(blob, (uint)index)) { face.Index = (uint)index; face.UnitsPerEm = (uint)Typeface.UnitsPerEm; font = new Font(face); font.SetScale(FONT_SIZE_SCALE, FONT_SIZE_SCALE); #if __MAC__ font.SetFunctionsOpenType(); #endif } buffer = new HarfBuzzSharp.Buffer(); }
public void ShouldThrowInvalidOperationExceptionOnSerializeGlyphsWhenBufferIsEmpty() { using (var buffer = new Buffer()) { Assert.Throws <InvalidOperationException>(() => { buffer.SerializeGlyphs(); }); } }
public ShapedBuffer ShapeText(ReadOnlySlice <char> text, TextShaperOptions options) { var typeface = options.Typeface; var fontRenderingEmSize = options.FontRenderingEmSize; var bidiLevel = options.BidLevel; var culture = options.Culture; using (var buffer = new Buffer()) { buffer.AddUtf16(text.Buffer.Span, text.Start, text.Length); MergeBreakPair(buffer); buffer.GuessSegmentProperties(); buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft; buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); var font = ((HarfBuzzGlyphTypefaceImpl)typeface.PlatformImpl).Font; font.Shape(buffer); if (buffer.Direction == Direction.RightToLeft) { buffer.Reverse(); } font.GetScale(out var scaleX, out _); var textScale = fontRenderingEmSize / scaleX; var bufferLength = buffer.Length; var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel); var glyphInfos = buffer.GetGlyphInfoSpan(); var glyphPositions = buffer.GetGlyphPositionSpan(); for (var i = 0; i < bufferLength; i++) { var sourceInfo = glyphInfos[i]; var glyphIndex = (ushort)sourceInfo.Codepoint; var glyphCluster = (int)sourceInfo.Cluster; var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale); var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); var targetInfo = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset); shapedBuffer[i] = targetInfo; } return(shapedBuffer); } }
public new Result Shape(string text, float xOffset, float yOffset, SKPaint paint) { if (string.IsNullOrEmpty(text)) { return(new Result()); } using var buffer = new Buffer(); switch (paint.TextEncoding) { case SKTextEncoding.Utf8: buffer.AddUtf8(text); break; case SKTextEncoding.Utf16: buffer.AddUtf16(text); break; case SKTextEncoding.Utf32: buffer.AddUtf32(text); break; default: throw new NotSupportedException("TextEncoding of type GlyphId is not supported."); } buffer.GuessSegmentProperties(); return(Shape(buffer, xOffset, yOffset, paint)); }
public void CanCreateFaceShaperFromTypeface() { var skiaTypeface = SKTypeface.FromFile(Path.Combine(PathToFonts, "content-font.ttf")); var clusters = new uint[] { 4, 2, 0 }; var codepoints = new uint[] { 629, 668, 891 }; using (var face = new Face(GetFaceBlob, () => skiaTypeface.Dispose())) using (var font = new Font(face)) using (var buffer = new HarfBuzzSharp.Buffer()) { buffer.AddUtf8("متن"); buffer.GuessSegmentProperties(); font.Shape(buffer); Assert.Equal(clusters, buffer.GlyphInfos.Select(i => i.Cluster)); Assert.Equal(codepoints, buffer.GlyphInfos.Select(i => i.Codepoint)); } Blob GetFaceBlob(Face face, Tag tag) { var size = skiaTypeface.GetTableSize(tag); var data = Marshal.AllocCoTaskMem(size); skiaTypeface.TryGetTableData(tag, 0, size, data); return(new Blob(data, size, MemoryMode.Writeable, () => Marshal.FreeCoTaskMem(data))); } }
static List <TextFormatting.GlyphInfo> GetGlyphs(HarfBuzzSharp.Buffer buffer, int clusterStart, float textSizeX, float textSizeY) { int length = buffer.Length; var hbGlyphs = buffer.GlyphInfos; var hbPositions = buffer.GlyphPositions; List <TextFormatting.GlyphInfo> glyphs = new(length); for (int i = 0; i < length; i++) { var hbGlyph = hbGlyphs[i]; var hbPos = hbPositions[i]; TextFormatting.GlyphInfo glyph = new( (ushort)hbGlyph.Codepoint, clusterStart + (int)hbGlyph.Cluster, hbPos.XAdvance *textSizeX, hbPos.XOffset *textSizeX, hbPos.YOffset *textSizeY ); glyphs.Add(glyph); } return(glyphs); }
public GlyphRun ShapeText(ReadOnlySlice <char> text, Typeface typeface, double fontRenderingEmSize, CultureInfo culture) { using (var buffer = new Buffer()) { FillBuffer(buffer, text); buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); buffer.GuessSegmentProperties(); var glyphTypeface = typeface.GlyphTypeface; var font = ((HarfBuzzGlyphTypefaceImpl)glyphTypeface.PlatformImpl).Font; font.Shape(buffer); font.GetScale(out var scaleX, out _); var textScale = fontRenderingEmSize / scaleX; var bufferLength = buffer.Length; var glyphInfos = buffer.GetGlyphInfoSpan(); var glyphPositions = buffer.GetGlyphPositionSpan(); var glyphIndices = new ushort[bufferLength]; var clusters = new ushort[bufferLength]; double[] glyphAdvances = null; Vector[] glyphOffsets = null; for (var i = 0; i < bufferLength; i++) { glyphIndices[i] = (ushort)glyphInfos[i].Codepoint; clusters[i] = (ushort)glyphInfos[i].Cluster; if (!glyphTypeface.IsFixedPitch) { SetAdvance(glyphPositions, i, textScale, ref glyphAdvances); } SetOffset(glyphPositions, i, textScale, ref glyphOffsets); } return(new GlyphRun(glyphTypeface, fontRenderingEmSize, new ReadOnlySlice <ushort>(glyphIndices), new ReadOnlySlice <double>(glyphAdvances), new ReadOnlySlice <Vector>(glyphOffsets), text, new ReadOnlySlice <ushort>(clusters), buffer.Direction == Direction.LeftToRight ? 0 : 1)); } }
public void ShouldThrowInvalidOperationExceptionOnDeserializeGlyphsWhenBufferIsNonEmpty() { using (var buffer = new Buffer()) { buffer.AddUtf8("A"); Assert.Throws <InvalidOperationException>(() => { buffer.DeserializeGlyphs(SerializedSimpleText); }); } }
public void ShouldThrowInvalidOperationExceptionOnSerializeGlyphsWhenBufferIsUnShaped() { using (var buffer = new Buffer()) { buffer.AddUtf8("A"); Assert.Throws <InvalidOperationException>(() => { buffer.SerializeGlyphs(); }); } }
public void ShouldAppendBuffer() { using (var buffer = new Buffer()) using (var source = new Buffer()) { source.AddUtf8("123"); buffer.Append(source, 0, source.Length); } }
public void ShouldHaveDefaultStateAfterReset() { using (var buffer = new Buffer()) { buffer.AddUtf8(SimpleText); buffer.Reset(); Assert.Equal(ContentType.Invalid, buffer.ContentType); Assert.Equal(0, buffer.Length); } }
public void ShouldClearContents() { using (var buffer = new Buffer()) { buffer.AddUtf8(SimpleText); Assert.Equal(SimpleText.Length, buffer.GlyphInfos.Length); buffer.ClearContents(); Assert.Empty(buffer.GlyphInfos); } }
public void ShouldThrowInvalidOperationExceptionOnAddUtfWhenBufferIsShaped() { using (var typeface = SKTypeface.FromFile(Path.Combine(PathToFonts, "content-font.ttf"))) using (var blob = typeface.OpenStream(out var index).ToHarfBuzzBlob()) using (var face = new Face(blob, index)) using (var font = new Font(face)) using (var buffer = new Buffer()) { buffer.AddUtf8(SimpleText); font.Shape(buffer); Assert.Throws <InvalidOperationException>(() => { buffer.AddUtf8("A"); }); } }
public void ShouldReverseClusters() { using (var buffer = new Buffer()) { buffer.AddUtf8("12"); buffer.ReverseClusters(); Assert.Equal(50u, buffer.GlyphInfos[0].Codepoint); Assert.Equal(1u, buffer.GlyphInfos[0].Cluster); Assert.Equal(49u, buffer.GlyphInfos[1].Codepoint); Assert.Equal(0u, buffer.GlyphInfos[1].Cluster); } }
public CustomSKShaper(SKTypeface typeface) : base(typeface) { using (var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob()) using (var face = new Face(blob, index)) { face.Index = index; face.UnitsPerEm = Typeface.UnitsPerEm; _font = new Font(face); _font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE); _font.SetFunctionsOpenType(); } _buffer = new Buffer(); }
private static void MergeBreakPair(Buffer buffer) { var length = buffer.Length; var glyphInfos = buffer.GetGlyphInfoSpan(); var second = glyphInfos[length - 1]; if (!new Codepoint((int)second.Codepoint).IsBreakChar) { return; } if (length > 1 && glyphInfos[length - 2].Codepoint == '\r' && second.Codepoint == '\n') { var first = glyphInfos[length - 2]; first.Codepoint = '\u200C'; second.Codepoint = '\u200C'; second.Cluster = first.Cluster; unsafe { fixed(GlyphInfo *p = &glyphInfos[length - 2]) { *p = first; } fixed(GlyphInfo *p = &glyphInfos[length - 1]) { *p = second; } } } else { second.Codepoint = '\u200C'; unsafe { fixed(GlyphInfo *p = &glyphInfos[length - 1]) { *p = second; } } } }
public void ShouldNormalize() { using (var typeface = SKTypeface.FromFile(Path.Combine(PathToFonts, "content-font.ttf"))) using (var blob = typeface.OpenStream(out var index).ToHarfBuzzBlob()) using (var face = new Face(blob, index)) using (var font = new Font(face)) using (var buffer = new Buffer()) { buffer.AddUtf16("Â̶"); font.Shape(buffer); buffer.NormalizeGlyphs(); Assert.Equal(1027, buffer.GlyphPositions[1].YOffset); } }
public void ShouldAddUtfByString() { using (var buffer = new Buffer()) { buffer.AddUtf8("A"); Assert.Equal(1, buffer.Length); buffer.AddUtf8("B"); Assert.Equal(2, buffer.Length); buffer.AddUtf8("C"); Assert.Equal(3, buffer.Length); } }
public Result Shape(Buffer buffer, float xOffset, float yOffset, SKPaint paint) { if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (paint == null) { throw new ArgumentNullException(nameof(paint)); } // do the shaping font.Shape(buffer); // get the shaping results var len = buffer.Length; var info = buffer.GlyphInfos; var pos = buffer.GlyphPositions; // get the sizes float textSizeY = paint.TextSize / FONT_SIZE_SCALE; float textSizeX = textSizeY * paint.TextScaleX; var points = new SKPoint[len]; var clusters = new uint[len]; var codepoints = new uint[len]; for (var i = 0; i < len; i++) { codepoints[i] = info[i].Codepoint; clusters[i] = info[i].Cluster; points[i] = new SKPoint( xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY); // move the cursor xOffset += pos[i].XAdvance * textSizeX; yOffset += pos[i].YAdvance * textSizeY; } return(new Result(codepoints, clusters, points)); }
private static void FillBuffer(Buffer buffer, ReadOnlySlice <char> text) { buffer.ContentType = ContentType.Unicode; var i = 0; while (i < text.Length) { var codepoint = Codepoint.ReadAt(text, i, out var count); var cluster = (uint)(text.Start + i); if (codepoint.IsBreakChar) { if (i + 1 < text.Length) { var nextCodepoint = Codepoint.ReadAt(text, i + 1, out _); if (nextCodepoint == '\r' && codepoint == '\n' || nextCodepoint == '\n' && codepoint == '\r') { count++; buffer.Add('\u200C', cluster); buffer.Add('\u200D', cluster); } else { buffer.Add('\u200C', cluster); } } else { buffer.Add('\u200C', cluster); } } else { buffer.Add(codepoint, cluster); } i += count; } }
public void ShouldSerializeGlyphs() { using (var typeface = SKTypeface.FromFile(Path.Combine(PathToFonts, "content-font.ttf"))) using (var blob = typeface.OpenStream(out var index).ToHarfBuzzBlob()) using (var face = new Face(blob, index)) using (var font = new Font(face)) using (var buffer = new Buffer()) { buffer.AddUtf16(SimpleText); buffer.GuessSegmentProperties(); font.Shape(buffer); var serializedGlyphs = buffer.SerializeGlyphs(); Assert.Equal(SerializedSimpleText, serializedGlyphs); } }
public void ShouldHaveCorrectContentType() { using (var typeface = SKTypeface.FromFile(Path.Combine(PathToFonts, "content-font.ttf"))) using (var blob = typeface.OpenStream(out var index).ToHarfBuzzBlob()) using (var face = new Face(blob, index)) using (var font = new Font(face)) using (var buffer = new Buffer()) { Assert.Equal(ContentType.Invalid, buffer.ContentType); buffer.AddUtf8(SimpleText); Assert.Equal(ContentType.Unicode, buffer.ContentType); font.Shape(buffer); Assert.Equal(ContentType.Glyphs, buffer.ContentType); } }
public SKShaper(SKTypeface typeface) { Typeface = typeface ?? throw new ArgumentNullException(nameof(typeface)); int index; using (var blob = Typeface.OpenStream(out index).ToHarfBuzzBlob()) using (var face = new Face(blob, index)) { face.Index = index; face.UnitsPerEm = Typeface.UnitsPerEm; font = new Font(face); font.SetScale(FONT_SIZE_SCALE, FONT_SIZE_SCALE); font.SetFunctionsOpenType(); } buffer = new Buffer(); }
public void ShouldDeserializeGlyphs() { using (var buffer = new Buffer()) { buffer.DeserializeGlyphs(SerializedSimpleText); Assert.Equal(SimpleText.Length, buffer.Length); Assert.Equal(0u, buffer.GlyphInfos[0].Cluster); Assert.Equal(25u, buffer.GlyphInfos[0].Codepoint); Assert.Equal(1u, buffer.GlyphInfos[1].Cluster); Assert.Equal(26u, buffer.GlyphInfos[1].Codepoint); Assert.Equal(2u, buffer.GlyphInfos[2].Cluster); Assert.Equal(27u, buffer.GlyphInfos[2].Codepoint); Assert.Equal(3u, buffer.GlyphInfos[3].Cluster); Assert.Equal(28u, buffer.GlyphInfos[3].Codepoint); } }
public void ShouldAddUtfBySpan() { using (var buffer = new Buffer()) { var utf8 = Encoding.UTF8.GetBytes("A").AsSpan(); buffer.AddUtf8(utf8); Assert.Equal(1, buffer.Length); var utf16 = "B".AsSpan(); buffer.AddUtf16(utf16); Assert.Equal(2, buffer.Length); var utf32 = new[] { char.ConvertToUtf32("C", 0) }; buffer.AddUtf32(utf32); Assert.Equal(3, buffer.Length); } }
public ShapedBuffer ShapeText(ReadOnlySlice <char> text, TextShaperOptions options) { var typeface = options.Typeface; var fontRenderingEmSize = options.FontRenderingEmSize; var bidiLevel = options.BidLevel; var culture = options.Culture; using (var buffer = new Buffer()) { buffer.AddUtf16(text.Buffer.Span, text.BufferOffset, text.Length); MergeBreakPair(buffer); buffer.GuessSegmentProperties(); buffer.Direction = Direction.LeftToRight; //Always shape LeftToRight buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font; font.Shape(buffer); font.GetScale(out var scaleX, out _); var textScale = fontRenderingEmSize / scaleX; var bufferLength = buffer.Length; var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel); var glyphInfos = buffer.GetGlyphInfoSpan(); var glyphPositions = buffer.GetGlyphPositionSpan(); for (var i = 0; i < bufferLength; i++) { var sourceInfo = glyphInfos[i]; var glyphIndex = (ushort)sourceInfo.Codepoint; var glyphCluster = (int)(sourceInfo.Cluster); var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale); var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); if (glyphIndex == 0 && text.Buffer.Span[glyphCluster] == '\t') { glyphIndex = typeface.GetGlyph(' '); glyphAdvance = options.IncrementalTabWidth > 0 ? options.IncrementalTabWidth : 4 * typeface.GetGlyphAdvance(glyphIndex) * textScale; } var targetInfo = new Avalonia.Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset); shapedBuffer[i] = targetInfo; } return(shapedBuffer); } }
public void Shape(Buffer buffer) { font.Shape(buffer); }
public GlyphRun ShapeText(ReadOnlySlice <char> text, TextFormat textFormat) { using (var buffer = new Buffer()) { buffer.ContentType = ContentType.Unicode; var breakCharPosition = text.Length - 1; var codepoint = Codepoint.ReadAt(text, breakCharPosition, out var count); if (codepoint.IsBreakChar) { var breakCharCount = 1; if (text.Length > 1) { var previousCodepoint = Codepoint.ReadAt(text, breakCharPosition - count, out _); if (codepoint == '\r' && previousCodepoint == '\n' || codepoint == '\n' && previousCodepoint == '\r') { breakCharCount = 2; } } if (breakCharPosition != text.Start) { buffer.AddUtf16(text.Buffer.Span.Slice(0, text.Length - breakCharCount)); } var cluster = buffer.GlyphInfos.Length > 0 ? buffer.GlyphInfos[buffer.Length - 1].Cluster + 1 : (uint)text.Start; switch (breakCharCount) { case 1: buffer.Add('\u200C', cluster); break; case 2: buffer.Add('\u200C', cluster); buffer.Add('\u200D', cluster); break; } } else { buffer.AddUtf16(text.Buffer.Span); } buffer.GuessSegmentProperties(); var glyphTypeface = textFormat.Typeface.GlyphTypeface; var font = ((GlyphTypefaceImpl)glyphTypeface.PlatformImpl).Font; font.Shape(buffer); font.GetScale(out var scaleX, out _); var textScale = textFormat.FontRenderingEmSize / scaleX; var len = buffer.Length; var info = buffer.GetGlyphInfoSpan(); var pos = buffer.GetGlyphPositionSpan(); var glyphIndices = new ushort[len]; var clusters = new ushort[len]; var glyphAdvances = new double[len]; var glyphOffsets = new Vector[len]; for (var i = 0; i < len; i++) { glyphIndices[i] = (ushort)info[i].Codepoint; clusters[i] = (ushort)(text.Start + info[i].Cluster); var advanceX = pos[i].XAdvance * textScale; // Depends on direction of layout //var advanceY = pos[i].YAdvance * textScale; glyphAdvances[i] = advanceX; var offsetX = pos[i].XOffset * textScale; var offsetY = pos[i].YOffset * textScale; glyphOffsets[i] = new Vector(offsetX, offsetY); } return(new GlyphRun(glyphTypeface, textFormat.FontRenderingEmSize, new ReadOnlySlice <ushort>(glyphIndices), new ReadOnlySlice <double>(glyphAdvances), new ReadOnlySlice <Vector>(glyphOffsets), text, new ReadOnlySlice <ushort>(clusters))); } }
public GlyphRun ShapeText(ReadOnlySlice <char> text, Typeface typeface, double fontRenderingEmSize, CultureInfo culture) { using (var buffer = new Buffer()) { buffer.ContentType = ContentType.Unicode; var breakCharPosition = text.Length - 1; var codepoint = Codepoint.ReadAt(text, breakCharPosition, out var count); if (codepoint.IsBreakChar) { var breakCharCount = 1; if (text.Length > 1) { var previousCodepoint = Codepoint.ReadAt(text, breakCharPosition - count, out _); if (codepoint == '\r' && previousCodepoint == '\n' || codepoint == '\n' && previousCodepoint == '\r') { breakCharCount = 2; } } if (breakCharPosition != text.Start) { buffer.AddUtf16(text.Buffer.Span.Slice(0, text.Length - breakCharCount)); } var cluster = buffer.GlyphInfos.Length > 0 ? buffer.GlyphInfos[buffer.Length - 1].Cluster + 1 : (uint)text.Start; switch (breakCharCount) { case 1: buffer.Add('\u200C', cluster); break; case 2: buffer.Add('\u200C', cluster); buffer.Add('\u200D', cluster); break; } } else { buffer.AddUtf16(text.Buffer.Span); } buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); buffer.GuessSegmentProperties(); var glyphTypeface = typeface.GlyphTypeface; var font = ((GlyphTypefaceImpl)glyphTypeface.PlatformImpl).Font; font.Shape(buffer); font.GetScale(out var scaleX, out _); var textScale = fontRenderingEmSize / scaleX; var bufferLength = buffer.Length; var glyphInfos = buffer.GetGlyphInfoSpan(); var glyphPositions = buffer.GetGlyphPositionSpan(); var glyphIndices = new ushort[bufferLength]; var clusters = new ushort[bufferLength]; double[] glyphAdvances = null; Vector[] glyphOffsets = null; for (var i = 0; i < bufferLength; i++) { glyphIndices[i] = (ushort)glyphInfos[i].Codepoint; clusters[i] = (ushort)(text.Start + glyphInfos[i].Cluster); if (!glyphTypeface.IsFixedPitch) { SetAdvance(glyphPositions, i, textScale, ref glyphAdvances); } SetOffset(glyphPositions, i, textScale, ref glyphOffsets); } return(new GlyphRun(glyphTypeface, fontRenderingEmSize, new ReadOnlySlice <ushort>(glyphIndices), new ReadOnlySlice <double>(glyphAdvances), new ReadOnlySlice <Vector>(glyphOffsets), text, new ReadOnlySlice <ushort>(clusters))); } }
public Result Shape(Buffer buffer, SKPaint paint) => Shape(buffer, 0, 0, paint);