internal static float ZeroLinesFit(ICharSequence text, TextPaint paint, float min, float max, int availWidth, int availHeight) { if (availHeight > int.MaxValue / 3) { return(max); } if (availWidth > int.MaxValue / 3) { var fontMetrics = paint.GetFontMetrics(); var fontLineHeight = fontMetrics.Descent * FontScale - fontMetrics.Ascent * FontScale; var fontPixelSize = paint.TextSize * FontScale; var lineHeightRatio = fontLineHeight / fontPixelSize; var result = (availHeight / lineHeightRatio - 0.1f); return(System.Math.Min(result, max)); } if (max - min < Precision) { return(min); } float mid = (max + min) / 2.0f; paint.TextSize = mid * Scale; var layout = TextExtensions.StaticLayout(text, paint, availWidth, Android.Text.Layout.Alignment.AlignNormal, 1.0f, 0.0f, true); var lineCount = layout.LineCount; var height = layout.Height - layout.BottomPadding + layout.TopPadding; if (height > availHeight) { return(ZeroLinesFit(text, paint, min, mid, availWidth, availHeight)); } if (height < availHeight) { return(ZeroLinesFit(text, paint, mid, max, availWidth, availHeight)); } float maxLineWidth = 0; for (int i = 0; i < lineCount; i++) { if (layout.GetLineWidth(i) > maxLineWidth) { maxLineWidth = layout.GetLineWidth(i); } } if (maxLineWidth > availWidth) { return(ZeroLinesFit(text, paint, min, mid, availWidth, availHeight)); } if (maxLineWidth < availWidth) { return(ZeroLinesFit(text, paint, mid, max, availWidth, availHeight)); } return(mid); }
#pragma warning disable IDE0060 // Remove unused parameter static float DescendingWidthFit(ICharSequence text, TextPaint paint, int lines, float min, float max, int availWidth, int availHeight, float step) #pragma warning restore IDE0060 // Remove unused parameter { float result; for (result = max; result > min; result -= step) { paint.TextSize = result * Scale * FontScale; var layout = TextExtensions.StaticLayout(text, paint, availWidth, Android.Text.Layout.Alignment.AlignNormal, 1.0f, 0.0f, true); if (layout.LineCount <= lines) { return(result); } } return(result); }
static string EndTruncatedIter(string text, TextPaint paint, int secondToLastEnd, int start, int endLow, int endHigh, float availWidth) { if (endHigh - endLow <= 1) { return((secondToLastEnd > 0 ? text.Substring(0, secondToLastEnd).TrimEnd() + "\n" : "") + text.Substring(start, endLow - start) + "…"); } int mid = (endLow + endHigh) / 2; var lastLineText = new Java.Lang.String(text.Substring(start, mid - start) + "…"); var layout = TextExtensions.StaticLayout(lastLineText, paint, int.MaxValue - 10000, Android.Text.Layout.Alignment.AlignNormal, 1.0f, 0.0f, true); if (layout.GetLineWidth(0) > availWidth) { return(EndTruncatedIter(text, paint, secondToLastEnd, start, endLow, mid, availWidth)); } return(EndTruncatedIter(text, paint, secondToLastEnd, start, mid, endHigh, availWidth)); }
static ICharSequence EndTruncatedFormattedIter(F9PFormattedString baseFormattedString, TextPaint paint, int secondToLastEnd, int start, int endLow, int endHigh, float availWidth) { if (endHigh - endLow <= 1) { return(baseFormattedString.ToSpannableString(EllipsePlacement.End, secondToLastEnd, start, endLow)); } int mid = (endLow + endHigh) / 2; var formattedText = baseFormattedString.ToSpannableString(EllipsePlacement.End, 0, start, mid); var layout = TextExtensions.StaticLayout(formattedText, paint, int.MaxValue - 10000, Android.Text.Layout.Alignment.AlignNormal, 1.0f, 0.0f, true); if (layout.GetLineWidth(0) > availWidth) { return(EndTruncatedFormattedIter(baseFormattedString, paint, secondToLastEnd, start, endLow, mid, availWidth)); } return(EndTruncatedFormattedIter(baseFormattedString, paint, secondToLastEnd, start, mid, endHigh, availWidth)); }
static string MidTruncatedIter(string text, TextPaint paint, int secondToLastEnd, int startLastVisible, int midLastVisible, int startLow, int startHigh, int end, float availWidth, float lineHeightMultiplier) { if (startHigh - startLow <= 1) { return((secondToLastEnd > 0 ? text.Substring(0, secondToLastEnd).TrimEnd() + "\n" : "") + text.Substring(startLastVisible, midLastVisible - startLastVisible).TrimEnd() + "…" + text.Substring(startHigh, end - startHigh).TrimStart()); } int mid = (startLow + startHigh) / 2; var lastLineText = new Java.Lang.String(text.Substring(startLastVisible, midLastVisible - startLastVisible).TrimEnd() + "…" + text.Substring(mid, end - mid).TrimStart()); using (var layout = TextExtensions.StaticLayout(lastLineText, paint, int.MaxValue - 10000, Android.Text.Layout.Alignment.AlignNormal, lineHeightMultiplier, 0.0f, true)) { if (layout.GetLineWidth(0) > availWidth) { return(MidTruncatedIter(text, paint, secondToLastEnd, startLastVisible, midLastVisible, mid, startHigh, end, availWidth, lineHeightMultiplier)); } return(MidTruncatedIter(text, paint, secondToLastEnd, startLastVisible, midLastVisible, startLow, mid, end, availWidth, lineHeightMultiplier)); } }
static ICharSequence MidTruncatedFormattedIter(F9PFormattedString baseFormattedString, TextPaint paint, int secondToLastEnd, int startLastVisible, int midLastVisible, int startLow, int startHigh, int end, float availWidth, float lineHeightMultiplier) { if (startHigh - startLow <= 1) { return(baseFormattedString.ToSpannableString(EllipsePlacement.Mid, secondToLastEnd, startHigh, end, startLastVisible, midLastVisible)); } int mid = (startLow + startHigh) / 2; var formattedText = baseFormattedString.ToSpannableString(EllipsePlacement.Mid, 0, mid, end, startLastVisible, midLastVisible); using (var layout = TextExtensions.StaticLayout(formattedText, paint, int.MaxValue - 10000, Android.Text.Layout.Alignment.AlignNormal, lineHeightMultiplier, 0.0f, true)) { if (layout.GetLineWidth(0) > availWidth) { return(MidTruncatedFormattedIter(baseFormattedString, paint, secondToLastEnd, startLastVisible, midLastVisible, mid, startHigh, end, availWidth, lineHeightMultiplier)); } return(MidTruncatedFormattedIter(baseFormattedString, paint, secondToLastEnd, startLastVisible, midLastVisible, startLow, mid, end, availWidth, lineHeightMultiplier)); } }
SizeRequest InternalLayout(F9PTextView control, TextControlState state) { if (Element is Forms9Patch.Label element && control != null) { ICharSequence text = state.JavaText; var tmpFontSize = BoundTextSize(element.FontSize); control.Typeface = state.Typeface; //control.SetTextColor(state.TextColor.ToAndroid()); UpdateColor(control); control.TextSize = tmpFontSize; control.IsNativeDrawEnabled = false; control.SetSingleLine(false); control.SetMaxLines(int.MaxValue / 2); control.SetIncludeFontPadding(false); control.Ellipsize = null; double tmpHt = -1; double tmpWd = -1; var fontMetrics = control.Paint.GetFontMetrics(); var fontLineHeight = fontMetrics.Descent - fontMetrics.Ascent; var fontLeading = System.Math.Abs(fontMetrics.Bottom - fontMetrics.Descent); if (state.Lines == 0) { if (state.AvailHeight < int.MaxValue / 3) { tmpFontSize = TextPaintExtensions.ZeroLinesFit(state.JavaText, new TextPaint(control.Paint), ModelMinFontSize, tmpFontSize, state.AvailWidth, state.AvailHeight); } } else { if (state.AutoFit == AutoFit.Lines) { if (state.AvailHeight > int.MaxValue / 3) { tmpHt = System.Math.Round(state.Lines * fontLineHeight + (state.Lines - 1) * fontLeading); } else { var fontPointSize = tmpFontSize; var lineHeightRatio = fontLineHeight / fontPointSize; var leadingRatio = fontLeading / fontPointSize; tmpFontSize = ((state.AvailHeight / (state.Lines + leadingRatio * (state.Lines - 1))) / lineHeightRatio - 0.1f); } } else if (state.AutoFit == AutoFit.Width) { tmpFontSize = TextPaintExtensions.WidthFit(state.JavaText, new TextPaint(control.Paint), state.Lines, ModelMinFontSize, tmpFontSize, state.AvailWidth, state.AvailHeight); } } tmpFontSize = BoundTextSize(tmpFontSize); // this is the optimal font size. Let it be known! if (System.Math.Abs(tmpFontSize - element.FittedFontSize) > 0.1) { if (System.Math.Abs(tmpFontSize - element.FontSize) < 0.1 || (element.FontSize < 0 && System.Math.Abs(tmpFontSize - F9PTextView.DefaultTextSize) < 0.1)) { element.FittedFontSize = -1; } else { element.FittedFontSize = tmpFontSize; } } var syncFontSize = (float)((ILabel)element).SynchronizedFontSize; if (syncFontSize >= 0 && System.Math.Abs(tmpFontSize - syncFontSize) > 0.1) { tmpFontSize = syncFontSize; } control.TextSize = tmpFontSize; state.RenderedFontSize = tmpFontSize; var layout = TextExtensions.StaticLayout(state.JavaText, new TextPaint(control.Paint), state.AvailWidth, Android.Text.Layout.Alignment.AlignNormal, 1.0f, 0.0f, true); int lines = state.Lines; if (lines == 0 && state.AutoFit == AutoFit.None) { for (int i = 0; i < layout.LineCount; i++) { if (layout.GetLineBottom(i) <= state.AvailHeight - layout.TopPadding - layout.BottomPadding) { lines++; } else { break; } } } if (layout.Height > state.AvailHeight || (lines > 0 && layout.LineCount > lines)) { if (state.Lines == 1) { control.SetSingleLine(true); control.SetMaxLines(1); control.Ellipsize = state.LineBreakMode.ToEllipsize(); } else { layout = TextPaintExtensions.Truncate(state.Text, element.F9PFormattedString, new TextPaint(control.Paint), state.AvailWidth, state.AvailHeight, element.AutoFit, element.LineBreakMode, ref lines, ref text); } } lines = lines > 0 ? System.Math.Min(lines, layout.LineCount) : layout.LineCount; for (int i = 0; i < lines; i++) { tmpHt = layout.GetLineBottom(i); var width = layout.GetLineWidth(i); if (width > tmpWd) { tmpWd = System.Math.Ceiling(width); } } if (state.AutoFit == AutoFit.None && state.Lines > 0) { control.SetMaxLines(state.Lines); } if (element.IsDynamicallySized && state.Lines > 0 && state.AutoFit == AutoFit.Lines) { fontMetrics = control.Paint.GetFontMetrics(); fontLineHeight = fontMetrics.Descent - fontMetrics.Ascent; fontLeading = System.Math.Abs(fontMetrics.Bottom - fontMetrics.Descent); tmpHt = fontLineHeight * state.Lines + fontLeading * (state.Lines - 1); } control.Gravity = element.HorizontalTextAlignment.ToHorizontalGravityFlags() | element.VerticalTextAlignment.ToVerticalGravityFlags(); if (element.Text != null) { control.Text = text.ToString(); } else { control.TextFormatted = text; } var result = new SizeRequest(new Size(System.Math.Ceiling(tmpWd), System.Math.Ceiling(tmpHt)), new Size(10, System.Math.Ceiling(tmpHt))); if (element.LineBreakMode == LineBreakMode.NoWrap) { control.SetSingleLine(true); } control.IsNativeDrawEnabled = true; if (control == Control) { //P42.Utils.Debug.Message(Element?.HtmlText, "!!!~~~~~~~ REQUEST LAYOUT~~~~~~~~~!!!"); // none of the below seems to help the issue where the label is not apparing in ConnectionCalc cells //Control.RequestLayout(); ////P42.Utils.Debug.Message(Element?.HtmlText, "Control.Invalidate()"); //Control.Invalidate(); //Invalidate(); //var widthSpec = MeasureSpec.MakeMeasureSpec(tmpWd, Android.Views.MeasureSpecMode.Exactly); //var heightSpec = MeasureSpec.MakeMeasureSpec(tmpHt, Android.Views.MeasureSpecMode.Exactly); //Control.Measure(widthSpec, heightSpec); //Control.RequestLayout(); //Control.Layout(0, 0, (int)System.Math.Ceiling(tmpWd), (int)System.Math.Ceiling(tmpHt)); //Control } return(result); } return(new SizeRequest(Size.Zero)); }
#pragma warning disable IDE0060 // Remove unused parameter internal static StaticLayout Truncate(string text, Forms9Patch.F9PFormattedString baseFormattedString, TextPaint paint, int availWidth, int availHeight, AutoFit fit, LineBreakMode lineBreakMode, ref int lines, ref ICharSequence textFormatted) #pragma warning restore IDE0060 // Remove unused parameter { StaticLayout layout; var fontMetrics = paint.GetFontMetrics(); var fontLineHeight = fontMetrics.Descent - fontMetrics.Ascent; var fontLeading = System.Math.Abs(fontMetrics.Bottom - fontMetrics.Descent); textFormatted = ((ICharSequence)baseFormattedString?.ToSpannableString()) ?? new Java.Lang.String(text); if (lines > 0) { if (baseFormattedString != null) { layout = TextExtensions.StaticLayout(textFormatted, paint, availWidth, Android.Text.Layout.Alignment.AlignNormal, 1.0f, 0.0f, true); if (layout.Height > availHeight) { var visibleLines = (int)((fontLeading + availHeight) / (fontLineHeight + fontLeading)); if (visibleLines < lines) { lines = visibleLines; } } if (layout.LineCount > lines && lines > 0) { var secondToLastEnd = lines > 1 ? layout.GetLineEnd(lines - 2) : 0; var start = lines > 1 ? layout.GetLineStart(layout.LineCount - 2) : 0; switch (lineBreakMode) { case LineBreakMode.HeadTruncation: textFormatted = StartTruncatedFormatted(baseFormattedString, paint, secondToLastEnd, start, layout.GetLineEnd(layout.LineCount - 1), availWidth); break; case LineBreakMode.MiddleTruncation: textFormatted = MidTruncatedFormatted(baseFormattedString, paint, secondToLastEnd, layout.GetLineStart(lines - 1), (layout.GetLineEnd(lines - 1) + layout.GetLineStart(lines - 1)) / 2 - 1, start, layout.GetLineEnd(layout.LineCount - 1), availWidth); break; case LineBreakMode.TailTruncation: textFormatted = EndTruncatedFormatted(baseFormattedString, paint, secondToLastEnd, layout.GetLineStart(lines - 1), layout.GetLineEnd(layout.LineCount - 1), availWidth); break; default: textFormatted = TruncatedFormatted(baseFormattedString, paint, secondToLastEnd, layout.GetLineStart(lines - 1), layout.GetLineEnd(layout.LineCount - 1), availWidth); break; } } } else { layout = TextExtensions.StaticLayout(text, paint, availWidth, Android.Text.Layout.Alignment.AlignNormal, 1.0f, 0.0f, true); if (layout.Height > availHeight) { var visibleLines = (int)((fontLeading + availHeight) / (fontLineHeight + fontLeading)); if (visibleLines < lines) { lines = visibleLines; } } if (layout.LineCount > lines && lines > 0) { var secondToLastEnd = lines > 1 ? layout.GetLineEnd(lines - 2) : 0; var start = lines > 1 ? layout.GetLineStart(layout.LineCount - 2) : 0; switch (lineBreakMode) { case LineBreakMode.HeadTruncation: text = StartTruncatedLastLine(text, paint, secondToLastEnd, start, layout.GetLineEnd(layout.LineCount - 1), availWidth); break; case LineBreakMode.MiddleTruncation: text = MidTruncatedLastLine(text, paint, secondToLastEnd, layout.GetLineStart(lines - 1), (layout.GetLineEnd(lines - 1) + layout.GetLineStart(lines - 1)) / 2 - 1, start, layout.GetLineEnd(layout.LineCount - 1), availWidth); break; case LineBreakMode.TailTruncation: text = EndTruncatedLastLine(text, paint, secondToLastEnd, layout.GetLineStart(lines - 1), layout.GetLineEnd(layout.LineCount - 1), availWidth); break; default: text = text.Substring(0, layout.GetLineEnd(lines - 1)); break; } textFormatted = new Java.Lang.String(text); } } } var result = TextExtensions.StaticLayout(textFormatted, paint, availWidth, Android.Text.Layout.Alignment.AlignNormal, 1.0f, 0.0f, true); return(result); }