|
|
@@ -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)
|