Skip to content

Commit

Permalink
Add RichTextFormatter #12 Special Tnx to @MPITech
Browse files Browse the repository at this point in the history
  • Loading branch information
ghost1372 committed Dec 15, 2024
1 parent 907bc70 commit 4952459
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
7 changes: 7 additions & 0 deletions dev/DevWinUI.Gallery/Assets/NavViewMenu/AppData.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions dev/DevWinUI.Gallery/T4Templates/NavigationPageMappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page x:Class="DevWinUIGallery.Views.RichTextFormatterPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dev="using:DevWinUI"
xmlns:local="using:DevWinUIGallery"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<ScrollViewer>
<StackPanel Margin="10"
dev:PanelAttach.ChildrenTransitions="Default"
Spacing="10">
<local:ControlExample DocPage="helpers/richTextFormatter"
HeaderText="TextBlock">
<local:ControlExample.CSharp>
<x:String>RichTextFormatterHelper.FormatTextBlock(text, textBlock);</x:String>
</local:ControlExample.CSharp>
<local:ControlExample.Pane>
<StackPanel Spacing="10">
<TextBox x:Name="Txt"
Header="Input"
PlaceholderText="Test of &lt;b&gt;bold&lt;/b&gt; &lt;i&gt;italic&lt;/i&gt; &lt;u&gt;underline&lt;/u&gt;&lt;br&gt;larger &lt;font size='20'&gt;font&lt;/font&gt;||&lt;font color='00c600'&gt;Forecolor&lt;/font&gt; &lt;font bgcolor='00c600'&gt; Background &lt;/font&gt;||&lt;font colors='cb3d00,004500'&gt; Both combined &lt;/font&gt;||regular"
Text="Test of &lt;b&gt;bold&lt;/b&gt; &lt;i&gt;italic&lt;/i&gt; &lt;u&gt;underline&lt;/u&gt;&lt;br&gt;larger &lt;font size='20'&gt;font&lt;/font&gt;||&lt;font color='00c600'&gt;Forecolor&lt;/font&gt; &lt;font bgcolor='00c600'&gt; Background &lt;/font&gt;||&lt;font colors='cb3d00,004500'&gt; Both combined &lt;/font&gt;||regular"
TextChanged="Txt_TextChanged" />
</StackPanel>
</local:ControlExample.Pane>
<TextBlock x:Name="txtBlock" />
</local:ControlExample>

<local:ControlExample DocPage="helpers/richTextFormatter"
HeaderText="RichTextBlock">
<local:ControlExample.CSharp>
<x:String>RichTextFormatterHelper.FormatRichTextBlock(text, richTextBlock);</x:String>
</local:ControlExample.CSharp>
<local:ControlExample.Pane>
<StackPanel Spacing="10">
<TextBox x:Name="Txt2"
Header="Input"
PlaceholderText="Test of &lt;b&gt;bold&lt;/b&gt; &lt;i&gt;italic&lt;/i&gt; &lt;u&gt;underline&lt;/u&gt;&lt;br&gt;larger &lt;font size='20'&gt;font&lt;/font&gt;||&lt;font color='00c600'&gt;Forecolor&lt;/font&gt; &lt;font bgcolor='00c600'&gt; Background &lt;/font&gt;||&lt;font colors='cb3d00,004500'&gt; Both combined &lt;/font&gt;||regular"
Text="Test of &lt;b&gt;bold&lt;/b&gt; &lt;i&gt;italic&lt;/i&gt; &lt;u&gt;underline&lt;/u&gt;&lt;br&gt;larger &lt;font size='20'&gt;font&lt;/font&gt;||&lt;font color='00c600'&gt;Forecolor&lt;/font&gt; &lt;font bgcolor='00c600'&gt; Background &lt;/font&gt;||&lt;font colors='cb3d00,004500'&gt; Both combined &lt;/font&gt;||regular"
TextChanged="Txt2_TextChanged" />
</StackPanel>
</local:ControlExample.Pane>
<RichTextBlock x:Name="txtRichBlock" />
</local:ControlExample>
</StackPanel>
</ScrollViewer>
</Page>
Original file line number Diff line number Diff line change
@@ -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);
}
}
8 changes: 8 additions & 0 deletions dev/DevWinUI/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
194 changes: 194 additions & 0 deletions dev/DevWinUI/Helpers/RichTextFormatterHelper.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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).
/// </summary>
/// <param name="textWithHTMLFormatting"></param>
/// <param name="richTextBlock"></param>
public static void FormatRichTextBlock(string textWithHTMLFormatting, RichTextBlock richTextBlock)
{
// Clearing existing content
richTextBlock.Blocks.Clear();

if (textWithHTMLFormatting == null)
return;

// Replacing <br> 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, "<br>", placeholder, RegexOptions.IgnoreCase); // replace <br> with linebreak placeholder
processedText = processedText.Replace("|", placeholder); // replace | with linebreak placeholder

// Pattern to match various tags including foreground and background colors
string pattern = $@"<b>(.*?)</b>|<u>(.*?)</u>|<i>(.*?)</i>|<font size='(.*?)'>(.*?)</font>|<font color='(.*?)'>(.*?)</font>|<font bgcolor='(.*?)'>(.*?)</font>|<font colors='(.*?)'>(.*?)</font>|{Regex.Escape(placeholder)}";

var paragraph = new Paragraph();
ProcessFormattingText(processedText, pattern, paragraph.Inlines);
richTextBlock.Blocks.Add(paragraph);
}

/// <summary>
/// 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.
/// </summary>
/// <param name="textWithHTMLFormatting"></param>
/// <param name="textBlock"></param>
public static void FormatTextBlock(string textWithHTMLFormatting, TextBlock textBlock)
{
// Clearing existing content
textBlock.Inlines.Clear();

if (textWithHTMLFormatting == null)
return;

// Replacing <br> 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, "<br>", placeholder, RegexOptions.IgnoreCase); // replace <br> with linebreak placeholder
processedText = processedText.Replace("|", placeholder); // replace | with linebreak placeholder

// Pattern to match various tags including foreground colors
string pattern = $@"<b>(.*?)</b>|<u>(.*?)</u>|<i>(.*?)</i>|<font size='(.*?)'>(.*?)</font>|<font color='(.*?)'>(.*?)</font>|{Regex.Escape(placeholder)}";

ProcessFormattingText(processedText, pattern, textBlock.Inlines);
}

/// <summary>
/// Used by FormatTextBlock() to process the text and apply formatting to apply to the RichTextBlock or TextBlock
/// </summary>
/// <param name="text"></param>
/// <param name="pattern"></param>
/// <param name="inlines"></param>
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);
}
}
}

0 comments on commit 4952459

Please sign in to comment.