Browse Source

Merge branch 'master' into osx-nswindow-refactor

pull/8078/head
Dan Walmsley 4 years ago
committed by GitHub
parent
commit
5bf3237cf1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  2. 6
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  3. 91
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  4. 25
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  5. 19
      src/Avalonia.Controls/TextBlock.cs
  6. 7
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  7. 33
      src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
  8. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png
  9. BIN
      tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png

6
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -528,7 +528,7 @@ namespace Avalonia.Media.TextFormatting
/// Creates an empty text line.
/// </summary>
/// <returns>The empty text line.</returns>
public static TextLineImpl CreateEmptyTextLine(int firstTextSourceIndex, TextParagraphProperties paragraphProperties)
public static TextLineImpl CreateEmptyTextLine(int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties)
{
var flowDirection = paragraphProperties.FlowDirection;
var properties = paragraphProperties.DefaultTextRunProperties;
@ -542,7 +542,7 @@ namespace Avalonia.Media.TextFormatting
var textRuns = new List<DrawableTextRun> { new ShapedTextCharacters(shapedBuffer, properties) };
return new TextLineImpl(textRuns, firstTextSourceIndex, 0, double.PositiveInfinity, paragraphProperties, flowDirection).FinalizeLine();
return new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection).FinalizeLine();
}
/// <summary>
@ -561,7 +561,7 @@ namespace Avalonia.Media.TextFormatting
{
if(textRuns.Count == 0)
{
return CreateEmptyTextLine(firstTextSourceIndex, paragraphProperties);
return CreateEmptyTextLine(firstTextSourceIndex,paragraphWidth, paragraphProperties);
}
if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength))

6
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -410,7 +410,7 @@ namespace Avalonia.Media.TextFormatting
{
if (MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight))
{
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, _paragraphProperties);
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);
Bounds = new Rect(0,0,0, textLine.Height);
@ -434,7 +434,7 @@ namespace Avalonia.Media.TextFormatting
{
if(previousLine != null && previousLine.NewLineLength > 0)
{
var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, _paragraphProperties);
var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, _paragraphProperties);
textLines.Add(emptyTextLine);
@ -483,7 +483,7 @@ namespace Avalonia.Media.TextFormatting
//Make sure the TextLayout always contains at least on empty line
if(textLines.Count == 0)
{
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, _paragraphProperties);
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties);
textLines.Add(textLine);

91
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -252,7 +252,7 @@ namespace Avalonia.Media.TextFormatting
//Look at the left and right edge of the current run
if (currentRun.IsLeftToRight)
{
if (lastRun == null || lastRun.IsLeftToRight)
if (_flowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight))
{
if (characterIndex <= textRun.Text.Start)
{
@ -418,11 +418,6 @@ namespace Avalonia.Media.TextFormatting
continue;
}
if(currentPosition + currentRun.TextSourceLength <= firstTextSourceCharacterIndex)
{
continue;
}
TextRun? nextRun = null;
if (index + 1 < TextRuns.Count)
@ -460,7 +455,7 @@ namespace Avalonia.Media.TextFormatting
}
default:
{
goto noop;
goto noop;
}
}
@ -541,6 +536,11 @@ namespace Avalonia.Media.TextFormatting
endX += currentRun.Size.Width;
}
if (currentPosition < firstTextSourceCharacterIndex)
{
startX += currentRun.Size.Width;
}
currentPosition += currentRun.TextSourceLength;
break;
@ -554,24 +554,29 @@ namespace Avalonia.Media.TextFormatting
var width = endX - startX;
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
if (!MathUtilities.IsZero(width))
{
currentRect = currentRect.WithWidth(currentRect.Width + width);
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
{
currentRect = currentRect.WithWidth(currentRect.Width + width);
var textBounds = new TextBounds(currentRect, currentDirection);
var textBounds = new TextBounds(currentRect, currentDirection);
result[result.Count - 1] = textBounds;
}
else
{
currentRect = new Rect(startX, 0, width, Height);
result[result.Count - 1] = textBounds;
}
else
{
result.Add(new TextBounds(currentRect, currentDirection));
currentRect = new Rect(startX, 0, width, Height);
result.Add(new TextBounds(currentRect, currentDirection));
}
}
if (currentDirection == FlowDirection.LeftToRight)
{
if (currentPosition >= firstTextSourceCharacterIndex + textLength)
if (currentPosition > firstTextSourceCharacterIndex + textLength)
{
break;
}
@ -595,10 +600,10 @@ namespace Avalonia.Media.TextFormatting
public TextLineImpl FinalizeLine()
{
BidiReorder();
_textLineMetrics = CreateLineMetrics();
BidiReorder();
return this;
}
@ -1026,7 +1031,7 @@ namespace Avalonia.Media.TextFormatting
var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface;
var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight;
var width = 0d;
var widthIncludingWhitespace = 0d;
var trailingWhitespaceLength = 0;
@ -1036,8 +1041,8 @@ namespace Avalonia.Media.TextFormatting
var lineGap = glyphTypeface.LineGap * scale;
var height = descent - ascent + lineGap;
var lineHeight = _paragraphProperties.LineHeight;
var lineHeight = _paragraphProperties.LineHeight;
for (var index = 0; index < _textRuns.Count; index++)
{
@ -1073,41 +1078,11 @@ namespace Avalonia.Media.TextFormatting
}
}
switch (_paragraphProperties.FlowDirection)
if (index == _textRuns.Count - 1)
{
case FlowDirection.LeftToRight:
{
if (index == _textRuns.Count - 1)
{
width = widthIncludingWhitespace + textRun.GlyphRun.Metrics.Width;
trailingWhitespaceLength = textRun.GlyphRun.Metrics.TrailingWhitespaceLength;
newLineLength = textRun.GlyphRun.Metrics.NewlineLength;
}
break;
}
case FlowDirection.RightToLeft:
{
if (index == _textRuns.Count - 1)
{
var firstRun = _textRuns[0];
if (firstRun is ShapedTextCharacters shapedTextCharacters)
{
var offset = shapedTextCharacters.GlyphRun.Metrics.WidthIncludingTrailingWhitespace -
shapedTextCharacters.GlyphRun.Metrics.Width;
width = widthIncludingWhitespace +
textRun.GlyphRun.Metrics.WidthIncludingTrailingWhitespace - offset;
trailingWhitespaceLength = shapedTextCharacters.GlyphRun.Metrics.TrailingWhitespaceLength;
newLineLength = shapedTextCharacters.GlyphRun.Metrics.NewlineLength;
}
}
break;
}
width = widthIncludingWhitespace + textRun.GlyphRun.Metrics.Width;
trailingWhitespaceLength = textRun.GlyphRun.Metrics.TrailingWhitespaceLength;
newLineLength = textRun.GlyphRun.Metrics.NewlineLength;
}
widthIncludingWhitespace += textRun.GlyphRun.Metrics.WidthIncludingTrailingWhitespace;
@ -1166,10 +1141,10 @@ namespace Avalonia.Media.TextFormatting
if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight))
{
if(lineHeight > height)
if (lineHeight > height)
{
height = lineHeight;
}
}
}
return new TextLineMetrics(widthIncludingWhitespace > _paragraphWidth, height, newLineLength, start,

25
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -511,28 +511,39 @@ namespace Avalonia.Controls.Presenters
InvalidateMeasure();
}
protected override Size MeasureOverride(Size availableSize)
{
if (string.IsNullOrEmpty(Text))
{
return new Size();
}
_constraint = availableSize;
_textLayout = null;
InvalidateArrange();
var measuredSize = PixelSize.FromSize(TextLayout.Bounds.Size, 1);
return new Size(measuredSize.Width, measuredSize.Height);
var measuredSize = TextLayout.Bounds.Size;
return measuredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
if (finalSize.Width < TextLayout.Bounds.Width)
{
finalSize = finalSize.WithWidth(TextLayout.Bounds.Width);
}
if (MathUtilities.AreClose(_constraint.Width, finalSize.Width))
{
return finalSize;
}
_constraint = new Size(finalSize.Width, Math.Ceiling(finalSize.Height));
_constraint = new Size(finalSize.Width, double.PositiveInfinity);
_textLayout = null;

19
src/Avalonia.Controls/TextBlock.cs

@ -604,7 +604,9 @@ namespace Avalonia.Controls
return new Size();
}
var padding = Padding;
var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
_constraint = availableSize.Deflate(padding);
@ -612,23 +614,24 @@ namespace Avalonia.Controls
InvalidateArrange();
var measuredSize = PixelSize.FromSize(TextLayout.Bounds.Size, 1);
var measuredSize = TextLayout.Bounds.Size.Inflate(padding);
return new Size(measuredSize.Width, measuredSize.Height).Inflate(padding);
return measuredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
if(finalSize.Width < TextLayout.Bounds.Width)
{
finalSize = finalSize.WithWidth(TextLayout.Bounds.Width);
}
if (MathUtilities.AreClose(_constraint.Width, finalSize.Width))
{
return finalSize;
}
var padding = Padding;
var textSize = finalSize.Deflate(padding);
_constraint = new Size(textSize.Width, Math.Ceiling(textSize.Height));
_constraint = new Size(finalSize.Width, double.PositiveInfinity);
_textLayout = null;

7
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -27,7 +27,7 @@ namespace Avalonia.Skia
buffer.GuessSegmentProperties();
buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft;
buffer.Direction = Direction.LeftToRight; //Always shape LeftToRight
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
@ -35,11 +35,6 @@ namespace Avalonia.Skia
font.Shape(buffer);
if (buffer.Direction == Direction.RightToLeft)
{
buffer.Reverse();
}
font.GetScale(out var scaleX, out _);
var textScale = fontRenderingEmSize / scaleX;

33
src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs

@ -1,6 +1,5 @@
using System;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
@ -11,8 +10,7 @@ using GlyphInfo = HarfBuzzSharp.GlyphInfo;
namespace Avalonia.Direct2D1.Media
{
internal class TextShaperImpl : ITextShaperImpl
internal class TextShaperImpl : ITextShaperImpl
{
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, TextShaperOptions options)
{
@ -23,25 +21,20 @@ internal class TextShaperImpl : ITextShaperImpl
using (var buffer = new Buffer())
{
buffer.AddUtf16(text.Buffer.Span, text.Start, text.Length);
buffer.AddUtf16(text.Buffer.Span, text.BufferOffset, text.Length);
MergeBreakPair(buffer);
buffer.GuessSegmentProperties();
buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft;
buffer.Direction = Direction.LeftToRight; //Always shape LeftToRight
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font;
font.Shape(buffer);
if (buffer.Direction == Direction.RightToLeft)
{
buffer.Reverse();
}
font.GetScale(out var scaleX, out _);
var textScale = fontRenderingEmSize / scaleX;
@ -60,13 +53,13 @@ internal class TextShaperImpl : ITextShaperImpl
var glyphIndex = (ushort)sourceInfo.Codepoint;
var glyphCluster = (int)sourceInfo.Cluster;
var glyphCluster = (int)(sourceInfo.Cluster);
var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale);
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
if (glyphIndex == 0 && text[glyphCluster] == '\t')
if (glyphIndex == 0 && text.Buffer.Span[glyphCluster] == '\t')
{
glyphIndex = typeface.GetGlyph(' ');
@ -75,9 +68,7 @@ internal class TextShaperImpl : ITextShaperImpl
4 * typeface.GetGlyphAdvance(glyphIndex) * textScale;
}
var targetInfo =
new Avalonia.Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance,
glyphOffset);
var targetInfo = new Avalonia.Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
shapedBuffer[i] = targetInfo;
}
@ -91,7 +82,7 @@ internal class TextShaperImpl : ITextShaperImpl
var length = buffer.Length;
var glyphInfos = buffer.GetGlyphInfoSpan();
var second = glyphInfos[length - 1];
if (!new Codepoint((int)second.Codepoint).IsBreakChar)
@ -102,7 +93,7 @@ internal class TextShaperImpl : ITextShaperImpl
if (length > 1 && glyphInfos[length - 2].Codepoint == '\r' && second.Codepoint == '\n')
{
var first = glyphInfos[length - 2];
first.Codepoint = '\u200C';
second.Codepoint = '\u200C';
second.Cluster = first.Cluster;
@ -113,7 +104,7 @@ internal class TextShaperImpl : ITextShaperImpl
{
*p = first;
}
fixed (GlyphInfo* p = &glyphInfos[length - 1])
{
*p = second;
@ -148,7 +139,7 @@ internal class TextShaperImpl : ITextShaperImpl
private static double GetGlyphAdvance(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale)
{
// Depends on direction of layout
// advanceBuffer[index] = buffer.GlyphPositions[index].YAdvance * textScale;
// glyphPositions[index].YAdvance * textScale;
return glyphPositions[index].XAdvance * textScale;
}
}

BIN
tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 752 B

BIN
tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 B

After

Width:  |  Height:  |  Size: 557 B

Loading…
Cancel
Save