Browse Source

Fix baseline alignment of multiple runs

Make sure we always wrap at least one character
pull/4807/head
Benedikt Schroeder 5 years ago
committed by Andrey Kunchev
parent
commit
40d74cf682
  1. 4
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  2. 17
      src/Avalonia.Visuals/Media/GlyphRun.cs
  3. 2
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  4. 67
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  5. 44
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

4
samples/ControlCatalog/Pages/TextBlockPage.xaml

@ -18,8 +18,8 @@
</StackPanel.Styles>
<Border>
<StackPanel Width="200" Spacing="8">
<TextBlock TextTrimming="CharacterEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock TextTrimming="WordEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock Margin="0 0 10 0" TextTrimming="CharacterEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock Margin="0 0 10 0" TextTrimming="WordEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock Text="Left aligned text" TextAlignment="Left" />
<TextBlock Text="Center aligned text" TextAlignment="Center" />
<TextBlock Text="Right aligned text" TextAlignment="Right" />

17
src/Avalonia.Visuals/Media/GlyphRun.cs

@ -18,7 +18,7 @@ namespace Avalonia.Media
private double _fontRenderingEmSize;
private Size? _size;
private int _biDiLevel;
private Point? _baselineOrigin;
private Point _baselineOrigin;
private ReadOnlySlice<ushort> _glyphIndices;
private ReadOnlySlice<double> _glyphAdvances;
@ -97,9 +97,7 @@ namespace Avalonia.Media
{
get
{
_baselineOrigin ??= CalculateBaselineOrigin();
return _baselineOrigin.Value;
return _baselineOrigin;
}
set => Set(ref _baselineOrigin, value);
}
@ -540,15 +538,6 @@ namespace Avalonia.Media
return GlyphAdvances[index];
}
/// <summary>
/// Calculates the default baseline origin of the <see cref="GlyphRun"/>.
/// </summary>
/// <returns>The baseline origin.</returns>
private Point CalculateBaselineOrigin()
{
return new Point(0, -GlyphTypeface.Ascent * Scale);
}
/// <summary>
/// Calculates the size of the <see cref="GlyphRun"/>.
/// </summary>
@ -611,8 +600,6 @@ namespace Avalonia.Media
throw new InvalidOperationException();
}
_baselineOrigin = new Point(0, -GlyphTypeface.Ascent * Scale);
var platformRenderInterface = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width);

2
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@ -52,7 +52,7 @@ namespace Avalonia.Media.TextFormatting
return;
}
if (Properties.Typeface == null)
if (Properties.Typeface == default)
{
return;
}

67
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@ -43,18 +43,23 @@ namespace Avalonia.Media.TextFormatting
}
/// <summary>
/// Measures the number of characters that fits into available width.
/// Measures the number of characters that fit into available width.
/// </summary>
/// <param name="textCharacters">The text run.</param>
/// <param name="availableWidth">The available width.</param>
/// <returns></returns>
internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth)
/// <param name="count">The count of fitting characters.</param>
/// <returns>
/// <c>true</c> if characters fit into the available width; otherwise, <c>false</c>.
/// </returns>
internal static bool TryMeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth, out int count)
{
var glyphRun = textCharacters.GlyphRun;
if (glyphRun.Size.Width < availableWidth)
{
return glyphRun.Characters.Length;
count = glyphRun.Characters.Length;
return true;
}
var glyphCount = 0;
@ -96,21 +101,34 @@ namespace Avalonia.Media.TextFormatting
}
}
if (glyphCount == 0)
{
count = 0;
return false;
}
if (glyphCount == glyphRun.GlyphIndices.Length)
{
return glyphRun.Characters.Length;
count = glyphRun.Characters.Length;
return true;
}
if (glyphRun.GlyphClusters.IsEmpty)
{
return glyphCount;
count = glyphCount;
return true;
}
var firstCluster = glyphRun.GlyphClusters[0];
var lastCluster = glyphRun.GlyphClusters[glyphCount];
return lastCluster - firstCluster;
count = lastCluster - firstCluster;
return count > 0;
}
/// <summary>
@ -350,29 +368,38 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth + currentRun.Size.Width > availableWidth)
{
var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth);
var breakFound = false;
var currentBreakPosition = 0;
if (measuredLength < currentRun.Text.Length)
if (TryMeasureCharacters(currentRun, paragraphWidth - currentWidth, out var measuredLength))
{
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
if (measuredLength < currentRun.Text.Length)
{
var nextBreakPosition = lineBreaker.Current.PositionWrap;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
break;
}
var nextBreakPosition = lineBreaker.Current.PositionWrap;
if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
{
break;
}
breakFound = lineBreaker.Current.Required ||
lineBreaker.Current.PositionWrap != currentRun.Text.Length;
breakFound = lineBreaker.Current.Required ||
lineBreaker.Current.PositionWrap != currentRun.Text.Length;
currentBreakPosition = nextBreakPosition;
currentBreakPosition = nextBreakPosition;
}
}
}
else
{
// Make sure we wrap at least one character.
if (currentLength == 0)
{
measuredLength = 1;
}
}

44
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -39,7 +39,9 @@ namespace Avalonia.Media.TextFormatting
foreach (var textRun in _textRuns)
{
using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, 0)))
var offsetY = LineMetrics.TextBaseline;
using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, offsetY)))
{
textRun.Draw(drawingContext);
}
@ -75,37 +77,35 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth > availableWidth)
{
var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth);
var currentBreakPosition = 0;
if (measuredLength < textRange.End)
if (TextFormatterImpl.TryMeasureCharacters(currentRun, availableWidth, out var measuredLength))
{
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord && measuredLength < textRange.End)
{
var nextBreakPosition = lineBreaker.Current.PositionWrap;
var currentBreakPosition = 0;
if (nextBreakPosition == 0)
{
break;
}
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
if (nextBreakPosition > measuredLength)
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
break;
var nextBreakPosition = lineBreaker.Current.PositionWrap;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition > measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
currentBreakPosition = nextBreakPosition;
measuredLength = currentBreakPosition;
}
}
if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord)
{
measuredLength = currentBreakPosition;
}
collapsedLength += measuredLength;
var splitResult = TextFormatterImpl.SplitTextRuns(_textRuns, collapsedLength);

Loading…
Cancel
Save