Skip to content

Commit a1ebdb1

Browse files
committed
Fixed canvas.GetStringSize() is not consistent with actual string size in GraphicsView
1 parent cefec86 commit a1ebdb1

6 files changed

Lines changed: 178 additions & 34 deletions

File tree

80.8 KB
Loading
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using Font = Microsoft.Maui.Graphics.Font;
2+
3+
namespace Maui.Controls.Sample.Issues
4+
{
5+
[Issue(IssueTracker.Github, 18679, "Canvas.GetStringSize() is not consistent with actual string size in GraphicsView", PlatformAffected.iOS | PlatformAffected.macOS)]
6+
public class Issue18679 : TestContentPage
7+
{
8+
const string ShortText = "ShortText";
9+
const string LongText = "CiaomondorowfdskleCiaomondorowfdsk";
10+
const string MultiLineText = "HELLO, WORLD!\nCiao mondo row 2\nGuten tag!?àèìòù@";
11+
12+
protected override void Init()
13+
{
14+
var layout = new VerticalStackLayout
15+
{
16+
Spacing = 10,
17+
};
18+
var label = new Label
19+
{
20+
Text = "The drawn text should not overflow the bounding rectangle.",
21+
FontSize = 16,
22+
AutomationId = "18679DescriptionLabel",
23+
};
24+
layout.Add(label);
25+
26+
// Test Case 1: Single long line (basic case)
27+
layout.Add(CreateDrawable("Test Case 1: Short text", ShortText));
28+
29+
// Test Case 2: Multi-line text
30+
layout.Add(CreateDrawable("Test Case 2: Multi-line Text", MultiLineText));
31+
32+
// Test Case 3: Unicode/non-Latin text
33+
layout.Add(CreateDrawable("Test Case 3: Long Text", LongText));
34+
35+
Content = layout;
36+
}
37+
38+
private Border CreateDrawable(string testName, string text)
39+
{
40+
var graphicsView = new GraphicsView
41+
{
42+
HeightRequest = 150,
43+
Drawable = new Issue18679_Drawable(text, testName)
44+
};
45+
46+
return new Border
47+
{
48+
Content = graphicsView,
49+
Stroke = Colors.LightGray,
50+
StrokeThickness = 1,
51+
};
52+
}
53+
}
54+
55+
public class Issue18679_Drawable : IDrawable
56+
{
57+
readonly string _text;
58+
readonly string _label;
59+
static readonly Font Font = Font.Default;
60+
const int FontSize = 20;
61+
62+
63+
public Issue18679_Drawable(string text, string label)
64+
{
65+
_text = text;
66+
_label = label;
67+
}
68+
69+
public void Draw(ICanvas canvas, RectF dirtyRect)
70+
{
71+
canvas.SaveState();
72+
73+
// Draw label
74+
canvas.FontSize = 14;
75+
canvas.Font = Font;
76+
canvas.FontColor = Colors.Blue;
77+
canvas.DrawString(_label, 10, 10, dirtyRect.Width - 20, 20,
78+
HorizontalAlignment.Left, VerticalAlignment.Top);
79+
80+
// Set up for text measurement
81+
canvas.FontSize = FontSize;
82+
canvas.Font = Font;
83+
84+
// Get text size and create bounds
85+
var stringSize = canvas.GetStringSize(_text, Font, FontSize);
86+
87+
// Draw the actual text first
88+
canvas.FontColor = Colors.Black;
89+
canvas.DrawString(_text, 2, 40, dirtyRect.Width, dirtyRect.Height,
90+
HorizontalAlignment.Left, VerticalAlignment.Top);
91+
92+
// Draw the measured bounds rectangle
93+
canvas.StrokeColor = Colors.Red;
94+
canvas.StrokeSize = 1;
95+
canvas.DrawRectangle(2, 40, stringSize.Width, stringSize.Height);
96+
97+
canvas.RestoreState();
98+
}
99+
}
100+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using NUnit.Framework;
2+
using UITest.Appium;
3+
using UITest.Core;
4+
5+
namespace Microsoft.Maui.TestCases.Tests.Issues;
6+
7+
public class Issue18679 : _IssuesUITest
8+
{
9+
public Issue18679(TestDevice testDevice) : base(testDevice)
10+
{
11+
}
12+
13+
public override string Issue => "Canvas.GetStringSize() is not consistent with actual string size in GraphicsView";
14+
15+
[Test]
16+
[Category(UITestCategories.GraphicsView)]
17+
public void DrawTextWithinBounds()
18+
{
19+
App.WaitForElement("18679DescriptionLabel");
20+
VerifyScreenshot();
21+
}
22+
}
90.6 KB
Loading

src/Graphics/src/Graphics/Platforms/Windows/PlatformStringSizeService.cs

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,42 @@ public SizeF GetStringSize(string value, IFont font, float textSize)
2323

2424
public SizeF GetStringSize(string value, IFont font, float textSize, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment)
2525
{
26-
var format = font.ToCanvasTextFormat(textSize);
27-
format.WordWrapping = CanvasWordWrapping.NoWrap;
28-
29-
var device = CanvasDevice.GetSharedDevice();
30-
var textLayout = new CanvasTextLayout(device, value, format, 0.0f, 0.0f);
31-
textLayout.VerticalAlignment = verticalAlignment switch
32-
{
33-
VerticalAlignment.Top => CanvasVerticalAlignment.Top,
34-
VerticalAlignment.Center => CanvasVerticalAlignment.Center,
35-
VerticalAlignment.Bottom => CanvasVerticalAlignment.Bottom,
36-
_ => CanvasVerticalAlignment.Top
37-
};
38-
textLayout.HorizontalAlignment = horizontalAlignment switch
39-
{
40-
HorizontalAlignment.Left => CanvasHorizontalAlignment.Left,
41-
HorizontalAlignment.Center => CanvasHorizontalAlignment.Center,
42-
HorizontalAlignment.Right => CanvasHorizontalAlignment.Right,
43-
HorizontalAlignment.Justified => CanvasHorizontalAlignment.Justified,
44-
_ => CanvasHorizontalAlignment.Left,
45-
};
46-
47-
return new SizeF((float)textLayout.DrawBounds.Width, (float)textLayout.DrawBounds.Height);
48-
}
26+
if (string.IsNullOrEmpty(value) || font is null || textSize <= 0)
27+
return SizeF.Zero;
28+
29+
var format = font.ToCanvasTextFormat(textSize);
30+
format.WordWrapping = CanvasWordWrapping.NoWrap;
31+
32+
var device = CanvasDevice.GetSharedDevice();
33+
34+
using (var textLayout = new CanvasTextLayout(device, value, format, float.MaxValue, float.MaxValue))
35+
{
36+
textLayout.VerticalAlignment = verticalAlignment switch
37+
{
38+
VerticalAlignment.Top => CanvasVerticalAlignment.Top,
39+
VerticalAlignment.Center => CanvasVerticalAlignment.Center,
40+
VerticalAlignment.Bottom => CanvasVerticalAlignment.Bottom,
41+
_ => CanvasVerticalAlignment.Top
42+
};
43+
textLayout.HorizontalAlignment = horizontalAlignment switch
44+
{
45+
HorizontalAlignment.Left => CanvasHorizontalAlignment.Left,
46+
HorizontalAlignment.Center => CanvasHorizontalAlignment.Center,
47+
HorizontalAlignment.Right => CanvasHorizontalAlignment.Right,
48+
HorizontalAlignment.Justified => CanvasHorizontalAlignment.Justified,
49+
_ => CanvasHorizontalAlignment.Left,
50+
};
51+
52+
var bounds = textLayout.LayoutBounds;
53+
54+
// If LayoutBounds is empty, fallback to DrawBounds
55+
if (bounds.Width <= 0 || bounds.Height <= 0)
56+
{
57+
bounds = textLayout.DrawBounds;
58+
}
59+
60+
return new SizeF((float)bounds.Width, (float)bounds.Height);
61+
}
62+
}
4963
}
50-
}
64+
}
Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using CoreGraphics;
3+
using CoreText;
34
using Foundation;
45
using UIKit;
56

@@ -10,19 +11,26 @@ public partial class PlatformStringSizeService : IStringSizeService
1011
public SizeF GetStringSize(string value, IFont font, float fontSize)
1112
{
1213
if (string.IsNullOrEmpty(value))
14+
{
1315
return new SizeF();
16+
}
1417

15-
var nsString = new NSString(value);
16-
var uiFont = font?.ToPlatformFont(fontSize) ?? FontExtensions.GetDefaultPlatformFont();
18+
var attributes = new CTStringAttributes();
19+
attributes.Font = font?.ToCTFont(fontSize) ?? FontExtensions.GetDefaultCTFont(fontSize);
1720

18-
CGSize size = nsString.GetBoundingRect(
19-
CGSize.Empty,
20-
NSStringDrawingOptions.UsesLineFragmentOrigin,
21-
new UIStringAttributes { Font = uiFont },
22-
null).Size;
21+
var attributedString = new NSAttributedString(value, attributes);
22+
var framesetter = new CTFramesetter(attributedString);
2323

24-
uiFont.Dispose();
25-
return new SizeF((float)size.Width, (float)size.Height);
24+
// Get suggested frame size with unlimited constraints
25+
var measuredSize = framesetter.SuggestFrameSize(
26+
new NSRange(0, 0),
27+
null,
28+
new CGSize(float.MaxValue, float.MaxValue),
29+
out _);
30+
31+
framesetter.Dispose();
32+
attributedString.Dispose();
33+
return new SizeF((float)measuredSize.Width, (float)measuredSize.Height);
2634
}
2735
}
28-
}
36+
}

0 commit comments

Comments
 (0)