public GlyphRun ShapeText(ReadOnlySlice <char> text, TextFormat textFormat) { var glyphTypeface = textFormat.Typeface.GlyphTypeface; var glyphIndices = new ushort[text.Length]; var height = textFormat.FontMetrics.LineHeight; var width = 0.0; for (var i = 0; i < text.Length;) { var index = i; var codepoint = Codepoint.ReadAt(text, i, out var count); i += count; var glyph = glyphTypeface.GetGlyph(codepoint); glyphIndices[index] = glyph; width += glyphTypeface.GetGlyphAdvance(glyph); } return(new GlyphRun(glyphTypeface, textFormat.FontRenderingEmSize, glyphIndices, characters: text, bounds: new Rect(0, 0, width, height))); }
private int GetUnicodeEndpointDataSize(string codepoint) { var cp = new Codepoint(codepoint); var charBuf = Encoding.UTF8.GetBytes(cp.AsString() + "\n"); return(charBuf.Length); }
public void IsSurrogate_ShouldReturnTrue_IfCodepointIsSurrogate(int code, bool expected) { // Arrange var codepoint = Codepoint.Get(code); // Act var result = codepoint.IsSurrogate(); // Assert result.Should().Be(expected); }
public static Codepoint ParseSingleLetter(string letter) { if (letter[0] == '\\') { var handle = ParseEscape(letter) as RangeSetHandle.Static; if ((handle == null) || !handle.TryGetSingle(out var result)) { throw new InvalidOperationException("A single character was expected, but a character class was found"); } return(result); } return(Codepoint.FromChars(letter)); }
/// <summary> /// Creates a shapeable text run with unique properties. /// </summary> /// <param name="text">The text to create text runs from.</param> /// <param name="defaultProperties">The default text run properties.</param> /// <returns>A list of shapeable text runs.</returns> private ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice <char> text, TextRunProperties defaultProperties) { var defaultTypeface = defaultProperties.Typeface; var currentTypeface = defaultTypeface; if (TryGetRunProperties(text, currentTypeface, defaultTypeface, out var count)) { return(new ShapeableTextCharacters(text.Take(count), new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize, defaultProperties.TextDecorations, defaultProperties.ForegroundBrush))); } var codepoint = Codepoint.ReadAt(text, count, out _); //ToDo: Fix FontFamily fallback var matchFound = FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.FontFamily, defaultProperties.CultureInfo, out currentTypeface); if (matchFound && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count)) { //Fallback found return(new ShapeableTextCharacters(text.Take(count), new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize, defaultProperties.TextDecorations, defaultProperties.ForegroundBrush))); } // no fallback found currentTypeface = defaultTypeface; var glyphTypeface = currentTypeface.GlyphTypeface; var enumerator = new GraphemeEnumerator(text); while (enumerator.MoveNext()) { var grapheme = enumerator.Current; if (!grapheme.FirstCodepoint.IsWhiteSpace && glyphTypeface.TryGetGlyph(grapheme.FirstCodepoint, out _)) { break; } count += grapheme.Text.Length; } return(new ShapeableTextCharacters(text.Take(count), new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize, defaultProperties.TextDecorations, defaultProperties.ForegroundBrush))); }
/// <summary> /// Creates a text style run with unique properties. /// </summary> /// <param name="text">The text to create text runs from.</param> /// <param name="defaultStyle"></param> /// <returns>A list of text runs.</returns> protected TextStyleRun CreateShapableTextStyleRun(ReadOnlySlice <char> text, TextStyle defaultStyle) { var defaultTypeface = defaultStyle.TextFormat.Typeface; var currentTypeface = defaultTypeface; if (TryGetRunProperties(text, currentTypeface, defaultTypeface, out var count)) { return(new TextStyleRun(new TextPointer(text.Start, count), new TextStyle(currentTypeface, defaultStyle.TextFormat.FontRenderingEmSize, defaultStyle.Foreground, defaultStyle.TextDecorations))); } var codepoint = Codepoint.ReadAt(text, count, out _); //ToDo: Fix FontFamily fallback currentTypeface = FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Weight, defaultTypeface.Style); if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count)) { //Fallback found return(new TextStyleRun(new TextPointer(text.Start, count), new TextStyle(currentTypeface, defaultStyle.TextFormat.FontRenderingEmSize, defaultStyle.Foreground, defaultStyle.TextDecorations))); } // no fallback found currentTypeface = defaultTypeface; var glyphTypeface = currentTypeface.GlyphTypeface; var enumerator = new GraphemeEnumerator(text); while (enumerator.MoveNext()) { var grapheme = enumerator.Current; if (!grapheme.FirstCodepoint.IsWhiteSpace && glyphTypeface.TryGetGlyph(grapheme.FirstCodepoint, out _)) { break; } count += grapheme.Text.Length; } return(new TextStyleRun(new TextPointer(text.Start, count), new TextStyle(currentTypeface, defaultStyle.TextFormat.FontRenderingEmSize, defaultStyle.Foreground, defaultStyle.TextDecorations))); }
public static bool IsStartOfWord(string text, int index) { if (index >= text.Length) { return(false); } var codepoint = new Codepoint(text[index]); // A 'word' starts with an AlphaNumeric or some punctuation symbols immediately // preceeded by lwsp. if (index > 0) { var previousCodepoint = new Codepoint(text[index - 1]); if (!previousCodepoint.IsWhiteSpace) { return(false); } if (previousCodepoint.IsBreakChar) { return(true); } } switch (codepoint.GeneralCategory) { case GeneralCategory.LowercaseLetter: case GeneralCategory.TitlecaseLetter: case GeneralCategory.UppercaseLetter: case GeneralCategory.DecimalNumber: case GeneralCategory.LetterNumber: case GeneralCategory.OtherNumber: case GeneralCategory.DashPunctuation: case GeneralCategory.InitialPunctuation: case GeneralCategory.OpenPunctuation: case GeneralCategory.CurrencySymbol: case GeneralCategory.MathSymbol: return(true); // TODO: How do you do this in .NET? // case UnicodeCategory.OtherPunctuation: // // words cannot start with '.', but they can start with '&' or '*' (for example) // return g_unichar_break_type(buffer->text[index]) == G_UNICODE_BREAK_ALPHABETIC; default: return(false); } }
public static IEnumerable <Grapheme> FromCharsMany(IEnumerable <char> chars) { using (var enumerator = chars.GetEnumerator()) { if (!enumerator.MoveNext()) { yield break; } var builder = new StringBuilder(16); do { var ch = enumerator.Current; if (Codepoint.IsCombiningMark((int)ch)) { if (builder.Length == 0) { throw new ArgumentException("A grapheme must start with a non-combining character", nameof(value)); } builder.Append(ch); } else { if (builder.Length > 0) { yield return(new Grapheme(builder.ToString(), false)); builder.Clear(); } builder.Append(ch); if (char.IsHighSurrogate(ch)) { if (!enumerator.MoveNext() || !char.IsLowSurrogate(enumerator.Current)) { throw new ArgumentException("A low surrogate must always follow a high surrogate", nameof(value)); } builder.Append(enumerator.Current); } else if (char.IsLowSurrogate(ch)) { throw new ArgumentException("A low surrogate is not valid without a preceding high surrogate", nameof(value)); } } } while (enumerator.MoveNext()); if (builder.Length > 0) { yield return(new Grapheme(builder.ToString(), false)); } } }
public bool TryGetSingle(out Codepoint ch) { var set = this.Negate ? ~this.Charset : this.Charset; if (set.Count == 1) { var range = set[0]; if (range.From.CompareTo(range.To) == 0) { ch = range.From; return(true); } } ch = Codepoints.EOF; return(false); }
/// <summary> /// Tries to find the next character hit. /// </summary> /// <param name="characterHit">The current character hit.</param> /// <param name="nextCharacterHit">The next character hit.</param> /// <returns></returns> private bool TryFindNextCharacterHit(CharacterHit characterHit, out CharacterHit nextCharacterHit) { nextCharacterHit = characterHit; var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; if (codepointIndex > TextRange.End) { return(false); // Cannot go forward anymore } var runIndex = GetRunIndexAtCodepointIndex(codepointIndex); while (runIndex < TextRuns.Count) { var run = _textRuns[runIndex]; var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); var isAtEnd = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength == TextRange.Length; var characterIndex = codepointIndex - run.Text.Start; var codepoint = Codepoint.ReadAt(run.GlyphRun.Characters, characterIndex, out _); if (codepoint.IsBreakChar) { foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(codepointIndex - 1, out _); isAtEnd = true; } nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ? foundCharacterHit : new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength); if (isAtEnd || nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex) { return(true); } runIndex++; } return(false); }
public static RangeSet <Codepoint> CaseInsensitive(this RangeSet <Codepoint> input) { var result = input; foreach (var c in input.Expand()) { if (Codepoint.IsUpper(c)) { result |= Codepoint.ToLowerInvariant(c); } else if (Codepoint.IsLower(c)) { result |= Codepoint.ToUpperInvariant(c); } } return(result); }
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 ShapedBuffer ShapeText(ReadOnlySlice <char> text, GlyphTypeface typeface, double fontRenderingEmSize, CultureInfo culture, sbyte bidiLevel) { var shapedBuffer = new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel); for (var i = 0; i < shapedBuffer.Length;) { var glyphCluster = i + text.Start; var codepoint = Codepoint.ReadAt(text, i, out var count); var glyphIndex = typeface.GetGlyph(codepoint); shapedBuffer[i] = new GlyphInfo(glyphIndex, glyphCluster, 10); i += count; } return(shapedBuffer); }
/// <summary> /// Measures the number of characters that fit into available width. /// </summary> /// <param name="availableWidth">The available width.</param> /// <param name="length">The count of fitting characters.</param> /// <returns> /// <c>true</c> if characters fit into the available width; otherwise, <c>false</c>. /// </returns> internal bool TryMeasureCharacters(double availableWidth, out int length) { length = 0; var currentWidth = 0.0; for (var i = 0; i < ShapedBuffer.Length; i++) { var advance = ShapedBuffer.GlyphAdvances[i]; if (currentWidth + advance > availableWidth) { break; } Codepoint.ReadAt(GlyphRun.Characters, length, out var count); length += count; currentWidth += advance; } return(length > 0); }
internal bool TryMeasureCharactersBackwards(double availableWidth, out int length, out double width) { length = 0; width = 0; for (var i = ShapedBuffer.Length - 1; i >= 0; i--) { var advance = ShapedBuffer.GlyphAdvances[i]; if (width + advance > availableWidth) { break; } Codepoint.ReadAt(GlyphRun.Characters, length, out var count); length += count; width += advance; } return(length > 0); }
public override Errno OnReadHandle( string file, PathInfo info, byte[] buf, long offset, out int bytesRead) { Trace.WriteLine($"OnReadHandle {file} Flags={info.OpenFlags}"); string content = null; if (file.StartsWith($"/{UnicodePath}")) { var codepointString = file.Replace("/unicode/", ""); var cp = new Codepoint(codepointString); var codeUnit = cp.AsString() + "\n"; bytesRead = CopyBuf(codeUnit, buf, codeUnit.Length); return(0); } else if (file.StartsWith($"/{AlphaPath}")) { content = AlphaContent; } else if (file.StartsWith($"/{NumericPath}")) { content = NumericContent; } else { bytesRead = -1; return(0); } int toBeReadCount = buf.Length; bytesRead = CopyBuf(content, buf, toBeReadCount, (int)offset); return(0); }
public ShapedBuffer ShapeText(ReadOnlySlice <char> text, TextShaperOptions options) { var typeface = options.Typeface; var fontRenderingEmSize = options.FontRenderingEmSize; var bidiLevel = options.BidLevel; var shapedBuffer = new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel); for (var i = 0; i < shapedBuffer.Length;) { var glyphCluster = i + text.Start; var codepoint = Codepoint.ReadAt(text, i, out var count); var glyphIndex = typeface.GetGlyph(codepoint); shapedBuffer[i] = new GlyphInfo(glyphIndex, glyphCluster, 10); i += count; } return(shapedBuffer); }
internal static string ProcessAllPlanes([NotNull] this IEnumerable <char> that, Func <char, char> processChar, Func <Codepoint, Codepoint> processCodepoint) { if (that == null) { throw new ArgumentNullException(nameof(that)); } var result = new StringBuilder((that as string)?.Length ?? (that as ICollection <char>)?.Count ?? 128); using (var enumerator = that.GetEnumerator()) { while (enumerator.MoveNext()) { if (char.IsSurrogate(enumerator.Current)) { processCodepoint(Codepoint.FromCharEnumerator(enumerator)).AppendTo(result); } else { result.Append(processChar(enumerator.Current)); } } } return(result.ToString()); }
protected IEnumerable <Codepoint> GenerateCaseInsensitiveCodepoints(Codepoint codepoint) { if (codepoint.FitsIntoChar) { var orig = (char)codepoint; yield return(orig); var upper = char.ToUpperInvariant(orig); if (upper != orig) { yield return(upper); } var lower = char.ToLowerInvariant(orig); if (upper != lower) { yield return(lower); } } else { yield return(codepoint); } }
private Grapheme(string value, bool check) { if (check) { using (var enumerator = value.GetEnumerator()) { if (!enumerator.MoveNext()) { throw new ArgumentException("An empty string is not a valid grapheme", nameof(value)); } if (Codepoint.IsCombiningMark(Codepoint.FromCharEnumerator(enumerator))) { throw new ArgumentException("A grapheme cannot start with a combining character", nameof(value)); } while (enumerator.MoveNext()) { if (!Codepoint.IsCombiningMark(Codepoint.FromCharEnumerator(enumerator))) { throw new ArgumentException("A grapheme cannot contain more than one non-combining character", nameof(value)); } } } } this.value = value; }
public GlyphRun ShapeText(ReadOnlySlice <char> text, Typeface typeface, double fontRenderingEmSize, CultureInfo culture) { var glyphTypeface = typeface.GlyphTypeface; var glyphIndices = new ushort[text.Length]; var glyphCount = 0; for (var i = 0; i < text.Length;) { var index = i; var codepoint = Codepoint.ReadAt(text, i, out var count); i += count; var glyph = glyphTypeface.GetGlyph(codepoint); glyphIndices[index] = glyph; glyphCount++; } return(new GlyphRun(glyphTypeface, fontRenderingEmSize, new ReadOnlySlice <ushort>(glyphIndices.AsMemory(0, glyphCount)), characters: text)); }
public static RangeSetHandle ParseEscape(string escape) { var match = rxEscape.Match(escape); if (!match.Success) { throw new ArgumentException("Escape is invalid", "escape"); } if (match.Groups["name"].Success) { return(new RangeSetHandle.Static(UnicodeRanges.FromUnicodeName(match.Groups["name"].Value), match.Groups["c"].Value == "P")); } if (match.Groups["hex"].Success) { return(new RangeSetHandle.Static(Codepoint.Parse(match.Groups["hex"].Value))); } var c = match.Groups["c"].Value[0]; switch (c) { case '0': return(new RangeSetHandle.Static('\0')); case 'r': return(new RangeSetHandle.Static('\r')); case 'n': return(new RangeSetHandle.Static('\n')); case 't': return(new RangeSetHandle.Static('\t')); case 'a': return(new RangeSetHandle.Static('\x07')); case 'e': return(new RangeSetHandle.Static('\x1B')); case 'f': return(new RangeSetHandle.Static('\x0C')); case 'v': return(new RangeSetHandle.Static('\x0B')); case 'd': return(new RangeSetHandle.Class(CharSetClass.Digit, false)); case 'D': return(new RangeSetHandle.Class(CharSetClass.Digit, true)); case 'w': return(new RangeSetHandle.Class(CharSetClass.Word, false)); case 'W': return(new RangeSetHandle.Class(CharSetClass.Word, true)); case 's': return(new RangeSetHandle.Class(CharSetClass.Space, false)); case 'S': return(new RangeSetHandle.Class(CharSetClass.Space, true)); default: if (char.IsLetterOrDigit(c)) { throw new ArgumentOutOfRangeException(nameof(escape), "Invalid escape character " + c); } return(new RangeSetHandle.Static(c)); } }
public void OnSkinToneSelected(Codepoint skinTone) { MODEL.OnSkinToneSelected(skinTone); }
/// <summary> /// Coalesces ranges of the same bidi level to form <see cref="ShapeableTextCharacters"/> /// </summary> /// <param name="textCharacters">The text characters to form <see cref="ShapeableTextCharacters"/> from.</param> /// <param name="levels">The bidi levels.</param> /// <returns></returns> private static IEnumerable <IReadOnlyList <TextRun> > CoalesceLevels( IReadOnlyList <TextRun> textCharacters, ReadOnlySlice <sbyte> levels) { if (levels.Length == 0) { yield break; } var levelIndex = 0; var runLevel = levels[0]; TextRunProperties?previousProperties = null; TextCharacters? currentRun = null; var runText = ReadOnlySlice <char> .Empty; for (var i = 0; i < textCharacters.Count; i++) { var j = 0; currentRun = textCharacters[i] as TextCharacters; if (currentRun == null) { var drawableRun = textCharacters[i]; yield return(new[] { drawableRun }); levelIndex += drawableRun.TextSourceLength; continue; } runText = currentRun.Text; for (; j < runText.Length;) { Codepoint.ReadAt(runText, j, out var count); if (levelIndex + 1 == levels.Length) { break; } levelIndex++; j += count; if (j == runText.Length) { yield return(currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties)); runLevel = levels[levelIndex]; continue; } if (levels[levelIndex] == runLevel) { continue; } // End of this run yield return(currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties)); runText = runText.Skip(j); j = 0; // Move to next run runLevel = levels[levelIndex]; } } if (currentRun is null || runText.IsEmpty) { yield break; } yield return(currentRun.GetShapeableCharacters(runText, runLevel, ref previousProperties)); }
public static StringBuilder Append(this StringBuilder that, Codepoint codepoint) { codepoint.AppendTo(that); return(that); }
public static IEnumerable <Codepoint> ToCodepoints(this IEnumerable <char> that) { return(Codepoint.FromCharsMany(that)); }
public Static(Codepoint ch) : this(new RangeSet <Codepoint>(ch), false) { }
public Static(Codepoint from, Codepoint to) : this(new RangeSet <Codepoint>(Range <Codepoint> .Create(from, to)), false) { }
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 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))); } }