diff --git a/README.md b/README.md index 824a429..d4b63ae 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ If you encounter any issues or have feedback, please report them [here](https:// ## 🔥 DevWinUI 🔥 ### ⚡ What’s Inside? ⚡ + - ✨ RichTextFormatter - ✨ Converter - ✨ Extensions - ✨ Helpers @@ -250,6 +251,9 @@ Install-Package DevWinUI.ContextMenu ### ProgressButton ![DevWinUI](https://raw.githubusercontent.com/ghost1372/DevWinUI-Resources/refs/heads/main/DevWinUI-Docs/ProgressButton.gif) +### RichTextFormatter Helper +![DevWinUI](https://raw.githubusercontent.com/ghost1372/DevWinUI-Resources/refs/heads/main/DevWinUI-Docs/RichTextFormatter.png) + ### TextBox ![DevWinUI](https://raw.githubusercontent.com/ghost1372/DevWinUI-Resources/refs/heads/main/DevWinUI-Docs/TextBox.png) diff --git a/dev/DevWinUI.Gallery/Assets/NavViewMenu/AppData.json b/dev/DevWinUI.Gallery/Assets/NavViewMenu/AppData.json index 6d7dfde..e74a752 100644 --- a/dev/DevWinUI.Gallery/Assets/NavViewMenu/AppData.json +++ b/dev/DevWinUI.Gallery/Assets/NavViewMenu/AppData.json @@ -94,6 +94,13 @@ "ImagePath": "ms-appx:///Assets/Fluent/RatingControl.png", "IsSpecialSection": false, "Items": [ + { + "UniqueId": "DevWinUIGallery.Views.RichTextFormatterPage", + "Title": "RichTextFormatter", + "Subtitle": "Formats a TextBlock/RichTextBlock using HTML-like formatting codes", + "IsNew": true, + "ImagePath": "ms-appx:///Assets/Fluent/RichEditBox.png" + }, { "UniqueId": "DevWinUIGallery.Views.ProgressButtonPage", "Title": "ProgressButton", diff --git a/dev/DevWinUI.Gallery/T4Templates/NavigationPageMappings.cs b/dev/DevWinUI.Gallery/T4Templates/NavigationPageMappings.cs index fa1d9c2..6079330 100644 --- a/dev/DevWinUI.Gallery/T4Templates/NavigationPageMappings.cs +++ b/dev/DevWinUI.Gallery/T4Templates/NavigationPageMappings.cs @@ -25,6 +25,7 @@ public partial class NavigationPageMappings {"DevWinUIGallery.Views.WaveCirclePage", typeof(DevWinUIGallery.Views.WaveCirclePage)}, {"DevWinUIGallery.Views.BubblePage", typeof(DevWinUIGallery.Views.BubblePage)}, {"DevWinUIGallery.Views.GooeyPage", typeof(DevWinUIGallery.Views.GooeyPage)}, + {"DevWinUIGallery.Views.RichTextFormatterPage", typeof(DevWinUIGallery.Views.RichTextFormatterPage)}, {"DevWinUIGallery.Views.ProgressButtonPage", typeof(DevWinUIGallery.Views.ProgressButtonPage)}, {"DevWinUIGallery.Views.TextBoxPage", typeof(DevWinUIGallery.Views.TextBoxPage)}, {"DevWinUIGallery.Views.TileControlPage", typeof(DevWinUIGallery.Views.TileControlPage)}, diff --git a/dev/DevWinUI.Gallery/Views/Pages/Features/RichTextFormatterPage.xaml b/dev/DevWinUI.Gallery/Views/Pages/Features/RichTextFormatterPage.xaml new file mode 100644 index 0000000..3bb29c9 --- /dev/null +++ b/dev/DevWinUI.Gallery/Views/Pages/Features/RichTextFormatterPage.xaml @@ -0,0 +1,50 @@ + + + + + + + + RichTextFormatterHelper.FormatTextBlock(text, textBlock); + + + + + + + + + + + + RichTextFormatterHelper.FormatRichTextBlock(text, richTextBlock); + + + + + + + + + + + diff --git a/dev/DevWinUI.Gallery/Views/Pages/Features/RichTextFormatterPage.xaml.cs b/dev/DevWinUI.Gallery/Views/Pages/Features/RichTextFormatterPage.xaml.cs new file mode 100644 index 0000000..0e789ae --- /dev/null +++ b/dev/DevWinUI.Gallery/Views/Pages/Features/RichTextFormatterPage.xaml.cs @@ -0,0 +1,26 @@ +namespace DevWinUIGallery.Views; + +public sealed partial class RichTextFormatterPage : Page +{ + public RichTextFormatterPage() + { + this.InitializeComponent(); + Loaded += RichTextFormatterPage_Loaded; + } + + private void RichTextFormatterPage_Loaded(object sender, RoutedEventArgs e) + { + Txt_TextChanged(null, null); + Txt2_TextChanged(null, null); + } + + private void Txt_TextChanged(object sender, TextChangedEventArgs e) + { + RichTextFormatterHelper.FormatTextBlock(Txt.Text, txtBlock); + } + + private void Txt2_TextChanged(object sender, TextChangedEventArgs e) + { + RichTextFormatterHelper.FormatRichTextBlock(Txt2.Text, txtRichBlock); + } +} diff --git a/dev/DevWinUI/Extensions/Extensions.cs b/dev/DevWinUI/Extensions/Extensions.cs index 2fc5fbb..22ce9d3 100644 --- a/dev/DevWinUI/Extensions/Extensions.cs +++ b/dev/DevWinUI/Extensions/Extensions.cs @@ -5,4 +5,12 @@ public static SolidColorBrush GetSolidColorBrush(this string hex) { return ColorHelper.GetSolidColorBrush(hex); } + public static void FormatRichTextBlock(this string textWithHTMLFormatting, RichTextBlock richTextBlock) + { + RichTextFormatterHelper.FormatRichTextBlock(textWithHTMLFormatting, richTextBlock); + } + public static void FormatTextBlock(this string textWithHTMLFormatting, TextBlock textBlock) + { + RichTextFormatterHelper.FormatTextBlock(textWithHTMLFormatting, textBlock); + } } diff --git a/dev/DevWinUI/Helpers/RichTextFormatterHelper.cs b/dev/DevWinUI/Helpers/RichTextFormatterHelper.cs new file mode 100644 index 0000000..691e660 --- /dev/null +++ b/dev/DevWinUI/Helpers/RichTextFormatterHelper.cs @@ -0,0 +1,194 @@ +using Microsoft.UI.Text; +using Microsoft.UI.Xaml.Documents; +using System.Text.RegularExpressions; +using Windows.UI.Text; + +namespace DevWinUI; +public partial class RichTextFormatterHelper +{ + /// + /// Formats a RichTextBlock using HTML-like formatting codes, supported tags: br, b, u, i, font size='size', and pipe characters which are treated like br. Also supports font color='hexcolor' and font bgcolor='hexcolor'. + /// To combine both foreground and background colors, use font colors='hexforecolor,hexbackcolor' (colors separated by a comma). + /// + /// + /// + public static void FormatRichTextBlock(string textWithHTMLFormatting, RichTextBlock richTextBlock) + { + // Clearing existing content + richTextBlock.Blocks.Clear(); + + if (textWithHTMLFormatting == null) + return; + + // Replacing
with a unique placeholder that is unlikely to appear in the text + string placeholder = "\uE000"; // Using a Private Use Area Unicode character as a placeholder + string processedText = Regex.Replace(textWithHTMLFormatting, "
", placeholder, RegexOptions.IgnoreCase); // replace
with linebreak placeholder + processedText = processedText.Replace("|", placeholder); // replace | with linebreak placeholder + + // Pattern to match various tags including foreground and background colors + string pattern = $@"(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|{Regex.Escape(placeholder)}"; + + var paragraph = new Paragraph(); + ProcessFormattingText(processedText, pattern, paragraph.Inlines); + richTextBlock.Blocks.Add(paragraph); + } + + /// + /// Formats a TextBlock using HTML-like formatting codes, supported tags: br, b, u, i, font size='size', and pipe characters which are treated like br. Also supports font color='hexcolor', but TextBlocks do not support the background colors in this fashion, so use the RichTextBlock overload instead if backcolors are needed. + /// + /// + /// + public static void FormatTextBlock(string textWithHTMLFormatting, TextBlock textBlock) + { + // Clearing existing content + textBlock.Inlines.Clear(); + + if (textWithHTMLFormatting == null) + return; + + // Replacing
with a unique placeholder that is unlikely to appear in the text + string placeholder = "\uE000"; // Using a Private Use Area Unicode character as a placeholder + string processedText = Regex.Replace(textWithHTMLFormatting, "
", placeholder, RegexOptions.IgnoreCase); // replace
with linebreak placeholder + processedText = processedText.Replace("|", placeholder); // replace | with linebreak placeholder + + // Pattern to match various tags including foreground colors + string pattern = $@"(.*?)|(.*?)|(.*?)|(.*?)|(.*?)|{Regex.Escape(placeholder)}"; + + ProcessFormattingText(processedText, pattern, textBlock.Inlines); + } + + /// + /// Used by FormatTextBlock() to process the text and apply formatting to apply to the RichTextBlock or TextBlock + /// + /// + /// + /// + private static void ProcessFormattingText(string text, string pattern, InlineCollection inlines) + { + int lastIndex = 0; + + foreach (Match match in Regex.Matches(text, pattern)) + { + // Text before bold, underlined, italic, sized or line break + if (match.Index > lastIndex) + { + var runBeforeTag = new Run + { + Text = text.Substring(lastIndex, match.Index - lastIndex) + }; + inlines.Add(runBeforeTag); + } + + if (match.Value == "\uE000") // Handle line break + { + inlines.Add(new LineBreak()); + } + else + { + if (match.Groups[1].Success) // Handle bold text + { + var span = new Span + { + FontWeight = FontWeights.Bold + }; + ProcessFormattingText(match.Groups[1].Value, pattern, span.Inlines); + inlines.Add(span); + } + else if (match.Groups[2].Success) // Handle underlined text + { + var underline = new Underline(); + ProcessFormattingText(match.Groups[2].Value, pattern, underline.Inlines); + inlines.Add(underline); + } + else if (match.Groups[3].Success) // Handle italic text + { + var span = new Span + { + FontStyle = FontStyle.Italic + }; + ProcessFormattingText(match.Groups[3].Value, pattern, span.Inlines); + inlines.Add(span); + } + else if (match.Groups[4].Success && match.Groups[5].Success) // Handle sized text + { + var span = new Span + { + FontSize = double.Parse(match.Groups[4].Value) + }; + ProcessFormattingText(match.Groups[5].Value, pattern, span.Inlines); + inlines.Add(span); + } + else if (match.Groups[6].Success && match.Groups[7].Success) // Handle foreground color text + { + var span = new Span + { + Foreground = new SolidColorBrush(ColorHelper.GetColorFromHex(match.Groups[6].Value)) + }; + ProcessFormattingText(match.Groups[7].Value, pattern, span.Inlines); + inlines.Add(span); + } + else if (match.Groups[8].Success && match.Groups[9].Success) // Handle background color text + { + var span = new Span(); + var border = new Border + { + Background = new SolidColorBrush(ColorHelper.GetColorFromHex(match.Groups[8].Value)), + Child = new TextBlock + { + Text = match.Groups[9].Value, + TextWrapping = TextWrapping.Wrap + } + }; + + var inlineUIContainer = new InlineUIContainer + { + Child = border + }; + + inlines.Add(inlineUIContainer); + } + else if (match.Groups[10].Success && match.Groups[11].Success) // Handle combined foreground and background color text + { + var colors = match.Groups[10].Value.Split(','); + var foregroundColor = ColorHelper.GetColorFromHex(colors[0]); + var backgroundColor = ColorHelper.GetColorFromHex(colors[1]); + + var span = new Span + { + Foreground = new SolidColorBrush(foregroundColor) + }; + + var border = new Border + { + Background = new SolidColorBrush(backgroundColor), + Child = new TextBlock + { + Text = match.Groups[11].Value, + TextWrapping = TextWrapping.Wrap, + Foreground = new SolidColorBrush(foregroundColor) + } + }; + + var inlineUIContainer = new InlineUIContainer + { + Child = border + }; + + inlines.Add(inlineUIContainer); + } + } + + lastIndex = match.Index + match.Length; + } + + // Text after the last tag, if any + if (lastIndex < text.Length) + { + var runAfterLastTag = new Run + { + Text = text.Substring(lastIndex) + }; + inlines.Add(runAfterLastTag); + } + } +}