Browse Source

Added HTMLUtils class to convert between full HTML (editing) and HTML-Enabled snippets (Reporting)

frogsoftware 9 months ago
parent
commit
6c501db58e

+ 17 - 13
InABox.Core/Editors/RichTextEditor.cs

@@ -7,28 +7,32 @@ namespace InABox.Core
     public enum RichTextEditorButtons
     {
         None = 0,
-        Bold,
-        Color,
-        Italic,
-        Underline,
-        Align,
-        Hyperlink,
-        Picture,
-        Table,
-        Zoom,
-        Font,
+        Bold = 1,
+        Color = 2,
+        Italic = 4,
+        Underline = 8,
+        Align = 16,
+        Hyperlink = 32,
+        Picture = 64,
+        Table = 128,
+        Zoom = 256,
+        Font = 512,
         All = ~None,
         Simple = Bold | Color | Italic | Underline | Picture
     }
     
     public class RichTextEditor : BaseEditor
     {
+        private RichTextEditorButtons visibleButtons = RichTextEditorButtons.Simple;
+
+        public RichTextEditorButtons VisibleButtons
+        {
+            get => visibleButtons;
+        }
 
-        public RichTextEditorButtons VisibleButtons { get; set; } = RichTextEditorButtons.All;
-        
         protected override BaseEditor DoClone()
         {
-            return new RichTextEditor() { VisibleButtons = this.VisibleButtons };
+            return new RichTextEditor();
         }
     }
 }

+ 123 - 0
InABox.Core/HTMLUtils.cs

@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using HtmlAgilityPack;
+
+namespace InABox.Core
+{
+    public static class HTMLUtils
+    {
+        
+        /// <summary>
+        /// Takes a HTML Snippet and converts it to full HTML.
+        /// This is used to convert Fast-Reports "html-enabled" text to Syncfusion RichTextEditor html.
+        /// </summary>
+        /// <param name="text">The html snippet (no <head> or <body> tags, paragraphs represented by line breaks</param>
+        /// <returns>Properly formed HTM suitable for editing</returns>
+        public static string TextToHtml(string text)
+        {
+            var result = text;
+            if (!result.Contains("<html>"))
+            {
+                var lines = result.Split("\n").Select(l => $"<p>{(string.IsNullOrWhiteSpace(l) ? "&nbsp;" : l)}</p>").ToList();
+                while (string.Equals(lines.LastOrDefault(),$"<p>&nbsp;</p>"))
+                    lines.RemoveAt(lines.Count-1);
+                result = $"<html><body>{string.Join("\n",lines)}</body></html>";
+            }
+            var doc = new HtmlDocument();
+            doc.LoadHtml(result);
+            HtmlNode? head = null;
+            var htmlnode = doc.DocumentNode.SelectSingleNode("//html");
+            if (!htmlnode.ChildNodes.Any(x => string.Equals(x.Name, "head", StringComparison.CurrentCultureIgnoreCase)))
+            {
+                head = doc.CreateElement("head");
+                htmlnode.PrependChild(head);
+            }
+            head = doc.DocumentNode.SelectSingleNode("//head");
+            if (head != null)
+            {
+                HtmlNode? style = null;
+                if (!head.ChildNodes.Any(x =>
+                        string.Equals(x.Name, "style", StringComparison.CurrentCultureIgnoreCase)))
+                {
+                    style = doc.CreateElement("style");
+                    head.PrependChild(style);
+                }
+                else
+                    style = head.ChildNodes["style"];
+                style.InnerHtml = "p { margin: 0px 0px 0px 0px; }";
+            }
+            return doc.DocumentNode.OuterHtml;
+        }
+
+        /// <summary>
+        /// Converts HTML data to Fast-Reports compatible "html-enabled" text
+        /// Will dump tags that Fast-Reports cannot display
+        /// </summary>
+        /// <param name="html">The HTML data to be processed</param>
+        /// <returns>Text Suitable for printing using Fast-Reports</returns>
+        public static string HtmlToText(string html)
+        {
+            Dictionary<string, string> _replacements = new Dictionary<string, string>()
+            {
+                { "\n", "" },
+                { "\r", "" },
+                { "<p[^>]*>", "" },
+                { "</p[^>]*>", "\r\n" },
+            };
+                
+            var doc = new HtmlDocument();
+            doc.LoadHtml(html);
+
+            ProcessNode(doc.DocumentNode, (n) => ReplaceAttribute(n, "font-style","italic", "i"));
+            ProcessNode(doc.DocumentNode, (n) => ReplaceAttribute(n, "text-decoration","underline", "u"));
+            ProcessNode(doc.DocumentNode, (n) => ReplaceAttribute(n, "font-weight","bold", "b"));
+            ProcessNode(doc.DocumentNode, (n) => ReplaceAttribute(n, "font-size","(.*?)", ""));
+            ProcessNode(doc.DocumentNode, (n) => ReplaceAttribute(n, "font-family","(.*?)", ""));
+            ProcessNode(doc.DocumentNode, (n) => ReplaceAttribute(n, "background","(.*?)", ""));
+
+                
+            string result = doc.DocumentNode.SelectSingleNode("//body").InnerHtml; 
+                
+            foreach (var _key in _replacements.Keys)
+                result = Regex.Replace(result, _key, _replacements[_key]);
+            var lines = result.Split("\r\n").ToList();
+            while (string.IsNullOrWhiteSpace(lines.LastOrDefault()))
+                lines.RemoveAt(lines.Count-1);
+            return string.Join("\r\n", lines);
+        }
+        
+        private static void ProcessNode(HtmlNode node, Action<HtmlNode> action)
+        {
+            if (node.Name.ToLower() == "span")
+                action(node);
+            foreach (var _child in node.ChildNodes)
+                ProcessNode(_child,action);
+        }
+        
+        private static void ReplaceAttribute(HtmlNode node, string attribute, string value, string tag)
+        {
+            var _value = $"{attribute}:{value};";
+            var _style = node.Attributes.Contains("style") ? node.Attributes["style"] : null;
+            if (_style != null)
+            {
+                var _oldvalue = _style.Value;
+                _style.Value = Regex.Replace(_style.Value, _value, "");
+                if (!string.Equals(_style.Value, _oldvalue))
+                {
+                    if (string.IsNullOrWhiteSpace(_style.Value))
+                        node.Attributes.Remove("style");
+                    if (!string.IsNullOrWhiteSpace(tag))
+                    {
+                        var text = node.OuterHtml;
+                        node.Name = tag;
+                        node.Attributes.RemoveAll();
+                        node.InnerHtml = text;
+                    }
+                }
+            }
+        }
+        
+    }
+}

+ 1 - 0
InABox.Core/InABox.Core.csproj

@@ -29,6 +29,7 @@
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
         </PackageReference>
+        <PackageReference Include="HtmlAgilityPack" Version="1.11.65" />
         <PackageReference Include="Inflector.NetStandard" Version="1.2.2" />
         <PackageReference Include="PropertyChanged.Fody" Version="4.1.0" />
         <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

+ 8 - 6
inabox.wpf/DynamicGrid/Editors/RichTextEditor/RichTextEditor.xaml

@@ -100,12 +100,14 @@
                               Visibility="{Binding VisibleButtons, Converter={StaticResource ZoomVisibleConverter}}"/>
             </sf:ToolBarAdv>
 
-            <sf:SfRichTextBoxAdv x:Name="Editor" DockPanel.Dock="Top" EnableMiniToolBar="False"
-                                 ContentChanged="RichTextBoxAdv_ContentChanged" BorderThickness="0"
-                                 LostFocus="RichTextBoxAdv_LostFocus" OverridesDocumentBackground="True"
-                                 LayoutType="Continuous"
-                                 RequestNavigate="Editor_RequestNavigate"
-                                 >
+            <sf:SfRichTextBoxAdv 
+                x:Name="Editor" 
+                DockPanel.Dock="Top" 
+                EnableMiniToolBar="False"
+                ContentChanged="RichTextBoxAdv_ContentChanged" BorderThickness="0"
+                LostFocus="RichTextBoxAdv_LostFocus" OverridesDocumentBackground="True"
+                LayoutType="Continuous"
+                RequestNavigate="Editor_RequestNavigate">
                 <sf:SfRichTextBoxAdv.HtmlImportExportSettings>
                     <sf:HtmlImportExportSettings ImageNodeVisited="HtmlImportExportSettings_OnImageNodeVisited"></sf:HtmlImportExportSettings>
                 </sf:SfRichTextBoxAdv.HtmlImportExportSettings>

+ 23 - 13
inabox.wpf/DynamicGrid/Editors/RichTextEditor/RichTextEditor.xaml.cs

@@ -1,12 +1,18 @@
-using System.Diagnostics;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
+using System.Linq;
 using System.Text;
+using System.Text.RegularExpressions;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Documents;
 using System.Windows.Media;
+using HtmlAgilityPack;
 using InABox.Core;
 using InABox.WPF;
+using NPOI.SS.Formula.Functions;
 using Syncfusion.Windows.Controls.RichTextBoxAdv;
 
 namespace InABox.DynamicGrid
@@ -74,12 +80,13 @@ namespace InABox.DynamicGrid
             ZoomOut.SmallIcon = Wpf.Resources.zoomout.AsBitmapImage(16, 16);
             Editor.CaretBrush = new SolidColorBrush(Colors.Black);
             Editor.FontFamily = new FontFamily("Calibri");
-            Editor.FontSize = 12.0F;
+            Editor.FontSize = 10.0F;
             Editor.Foreground = new SolidColorBrush(Colors.Black);
             ZoomFactor = 150;
+            
             Text = "";
         }
-
+        
         // public bool HideToolbar
         // {
         //     get => (bool)GetValue(HideToolbarProperty);
@@ -122,10 +129,12 @@ namespace InABox.DynamicGrid
         {
             if (content == null)
                 content = "";
-
-            content = content.Replace("background:#000000", "").Replace("background:NoColor;", "");
-            var ms = new MemoryStream(Encoding.ASCII.GetBytes(content));
-            Editor.Load(ms, FormatType.Html);
+            content = HTMLUtils.TextToHtml(content); 
+            using (var ms = new MemoryStream(Encoding.ASCII.GetBytes(content)))
+            {
+                Editor.Load(ms, FormatType.Html);
+            }
+            
             VerticalAlignment = VerticalAlignment.Top;
             VerticalAlignment = VerticalAlignment.Stretch;
 
@@ -137,18 +146,19 @@ namespace InABox.DynamicGrid
         {
             if (bdirty)
             {
-                var ms = new MemoryStream();
-                Editor.Save(ms, FormatType.Html);
-                var reader = new StreamReader(ms);
-                curvalue = Encoding.UTF8.GetString(ms.GetBuffer());
-                curvalue = curvalue.Replace("background:#000000", "");
+                using (var ms = new MemoryStream())
+                {
+                    Editor.Save(ms, FormatType.Html);
+                    curvalue = Encoding.UTF8.GetString(ms.GetBuffer());
+                    curvalue = HTMLUtils.HtmlToText(curvalue);
+                }
 
                 bdirty = false;
             }
 
             return curvalue;
         }
-
+        
         private void RichTextBoxAdv_ContentChanged(object obj, ContentChangedEventArgs args)
         {
             bdirty = true;

+ 5 - 3
inabox.wpf/DynamicGrid/Editors/RichTextEditor/RichTextEditorControl.cs

@@ -1,4 +1,6 @@
-using InABox.Core;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using InABox.Core;
 using System.Windows;
 using System.Windows.Media;
 
@@ -57,12 +59,12 @@ namespace InABox.DynamicGrid
                 //VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
                 //TextWrapping = TextWrapping.Wrap,
                 //AcceptsReturn = true,
-                VisibleButtons = EditorDefinition?.VisibleButtons ?? RichTextEditorButtons.All
+                VisibleButtons = EditorDefinition?.VisibleButtons ?? RichTextEditorButtons.Simple
             };
             Editor.OnChanged += o => { CheckChanged(); };
             return Editor;
         }
-
+        
         protected override string RetrieveValue()
         {
             return Editor.Text;

+ 16 - 13
inabox.wpf/Reports/CustomObjects/HTMLView.cs

@@ -53,6 +53,7 @@ public class HTMLView : ReportComponentBase
         Padding = Padding.Empty;
         SetFlags(Flags.HasSmartTag, true);
         CanGrow = true;
+        Width = 10.0f;
     }
     
     public override SmartTagBase GetSmartTag()
@@ -103,11 +104,11 @@ public class HTMLView : ReportComponentBase
     {
         if (IsDesigning || !CanGrow)
             return base.CalcHeight();
-        var data = Report.GetColumnValueNullable(DataColumn) as string;
+        var data = Report.GetColumnValueNullable(DataColumn.Replace("[","").Replace("]","")) as string;
         if (String.IsNullOrWhiteSpace(data))
             return base.CalcHeight();
-        var size = HtmlRender.Measure(data, Width - Padding.Horizontal);
-        return ((float)size.Height + Padding.Vertical);
+        var size = HtmlRender.Measure(data, (Width - Padding.Horizontal) * 2f);
+        return ((float)(size.Height + Padding.Vertical) * 2f);
 
     }
     
@@ -119,8 +120,9 @@ public class HTMLView : ReportComponentBase
         {
             render(drawingContext);
         }
-        RenderTargetBitmap bitmap = new RenderTargetBitmap(
-            width, height, dpiX, dpiY, PixelFormats.Default);
+
+        RenderTargetBitmap bitmap = new RenderTargetBitmap(width * 2, height * 2, dpiX, dpiY, PixelFormats.Pbgra32);
+            //width * (int)(dpiX/96.0), height * (int)(dpiY/96.0), 96.0, 96.0, PixelFormats.Pbgra32);
         bitmap.Render(drawingVisual);
 
         return bitmap;
@@ -134,10 +136,11 @@ public class HTMLView : ReportComponentBase
         IGraphics g = e.Graphics;
         
         RectangleF rect = new RectangleF(
-            (AbsLeft + Padding.Left) * e.ScaleX,
-            (AbsTop + Padding.Top) * e.ScaleY,
-            (Width - Padding.Horizontal) * e.ScaleX,
-            (Height - Padding.Vertical) * e.ScaleY);
+            AbsLeft + Padding.Left,
+            AbsTop + Padding.Top,
+            Width - Padding.Horizontal,
+            Height - Padding.Vertical
+        );
         
         StringFormat format = e.Cache.GetStringFormat(
             StringAlignment.Near, 
@@ -169,9 +172,9 @@ public class HTMLView : ReportComponentBase
 
             if (!string.IsNullOrWhiteSpace(DataColumn))
             {
-                var data = Report.GetColumnValueNullable(DataColumn) as string;
-                BitmapSource image = CreateBitmap(
-                    (int)(Width * (e.Graphics.DpiX / 96f)) , (int)(Height * e.ScaleX * (e.Graphics.DpiY / 96f)), e.Graphics.DpiX , e.Graphics.DpiY ,
+                var data = Report.GetColumnValueNullable(DataColumn.Replace("[","").Replace("]","")) as string;
+                BitmapSource image = CreateBitmap((int)rect.Width, (int)rect.Height, 96f, 96f,
+                    //(int)(Width * e.ScaleX * (e.Graphics.DpiX / 96f)) , (int)(Height * e.ScaleY * (e.Graphics.DpiY / 96f)), e.Graphics.DpiX , e.Graphics.DpiY ,
                     context =>
                     {
                         HtmlRender.Render(context, data,0,0,(int)(Width));
@@ -179,7 +182,7 @@ public class HTMLView : ReportComponentBase
                 
                 var bmp = ImageUtils.BitmapSourceToBitmap(image);
                 bmp.Save("c:\\development\\html.png");
-                g.DrawImage(bmp, rect.X, rect.Y, bmp.Width * 96f * e.ScaleX / e.Graphics.DpiX, bmp.Height * 96f * e.ScaleY / e.Graphics.DpiY);
+                g.DrawImage(bmp, rect.X, rect.Y, bmp.Width, bmp.Height);
             }
             
         }