public void UnicodeGlyphsReturnsTheCorrectNumberOfCharacters() { const string text = "🚀"; var emojiChar = StringUtilities.GetUnicodeCharacterCode(text, SKTextEncoding.Utf32); var typeface = SKFontManager.Default.MatchCharacter(emojiChar); Assert.NotNull(typeface); using var font = new SKFont(); font.Typeface = typeface; Assert.Equal(1, font.CountGlyphs(text)); Assert.Single(font.GetGlyphs(text)); Assert.NotEqual(0, font.GetGlyphs(text)[0]); }
public void PlainGlyphsReturnsTheCorrectNumberOfCharacters() { const string text = "Hello World!"; var font = new SKFont(); Assert.Equal(text.Length, font.CountGlyphs(text)); Assert.Equal(text.Length, font.GetGlyphs(text).Length); }
public void MeasureTextMeasuresTheTextForGlyphs() { var font = new SKFont(); var expectedWidth = font.MeasureText("Hello World!"); var glyphs = font.GetGlyphs("Hello World!"); var width = font.MeasureText(glyphs); Assert.Equal(expectedWidth, width); }
public void MeasureTextReturnsTheBoundsForGlyphs() { var font = new SKFont(); var expectedWidth = font.MeasureText("Hello World!", out var expectedBounds); var glyphs = font.GetGlyphs("Hello World!"); var width = font.MeasureText(glyphs, out var bounds); Assert.Equal(expectedWidth, width); Assert.Equal(expectedBounds, bounds); }
public unsafe void UnicharCountReturnsCorrectCount() { var text = new uint[] { 79 }; var count = text.Length * sizeof(uint); using var font = new SKFont(); fixed(uint *t = text) { Assert.Equal(1, font.CountGlyphs((IntPtr)t, count, SKTextEncoding.Utf32)); var glyphs = font.GetGlyphs((IntPtr)t, count, SKTextEncoding.Utf32); Assert.Single(glyphs); } }
public static IEnumerable <Run> GetFontRuns(Slice <int> codePoints, SKTypeface typeface) { // Get the font manager - we'll use this to select font fallbacks var fontManager = SKFontManager.Default; // Get glyphs using the top-level typeface var glyphs = new ushort[codePoints.Length]; var font = new SKFont(typeface); font.GetGlyphs(codePoints.AsSpan(), glyphs); // Look for subspans that need font fallback (where glyphs are zero) int runStart = 0; for (int i = 0; i < codePoints.Length; i++) { // Do we need fallback for this character? if (glyphs[i] == 0) { // Check if there's a fallback available, if not, might as well continue with the current top-level typeface var subSpanTypeface = fontManager.MatchCharacter(typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, codePoints[i]); if (subSpanTypeface == null) { continue; } // We can do font fallback... // Flush the current top-level run if (i > runStart) { yield return(new Run() { Start = runStart, Length = i - runStart, Typeface = typeface, }); } // Count how many unmatched characters var unmatchedStart = i; var unmatchedEnd = i + 1; while (unmatchedEnd < codePoints.Length && glyphs[unmatchedEnd] == 0) { unmatchedEnd++; } var unmatchedLength = unmatchedEnd - unmatchedStart; // Match the missing characters while (unmatchedLength > 0) { // Find the font fallback using the first character subSpanTypeface = fontManager.MatchCharacter(typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, codePoints[unmatchedStart]); if (subSpanTypeface == null) { unmatchedEnd = unmatchedStart; break; } var subSpanFont = new SKFont(subSpanTypeface); // Get the glyphs over the current unmatched range subSpanFont.GetGlyphs(codePoints.SubSlice(unmatchedStart, unmatchedLength).AsSpan(), new Span <ushort>(glyphs, unmatchedStart, unmatchedLength)); // Count how many characters were matched var fallbackStart = unmatchedStart; var fallbackEnd = unmatchedStart + 1; while (fallbackEnd < unmatchedEnd && glyphs[fallbackEnd] != 0) { fallbackEnd++; } var fallbackLength = fallbackEnd - fallbackStart; // Yield this font fallback run yield return(new Run() { Start = fallbackStart, Length = fallbackLength, Typeface = subSpanTypeface, }); // Continue selecting font fallbacks until the entire unmatched ranges has been matched unmatchedStart += fallbackLength; unmatchedLength -= fallbackLength; } // Move onto the next top level span i = unmatchedEnd - 1; // account for i++ on for loop runStart = unmatchedEnd; } } // Flush find run if (codePoints.Length > runStart) { yield return(new Run() { Start = runStart, Length = codePoints.Length - runStart, Typeface = typeface, }); } }
/// <summary> /// Splits a sequence of code points into a series of runs with font fallback applied /// </summary> /// <param name="codePoints">The code points</param> /// <param name="typeface">The preferred typeface</param> /// <param name="replacementCharacter">The replacement character to be used for the run</param> /// <returns>A sequence of runs with unsupported code points replaced by a selected font fallback</returns> public static IEnumerable <Run> GetFontRuns(Slice <int> codePoints, SKTypeface typeface, char replacementCharacter = '\0') { var font = new SKFont(typeface); if (replacementCharacter != '\0') { var glyph = font.GetGlyph(replacementCharacter); if (glyph == 0) { var fallbackTypeface = CharacterMatcher.MatchCharacter(typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, replacementCharacter); if (fallbackTypeface != null) { typeface = fallbackTypeface; } } yield return(new Run() { Start = 0, Length = codePoints.Length, Typeface = typeface, }); yield break; } // Get glyphs using the top-level typeface var glyphs = new ushort[codePoints.Length]; font.GetGlyphs(codePoints.AsSpan(), glyphs); // Look for subspans that need font fallback (where glyphs are zero) int runStart = 0; for (int i = 0; i < codePoints.Length; i++) { // Do we need fallback for this character? if (glyphs[i] == 0) { // Check if there's a fallback available, if not, might as well continue with the current top-level typeface var subSpanTypeface = CharacterMatcher.MatchCharacter(typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, codePoints[i]); if (subSpanTypeface == null) { continue; } // Don't fallback for whitespace characters if (UnicodeClasses.BoundaryGroup(codePoints[i]) == WordBoundaryClass.Space) { continue; } // Must be a cluster boundary if (!GraphemeClusterAlgorithm.IsBoundary(codePoints, i)) { continue; } // We can do font fallback... // Flush the current top-level run if (i > runStart) { yield return(new Run() { Start = runStart, Length = i - runStart, Typeface = typeface, }); } // Count how many unmatched characters var unmatchedStart = i; var unmatchedEnd = i + 1; while (unmatchedEnd < codePoints.Length && (glyphs[unmatchedEnd] == 0 || !GraphemeClusterAlgorithm.IsBoundary(codePoints, unmatchedEnd))) { unmatchedEnd++; } var unmatchedLength = unmatchedEnd - unmatchedStart; // Match the missing characters while (unmatchedLength > 0) { // Find the font fallback using the first character subSpanTypeface = CharacterMatcher.MatchCharacter(typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, codePoints[unmatchedStart]); if (subSpanTypeface == null) { unmatchedEnd = unmatchedStart; break; } var subSpanFont = new SKFont(subSpanTypeface); // Get the glyphs over the current unmatched range subSpanFont.GetGlyphs(codePoints.SubSlice(unmatchedStart, unmatchedLength).AsSpan(), new Span <ushort>(glyphs, unmatchedStart, unmatchedLength)); // Count how many characters were matched var fallbackStart = unmatchedStart; var fallbackEnd = unmatchedStart + 1; while (fallbackEnd < unmatchedEnd && glyphs[fallbackEnd] != 0) { fallbackEnd++; } var fallbackLength = fallbackEnd - fallbackStart; // Yield this font fallback run yield return(new Run() { Start = fallbackStart, Length = fallbackLength, Typeface = subSpanTypeface, }); // Continue selecting font fallbacks until the entire unmatched ranges has been matched unmatchedStart += fallbackLength; unmatchedLength -= fallbackLength; } // Move onto the next top level span i = unmatchedEnd - 1; // account for i++ on for loop runStart = unmatchedEnd; } } // Flush find run if (codePoints.Length > runStart) { yield return(new Run() { Start = runStart, Length = codePoints.Length - runStart, Typeface = typeface, }); } }