diff --git a/Maui/.editorconfig b/Maui/.editorconfig
new file mode 100644
index 0000000..d753c22
--- /dev/null
+++ b/Maui/.editorconfig
@@ -0,0 +1,19 @@
+[*.cs]
+
+# CA1010: Collections should implement generic interface
+dotnet_diagnostic.CA1010.severity = silent
+
+# CA1710: Identifiers should have correct suffix
+dotnet_diagnostic.CA1710.severity = silent
+
+# CA1303: Do not pass literals as localized parameters
+dotnet_diagnostic.CA1303.severity = silent
+
+# CS0618: Type or member is obsolete
+dotnet_diagnostic.CS0618.severity = silent
+
+# CA1031: Do not catch general exception types
+dotnet_diagnostic.CA1031.severity = silent
+
+# CA2000: Dispose objects before losing scope
+dotnet_diagnostic.CA2000.severity = silent
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/App.xaml b/Maui/HtmlLabel.Forms.Plugin.Tests.App/App.xaml
new file mode 100644
index 0000000..fd8f0d8
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/App.xaml.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/App.xaml.cs
new file mode 100644
index 0000000..6360e31
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/App.xaml.cs
@@ -0,0 +1,11 @@
+namespace HyperTextLabel.Maui.Tests.App;
+
+public partial class App : Application
+{
+ public App()
+ {
+ InitializeComponent();
+
+ MainPage = new AppShell();
+ }
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/AppShell.xaml b/Maui/HtmlLabel.Forms.Plugin.Tests.App/AppShell.xaml
new file mode 100644
index 0000000..31a5d7e
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/AppShell.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/AppShell.xaml.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/AppShell.xaml.cs
new file mode 100644
index 0000000..a404912
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/AppShell.xaml.cs
@@ -0,0 +1,9 @@
+namespace HyperTextLabel.Maui.Tests.App;
+
+public partial class AppShell : Shell
+{
+ public AppShell()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/HtmlSources.Designer.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/HtmlSources.Designer.cs
new file mode 100644
index 0000000..5c45ee2
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/HtmlSources.Designer.cs
@@ -0,0 +1,252 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace HyperTextLabel.Maui.Tests.App {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class HtmlSources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal HtmlSources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HyperTextLabel.Maui.Tests.App.HtmlSources", typeof(HtmlSources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to An align center string..
+ ///
+ internal static string AlignCenter {
+ get {
+ return ResourceManager.GetString("AlignCenter", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to An align end string..
+ ///
+ internal static string AlignEnd {
+ get {
+ return ResourceManager.GetString("AlignEnd", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to سلسلة عربية..
+ ///
+ internal static string Arab {
+ get {
+ return ResourceManager.GetString("Arab", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A <strong>bold</strong> string..
+ ///
+ internal static string Bold {
+ get {
+ return ResourceManager.GetString("Bold", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Red color string..
+ ///
+ internal static string Color {
+ get {
+ return ResourceManager.GetString("Color", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to This is a string with a custom font..
+ ///
+ internal static string CustomFont {
+ get {
+ return ResourceManager.GetString("CustomFont", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Text with image <img src="https://raw.githubusercontent.com/matteobortolazzo/HtmlLabelPlugin/master/Assets/icon.png" />.
+ ///
+ internal static string Image {
+ get {
+ return ResourceManager.GetString("Image", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to An <em>italic</em> string..
+ ///
+ internal static string Italic {
+ get {
+ return ResourceManager.GetString("Italic", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to This is a string with a different line height..
+ ///
+ internal static string LineHeight {
+ get {
+ return ResourceManager.GetString("LineHeight", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to This is a <a href="https://github.com/matteobortolazzo/HtmlLabelPlugin">Link</a>..
+ ///
+ internal static string Links {
+ get {
+ return ResourceManager.GetString("Links", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to This is a <a href="https://github.com/matteobortolazzo/HtmlLabelPlugin">Link</a> with options..
+ ///
+ internal static string LinksWithOptions {
+ get {
+ return ResourceManager.GetString("LinksWithOptions", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Email to <a href="mailto:github@github.com?subject=Awesome&body=Awesome%20plugin">github@github.com</a>..
+ ///
+ internal static string LinkToEmail {
+ get {
+ return ResourceManager.GetString("LinkToEmail", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Link to open maps <a href="geo:53.3242377,-6.3861295;u=5">here</a>.
+ ///
+ internal static string LinkToGeo {
+ get {
+ return ResourceManager.GetString("LinkToGeo", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Send SMS to <a href="sms://8372717112?body=Awesome%20plugin">8372717112</a>..
+ ///
+ internal static string LinkToSms {
+ get {
+ return ResourceManager.GetString("LinkToSms", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Call to <a href="tel://8372717112">8372717112</a>..
+ ///
+ internal static string LinkToTel {
+ get {
+ return ResourceManager.GetString("LinkToTel", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Call to <a href="tel:083 7271 7112">08372717112</a>..
+ ///
+ internal static string LinkToTelAlternate {
+ get {
+ return ResourceManager.GetString("LinkToTelAlternate", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to This is a <a href="https://github.com/matteobortolazzo/HtmlLabelPlugin">Link</a> with custom color..
+ ///
+ internal static string LinkWithColor {
+ get {
+ return ResourceManager.GetString("LinkWithColor", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A label with custom gestures..
+ ///
+ internal static string LinkWithGestures {
+ get {
+ return ResourceManager.GetString("LinkWithGestures", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to This is a <a href="https://github.com/matteobortolazzo/HtmlLabelPlugin">Link</a> with no underline..
+ ///
+ internal static string LinkWithoutUnderline {
+ get {
+ return ResourceManager.GetString("LinkWithoutUnderline", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to List<ul><li>First item</li><li>Second item</li></ul>.
+ ///
+ internal static string List {
+ get {
+ return ResourceManager.GetString("List", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to <p>First paragraph</p><p>Second paragraph</p>.
+ ///
+ internal static string Paragraphs {
+ get {
+ return ResourceManager.GetString("Paragraphs", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/HtmlSources.resx b/Maui/HtmlLabel.Forms.Plugin.Tests.App/HtmlSources.resx
new file mode 100644
index 0000000..d6be7ee
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/HtmlSources.resx
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ An align center string.
+
+
+ An align end string.
+
+
+ سلسلة عربية.
+
+
+ A <strong>bold</strong> string.
+
+
+ Red color string.
+
+
+ This is a string with a custom font.
+
+
+ Text with image <img src="https://raw.githubusercontent.com/matteobortolazzo/HtmlLabelPlugin/master/Assets/icon.png" />
+
+
+ An <em>italic</em> string.
+
+
+ This is a string with a different line height.
+
+
+ This is a <a href="https://github.com/matteobortolazzo/HtmlLabelPlugin">Link</a>.
+
+
+ This is a <a href="https://github.com/matteobortolazzo/HtmlLabelPlugin">Link</a> with options.
+
+
+ Email to <a href="mailto:github@github.com?subject=Awesome&body=Awesome%20plugin">github@github.com</a>.
+
+
+ Link to open maps <a href="geo:53.3242377,-6.3861295;u=5">here</a>
+
+
+ Send SMS to <a href="sms://8372717112?body=Awesome%20plugin">8372717112</a>.
+
+
+ Call to <a href="tel://8372717112">8372717112</a>.
+
+
+ Call to <a href="tel:083 7271 7112">08372717112</a>.
+
+
+ This is a <a href="https://github.com/matteobortolazzo/HtmlLabelPlugin">Link</a> with custom color.
+
+
+ A label with custom gestures.
+
+
+ This is a <a href="https://github.com/matteobortolazzo/HtmlLabelPlugin">Link</a> with no underline.
+
+
+ List<ul><li>First item</li><li>Second item</li></ul>
+
+
+ <p>First paragraph</p><p>Second paragraph</p>
+
+
\ No newline at end of file
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/HyperTextLabel.Maui.Tests.App.csproj b/Maui/HtmlLabel.Forms.Plugin.Tests.App/HyperTextLabel.Maui.Tests.App.csproj
new file mode 100644
index 0000000..64a353b
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/HyperTextLabel.Maui.Tests.App.csproj
@@ -0,0 +1,71 @@
+
+
+
+ net6.0-android;net6.0-ios
+ $(TargetFrameworks);net6.0-windows10.0.19041.0
+
+
+ Exe
+ HyperTextLabel.Maui.Tests.App
+ true
+ true
+ enable
+
+
+ HyperTextLabel.Maui.Tests.App
+
+
+ com.companyname.HyperTextLabel.Maui.Tests.App
+ F49B1950-D2D6-4246-861F-A061DA235263
+
+
+ 1.0
+ 1
+
+ 14.2
+ 14.0
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+ en-US
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ HtmlSources.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ HtmlSources.Designer.cs
+
+
+
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/MainPage.xaml b/Maui/HtmlLabel.Forms.Plugin.Tests.App/MainPage.xaml
new file mode 100644
index 0000000..5ad6829
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/MainPage.xaml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/MainPage.xaml.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/MainPage.xaml.cs
new file mode 100644
index 0000000..6afd02b
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/MainPage.xaml.cs
@@ -0,0 +1,43 @@
+namespace HyperTextLabel.Maui.Tests.App;
+
+public partial class MainPage : ContentPage
+{
+ public MainPage()
+ {
+ InitializeComponent();
+ BindingContext = new Sources();
+ }
+}
+
+public class Sources
+{
+ public string Bold => HtmlSources.Bold;
+ public string Italic => HtmlSources.Italic;
+ public string Color => HtmlSources.Color;
+ public string List => HtmlSources.List;
+ public string AlignCenter => HtmlSources.AlignCenter;
+ public string AlignEnd => HtmlSources.AlignEnd;
+ public string Links => HtmlSources.Links;
+ public string LinksWithOptions => HtmlSources.LinksWithOptions;
+ public string LinkToEmail => HtmlSources.LinkToEmail;
+ public string LinkToTel => HtmlSources.LinkToTel;
+ public string LinkToTelAlternative => HtmlSources.LinkToTelAlternate;
+ public string LinkToSms => HtmlSources.LinkToSms;
+ public string LinkToGeo => HtmlSources.LinkToGeo;
+ public string LinkWithColor => HtmlSources.LinkWithColor;
+ public string LinkWithoutUnderline => HtmlSources.LinkWithoutUnderline;
+ public string LinkWithGestures => HtmlSources.LinkWithGestures;
+ public string CustomFont => HtmlSources.CustomFont;
+ public string Arab => HtmlSources.Arab;
+ public string Image => HtmlSources.Image;
+ public string Paragraphs => HtmlSources.Paragraphs;
+ public string LineHeight => HtmlSources.LineHeight;
+ public Command Clicked => new Command(() => Browser.OpenAsync("https://github.com/matteobortolazzo/HtmlLabelPlugin"));
+ public BrowserLaunchOptions BrowserLaunchOptions => new BrowserLaunchOptions
+ {
+ LaunchMode = BrowserLaunchMode.SystemPreferred,
+ TitleMode = BrowserTitleMode.Show,
+ PreferredToolbarColor = Colors.AliceBlue,
+ PreferredControlColor = Colors.Violet
+ };
+}
\ No newline at end of file
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/MauiProgram.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/MauiProgram.cs
new file mode 100644
index 0000000..cf503b6
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/MauiProgram.cs
@@ -0,0 +1,22 @@
+using HyperTextLabel.Maui.Hosting;
+
+namespace HyperTextLabel.Maui.Tests.App;
+
+public static class MauiProgram
+{
+ public static MauiApp CreateMauiApp()
+ {
+ var builder = MauiApp.CreateBuilder();
+ builder
+ .UseMauiApp()
+ .ConfigureHyperTextLabel()
+ .ConfigureFonts(fonts =>
+ {
+ fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+ fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ fonts.AddFont("Allura-Regular.otf", "AlluraRegular");
+ });
+
+ return builder.Build();
+ }
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/AndroidManifest.xml b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 0000000..fb20840
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/MainActivity.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/MainActivity.cs
new file mode 100644
index 0000000..c08ae90
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/MainActivity.cs
@@ -0,0 +1,10 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+
+namespace HyperTextLabel.Maui.Tests.App;
+
+[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
+public class MainActivity : MauiAppCompatActivity
+{
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/MainApplication.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/MainApplication.cs
new file mode 100644
index 0000000..06c43ec
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/MainApplication.cs
@@ -0,0 +1,15 @@
+using Android.App;
+using Android.Runtime;
+
+namespace HyperTextLabel.Maui.Tests.App;
+
+[Application]
+public class MainApplication : MauiApplication
+{
+ public MainApplication(IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/Resources/values/colors.xml b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/Resources/values/colors.xml
new file mode 100644
index 0000000..c04d749
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Android/Resources/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #512BD4
+ #2B0B98
+ #2B0B98
+
\ No newline at end of file
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/MacCatalyst/AppDelegate.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/MacCatalyst/AppDelegate.cs
new file mode 100644
index 0000000..ecdac4d
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/MacCatalyst/AppDelegate.cs
@@ -0,0 +1,9 @@
+using Foundation;
+
+namespace HyperTextLabel.Maui.Tests.App;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/MacCatalyst/Info.plist b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/MacCatalyst/Info.plist
new file mode 100644
index 0000000..c96dd0a
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/MacCatalyst/Info.plist
@@ -0,0 +1,30 @@
+
+
+
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/MacCatalyst/Program.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/MacCatalyst/Program.cs
new file mode 100644
index 0000000..eeea0f7
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/MacCatalyst/Program.cs
@@ -0,0 +1,15 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace HyperTextLabel.Maui.Tests.App;
+
+public class Program
+{
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Tizen/Main.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Tizen/Main.cs
new file mode 100644
index 0000000..d00d340
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Tizen/Main.cs
@@ -0,0 +1,16 @@
+using System;
+using Microsoft.Maui;
+using Microsoft.Maui.Hosting;
+
+namespace HyperTextLabel.Maui.Tests.App;
+
+class Program : MauiApplication
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+
+ static void Main(string[] args)
+ {
+ var app = new Program();
+ app.Run(args);
+ }
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Tizen/tizen-manifest.xml b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Tizen/tizen-manifest.xml
new file mode 100644
index 0000000..a67d6c8
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Tizen/tizen-manifest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ appicon.xhigh.png
+
+
+
+
+ http://tizen.org/privilege/internet
+
+
+
+
\ No newline at end of file
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/App.xaml b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/App.xaml
new file mode 100644
index 0000000..2cbb57c
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/App.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/App.xaml.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/App.xaml.cs
new file mode 100644
index 0000000..00e6ebc
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/App.xaml.cs
@@ -0,0 +1,24 @@
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace HyperTextLabel.Maui.Tests.App.WinUI;
+
+///
+/// Provides application-specific behavior to supplement the default Application class.
+///
+public partial class App : MauiWinUIApplication
+{
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/Package.appxmanifest b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/Package.appxmanifest
new file mode 100644
index 0000000..299c62a
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/Package.appxmanifest
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+ c0d34b5b-a78e-4983-bba9-3166df9c2a11
+ HtmlLabel.Forms.Plugin.Tests.App.UWP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/app.manifest b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/app.manifest
new file mode 100644
index 0000000..25169e8
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/Windows/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/iOS/AppDelegate.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/iOS/AppDelegate.cs
new file mode 100644
index 0000000..ecdac4d
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/iOS/AppDelegate.cs
@@ -0,0 +1,9 @@
+using Foundation;
+
+namespace HyperTextLabel.Maui.Tests.App;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/iOS/Info.plist b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/iOS/Info.plist
new file mode 100644
index 0000000..3614e68
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/iOS/Info.plist
@@ -0,0 +1,53 @@
+
+
+
+
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ MinimumOSVersion
+ 14.2
+ CFBundleDisplayName
+ HyperTextLabel.Maui.Tests.App
+ CFBundleIdentifier
+ com.companyname.HyperTextLabel.Maui.Tests.App
+ CFBundleVersion
+ 1.0
+ CFBundleName
+ HyperTextLabel.Maui.Tests.App
+ LSApplicationQueriesSchemes
+
+ mailto
+ tel
+ sms
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleLocalizations
+
+ en
+ ar
+
+
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/iOS/Program.cs b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/iOS/Program.cs
new file mode 100644
index 0000000..eeea0f7
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Platforms/iOS/Program.cs
@@ -0,0 +1,15 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace HyperTextLabel.Maui.Tests.App;
+
+public class Program
+{
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Properties/launchSettings.json b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Properties/launchSettings.json
new file mode 100644
index 0000000..edf8aad
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Windows Machine": {
+ "commandName": "MsixPackage",
+ "nativeDebugging": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Fonts/Allura-Regular.otf b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Fonts/Allura-Regular.otf
new file mode 100644
index 0000000..cad9a53
Binary files /dev/null and b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Fonts/Allura-Regular.otf differ
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Fonts/OpenSans-Regular.ttf b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Fonts/OpenSans-Regular.ttf
new file mode 100644
index 0000000..c9237de
Binary files /dev/null and b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Fonts/OpenSans-Regular.ttf differ
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Fonts/OpenSans-Semibold.ttf b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Fonts/OpenSans-Semibold.ttf
new file mode 100644
index 0000000..0da9f99
Binary files /dev/null and b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Fonts/OpenSans-Semibold.ttf differ
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Images/dotnet_bot.svg b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Images/dotnet_bot.svg
new file mode 100644
index 0000000..abfaff2
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Images/dotnet_bot.svg
@@ -0,0 +1,93 @@
+
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Raw/AboutAssets.txt b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Raw/AboutAssets.txt
new file mode 100644
index 0000000..3f7a940
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Raw/AboutAssets.txt
@@ -0,0 +1,14 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories) and given a Build Action of "MauiAsset":
+
+
+
+These files will be deployed with you package and will be accessible using Essentials:
+
+ async Task LoadMauiAsset()
+ {
+ using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
+ using var reader = new StreamReader(stream);
+
+ var contents = reader.ReadToEnd();
+ }
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Styles.xaml b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Styles.xaml
new file mode 100644
index 0000000..0eb4369
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/Styles.xaml
@@ -0,0 +1,456 @@
+
+
+
+
+ #512BD4
+ #DFD8F7
+ #2B0B98
+ White
+ Black
+ #E5E5E1
+ #969696
+ #505050
+
+
+
+
+
+
+
+
+
+ #F7B548
+ #FFD590
+ #FFE5B9
+ #28C2D1
+ #7BDDEF
+ #C3F2F4
+ #3E8EED
+ #72ACF1
+ #A7CBF6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/appicon.svg b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/appicon.svg
new file mode 100644
index 0000000..9d63b65
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/appicon.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/appiconfg.svg b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/appiconfg.svg
new file mode 100644
index 0000000..21dfb25
--- /dev/null
+++ b/Maui/HtmlLabel.Forms.Plugin.Tests.App/Resources/appiconfg.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/Maui/HtmlLabel.sln b/Maui/HtmlLabel.sln
new file mode 100644
index 0000000..42a4a87
--- /dev/null
+++ b/Maui/HtmlLabel.sln
@@ -0,0 +1,33 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32422.2
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HyperTextLabel.Maui", "HtmlLabel\HyperTextLabel.Maui.csproj", "{49120CED-9682-4AAF-A31C-74D1164A009C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HyperTextLabel.Maui.Tests.App", "HtmlLabel.Forms.Plugin.Tests.App\HyperTextLabel.Maui.Tests.App.csproj", "{20CB997D-35C9-48D9-95C8-006D04DDF026}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {49120CED-9682-4AAF-A31C-74D1164A009C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {49120CED-9682-4AAF-A31C-74D1164A009C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {49120CED-9682-4AAF-A31C-74D1164A009C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {49120CED-9682-4AAF-A31C-74D1164A009C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {20CB997D-35C9-48D9-95C8-006D04DDF026}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {20CB997D-35C9-48D9-95C8-006D04DDF026}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {20CB997D-35C9-48D9-95C8-006D04DDF026}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {20CB997D-35C9-48D9-95C8-006D04DDF026}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {20CB997D-35C9-48D9-95C8-006D04DDF026}.Release|Any CPU.Build.0 = Release|Any CPU
+ {20CB997D-35C9-48D9-95C8-006D04DDF026}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {74AEE0DB-B50C-451B-A0C5-3D939F8A5147}
+ EndGlobalSection
+EndGlobal
diff --git a/Maui/HtmlLabel/Controls/HtmlLabel.cs b/Maui/HtmlLabel/Controls/HtmlLabel.cs
new file mode 100644
index 0000000..da2c4fc
--- /dev/null
+++ b/Maui/HtmlLabel/Controls/HtmlLabel.cs
@@ -0,0 +1,106 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("HyperTextLabel.Maui.Shared.Tests")]
+namespace HyperTextLabel.Maui.Controls
+{
+ ///
+ ///
+ /// A label that is able to display HTML content
+ ///
+ public class HtmlLabel : Label, IHtmlLabel
+ {
+ ///
+ /// Identify the UnderlineText property.
+ ///
+ public static readonly BindableProperty UnderlineTextProperty =
+ BindableProperty.Create(nameof(UnderlineText), typeof(bool), typeof(HtmlLabel), true);
+
+ ///
+ public bool UnderlineText
+ {
+ get { return (bool)GetValue(UnderlineTextProperty); }
+ set { SetValue(UnderlineTextProperty, value); }
+ }
+
+ ///
+ /// Identify the LinkColor property.
+ ///
+ public static readonly BindableProperty LinkColorProperty =
+ BindableProperty.Create(nameof(LinkColor), typeof(Color), typeof(HtmlLabel), default);
+
+ ///
+ public Color LinkColor
+ {
+ get { return (Color)GetValue(LinkColorProperty); }
+ set { SetValue(LinkColorProperty, value); }
+ }
+
+ ///
+ /// Identify the BrowserLaunchOptions property.
+ ///
+ public static readonly BindableProperty BrowserLaunchOptionsProperty =
+ BindableProperty.Create(nameof(BrowserLaunchOptions), typeof(BrowserLaunchOptions), typeof(HtmlLabel), default);
+
+ ///
+ public BrowserLaunchOptions BrowserLaunchOptions
+ {
+ get { return (BrowserLaunchOptions)GetValue(BrowserLaunchOptionsProperty); }
+ set { SetValue(BrowserLaunchOptionsProperty, value); }
+ }
+
+ ///
+ /// Identify the AndroidLegacyMode property.
+ ///
+ public static readonly BindableProperty AndroidLegacyModeProperty =
+ BindableProperty.Create(nameof(AndroidLegacyMode), typeof(bool), typeof(HtmlLabel), default);
+
+ ///
+ public bool AndroidLegacyMode
+ {
+ get { return (bool)GetValue(AndroidLegacyModeProperty); }
+ set { SetValue(AndroidLegacyModeProperty, value); }
+ }
+
+ ///
+ /// Identify the AndroidListIndent property KWI-FIX.
+ /// Default value = 20 (to continue support `old value`)
+ ///
+ public static readonly BindableProperty AndroidListIndentProperty =
+ BindableProperty.Create(nameof(AndroidListIndent), typeof(int), typeof(HtmlLabel), defaultValue: 20);
+
+ ///
+ public int AndroidListIndent
+ {
+ get { return (int)GetValue(AndroidListIndentProperty); }
+ set { SetValue(AndroidListIndentProperty, value); }
+ }
+
+ ///
+ /// Fires before the open URL request is done.
+ ///
+ public event EventHandler Navigating;
+
+ ///
+ /// Fires when the open URL request is done.
+ ///
+ public event EventHandler Navigated;
+
+ ///
+ /// Send the Navigating event
+ ///
+ ///
+ void IHtmlLabelInternals.SendNavigating(WebNavigatingEventArgs args)
+ {
+ Navigating?.Invoke(this, args);
+ }
+
+ ///
+ /// Send the Navigated event
+ ///
+ ///
+ void IHtmlLabelInternals.SendNavigated(WebNavigatingEventArgs args)
+ {
+ Navigated?.Invoke(this, args);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Maui/HtmlLabel/Controls/IHtmlLabel.cs b/Maui/HtmlLabel/Controls/IHtmlLabel.cs
new file mode 100644
index 0000000..4032dd7
--- /dev/null
+++ b/Maui/HtmlLabel/Controls/IHtmlLabel.cs
@@ -0,0 +1,56 @@
+using Microsoft.Maui.Controls.Internals;
+
+namespace HyperTextLabel.Maui.Controls
+{
+ ///
+ /// Internal class requirments
+ ///
+ public interface IHtmlLabelInternals
+ {
+ void SendNavigating(WebNavigatingEventArgs args);
+
+ void SendNavigated(WebNavigatingEventArgs args);
+ }
+
+ ///
+ /// Properties that the HtmlLabel needs the base class to implement.
+ /// is currently a public interface but in an internal namespace and
+ /// EditorBrowsableState.Never so I'm not sure how that's going to play out. I don't want to reference the
+ /// Xamarin.Forms interface with MAUI custom control.
+ ///
+ public interface IHtmlLabelRequiredProperties : ILabel, IFontElement
+ {
+ IList GestureRecognizers { get; }
+ }
+
+ ///
+ /// Need to look into what should be public vs internal.
+ ///
+ public interface IHtmlLabel : IHtmlLabelInternals, IHtmlLabelRequiredProperties
+ {
+ ///
+ /// Get or set if hyperlinks are underlined.
+ ///
+ bool UnderlineText { get; }
+
+ ///
+ /// Get or set the color of hyperlinks.
+ ///
+ Color LinkColor { get; }
+
+ ///
+ /// Get or set the options to use when opening a web link.
+ ///
+ BrowserLaunchOptions BrowserLaunchOptions { get; }
+
+ ///
+ /// Get or set if the Android renderer separates block-level elements with blank lines.
+ ///
+ bool AndroidLegacyMode { get; }
+
+ ///
+ /// Get or set if the Android List Indent property KWI-FIX.
+ ///
+ int AndroidListIndent { get; }
+ }
+}
diff --git a/Maui/HtmlLabel/Extensions/MauiServiceExtensions.cs b/Maui/HtmlLabel/Extensions/MauiServiceExtensions.cs
new file mode 100644
index 0000000..cecf4cd
--- /dev/null
+++ b/Maui/HtmlLabel/Extensions/MauiServiceExtensions.cs
@@ -0,0 +1,32 @@
+using HyperTextLabel.Maui.Extensions;
+
+namespace HyperTextLabel.Maui.Extensions
+{
+ ///
+ /// These extension methods were pulled in from MAUI codebase as they are not public.
+ ///
+ internal static class MauiServiceExtensions
+ {
+ public static IServiceProvider GetServiceProvider(this IElementHandler handler)
+ {
+ var context = handler.MauiContext ??
+ throw new InvalidOperationException($"Unable to find the context. The {nameof(handler.MauiContext)} property should have been set by the host.");
+
+ var services = context?.Services ??
+ throw new InvalidOperationException($"Unable to find the service provider. The {nameof(handler.MauiContext)} property should have been set by the host.");
+
+ return services;
+ }
+
+ public static T GetRequiredService(this IElementHandler handler)
+ where T : notnull
+ {
+ var services = handler.GetServiceProvider();
+
+ var service = services.GetRequiredService();
+
+ return service;
+ }
+
+ }
+}
diff --git a/Maui/HtmlLabel/Extensions/StringExtensions.cs b/Maui/HtmlLabel/Extensions/StringExtensions.cs
new file mode 100644
index 0000000..74db7e1
--- /dev/null
+++ b/Maui/HtmlLabel/Extensions/StringExtensions.cs
@@ -0,0 +1,10 @@
+using System.Text.RegularExpressions;
+
+namespace HyperTextLabel.Maui.Extensions
+{
+ internal static class StringExtensions
+ {
+ public static string ReplaceTag(this string html, string oldTagRegex, string newTag) =>
+ Regex.Replace(html, @"(<\s*\/?\s*)" + oldTagRegex + @"((\s+[\w\-\,\.\(\)\=""\:\;]*)*>)", "$1" + newTag + "$2");
+ }
+}
diff --git a/Maui/HtmlLabel/Extensions/UriExtensions.cs b/Maui/HtmlLabel/Extensions/UriExtensions.cs
new file mode 100644
index 0000000..7dad788
--- /dev/null
+++ b/Maui/HtmlLabel/Extensions/UriExtensions.cs
@@ -0,0 +1,125 @@
+using HyperTextLabel.Maui.Utilities;
+using System.Globalization;
+
+namespace HyperTextLabel.Maui.Extensions
+{
+ public static class UriExtensions
+ {
+ public static bool IsHttp(this Uri uri) => uri != null && uri.Scheme.ToUpperInvariant().Contains("HTTP");
+ public static bool IsEmail(this Uri uri) => uri.MatchSchema("mailto");
+ public static bool IsTel(this Uri uri) => uri.MatchSchema("tel");
+ public static bool IsSms(this Uri uri) => uri.MatchSchema("sms");
+ public static bool IsGeo(this Uri uri) => uri.MatchSchema("geo");
+
+ public static void LaunchBrowser(this Uri uri, BrowserLaunchOptions options)
+ {
+ if (options == null)
+ {
+ Browser.OpenAsync(uri);
+ }
+ else
+ {
+ Browser.OpenAsync(uri, options);
+ }
+ }
+
+ public static bool LaunchEmail(this Uri uri)
+ {
+ if (uri == null)
+ return false;
+
+ var qParams = uri.ParseQueryString();
+ var to = uri.Target();
+ try
+ {
+ var message = new EmailMessage
+ {
+ To = new List { to },
+ Subject = qParams.GetFirst("subject") ?? string.Empty,
+ Body = qParams.GetFirst("body") ?? string.Empty,
+ Cc = qParams.Get("cc") ?? new List(),
+ Bcc = qParams.Get("bcc") ?? new List()
+ };
+ Email.ComposeAsync(message);
+ return true;
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ System.Diagnostics.Debug.WriteLine(@" ERROR: ", ex.Message);
+ return false;
+ }
+
+ }
+
+ public static bool LaunchTel(this Uri uri)
+ {
+ if (uri == null)
+ return false;
+
+ var to = uri.Target();
+ try
+ {
+ PhoneDialer.Open(to);
+ return true;
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ System.Diagnostics.Debug.WriteLine(@" ERROR: ", ex.Message);
+ return false;
+ }
+ }
+
+ public static bool LaunchSms(this Uri uri)
+ {
+ if (uri == null)
+ return false;
+
+ var qParams = uri.ParseQueryString();
+ var to = uri.Target();
+ try
+ {
+ var messageText = qParams.GetFirst("body");
+ var message = new SmsMessage(messageText, new[] { to });
+ Sms.ComposeAsync(message);
+ return true;
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ System.Diagnostics.Debug.WriteLine(@" ERROR: ", ex.Message);
+ return false;
+ }
+ }
+
+ public static bool LaunchMaps(this Uri uri)
+ {
+ if (uri == null)
+ return false;
+
+ var target = uri.Target();
+ try
+ {
+ var coordinates = target.Split(',');
+ var latitude = double.Parse(coordinates[0], CultureInfo.InvariantCulture.NumberFormat);
+ var longitude = double.Parse(coordinates[1].Split(';')[0], CultureInfo.InvariantCulture.NumberFormat);
+ var location = new Location(latitude, longitude);
+ Map.OpenAsync(location);
+ return true;
+ }
+ catch (FeatureNotSupportedException ex)
+ {
+ System.Diagnostics.Debug.WriteLine(@" ERROR: ", ex.Message);
+ return false;
+ }
+ }
+
+ private static string Target(this Uri uri)
+ {
+ return Uri.UnescapeDataString(uri.AbsoluteUri.Substring(uri.Scheme.Length + 1).Split('?')[0].Replace("/", ""));
+ }
+
+ private static bool MatchSchema(this Uri uri, string schema)
+ {
+ return uri != null && uri.Scheme.Equals(schema, StringComparison.InvariantCultureIgnoreCase);
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/Handlers/HtmlLabelHandler.cs b/Maui/HtmlLabel/Handlers/HtmlLabelHandler.cs
new file mode 100644
index 0000000..ce0d27c
--- /dev/null
+++ b/Maui/HtmlLabel/Handlers/HtmlLabelHandler.cs
@@ -0,0 +1,32 @@
+using HyperTextLabel.Maui.Controls;
+using Microsoft.Maui.Handlers;
+
+namespace HyperTextLabel.Maui.Handlers
+{
+ public partial class HtmlLabelHandler
+ {
+ public static IPropertyMapper Mapper =
+ new PropertyMapper(LabelHandler.Mapper)
+ {
+ [nameof(ILabel.Text)] = MapLabelText,
+ [nameof(Label.FormattedText)] = MapLabelText,
+ [nameof(Label.TextTransform)] = MapLabelText,
+ [nameof(Label.TextType)] = MapLabelText,
+ [nameof(IHtmlLabel.UnderlineText)] = MapUnderlineText,
+ [nameof(IHtmlLabel.LinkColor)] = MapLinkColor,
+ [nameof(IHtmlLabel.BrowserLaunchOptions)] = MapBrowserLaunchOptions,
+ [nameof(IHtmlLabel.AndroidLegacyMode)] = MapAndroidLegacyMode,
+ [nameof(IHtmlLabel.AndroidListIndent)] = MapAndroidListIndent,
+ };
+
+ public HtmlLabelHandler() : this(null)
+ {
+
+ }
+
+ public HtmlLabelHandler(IPropertyMapper mapper = null) : base(mapper ?? Mapper)
+ {
+
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/Handlers/HtmlLableMapper.Standard.cs b/Maui/HtmlLabel/Handlers/HtmlLableMapper.Standard.cs
new file mode 100644
index 0000000..ddb9b91
--- /dev/null
+++ b/Maui/HtmlLabel/Handlers/HtmlLableMapper.Standard.cs
@@ -0,0 +1,22 @@
+using HyperTextLabel.Maui.Controls;
+using Microsoft.Maui.Handlers;
+
+namespace HyperTextLabel.Maui.Handlers
+{
+ public partial class HtmlLabelHandler : ViewHandler
+ {
+ protected override object CreatePlatformView() => throw new NotImplementedException();
+
+ public static void MapLabelText(HtmlLabelHandler handler, IHtmlLabel label) { }
+
+ public static void MapUnderlineText(HtmlLabelHandler handler, IHtmlLabel label) { }
+
+ public static void MapLinkColor(HtmlLabelHandler handler, IHtmlLabel label) { }
+
+ public static void MapBrowserLaunchOptions(HtmlLabelHandler handler, IHtmlLabel label) { }
+
+ public static void MapAndroidLegacyMode(HtmlLabelHandler handler, IHtmlLabel label) { }
+
+ public static void MapAndroidListIndent(HtmlLabelHandler handler, IHtmlLabel label) { }
+ }
+}
diff --git a/Maui/HtmlLabel/Hosting/AppHostBuilderExtensions.cs b/Maui/HtmlLabel/Hosting/AppHostBuilderExtensions.cs
new file mode 100644
index 0000000..d20e8d9
--- /dev/null
+++ b/Maui/HtmlLabel/Hosting/AppHostBuilderExtensions.cs
@@ -0,0 +1,36 @@
+using HyperTextLabel.Maui.Controls;
+using HyperTextLabel.Maui.Handlers;
+using Microsoft.Maui.Controls.Compatibility.Hosting;
+
+namespace HyperTextLabel.Maui.Hosting
+{
+ public static class AppHostBuilderExtensions
+ {
+ public static MauiAppBuilder ConfigureHyperTextLabel(this MauiAppBuilder builder, bool useCompatibilityRenderers = false)
+ {
+ return useCompatibilityRenderers ?
+ builder.UseMauiCompatibility()
+ .ConfigureMauiHandlers(handlers => handlers.AddLibraryCompatibilityRenderers()) :
+ builder.ConfigureMauiHandlers(handlers => handlers.AddLibraryHandlers());
+ }
+
+ private static IMauiHandlersCollection AddLibraryCompatibilityRenderers(this IMauiHandlersCollection handlers)
+ {
+#if __ANDROID__
+ //handlers.AddCompatibilityRenderer(typeof(HtmlLabel), typeof(Droid.HtmlLabelRenderer));
+#elif __IOS__
+ //handlers.AddCompatibilityRenderer(typeof(HtmlLabel), typeof(iOS.HtmlLabelRenderer));
+#elif WINDOWS10_0_17763_0_OR_GREATER
+ //handlers.AddCompatibilityRenderer(typeof(HtmlLabel), typeof(UWP.HtmlLabelRenderer));
+#endif
+ return handlers;
+ }
+
+ private static IMauiHandlersCollection AddLibraryHandlers(this IMauiHandlersCollection handlers)
+ {
+ handlers.AddHandler();
+
+ return handlers;
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/HyperTextLabel.Maui.csproj b/Maui/HtmlLabel/HyperTextLabel.Maui.csproj
new file mode 100644
index 0000000..a42d29c
--- /dev/null
+++ b/Maui/HtmlLabel/HyperTextLabel.Maui.csproj
@@ -0,0 +1,109 @@
+
+
+
+ net6.0;net6.0-android;net6.0-ios
+ $(TargetFrameworks);net6.0-windows10.0.19041.0
+
+
+ true
+ true
+ enable
+
+ 14.2
+ 14.0
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+ HyperTextLabel.Maui
+ HyperTextLabel.Maui
+ Maui.Plugin.HtmlLabel
+ HtmlLabel.Maui.Plugin
+ icon.png
+ Maui.Plugin.HtmlLabel: display HTML content in labels
+ MAUI, windows, uwp, ios, android, Maui.HtmlLabel, html
+ Maui.Plugin.HtmlLabel
+ Maui.Plugin.HtmlLabel: display HTML content in labels
+ $(AssemblyName) ($(TargetFramework))
+ $(Version)$(VersionSuffix)
+ Matteo Bortolazzo
+ Matteo Bortolazzo
+ en
+ © Matteo Bortolazzo. All rights reserved.
+ https://github.com/matteobortolazzo/HtmlLabelPlugin
+ $(DefineConstants);
+ false
+ LICENSE
+ true
+ https://github.com/matteobortolazzo/HtmlLabelPlugin
+ portable
+ Debug;Release
+ Latest
+ false
+
+
+
+ true
+
+
+
+
+ true
+
+ true
+
+ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2.0.8
+
+
+
+
diff --git a/Maui/HtmlLabel/Platforms/Android/HtmlLabelExtensions.cs b/Maui/HtmlLabel/Platforms/Android/HtmlLabelExtensions.cs
new file mode 100644
index 0000000..35bfe9e
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/Android/HtmlLabelExtensions.cs
@@ -0,0 +1,153 @@
+using System.ComponentModel;
+using Android.OS;
+using Android.Text;
+using Android.Text.Method;
+using Android.Text.Style;
+using Java.Lang;
+using HyperTextLabel.Maui.Platform.Droid;
+using AndroidX.AppCompat.Widget;
+using Microsoft.Maui.Platform;
+using HyperTextLabel.Maui.Controls;
+using HyperTextLabel.Maui.Utilities;
+using HyperTextLabel.Maui.Extensions;
+
+namespace HyperTextLabel.Maui.Platforms.Droid
+{
+ internal static class HtmlLabelExtensions
+ {
+ private const string _tagUlRegex = "[uU][lL]";
+ private const string _tagOlRegex = "[oO][lL]";
+ private const string _tagLiRegex = "[lL][iI]";
+
+ public static void UpdateText(this AppCompatTextView view, IHtmlLabel label)
+ {
+ Color linkColor = label.LinkColor;
+ if (!linkColor.IsDefault())
+ {
+ view.SetLinkTextColor(linkColor.ToPlatform());
+ }
+
+ view.SetIncludeFontPadding(false);
+ var isRtl = AppInfo.RequestedLayoutDirection == LayoutDirection.RightToLeft;
+ var styledHtml = new RendererHelper(label, label.Text, DevicePlatform.Android, isRtl).ToString();
+ /*
+ * Android's TextView doesn't support lists.
+ * List tags must be replaces with custom tags,
+ * that it will be renderer by a custom tag handler.
+ */
+ styledHtml = styledHtml
+ ?.ReplaceTag(_tagUlRegex, ListTagHandler.TagUl)
+ ?.ReplaceTag(_tagOlRegex, ListTagHandler.TagOl)
+ ?.ReplaceTag(_tagLiRegex, ListTagHandler.TagLi);
+
+ if (styledHtml != null)
+ {
+ SetText(view, label, styledHtml);
+ }
+ }
+
+ public static void UpdateUnderlineText(this AppCompatTextView view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateLinkColor(this AppCompatTextView view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateBrowserLaunchOptions(this AppCompatTextView view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateAndroidLegacyMode(this AppCompatTextView view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateAndroidListIndent(this AppCompatTextView view, IHtmlLabel label)
+ {
+ }
+
+ private static void SetText(AppCompatTextView control, IHtmlLabel htmlLabel, string html)
+ {
+ // Set the type of content and the custom tag list handler
+ using var listTagHandler = new ListTagHandler(htmlLabel.AndroidListIndent); // KWI-FIX: added AndroidListIndent parameter
+ var imageGetter = new UrlImageParser(control);
+ FromHtmlOptions fromHtmlOptions = htmlLabel.AndroidLegacyMode ? FromHtmlOptions.ModeLegacy : FromHtmlOptions.ModeCompact;
+ ISpanned sequence = Build.VERSION.SdkInt >= BuildVersionCodes.N ?
+ Html.FromHtml(html, fromHtmlOptions, imageGetter, listTagHandler) :
+ Html.FromHtml(html, imageGetter, listTagHandler);
+ using var strBuilder = new SpannableStringBuilder(sequence);
+
+ // Make clickable links
+ if (!htmlLabel.GestureRecognizers.Any())
+ {
+ control.MovementMethod = LinkMovementMethod.Instance;
+ URLSpan[] urls = strBuilder
+ .GetSpans(0, sequence.Length(), Class.FromType(typeof(URLSpan)))
+ .Cast()
+ .ToArray();
+ foreach (URLSpan span in urls)
+ {
+ MakeLinkClickable(strBuilder, span, htmlLabel);
+ }
+ }
+
+ // Android adds an unnecessary "\n" that must be removed
+ using ISpanned value = RemoveTrailingNewLines(strBuilder);
+
+ // Finally sets the value of the TextView
+ control.SetText(value, global::Android.Widget.TextView.BufferType.Spannable);
+ }
+
+ private static ISpanned RemoveTrailingNewLines(ICharSequence text)
+ {
+ var builder = new SpannableStringBuilder(text);
+
+ var count = 0;
+ for (int i = 1; i <= text.Length(); i++)
+ {
+ if (!'\n'.Equals(text.CharAt(text.Length() - i)))
+ break;
+
+ count++;
+ }
+
+ if (count > 0)
+ _ = builder.Delete(text.Length() - count, text.Length());
+
+ return builder;
+ }
+
+ private static void MakeLinkClickable(ISpannable strBuilder, URLSpan span, IHtmlLabel htmlLabel)
+ {
+ var start = strBuilder.GetSpanStart(span);
+ var end = strBuilder.GetSpanEnd(span);
+ SpanTypes flags = strBuilder.GetSpanFlags(span);
+ var clickable = new HtmlLabelClickableSpan(htmlLabel, span);
+ strBuilder.SetSpan(clickable, start, end, flags);
+ strBuilder.RemoveSpan(span);
+ }
+
+ private class HtmlLabelClickableSpan : ClickableSpan
+ {
+ private readonly IHtmlLabel _label;
+ private readonly URLSpan _span;
+
+ public HtmlLabelClickableSpan(IHtmlLabel label, URLSpan span)
+ {
+ _label = label;
+ _span = span;
+ }
+
+ public override void UpdateDrawState(TextPaint ds)
+ {
+ base.UpdateDrawState(ds);
+ ds.UnderlineText = _label.UnderlineText;
+ }
+
+ public override void OnClick(global::Android.Views.View widget)
+ {
+ RendererHelper.HandleUriClick(_label, _span.URL);
+ }
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/Platforms/Android/HtmlLabelHandler.cs b/Maui/HtmlLabel/Platforms/Android/HtmlLabelHandler.cs
new file mode 100644
index 0000000..c93a95e
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/Android/HtmlLabelHandler.cs
@@ -0,0 +1,50 @@
+using HyperTextLabel.Maui.Platforms.Droid;
+using HyperTextLabel.Maui.Controls;
+
+using PlatformView = AndroidX.AppCompat.Widget.AppCompatTextView;
+
+namespace HyperTextLabel.Maui.Handlers
+{
+ public partial class HtmlLabelHandler : Microsoft.Maui.Handlers.LabelHandler
+ {
+ protected override void ConnectHandler(PlatformView platformView)
+ {
+ base.ConnectHandler(platformView);
+ }
+
+ protected override void DisconnectHandler(PlatformView platformView)
+ {
+ base.DisconnectHandler(platformView);
+ }
+
+ public static void MapLabelText(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateText(label);
+ }
+
+ public static void MapUnderlineText(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateUnderlineText(label);
+ }
+
+ public static void MapLinkColor(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateLinkColor(label);
+ }
+
+ public static void MapBrowserLaunchOptions(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateBrowserLaunchOptions(label);
+ }
+
+ public static void MapAndroidLegacyMode(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateAndroidLegacyMode(label);
+ }
+
+ public static void MapAndroidListIndent(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateAndroidListIndent(label);
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/Platforms/Android/ListBuilder.cs b/Maui/HtmlLabel/Platforms/Android/ListBuilder.cs
new file mode 100644
index 0000000..3f90971
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/Android/ListBuilder.cs
@@ -0,0 +1,142 @@
+using Android.Text;
+using Android.Text.Style;
+using Android.Widget;
+using Java.Lang;
+
+namespace HyperTextLabel.Maui.Platform.Droid
+{
+ internal class ListBuilder
+ {
+ private int _listIndent = 20; // KWI-FIX : changed from constant to prop
+
+
+ private readonly int _gap = 0;
+ private readonly LiGap _liGap;
+ private readonly ListBuilder _parent = null;
+
+ private int _liIndex = -1;
+ private int _liStart = -1;
+
+ public ListBuilder(int listIndent) // KWI-FIX: added listIndent
+ {
+ _listIndent = listIndent;
+ _parent = null;
+ _gap = 0;
+ _liGap = GetLiGap(null);
+ }
+
+ private ListBuilder(ListBuilder parent, bool ordered, int listIndent) // KWI-FIX: added listIndent
+ {
+ _listIndent = listIndent;
+ _parent = parent;
+ _liGap = parent._liGap;
+ _gap = parent._gap + _listIndent + _liGap.GetGap(ordered);
+ _liIndex = ordered ? 0 : -1;
+ }
+
+ public ListBuilder StartList(bool ordered, IEditable output)
+ {
+ if (_parent == null && output.Length() > 0)
+ {
+ _ = output.Append("\n ");
+ }
+ return new ListBuilder(this, ordered, _listIndent); // KWI-FIX: pass thru listIndent
+ }
+
+ public void AddListItem(bool isOpening, IEditable output)
+ {
+ if (isOpening)
+ {
+ EnsureParagraphBoundary(output);
+ _liStart = output.Length();
+
+ var lineStart = IsOrdered()
+ ? ++_liIndex + ". "
+ : "• ";
+ _ = output.Append(lineStart);
+ }
+ else
+ {
+ if (_liStart >= 0)
+ {
+ EnsureParagraphBoundary(output);
+ using var leadingMarginSpan = new LeadingMarginSpanStandard(_gap - _liGap.GetGap(IsOrdered()), _gap);
+ output.SetSpan(leadingMarginSpan, _liStart, output.Length(), SpanTypes.ExclusiveExclusive);
+ _liStart = -1;
+ }
+ }
+ }
+
+ public ListBuilder CloseList(IEditable output)
+ {
+ EnsureParagraphBoundary(output);
+ ListBuilder result = _parent;
+ if (result == null)
+ {
+ result = this;
+ }
+
+ if (result._parent == null)
+ {
+ _ = output.Append('\n');
+ }
+
+ return result;
+ }
+
+ private bool IsOrdered()
+ {
+ return _liIndex >= 0;
+ }
+
+ private static void EnsureParagraphBoundary(IEditable output)
+ {
+ if (output.Length() == 0)
+ {
+ return;
+ }
+
+ var lastChar = output.CharAt(output.Length() - 1);
+ if (lastChar != '\n')
+ {
+ _ = output.Append('\n');
+ }
+ }
+
+ private class LiGap
+ {
+ private readonly int _orderedGap;
+ private readonly int _unorderedGap;
+
+ internal LiGap(int orderedGap, int unorderedGap)
+ {
+ _orderedGap = orderedGap;
+ _unorderedGap = unorderedGap;
+ }
+
+ public int GetGap(bool ordered)
+ {
+ return ordered ? _orderedGap : _unorderedGap;
+ }
+ }
+
+ private static LiGap GetLiGap(TextView tv)
+ {
+ var orderedGap = tv == null ? 40 : ComputeWidth(tv, true);
+ var unorderedGap = tv == null ? 30 : ComputeWidth(tv, false);
+
+ return new LiGap(orderedGap, unorderedGap);
+ }
+
+ private static int ComputeWidth(TextView tv, bool isOrdered)
+ {
+ Android.Graphics.Paint paint = tv.Paint;
+ using var bounds = new Android.Graphics.Rect();
+ var startString = isOrdered ? "99. " : "• ";
+ paint.GetTextBounds(startString, 0, startString.Length, bounds);
+ var width = bounds.Width();
+ var pt = Android.Util.TypedValue.ApplyDimension(Android.Util.ComplexUnitType.Pt, width, tv.Context.Resources.DisplayMetrics);
+ return (int)pt;
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/Platforms/Android/ListTagHandler.cs b/Maui/HtmlLabel/Platforms/Android/ListTagHandler.cs
new file mode 100644
index 0000000..97a3f04
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/Android/ListTagHandler.cs
@@ -0,0 +1,46 @@
+using Android.Text;
+using Org.Xml.Sax;
+
+namespace HyperTextLabel.Maui.Platform.Droid
+{
+ ///
+ /// Tag handler to support HTML lists.
+ ///
+ internal class ListTagHandler : Java.Lang.Object, Html.ITagHandler
+ {
+ public const string TagUl = "ULC";
+ public const string TagOl = "OLC";
+ public const string TagLi = "LIC";
+
+ private ListBuilder _listBuilder; // KWI-FIX: removed new, set in constructor
+ public ListTagHandler(int listIndent) // KWI-FIX: added constructor with listIndent property
+ {
+ _listBuilder = new ListBuilder(listIndent);
+ }
+
+ public void HandleTag(bool isOpening, string tag, IEditable output, IXMLReader xmlReader)
+ {
+ tag = tag.ToUpperInvariant();
+ var isItem = tag == TagLi;
+
+ // Is list item
+ if (isItem)
+ {
+ _listBuilder.AddListItem(isOpening, output);
+ }
+ // Is list
+ else
+ {
+ if (isOpening)
+ {
+ var isOrdered = tag == TagOl;
+ _listBuilder = _listBuilder.StartList(isOrdered, output);
+ }
+ else
+ {
+ _listBuilder = _listBuilder.CloseList(output);
+ }
+ }
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/Platforms/Android/URLImageParser.cs b/Maui/HtmlLabel/Platforms/Android/URLImageParser.cs
new file mode 100644
index 0000000..e64e944
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/Android/URLImageParser.cs
@@ -0,0 +1,107 @@
+using Android.Graphics;
+using Android.Graphics.Drawables;
+using Android.OS;
+using Android.Text;
+using Android.Widget;
+using Java.Net;
+
+namespace HyperTextLabel.Maui.Platform.Droid
+{
+ internal class UrlDrawable : BitmapDrawable
+ {
+ public Drawable Drawable { get; set; }
+
+ public override void Draw(Canvas canvas)
+ {
+ if (Drawable != null)
+ {
+ Drawable.Draw(canvas); ;
+ }
+ }
+ }
+
+ internal class ImageGetterAsyncTask : AsyncTask
+ {
+ private readonly UrlDrawable _urlDrawable;
+ private readonly TextView _container;
+
+ public ImageGetterAsyncTask(UrlDrawable urlDrawable, TextView container)
+ {
+ _urlDrawable = urlDrawable;
+ _container = container;
+ }
+
+ protected override Drawable RunInBackground(params string[] @params)
+ {
+ var source = @params[0];
+ return FetchDrawable(source);
+ }
+
+ protected override void OnPostExecute(Drawable result)
+ {
+ if (result == null)
+ {
+ return;
+ }
+
+ // Set the correct bound according to the result from HTTP call
+ _urlDrawable.SetBounds(0, 0, 0 + result.IntrinsicWidth, 0 + result.IntrinsicHeight);
+
+ // Change the reference of the current drawable to the result from the HTTP call
+ _urlDrawable.Drawable = result;
+
+ // Redraw the image by invalidating the container
+ _container.Invalidate();
+
+ // For ICS
+ _container.SetHeight(_container.Height + result.IntrinsicHeight);
+
+ // Pre ICS
+ _container.Ellipsize = null;
+ }
+
+ private Drawable FetchDrawable(string urlString)
+ {
+ try
+ {
+ Stream stream = Fetch(urlString);
+ var drawable = Drawable.CreateFromStream(stream, "src");
+ drawable.SetBounds(0, 0, 0 + drawable.IntrinsicWidth, 0 + drawable.IntrinsicHeight);
+ return drawable;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine(@" ERROR: ", ex.Message);
+ return null;
+ }
+ }
+
+ private static Stream Fetch(string urlString)
+ {
+ var url = new URL(urlString);
+ var urlConnection = (HttpURLConnection)url.OpenConnection();
+ Stream stream = urlConnection.InputStream;
+ return stream;
+ }
+ }
+
+ internal class UrlImageParser : Java.Lang.Object, Html.IImageGetter
+ {
+ private readonly TextView _container;
+
+ public UrlImageParser(TextView container)
+ {
+ _container = container;
+ }
+
+ public Drawable GetDrawable(string source)
+ {
+ var urlDrawable = new UrlDrawable();
+
+ var asyncTask = new ImageGetterAsyncTask(urlDrawable, _container);
+ _ = asyncTask.Execute(source);
+
+ return urlDrawable;
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/Platforms/Windows/Behavior.cs b/Maui/HtmlLabel/Platforms/Windows/Behavior.cs
new file mode 100644
index 0000000..efdfe56
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/Windows/Behavior.cs
@@ -0,0 +1,24 @@
+using Microsoft.UI.Xaml;
+using Microsoft.Xaml.Interactivity;
+
+namespace HyperTextLabel.Maui.Platforms.Windows
+{
+ internal abstract class Behavior : DependencyObject, IBehavior
+ {
+ public void Attach(DependencyObject associatedObject)
+ {
+ AssociatedObject = associatedObject;
+ OnAttached();
+ }
+
+ public void Detach() => OnDetaching();
+
+ protected virtual void OnAttached() { }
+
+ protected virtual void OnDetaching() { }
+
+ protected DependencyObject AssociatedObject { get; set; }
+
+ DependencyObject IBehavior.AssociatedObject => AssociatedObject;
+ }
+}
\ No newline at end of file
diff --git a/Maui/HtmlLabel/Platforms/Windows/GenericBehavior.cs b/Maui/HtmlLabel/Platforms/Windows/GenericBehavior.cs
new file mode 100644
index 0000000..5c09c00
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/Windows/GenericBehavior.cs
@@ -0,0 +1,18 @@
+using Microsoft.UI.Xaml;
+
+namespace HyperTextLabel.Maui.Platforms.Windows
+{
+ internal abstract class Behavior : Behavior where T : DependencyObject
+ {
+ protected new T AssociatedObject => base.AssociatedObject as T;
+
+ protected override void OnAttached()
+ {
+ base.OnAttached();
+ if (AssociatedObject == null)
+ {
+ throw new InvalidOperationException("AssociatedObject is not of the right type");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Maui/HtmlLabel/Platforms/Windows/HtmlLabelExtensions.cs b/Maui/HtmlLabel/Platforms/Windows/HtmlLabelExtensions.cs
new file mode 100644
index 0000000..203259e
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/Windows/HtmlLabelExtensions.cs
@@ -0,0 +1,55 @@
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.Xaml.Interactivity;
+using HyperTextLabel.Maui.Controls;
+using HyperTextLabel.Maui.Utilities;
+
+namespace HyperTextLabel.Maui.Platforms.Windows
+{
+ internal static class HtmlLabelExtensions
+ {
+ public static void UpdateText(this TextBlock view, IHtmlLabel label)
+ {
+ ProcessText( view, label);
+ }
+
+ public static void UpdateUnderlineText(this TextBlock view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateLinkColor(this TextBlock view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateBrowserLaunchOptions(this TextBlock view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateAndroidLegacyMode(this TextBlock view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateAndroidListIndent(this TextBlock view, IHtmlLabel label)
+ {
+ }
+
+ private static void ProcessText(TextBlock view, IHtmlLabel label)
+ {
+ // Gets the complete HTML string
+ var isRtl = AppInfo.RequestedLayoutDirection == LayoutDirection.RightToLeft;
+ var styledHtml = new RendererHelper(label, label.Text, DevicePlatform.WinUI, isRtl).ToString();
+ if (styledHtml == null)
+ {
+ return;
+ }
+
+ view.Text = styledHtml;
+
+ // Adds the HtmlTextBehavior because UWP's TextBlock
+ // does not natively support HTML content
+ var behavior = new HtmlTextBehavior() { HtmlLabel = label };
+ BehaviorCollection behaviors = Interaction.GetBehaviors(view);
+ behaviors.Clear();
+ behaviors.Add(behavior);
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/Platforms/Windows/HtmlLabelHandler.cs b/Maui/HtmlLabel/Platforms/Windows/HtmlLabelHandler.cs
new file mode 100644
index 0000000..59f8798
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/Windows/HtmlLabelHandler.cs
@@ -0,0 +1,50 @@
+using HyperTextLabel.Maui.Controls;
+using HyperTextLabel.Maui.Platforms.Windows;
+
+using PlatformView = Microsoft.UI.Xaml.Controls.TextBlock;
+
+namespace HyperTextLabel.Maui.Handlers
+{
+ public partial class HtmlLabelHandler : Microsoft.Maui.Handlers.LabelHandler
+ {
+ protected override void ConnectHandler(PlatformView platformView)
+ {
+ base.ConnectHandler(platformView);
+ }
+
+ protected override void DisconnectHandler(PlatformView platformView)
+ {
+ base.DisconnectHandler(platformView);
+ }
+
+ public static void MapLabelText(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateText(label);
+ }
+
+ public static void MapUnderlineText(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateUnderlineText(label);
+ }
+
+ public static void MapLinkColor(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateLinkColor(label);
+ }
+
+ public static void MapBrowserLaunchOptions(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateBrowserLaunchOptions(label);
+ }
+
+ public static void MapAndroidLegacyMode(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateAndroidLegacyMode(label);
+ }
+
+ public static void MapAndroidListIndent(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateAndroidListIndent(label);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Maui/HtmlLabel/Platforms/Windows/HtmlTextBehavior.cs b/Maui/HtmlLabel/Platforms/Windows/HtmlTextBehavior.cs
new file mode 100644
index 0000000..f6ba3ce
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/Windows/HtmlTextBehavior.cs
@@ -0,0 +1,226 @@
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+using Microsoft.Maui.Controls.Platform;
+using Microsoft.UI.Xaml.Documents;
+using Microsoft.UI.Xaml;
+using Microsoft.Maui.Platform;
+using HyperTextLabel.Maui.Controls;
+using HyperTextLabel.Maui.Utilities;
+
+using PlatformView = Microsoft.UI.Xaml.Controls.TextBlock;
+using Span = Microsoft.UI.Xaml.Documents.Span;
+
+namespace HyperTextLabel.Maui.Platforms.Windows
+{
+ internal class HtmlTextBehavior : Behavior
+ {
+ // All the supported tags
+ internal const string _elementA = "A";
+ 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 IHtmlLabel HtmlLabel { get; set; }
+
+ protected override void OnAttached()
+ {
+ base.OnAttached();
+
+ AssociatedObject.Loaded += OnAssociatedObjectLoaded;
+ AssociatedObject.LayoutUpdated += OnAssociatedObjectLayoutUpdated;
+ }
+
+ protected override void OnDetaching()
+ {
+ base.OnDetaching();
+
+ AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
+ AssociatedObject.LayoutUpdated -= OnAssociatedObjectLayoutUpdated;
+ }
+
+ private void OnAssociatedObjectLayoutUpdated(object sender, object o) => UpdateText();
+
+ private void OnAssociatedObjectLoaded(object sender, RoutedEventArgs e) => UpdateText();
+
+ private void UpdateText()
+ {
+ if (AssociatedObject == null)
+ {
+ return;
+ }
+
+ if (string.IsNullOrEmpty(AssociatedObject.Text))
+ {
+ return;
+ }
+
+ AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
+ AssociatedObject.LayoutUpdated -= OnAssociatedObjectLayoutUpdated;
+
+ var text = AssociatedObject.Text;
+
+ // Just incase we are not given text with elements.
+ var modifiedText = $"{text}
";
+
+ var linkRegex = new Regex(@" 0)
+ {
+ foreach (Match match in matches)
+ {
+ for (var i = 1; i < match.Groups.Count; i++)
+ {
+ Group group = match.Groups[i];
+ var escapedUri = Uri.EscapeDataString(group.Value);
+ modifiedText = modifiedText.Replace(group.Value, escapedUri, StringComparison.InvariantCulture);
+ }
+ }
+ System.Diagnostics.Debug.WriteLine(@$"ERROR: ${matches}");
+ }
+
+ modifiedText = Regex.Replace(modifiedText, "
", "
", RegexOptions.IgnoreCase)
+ .Replace("\n", String.Empty, StringComparison.OrdinalIgnoreCase) // KWI-FIX Enters resulted in multiple lines
+ .Replace(" ", " ", StringComparison.OrdinalIgnoreCase); // KWI-FIX is not supported by the UWP TextBlock
+
+ // reset the text because we will add to it.
+ AssociatedObject.Inlines.Clear();
+
+ var element = XElement.Parse(modifiedText);
+ ParseText(element, AssociatedObject.Inlines, HtmlLabel);
+ }
+
+ private static void ParseText(XElement element, InlineCollection inlines, IHtmlLabel label)
+ {
+ if (element == null)
+ {
+ return;
+ }
+
+ InlineCollection currentInlines = inlines;
+ var elementName = element.Name.ToString().ToUpperInvariant();
+ switch (elementName)
+ {
+ case _elementA:
+ var link = new Hyperlink();
+ XAttribute href = element.Attribute("href");
+ var unescapedUri = Uri.UnescapeDataString(href?.Value);
+ if (href != null)
+ {
+ try
+ {
+ link.NavigateUri = new Uri(unescapedUri);
+ }
+ catch (FormatException) { /* href is not valid */ }
+ }
+ link.Click += (sender, e) =>
+ {
+ sender.NavigateUri = null;
+ RendererHelper.HandleUriClick(label, unescapedUri);
+ };
+ if ( !ControlsColorExtensions.IsDefault( label.LinkColor ) )
+ {
+ link.Foreground = label.LinkColor.ToPlatform();
+ }
+ if (!label.UnderlineText)
+ {
+ link.UnderlineStyle = UnderlineStyle.None;
+ }
+ inlines.Add(link);
+ currentInlines = link.Inlines;
+ break;
+ 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 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 Span();
+ inlines.Add(divSpan);
+ currentInlines = divSpan.Inlines;
+ break;
+ }
+ foreach (XNode 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());
+ }
+ }
+ private static bool AddLineBreakIfNeeded(InlineCollection inlines)
+ {
+ if (inlines.Count <= 0)
+ {
+ return false;
+ }
+
+ Inline lastInline = inlines[inlines.Count - 1];
+ while ((lastInline is Span))
+ {
+ var span = (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
diff --git a/Maui/HtmlLabel/Platforms/iOS/Extensions.cs b/Maui/HtmlLabel/Platforms/iOS/Extensions.cs
new file mode 100644
index 0000000..a480f99
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/iOS/Extensions.cs
@@ -0,0 +1,440 @@
+using Foundation;
+using System.Runtime.InteropServices;
+using UIKit;
+using Microsoft.Maui.Controls.Compatibility.Platform.iOS;
+using HyperTextLabel.Maui.Controls;
+
+namespace HyperTextLabel.Maui.Platforms.iOS
+{
+ internal static class ColorExtensions
+ {
+ internal static bool IsEqualToColor(this UIColor self, UIColor otherColor)
+ {
+ NFloat r;
+ NFloat g;
+ NFloat b;
+ NFloat a;
+
+ self.GetRGBA(out r, out g, out b, out a);
+ NFloat r2;
+ NFloat g2;
+ NFloat b2;
+ NFloat a2;
+
+ otherColor.GetRGBA(out r2, out g2, out b2, out a2);
+
+ return r == r2 && g == g2 && b == b2 && a == a2;
+ }
+
+ internal static UIColor LabelColor
+ {
+ get
+ {
+ if ( IsiOS13OrNewer )
+ return UIColor.Label;
+
+ return UIColor.Black;
+ }
+ }
+
+ static bool? s_isiOS13OrNewer;
+ private static bool IsiOS13OrNewer
+ {
+ get
+ {
+ if ( !s_isiOS13OrNewer.HasValue )
+ s_isiOS13OrNewer = UIDevice.CurrentDevice.CheckSystemVersion( 13, 0 );
+
+ return s_isiOS13OrNewer.Value;
+ }
+ }
+ }
+
+ internal static class FontExtensionss
+ {
+ // static readonly string _defaultFontName = UIFont.SystemFontOfSize(12).Name;
+ // internal static bool IsBold(UIFont font)
+ // {
+ // UIFontDescriptor fontDescriptor = font.FontDescriptor;
+ // UIFontDescriptorSymbolicTraits traits = fontDescriptor.SymbolicTraits;
+ // return traits.HasFlag(UIFontDescriptorSymbolicTraits.Bold);
+ // }
+
+ // internal static UIFont Bold(this UIFont font)
+ // {
+ // UIFontDescriptor fontDescriptor = font.FontDescriptor;
+ // UIFontDescriptorSymbolicTraits traits = fontDescriptor.SymbolicTraits;
+ // traits = traits | UIFontDescriptorSymbolicTraits.Bold;
+ // UIFontDescriptor boldFontDescriptor = fontDescriptor.CreateWithTraits(traits);
+ // return UIFont.FromDescriptor(boldFontDescriptor, font.PointSize);
+ // }
+ // internal static UIFont Italic(this UIFont self)
+ // {
+ // UIFontDescriptor fontDescriptor = self.FontDescriptor;
+ // UIFontDescriptorSymbolicTraits traits = fontDescriptor.SymbolicTraits;
+ // traits = traits | UIFontDescriptorSymbolicTraits.Italic;
+ // UIFontDescriptor boldFontDescriptor = fontDescriptor.CreateWithTraits(traits);
+ // return UIFont.FromDescriptor(boldFontDescriptor, self.PointSize);
+ // }
+
+ internal static UIFont WithTraitsOfFont(this UIFont self, UIFont font)
+ {
+ UIFontDescriptor fontDescriptor = self.FontDescriptor;
+ UIFontDescriptorSymbolicTraits traits = fontDescriptor.SymbolicTraits;
+ traits = traits | font.FontDescriptor.SymbolicTraits;
+ UIFontDescriptor boldFontDescriptor = fontDescriptor.CreateWithTraits(traits);
+ return UIFont.FromDescriptor(boldFontDescriptor, self.PointSize);
+ }
+ // public static UIFont ToUIFont(this Font self) => ToNativeFont(self);
+
+ // internal static UIFont ToUIFont(this IFontElement element) => ToNativeFont(element);
+
+ // static UIFont _ToNativeFont(string family, float size, FontAttributes attributes)
+ // {
+ // var bold = (attributes & FontAttributes.Bold) != 0;
+ // var italic = (attributes & FontAttributes.Italic) != 0;
+
+ // if (family != null && family != _defaultFontName)
+ // {
+ // try
+ // {
+ // UIFont result = null;
+ // if (UIFont.FamilyNames.Contains(family))
+ // {
+ // var descriptor = new UIFontDescriptor().CreateWithFamily(family);
+
+ // if (bold || italic)
+ // {
+ // var traits = (UIFontDescriptorSymbolicTraits)0;
+ // if (bold)
+ // traits = traits | UIFontDescriptorSymbolicTraits.Bold;
+ // if (italic)
+ // traits = traits | UIFontDescriptorSymbolicTraits.Italic;
+
+ // descriptor = descriptor.CreateWithTraits(traits);
+ // result = UIFont.FromDescriptor(descriptor, size);
+ // if (result != null)
+ // return result;
+ // }
+ // }
+
+ // var cleansedFont = CleanseFontName(family);
+ // result = UIFont.FromName(cleansedFont, size);
+ // if (family.StartsWith(".SFUI", System.StringComparison.InvariantCultureIgnoreCase))
+ // {
+ // var fontWeight = family.Split('-').LastOrDefault();
+
+ // if (!string.IsNullOrWhiteSpace(fontWeight) && System.Enum.TryParse(fontWeight, true, out var uIFontWeight))
+ // {
+ // result = UIFont.SystemFontOfSize(size, uIFontWeight);
+ // return result;
+ // }
+
+ // result = UIFont.SystemFontOfSize(size, UIFontWeight.Regular);
+ // return result;
+ // }
+ // if (result == null)
+ // result = UIFont.FromName(family, size);
+ // if (result != null)
+ // return result;
+ // }
+ // catch
+ // {
+ // Debug.WriteLine("Could not load font named: {0}", family);
+ // }
+ // }
+
+ // if (bold && italic)
+ // {
+ // var defaultFont = UIFont.SystemFontOfSize(size);
+
+ // var descriptor = defaultFont.FontDescriptor.CreateWithTraits(UIFontDescriptorSymbolicTraits.Bold | UIFontDescriptorSymbolicTraits.Italic);
+ // return UIFont.FromDescriptor(descriptor, 0);
+ // }
+
+ // if (italic)
+ // return UIFont.ItalicSystemFontOfSize(size);
+
+ // if (bold)
+ // return UIFont.BoldSystemFontOfSize(size);
+
+ // return UIFont.SystemFontOfSize(size);
+ // }
+
+ // internal static string CleanseFontName(string fontName)
+ // {
+
+ // //First check Alias
+ // var (hasFontAlias, fontPostScriptName) = FontRegistrar.HasFont(fontName);
+ // if (hasFontAlias)
+ // return fontPostScriptName;
+
+ // var fontFile = FontFile.FromString(fontName);
+
+ // if (!string.IsNullOrWhiteSpace(fontFile.Extension))
+ // {
+ // var (hasFont, filePath) = FontManager FontRegistrar.HasFont(fontFile.FileNameWithExtension());
+ // if (hasFont)
+ // return filePath ?? fontFile.PostScriptName;
+ // }
+ // else
+ // {
+ // foreach (var ext in FontFile.Extensions)
+ // {
+
+ // var formated = fontFile.FileNameWithExtension(ext);
+ // var (hasFont, filePath) = FontRegistrar.HasFont(formated);
+ // if (hasFont)
+ // return filePath;
+ // }
+ // }
+ // return fontFile.PostScriptName;
+ // }
+
+ // static readonly Dictionary ToUiFont = new Dictionary();
+
+ // internal static bool IsDefault(this Span self)
+ // {
+ // return self.FontFamily == null && self.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(Label), true) &&
+ // self.FontAttributes == FontAttributes.None;
+ // }
+
+ // static NativeFont ToNativeFont(this IFontElement element)
+ // {
+ // var fontFamily = element.FontFamily;
+ // var fontSize = (float)element.FontSize;
+ // var fontAttributes = element.FontAttributes;
+ // return ToNativeFont(fontFamily, fontSize, fontAttributes, _ToNativeFont);
+ // }
+
+ // static NativeFont ToNativeFont(this Font self)
+ // {
+ // var size = (float)self.FontSize;
+ // if (self.UseNamedSize)
+ // {
+ // switch (self.NamedSize)
+ // {
+ // case NamedSize.Micro:
+ // size = 12;
+ // break;
+ // case NamedSize.Small:
+ // size = 14;
+ // break;
+ // case NamedSize.Medium:
+ // size = 17; // as defined by iOS documentation
+ // break;
+ // case NamedSize.Large:
+ // size = 22;
+ // break;
+ // default:
+ // size = 17;
+ // break;
+ // }
+ // }
+
+ // var fontAttributes = self.FontAttributes;
+
+ // return ToNativeFont(self.FontFamily, size, fontAttributes, _ToNativeFont);
+ // }
+
+ // static NativeFont ToNativeFont(string family, float size, FontAttributes attributes, Func factory)
+ // {
+ // var key = new ToNativeFontFontKey(family, size, attributes);
+
+ // lock (ToUiFont)
+ // {
+ // NativeFont value;
+ // if (ToUiFont.TryGetValue(key, out value))
+ // return value;
+ // }
+
+ // var generatedValue = factory(family, size, attributes);
+
+ // lock (ToUiFont)
+ // {
+ // NativeFont value;
+ // if (!ToUiFont.TryGetValue(key, out value))
+ // ToUiFont.Add(key, value = generatedValue);
+ // return value;
+ // }
+ // }
+
+ // struct ToNativeFontFontKey
+ // {
+ // internal ToNativeFontFontKey(string family, float size, FontAttributes attributes)
+ // {
+ // _family = family;
+ // _size = size;
+ // _attributes = attributes;
+ // }
+ //#pragma warning disable 0414 // these are not called explicitly, but they are used to establish uniqueness. allow it!
+ // string _family;
+ // float _size;
+ // FontAttributes _attributes;
+ //#pragma warning restore 0414
+ // }
+ }
+
+ internal static class AttributedStringExtensions
+ {
+ internal static void SetLineHeight(this NSMutableAttributedString mutableHtmlString, IHtmlLabel element)
+ {
+ if (element.LineHeight < 0)
+ {
+ return;
+ }
+
+ using (var lineHeightStyle = new NSMutableParagraphStyle { LineHeightMultiple = (NFloat)element.LineHeight })
+ {
+ mutableHtmlString.AddAttribute(UIStringAttributeKey.ParagraphStyle, lineHeightStyle, new NSRange(0, mutableHtmlString.Length));
+ }
+ }
+
+ internal static void SetLinksStyles(this NSMutableAttributedString mutableHtmlString, IHtmlLabel element)
+ {
+
+ UIStringAttributes linkAttributes = null;
+
+ if (!element.UnderlineText)
+ {
+ linkAttributes ??= new UIStringAttributes();
+ linkAttributes.UnderlineStyle = NSUnderlineStyle.None;
+ };
+ if (!element.LinkColor.IsDefault())
+ {
+ linkAttributes ??= new UIStringAttributes();
+ linkAttributes.ForegroundColor = element.LinkColor.ToUIColor();
+ };
+
+ mutableHtmlString.EnumerateAttribute(UIStringAttributeKey.Link, new NSRange(0, mutableHtmlString.Length), NSAttributedStringEnumeration.LongestEffectiveRangeNotRequired,
+ (NSObject value, NSRange range, ref bool stop) =>
+ {
+ if (value != null && value is NSUrl url)
+ {
+ // Applies the style
+ if (linkAttributes != null)
+ {
+ mutableHtmlString.AddAttributes(linkAttributes, range);
+ }
+ }
+ });
+
+ }
+ internal static NSMutableAttributedString RemoveTrailingNewLines(this NSAttributedString htmlString)
+ {
+ var count = 0;
+ for (int i = 1; i <= htmlString.Length; i++)
+ {
+ if ("\n" != htmlString.Substring(htmlString.Length - i, 1).Value)
+ break;
+
+ count++;
+ }
+
+ if (count > 0)
+ htmlString = htmlString.Substring(0, htmlString.Length - count);
+
+ return new NSMutableAttributedString(htmlString);
+ }
+
+ internal static NSMutableAttributedString AddCharacterSpacing(this NSAttributedString attributedString, string text, double characterSpacing)
+ {
+ if (attributedString == null && characterSpacing == 0)
+ return null;
+
+ NSMutableAttributedString mutableAttributedString = attributedString as NSMutableAttributedString;
+ if (attributedString == null || attributedString.Length == 0)
+ {
+ mutableAttributedString = text == null ? new NSMutableAttributedString() : new NSMutableAttributedString(text);
+ }
+ else
+ {
+ mutableAttributedString = new NSMutableAttributedString(attributedString);
+ }
+
+ AddKerningAdjustment(mutableAttributedString, mutableAttributedString.Value, characterSpacing);
+
+ return mutableAttributedString;
+ }
+ internal static bool HasCharacterAdjustment(this NSMutableAttributedString mutableAttributedString)
+ {
+ if (mutableAttributedString == null)
+ return false;
+
+ NSRange removalRange;
+ var attributes = mutableAttributedString.GetAttributes(0, out removalRange);
+
+ for (uint i = 0; i < attributes.Count; i++)
+ if (attributes.Keys[i] is NSString nSString && nSString == UIStringAttributeKey.KerningAdjustment)
+ return true;
+
+ return false;
+ }
+
+ internal static void AddKerningAdjustment(NSMutableAttributedString mutableAttributedString, string text, double characterSpacing)
+ {
+ try
+ {
+ if ( !string.IsNullOrEmpty(text) )
+ {
+ if ( characterSpacing == 0 && !mutableAttributedString.HasCharacterAdjustment() )
+ return;
+
+ mutableAttributedString.AddAttribute
+ (
+ UIStringAttributeKey.KerningAdjustment,
+ NSObject.FromObject( characterSpacing ), new NSRange( 0, text.Length - 1 )
+ );
+ }
+ }
+ catch ( Exception e )
+ {
+ Console.WriteLine( e );
+
+ throw;
+ }
+ }
+
+ internal static bool IsHorizontal(this Button.ButtonContentLayout layout) =>
+ layout.Position == Button.ButtonContentLayout.ImagePosition.Left ||
+ layout.Position == Button.ButtonContentLayout.ImagePosition.Right;
+ }
+
+ internal static class AlignmentExtensions
+ {
+ internal static UITextAlignment ToNativeTextAlignment(this TextAlignment alignment, EffectiveFlowDirection flowDirection)
+ {
+ var isLtr = (flowDirection & EffectiveFlowDirection.RightToLeft) == EffectiveFlowDirection.RightToLeft;
+ switch (alignment)
+ {
+ case TextAlignment.Center:
+ return UITextAlignment.Center;
+ case TextAlignment.End:
+ if (isLtr)
+ return UITextAlignment.Right;
+ else
+ return UITextAlignment.Left;
+ default:
+ if (isLtr)
+ return UITextAlignment.Left;
+ else
+ return UITextAlignment.Right;
+ }
+ }
+
+ internal static UIControlContentVerticalAlignment ToNativeTextAlignment(this TextAlignment alignment)
+ {
+ switch (alignment)
+ {
+ case TextAlignment.Center:
+ return UIControlContentVerticalAlignment.Center;
+ case TextAlignment.End:
+ return UIControlContentVerticalAlignment.Bottom;
+ case TextAlignment.Start:
+ return UIControlContentVerticalAlignment.Top;
+ default:
+ return UIControlContentVerticalAlignment.Top;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Maui/HtmlLabel/Platforms/iOS/HtmlLabelExtensions.cs b/Maui/HtmlLabel/Platforms/iOS/HtmlLabelExtensions.cs
new file mode 100644
index 0000000..b222ed5
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/iOS/HtmlLabelExtensions.cs
@@ -0,0 +1,117 @@
+using Foundation;
+using HyperTextLabel.Maui.Controls;
+using HyperTextLabel.Maui.Utilities;
+using Microsoft.Maui.Platform;
+using UIKit;
+
+namespace HyperTextLabel.Maui.Platforms.iOS
+{
+ internal static class HtmlLabelExtensions
+ {
+ public static void UpdateText(this MauiLabel view, IHtmlLabel label, IFontManager fontManager)
+ {
+ if (string.IsNullOrWhiteSpace(label?.Text))
+ {
+ view.Text = string.Empty;
+ return;
+ }
+
+ var uiFont = fontManager.GetFont(label.Font, UIFont.LabelFontSize);
+ view.Font = uiFont;
+
+ var linkColor = label.LinkColor;
+ if (!linkColor.IsDefault())
+ {
+ view.TintColor = linkColor.ToPlatform();
+ }
+ var isRtl = AppInfo.RequestedLayoutDirection == LayoutDirection.RightToLeft;
+ var styledHtml = new RendererHelper(label, label.Text, DevicePlatform.iOS, isRtl).ToString();
+ SetText(styledHtml, view, label);
+ view.SetNeedsDisplay();
+ }
+
+ public static void UpdateUnderlineText(this MauiLabel view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateLinkColor(this MauiLabel view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateBrowserLaunchOptions(this MauiLabel view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateAndroidLegacyMode(this MauiLabel view, IHtmlLabel label)
+ {
+ }
+
+ public static void UpdateAndroidListIndent(this MauiLabel view, IHtmlLabel label)
+ {
+ }
+
+ private static void SetText(string html, MauiLabel view, IHtmlLabel label)
+ {
+ // Create HTML data sting
+ var stringType = new NSAttributedStringDocumentAttributes
+ {
+ DocumentType = NSDocumentType.HTML,
+ StringEncoding = NSStringEncoding.UTF8
+ };
+ var nsError = new NSError();
+
+ var htmlData = NSData.FromString(html, NSStringEncoding.Unicode);
+
+ using var htmlString = new NSAttributedString(htmlData, stringType, out _, ref nsError);
+ var mutableHtmlString = htmlString.RemoveTrailingNewLines();
+
+ mutableHtmlString.EnumerateAttributes(new NSRange(0, mutableHtmlString.Length), NSAttributedStringEnumeration.None,
+ (NSDictionary value, NSRange range, ref bool stop) =>
+ {
+ try
+ {
+ var md = new NSMutableDictionary(value);
+ var font = md[UIStringAttributeKey.Font] as UIFont;
+
+ if (font != null)
+ {
+ md[UIStringAttributeKey.Font] = view.Font.WithTraitsOfFont(font);
+ }
+ else
+ {
+ md[UIStringAttributeKey.Font] = view.Font;
+ }
+
+ var foregroundColor = md[UIStringAttributeKey.ForegroundColor] as UIColor;
+ if (foregroundColor == null || foregroundColor.IsEqualToColor(UIColor.Black))
+ {
+ md[UIStringAttributeKey.ForegroundColor] = view.TextColor;
+ }
+
+ mutableHtmlString.SetAttributes(md, range);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+
+ throw;
+ }
+ });
+
+ mutableHtmlString.SetLineHeight(label);
+ mutableHtmlString.SetLinksStyles(label);
+ view.AttributedText = mutableHtmlString;
+ }
+
+ //private static bool NavigateToUrl(NSUrl url)
+ //{
+ // if (url == null)
+ // {
+ // throw new ArgumentNullException(nameof(url));
+ // }
+ // // Try to handle uri, if it can't be handled, fall back to IOS his own handler.
+ // return !RendererHelper.HandleUriClick(Element, url.AbsoluteString);
+ //}
+
+ }
+}
diff --git a/Maui/HtmlLabel/Platforms/iOS/HtmlLabelHandler.cs b/Maui/HtmlLabel/Platforms/iOS/HtmlLabelHandler.cs
new file mode 100644
index 0000000..746505f
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/iOS/HtmlLabelHandler.cs
@@ -0,0 +1,54 @@
+using Microsoft.Maui.Platform;
+using HyperTextLabel.Maui.Controls;
+using HyperTextLabel.Maui.Extensions;
+using HyperTextLabel.Maui.Platforms.iOS;
+
+using PlatformView = Microsoft.Maui.Platform.MauiLabel;
+
+namespace HyperTextLabel.Maui.Handlers
+{
+ public partial class HtmlLabelHandler : Microsoft.Maui.Handlers.LabelHandler
+ {
+ protected override void ConnectHandler(PlatformView platformView)
+ {
+ base.ConnectHandler(platformView);
+ }
+
+ protected override void DisconnectHandler(PlatformView platformView)
+ {
+ base.DisconnectHandler(platformView);
+ }
+
+ public static void MapLabelText(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ var fontManager = handler.GetRequiredService();
+
+ handler.PlatformView?.UpdateText(label, fontManager);
+ }
+
+ public static void MapUnderlineText(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateUnderlineText(label);
+ }
+
+ public static void MapLinkColor(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateLinkColor(label);
+ }
+
+ public static void MapBrowserLaunchOptions(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateBrowserLaunchOptions(label);
+ }
+
+ public static void MapAndroidLegacyMode(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateAndroidLegacyMode(label);
+ }
+
+ public static void MapAndroidListIndent(HtmlLabelHandler handler, IHtmlLabel label)
+ {
+ handler.PlatformView?.UpdateAndroidListIndent(label);
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/Platforms/iOS/LinkTapHelper.cs b/Maui/HtmlLabel/Platforms/iOS/LinkTapHelper.cs
new file mode 100644
index 0000000..a180650
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/iOS/LinkTapHelper.cs
@@ -0,0 +1,78 @@
+using CoreGraphics;
+using Foundation;
+using HyperTextLabel.Maui.Controls;
+using HyperTextLabel.Maui.Utilities;
+using System.Runtime.InteropServices;
+using UIKit;
+
+namespace HyperTextLabel.Maui.Platforms.iOS
+{
+ internal static class LinkTapHelper
+ {
+ public static readonly NSString CustomLinkAttribute = new NSString("LabelLink");
+
+ public static void HandleLinkTap(this UILabel control, IHtmlLabel element)
+ {
+ void TapHandler(UITapGestureRecognizer tap)
+ {
+ var detectedUrl = DetectTappedUrl(tap, (UILabel)tap.View);
+ RendererHelper.HandleUriClick(element, detectedUrl);
+ }
+
+ var tapGesture = new UITapGestureRecognizer(TapHandler);
+ control.AddGestureRecognizer(tapGesture);
+ control.UserInteractionEnabled = true;
+ }
+
+ private static string DetectTappedUrl(UIGestureRecognizer tap, UILabel control)
+ {
+ CGRect bounds = control.Bounds;
+ NSAttributedString attributedText = control.AttributedText;
+
+ // Setup containers
+ using var textContainer = new NSTextContainer(bounds.Size)
+ {
+ LineFragmentPadding = 0,
+ LineBreakMode = control.LineBreakMode,
+ MaximumNumberOfLines = (nuint)control.Lines
+ };
+
+ using var layoutManager = new NSLayoutManager();
+ layoutManager.AddTextContainer(textContainer);
+
+ using var textStorage = new NSTextStorage();
+ textStorage.SetString(attributedText);
+
+ using var fontAttributeName = new NSString("NSFont");
+ var textRange = new NSRange(0, control.AttributedText.Length);
+ textStorage.AddAttribute(fontAttributeName, control.Font, textRange);
+ textStorage.AddLayoutManager(layoutManager);
+ CGRect textBoundingBox = layoutManager.GetUsedRect(textContainer);
+
+ // Calculate align offset
+ static NFloat GetAlignOffset(UITextAlignment textAlignment) => textAlignment switch
+ {
+ UITextAlignment.Center => 0.5f,
+ UITextAlignment.Right => 1f,
+ _ => 0.0f,
+ };
+ NFloat alignmentOffset = GetAlignOffset(control.TextAlignment);
+ NFloat xOffset = (bounds.Size.Width - textBoundingBox.Size.Width) * alignmentOffset - textBoundingBox.Location.X;
+ NFloat yOffset = (bounds.Size.Height - textBoundingBox.Size.Height) * alignmentOffset - textBoundingBox.Location.Y;
+
+ // Find tapped character
+ CGPoint locationOfTouchInLabel = tap.LocationInView(control);
+ var locationOfTouchInTextContainer = new CGPoint(locationOfTouchInLabel.X - xOffset, locationOfTouchInLabel.Y - yOffset);
+ var characterIndex = (nint)layoutManager.GetCharacterIndex(locationOfTouchInTextContainer, textContainer);
+
+ if (characterIndex >= attributedText.Length)
+ {
+ return null;
+ }
+
+ // Try to get the URL
+ NSObject linkAttributeValue = attributedText.GetAttribute(CustomLinkAttribute, characterIndex, out NSRange range);
+ return linkAttributeValue is NSUrl url ? url.AbsoluteString : null;
+ }
+ }
+}
diff --git a/Maui/HtmlLabel/Platforms/iOS/TextViewDelegate.cs b/Maui/HtmlLabel/Platforms/iOS/TextViewDelegate.cs
new file mode 100644
index 0000000..16ce1bc
--- /dev/null
+++ b/Maui/HtmlLabel/Platforms/iOS/TextViewDelegate.cs
@@ -0,0 +1,25 @@
+using Foundation;
+using UIKit;
+
+namespace HyperTextLabel.Maui.Platforms.iOS
+{
+ internal class TextViewDelegate : UITextViewDelegate
+ {
+ private Func _navigateTo;
+
+ public TextViewDelegate(Func navigateTo)
+ {
+ _navigateTo = navigateTo;
+ }
+
+ public override bool ShouldInteractWithUrl(UITextView textView, NSUrl URL, NSRange characterRange)
+ {
+ if (_navigateTo != null)
+ {
+ return _navigateTo(URL);
+ }
+ return true;
+ }
+ }
+
+}
diff --git a/Maui/HtmlLabel/Utilities/HttpUtility.cs b/Maui/HtmlLabel/Utilities/HttpUtility.cs
new file mode 100644
index 0000000..81969f8
--- /dev/null
+++ b/Maui/HtmlLabel/Utilities/HttpUtility.cs
@@ -0,0 +1,45 @@
+namespace HyperTextLabel.Maui.Utilities
+{
+ public static class HttpUtility
+ {
+ public static Dictionary> ParseQueryString(this Uri uri, bool decode = true)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ if (uri.Query.Length == 0 || uri.Query.Length == 1 && uri.Query[0] == '?')
+ {
+ return new Dictionary>();
+ }
+
+ var query = uri.Query;
+ if (query[0] == '?')
+ {
+ query = query.Substring(1);
+ }
+
+ return query
+ .Split('&')
+ .Select(p => p.Split('='))
+ .Select(p => p.Length == 1 ? (p[0], "true") : (p[0], p[1]))
+ .GroupBy(p => p.Item1.ToUpperInvariant())
+ .ToDictionary(
+ g => g.Key,
+ g =>
+ {
+ var values = g.Select(p => p.Item2);
+ if (decode)
+ values = values.Select(Uri.UnescapeDataString);
+ return values.ToList();
+ });
+ }
+
+ public static string GetFirst(this Dictionary> qParams, string key) =>
+ qParams.Get(key)?.FirstOrDefault();
+
+ public static List Get(this Dictionary> qParams, string key) =>
+ qParams != null && key != null && qParams.ContainsKey(key.ToUpperInvariant()) ? qParams[key.ToUpperInvariant()] : null;
+ }
+}
\ No newline at end of file
diff --git a/Maui/HtmlLabel/Utilities/RendererHelper.cs b/Maui/HtmlLabel/Utilities/RendererHelper.cs
new file mode 100644
index 0000000..d95d810
--- /dev/null
+++ b/Maui/HtmlLabel/Utilities/RendererHelper.cs
@@ -0,0 +1,205 @@
+using HyperTextLabel.Maui.Controls;
+using HyperTextLabel.Maui.Extensions;
+using System.Net;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+[assembly: InternalsVisibleTo("HyperTextLabel.Maui.Shared.Tests")]
+namespace HyperTextLabel.Maui.Utilities
+{
+ internal class RendererHelper
+ {
+ private readonly IHtmlLabel _label;
+ private readonly DevicePlatform _runtimePlatform;
+ private readonly bool _isRtl;
+ private readonly string _text;
+ private readonly IList> _styles;
+ private static readonly string[] SupportedProperties = {
+ Label.TextProperty.PropertyName,
+ Label.TextColorProperty.PropertyName,
+ Label.FontAttributesProperty.PropertyName,
+ Label.FontFamilyProperty.PropertyName,
+ Label.FontSizeProperty.PropertyName,
+ Label.LineBreakModeProperty.PropertyName,
+ Label.HorizontalTextAlignmentProperty.PropertyName,
+ Label.LineHeightProperty.PropertyName,
+ Label.PaddingProperty.PropertyName,
+ HtmlLabel.LinkColorProperty.PropertyName
+ };
+
+ public RendererHelper(IHtmlLabel label, string text, DevicePlatform runtimePlatform, bool isRtl)
+ {
+ _label = label ?? throw new ArgumentNullException(nameof(label));
+ _runtimePlatform = runtimePlatform;
+ _isRtl = isRtl;
+ _text = text?.Trim();
+ _styles = new List>();
+ }
+
+ public void AddFontAttributesStyle(FontAttributes fontAttributes)
+ {
+ if (fontAttributes == FontAttributes.Bold)
+ {
+ AddStyle("font-weight", "bold");
+ }
+ else if (fontAttributes == FontAttributes.Italic)
+ {
+ AddStyle("font-style", "italic");
+ }
+ }
+
+ public void AddFontFamilyStyle(string fontFamily)
+ {
+ string GetSystemFont() => _runtimePlatform == DevicePlatform.iOS ? "-apple-system" :
+ _runtimePlatform == DevicePlatform.Android ? "Roboto" :
+ _runtimePlatform == DevicePlatform.WinUI ? "Segoe UI" :
+ "system-ui";
+
+ var fontFamilyValue = string.IsNullOrWhiteSpace(fontFamily)
+ ? GetSystemFont()
+ : fontFamily;
+ AddStyle("font-family", $"'{fontFamilyValue}'");
+ }
+
+ public void AddFontSizeStyle(double fontSize)
+ {
+ AddStyle("font-size", $"{fontSize}px");
+ }
+
+ public void AddTextColorStyle(Color color)
+ {
+ if (color.IsDefault())
+ {
+ return;
+ }
+
+ var red = (int)(color.Red * 255);
+ var green = (int)(color.Green * 255);
+ var blue = (int)(color.Blue * 255);
+ var alpha = color.Alpha;
+ var hex = $"#{red:X2}{green:X2}{blue:X2}";
+ var rgba = $"rgba({red},{green},{blue},{alpha})";
+ AddStyle("color", hex);
+ AddStyle("color", rgba);
+ }
+
+ public void AddHorizontalTextAlignStyle(TextAlignment textAlignment)
+ {
+ if (textAlignment == TextAlignment.Start)
+ {
+ AddStyle("text-align", _isRtl ? "right" : "left");
+ }
+ else if (textAlignment == TextAlignment.Center)
+ {
+ AddStyle("text-align", "center");
+ }
+ else if (textAlignment == TextAlignment.End)
+ {
+ AddStyle("text-align", _isRtl ? "left" : "right");
+ }
+ }
+
+ public override string ToString()
+ {
+ if (string.IsNullOrWhiteSpace(_text))
+ {
+ return null;
+ }
+
+ AddFontAttributesStyle(_label.FontAttributes);
+ AddFontFamilyStyle(_label.FontFamily);
+ AddTextColorStyle(_label.TextColor);
+ AddHorizontalTextAlignStyle(_label.HorizontalTextAlignment);
+ AddFontSizeStyle(_label.FontSize);
+
+ var style = GetStyle();
+ return $"{_text}
";
+ }
+
+ public string GetStyle()
+ {
+ var builder = new StringBuilder();
+
+ foreach (KeyValuePair style in _styles)
+ {
+ _ = builder.Append($"{style.Key}:{style.Value};");
+ }
+
+ var css = builder.ToString();
+ if (_styles.Any())
+ {
+ css = css.Substring(0, css.Length - 1);
+ }
+
+ return css;
+ }
+
+ public static bool RequireProcess(string propertyName) => SupportedProperties.Contains(propertyName);
+
+ ///
+ /// Handles the Uri for the following types:
+ /// - Web url
+ /// - Email
+ /// - Telephone
+ /// - SMS
+ /// - GEO
+ ///
+ ///
+ ///
+ /// true if the uri has been handled correctly, false if the uri is not handled because of an error
+ public static bool HandleUriClick(IHtmlLabel label, string url)
+ {
+
+ if (url == null || !Uri.IsWellFormedUriString(WebUtility.UrlEncode(url), UriKind.RelativeOrAbsolute))
+ {
+ return false;
+ }
+
+ var args = new WebNavigatingEventArgs(WebNavigationEvent.NewPage, new UrlWebViewSource { Url = url }, url);
+
+ label.SendNavigating(args);
+
+ if (args.Cancel)
+ {
+ // Uri is handled because it is cancled;
+ return true;
+ }
+ bool result = false;
+ var uri = new Uri(url);
+
+ if (uri.IsHttp())
+ {
+ uri.LaunchBrowser(label.BrowserLaunchOptions);
+ result = true;
+ }
+ else if (uri.IsEmail())
+ {
+ result = uri.LaunchEmail();
+ }
+ else if (uri.IsTel())
+ {
+ result = uri.LaunchTel();
+ }
+ else if (uri.IsSms())
+ {
+ result = uri.LaunchSms();
+ }
+ else if (uri.IsGeo())
+ {
+ result = uri.LaunchMaps();
+ }
+ else
+ {
+ result = Launcher.TryOpenAsync(uri).Result;
+ }
+ // KWI-FIX What to do if the navigation failed? I assume not to spawn the SendNavigated event or introduce a fail bit on the args
+ label.SendNavigated(args);
+ return result;
+ }
+
+ private void AddStyle(string selector, string value)
+ {
+ _styles.Add(new KeyValuePair(selector, value));
+ }
+ }
+}
diff --git a/Maui/nuget.config b/Maui/nuget.config
new file mode 100644
index 0000000..01ef0af
--- /dev/null
+++ b/Maui/nuget.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file