--- /dev/null
+using System;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+using System.Linq;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.Label)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.None, 0, "Implementation of Label TextType", PlatformAffected.All)]
+ public class LabelTextType : TestContentPage
+ {
+ protected override void Init()
+ {
+ var label = new Label
+ {
+ AutomationId = "TextTypeLabel",
+ Text = "<h1>Hello World!</h1>"
+ };
+
+ var button = new Button
+ {
+ AutomationId = "ToggleTextTypeButton",
+ Text = "Toggle HTML/Plain"
+ };
+
+ button.Clicked += (s, a) =>
+ {
+ label.TextType = label.TextType == TextType.Html ? TextType.Text : TextType.Html;
+ };
+
+
+ Label htmlLabel = new Label() { TextType = TextType.Html };
+ Label normalLabel = new Label();
+ Label nullLabel = new Label() { TextType = TextType.Html };
+
+ Button toggle = new Button()
+ {
+ Text = "Toggle some more things",
+ Command = new Command(() =>
+ {
+ htmlLabel.Text = $"<b>{DateTime.UtcNow}</b>";
+ normalLabel.Text = $"<b>{DateTime.UtcNow}</b>";
+
+ if (String.IsNullOrWhiteSpace(nullLabel.Text))
+ nullLabel.Text = "hi there";
+ else
+ nullLabel.Text = null;
+ })
+ };
+
+
+ var stacklayout = new StackLayout();
+ stacklayout.Children.Add(label);
+ stacklayout.Children.Add(button);
+ stacklayout.Children.Add(htmlLabel);
+ stacklayout.Children.Add(normalLabel);
+ stacklayout.Children.Add(nullLabel);
+ stacklayout.Children.Add(toggle);
+
+ Content = stacklayout;
+ }
+
+#if UITEST
+ [Test]
+ public void LabelToggleHtmlAndPlainTextTest()
+ {
+ RunningApp.WaitForElement ("TextTypeLabel");
+ RunningApp.Screenshot ("I see plain text");
+
+ Assert.IsTrue(RunningApp.Query("TextTypeLabel").FirstOrDefault()?.Text == "<h1>Hello World!</h1>");
+
+ RunningApp.Tap("ToggleTextTypeButton");
+ RunningApp.Screenshot ("I see HTML text");
+
+ Assert.IsFalse(RunningApp.Query("TextTypeLabel").FirstOrDefault()?.Text.Contains("<h1>") ?? true);
+ }
+#endif
+ }
+}
<Compile Include="$(MSBuildThisFileDirectory)Issue6738.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GitHub6926.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue5503.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)LabelTextType.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Bugzilla22229.xaml">
Padding = new Thickness(40, 20)
}
);
+
+ var htmlLabelContainer = new ViewContainer<Label>(Test.Label.TextType,
+ new Label
+ {
+ Text = "<h1>Hello world!</h1>",
+ TextType = TextType.Html
+ });
+
+ var htmlLabelMultipleLinesContainer = new ViewContainer<Label>(Test.Label.TextType,
+ new Label
+ {
+ Text = "<h1>Hello world!</h1><p>Lorem <strong>ipsum</strong> bla di bla <i>blabla</i> blablabl ablabla & blablablablabl ablabl ablablabl ablablabla blablablablablablab lablablabla blablab lablablabla blablabl ablablablab lablabla blab lablablabla blablab lablabla blablablablab lablabla blablab lablablabl ablablabla blablablablablabla blablabla</p>",
+ TextType = TextType.Html,
+ MaxLines = 3
+ });
+
+ var toggleLabel = new Label
+ {
+ TextType = TextType.Html,
+ Text = "<h1 style=\"color: red;\">Hello world!</h1><p>Lorem <strong>ipsum</strong></p>"
+
+ };
+
+ var gestureRecognizer = new TapGestureRecognizer();
+
+ gestureRecognizer.Tapped += (s, a) =>
+ {
+ toggleLabel.TextType = toggleLabel.TextType == TextType.Html ? TextType.Text : TextType.Html;
+ };
+
+ toggleLabel.GestureRecognizers.Add(gestureRecognizer);
+
+ var toggleHtmlPlainTextLabelContainer = new ViewContainer<Label>(Test.Label.TextType,
+ toggleLabel);
Add (namedSizeMediumBoldContainer);
Add (namedSizeMediumItalicContainer);
Add (maxlinesTailTruncContainer);
Add (maxlinesWordWrapContainer);
Add(paddingContainer);
+ Add (htmlLabelContainer);
+ Add (htmlLabelMultipleLinesContainer);
+ Add (toggleHtmlPlainTextLabelContainer);
}
}
}
\ No newline at end of file
});
public static readonly BindableProperty PaddingProperty = PaddingElement.PaddingProperty;
+
+ public static readonly BindableProperty TextTypeProperty = BindableProperty.Create(nameof(TextType), typeof(TextType), typeof(Label), TextType.Text,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((Label)bindable).InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged));
readonly Lazy<PlatformConfigurationRegistry<Label>> _platformConfigurationRegistry;
get { return (Thickness)GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
+
+ public TextType TextType
+ {
+ get => (TextType)GetValue(TextTypeProperty);
+ set => SetValue(TextTypeProperty, value);
+ }
double IFontElement.FontSizeDefaultValueCreator() =>
Device.GetNamedSize(NamedSize.Default, (Label)this);
--- /dev/null
+namespace Xamarin.Forms
+{
+ public enum TextType
+ {
+ Text,
+ Html
+ }
+}
\ No newline at end of file
VerticalTextAlignmentStart,
VerticalTextAlignmentCenter,
VerticalTextAlignmentEnd,
- MaxLines
+ MaxLines,
+ TextType
}
public enum MasterDetailPage
UpdateGravity();
if (e.OldElement?.MaxLines != e.NewElement.MaxLines)
UpdateMaxLines();
+
UpdatePadding();
ElevationHelper.SetElevation(this, e.NewElement);
if (e.PropertyName == Label.HorizontalTextAlignmentProperty.PropertyName || e.PropertyName == Label.VerticalTextAlignmentProperty.PropertyName)
UpdateGravity();
- else if (e.PropertyName == Label.TextColorProperty.PropertyName)
+ else if (e.PropertyName == Label.TextColorProperty.PropertyName ||
+ e.PropertyName == Label.TextTypeProperty.PropertyName)
UpdateText();
else if (e.PropertyName == Label.FontProperty.PropertyName)
UpdateText();
SetTextColor(_labelTextColorDefault);
_lastUpdateColor = Color.Default;
}
- Text = Element.Text;
+
+ switch (Element.TextType)
+ {
+ case TextType.Html:
+ if (Forms.IsNougatOrNewer)
+ Control.SetText(Html.FromHtml(Element.Text ?? string.Empty, FromHtmlOptions.ModeCompact), BufferType.Spannable);
+ else
+#pragma warning disable CS0618 // Type or member is obsolete
+ Control.SetText(Html.FromHtml(Element.Text ?? string.Empty), BufferType.Spannable);
+#pragma warning restore CS0618 // Type or member is obsolete
+ break;
+
+ default:
+ Text = Element.Text;
+ break;
+ }
+
UpdateColor();
UpdateFont();
_lastSizeRequest = null;
}
}
-}
+}
\ No newline at end of file
static BuildVersionCodes? s_sdkInt;
static bool? s_isLollipopOrNewer;
static bool? s_isMarshmallowOrNewer;
+ static bool? s_isNougatOrNewer;
[Obsolete("Context is obsolete as of version 2.5. Please use a local context instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
}
}
+ internal static bool IsNougatOrNewer
+ {
+ get
+ {
+ if (!s_isNougatOrNewer.HasValue)
+ s_isNougatOrNewer = (int)Build.VERSION.SdkInt >= 24;
+ return s_isNougatOrNewer.Value;
+ }
+ }
+
public static float GetFontSizeNormal(Context context)
{
float size = 50;
UpdateMaxLines();
if (e.OldElement.CharacterSpacing != e.NewElement.CharacterSpacing)
UpdateCharacterSpacing();
-
}
UpdateTextDecorations();
UpdatePadding();
UpdateLineBreakMode();
else if (e.PropertyName == Label.TextDecorationsProperty.PropertyName)
UpdateTextDecorations();
- else if (e.PropertyName == Label.TextProperty.PropertyName || e.PropertyName == Label.FormattedTextProperty.PropertyName)
+ else if (e.IsOneOf(Label.TextProperty, Label.FormattedTextProperty, Label.TextTypeProperty))
UpdateText();
else if (e.PropertyName == Label.LineHeightProperty.PropertyName)
UpdateLineHeight();
else if (e.PropertyName == Label.MaxLinesProperty.PropertyName)
UpdateMaxLines();
- else if (e.PropertyName == ImageButton.PaddingProperty.PropertyName)
+ else if (e.PropertyName == Label.PaddingProperty.PropertyName)
UpdatePadding();
}
}
}
-
void UpdateLineHeight()
{
_lastSizeRequest = null;
_view.SetTextColor(_labelTextColorDefault);
_lastUpdateColor = Color.Default;
}
- _view.Text = Element.Text;
+
+ switch (Element.TextType)
+ {
+
+ case TextType.Html:
+ if (Forms.IsNougatOrNewer)
+ Control.SetText(Html.FromHtml(Element.Text ?? string.Empty, FromHtmlOptions.ModeCompact), TextView.BufferType.Spannable);
+ else
+#pragma warning disable CS0618 // Type or member is obsolete
+ Control.SetText(Html.FromHtml(Element.Text ?? string.Empty), TextView.BufferType.Spannable);
+#pragma warning restore CS0618 // Type or member is obsolete
+ break;
+
+ default:
+ _view.Text = Element.Text;
+
+ break;
+ }
+
UpdateColor();
UpdateFont();
--- /dev/null
+using System.Xml.Linq;
+using Windows.UI.Xaml.Documents;
+
+namespace Xamarin.Forms.Platform.UAP
+{
+ internal static class LabelHtmlHelper
+ {
+ // All the supported HTML tags
+ internal const string ElementB = "B";
+ internal const string ElementBr = "BR";
+ internal const string ElementEm = "EM";
+ internal const string ElementI = "I";
+ internal const string ElementP = "P";
+ internal const string ElementStrong = "STRONG";
+ internal const string ElementU = "U";
+ internal const string ElementUl = "UL";
+ internal const string ElementLi = "LI";
+ internal const string ElementDiv = "DIV";
+
+ public static void ParseText(XElement element, InlineCollection inlines, Label label)
+ {
+ if (element == null)
+ return;
+
+ var currentInlines = inlines;
+ var elementName = element.Name.ToString().ToUpper();
+ switch (elementName)
+ {
+ case ElementB:
+ case ElementStrong:
+ var bold = new Bold();
+ inlines.Add(bold);
+ currentInlines = bold.Inlines;
+ break;
+ case ElementI:
+ case ElementEm:
+ var italic = new Italic();
+ inlines.Add(italic);
+ currentInlines = italic.Inlines;
+ break;
+ case ElementU:
+ var underline = new Underline();
+ inlines.Add(underline);
+ currentInlines = underline.Inlines;
+ break;
+ case ElementBr:
+ inlines.Add(new LineBreak());
+ break;
+ case ElementP:
+ // Add two line breaks, one for the current text and the second for the gap.
+ if (AddLineBreakIfNeeded(inlines))
+ {
+ inlines.Add(new LineBreak());
+ }
+
+ var paragraphSpan = new Windows.UI.Xaml.Documents.Span();
+ inlines.Add(paragraphSpan);
+ currentInlines = paragraphSpan.Inlines;
+ break;
+ case ElementLi:
+ inlines.Add(new LineBreak());
+ inlines.Add(new Run { Text = " • " });
+ break;
+ case ElementUl:
+ case ElementDiv:
+ AddLineBreakIfNeeded(inlines);
+ var divSpan = new Windows.UI.Xaml.Documents.Span();
+ inlines.Add(divSpan);
+ currentInlines = divSpan.Inlines;
+ break;
+ }
+ foreach (var node in element.Nodes())
+ {
+ if (node is XText textElement)
+ {
+ currentInlines.Add(new Run { Text = textElement.Value });
+ }
+ else
+ {
+ ParseText(node as XElement, currentInlines, label);
+ }
+ }
+ // Add newlines for paragraph tags
+ if (elementName == "ElementP")
+ {
+ currentInlines.Add(new LineBreak());
+ }
+ }
+
+ static bool AddLineBreakIfNeeded(InlineCollection inlines)
+ {
+ if (inlines.Count <= 0)
+ return false;
+
+ var lastInline = inlines[inlines.Count - 1];
+ while ((lastInline is Windows.UI.Xaml.Documents.Span))
+ {
+ var span = (Windows.UI.Xaml.Documents.Span)lastInline;
+ if (span.Inlines.Count > 0)
+ {
+ lastInline = span.Inlines[span.Inlines.Count - 1];
+ }
+ }
+
+ if (lastInline is LineBreak)
+ return false;
+
+ inlines.Add(new LineBreak());
+ return true;
+ }
+ }
+}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
using Windows.Foundation;
using Windows.UI.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;
+using Xamarin.Forms.Platform.UAP;
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;
using Specifics = Xamarin.Forms.PlatformConfiguration.WindowsSpecific.Label;
using WThickness = Windows.UI.Xaml.Thickness;
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
- if (e.PropertyName == Label.TextProperty.PropertyName ||
- e.PropertyName == Label.FormattedTextProperty.PropertyName)
- {
+ if (e.IsOneOf(Label.TextProperty, Label.FormattedTextProperty, Label.TextTypeProperty))
UpdateText(Control);
- }
else if (e.PropertyName == Label.TextColorProperty.PropertyName)
UpdateColor(Control);
else if (e.PropertyName == Label.HorizontalTextAlignmentProperty.PropertyName || e.PropertyName == Label.VerticalTextAlignmentProperty.PropertyName)
UpdateMaxLines(Control);
else if (e.PropertyName == Label.PaddingProperty.PropertyName)
UpdatePadding(Control);
+
base.OnElementPropertyChanged(sender, e);
}
_perfectSizeValid = false;
if (textBlock == null)
- {
return;
+
+ switch (Element.TextType)
+ {
+ case TextType.Html:
+ UpdateTextHtml(textBlock);
+ break;
+
+ default:
+ UpdateTextPlainText(textBlock);
+ break;
}
+ }
+ void UpdateTextPlainText(TextBlock textBlock)
+ {
Label label = Element;
if (label != null)
{
}
}
+ void UpdateTextHtml(TextBlock textBlock)
+ {
+ var text = Element.Text ?? String.Empty;
+
+ // Just in case we are not given text with elements.
+ var modifiedText = string.Format("<div>{0}</div>", text);
+ modifiedText = Regex.Replace(modifiedText, "<br>", "<br></br>", RegexOptions.IgnoreCase);
+ // reset the text because we will add to it.
+ Control.Inlines.Clear();
+ try
+ {
+ var element = XElement.Parse(modifiedText);
+ LabelHtmlHelper.ParseText(element, Control.Inlines, Element);
+ }
+ catch (Exception)
+ {
+ // if anything goes wrong just show the html
+ textBlock.Text = Windows.Data.Html.HtmlUtilities.ConvertToText(Element.Text);
+ }
+ }
+
void UpdateDetectReadingOrderFromContent(TextBlock textBlock)
{
if (Element.IsSet(Specifics.DetectReadingOrderFromContentProperty))
Element.Padding.Bottom);
}
}
-}
+}
\ No newline at end of file
<Compile Include="ITitleViewRendererController.cs" />
<Compile Include="IToolbarProvider.cs" />
<Compile Include="IVisualNativeElementRenderer.cs" />
+ <Compile Include="LabelHtmlHelper.cs" />
<Compile Include="NativeBindingExtensions.cs" />
<Compile Include="NativeEventWrapper.cs" />
<Compile Include="NativePropertyListener.cs" />
Label.FormattedTextProperty.PropertyName,
Label.LineBreakModeProperty.PropertyName,
Label.LineHeightProperty.PropertyName,
- Label.PaddingProperty.PropertyName
+ Label.PaddingProperty.PropertyName,
+ Label.TextTypeProperty.PropertyName
};
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
UpdateMaxLines();
else if (e.PropertyName == Label.PaddingProperty.PropertyName)
UpdatePadding();
+ else if (e.PropertyName == Label.TextTypeProperty.PropertyName)
+ UpdateText();
}
-
protected override NativeLabel CreateNativeControl()
{
#if __MOBILE__
void UpdateTextDecorations()
{
+ if (Element?.TextType != TextType.Text)
+ return;
+
if (!Element.IsSet(Label.TextDecorationsProperty))
return;
#else
Control.AttributedStringValue = newAttributedText;
#endif
+ _perfectSizeValid = false;
}
#if __MOBILE__
{
#if __MOBILE__
+ if (Element?.TextType != TextType.Text)
+ return;
+
var textAttr = Control.AttributedText.AddCharacterSpacing(Element.Text, Element.CharacterSpacing);
if (textAttr != null)
Control.AttributedText = textAttr;
+
+ _perfectSizeValid = false;
#endif
}
void UpdateText()
{
+ switch (Element.TextType)
+ {
+ case TextType.Html:
+ UpdateTextHtml();
+ break;
+
+ default:
+ UpdateTextPlainText();
+ break;
+ }
+ }
+
+ void UpdateTextPlainText()
+ {
_formatted = Element.FormattedText;
if (_formatted == null && Element.LineHeight >= 0)
_formatted = Element.Text;
#else
Control.AttributedStringValue = _formatted.ToAttributed(Element, Element.TextColor, Element.HorizontalTextAlignment, Element.LineHeight);
#endif
+ _perfectSizeValid = false;
+ }
+
+ void UpdateTextHtml()
+ {
+ string text = Element.Text ?? string.Empty;
+
+#if __MOBILE__
+ var attr = new NSAttributedStringDocumentAttributes
+ {
+ DocumentType = NSDocumentType.HTML
+ };
+
+ NSError nsError = null;
+
+ Control.AttributedText = new NSAttributedString(text, attr, ref nsError);
+
+#else
+ var attr = new NSAttributedStringDocumentAttributes
+ {
+ DocumentType = NSDocumentType.HTML
+ };
+
+ var htmlData = new NSMutableData();
+ htmlData.SetData(text);
+
+ Control.AttributedStringValue = new NSAttributedString(htmlData, attr, out _);
+#endif
+ _perfectSizeValid = false;
}
void UpdateFont()
{
+ if (Element?.TextType != TextType.Text)
+ return;
+
if (IsTextFormatted)
{
UpdateFormattedText();
void UpdateTextColor()
{
+ if (Element?.TextType != TextType.Text)
+ return;
+
if (IsTextFormatted)
{
UpdateFormattedText();
}
#endif
}
-}
\ No newline at end of file
+}