Skip to content

Commit 610d0fc

Browse files
authored
Merge branch 'master' into typefaceSeperation
2 parents e5c0573 + a533f5f commit 610d0fc

File tree

11 files changed

+205
-51
lines changed

11 files changed

+205
-51
lines changed

src/Avalonia.Base/Media/FontManager.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ public bool TryGetGlyphTypeface(Typeface typeface, [NotNullWhen(true)] out IGlyp
163163
}
164164

165165
//Nothing was found so use the default
166-
return TryGetGlyphTypeface(new Typeface(FontFamily.DefaultFontFamilyName, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
166+
return TryGetGlyphTypeface(new Typeface(DefaultFontFamily, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
167167

168168
FontFamily GetMappedFontFamily(FontFamily fontFamily)
169169
{
@@ -385,6 +385,12 @@ private string GetDefaultFontFamilyName(FontManagerOptions? options)
385385
"Default font family name can't be null or empty.");
386386
}
387387

388+
if (defaultFontFamilyName == FontFamily.DefaultFontFamilyName)
389+
{
390+
throw new InvalidOperationException(
391+
$"'{FontFamily.DefaultFontFamilyName}' is a placeholder and cannot be used as the default font family name. Provide a concrete font family name via {nameof(FontManagerOptions)} or the platform implementation.");
392+
}
393+
388394
return defaultFontFamilyName;
389395
}
390396

src/Avalonia.Base/Media/GlyphRun.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,9 +711,13 @@ private GlyphRunMetrics CreateGlyphRunMetrics()
711711
}
712712
}
713713

714+
var ascent = GlyphTypeface.Metrics.Ascent * Scale;
715+
var lineGap = GlyphTypeface.Metrics.LineGap * Scale;
716+
var baseline = -ascent + lineGap * 0.5;
717+
714718
return new GlyphRunMetrics
715719
{
716-
Baseline = -GlyphTypeface.Metrics.Ascent * Scale,
720+
Baseline = baseline,
717721
Width = width,
718722
WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace,
719723
Height = height,

src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public override int Length
3636

3737
public TextMetrics TextMetrics { get; }
3838

39-
public override double Baseline => -TextMetrics.Ascent;
39+
public override double Baseline => -TextMetrics.Ascent + TextMetrics.LineGap * 0.5;
4040

4141
public override Size Size => GlyphRun.Bounds.Size;
4242

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

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ bool TryMergeWithLastBounds(TextBounds currentBounds, TextBounds lastBounds)
814814
}
815815
}
816816

817-
private CharacterHit GetPreviousCharacterHit(CharacterHit characterHit, bool useGraphemeBoundaries)
817+
private CharacterHit GetPreviousCharacterHit(CharacterHit characterHit, bool isBackspaceDelete)
818818
{
819819
if (_textRuns.Length == 0 || _indexedTextRuns is null)
820820
{
@@ -833,8 +833,6 @@ private CharacterHit GetPreviousCharacterHit(CharacterHit characterHit, bool use
833833
return new CharacterHit(FirstTextSourceIndex);
834834
}
835835

836-
var currentCharacterHit = characterHit;
837-
838836
var currentRun = GetRunAtCharacterIndex(characterIndex, LogicalDirection.Backward, out var currentPosition);
839837

840838
var previousCharacterHit = characterHit;
@@ -843,46 +841,38 @@ private CharacterHit GetPreviousCharacterHit(CharacterHit characterHit, bool use
843841
{
844842
case ShapedTextRun shapedRun:
845843
{
846-
var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster);
844+
//Determine the start of the first hit in local positions.
845+
var runOffset = Math.Max(0, characterIndex - currentPosition);
847846

848-
if (offset > 0)
849-
{
850-
currentCharacterHit = new CharacterHit(Math.Max(0, characterHit.FirstCharacterIndex - offset), characterHit.TrailingLength);
851-
}
847+
var firstCluster = shapedRun.GlyphRun.Metrics.FirstCluster;
852848

853-
previousCharacterHit = shapedRun.GlyphRun.GetPreviousCaretCharacterHit(currentCharacterHit);
849+
//Current position is a text source index and first cluster is relative to the GlyphRun's buffer.
850+
var textSourceOffset = currentPosition - firstCluster;
854851

855-
if (useGraphemeBoundaries)
852+
if (isBackspaceDelete)
856853
{
857-
var textPosition = Math.Max(0, previousCharacterHit.FirstCharacterIndex - shapedRun.GlyphRun.Metrics.FirstCluster);
858-
859-
var text = shapedRun.GlyphRun.Characters.Slice(textPosition);
860-
861-
var graphemeEnumerator = new GraphemeEnumerator(text.Span);
862-
863854
var length = 0;
864855

865-
var clusterLength = Math.Max(0, currentCharacterHit.FirstCharacterIndex + currentCharacterHit.TrailingLength -
866-
previousCharacterHit.FirstCharacterIndex - previousCharacterHit.TrailingLength);
867-
868-
while (graphemeEnumerator.MoveNext(out var grapheme))
856+
while (Codepoint.ReadAt(shapedRun.GlyphRun.Characters.Span, length, out var count) != Codepoint.ReplacementCodepoint)
869857
{
870-
if (length + grapheme.Length < clusterLength)
858+
if (length + count >= runOffset)
871859
{
872-
length += grapheme.Length;
873-
874-
continue;
860+
break;
875861
}
876862

877-
previousCharacterHit = new CharacterHit(previousCharacterHit.FirstCharacterIndex + length);
878-
879-
break;
863+
length += count;
880864
}
881-
}
882865

883-
if (offset > 0)
866+
previousCharacterHit = new CharacterHit(characterIndex - runOffset + length);
867+
}
868+
else
884869
{
885-
previousCharacterHit = new CharacterHit(previousCharacterHit.FirstCharacterIndex + offset, previousCharacterHit.TrailingLength);
870+
previousCharacterHit = shapedRun.GlyphRun.GetPreviousCaretCharacterHit(new CharacterHit(firstCluster + runOffset));
871+
872+
if(textSourceOffset > 0)
873+
{
874+
previousCharacterHit = new CharacterHit(textSourceOffset + previousCharacterHit.FirstCharacterIndex, previousCharacterHit.TrailingLength);
875+
}
886876
}
887877

888878
break;
@@ -1326,8 +1316,6 @@ private TextLineMetrics CreateLineMetrics()
13261316
}
13271317
}
13281318

1329-
var height = descent - ascent + lineGap;
1330-
13311319
var inkBounds = new Rect();
13321320

13331321
for (var index = 0; index < _textRuns.Length; index++)
@@ -1363,6 +1351,28 @@ private TextLineMetrics CreateLineMetrics()
13631351
}
13641352
}
13651353

1354+
var halfLineGap = lineGap * 0.5;
1355+
var naturalHeight = descent - ascent + lineGap;
1356+
var baseline = -ascent + halfLineGap;
1357+
var height = naturalHeight;
1358+
1359+
if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight))
1360+
{
1361+
if (lineHeight <= naturalHeight)
1362+
{
1363+
//Clamp to the specified line height
1364+
height = lineHeight;
1365+
baseline = -ascent;
1366+
}
1367+
else
1368+
{
1369+
// Center the text vertically within the specified line height
1370+
height = lineHeight;
1371+
var extra = lineHeight - (descent - ascent);
1372+
baseline = -ascent + extra / 2;
1373+
}
1374+
}
1375+
13661376
height += lineSpacing;
13671377

13681378
var width = widthIncludingWhitespace;
@@ -1395,24 +1405,14 @@ private TextLineMetrics CreateLineMetrics()
13951405
}
13961406

13971407
var extent = inkBounds.Height;
1398-
//The width of overhanging pixels at the bottom
1399-
var overhangAfter = inkBounds.Bottom - height;
1408+
//The height of overhanging pixels at the bottom
1409+
var overhangAfter = inkBounds.Bottom - height + halfLineGap;
14001410
//The width of overhanging pixels at the natural alignment point. Positive value means we are inside.
14011411
var overhangLeading = inkBounds.Left;
14021412
//The width of overhanging pixels at the end of the natural bounds. Positive value means we are inside.
14031413
var overhangTrailing = widthIncludingWhitespace - inkBounds.Right;
14041414
var hasOverflowed = width > _paragraphWidth;
14051415

1406-
if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight))
1407-
{
1408-
//Center the line
1409-
var offset = (height - lineHeight) / 2;
1410-
1411-
ascent += offset;
1412-
1413-
height = lineHeight;
1414-
}
1415-
14161416
var start = GetParagraphOffsetX(width, widthIncludingWhitespace);
14171417

14181418
_inkBounds = inkBounds.Translate(new Vector(start, 0));
@@ -1426,7 +1426,7 @@ private TextLineMetrics CreateLineMetrics()
14261426
Extent = extent,
14271427
NewlineLength = newLineLength,
14281428
Start = start,
1429-
TextBaseline = -ascent,
1429+
TextBaseline = baseline,
14301430
TrailingWhitespaceLength = trailingWhitespaceLength,
14311431
Width = width,
14321432
WidthIncludingTrailingWhitespace = widthIncludingWhitespace,

src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public TextMetrics(IGlyphTypeface glyphTypeface, double fontRenderingEmSize)
1919

2020
LineGap = fontMetrics.LineGap * scale;
2121

22+
Baseline = -Ascent + LineGap * 0.5;
23+
2224
LineHeight = Descent - Ascent + LineGap;
2325

2426
UnderlineThickness = fontMetrics.UnderlineThickness * scale;
@@ -35,6 +37,11 @@ public TextMetrics(IGlyphTypeface glyphTypeface, double fontRenderingEmSize)
3537
/// </summary>
3638
public double FontRenderingEmSize { get; }
3739

40+
/// <summary>
41+
/// Gets the distance from the top to the baseline of the line of text.
42+
/// </summary>
43+
public double Baseline { get; }
44+
3845
/// <summary>
3946
/// Gets the recommended distance above the baseline.
4047
/// </summary>

src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
</Design.PreviewWith>
1414

1515
<StreamGeometry x:Key="CheckMarkPathData">M5.5 10.586 1.707 6.793A1 1 0 0 0 .293 8.207l4.5 4.5a1 1 0 0 0 1.414 0l11-11A1 1 0 0 0 15.793.293L5.5 10.586Z</StreamGeometry>
16+
<x:Double x:Key="CheckBoxMinHeight">32</x:Double>
1617

1718
<ControlTheme x:Key="{x:Type CheckBox}" TargetType="CheckBox">
1819
<Setter Property="Padding" Value="8,0,0,0" />
@@ -21,7 +22,7 @@
2122
<Setter Property="HorizontalContentAlignment" Value="Left" />
2223
<Setter Property="VerticalContentAlignment" Value="Center" />
2324
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
24-
<Setter Property="MinHeight" Value="32" />
25+
<Setter Property="MinHeight" Value="{DynamicResource CheckBoxMinHeight}" />
2526
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" />
2627
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" />
2728
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUnchecked}" />
@@ -35,7 +36,7 @@
3536
BorderThickness="{TemplateBinding BorderThickness}"
3637
CornerRadius="{TemplateBinding CornerRadius}" />
3738

38-
<Grid VerticalAlignment="Top" Height="32">
39+
<Grid VerticalAlignment="Top" Height="{DynamicResource CheckBoxMinHeight}">
3940
<Border x:Name="NormalRectangle"
4041
BorderBrush="{DynamicResource CheckBoxCheckBackgroundStrokeUnchecked}"
4142
Background="{DynamicResource CheckBoxCheckBackgroundFillUnchecked}"

src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
</Border>
1212
</Design.PreviewWith>
1313

14+
<x:Double x:Key="RadioButtonMinHeight">32</x:Double>
15+
1416
<ControlTheme x:Key="{x:Type RadioButton}" TargetType="RadioButton">
1517
<Setter Property="Background" Value="{DynamicResource RadioButtonBackground}" />
1618
<Setter Property="Foreground" Value="{DynamicResource RadioButtonForeground}" />
@@ -30,7 +32,7 @@
3032
BorderThickness="{TemplateBinding BorderThickness}"
3133
CornerRadius="{TemplateBinding CornerRadius}">
3234
<Grid ColumnDefinitions="20,*">
33-
<Grid Height="32" VerticalAlignment="Top">
35+
<Grid Height="{DynamicResource RadioButtonMinHeight}" VerticalAlignment="Top">
3436

3537
<Ellipse
3638
Name="OuterEllipse"

src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@
2121
<x:Double x:Key="TabItemMinHeight">28</x:Double>
2222
<Thickness x:Key="TabItemHeaderMargin">6, 0</Thickness>
2323
<Thickness x:Key="ButtonPadding">6,4</Thickness>
24+
<x:Double x:Key="CheckBoxMinHeight">24</x:Double>
25+
<x:Double x:Key="RadioButtonMinHeight">24</x:Double>
2426
</ResourceDictionary>
Binary file not shown.

tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,25 @@ public void Should_Get_Distance_From_CharacterHit_Within_Cluster()
379379
}
380380
}
381381

382+
[Fact]
383+
public void Should_Add_Half_LineGap_To_Baseline()
384+
{
385+
using (Start())
386+
{
387+
var typeface = new Typeface("resm:Avalonia.Skia.UnitTests.Fonts?assembly=Avalonia.Skia.UnitTests#Inter");
388+
var options = new TextShaperOptions(typeface.GlyphTypeface, 14);
389+
var shapedBuffer = TextShaper.Current.ShapeText("F", options);
390+
391+
var textMetrics = new TextMetrics(shapedBuffer.GlyphTypeface, 14);
392+
393+
var glyphRun = CreateGlyphRun(shapedBuffer);
394+
395+
var expectedBaseline = -textMetrics.Ascent + textMetrics.LineGap / 2;
396+
397+
Assert.Equal(expectedBaseline, glyphRun.Metrics.Baseline);
398+
}
399+
}
400+
382401
private static List<Rect> BuildRects(GlyphRun glyphRun)
383402
{
384403
var height = glyphRun.Bounds.Height;

0 commit comments

Comments
 (0)