示例#1
0
        public static void SplitKey(Slice key, out string docId, out string name)
        {
            var bytes          = key.AsSpan();
            var separatorIndex = key.Content.IndexOf(SpecialChars.RecordSeparator);

            docId = Encoding.UTF8.GetString(bytes.Slice(0, separatorIndex));
            var index = separatorIndex + 1;

            name = Encoding.UTF8.GetString(bytes.Slice(index, bytes.Length - index));
        }
示例#2
0
        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,
                });
            }
        }
示例#3
0
 string FromSlice(Slice <char> chars)
 {
     return(new string(chars.AsSpan()));
 }
示例#4
0
        /// <summary>
        /// Paint this font run
        /// </summary>
        /// <param name="ctx"></param>
        internal void Paint(PaintTextContext ctx)
        {
            // Paint selection?
            if (ctx.PaintSelectionBackground != null && RunKind != FontRunKind.Ellipsis)
            {
                float selStartXCoord;
                if (ctx.SelectionStart < Start)
                {
                    selStartXCoord = Direction == TextDirection.LTR ? 0 : Width;
                }
                else if (ctx.SelectionStart >= End)
                {
                    selStartXCoord = Direction == TextDirection.LTR ? Width : 0;
                }
                else
                {
                    selStartXCoord = RelativeCodePointXCoords[ctx.SelectionStart - this.Start];
                }

                float selEndXCoord;
                if (ctx.SelectionEnd < Start)
                {
                    selEndXCoord = Direction == TextDirection.LTR ? 0 : Width;
                }
                else if (ctx.SelectionEnd >= End)
                {
                    selEndXCoord = Direction == TextDirection.LTR ? Width : 0;
                }
                else
                {
                    selEndXCoord = RelativeCodePointXCoords[ctx.SelectionEnd - this.Start];
                }

                if (selStartXCoord != selEndXCoord)
                {
                    var tl = new SKPoint(selStartXCoord + this.XCoord, Line.YCoord);
                    var br = new SKPoint(selEndXCoord + this.XCoord, Line.YCoord + Line.Height);

                    // Align coords to pixel boundaries
                    // Not needed - disabled antialias on SKPaint instead

                    /*
                     * if (ctx.Canvas.TotalMatrix.TryInvert(out var inverse))
                     * {
                     *  tl = ctx.Canvas.TotalMatrix.MapPoint(tl);
                     *  br = ctx.Canvas.TotalMatrix.MapPoint(br);
                     *  tl = new SKPoint((float)Math.Round(tl.X), (float)Math.Round(tl.Y));
                     *  br = new SKPoint((float)Math.Round(br.X), (float)Math.Round(br.Y));
                     *  tl = inverse.MapPoint(tl);
                     *  br = inverse.MapPoint(br);
                     * }
                     */

                    var rect = new SKRect(tl.X, tl.Y, br.X, br.Y);
                    ctx.Canvas.DrawRect(rect, ctx.PaintSelectionBackground);
                }
            }

            // Don't paint trailing whitespace runs
            if (RunKind == FontRunKind.TrailingWhitespace)
            {
                return;
            }

            // Text
            using (var paint = new SKPaint())
            {
                // Work out font variant adjustments
                float glyphScale   = 1;
                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;
                }

                // Setup SKPaint
                paint.Color         = Style.TextColor;
                paint.IsAntialias   = ctx.Options.IsAntialias;
                paint.LcdRenderText = ctx.Options.LcdRenderText;

                unsafe
                {
                    fixed(ushort *pGlyphs = Glyphs.Underlying)
                    {
                        // Get glyph positions
                        var glyphPositions = GlyphPositions.ToArray();

                        // Create the font
                        if (_font == null)
                        {
                            _font          = new SKFont(this.Typeface, this.Style.FontSize * glyphScale);
                            _font.Subpixel = true;
                        }

                        // Create the SKTextBlob (if necessary)
                        if (_textBlob == null)
                        {
                            _textBlob = SKTextBlob.CreatePositioned(
                                (IntPtr)(pGlyphs + Glyphs.Start),
                                Glyphs.Length * sizeof(ushort),
                                SKTextEncoding.GlyphId,
                                _font,
                                GlyphPositions.AsSpan());
                        }

                        // Paint underline
                        if (Style.Underline != UnderlineStyle.None && RunKind == FontRunKind.Normal)
                        {
                            // Work out underline metrics
                            float underlineYPos = Line.YCoord + Line.BaseLine + (_font.Metrics.UnderlinePosition ?? 0);
                            paint.StrokeWidth = _font.Metrics.UnderlineThickness ?? 1;

                            if (Style.Underline == UnderlineStyle.Gapped)
                            {
                                // Get intercept positions
                                var interceptPositions = _textBlob.GetIntercepts(underlineYPos - paint.StrokeWidth / 2, underlineYPos + paint.StrokeWidth);

                                // Paint gapped underlinline
                                float x = XCoord;
                                for (int i = 0; i < interceptPositions.Length; i += 2)
                                {
                                    float b = interceptPositions[i] - paint.StrokeWidth;
                                    if (x < b)
                                    {
                                        ctx.Canvas.DrawLine(new SKPoint(x, underlineYPos), new SKPoint(b, underlineYPos), paint);
                                    }
                                    x = interceptPositions[i + 1] + paint.StrokeWidth;
                                }
                                if (x < XCoord + Width)
                                {
                                    ctx.Canvas.DrawLine(new SKPoint(x, underlineYPos), new SKPoint(XCoord + Width, underlineYPos), paint);
                                }
                            }
                            else
                            {
                                switch (Style.Underline)
                                {
                                case UnderlineStyle.ImeInput:
                                    paint.PathEffect = SKPathEffect.CreateDash(new float[] { paint.StrokeWidth, paint.StrokeWidth }, paint.StrokeWidth);
                                    break;

                                case UnderlineStyle.ImeConverted:
                                    paint.PathEffect = SKPathEffect.CreateDash(new float[] { paint.StrokeWidth, paint.StrokeWidth }, paint.StrokeWidth);
                                    break;

                                case UnderlineStyle.ImeTargetConverted:
                                    paint.StrokeWidth *= 2;
                                    break;

                                case UnderlineStyle.ImeTargetNonConverted:
                                    break;
                                }
                                // Paint solid underline
                                ctx.Canvas.DrawLine(new SKPoint(XCoord, underlineYPos), new SKPoint(XCoord + Width, underlineYPos), paint);
                                paint.PathEffect = null;
                            }
                        }


                        ctx.Canvas.DrawText(_textBlob, 0, 0, paint);
                    }
                }

                // Paint strikethrough
                if (Style.StrikeThrough != StrikeThroughStyle.None && RunKind == FontRunKind.Normal)
                {
                    paint.StrokeWidth = _font.Metrics.StrikeoutThickness ?? 0;
                    float strikeYPos = Line.YCoord + Line.BaseLine + (_font.Metrics.StrikeoutPosition ?? 0) + glyphVOffset;
                    ctx.Canvas.DrawLine(new SKPoint(XCoord, strikeYPos), new SKPoint(XCoord + Width, strikeYPos), paint);
                }
            }
        }
示例#5
0
        /// <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(codePoints.AsSpan(), 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;

                    // Ensure paragraph separator character (0x2029) has some
                    // width so it can be seen as part of the selection in the editor.
                    if (pos.XAdvance == 0 && codePoints[(int)gi[i].Cluster] == 0x2029)
                    {
                        cursorX += style.FontSize * 2 / 3;
                    }

                    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);
            }
        }
示例#6
0
        /// <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,
                });
            }
        }