Пример #1
0
        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)));
        }
Пример #2
0
        private int GetUnicodeEndpointDataSize(string codepoint)
        {
            var cp      = new Codepoint(codepoint);
            var charBuf = Encoding.UTF8.GetBytes(cp.AsString() + "\n");

            return(charBuf.Length);
        }
Пример #3
0
        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));
 }
Пример #5
0
        /// <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)));
        }
Пример #6
0
        /// <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)));
        }
Пример #7
0
        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);
            }
        }
Пример #8
0
        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));
                }
            }
        }
Пример #9
0
            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);
            }
Пример #10
0
        /// <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);
        }
Пример #11
0
        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);
        }
Пример #12
0
        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;
            }
        }
Пример #13
0
        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);
        }
Пример #14
0
        /// <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);
        }
Пример #15
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);
        }
Пример #16
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);
        }
Пример #17
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);
        }
Пример #18
0
        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);
            }
        }
Пример #20
0
 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));
            }
        }
Пример #23
0
 public void OnSkinToneSelected(Codepoint skinTone)
 {
     MODEL.OnSkinToneSelected(skinTone);
 }
Пример #24
0
        /// <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));
        }
Пример #25
0
 public static StringBuilder Append(this StringBuilder that, Codepoint codepoint)
 {
     codepoint.AppendTo(that);
     return(that);
 }
Пример #26
0
 public static IEnumerable <Codepoint> ToCodepoints(this IEnumerable <char> that)
 {
     return(Codepoint.FromCharsMany(that));
 }
Пример #27
0
 public Static(Codepoint ch) : this(new RangeSet <Codepoint>(ch), false)
 {
 }
Пример #28
0
 public Static(Codepoint from, Codepoint to) : this(new RangeSet <Codepoint>(Range <Codepoint> .Create(from, to)), false)
 {
 }
Пример #29
0
        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)));
            }
        }
Пример #30
0
        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)));
            }
        }