Browse Source

Initial implementation of TextAlignment.Justify

pull/8347/head
Benedikt Stebner 4 years ago
parent
commit
a794d1765a
  1. 7
      src/Avalonia.Base/Media/GlyphRun.cs
  2. 5
      src/Avalonia.Base/Media/TextAlignment.cs
  3. 4
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  4. 20
      src/Avalonia.Base/Media/TextFormatting/TextLine.cs
  5. 111
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  6. 4
      src/Avalonia.Controls/TextBlock.cs

7
src/Avalonia.Base/Media/GlyphRun.cs

@ -740,10 +740,9 @@ namespace Avalonia.Media
private void Set<T>(ref T field, T value)
{
if (_glyphRunImpl != null)
{
throw new InvalidOperationException("GlyphRun can't be changed after it has been initialized.'");
}
_glyphRunImpl?.Dispose();
_glyphRunImpl = null;
_glyphRunMetrics = null;

5
src/Avalonia.Base/Media/TextAlignment.cs

@ -19,5 +19,10 @@ namespace Avalonia.Media
/// The text is right-aligned.
/// </summary>
Right,
/// <summary>
/// The text is layed out so each line is stretched to an equal width.
/// </summary>
Justify
}
}

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

@ -431,9 +431,9 @@ namespace Avalonia.Media.TextFormatting
break;
}
case DrawableTextRun drawableTextRun:
default:
{
textRuns.Add(drawableTextRun);
textRuns.Add(textRun);
break;
}
}

20
src/Avalonia.Base/Media/TextFormatting/TextLine.cs

@ -218,24 +218,14 @@ namespace Avalonia.Media.TextFormatting
return Math.Max(0, (paragraphWidth - width) / 2);
case TextAlignment.Right:
return Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace);
return flowDirection == FlowDirection.LeftToRight ? Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace) : 0;
default:
return 0;
case TextAlignment.Left:
return flowDirection == FlowDirection.LeftToRight ? 0 : Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace);
}
}
switch (textAlignment)
{
case TextAlignment.Center:
return Math.Max(0, (paragraphWidth - width) / 2);
case TextAlignment.Right:
return 0;
default:
return Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace);
}
return 0;
}
}
}

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

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
@ -708,11 +709,121 @@ namespace Avalonia.Media.TextFormatting
{
_textLineMetrics = CreateLineMetrics();
Justify();
BidiReorder();
return this;
}
private void Justify()
{
if (_paragraphProperties.TextAlignment != TextAlignment.Justify)
{
return;
}
var paragraphWidth = _paragraphWidth;
if (double.IsInfinity(paragraphWidth))
{
return;
}
if(_textLineMetrics.NewLineLength > 0)
{
return;
}
if(TextLineBreak is not null && TextLineBreak.TextEndOfLine is not null)
{
if(TextLineBreak.RemainingRuns is null || TextLineBreak.RemainingRuns.Count == 0)
{
return;
}
}
var breakOportunities = new Queue<int>();
foreach (var textRun in TextRuns)
{
var text = textRun.Text;
if (text.IsEmpty)
{
continue;
}
var start = text.Start;
var lineBreakEnumerator = new LineBreakEnumerator(text);
while (lineBreakEnumerator.MoveNext())
{
var currentBreak = lineBreakEnumerator.Current;
if (!currentBreak.Required && currentBreak.PositionWrap != text.Length)
{
breakOportunities.Enqueue(start + currentBreak.PositionMeasure);
}
}
}
if(breakOportunities.Count == 0)
{
return;
}
var remainingSpace = Math.Max(0, paragraphWidth - WidthIncludingTrailingWhitespace);
var spacing = remainingSpace / breakOportunities.Count;
foreach (var textRun in TextRuns)
{
var text = textRun.Text;
if (text.IsEmpty)
{
continue;
}
if(textRun is ShapedTextCharacters shapedText)
{
var glyphRun = shapedText.GlyphRun;
var shapedBuffer = shapedText.ShapedBuffer;
var currentPosition = text.Start;
while(breakOportunities.Count > 0)
{
var characterIndex = breakOportunities.Dequeue();
if (characterIndex < currentPosition)
{
continue;
}
var glyphIndex = glyphRun.FindGlyphIndex(characterIndex);
var glyphInfo = shapedBuffer.GlyphInfos[glyphIndex];
shapedBuffer.GlyphInfos[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex, glyphInfo.GlyphCluster, glyphInfo.GlyphAdvance + spacing);
}
glyphRun.GlyphAdvances = shapedBuffer.GlyphAdvances;
}
}
var trailingWhitespaceWidth = _textLineMetrics.WidthIncludingTrailingWhitespace - _textLineMetrics.Width;
_textLineMetrics = new TextLineMetrics(
_textLineMetrics.HasOverflowed,
_textLineMetrics.Height,
_textLineMetrics.NewLineLength,
_textLineMetrics.Start,
_textLineMetrics.TextBaseline,
_textLineMetrics.TrailingWhitespaceLength,
paragraphWidth - trailingWhitespaceWidth,
paragraphWidth);
}
private static sbyte GetRunBidiLevel(DrawableTextRun run, FlowDirection flowDirection)
{
if (run is ShapedTextCharacters shapedTextCharacters)

4
src/Avalonia.Controls/TextBlock.cs

@ -748,14 +748,14 @@ namespace Avalonia.Controls
{
if (textSourceIndex > _text.Length)
{
return null;
return new TextEndOfParagraph();
}
var runText = _text.Skip(textSourceIndex);
if (runText.IsEmpty)
{
return null;
return new TextEndOfParagraph();
}
return new TextCharacters(runText, _defaultProperties);

Loading…
Cancel
Save