Skip to content

Commit

Permalink
Merge pull request #7044 from Gillibald/feature/handleTabstopps
Browse files Browse the repository at this point in the history
Handle TabStopps
  • Loading branch information
grokys authored Dec 6, 2021
2 parents e185e07 + d6fb97c commit 39e1e50
Showing 1 changed file with 130 additions and 39 deletions.
169 changes: 130 additions & 39 deletions src/Skia/Avalonia.Skia/FormattedTextImpl.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 = 10000;

private readonly List<KeyValuePair<FBrushRange, IBrush>> _foregroundBrushes =
new List<KeyValuePair<FBrushRange, IBrush>>();
private readonly List<FormattedTextLine> _lines = 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 _lineHeight = 0;
private float _lineOffset = 0;
private Rect _bounds;
private List<AvaloniaFormattedTextLine> _skiaLines;
private ReadOnlySlice<ushort> _glyphs;
private ReadOnlySlice<float> _advances;

public FormattedTextImpl(
string text,
Typeface typeface,
Expand All @@ -23,12 +43,9 @@ public FormattedTextImpl(
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,
Expand All @@ -37,7 +54,7 @@ public FormattedTextImpl(
LcdRenderText = true,
SubpixelText = true,
IsLinearText = true,
Typeface = glyphTypeface.Typeface,
Typeface = ((GlyphTypefaceImpl)typeface.GlyphTypeface.PlatformImpl).Typeface,
TextSize = (float)fontSize,
TextAlign = textAlignment.ToSKTextAlign()
};
Expand Down Expand Up @@ -195,6 +212,40 @@ public override string ToString()
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,
Expand Down Expand Up @@ -244,16 +295,15 @@ internal void Draw(DrawingContextImpl context,

if (!hasCusomFGBrushes)
{
var subString = Text.Substring(line.Start, line.Length);
canvas.DrawText(subString, x, origin.Y + line.Top + _lineOffset, paint);
DrawTextBlob(line.Start, line.Length, x, origin.Y + line.Top + _lineOffset, canvas, paint);
}
else
{
float currX = x;
string subStr;
float measure;
int len;
float factor;

switch (paint.TextAlign)
{
case SKTextAlign.Left:
Expand All @@ -269,8 +319,7 @@ internal void Draw(DrawingContextImpl context,
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;)
{
Expand All @@ -288,13 +337,12 @@ internal void Draw(DrawingContextImpl context,
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 + _lineOffset, paint);
DrawTextBlob(i, len, currX, origin.Y + line.Top + _lineOffset, canvas, paint);

i += len;
currX += measure * (1 - factor);
Expand All @@ -310,21 +358,6 @@ internal void Draw(DrawingContextImpl context,
}
}

private const float MAX_LINE_WIDTH = 10000;

private readonly List<KeyValuePair<FBrushRange, IBrush>> _foregroundBrushes =
new List<KeyValuePair<FBrushRange, IBrush>>();
private readonly List<FormattedTextLine> _lines = 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 _lineHeight = 0;
private float _lineOffset = 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)
{
Expand Down Expand Up @@ -352,9 +385,8 @@ private static int LineBreak(string textInput, int textIndex, int stop,
}
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
Expand Down Expand Up @@ -468,8 +500,7 @@ private void BuildRects()

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,
Expand Down Expand Up @@ -554,7 +585,7 @@ private void Rebuild()

// This seems like the best measure of full vertical extent
// matches Direct2D line height
_lineHeight = mDescent - mAscent;
_lineHeight = mDescent - mAscent + metrics.Leading;

// Rendering is relative to baseline
_lineOffset = (-metrics.Ascent);
Expand Down Expand Up @@ -585,7 +616,7 @@ private void Rebuild()
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 = _lineHeight;
Expand All @@ -608,8 +639,7 @@ private void Rebuild()
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 = _lineHeight;
Expand Down Expand Up @@ -668,6 +698,67 @@ private void Rebuild()
}
}

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;
Expand Down

0 comments on commit 39e1e50

Please sign in to comment.