diff --git a/Ooui.Forms/Extensions/ElementExtensions.cs b/Ooui.Forms/Extensions/ElementExtensions.cs index 53c1366..32a07b2 100644 --- a/Ooui.Forms/Extensions/ElementExtensions.cs +++ b/Ooui.Forms/Extensions/ElementExtensions.cs @@ -14,7 +14,7 @@ namespace Ooui.Forms.Extensions var measured = false; if (self.Style.Width.Equals ("inherit")) { - s = self.Text.MeasureSize (self.Style); + s = self.Text.MeasureSize (self.Style, widthConstraint, heightConstraint); measured = true; 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 (!measured) { - s = self.Text.MeasureSize (self.Style); + s = self.Text.MeasureSize (self.Style, widthConstraint, heightConstraint); measured = true; } rh = double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : Math.Ceiling (s.Height * 1.4); diff --git a/Ooui.Forms/Extensions/FontExtensions.cs b/Ooui.Forms/Extensions/FontExtensions.cs index f364559..5588bda 100644 --- a/Ooui.Forms/Extensions/FontExtensions.cs +++ b/Ooui.Forms/Extensions/FontExtensions.cs @@ -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)) return Size.Zero; var fontHeight = fontSize; + var lineHeight = fontHeight * 1.4; var isBold = fontAttrs.HasFlag (FontAttributes.Bold); var props = isBold ? BoldCharacterProportions : CharacterProportions; var avgp = isBold ? BoldAverageCharProportion : AverageCharProportion; - var pwidth = 1.0e-6; // Tiny little padding to account for sampling errors - for (var i = 0; i < text.Length; i++) { - var c = (int)text[i]; - if (c < 128) { - pwidth += props[c]; - } - else { - pwidth += avgp; - } - } - var width = fontSize * pwidth; + var px = 0.0; + var lines = 1; + var maxPWidth = 0.0; + var pwidthConstraint = double.IsPositiveInfinity (widthConstraint) ? double.PositiveInfinity : widthConstraint / fontSize; + var lastSpaceWidth = -1.0; - 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) diff --git a/Ooui.Forms/Renderers/ButtonRenderer.cs b/Ooui.Forms/Renderers/ButtonRenderer.cs index 04f30b2..7687430 100644 --- a/Ooui.Forms/Renderers/ButtonRenderer.cs +++ b/Ooui.Forms/Renderers/ButtonRenderer.cs @@ -14,8 +14,8 @@ namespace Ooui.Forms.Renderers public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint) { - var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes); - size = new Size (size.Width, size.Height * 1.428 + 14); + var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes, widthConstraint, heightConstraint); + size = new Size (size.Width + 2 * Element.FontSize, size.Height + Element.FontSize); return new SizeRequest (size, size); } diff --git a/Ooui.Forms/Renderers/DatePickerRenderer.cs b/Ooui.Forms/Renderers/DatePickerRenderer.cs index 3df8d05..c97fd3b 100644 --- a/Ooui.Forms/Renderers/DatePickerRenderer.cs +++ b/Ooui.Forms/Renderers/DatePickerRenderer.cs @@ -13,8 +13,8 @@ namespace Ooui.Forms.Renderers public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint) { - var size = "00/00/0000".MeasureSize ("", 16.0, FontAttributes.None); - size = new Size (size.Width, size.Height * 1.428 + 14); + var size = "00/00/0000".MeasureSize ("", 16.0, FontAttributes.None, widthConstraint, heightConstraint); + size = new Size (size.Width, size.Height); return new SizeRequest (size, size); } diff --git a/Ooui.Forms/Renderers/EntryRenderer.cs b/Ooui.Forms/Renderers/EntryRenderer.cs index 4e54dbd..4704819 100644 --- a/Ooui.Forms/Renderers/EntryRenderer.cs +++ b/Ooui.Forms/Renderers/EntryRenderer.cs @@ -21,14 +21,12 @@ namespace Ooui.Forms.Renderers if (text == null || text.Length == 0) { text = Element.Placeholder; } - Size size; if (text == null || text.Length == 0) { - size = new Size (Element.FontSize * 0.25, Element.FontSize); + text = " "; } - else { - size = text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes); - } - size = new Size (size.Width, size.Height * 1.428 + 14); + var size = text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes, widthConstraint, heightConstraint); + var vpadding = Element.FontSize; + size = new Size (size.Width, size.Height + vpadding); return new SizeRequest (size, size); } @@ -149,7 +147,7 @@ namespace Ooui.Forms.Renderers { if (initialSize == Size.Zero) { 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); diff --git a/Ooui.Forms/Renderers/LabelRenderer.cs b/Ooui.Forms/Renderers/LabelRenderer.cs index 85ddc50..8405925 100644 --- a/Ooui.Forms/Renderers/LabelRenderer.cs +++ b/Ooui.Forms/Renderers/LabelRenderer.cs @@ -16,11 +16,12 @@ namespace Ooui.Forms.Renderers public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint) { + // System.Console.WriteLine($"Label.GetDesiredSize ({widthConstraint}, {heightConstraint})"); 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.Height = Math.Ceiling (size.Height * 1.4); - _perfectSize = new SizeRequest (size, size); + size.Height = Math.Ceiling (size.Height); + _perfectSize = new SizeRequest (size, new Size (Element.FontSize, Element.FontSize)); _perfectSizeValid = true; } @@ -30,7 +31,8 @@ namespace Ooui.Forms.Renderers if (widthFits && heightFits) 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); result.Minimum = new Size (tinyWidth, result.Request.Height); diff --git a/Ooui.Forms/Renderers/LinkLabelRenderer.cs b/Ooui.Forms/Renderers/LinkLabelRenderer.cs index 4e0f79c..79cbce0 100644 --- a/Ooui.Forms/Renderers/LinkLabelRenderer.cs +++ b/Ooui.Forms/Renderers/LinkLabelRenderer.cs @@ -14,10 +14,10 @@ namespace Ooui.Forms.Renderers public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint) { 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.Height = Math.Ceiling (size.Height * 1.4); - _perfectSize = new SizeRequest (size, size); + size.Height = Math.Ceiling (size.Height); + _perfectSize = new SizeRequest (size, new Size (Element.FontSize, Element.FontSize)); _perfectSizeValid = true; } @@ -27,7 +27,8 @@ namespace Ooui.Forms.Renderers if (widthFits && heightFits) 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); result.Minimum = new Size (tinyWidth, result.Request.Height); diff --git a/Ooui.Forms/Renderers/SearchBarRenderer.cs b/Ooui.Forms/Renderers/SearchBarRenderer.cs index 9e117e5..adf5b87 100644 --- a/Ooui.Forms/Renderers/SearchBarRenderer.cs +++ b/Ooui.Forms/Renderers/SearchBarRenderer.cs @@ -27,9 +27,9 @@ namespace Ooui.Forms.Renderers } 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); } diff --git a/Ooui.Forms/Renderers/TimePickerRenderer.cs b/Ooui.Forms/Renderers/TimePickerRenderer.cs index b282e23..56fe30d 100644 --- a/Ooui.Forms/Renderers/TimePickerRenderer.cs +++ b/Ooui.Forms/Renderers/TimePickerRenderer.cs @@ -13,8 +13,9 @@ namespace Ooui.Forms.Renderers public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) { - var size = "00:00:00".MeasureSize(string.Empty, 16.0, FontAttributes.None); - size = new Size(size.Width, size.Height * 1.428 + 14); + var fontSize = 16.0; + 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); } diff --git a/Samples/WrappingTextSample.cs b/Samples/WrappingTextSample.cs new file mode 100644 index 0000000..18e2daa --- /dev/null +++ b/Samples/WrappingTextSample.cs @@ -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."; + } +}