diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyTextIsNotMissing.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyTextIsNotMissing.png
new file mode 100644
index 000000000000..8a99aedaf819
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyTextIsNotMissing.png differ
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue17884.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue17884.xaml
new file mode 100644
index 000000000000..835f8c186991
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue17884.xaml
@@ -0,0 +1,20 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue17884.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue17884.xaml.cs
new file mode 100644
index 000000000000..b2fc8ba1fecc
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue17884.xaml.cs
@@ -0,0 +1,17 @@
+
+namespace Maui.Controls.Sample.Issues
+{
+ [Issue(IssueTracker.Github, 17884, "[Android] Entire words omitted & letters truncated from Label display", PlatformAffected.Android)]
+ public partial class Issue17884 : ContentPage
+ {
+ public Issue17884()
+ {
+ InitializeComponent();
+ }
+
+ private void OnStubLabelTapped(object sender, EventArgs e)
+ {
+ ++StubLabel.FontSize;
+ }
+ }
+}
diff --git a/src/Controls/tests/TestCases.HostApp/MauiProgram.cs b/src/Controls/tests/TestCases.HostApp/MauiProgram.cs
index d10a58ca8c51..7fadcbae7c19 100644
--- a/src/Controls/tests/TestCases.HostApp/MauiProgram.cs
+++ b/src/Controls/tests/TestCases.HostApp/MauiProgram.cs
@@ -21,6 +21,7 @@ public static MauiApp CreateMauiApp()
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("FontAwesome.ttf", "FA");
fonts.AddFont("ionicons.ttf", "Ion");
+ fonts.AddFont("Montserrat-Bold.otf", "MontserratBold");
})
.RenderingPerformanceAddMappers()
.Issue21109AddMappers()
diff --git a/src/Controls/tests/TestCases.HostApp/Resources/Fonts/Montserrat-Bold.otf b/src/Controls/tests/TestCases.HostApp/Resources/Fonts/Montserrat-Bold.otf
new file mode 100644
index 000000000000..cdfb83df2f2f
Binary files /dev/null and b/src/Controls/tests/TestCases.HostApp/Resources/Fonts/Montserrat-Bold.otf differ
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17884.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17884.cs
new file mode 100644
index 000000000000..c867e7c3a3bc
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17884.cs
@@ -0,0 +1,25 @@
+#if ANDROID
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues
+{
+ public class Issue17884 : _IssuesUITest
+ {
+ public Issue17884(TestDevice device)
+ : base(device)
+ { }
+
+ public override string Issue => "[Android] Entire words omitted & letters truncated from Label display";
+
+ [Test]
+ [Category(UITestCategories.Visual)]
+ public void VerifyTextIsNotMissing()
+ {
+ App.WaitForElement("StubLabel");
+ VerifyScreenshot();
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Core/src/Handlers/ViewHandlerExtensions.Android.cs b/src/Core/src/Handlers/ViewHandlerExtensions.Android.cs
index 654a33f46629..061411fcd618 100644
--- a/src/Core/src/Handlers/ViewHandlerExtensions.Android.cs
+++ b/src/Core/src/Handlers/ViewHandlerExtensions.Android.cs
@@ -102,10 +102,10 @@ internal static void PlatformArrangeHandler(this IViewHandler viewHandler, Rect
{
var platformView = viewHandler.ToPlatform();
- var Context = viewHandler.MauiContext?.Context;
- var MauiContext = viewHandler.MauiContext;
+ var context = viewHandler.MauiContext?.Context;
+ var mauiContext = viewHandler.MauiContext;
- if (platformView == null || MauiContext == null || Context == null)
+ if (platformView == null || mauiContext == null || context == null)
{
return;
}
@@ -116,10 +116,7 @@ internal static void PlatformArrangeHandler(this IViewHandler viewHandler, Rect
return;
}
- var left = Context.ToPixels(frame.Left);
- var top = Context.ToPixels(frame.Top);
- var bottom = Context.ToPixels(frame.Bottom);
- var right = Context.ToPixels(frame.Right);
+ var (left, top, right, bottom) = context.ToPixels(frame);
var viewParent = platformView.Parent;
if (viewParent?.LayoutDirection == LayoutDirection.Rtl && viewParent is View parentView)
@@ -130,7 +127,7 @@ internal static void PlatformArrangeHandler(this IViewHandler viewHandler, Rect
right = left + width;
}
- platformView.Layout((int)left, (int)top, (int)right, (int)bottom);
+ platformView.Layout(left, top, right, bottom);
viewHandler.Invoke(nameof(IView.Frame), frame);
}
diff --git a/src/Core/src/Platform/Android/ContextExtensions.cs b/src/Core/src/Platform/Android/ContextExtensions.cs
index 88ead155d5dd..5d2a40a3fdb6 100644
--- a/src/Core/src/Platform/Android/ContextExtensions.cs
+++ b/src/Core/src/Platform/Android/ContextExtensions.cs
@@ -104,15 +104,36 @@ public static float ToPixels(this Context? self, double dp)
return (float)Math.Ceiling(dp * s_displayDensity);
}
+ ///
+ /// Converts dp to pixels, rounding to the nearest whole pixel.
+ ///
+ ///
+ /// This is especially useful when converting coordinates to the nearest pixel.
+ /// Do not use this method when setting sizes, as it may lead to visual truncation of the content
+ /// when rounding down.
+ ///
+ internal static float ToPixelsRound(this Context? self, double dp)
+ {
+ EnsureMetrics(self);
+
+ return (float)Math.Round(dp * s_displayDensity, MidpointRounding.AwayFromZero);
+ }
+
public static (int left, int top, int right, int bottom) ToPixels(this Context context, Graphics.Rect rectangle)
{
- return
- (
- (int)context.ToPixels(rectangle.Left),
- (int)context.ToPixels(rectangle.Top),
- (int)context.ToPixels(rectangle.Right),
- (int)context.ToPixels(rectangle.Bottom)
- );
+ // Use `ToPixels` to get enough pixels to display the whole content
+ var width = (int)context.ToPixels(rectangle.Width);
+ var height = (int)context.ToPixels(rectangle.Height);
+ // Use `ToPixelsRound` to position the content on the nearest pixel
+ var left = (int)context.ToPixelsRound(rectangle.Left);
+ var top = (int)context.ToPixelsRound(rectangle.Top);
+
+ // Compute the right and bottom edges based on platform-specific sizes
+ // Do NOT compute them based on the rectangle's right and bottom as it may lead to visual truncation
+ var right = left + width;
+ var bottom = top + height;
+
+ return (left, top, right, bottom);
}
public static Thickness ToPixels(this Context context, Thickness thickness)