diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index 09f86f462c..6e534bbb2a 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -273,7 +273,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText
{
Constraint = constraint,
- Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
+ Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
FontSize = FontSize,
Text = text ?? string.Empty,
TextAlignment = TextAlignment,
@@ -490,7 +490,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText
{
Text = "X",
- Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
+ Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
FontSize = FontSize,
TextAlignment = TextAlignment,
Constraint = availableSize,
diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs
index dd33023e38..89f672deaa 100644
--- a/src/Avalonia.Controls/Primitives/AccessText.cs
+++ b/src/Avalonia.Controls/Primitives/AccessText.cs
@@ -97,9 +97,7 @@ namespace Avalonia.Controls.Primitives
{
var lastLine = TextLayout.TextLines[TextLayout.TextLines.Count - 1];
- var offsetX = lastLine.LineMetrics.BaselineOrigin.X;
-
- var lineX = offsetX + lastLine.LineMetrics.Size.Width;
+ var lineX = lastLine.LineMetrics.Size.Width;
var lineY = Bounds.Height - lastLine.LineMetrics.Size.Height;
@@ -117,7 +115,7 @@ namespace Avalonia.Controls.Primitives
continue;
}
- var currentX = textLine.LineMetrics.BaselineOrigin.X;
+ var currentX = 0.0;
foreach (var textRun in textLine.TextRuns)
{
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index 2361ea9011..7e5287f81f 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -411,9 +411,30 @@ namespace Avalonia.Controls
context.FillRectangle(background, new Rect(Bounds.Size));
}
+ if (TextLayout is null)
+ {
+ return;
+ }
+
+ var textAlignment = TextAlignment;
+
+ var width = Bounds.Size.Width;
+
+ var offsetX = 0.0;
+
+ switch (textAlignment)
+ {
+ case TextAlignment.Center:
+ offsetX = (width - TextLayout.Size.Width) / 2;
+ break;
+ case TextAlignment.Right:
+ offsetX = width - TextLayout.Size.Width;
+ break;
+ }
+
var padding = Padding;
- TextLayout?.Draw(context, new Point(padding.Left, padding.Top));
+ TextLayout.Draw(context, new Point(padding.Left + offsetX, padding.Top));
}
///
@@ -431,7 +452,7 @@ namespace Avalonia.Controls
return new TextLayout(
text ?? string.Empty,
- FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
+ FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
FontSize,
Foreground,
TextAlignment,
@@ -470,12 +491,12 @@ namespace Avalonia.Controls
if (_constraint != availableSize)
{
+ _constraint = availableSize;
+
InvalidateTextLayout();
}
- _constraint = availableSize;
-
- var measuredSize = TextLayout?.Bounds.Size ?? Size.Empty;
+ var measuredSize = TextLayout?.Size ?? Size.Empty;
return measuredSize.Inflate(padding);
}
diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs
index bc17fb3faa..763d192693 100644
--- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs
@@ -54,7 +54,7 @@ namespace Avalonia.Headless
class HeadlessCursorFactoryStub : IStandardCursorFactory
{
-
+
public IPlatformHandle GetCursor(StandardCursorType cursorType)
{
return new PlatformHandle(new IntPtr((int)cursorType), "STUB");
@@ -101,7 +101,7 @@ namespace Avalonia.Headless
public bool IsFixedPitch => true;
public void Dispose()
- {
+ {
}
public ushort GetGlyph(uint codepoint)
@@ -155,9 +155,9 @@ namespace Avalonia.Headless
return new List { "Arial" };
}
- public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle, FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
+ public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
{
- fontKey = new FontKey("Arial", fontWeight, fontStyle);
+ fontKey = new FontKey("Arial", fontStyle, fontWeight);
return true;
}
}
@@ -169,7 +169,7 @@ namespace Avalonia.Headless
{
public void Save(Stream outputStream)
{
-
+
}
}
public IWindowIconImpl LoadIcon(string fileName)
diff --git a/src/Avalonia.Visuals/Media/FontManager.cs b/src/Avalonia.Visuals/Media/FontManager.cs
index bc979c15ee..ad3fee7eb7 100644
--- a/src/Avalonia.Visuals/Media/FontManager.cs
+++ b/src/Avalonia.Visuals/Media/FontManager.cs
@@ -79,12 +79,13 @@ namespace Avalonia.Media
/// Returns a new typeface, or an existing one if a matching typeface exists.
///
/// The font family.
- /// The font weight.
/// The font style.
+ /// The font weight.
///
/// The typeface.
///
- public Typeface GetOrAddTypeface(FontFamily fontFamily, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
+ public Typeface GetOrAddTypeface(FontFamily fontFamily, FontStyle fontStyle = FontStyle.Normal,
+ FontWeight fontWeight = FontWeight.Normal)
{
while (true)
{
@@ -93,7 +94,7 @@ namespace Avalonia.Media
fontFamily = _defaultFontFamily;
}
- var key = new FontKey(fontFamily.Name, fontWeight, fontStyle);
+ var key = new FontKey(fontFamily.Name, fontStyle, fontWeight);
if (_typefaceCache.TryGetValue(key, out var typeface))
{
@@ -121,15 +122,16 @@ namespace Avalonia.Media
/// Returns null if no fallback was found.
///
/// The codepoint to match against.
- /// The font weight.
/// The font style.
+ /// The font weight.
/// The font family. This is optional and used for fallback lookup.
/// The culture.
///
/// The matched typeface.
///
- public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = FontWeight.Normal,
+ public Typeface MatchCharacter(int codepoint,
FontStyle fontStyle = FontStyle.Normal,
+ FontWeight fontWeight = FontWeight.Normal,
FontFamily fontFamily = null, CultureInfo culture = null)
{
foreach (var cachedTypeface in _typefaceCache.Values)
@@ -142,7 +144,7 @@ namespace Avalonia.Media
}
}
- var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontWeight, fontStyle, fontFamily, culture, out var key) ?
+ var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out var key) ?
_typefaceCache.GetOrAdd(key, new Typeface(key.FamilyName, key.Style, key.Weight)) :
null;
diff --git a/src/Avalonia.Visuals/Media/Fonts/FontKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontKey.cs
index a8d81648ba..b330db8462 100644
--- a/src/Avalonia.Visuals/Media/Fonts/FontKey.cs
+++ b/src/Avalonia.Visuals/Media/Fonts/FontKey.cs
@@ -4,7 +4,7 @@ namespace Avalonia.Media.Fonts
{
public readonly struct FontKey : IEquatable
{
- public FontKey(string familyName, FontWeight weight, FontStyle style)
+ public FontKey(string familyName, FontStyle style, FontWeight weight)
{
FamilyName = familyName;
Style = style;
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
index 2e7e7aceb1..b71fe5bc3c 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
@@ -89,16 +89,7 @@ namespace Avalonia.Media.TextFormatting
/// The split result.
public SplitTextCharactersResult Split(int length)
{
- var glyphCount = 0;
-
- var firstCharacters = GlyphRun.Characters.Take(length);
-
- var codepointEnumerator = new CodepointEnumerator(firstCharacters);
-
- while (codepointEnumerator.MoveNext())
- {
- glyphCount++;
- }
+ var glyphCount = GlyphRun.FindGlyphIndex(GlyphRun.Characters.Start + length);
if (GlyphRun.Characters.Length == length)
{
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
index b35882fc0e..47e716982c 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
@@ -71,7 +71,7 @@ namespace Avalonia.Media.TextFormatting
//ToDo: Fix FontFamily fallback
currentTypeface =
- FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Weight, defaultTypeface.Style, defaultTypeface.FontFamily);
+ FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.FontFamily);
if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
{
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
index 793707d0b2..3ad23f3504 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
@@ -72,6 +72,11 @@ namespace Avalonia.Media.TextFormatting
{
foreach (var shapedCharacters in previousLineBreak.RemainingCharacters)
{
+ if (shapedCharacters == null)
+ {
+ continue;
+ }
+
textRuns.Add(shapedCharacters);
if (TryGetLineBreak(shapedCharacters, out var runLineBreak))
@@ -106,7 +111,7 @@ namespace Avalonia.Media.TextFormatting
var glyphRun = TextShaper.Current.ShapeText(run.Text, run.Properties.Typeface,
run.Properties.FontRenderingEmSize, run.Properties.CultureInfo);
- var shapedCharacters = new ShapedTextCharacters(glyphRun, textRun.Properties);
+ var shapedCharacters = new ShapedTextCharacters(glyphRun, run.Properties);
textRuns.Add(shapedCharacters);
}
@@ -355,9 +360,67 @@ namespace Avalonia.Media.TextFormatting
{
var glyphRun = textCharacters.GlyphRun;
- var characterHit = glyphRun.GetCharacterHitFromDistance(availableWidth, out _);
+ if (glyphRun.Bounds.Width < availableWidth)
+ {
+ return glyphRun.Characters.Length;
+ }
+
+ var glyphCount = 0;
+
+ var currentWidth = 0.0;
+
+ if (glyphRun.GlyphAdvances.IsEmpty)
+ {
+ var glyphTypeface = glyphRun.GlyphTypeface;
+
+ for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
+ {
+ var glyph = glyphRun.GlyphIndices[i];
+
+ var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
+
+ if (currentWidth + advance > availableWidth)
+ {
+ break;
+ }
+
+ currentWidth += advance;
+
+ glyphCount++;
+ }
+ }
+ else
+ {
+ for (var i = 0; i < glyphRun.GlyphAdvances.Length; i++)
+ {
+ var advance = glyphRun.GlyphAdvances[i];
+
+ if (currentWidth + advance > availableWidth)
+ {
+ break;
+ }
+
+ currentWidth += advance;
+
+ glyphCount++;
+ }
+ }
+
+ if (glyphCount == glyphRun.GlyphIndices.Length)
+ {
+ return glyphRun.Characters.Length;
+ }
+
+ if (glyphRun.GlyphClusters.IsEmpty)
+ {
+ return glyphCount;
+ }
+
+ var firstCluster = glyphRun.GlyphClusters[0];
+
+ var lastCluster = glyphRun.GlyphClusters[glyphCount];
- return characterHit.FirstCharacterIndex + characterHit.TrailingLength - textCharacters.Text.Start;
+ return lastCluster - firstCluster;
}
///
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
index 2e2e4a8c68..54745144c8 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
@@ -103,12 +103,12 @@ namespace Avalonia.Media.TextFormatting
public IReadOnlyList TextLines { get; private set; }
///
- /// Gets the bounds of the layout.
+ /// Gets the size of the layout.
///
///
/// The bounds.
///
- public Rect Bounds { get; private set; }
+ public Size Size { get; private set; }
///
/// Draws the text layout.
@@ -126,7 +126,10 @@ namespace Avalonia.Media.TextFormatting
foreach (var textLine in TextLines)
{
- textLine.Draw(context, new Point(origin.X, currentY));
+ var offsetX = TextLine.GetParagraphOffsetX(textLine.LineMetrics.Size.Width, Size.Width,
+ _paragraphProperties.TextAlignment);
+
+ textLine.Draw(context, new Point(origin.X + offsetX, currentY));
currentY += textLine.LineMetrics.Size.Height;
}
@@ -158,22 +161,16 @@ namespace Avalonia.Media.TextFormatting
/// Updates the current bounds.
///
/// The text line.
- /// The left.
- /// The right.
- /// The bottom.
- private static void UpdateBounds(TextLine textLine, ref double left, ref double right, ref double bottom)
+ /// The current width.
+ /// The current height.
+ private static void UpdateBounds(TextLine textLine, ref double width, ref double height)
{
- if (right < textLine.LineMetrics.BaselineOrigin.X + textLine.LineMetrics.Size.Width)
- {
- right = textLine.LineMetrics.BaselineOrigin.X + textLine.LineMetrics.Size.Width;
- }
-
- if (left < textLine.LineMetrics.BaselineOrigin.X)
+ if (width < textLine.LineMetrics.Size.Width)
{
- left = textLine.LineMetrics.BaselineOrigin.X;
+ width = textLine.LineMetrics.Size.Width;
}
- bottom += textLine.LineMetrics.Size.Height;
+ height += textLine.LineMetrics.Size.Height;
}
///
@@ -204,13 +201,13 @@ namespace Avalonia.Media.TextFormatting
TextLines = new List { textLine };
- Bounds = new Rect(textLine.LineMetrics.BaselineOrigin.X, 0, 0, textLine.LineMetrics.Size.Height);
+ Size = new Size(0, textLine.LineMetrics.Size.Height);
}
else
{
var textLines = new List();
- double left = 0.0, right = 0.0, bottom = 0.0;
+ double width = 0.0, height = 0.0;
var currentPosition = 0;
@@ -228,9 +225,9 @@ namespace Avalonia.Media.TextFormatting
textLines.Add(textLine);
- UpdateBounds(textLine, ref left, ref right, ref bottom);
+ UpdateBounds(textLine, ref width, ref height);
- if (!double.IsPositiveInfinity(MaxHeight) && bottom > MaxHeight)
+ if (!double.IsPositiveInfinity(MaxHeight) && height > MaxHeight)
{
break;
}
@@ -247,7 +244,7 @@ namespace Avalonia.Media.TextFormatting
textLines.Add(emptyTextLine);
}
- Bounds = new Rect(left, 0, right, bottom);
+ Size = new Size(width, height);
TextLines = textLines;
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
index cf00399b8a..a1a9b50793 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
@@ -33,8 +33,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textRun in _textRuns)
{
- var baselineOrigin = new Point(currentX + LineMetrics.BaselineOrigin.X,
- origin.Y + LineMetrics.BaselineOrigin.Y);
+ var baselineOrigin = new Point(currentX, origin.Y + LineMetrics.TextBaseline);
textRun.Draw(drawingContext, baselineOrigin);
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
index d47cc0c394..2f7809ff35 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
@@ -9,10 +9,10 @@ namespace Avalonia.Media.TextFormatting
///
public readonly struct TextLineMetrics
{
- public TextLineMetrics(Size size, Point baselineOrigin, TextRange textRange)
+ public TextLineMetrics(Size size, double textBaseline, TextRange textRange)
{
Size = size;
- BaselineOrigin = baselineOrigin;
+ TextBaseline = textBaseline;
TextRange = textRange;
}
@@ -33,12 +33,9 @@ namespace Avalonia.Media.TextFormatting
public Size Size { get; }
///
- /// Gets the baseline origin.
+ /// Gets the distance from the top to the baseline of the line of text.
///
- ///
- /// The baseline origin.
- ///
- public Point BaselineOrigin { get; }
+ public double TextBaseline { get; }
///
/// Creates the text line metrics.
@@ -81,16 +78,12 @@ namespace Avalonia.Media.TextFormatting
}
}
- var xOrigin = TextLine.GetParagraphOffsetX(lineWidth, paragraphWidth, paragraphProperties.TextAlignment);
-
- var baselineOrigin = new Point(xOrigin, -ascent);
-
var size = new Size(lineWidth,
double.IsNaN(paragraphProperties.LineHeight) || MathUtilities.IsZero(paragraphProperties.LineHeight) ?
descent - ascent + lineGap :
paragraphProperties.LineHeight);
- return new TextLineMetrics(size, baselineOrigin, textRange);
+ return new TextLineMetrics(size, -ascent, textRange);
}
}
}
diff --git a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
index 3d0bea8c80..59b08aae0a 100644
--- a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
@@ -22,15 +22,16 @@ namespace Avalonia.Platform
/// Tries to match a specified character to a typeface that supports specified font properties.
///
/// The codepoint to match against.
- /// The font weight.
/// The font style.
+ /// The font weight.
/// The font family. This is optional and used for fallback lookup.
/// The culture.
/// The matching font key.
///
/// True, if the could match the character to specified parameters, False otherwise.
///
- bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle,
+ bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
+ FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey);
///
diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs
index 415a89e1c1..91bc937475 100644
--- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs
+++ b/src/Skia/Avalonia.Skia/FontManagerImpl.cs
@@ -29,7 +29,8 @@ namespace Avalonia.Skia
[ThreadStatic] private static string[] t_languageTagBuffer;
- public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle,
+ public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
+ FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
{
SKFontStyle skFontStyle;
@@ -80,7 +81,7 @@ namespace Avalonia.Skia
continue;
}
- fontKey = new FontKey(skTypeface.FamilyName, fontWeight, fontStyle);
+ fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight);
return true;
}
@@ -91,7 +92,7 @@ namespace Avalonia.Skia
if (skTypeface != null)
{
- fontKey = new FontKey(skTypeface.FamilyName, fontWeight, fontStyle);
+ fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight);
return true;
}
diff --git a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs
index 0fdea5ed40..f59a0a32c2 100644
--- a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs
+++ b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Platform;
+using JetBrains.Annotations;
using SkiaSharp;
namespace Avalonia.Skia
@@ -7,9 +8,9 @@ namespace Avalonia.Skia
///
public class GlyphRunImpl : IGlyphRunImpl
{
- public GlyphRunImpl(SKTextBlob textBlob)
+ public GlyphRunImpl([NotNull] SKTextBlob textBlob)
{
- TextBlob = textBlob;
+ TextBlob = textBlob ?? throw new ArgumentNullException (nameof (textBlob));
}
///
diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
index 7aea90e61e..6c2ac17923 100644
--- a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
+++ b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
@@ -19,42 +19,49 @@ namespace Avalonia.Skia
public SKTypeface Get(Typeface typeface)
{
- var key = new FontKey(typeface.FontFamily.Name, typeface.Weight, typeface.Style);
+ var key = new FontKey(typeface.FontFamily.Name, typeface.Style, typeface.Weight);
return GetNearestMatch(_typefaces, key);
}
private static SKTypeface GetNearestMatch(IDictionary typefaces, FontKey key)
{
- if (typefaces.ContainsKey(key))
+ if (typefaces.TryGetValue(new FontKey(key.FamilyName, key.Style, key.Weight), out var typeface))
{
- return typefaces[key];
+ return typeface;
}
- var keys = typefaces.Keys.Where(
- x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && x.Style == key.Style).ToArray();
+ var weight = (int)key.Weight;
- if (!keys.Any())
- {
- keys = typefaces.Keys.Where(
- x => x.Weight == key.Weight && (x.Style >= key.Style || x.Style < key.Style)).ToArray();
+ weight -= weight % 100; // make sure we start at a full weight
- if (!keys.Any())
+ for (var i = (int)key.Style; i < 2; i++)
+ {
+ // only try 2 font weights in each direction
+ for (var j = 0; j < 200; j += 100)
{
- keys = typefaces.Keys.Where(
- x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) &&
- (x.Style >= key.Style || x.Style < key.Style)).ToArray();
- }
- }
+ if (weight - j >= 100)
+ {
+ if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight - j)), out typeface))
+ {
+ return typeface;
+ }
+ }
- if (keys.Length == 0)
- {
- return null;
- }
+ if (weight + j > 900)
+ {
+ continue;
+ }
- key = keys[0];
+ if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight + j)), out typeface))
+ {
+ return typeface;
+ }
+ }
+ }
- return typefaces[key];
+ //Nothing was found so we use the first typeface we can get.
+ return typefaces.Values.FirstOrDefault();
}
}
}
diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
index a9aed80a04..d36baf331d 100644
--- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
+++ b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
@@ -43,18 +43,20 @@ namespace Avalonia.Skia
{
var assetStream = assetLoader.Open(asset);
- if (assetStream == null) throw new InvalidOperationException("Asset could not be loaded.");
+ if (assetStream == null)
+ throw new InvalidOperationException("Asset could not be loaded.");
var typeface = SKTypeface.FromStream(assetStream);
- if(typeface == null) throw new InvalidOperationException("Typeface could not be loaded.");
+ if (typeface == null)
+ throw new InvalidOperationException("Typeface could not be loaded.");
if (typeface.FamilyName != fontFamily.Name)
{
continue;
}
- var key = new FontKey(fontFamily.Name, (FontWeight)typeface.FontWeight, (FontStyle)typeface.FontSlant);
+ var key = new FontKey(fontFamily.Name, (FontStyle)typeface.FontSlant, (FontWeight)typeface.FontWeight);
typeFaceCollection.AddTypeface(key, typeface);
}
diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
index 786af7726c..ffe1175567 100644
--- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs
+++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
@@ -15,51 +15,7 @@ namespace Avalonia.Skia
{
using (var buffer = new Buffer())
{
- buffer.ContentType = ContentType.Unicode;
-
- var breakCharPosition = text.Length - 1;
-
- var codepoint = Codepoint.ReadAt(text, breakCharPosition, out var count);
-
- if (codepoint.IsBreakChar)
- {
- var breakCharCount = 1;
-
- if (text.Length > 1)
- {
- var previousCodepoint = Codepoint.ReadAt(text, breakCharPosition - count, out _);
-
- if (codepoint == '\r' && previousCodepoint == '\n'
- || codepoint == '\n' && previousCodepoint == '\r')
- {
- breakCharCount = 2;
- }
- }
-
- if (breakCharPosition != text.Start)
- {
- buffer.AddUtf16(text.Buffer.Span.Slice(0, text.Length - breakCharCount));
- }
-
- var cluster = buffer.GlyphInfos.Length > 0 ?
- buffer.GlyphInfos[buffer.Length - 1].Cluster + 1 :
- (uint)text.Start;
-
- switch (breakCharCount)
- {
- case 1:
- buffer.Add('\u200C', cluster);
- break;
- case 2:
- buffer.Add('\u200C', cluster);
- buffer.Add('\u200D', cluster);
- break;
- }
- }
- else
- {
- buffer.AddUtf16(text.Buffer.Span);
- }
+ FillBuffer(buffer, text);
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
@@ -93,7 +49,7 @@ namespace Avalonia.Skia
{
glyphIndices[i] = (ushort)glyphInfos[i].Codepoint;
- clusters[i] = (ushort)(text.Start + glyphInfos[i].Cluster);
+ clusters[i] = (ushort)glyphInfos[i].Cluster;
if (!glyphTypeface.IsFixedPitch)
{
@@ -112,6 +68,51 @@ namespace Avalonia.Skia
}
}
+ private static void FillBuffer(Buffer buffer, ReadOnlySlice text)
+ {
+ buffer.ContentType = ContentType.Unicode;
+
+ var i = 0;
+
+ while (i < text.Length)
+ {
+ var codepoint = Codepoint.ReadAt(text, i, out var count);
+
+ var cluster = (uint)(text.Start + i);
+
+ if (codepoint.IsBreakChar)
+ {
+ if (i < text.End)
+ {
+ var nextCodepoint = Codepoint.ReadAt(text, i + 1, out _);
+
+ if (nextCodepoint == '\r' && codepoint == '\n' || nextCodepoint == '\n' && codepoint == '\r')
+ {
+ count++;
+
+ buffer.Add('\u200C', cluster);
+
+ buffer.Add('\u200D', cluster);
+ }
+ else
+ {
+ buffer.Add('\u200C', cluster);
+ }
+ }
+ else
+ {
+ buffer.Add('\u200C', cluster);
+ }
+ }
+ else
+ {
+ buffer.Add(codepoint, cluster);
+ }
+
+ i += count;
+ }
+ }
+
private static void SetOffset(ReadOnlySpan glyphPositions, int index, double textScale,
ref Vector[] offsetBuffer)
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
index 253a373106..33af15076d 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
@@ -32,7 +32,8 @@ namespace Avalonia.Direct2D1.Media
return fontFamilies;
}
- public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle,
+ public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
+ FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
{
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
@@ -50,7 +51,7 @@ namespace Avalonia.Direct2D1.Media
var fontFamilyName = font.FontFamily.FamilyNames.GetString(0);
- fontKey = new FontKey(fontFamilyName, fontWeight, fontStyle);
+ fontKey = new FontKey(fontFamilyName, fontStyle, fontWeight);
return true;
}
diff --git a/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs b/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
index e65cdf0312..8683da9a01 100644
--- a/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
+++ b/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
@@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
var r = AvaloniaLocator.Current.GetService();
return r.CreateFormattedText(text,
- FontManager.Current.GetOrAddTypeface(fontFamily, fontWeight, fontStyle),
+ FontManager.Current.GetOrAddTypeface(fontFamily, fontStyle, fontWeight),
fontSize,
textAlignment,
wrapping,
diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
index feed1179ef..f36d6d9e4a 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
@@ -38,7 +38,7 @@ namespace Avalonia.Skia.UnitTests.Media
private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
- public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle, FontFamily fontFamily,
+ public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out FontKey fontKey)
{
foreach (var customTypeface in _customTypefaces)
@@ -48,7 +48,7 @@ namespace Avalonia.Skia.UnitTests.Media
continue;
}
- fontKey = new FontKey(customTypeface.FontFamily.Name, fontWeight, fontStyle);
+ fontKey = new FontKey(customTypeface.FontFamily.Name, fontStyle, fontWeight);
return true;
}
@@ -56,7 +56,7 @@ namespace Avalonia.Skia.UnitTests.Media
var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
- fontKey = new FontKey(fallback?.FamilyName ?? _defaultFamilyName, fontWeight, fontStyle);
+ fontKey = new FontKey(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
return true;
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
index 697cc4fec7..4a88b259bc 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -260,6 +260,39 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
+ [Fact]
+ public void Should_Not_Produce_TextLine_Wider_Than_ParagraphWidth()
+ {
+ using (Start())
+ {
+ const string text =
+ "Multiline TextBlock with TextWrapping.\r\rLorem ipsum dolor sit amet, consectetur adipiscing elit. " +
+ "Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. " +
+ "Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. " +
+ "Vivamus pretium ornare est.";
+
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+
+ var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrapping: TextWrapping.Wrap);
+
+ var textSource = new SingleBufferTextSource(text, defaultProperties);
+
+ var formatter = new TextFormatterImpl();
+
+ var textSourceIndex = 0;
+
+ while (textSourceIndex < text.Length)
+ {
+ var textLine =
+ formatter.FormatLine(textSource, textSourceIndex, 200, paragraphProperties);
+
+ Assert.True(textLine.LineMetrics.Size.Width <= 200);
+
+ textSourceIndex += textLine.TextRange.Length;
+ }
+ }
+ }
+
public static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
index 5d9aa2cf97..43a791b2cb 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
@@ -512,7 +512,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(numberOfLines, layout.TextLines.Count);
- Assert.Equal(numberOfLines * lineHeight, layout.Bounds.Height);
+ Assert.Equal(numberOfLines * lineHeight, layout.Size.Height);
}
}
diff --git a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs
index 55656fcfc0..e614c60310 100644
--- a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs
+++ b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs
@@ -25,7 +25,7 @@ namespace Avalonia.UnitTests
return new[] { _defaultFamilyName };
}
- public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle, FontFamily fontFamily,
+ public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out FontKey fontKey)
{
fontKey = default;
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
index e219682fa6..42e573c8a5 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
@@ -34,6 +34,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Background = Brushes.Red,
Child = textBlock = new TextBlock
{
+ TextWrapping = TextWrapping.NoWrap,
Text = "Hello World",
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Utilities/ReadOnlySpanTests.cs b/tests/Avalonia.Visuals.UnitTests/Utilities/ReadOnlySpanTests.cs
new file mode 100644
index 0000000000..1afd84e546
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/Utilities/ReadOnlySpanTests.cs
@@ -0,0 +1,37 @@
+using System.Linq;
+using Avalonia.Utilities;
+using Xunit;
+
+namespace Avalonia.Visuals.UnitTests.Utilities
+{
+ public class ReadOnlySpanTests
+ {
+ [Fact]
+ public void Should_Skip()
+ {
+ var buffer = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+
+ var slice = new ReadOnlySlice(buffer);
+
+ var skipped = slice.Skip(2);
+
+ var expected = buffer.Skip(2);
+
+ Assert.Equal(expected, skipped);
+ }
+
+ [Fact]
+ public void Should_Take()
+ {
+ var buffer = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+
+ var slice = new ReadOnlySlice(buffer);
+
+ var taken = slice.Take(8);
+
+ var expected = buffer.Take(8);
+
+ Assert.Equal(expected, taken);
+ }
+ }
+}