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))); } }
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 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 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 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 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))); } }
/// <summary> /// Shape an array of utf-32 code points /// </summary> /// <param name="bufferSet">A re-usable text shaping buffer set that results will be allocated from</param> /// <param name="codePoints">The utf-32 code points to be shaped</param> /// <param name="style">The user style for the text</param> /// <param name="direction">LTR or RTL direction</param> /// <param name="clusterAdjustment">A value to add to all reported cluster numbers</param> /// <param name="asFallbackFor">The type face this font is a fallback for</param> /// <param name="textAlignment">The text alignment of the paragraph, used to control placement of glyphs within character cell when letter spacing used</param> /// <returns>A TextShaper.Result representing the shaped text</returns> public Result Shape(ResultBufferSet bufferSet, Slice <int> codePoints, IStyle style, TextDirection direction, int clusterAdjustment, SKTypeface asFallbackFor, TextAlignment textAlignment) { // Work out if we need to force this to a fixed pitch and if // so the unscale character width we need to use float forceFixedPitchWidth = 0; if (asFallbackFor != _typeface && asFallbackFor != null) { var originalTypefaceShaper = ForTypeface(asFallbackFor); if (originalTypefaceShaper._isFixedPitch) { forceFixedPitchWidth = originalTypefaceShaper._fixedCharacterWidth; } } // Work out how much to shift glyphs in the character cell when using letter spacing // The idea here is to align the glyphs within the character cell the same way as the // text block alignment so that left/right aligned text still aligns with the margin // and centered text is still centered (and not shifted slightly due to the extra // space that would be at the right with normal letter spacing). float glyphLetterSpacingAdjustment = 0; switch (textAlignment) { case TextAlignment.Right: glyphLetterSpacingAdjustment = style.LetterSpacing; break; case TextAlignment.Center: glyphLetterSpacingAdjustment = style.LetterSpacing / 2; break; } using (var buffer = new HarfBuzzSharp.Buffer()) { // Setup buffer buffer.AddUtf32(new ReadOnlySpan <int>(codePoints.Underlying, codePoints.Start, codePoints.Length), 0, -1); // Setup directionality (if supplied) switch (direction) { case TextDirection.LTR: buffer.Direction = Direction.LeftToRight; break; case TextDirection.RTL: buffer.Direction = Direction.RightToLeft; break; default: throw new ArgumentException(nameof(direction)); } // Guess other attributes buffer.GuessSegmentProperties(); // Shape it _font.Shape(buffer); // RTL? bool rtl = buffer.Direction == Direction.RightToLeft; // Work out glyph scaling and offsetting for super/subscript float glyphScale = style.FontSize / overScale; float glyphVOffset = 0; if (style.FontVariant == FontVariant.SuperScript) { glyphScale *= 0.65f; glyphVOffset -= style.FontSize * 0.35f; } if (style.FontVariant == FontVariant.SubScript) { glyphScale *= 0.65f; glyphVOffset += style.FontSize * 0.1f; } // Create results and get buffes var r = new Result(); r.GlyphIndicies = bufferSet.GlyphIndicies.Add((int)buffer.Length, false); r.GlyphPositions = bufferSet.GlyphPositions.Add((int)buffer.Length, false); r.Clusters = bufferSet.Clusters.Add((int)buffer.Length, false); r.CodePointXCoords = bufferSet.CodePointXCoords.Add(codePoints.Length, false); r.CodePointXCoords.Fill(0); // Convert points var gp = buffer.GlyphPositions; var gi = buffer.GlyphInfos; float cursorX = 0; float cursorY = 0; float cursorXCluster = 0; for (int i = 0; i < buffer.Length; i++) { r.GlyphIndicies[i] = (ushort)gi[i].Codepoint; r.Clusters[i] = (int)gi[i].Cluster + clusterAdjustment; // Update code point positions if (!rtl) { // First cluster, different cluster, or same cluster with lower x-coord if (i == 0 || (r.Clusters[i] != r.Clusters[i - 1]) || (cursorX < r.CodePointXCoords[r.Clusters[i] - clusterAdjustment])) { r.CodePointXCoords[r.Clusters[i] - clusterAdjustment] = cursorX; } } // Get the position var pos = gp[i]; // Update glyph position r.GlyphPositions[i] = new SKPoint( cursorX + pos.XOffset * glyphScale + glyphLetterSpacingAdjustment, cursorY - pos.YOffset * glyphScale + glyphVOffset ); // Update cursor position cursorX += pos.XAdvance * glyphScale; cursorY += pos.YAdvance * glyphScale; if (i + 1 == gi.Length || gi[i].Cluster != gi[i + 1].Cluster) { cursorX += style.LetterSpacing; } // Are we falling back for a fixed pitch font and is the next character a // new cluster? If so advance by the width of the original font, not this // fallback font if (forceFixedPitchWidth != 0) { // New cluster? if (i + 1 >= buffer.Length || gi[i].Cluster != gi[i + 1].Cluster) { // Work out fixed pitch position of next cluster cursorXCluster += forceFixedPitchWidth * glyphScale; if (cursorXCluster > cursorX) { // Nudge characters to center them in the fixed pitch width if (i == 0 || gi[i - 1].Cluster != gi[i].Cluster) { r.GlyphPositions[i].X += (cursorXCluster - cursorX) / 2; } // Use fixed width character position cursorX = cursorXCluster; } else { // Character is wider (probably an emoji) so we // allow it to exceed the fixed pitch character width cursorXCluster = cursorX; } } } // Store RTL cursor position if (rtl) { // First cluster, different cluster, or same cluster with lower x-coord if (i == 0 || (r.Clusters[i] != r.Clusters[i - 1]) || (cursorX > r.CodePointXCoords[r.Clusters[i] - clusterAdjustment])) { r.CodePointXCoords[r.Clusters[i] - clusterAdjustment] = cursorX; } } } // Finalize cursor positions by filling in any that weren't // referenced by a cluster if (rtl) { r.CodePointXCoords[0] = cursorX; for (int i = codePoints.Length - 2; i >= 0; i--) { if (r.CodePointXCoords[i] == 0) { r.CodePointXCoords[i] = r.CodePointXCoords[i + 1]; } } } else { for (int i = 1; i < codePoints.Length; i++) { if (r.CodePointXCoords[i] == 0) { r.CodePointXCoords[i] = r.CodePointXCoords[i - 1]; } } } // Also return the end cursor position r.EndXCoord = new SKPoint(cursorX, cursorY); // And some other useful metrics r.Ascent = _fontMetrics.Ascent * style.FontSize / overScale; r.Descent = _fontMetrics.Descent * style.FontSize / overScale; r.XMin = _fontMetrics.XMin * style.FontSize / overScale; // Done return(r); } }
/// <summary> /// Shape an array of utf-32 code points /// </summary> /// <param name="bufferSet">A re-usable text shaping buffer set that results will be allocated from</param> /// <param name="codePoints">The utf-32 code points to be shaped</param> /// <param name="style">The user style for the text</param> /// <param name="direction">LTR or RTL direction</param> /// <param name="clusterAdjustment">A value to add to all reported cluster numbers</param> /// <returns>A TextShaper.Result representing the shaped text</returns> public Result Shape(ResultBufferSet bufferSet, Slice <int> codePoints, IStyle style, TextDirection direction, int clusterAdjustment = 0) { using (var buffer = new HarfBuzzSharp.Buffer()) { // Setup buffer unsafe { fixed(int *pCodePoints = codePoints.Underlying) { hb_buffer_add_utf32(buffer.Handle, (IntPtr)(pCodePoints + codePoints.Start), codePoints.Length, 0, -1); } } // Setup directionality (if supplied) switch (direction) { case TextDirection.LTR: buffer.Direction = Direction.LeftToRight; break; case TextDirection.RTL: buffer.Direction = Direction.RightToLeft; break; default: throw new ArgumentException(nameof(direction)); } // Guess other attributes buffer.GuessSegmentProperties(); // Shape it _font.Shape(buffer); // RTL? bool rtl = buffer.Direction == Direction.RightToLeft; // Work out glyph scaling and offsetting for super/subscript float glyphScale = style.FontSize / overScale; float glyphVOffset = 0; if (style.FontVariant == FontVariant.SuperScript) { glyphScale *= 0.65f; glyphVOffset -= style.FontSize * 0.35f; } if (style.FontVariant == FontVariant.SubScript) { glyphScale *= 0.65f; glyphVOffset += style.FontSize * 0.1f; } // Create results and get buffes var r = new Result(); r.GlyphIndicies = bufferSet.GlyphIndicies.Add((int)buffer.Length, false); r.GlyphPositions = bufferSet.GlyphPositions.Add((int)buffer.Length, false); r.Clusters = bufferSet.Clusters.Add((int)buffer.Length, false); r.CodePointXCoords = bufferSet.CodePointXCoords.Add(codePoints.Length, false); // Convert points var gp = buffer.GlyphPositions; var gi = buffer.GlyphInfos; float cursorX = 0; float cursorY = 0; for (int i = 0; i < buffer.Length; i++) { r.GlyphIndicies[i] = (ushort)gi[i].Codepoint; r.Clusters[i] = (int)gi[i].Cluster + clusterAdjustment; // Update code point positions if (!rtl) { // First cluster, different cluster, or same cluster with lower x-coord if (i == 0 || (r.Clusters[i] != r.Clusters[i - 1]) || (cursorX < r.CodePointXCoords[r.Clusters[i] - clusterAdjustment])) { r.CodePointXCoords[r.Clusters[i] - clusterAdjustment] = cursorX; } } // Get the position var pos = gp[i]; // Update glyph position r.GlyphPositions[i] = new SKPoint( cursorX + pos.XOffset * glyphScale, cursorY - pos.YOffset * glyphScale + glyphVOffset ); // Update cursor position cursorX += pos.XAdvance * glyphScale; cursorY += pos.YAdvance * glyphScale; // Store RTL cursor position if (rtl) { // First cluster, different cluster, or same cluster with lower x-coord if (i == 0 || (r.Clusters[i] != r.Clusters[i - 1]) || (cursorX > r.CodePointXCoords[r.Clusters[i] - clusterAdjustment])) { r.CodePointXCoords[r.Clusters[i] - clusterAdjustment] = cursorX; } } } // Finalize cursor positions by filling in any that weren't // referenced by a cluster if (rtl) { r.CodePointXCoords[0] = cursorX; for (int i = codePoints.Length - 2; i >= 0; i--) { if (r.CodePointXCoords[i] == 0) { r.CodePointXCoords[i] = r.CodePointXCoords[i + 1]; } } } else { for (int i = 1; i < codePoints.Length; i++) { if (r.CodePointXCoords[i] == 0) { r.CodePointXCoords[i] = r.CodePointXCoords[i - 1]; } } } // Also return the end cursor position r.EndXCoord = new SKPoint(cursorX, cursorY); // And some other useful metrics r.Ascent = _fontMetrics.Ascent * style.FontSize / overScale; r.Descent = _fontMetrics.Descent * style.FontSize / overScale; r.XMin = _fontMetrics.XMin * style.FontSize / overScale; // Done return(r); } }