Browse Source

Merge pull request #10936 from Gillibald/multipleTextFixes

[Text] Fixes
pull/10987/head
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
5ed97387eb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 92
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  2. 76
      src/Avalonia.Controls/TextBlock.cs
  3. 37
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  4. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

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

@ -57,21 +57,21 @@ namespace Avalonia.Media.TextFormatting
switch (paragraphProperties.TextWrapping)
{
case TextWrapping.NoWrap:
{
var textLine = new TextLineImpl(shapedTextRuns.ToArray(), firstTextSourceIndex,
textSourceLength,
paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);
{
var textLine = new TextLineImpl(shapedTextRuns.ToArray(), firstTextSourceIndex,
textSourceLength,
paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);
textLine.FinalizeLine();
return textLine;
}
return textLine;
}
case TextWrapping.WrapWithOverflow:
case TextWrapping.Wrap:
{
return PerformTextWrapping(shapedTextRuns, false, firstTextSourceIndex, paragraphWidth,
paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool);
}
{
return PerformTextWrapping(shapedTextRuns, false, firstTextSourceIndex, paragraphWidth,
paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool);
}
default:
throw new ArgumentOutOfRangeException(nameof(paragraphProperties.TextWrapping));
}
@ -568,9 +568,9 @@ namespace Avalonia.Media.TextFormatting
return false;
}
private static bool TryMeasureLength(IReadOnlyList<TextRun> textRuns, double paragraphWidth, out int measuredLength)
private static int MeasureLength(IReadOnlyList<TextRun> textRuns, double paragraphWidth)
{
measuredLength = 0;
var measuredLength = 0;
var currentWidth = 0.0;
for (var i = 0; i < textRuns.Count; ++i)
@ -583,25 +583,59 @@ namespace Avalonia.Media.TextFormatting
{
if (shapedTextCharacters.ShapedBuffer.Length > 0)
{
var firstCluster = shapedTextCharacters.ShapedBuffer[0].GlyphCluster;
var lastCluster = firstCluster;
var runLength = 0;
for (var j = 0; j < shapedTextCharacters.ShapedBuffer.Length; j++)
{
var glyphInfo = shapedTextCharacters.ShapedBuffer[j];
var currentInfo = shapedTextCharacters.ShapedBuffer[j];
var clusterWidth = currentInfo.GlyphAdvance;
if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth)
GlyphInfo nextInfo = default;
while (j + 1 < shapedTextCharacters.ShapedBuffer.Length)
{
measuredLength += Math.Max(0, lastCluster - firstCluster);
nextInfo = shapedTextCharacters.ShapedBuffer[j + 1];
if (currentInfo.GlyphCluster == nextInfo.GlyphCluster)
{
clusterWidth += nextInfo.GlyphAdvance;
j++;
continue;
}
break;
}
return measuredLength != 0;
var clusterLength = Math.Max(0, nextInfo.GlyphCluster - currentInfo.GlyphCluster);
if(clusterLength == 0)
{
clusterLength = currentRun.Length - runLength;
}
if(clusterLength == 0)
{
clusterLength = shapedTextCharacters.GlyphRun.Metrics.FirstCluster + currentRun.Length - currentInfo.GlyphCluster;
}
if (currentWidth + clusterWidth > paragraphWidth)
{
if (runLength == 0 && measuredLength == 0)
{
runLength = clusterLength;
}
return measuredLength + runLength;
}
lastCluster = glyphInfo.GlyphCluster;
currentWidth += glyphInfo.GlyphAdvance;
currentWidth += clusterWidth;
runLength += clusterLength;
}
measuredLength += currentRun.Length;
measuredLength += runLength;
}
break;
@ -611,7 +645,7 @@ namespace Avalonia.Media.TextFormatting
{
if (currentWidth + drawableTextRun.Size.Width >= paragraphWidth)
{
return measuredLength != 0;
return measuredLength;
}
measuredLength += currentRun.Length;
@ -628,7 +662,7 @@ namespace Avalonia.Media.TextFormatting
}
}
return measuredLength != 0;
return measuredLength;
}
/// <summary>
@ -675,9 +709,11 @@ namespace Avalonia.Media.TextFormatting
return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties);
}
if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength))
var measuredLength = MeasureLength(textRuns, paragraphWidth);
if(measuredLength == 0)
{
measuredLength = 1;
}
var currentLength = 0;
@ -798,6 +834,12 @@ namespace Avalonia.Media.TextFormatting
continue;
}
//We don't want to surpass the measuredLength with trailing whitespace when we are in a right to left setting.
if(currentPosition > measuredLength && resolvedFlowDirection == FlowDirection.RightToLeft)
{
break;
}
measuredLength = currentPosition;
break;

76
src/Avalonia.Controls/TextBlock.cs

@ -663,7 +663,6 @@ namespace Avalonia.Controls
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
_constraint = availableSize.Deflate(padding);
_textLayout?.Dispose();
_textLayout = null;
@ -690,18 +689,18 @@ namespace Avalonia.Controls
inline.BuildTextRun(textRuns);
}
foreach (var textRun in textRuns)
_textRuns = textRuns;
foreach (var textRun in _textRuns)
{
if (textRun is EmbeddedControlRun controlRun &&
controlRun.Control is Control control)
controlRun.Control is Control control)
{
VisualChildren.Add(control);
control.Measure(Size.Infinity);
}
}
_textRuns = textRuns;
}
var measuredSize = TextLayout.Bounds.Size.Inflate(padding);
@ -711,64 +710,39 @@ namespace Avalonia.Controls
protected override Size ArrangeOverride(Size finalSize)
{
var textWidth = Math.Ceiling(TextLayout.Bounds.Width);
if (finalSize.Width < textWidth)
{
finalSize = finalSize.WithWidth(textWidth);
}
var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
if (HasComplexContent)
{
ArrangeComplexContent(TextLayout, padding);
}
var scale = LayoutHelper.GetLayoutScale(this);
if (MathUtilities.AreClose(_constraint.Inflate(padding).Width, finalSize.Width))
{
return finalSize;
}
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
_constraint = new Size(Math.Ceiling(finalSize.Deflate(padding).Width), double.PositiveInfinity);
var currentY = padding.Top;
_textLayout?.Dispose();
_textLayout = null;
if (HasComplexContent)
{
ArrangeComplexContent(TextLayout, padding);
}
return finalSize;
}
private static void ArrangeComplexContent(TextLayout textLayout, Thickness padding)
{
var currentY = padding.Top;
foreach (var textLine in textLayout.TextLines)
{
var currentX = padding.Left + textLine.Start;
foreach (var run in textLine.TextRuns)
foreach (var textLine in TextLayout.TextLines)
{
if (run is DrawableTextRun drawable)
var currentX = padding.Left + textLine.Start;
foreach (var run in textLine.TextRuns)
{
if (drawable is EmbeddedControlRun controlRun
&& controlRun.Control is Control control)
if (run is DrawableTextRun drawable)
{
control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize));
if (drawable is EmbeddedControlRun controlRun
&& controlRun.Control is Control control)
{
control.Arrange(
new Rect(new Point(currentX, currentY),
new Size(control.DesiredSize.Width, textLine.Height)));
}
currentX += drawable.Size.Width;
}
currentX += drawable.Size.Width;
}
}
currentY += textLine.Height;
currentY += textLine.Height;
}
}
return finalSize;
}
protected override AutomationPeer OnCreateAutomationPeer()

37
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -731,36 +731,25 @@ namespace Avalonia.Win32
}
case WindowsMessage.WM_IME_COMPOSITION:
{
var previousComposition = Imm32InputMethod.Current.Composition;
var flags = (GCS)ToInt32(lParam);
var currentComposition = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_COMPSTR);
if ((flags & GCS.GCS_COMPSTR) != 0)
{
var currentComposition = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_COMPSTR);
Imm32InputMethod.Current.CompositionChanged(currentComposition);
Imm32InputMethod.Current.CompositionChanged(currentComposition);
}
switch (flags)
if ((flags & GCS.GCS_RESULTSTR) != 0)
{
case GCS.GCS_RESULTSTR:
{
if(!string.IsNullOrEmpty(previousComposition) && ToInt32(wParam) >= 32)
{
Imm32InputMethod.Current.Composition = previousComposition;
var result = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_RESULTSTR);
_ignoreWmChar = true;
}
break;
}
case GCS.GCS_RESULTREADCLAUSE | GCS.GCS_RESULTSTR | GCS.GCS_RESULTCLAUSE:
{
// Chinese IME sends WM_CHAR after composition has finished.
break;
}
case GCS.GCS_RESULTREADSTR | GCS.GCS_RESULTREADCLAUSE | GCS.GCS_RESULTSTR | GCS.GCS_RESULTCLAUSE:
{
// Japanese IME sends WM_CHAR after composition has finished.
break;
}
if (!string.IsNullOrEmpty(result))
{
Imm32InputMethod.Current.Composition = result;
_ignoreWmChar = true;
}
}
break;

2
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -416,7 +416,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
[InlineData("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor",
new[] { "Lorem ipsum ", "dolor sit ", "amet, ", "consectetur ", "adipisicing ", "elit, sed do ", "eiusmod tempor" })]
new[] { "Lorem ipsum ", "dolor sit amet, ", "consectetur ", "adipisicing ", "elit, sed do ", "eiusmod tempor" })]
[Theory]
public void Should_Produce_Wrapped_And_Trimmed_Lines(string text, string[] expectedLines)

Loading…
Cancel
Save