@ -1,9 +1,10 @@
using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
using System.Threading ;
using Avalonia.Media ;
using Avalonia.Platform ;
using Avalonia.Utilities ;
using SkiaSharp ;
namespace Avalonia.Skia
@ -13,6 +14,25 @@ namespace Avalonia.Skia
/// </summary>
internal class FormattedTextImpl : IFormattedTextImpl
{
private static readonly ThreadLocal < SKTextBlobBuilder > t_builder = new ThreadLocal < SKTextBlobBuilder > ( ( ) = > new SKTextBlobBuilder ( ) ) ;
private const float MAX_LINE_WIDTH = 1 0 0 0 0 ;
private readonly List < KeyValuePair < FBrushRange , IBrush > > _f oregroundBrushes =
new List < KeyValuePair < FBrushRange , IBrush > > ( ) ;
private readonly List < FormattedTextLine > _l ines = new List < FormattedTextLine > ( ) ;
private readonly SKPaint _ paint ;
private readonly List < Rect > _ rects = new List < Rect > ( ) ;
public string Text { get ; }
private readonly TextWrapping _ wrapping ;
private Size _ constraint = new Size ( double . PositiveInfinity , double . PositiveInfinity ) ;
private float _l ineHeight = 0 ;
private float _l ineOffset = 0 ;
private Rect _ bounds ;
private List < AvaloniaFormattedTextLine > _ skiaLines ;
private ReadOnlySlice < ushort > _ glyphs ;
private ReadOnlySlice < float > _ advances ;
public FormattedTextImpl (
string text ,
Typeface typeface ,
@ -23,12 +43,9 @@ namespace Avalonia.Skia
IReadOnlyList < FormattedTextStyleSpan > spans )
{
Text = text ? ? string . Empty ;
// Replace 0 characters with zero-width spaces (200B)
Text = Text . Replace ( ( char ) 0 , ( char ) 0x200B ) ;
var glyphTypeface = ( GlyphTypefaceImpl ) typeface . GlyphTypeface . PlatformImpl ;
UpdateGlyphInfo ( Text , typeface . GlyphTypeface , ( float ) fontSize ) ;
_ paint = new SKPaint
{
TextEncoding = SKTextEncoding . Utf16 ,
@ -37,7 +54,7 @@ namespace Avalonia.Skia
LcdRenderText = true ,
SubpixelText = true ,
IsLinearText = true ,
Typeface = glyphTypeface . Typeface ,
Typeface = ( ( GlyphTypefaceImpl ) typeface . GlyphTypeface . PlatformImpl ) . Typeface ,
TextSize = ( float ) fontSize ,
TextAlign = textAlignment . ToSKTextAlign ( )
} ;
@ -195,6 +212,40 @@ namespace Avalonia.Skia
return Text ;
}
private void DrawTextBlob ( int start , int length , float x , float y , SKCanvas canvas , SKPaint paint )
{
if ( length = = 0 )
{
return ;
}
var glyphs = _ glyphs . Buffer . Span . Slice ( start , length ) ;
var advances = _ advances . Buffer . Span . Slice ( start , length ) ;
var builder = t_builder . Value ;
var buffer = builder . AllocateHorizontalRun ( _ paint . ToFont ( ) , length , 0 ) ;
buffer . SetGlyphs ( glyphs ) ;
var positions = buffer . GetPositionSpan ( ) ;
var pos = 0f ;
for ( int i = 0 ; i < advances . Length ; i + + )
{
positions [ i ] = pos ;
pos + = advances [ i ] ;
}
var blob = builder . Build ( ) ;
if ( blob ! = null )
{
canvas . DrawText ( blob , x , y , paint ) ;
}
}
internal void Draw ( DrawingContextImpl context ,
SKCanvas canvas ,
SKPoint origin ,
@ -244,16 +295,15 @@ namespace Avalonia.Skia
if ( ! hasCusomFGBrushes )
{
var subString = Text . Substring ( line . Start , line . Length ) ;
canvas . DrawText ( subString , x , origin . Y + line . Top + _l ineOffset , paint ) ;
DrawTextBlob ( line . Start , line . Length , x , origin . Y + line . Top + _l ineOffset , canvas , paint ) ;
}
else
{
float currX = x ;
string subStr ;
float measure ;
int len ;
float factor ;
switch ( paint . TextAlign )
{
case SKTextAlign . Left :
@ -269,8 +319,7 @@ namespace Avalonia.Skia
throw new ArgumentOutOfRangeException ( ) ;
}
var textLine = Text . Substring ( line . Start , line . Length ) ;
currX - = textLine . Length = = 0 ? 0 : paint . MeasureText ( textLine ) * factor ;
currX - = line . Length = = 0 ? 0 : MeasureText ( line . Start , line . Length ) * factor ;
for ( int i = line . Start ; i < line . Start + line . Length ; )
{
@ -288,13 +337,12 @@ namespace Avalonia.Skia
currentWrapper = foreground ;
}
subStr = Text . Substring ( i , len ) ;
measure = paint . MeasureText ( subStr ) ;
measure = MeasureText ( i , len ) ;
currX + = measure * factor ;
ApplyWrapperTo ( ref currentPaint , currentWrapper , ref currd , paint , canUseLcdRendering ) ;
ApplyWrapperTo ( ref currentPaint , currentWrapper , ref currd , paint , canUseLcdRendering ) ;
canvas . DrawText ( subStr , currX , origin . Y + line . Top + _l ineOffset , paint ) ;
DrawTextBlob ( i , len , currX , origin . Y + line . Top + _l ineOffset , canvas , paint ) ;
i + = len ;
currX + = measure * ( 1 - factor ) ;
@ -310,21 +358,6 @@ namespace Avalonia.Skia
}
}
private const float MAX_LINE_WIDTH = 1 0 0 0 0 ;
private readonly List < KeyValuePair < FBrushRange , IBrush > > _f oregroundBrushes =
new List < KeyValuePair < FBrushRange , IBrush > > ( ) ;
private readonly List < FormattedTextLine > _l ines = new List < FormattedTextLine > ( ) ;
private readonly SKPaint _ paint ;
private readonly List < Rect > _ rects = new List < Rect > ( ) ;
public string Text { get ; }
private readonly TextWrapping _ wrapping ;
private Size _ constraint = new Size ( double . PositiveInfinity , double . PositiveInfinity ) ;
private float _l ineHeight = 0 ;
private float _l ineOffset = 0 ;
private Rect _ bounds ;
private List < AvaloniaFormattedTextLine > _ skiaLines ;
private static void ApplyWrapperTo ( ref SKPaint current , DrawingContextImpl . PaintWrapper wrapper ,
ref IDisposable curr , SKPaint paint , bool canUseLcdRendering )
{
@ -352,9 +385,8 @@ namespace Avalonia.Skia
}
else
{
float measuredWidth ;
string subText = textInput . Substring ( textIndex , stop - textIndex ) ;
lengthBreak = ( int ) paint . BreakText ( subText , maxWidth , out measuredWidth ) ;
lengthBreak = ( int ) paint . BreakText ( subText , maxWidth , out _ ) ;
}
//Check for white space or line breakers before the lengthBreak
@ -468,8 +500,7 @@ namespace Avalonia.Skia
for ( int i = line . Start ; i < line . Start + line . TextLength ; i + + )
{
var c = Text [ i ] ;
var w = line . IsEmptyTrailingLine ? 0 : _ paint . MeasureText ( Text [ i ] . ToString ( ) ) ;
var w = line . IsEmptyTrailingLine ? 0 : _ advances [ i ] ;
_ rects . Add ( new Rect (
prevRight ,
@ -554,7 +585,7 @@ namespace Avalonia.Skia
// This seems like the best measure of full vertical extent
// matches Direct2D line height
_l ineHeight = mDescent - mAscent ;
_l ineHeight = mDescent - mAscent + metrics . Leading ;
// Rendering is relative to baseline
_l ineOffset = ( - metrics . Ascent ) ;
@ -585,7 +616,7 @@ namespace Avalonia.Skia
line . Start = curOff ;
line . TextLength = measured ;
subString = Text . Substring ( line . Start , line . TextLength ) ;
lineWidth = _ paint . MeasureText ( subString ) ;
lineWidth = MeasureText ( line . Start , line . TextLength ) ;
line . Length = measured - trailingnumber ;
line . Width = lineWidth ;
line . Height = _l ineHeight ;
@ -608,8 +639,7 @@ namespace Avalonia.Skia
AvaloniaFormattedTextLine lastLine = new AvaloniaFormattedTextLine ( ) ;
lastLine . TextLength = lengthDiff ;
lastLine . Start = curOff - lengthDiff ;
var lastLineSubString = Text . Substring ( line . Start , line . TextLength ) ;
var lastLineWidth = _ paint . MeasureText ( lastLineSubString ) ;
var lastLineWidth = MeasureText ( line . Start , line . TextLength ) ;
lastLine . Length = 0 ;
lastLine . Width = lastLineWidth ;
lastLine . Height = _l ineHeight ;
@ -668,6 +698,67 @@ namespace Avalonia.Skia
}
}
private float MeasureText ( int start , int length )
{
var width = 0f ;
for ( int i = start ; i < start + length ; i + + )
{
var advance = _ advances [ i ] ;
width + = advance ;
}
return width ;
}
private void UpdateGlyphInfo ( string text , GlyphTypeface glyphTypeface , float fontSize )
{
var glyphs = new ushort [ text . Length ] ;
var advances = new float [ text . Length ] ;
var scale = fontSize / glyphTypeface . DesignEmHeight ;
var width = 0f ;
var characters = text . AsSpan ( ) ;
for ( int i = 0 ; i < characters . Length ; i + + )
{
var c = characters [ i ] ;
float advance ;
ushort glyph ;
switch ( c )
{
case ( char ) 0 :
{
glyph = glyphTypeface . GetGlyph ( 0x200B ) ;
advance = 0 ;
break ;
}
case '\t' :
{
glyph = glyphTypeface . GetGlyph ( ' ' ) ;
advance = glyphTypeface . GetGlyphAdvance ( glyph ) * scale * 4 ;
break ;
}
default :
{
glyph = glyphTypeface . GetGlyph ( c ) ;
advance = glyphTypeface . GetGlyphAdvance ( glyph ) * scale ;
break ;
}
}
glyphs [ i ] = glyph ;
advances [ i ] = advance ;
width + = advance ;
}
_ glyphs = new ReadOnlySlice < ushort > ( glyphs ) ;
_ advances = new ReadOnlySlice < float > ( advances ) ;
}
private float TransformX ( float originX , float lineWidth , SKTextAlign align )
{
float x = 0 ;