Xamarin.Forms: Fast HtmlLabel with support for RTL languages

1_28-1lYrYTQoLhi87mllgBw.pngHere’s a really simple, and fast rendering HTML label control for Xamarin Forms. It focuses on speed and only implementing a few common HTML elements.

Supports all human languages including Right-To-Left languages like Arabic and Persian.

To your main project add a new HtmlLabel class, renderers will handle the rest in the Android and iOS project:

namespace App.Views.Common
{
    public class HtmlLabel :Label
    {
        
    }
}

In iOS, add this Renderer:

[assemblyExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
 
namespace App.iOS.Renderers
{
    public class HtmlLabelRenderer : LabelRenderer
    {
        private HtmlLabel _element//Holds the view for later re-use
 
        protected override void OnElementPropertyChanged(object senderSystem.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sendere);
            if (this.Element == null || this.Control == null)
                return;
 
	    //Watch for changes in common properties
            if (e.PropertyName == Label.TextProperty.PropertyName || 
                e.PropertyName == Label.FontAttributesProperty.PropertyName ||
                e.PropertyName == Label.FontFamilyProperty.PropertyName || 
                e.PropertyName == Label.FontSizeProperty.PropertyName ||
                e.PropertyName == Label.TextColorProperty.PropertyName 
                )
            {
                Update();
            }
        }
 
        protected override void OnElementChanged(ElementChangedEventArgs<Labele)
        {
            base.OnElementChanged(e);
 
            if (Element is HtmlLabel view)
            {
	            view.FontFamily = "Arial"//Update with your font.
	            _element = view;   //Hold on to the element
	            Update();
            }
        }
 
        private void Update()
        {
            var attr = new NSAttributedStringDocumentAttributes();
            var nsError = new NSError();
            attr.DocumentType = NSDocumentType.HTML;
 
            if (_element is HtmlLabel htmlLabel)
            {
                var fontFamily = htmlLabel.FontFamily??"Arial";
                var fontSize = htmlLabel.FontSize;
                var text = htmlLabel.Text;
                var color = htmlLabel.TextColor;
                
                //Default to Left To Right
                var dir = "ltr";
                if (text.HasValues() && ContainsArabicOrPersianCharacter(text))
                {
                    dir = "rtl";  //Arabic or Persian
                }
 
				//Render the string with left to right or right to left support
                string html = $"\"font-family:{fontFamily};font-size:{fontSize};color:{color.ToHexString()};\">{text}";
                var myHtmlData = NSData.FromString(htmlNSStringEncoding.Unicode);
                Control.AttributedText = new NSAttributedString(myHtmlDataattrref nsError);
            }
        }
 
        static bool ContainsArabicOrPersianCharacter(string s)
        {
	        byte[] bytes = Encoding.Unicode.GetBytes(s.SafeSubstring(s.Length/2,5));  //Sample half way in the string then see if any are arabic code page
            for (int i = 1; i < bytes.Lengthi += 2)
                if (bytes[i] == 6) //0x06** is arabic/persian code page
                    return true;
            return false;
        }
    }

And in Android:

[assemblyExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace App.Droid.Renderers
{
    public class HtmlLabelRenderer : LabelRenderer
    {
		private static bool _canUseHtml = true;	 //Android 5 and below can't
        private HtmlLabel _element;	 //Hold on to the element
        public HtmlLabelRenderer(Context c): base(c)
        {
           
        }
 
        protected override void OnElementPropertyChanged(object senderSystem.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sendere);
            if (this.Element == null || this.Control == null)
                return;
 
			//Watch these properties
            if (e.PropertyName == Label.TextProperty.PropertyName ||
                e.PropertyName == Label.FontAttributesProperty.PropertyName ||
                e.PropertyName == Label.FontFamilyProperty.PropertyName ||
                e.PropertyName == Label.FontSizeProperty.PropertyName ||
                e.PropertyName == Label.TextColorProperty.PropertyName)
            {
                Update();
            }
        }
 
        protected override void OnElementChanged(ElementChangedEventArgs<Labele)
        {
            base.OnElementChanged(e);
 
            if (Element is HtmlLabel view)
            {
	            _element = view;  //Hold on the the element
	            Update();
            }
        }
 
        private void Update()
        {
            if (_element is HtmlLabel htmlLabel)
            {
                var fontFamily = htmlLabel.FontFamily;
                var fontSize = htmlLabel.FontSize;
                var text = htmlLabel.Text;
                var color = htmlLabel.TextColor;
                try
                {
 
	           //Watch for Right to Left languages
                    var dir = "ltr";
                    if (text.HasValues() && ContainsArabicOrPersianCharacter(text))
                    {
                        dir = "rtl";
                    }
 
		    //SEE:
                    //https://www.grokkingandroid.com/android-quick-tip-formatting-text-with-html-fromhtml/
                    string html = $"<html dir='{dir}'><body><font style=\"face:{fontFamily};size:{fontSize};color:{color.ToHexString()}\">{text}</font></body></html>";
 
		 //If we ever fail, it means the Android version can't use HTML labels (Android 5)
		 //so gracefully downgrade
                    if (_canUseHtml)
                    {
	                    Control.SetText(Html.FromHtml(htmlFromHtmlOptions.OptionUseCssColors), TextView.BufferType.Spannable);
                    }
                    else
                    {
	                    Control.SetText(text.StripHtml(false), TextView.BufferType.Normal);
                    }
                }
                catch  //Failed so this version of android can't
                {
	                _canUseHtml = false;  //Android 5 and below - don't try again
                    Control.SetText(text.StripHtml(false),TextView.BufferType.Normal);
                }
            }
        }
 
        static bool ContainsArabicOrPersianCharacter(string s)
        {
            byte[] bytes = Encoding.Unicode.GetBytes(s.SafeSubstring(s.Length / 2, 5)); //Sample half way in the string then see if any are arabic code page
            for (int i = 1; i < bytes.Lengthi += 2)
                if (bytes[i] == 6) //0x06** is arabic code page
                    return true;
            return false;
        }
    }

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s