123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844 |
- #define USE_SKTEXTBLOB
- // RichTextKit
- // Copyright © 2019-2020 Topten Software. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License"); you may
- // not use this product except in compliance with the License. You may obtain
- // a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- // License for the specific language governing permissions and limitations
- // under the License.
- // Modifications by Alexander Tsyganeko: marked by ATZ
- using HarfBuzzSharp;
- using SkiaSharp;
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using Topten.RichTextKit.Utils;
- namespace Topten.RichTextKit
- {
- /// <summary>
- /// Represents a font run - a physical sequence of laid glyphs all with
- /// the same font and style attributes.
- /// </summary>
- public class FontRun
- {
- /// <summary>
- /// The kind of font run.
- /// </summary>
- public FontRunKind RunKind = FontRunKind.Normal;
- /// <summary>
- /// The style run this typeface run was derived from.
- /// </summary>
- public StyleRun StyleRun;
- /// <summary>
- /// Get the code points of this run
- /// </summary>
- public Slice<int> CodePoints => CodePointBuffer.SubSlice(Start, Length);
- /// <summary>
- /// Code point index of the start of this run
- /// </summary>
- public int Start;
- /// <summary>
- /// The length of this run in codepoints
- /// </summary>
- public int Length;
- /// <summary>
- /// The index of the first character after this run
- /// </summary>
- public int End => Start + Length;
- /// <summary>
- /// The user supplied style for this run
- /// </summary>
- public IStyle Style;
- /// <summary>
- /// The direction of this run
- /// </summary>
- public TextDirection Direction;
- /// <summary>
- /// The typeface of this run (use this over Style.Fontface)
- /// </summary>
- public SKTypeface Typeface;
- /// <summary>
- /// The glyph indicies
- /// </summary>
- public Slice<ushort> Glyphs;
- /// <summary>
- /// The glyph positions (relative to the entire text block)
- /// </summary>
- public Slice<SKPoint> GlyphPositions;
- /// <summary>
- /// The cluster numbers for each glyph
- /// </summary>
- public Slice<int> Clusters;
- /// <summary>
- /// The x-coords of each code point, relative to this text run
- /// </summary>
- public Slice<float> RelativeCodePointXCoords;
- /// <summary>
- /// Get the x-coord of a code point
- /// </summary>
- /// <remarks>
- /// For LTR runs this will be the x-coordinate to the left, or RTL
- /// runs it will be the x-coordinate to the right.
- /// </remarks>
- /// <param name="codePointIndex">The code point index (relative to the entire text block)</param>
- /// <returns>The x-coord relative to the entire text block</returns>
- public float GetXCoordOfCodePointIndex(int codePointIndex)
- {
- if (this.RunKind == FontRunKind.Ellipsis)
- codePointIndex = 0;
- // Check in range
- if (codePointIndex < Start || codePointIndex > End)
- throw new ArgumentOutOfRangeException(nameof(codePointIndex));
- // End of run?
- if (codePointIndex == End)
- return XCoord + (Direction == TextDirection.LTR ? Width : 0);
- // Lookup
- return XCoord + RelativeCodePointXCoords[codePointIndex - Start];
- }
- /// <summary>
- /// The ascent of the font used in this run
- /// </summary>
- public float Ascent;
- /// <summary>
- /// The descent of the font used in this run
- /// </summary>
- public float Descent;
- /// <summary>
- /// The leading of the font used in this run
- /// </summary>
- public float Leading;
- /// <summary>
- /// The height of text in this run (ascent + descent)
- /// </summary>
- public float TextHeight => -Ascent + Descent;
- /// <summary>
- /// Calculate the half leading height for text in this run
- /// </summary>
- public float HalfLeading => (TextHeight * (Style.LineHeight - 1) + Leading) / 2;
- /// <summary>
- /// Width of this typeface run
- /// </summary>
- public float Width;
- /// <summary>
- /// Horizontal position of this run, relative to the left margin
- /// </summary>
- public float XCoord;
- /// <summary>
- /// The line that owns this font run
- /// </summary>
- public TextLine Line { get; internal set; }
- /// <summary>
- /// Get the next font run from this one
- /// </summary>
- public FontRun NextRun
- {
- get
- {
- var allRuns = Line.TextBlock.FontRuns as List<FontRun>;
- int index = allRuns.IndexOf(this);
- if (index < 0 || index + 1 >= Line.Runs.Count)
- return null;
- return Line.Runs[index + 1];
- }
- }
- /// <summary>
- /// Get the previous font run from this one
- /// </summary>
- public FontRun PreviousRun
- {
- get
- {
- var allRuns = Line.TextBlock.FontRuns as List<FontRun>;
- int index = allRuns.IndexOf(this);
- if (index - 1 < 0)
- return null;
- return Line.Runs[index - 1];
- }
- }
- /// <summary>
- /// For debugging
- /// </summary>
- /// <returns>Debug string</returns>
- public override string ToString()
- {
- switch (RunKind)
- {
- case FontRunKind.Normal:
- return $"{Start} - {End} @ {XCoord} - {XCoord + Width} = '{Utf32Utils.FromUtf32(CodePoints)}'";
- default:
- return $"{Start} - {End} @ {XCoord} - {XCoord + Width} {RunKind}'";
- }
- }
- /// <summary>
- /// Moves all glyphs by the specified offset amount
- /// </summary>
- /// <param name="dx">The x-delta to move glyphs by</param>
- /// <param name="dy">The y-delta to move glyphs by</param>
- public void MoveGlyphs(float dx, float dy)
- {
- for (int i = 0; i < GlyphPositions.Length; i++)
- {
- GlyphPositions[i].X += dx;
- GlyphPositions[i].Y += dy;
- }
- _textBlob?.Dispose();
- _textBlob = null;
- }
- /// <summary>
- /// Calculates the leading width of all character from the start of the run (either
- /// the left or right depending on run direction) to the specified code point
- /// </summary>
- /// <param name="codePoint">The code point index to measure to</param>
- /// <returns>The distance from the start to the specified code point</returns>
- public float LeadingWidth(int codePoint)
- {
- // At either end?
- if (codePoint == this.End)
- return this.Width;
- if (codePoint == 0)
- return 0;
- // Internal, calculate the leading width (ie from code point 0 to code point N)
- int codePointIndex = codePoint - this.Start;
- if (this.Direction == TextDirection.LTR)
- {
- return this.RelativeCodePointXCoords[codePointIndex];
- }
- else
- {
- return this.Width - this.RelativeCodePointXCoords[codePointIndex];
- }
- }
- /// <summary>
- /// Calculate the position at which to break a text run
- /// </summary>
- /// <param name="maxWidth">The max width available</param>
- /// <param name="force">Whether to force the use of at least one glyph</param>
- /// <returns>The code point position to break at</returns>
- internal int FindBreakPosition(float maxWidth, bool force)
- {
- int lastFittingCodePoint = this.Start;
- int firstNonZeroWidthCodePoint = -1;
- var prevWidth = 0f;
- for (int i = this.Start; i < this.End; i++)
- {
- var width = this.LeadingWidth(i);
- if (prevWidth != width)
- {
- if (firstNonZeroWidthCodePoint < 0)
- firstNonZeroWidthCodePoint = i;
- if (width < maxWidth)
- {
- lastFittingCodePoint = i;
- }
- else
- {
- break;
- }
- }
- prevWidth = width;
- }
- if (lastFittingCodePoint > this.Start || !force)
- return lastFittingCodePoint;
- if (firstNonZeroWidthCodePoint > this.Start)
- return firstNonZeroWidthCodePoint;
- // Split at the end
- return this.End;
- }
- /// <summary>
- /// Split a typeface run into two separate runs, truncating this run at
- /// the specified code point index and returning a new run containing the
- /// split off part.
- /// </summary>
- /// <param name="splitAtCodePoint">The code point index to split at</param>
- /// <returns>A new typeface run for the split off part</returns>
- internal FontRun Split(int splitAtCodePoint)
- {
- if (this.Direction == TextDirection.LTR)
- {
- return SplitLTR(splitAtCodePoint);
- }
- else
- {
- return SplitRTL(splitAtCodePoint);
- }
- }
- /// <summary>
- /// Split a LTR typeface run into two separate runs, truncating the passed
- /// run (LHS) and returning a new run containing the split off part (RHS)
- /// </summary>
- /// <param name="splitAtCodePoint">To code point position to split at</param>
- /// <returns>The RHS run after splitting</returns>
- private FontRun SplitLTR(int splitAtCodePoint)
- {
- // Check split point is internal to the run
- System.Diagnostics.Debug.Assert(this.Direction == TextDirection.LTR);
- System.Diagnostics.Debug.Assert(splitAtCodePoint > this.Start);
- System.Diagnostics.Debug.Assert(splitAtCodePoint < this.End);
- // Work out the split position
- int codePointSplitPos = splitAtCodePoint - this.Start;
- // Work out the width that we're slicing off
- float sliceLeftWidth = this.RelativeCodePointXCoords[codePointSplitPos];
- float sliceRightWidth = this.Width - sliceLeftWidth;
- // Work out the glyph split position
- int glyphSplitPos = 0;
- for (glyphSplitPos = 0; glyphSplitPos < this.Clusters.Length; glyphSplitPos++)
- {
- if (this.Clusters[glyphSplitPos] >= splitAtCodePoint)
- break;
- }
- // Create the other run
- var newRun = FontRun.Pool.Value.Get();
- newRun.StyleRun = this.StyleRun;
- newRun.CodePointBuffer = this.CodePointBuffer;
- newRun.Direction = this.Direction;
- newRun.Ascent = this.Ascent;
- newRun.Descent = this.Descent;
- newRun.Leading = this.Leading;
- newRun.Style = this.Style;
- newRun.Typeface = this.Typeface;
- newRun.Start = splitAtCodePoint;
- newRun.Length = this.End - splitAtCodePoint;
- newRun.Width = sliceRightWidth;
- newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
- newRun.GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos);
- newRun.Glyphs = this.Glyphs.SubSlice(glyphSplitPos);
- newRun.Clusters = this.Clusters.SubSlice(glyphSplitPos);
- // Adjust code point positions
- for (int i = 0; i < newRun.RelativeCodePointXCoords.Length; i++)
- {
- newRun.RelativeCodePointXCoords[i] -= sliceLeftWidth;
- }
- // Adjust glyph positions
- for (int i = 0; i < newRun.GlyphPositions.Length; i++)
- {
- newRun.GlyphPositions[i].X -= sliceLeftWidth;
- }
- // ATZ: remove \r symbol
- if (codePointSplitPos > 0 && this.CodePoints[codePointSplitPos - 1] == '\r')
- {
- codePointSplitPos--;
- glyphSplitPos--;
- sliceLeftWidth = this.RelativeCodePointXCoords[codePointSplitPos];
- if (codePointSplitPos == 0 || glyphSplitPos == 0)
- this.RunKind = FontRunKind.TrailingWhitespace;
- }
- // Update this run
- this.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(0, codePointSplitPos);
- this.Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos);
- this.GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos);
- this.Clusters = this.Clusters.SubSlice(0, glyphSplitPos);
- this.Width = sliceLeftWidth;
- this.Length = codePointSplitPos;
- this._textBlob?.Dispose();
- this._textBlob = null;
- // Return the new run
- return newRun;
- }
- /// <summary>
- /// Split a RTL typeface run into two separate runs, truncating the passed
- /// run (RHS) and returning a new run containing the split off part (LHS)
- /// </summary>
- /// <param name="splitAtCodePoint">To code point position to split at</param>
- /// <returns>The LHS run after splitting</returns>
- private FontRun SplitRTL(int splitAtCodePoint)
- {
- // Check split point is internal to the run
- System.Diagnostics.Debug.Assert(this.Direction == TextDirection.RTL);
- System.Diagnostics.Debug.Assert(splitAtCodePoint > this.Start);
- System.Diagnostics.Debug.Assert(splitAtCodePoint < this.End);
- // Work out the split position
- int codePointSplitPos = splitAtCodePoint - this.Start;
- // Work out the width that we're slicing off
- float sliceLeftWidth = this.RelativeCodePointXCoords[codePointSplitPos];
- float sliceRightWidth = this.Width - sliceLeftWidth;
- // Work out the glyph split position
- int glyphSplitPos = 0;
- for (glyphSplitPos = this.Clusters.Length; glyphSplitPos > 0; glyphSplitPos--)
- {
- if (this.Clusters[glyphSplitPos - 1] >= splitAtCodePoint)
- break;
- }
- // Create the other run
- var newRun = FontRun.Pool.Value.Get();
- newRun.StyleRun = this.StyleRun;
- newRun.CodePointBuffer = this.CodePointBuffer;
- newRun.Direction = this.Direction;
- newRun.Ascent = this.Ascent;
- newRun.Descent = this.Descent;
- newRun.Leading = this.Leading;
- newRun.Style = this.Style;
- newRun.Typeface = this.Typeface;
- newRun.Start = splitAtCodePoint;
- newRun.Length = this.End - splitAtCodePoint;
- newRun.Width = sliceLeftWidth;
- newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
- newRun.GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos);
- newRun.Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos);
- newRun.Clusters = this.Clusters.SubSlice(0, glyphSplitPos);
- // ATZ: remove \r symbol
- if (codePointSplitPos > 0 && this.CodePoints[codePointSplitPos - 1] == '\r')
- {
- codePointSplitPos++;
- glyphSplitPos++;
- sliceLeftWidth = this.RelativeCodePointXCoords[codePointSplitPos];
- sliceRightWidth = this.Width - sliceLeftWidth;
- if (codePointSplitPos >= this.CodePoints.Length || glyphSplitPos >= this.Glyphs.Length)
- this.RunKind = FontRunKind.TrailingWhitespace;
- }
- // Update this run
- this.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(0, codePointSplitPos);
- this.Glyphs = this.Glyphs.SubSlice(glyphSplitPos);
- this.GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos);
- this.Clusters = this.Clusters.SubSlice(glyphSplitPos);
- this.Width = sliceRightWidth;
- this.Length = codePointSplitPos;
- this._textBlob?.Dispose();
- this._textBlob = null;
- // Adjust code point positions
- for (int i = 0; i < this.RelativeCodePointXCoords.Length; i++)
- {
- this.RelativeCodePointXCoords[i] -= sliceLeftWidth;
- }
- // Adjust glyph positions
- for (int i = 0; i < this.GlyphPositions.Length; i++)
- {
- this.GlyphPositions[i].X -= sliceLeftWidth;
- }
- // Return the new run
- return newRun;
- }
- /// <summary>
- /// The global list of code points
- /// </summary>
- internal Buffer<int> CodePointBuffer;
- /// <summary>
- /// Calculate any overhang for this text line
- /// </summary>
- /// <param name="right"></param>
- /// <param name="leftOverhang"></param>
- /// <param name="rightOverhang"></param>
- internal void UpdateOverhang(float right, ref float leftOverhang, ref float rightOverhang)
- {
- if (RunKind == FontRunKind.TrailingWhitespace)
- return;
- if (Glyphs.Length == 0)
- return;
- using (var paint = new SKPaint())
- {
- float glyphScale = 1;
- if (Style.FontVariant == FontVariant.SuperScript)
- {
- glyphScale = 0.65f;
- }
- if (Style.FontVariant == FontVariant.SubScript)
- {
- glyphScale = 0.65f;
- }
- paint.TextEncoding = SKTextEncoding.GlyphId;
- paint.Typeface = Typeface;
- paint.TextSize = Style.FontSize * glyphScale;
- paint.SubpixelText = true;
- paint.IsAntialias = true;
- paint.LcdRenderText = false;
- unsafe
- {
- fixed (ushort* pGlyphs = Glyphs.Underlying)
- {
- // ATZ: should it be Glyphs.Start here? VVVVV
- paint.GetGlyphWidths((IntPtr)(pGlyphs + Start), sizeof(ushort) * Glyphs.Length, out var bounds);
- if (bounds != null)
- {
- for (int i = 0; i < bounds.Length; i++)
- {
- float gx = GlyphPositions[i].X;
- var loh = -(gx + bounds[i].Left);
- if (loh > leftOverhang)
- leftOverhang = loh;
- var roh = (gx + bounds[i].Right + 1) - right;
- if (roh > rightOverhang)
- rightOverhang = roh;
- }
- }
- }
- }
- }
- }
- /// <summary>
- /// Paint this font run
- /// </summary>
- /// <param name="ctx"></param>
- internal void Paint(PaintTextContext ctx)
- {
- // Paint selection?
- if (ctx.PaintSelectionBackground != null && RunKind != FontRunKind.Ellipsis)
- {
- bool paintStartHandle = false;
- bool paintEndHandle = false;
- float selStartXCoord;
- if (ctx.SelectionStart < Start)
- selStartXCoord = Direction == TextDirection.LTR ? 0 : Width;
- else if (ctx.SelectionStart >= End)
- selStartXCoord = Direction == TextDirection.LTR ? Width : 0;
- else
- {
- paintStartHandle = true;
- 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;
- paintEndHandle = ctx.SelectionEnd == End;
- }
- else
- {
- selEndXCoord = RelativeCodePointXCoords[ctx.SelectionEnd - this.Start];
- paintEndHandle = true;
- }
- 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);
- // Paint selection handles?
- if (ctx.PaintSelectionHandle != null)
- {
- if (paintStartHandle)
- {
- rect = new SKRect(tl.X - 1 * ctx.SelectionHandleScale, tl.Y, tl.X + 1 * ctx.SelectionHandleScale, br.Y);
- ctx.Canvas.DrawRect(rect, ctx.PaintSelectionHandle);
- ctx.Canvas.DrawCircle(new SKPoint(tl.X, tl.Y), 5 * ctx.SelectionHandleScale, ctx.PaintSelectionHandle);
- }
- if (paintEndHandle)
- {
- rect = new SKRect(br.X - 1 * ctx.SelectionHandleScale, tl.Y, br.X + 1 * ctx.SelectionHandleScale, br.Y);
- ctx.Canvas.DrawRect(rect, ctx.PaintSelectionHandle);
- ctx.Canvas.DrawCircle(new SKPoint(br.X, br.Y), 5 * ctx.SelectionHandleScale, ctx.PaintSelectionHandle);
- }
- }
- }
- }
- // Don't paint trailing whitespace runs
- if (RunKind == FontRunKind.TrailingWhitespace)
- return;
- // Text
- using (var paint = new SKPaint())
- using (var paintHalo = 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;
- if (Style.HaloColor != SKColor.Empty)
- {
- paintHalo.Color = Style.HaloColor;
- paintHalo.Style = SKPaintStyle.Stroke;
- paintHalo.StrokeWidth = Style.HaloWidth;
- paintHalo.StrokeCap = SKStrokeCap.Square;
- if (Style.HaloBlur > 0)
- paintHalo.MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, Style.HaloBlur);
- }
- 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.Hinting = ctx.Options.Hinting;
- _font.Edging = ctx.Options.Edging;
- _font.Subpixel = ctx.Options.SubpixelPositioning;
- // 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;
- paintHalo.StrokeWidth = paint.StrokeWidth + Style.HaloWidth;
- 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)
- {
- if (Style.HaloColor != SKColor.Empty)
- ctx.Canvas.DrawLine(new SKPoint(x, underlineYPos), new SKPoint(b, underlineYPos), paintHalo);
- // ATZ : draw to path
- if (ctx.Options.DrawToPath != null)
- ctx.Options.DrawToPath.AddRect(new SKRect(x, underlineYPos, b, underlineYPos + 1));
- else
- //
- ctx.Canvas.DrawLine(new SKPoint(x, underlineYPos), new SKPoint(b, underlineYPos), paint);
- }
- x = interceptPositions[i + 1] + paint.StrokeWidth;
- }
- if (x < XCoord + Width)
- {
- if (Style.HaloColor != SKColor.Empty)
- ctx.Canvas.DrawLine(new SKPoint(x, underlineYPos), new SKPoint(XCoord + Width, underlineYPos), paintHalo);
- // ATZ : draw to path
- if (ctx.Options.DrawToPath != null)
- ctx.Options.DrawToPath.AddRect(new SKRect(x, underlineYPos, XCoord + Width, underlineYPos + paint.StrokeWidth));
- else
- //
- 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);
- paintHalo.PathEffect = SKPathEffect.CreateDash(new float[] { paintHalo.StrokeWidth, paintHalo.StrokeWidth }, paintHalo.StrokeWidth);
- break;
- case UnderlineStyle.ImeConverted:
- paint.PathEffect = SKPathEffect.CreateDash(new float[] { paint.StrokeWidth, paint.StrokeWidth }, paint.StrokeWidth);
- paintHalo.PathEffect = SKPathEffect.CreateDash(new float[] { paintHalo.StrokeWidth, paintHalo.StrokeWidth }, paintHalo.StrokeWidth);
- break;
- case UnderlineStyle.ImeTargetConverted:
- paint.StrokeWidth *= 2;
- paintHalo.StrokeWidth *= 2;
- break;
- case UnderlineStyle.ImeTargetNonConverted:
- break;
- }
- // Paint solid underline
- if (Style.HaloColor != SKColor.Empty)
- ctx.Canvas.DrawLine(new SKPoint(XCoord, underlineYPos), new SKPoint(XCoord + Width, underlineYPos), paintHalo);
- // ATZ : draw to path
- if (ctx.Options.DrawToPath != null)
- ctx.Options.DrawToPath.AddRect(new SKRect(XCoord, underlineYPos, XCoord + Width, underlineYPos + paint.StrokeWidth));
- else
- //
- ctx.Canvas.DrawLine(new SKPoint(XCoord, underlineYPos), new SKPoint(XCoord + Width, underlineYPos), paint);
- paint.PathEffect = null;
- paintHalo.PathEffect = null;
- }
- }
- if (Style.HaloColor != SKColor.Empty)
- {
- // 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), paintHalo);
- }
- ctx.Canvas.DrawText(_textBlob, 0, 0, paintHalo);
- }
- // ATZ : draw to path
- if (ctx.Options.DrawToPath != null)
- {
- paint.Typeface = _font.Typeface;
- paint.TextSize = _font.Size;
- paint.TextEncoding = SKTextEncoding.GlyphId;
- ctx.Options.DrawToPath.AddPath(
- paint.GetTextPath((IntPtr)(pGlyphs + Glyphs.Start), Glyphs.Length * sizeof(ushort), GlyphPositions.AsSpan()));
- }
- else
- //
- 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;
- // ATZ : draw to path
- if (ctx.Options.DrawToPath != null)
- ctx.Options.DrawToPath.AddRect(new SKRect(XCoord, strikeYPos, XCoord + Width, strikeYPos + paint.StrokeWidth));
- else
- //
- ctx.Canvas.DrawLine(new SKPoint(XCoord, strikeYPos), new SKPoint(XCoord + Width, strikeYPos), paint);
- }
- }
- }
-
- /// <summary>
- /// Paint background of this font run
- /// </summary>
- /// <param name="ctx"></param>
- internal void PaintBackground(PaintTextContext ctx)
- {
- if (Style.BackgroundColor != SKColor.Empty && RunKind == FontRunKind.Normal)
- {
- var rect = new SKRect(XCoord , Line.YCoord,
- XCoord + Width, Line.YCoord + Line.Height);
- using (var skPaint = new SKPaint {Style = SKPaintStyle.Fill, Color = Style.BackgroundColor})
- {
- ctx.Canvas.DrawRect(rect, skPaint);
- }
- }
- }
- SKTextBlob _textBlob;
- SKFont _font;
- void Reset()
- {
- RunKind = FontRunKind.Normal;
- CodePointBuffer = null;
- Style = null;
- Typeface = null;
- Line = null;
- _textBlob = null;
- _font = null;
- }
- internal static ThreadLocal<ObjectPool<FontRun>> Pool = new ThreadLocal<ObjectPool<FontRun>>(() => new ObjectPool<FontRun>()
- {
- Cleaner = (r) => r.Reset()
- });
- }
- }
|