Add wrapping support to text measuring

This fixes #72

I also improved the default sizes of text-based
elements including button. This fixes #69
This commit is contained in:
Frank A. Krueger 2018-02-02 17:15:38 -08:00
parent 674f9d2fba
commit ff9e9f749a
No known key found for this signature in database
GPG Key ID: 7267E9B18C8DFACE
10 changed files with 114 additions and 40 deletions

View File

@ -14,7 +14,7 @@ namespace Ooui.Forms.Extensions
var measured = false; var measured = false;
if (self.Style.Width.Equals ("inherit")) { if (self.Style.Width.Equals ("inherit")) {
s = self.Text.MeasureSize (self.Style); s = self.Text.MeasureSize (self.Style, widthConstraint, heightConstraint);
measured = true; measured = true;
rw = double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : Math.Ceiling (s.Width); rw = double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : Math.Ceiling (s.Width);
} }
@ -24,7 +24,7 @@ namespace Ooui.Forms.Extensions
if (self.Style.Height.Equals ("inherit")) { if (self.Style.Height.Equals ("inherit")) {
if (!measured) { if (!measured) {
s = self.Text.MeasureSize (self.Style); s = self.Text.MeasureSize (self.Style, widthConstraint, heightConstraint);
measured = true; measured = true;
} }
rh = double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : Math.Ceiling (s.Height * 1.4); rh = double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : Math.Ceiling (s.Height * 1.4);

View File

@ -38,36 +38,65 @@ namespace Ooui.Forms.Extensions
} }
} }
public static Size MeasureSize (this string text, string fontFamily, double fontSize, FontAttributes fontAttrs) public static Size MeasureSize (this string text, string fontFamily, double fontSize, FontAttributes fontAttrs, double widthConstraint, double heightConstraint)
{ {
if (string.IsNullOrEmpty (text)) if (string.IsNullOrEmpty (text))
return Size.Zero; return Size.Zero;
var fontHeight = fontSize; var fontHeight = fontSize;
var lineHeight = fontHeight * 1.4;
var isBold = fontAttrs.HasFlag (FontAttributes.Bold); var isBold = fontAttrs.HasFlag (FontAttributes.Bold);
var props = isBold ? BoldCharacterProportions : CharacterProportions; var props = isBold ? BoldCharacterProportions : CharacterProportions;
var avgp = isBold ? BoldAverageCharProportion : AverageCharProportion; var avgp = isBold ? BoldAverageCharProportion : AverageCharProportion;
var pwidth = 1.0e-6; // Tiny little padding to account for sampling errors var px = 0.0;
for (var i = 0; i < text.Length; i++) { var lines = 1;
var c = (int)text[i]; var maxPWidth = 0.0;
if (c < 128) { var pwidthConstraint = double.IsPositiveInfinity (widthConstraint) ? double.PositiveInfinity : widthConstraint / fontSize;
pwidth += props[c]; var lastSpaceWidth = -1.0;
}
else {
pwidth += avgp;
}
}
var width = fontSize * pwidth;
return new Size (width, fontHeight); // Tiny little padding to account for sampling errors
var pwidthHack = 1.0e-6;
var plineHack = 0.333;
var n = text != null ? text.Length : 0;
for (var i = 0; i < n; i++) {
var c = (int)text[i];
var pw = (c < 128) ? props[c] : avgp;
// Should we wrap?
if (px + pw + plineHack > pwidthConstraint) {
lines++;
if (lastSpaceWidth > 0) {
maxPWidth = Math.Max (maxPWidth, lastSpaceWidth + pwidthHack);
px = pw - lastSpaceWidth;
lastSpaceWidth = -1;
}
else {
maxPWidth = Math.Max (maxPWidth, px + pwidthHack);
px = 0;
}
}
if (c == ' ') {
lastSpaceWidth = pw;
}
px += pw;
}
maxPWidth = Math.Max (maxPWidth, px + pwidthHack);
var width = fontSize * maxPWidth;
var height = lines * lineHeight;
// Console.WriteLine ($"MEASURE TEXT SIZE {widthConstraint}x{heightConstraint} \"{text}\" == {width}x{height}");
return new Size (width, height);
} }
public static Size MeasureSize (this string text, Style style) public static Size MeasureSize (this string text, Style style, double widthConstraint, double heightConstraint)
{ {
return MeasureSize (text, "", 14, FontAttributes.None); // System.Console.WriteLine("!!! MEASURE STYLED TEXT SIZE: " + style);
return MeasureSize (text, "", 14, FontAttributes.None, widthConstraint, heightConstraint);
} }
public static string ToOouiTextAlign (this TextAlignment align) public static string ToOouiTextAlign (this TextAlignment align)

View File

@ -14,8 +14,8 @@ namespace Ooui.Forms.Renderers
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint) public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
{ {
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes); var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes, widthConstraint, heightConstraint);
size = new Size (size.Width, size.Height * 1.428 + 14); size = new Size (size.Width + 2 * Element.FontSize, size.Height + Element.FontSize);
return new SizeRequest (size, size); return new SizeRequest (size, size);
} }

View File

@ -13,8 +13,8 @@ namespace Ooui.Forms.Renderers
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint) public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
{ {
var size = "00/00/0000".MeasureSize ("", 16.0, FontAttributes.None); var size = "00/00/0000".MeasureSize ("", 16.0, FontAttributes.None, widthConstraint, heightConstraint);
size = new Size (size.Width, size.Height * 1.428 + 14); size = new Size (size.Width, size.Height);
return new SizeRequest (size, size); return new SizeRequest (size, size);
} }

View File

@ -21,14 +21,12 @@ namespace Ooui.Forms.Renderers
if (text == null || text.Length == 0) { if (text == null || text.Length == 0) {
text = Element.Placeholder; text = Element.Placeholder;
} }
Size size;
if (text == null || text.Length == 0) { if (text == null || text.Length == 0) {
size = new Size (Element.FontSize * 0.25, Element.FontSize); text = " ";
} }
else { var size = text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes, widthConstraint, heightConstraint);
size = text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes); var vpadding = Element.FontSize;
} size = new Size (size.Width, size.Height + vpadding);
size = new Size (size.Width, size.Height * 1.428 + 14);
return new SizeRequest (size, size); return new SizeRequest (size, size);
} }
@ -149,7 +147,7 @@ namespace Ooui.Forms.Renderers
{ {
if (initialSize == Size.Zero) { if (initialSize == Size.Zero) {
var testString = "Tj"; var testString = "Tj";
initialSize = testString.MeasureSize (Control.Style); initialSize = testString.MeasureSize (Control.Style, double.PositiveInfinity, double.PositiveInfinity);
} }
Element.SetStyleFont (Element.FontFamily, Element.FontSize, Element.FontAttributes, Control.Style); Element.SetStyleFont (Element.FontFamily, Element.FontSize, Element.FontAttributes, Control.Style);

View File

@ -16,11 +16,12 @@ namespace Ooui.Forms.Renderers
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint) public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
{ {
// System.Console.WriteLine($"Label.GetDesiredSize ({widthConstraint}, {heightConstraint})");
if (!_perfectSizeValid) { if (!_perfectSizeValid) {
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes); var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes, double.PositiveInfinity, double.PositiveInfinity);
size.Width = Math.Ceiling (size.Width); size.Width = Math.Ceiling (size.Width);
size.Height = Math.Ceiling (size.Height * 1.4); size.Height = Math.Ceiling (size.Height);
_perfectSize = new SizeRequest (size, size); _perfectSize = new SizeRequest (size, new Size (Element.FontSize, Element.FontSize));
_perfectSizeValid = true; _perfectSizeValid = true;
} }
@ -30,7 +31,8 @@ namespace Ooui.Forms.Renderers
if (widthFits && heightFits) if (widthFits && heightFits)
return _perfectSize; return _perfectSize;
var result = base.GetDesiredSize (widthConstraint, heightConstraint); var resultRequestSize = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes, widthConstraint, heightConstraint);
var result = new SizeRequest (resultRequestSize, resultRequestSize);
var tinyWidth = Math.Min (10, result.Request.Width); var tinyWidth = Math.Min (10, result.Request.Width);
result.Minimum = new Size (tinyWidth, result.Request.Height); result.Minimum = new Size (tinyWidth, result.Request.Height);

View File

@ -14,10 +14,10 @@ namespace Ooui.Forms.Renderers
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint) public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
{ {
if (!_perfectSizeValid) { if (!_perfectSizeValid) {
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes); var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes, double.PositiveInfinity, double.PositiveInfinity);
size.Width = Math.Ceiling (size.Width); size.Width = Math.Ceiling (size.Width);
size.Height = Math.Ceiling (size.Height * 1.4); size.Height = Math.Ceiling (size.Height);
_perfectSize = new SizeRequest (size, size); _perfectSize = new SizeRequest (size, new Size (Element.FontSize, Element.FontSize));
_perfectSizeValid = true; _perfectSizeValid = true;
} }
@ -27,7 +27,8 @@ namespace Ooui.Forms.Renderers
if (widthFits && heightFits) if (widthFits && heightFits)
return _perfectSize; return _perfectSize;
var result = base.GetDesiredSize (widthConstraint, heightConstraint); var resultRequestSize = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes, widthConstraint, heightConstraint);
var result = new SizeRequest (resultRequestSize, resultRequestSize);
var tinyWidth = Math.Min (10, result.Request.Width); var tinyWidth = Math.Min (10, result.Request.Width);
result.Minimum = new Size (tinyWidth, result.Request.Height); result.Minimum = new Size (tinyWidth, result.Request.Height);

View File

@ -27,9 +27,9 @@ namespace Ooui.Forms.Renderers
} }
else else
{ {
size = text.MeasureSize(Element.FontFamily, Element.FontSize, Element.FontAttributes); size = text.MeasureSize(Element.FontFamily, Element.FontSize, Element.FontAttributes, widthConstraint, heightConstraint);
} }
size = new Size(size.Width, size.Height * 1.428 + 14); size = new Size(size.Width, size.Height + Element.FontSize);
return new SizeRequest(size, size); return new SizeRequest(size, size);
} }

View File

@ -13,8 +13,9 @@ namespace Ooui.Forms.Renderers
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{ {
var size = "00:00:00".MeasureSize(string.Empty, 16.0, FontAttributes.None); var fontSize = 16.0;
size = new Size(size.Width, size.Height * 1.428 + 14); var size = "00:00:00".MeasureSize(string.Empty, fontSize, FontAttributes.None, widthConstraint, heightConstraint);
size = new Size(size.Width, size.Height + fontSize);
return new SizeRequest(size, size); return new SizeRequest(size, size);
} }

View File

@ -0,0 +1,43 @@
using System;
using Xamarin.Forms;
namespace Samples
{
public class WrappingTextSample : ISample
{
public string Title => "Xamarin.Forms Wrapping Text";
public Ooui.Element CreateElement()
{
var rows = new StackLayout { Orientation = StackOrientation.Vertical };
var row0 = new StackLayout { Orientation = StackOrientation.Horizontal, BackgroundColor = Color.Azure };
row0.Children.Add (new Label { Text = shortText, LineBreakMode = LineBreakMode.WordWrap });
row0.Children.Add (new Label { Text = mediumText, LineBreakMode = LineBreakMode.WordWrap });
row0.Children.Add (new Label { Text = longText, LineBreakMode = LineBreakMode.WordWrap });
rows.Children.Add (row0);
var row1 = new StackLayout { Orientation = StackOrientation.Horizontal, BackgroundColor = Color.GhostWhite };
row1.Children.Add (new Label { Text = shortText, FontAttributes = FontAttributes.Bold, HorizontalOptions = LayoutOptions.Start });
row1.Children.Add (new Label { Text = mediumText, FontAttributes = FontAttributes.Bold, HorizontalOptions = LayoutOptions.FillAndExpand });
row1.Children.Add (new Label { Text = longText, FontAttributes = FontAttributes.Bold, HorizontalOptions = LayoutOptions.End });
rows.Children.Add (row1);
var page = new ContentPage
{
Content = rows
};
return page.GetOouiElement();
}
public void Publish()
{
Ooui.UI.Publish("/wrapping", CreateElement);
}
const string shortText = "Lorem ipsum dolor sit amet.";
const string mediumText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
const string longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
}
}