瀏覽代碼

Added OCR fucntionality to Data Entry Module

frankvandenbos 1 月之前
父節點
當前提交
fd63dbed77

+ 24 - 1
prs.desktop/PRSDesktop.csproj

@@ -12,7 +12,6 @@
         <IsPackable>false</IsPackable>
 		<Nullable>enable</Nullable>
 		<Configurations>Debug;Release;Debug - DB;Test;DebugDev</Configurations>
-
 		<PreserveCompilationContext>true</PreserveCompilationContext>
 
 		<Platforms>AnyCPU;x64</Platforms>
@@ -924,6 +923,7 @@
       <PackageReference Include="Syncfusion.Gantt.WPF" Version="29.2.7" />
       <PackageReference Include="Syncfusion.Grid.WPF" Version="29.2.7" />
       <PackageReference Include="Syncfusion.Licensing" Version="29.2.7" />
+      <PackageReference Include="Syncfusion.Pdf.OCR.Wpf" Version="29.2.7" />
       <PackageReference Include="Syncfusion.Pdf.Wpf" Version="29.2.7" />
       <PackageReference Include="Syncfusion.SfBarcode.WPF" Version="29.2.7" />
       <PackageReference Include="Syncfusion.SfChart.WPF" Version="29.2.7" />
@@ -946,6 +946,9 @@
     </ItemGroup>
 
     <ItemGroup>
+      <Reference Include="Microsoft.Web.WebView2.Wpf">
+        <HintPath>..\..\..\Users\frank\.nuget\packages\microsoft.web.webview2\1.0.2592.51\lib\netcoreapp3.0\Microsoft.Web.WebView2.Wpf.dll</HintPath>
+      </Reference>
       <Reference Include="Syncfusion.SfChart.WPF">
         <HintPath>..\..\..\Program Files (x86)\Syncfusion\Essential Studio\WPF\17.2.0.34\Assemblies\4.6\Syncfusion.SfChart.WPF.dll</HintPath>
       </Reference>
@@ -1141,9 +1144,29 @@
     </ItemGroup>
 
     <ItemGroup>
+      <Folder Include="Tesseract\" />
       <Folder Include="Utils\LogikalUtils\" />
     </ItemGroup>
 
+    <ItemGroup>
+      <None Remove="Tesseract\ARIALUNI.TTF" />
+      <Content Include="Tesseract\ARIALUNI.TTF">
+        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      </Content>
+      <None Remove="Tesseract\eng.traineddata" />
+      <Content Include="Tesseract\eng.traineddata">
+        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      </Content>
+      <None Remove="Tesseract\leptonica-1.80.0.dll" />
+      <Content Include="Tesseract\leptonica-1.80.0.dll">
+        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      </Content>
+      <None Remove="Tesseract\libSyncfusionTesseract.dll" />
+      <Content Include="Tesseract\libSyncfusionTesseract.dll">
+        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      </Content>
+    </ItemGroup>
+
     <Import Project="..\PRS.Scheduler\Comal.TaskScheduler.Shared.projitems" Label="Shared" />
 
     <Import Project="..\..\InABox\InABox.DeviceIdentifier\InABox.DeviceIdentifier.projitems" Label="Shared" />

+ 2 - 1
prs.desktop/Panels/DataEntry/DataEntryList.xaml

@@ -53,7 +53,8 @@
                                              CanExplode="True"
                                              Explode="ViewList_Explode"
                                              ExplodeAll="ViewList_ExplodeAll"
-                                             UpdateDocument="ViewList_UpdateDocument"/>
+                                             UpdateDocument="ViewList_UpdateDocument"
+                                             OCRContextMenuOpening="ViewList_OnOCRContextMenuOpening"/>
                 </Grid>
             </dynamic:DynamicTabItem>
             

+ 7 - 0
prs.desktop/Panels/DataEntry/DataEntryList.xaml.cs

@@ -447,4 +447,11 @@ public partial class DataEntryList : UserControl, ICorePanel, IDockPanel
 
         ViewList.UpdateViewList(_dataEntryGrid.SelectedRows.ToArray(x => x.ToObject<DataEntryDocument>()), true);
     }
+    
+    public event DocumentOCRContextMenuOpeningEvent OCRContextMenuOpening;
+    
+    private void ViewList_OnOCRContextMenuOpening(object sender, DocumentOCRContextMenuArgs args)
+    {
+        OCRContextMenuOpening?.Invoke(this, args);
+    }
 }

+ 1 - 1
prs.desktop/Panels/DataEntry/DataEntryPanel.xaml

@@ -17,7 +17,7 @@
             </dynamic:DynamicSplitPanel.Header>
             
             <dynamic:DynamicSplitPanel.Master>
-                <local:DataEntryList x:Name="_documents" SelectionChanged="ScanPanel_OnSelectScan" />
+                <local:DataEntryList x:Name="_documents" SelectionChanged="ScanPanel_OnSelectScan" OCRContextMenuOpening="_documents_OnOCRContextMenuOpening"/>
             </dynamic:DynamicSplitPanel.Master>
             
             <dynamic:DynamicSplitPanel.Detail>

+ 77 - 0
prs.desktop/Panels/DataEntry/DataEntryPanel.xaml.cs

@@ -7,10 +7,12 @@ using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Linq;
+using System.Linq.Expressions;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media;
 using InABox.Configuration;
+using Microsoft.Xaml.Behaviors.Core;
 using PRSDesktop.Panels.DataEntry.Grids;
 
 namespace PRSDesktop;
@@ -457,4 +459,79 @@ public partial class DataEntryPanel : UserControl, IBasePanel, IDynamicEditorHos
         _settings.PreviewWidth = e.AnchorWidth;
         new UserConfiguration<DataEntryPanelSettings>().Save(_settings);
     }
+
+    private void CreateMenuItem<T>(ContextMenu menu, Expression<Func<T,object>> column, string caption, Action<string> action)
+    {
+        var columnname = CoreUtils.GetFullPropertyName<T, object>(column, ".");
+        if (Editor.FindEditor(columnname) != null)
+        {
+            var menuitem = new MenuItem()
+            {
+                Header = caption
+            };
+            menuitem.Click += (sender, args) => action?.Invoke(columnname);
+            menu.Items.Add(menuitem);
+        }
+    }
+    
+    private void CreateMenuItem<T>(ContextMenu menu, Expression<Func<T,object>> column, string caption, Action<MenuItem,string> build)
+    {
+        var columnname = CoreUtils.GetFullPropertyName<T, object>(column, ".");
+        if (Editor.FindEditor(columnname) != null)
+        {
+            var menuitem = new MenuItem()
+            {
+                Header = caption
+            };
+            build.Invoke(menuitem,columnname);
+            menu.Items.Add(menuitem);
+        }
+    }
+
+    private void _documents_OnOCRContextMenuOpening(object sender, DocumentOCRContextMenuArgs args)
+    {
+        if (GetEditorType() == typeof(Bill))
+        {
+            
+            CreateMenuItem<Bill>(args.Menu, x=>x.SupplierLink.ID, "Supplier", (m,c) =>
+            {
+                m.Items.Add(new MenuItem() { Header = "(Loading)", IsEnabled = false });
+                Client.Query<Supplier>(
+                    new Filter<Supplier>(x => x.ABN).IsEqualTo(args.Text).Or(x => x.Name).Contains(args.Text),
+                    Columns.None<Supplier>().Add(x => x.ID).Add(x => x.Code).Add(x => x.Name),
+                    new SortOrder<Supplier>(x => x.Code),
+                    (o, e) =>
+                    {
+                        if (o != null)
+                        {
+                            Dispatcher.Invoke(() =>
+                            {
+                                m.Items.Clear();
+                                foreach (var supp in o.ToArray<Supplier>())
+                                {
+                                    m.Items.Add(new MenuItem()
+                                    {
+                                        Header = $"{supp.Code}: {supp.Name}",
+                                        Command = new ActionCommand(() => { Editor.SetEditorValue(c, supp.ID); })
+                                    });
+                                }
+
+                                if (m.Items.Count.Equals(0))
+                                    m.Items.Add(new MenuItem()
+                                        { Header = "(No Suppliers Found)", IsEnabled = false });
+                            });
+                        }
+                    }
+                );
+            });
+            
+            CreateMenuItem<Bill>(args.Menu, x => x.Number, "Invoice Number", (c) => Editor.SetEditorValue(c, args.Text));
+
+            if (DateTime.TryParse(args.Text, out var date))
+            {
+                CreateMenuItem<Bill>(args.Menu, x=>x.BillDate, "Invoice Date", (c) => Editor.SetEditorValue(c, date));
+            }
+            
+        }
+    }
 }

+ 30 - 0
prs.desktop/Panels/DataEntry/DataEntryViewList.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Comal.Classes;
+using InABox.Core;
+
+namespace PRSDesktop;
+
+public class DataEntryViewList : DocumentViewList<DataEntryDocument>
+{
+    protected override IEnumerable<Document> LoadDocuments(IEnumerable<Guid> ids)
+    {
+        return DataEntryCache.Cache.LoadDocuments(ids, checkTimestamp: true);
+    }
+
+    protected override Guid GetID(DataEntryDocument document)
+    {
+        return document.ID;
+    }
+
+    protected override Guid GetDocumentID(DataEntryDocument document)
+    {
+        return document.Document.ID;
+    }
+    
+    protected override string GetDocumentFileName(IEnumerable<DataEntryDocument> documents, Document document)
+    {
+        return Documents.FirstOrDefault(x => x.Document.ID == document.ID)?.Document.FileName ?? "";
+    }
+}

+ 436 - 40
prs.desktop/Panels/DataEntry/DocumentViewList.cs

@@ -11,9 +11,11 @@ using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Drawing;
+using System.Drawing.Drawing2D;
 using System.Drawing.Imaging;
 using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Text;
 using System.Threading.Tasks;
@@ -22,10 +24,23 @@ using System.Windows.Controls;
 using System.Windows.Data;
 using System.Windows.Input;
 using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using Syncfusion.OCRProcessor;
+using Syncfusion.Pdf.Graphics;
+using Color = System.Drawing.Color;
 using Image = System.Windows.Controls.Image;
+using Rectangle = System.Windows.Shapes.Rectangle;
 
 namespace PRSDesktop;
 
+public class DocumentOCRContextMenuArgs(ContextMenu menu, string text) : EventArgs
+{
+    public ContextMenu Menu { get; private set; } = menu;
+    public string Text { get; private set; } = text;
+}
+    
+public delegate void DocumentOCRContextMenuOpeningEvent(object sender, DocumentOCRContextMenuArgs args);
+
 /// <summary>
 /// Control that allows to view a list of documents, within a zoom control, and providing methods to rotate/explode data.
 /// </summary>
@@ -101,9 +116,27 @@ public abstract class DocumentViewList<TDocument> : UserControl, INotifyProperty
         get => (bool)GetValue(CanRotateImageProperty);
         set => SetValue(CanRotateImageProperty, value);
     }
-
+    
+    private static OCRProcessor? processor = null;
+    
     public DocumentViewList()
     {
+        var tesseractpath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
+            "Tesseract");
+        if (Directory.Exists(tesseractpath))
+        {
+            processor ??= new OCRProcessor(tesseractpath);
+            processor.Settings.Language = Languages.English;
+            processor.Settings.PageSegment = PageSegMode.SparseText;
+
+            var fontfile = Path.Combine(tesseractpath, "ARIALUNI.ttf");
+            if (File.Exists(fontfile))
+            {
+                using (var fontStream = new FileStream(fontfile, FileMode.Open))
+                    processor.UnicodeFont = new PdfTrueTypeFont(fontStream, 8);
+            }
+        }
+
         var border = new Border();
         border.BorderBrush = Colors.Gray.ToBrush();
         border.Background = Colors.DimGray.ToBrush();
@@ -142,23 +175,166 @@ public abstract class DocumentViewList<TDocument> : UserControl, INotifyProperty
         rotateImage.Click += RotateImage_Click;
         itemsControl.ContextMenu.Items.Add(rotateImage);
 
+        //var ocrImage = new MenuItem()
+        //{
+        //    Header = "OCR Document (Preview)"
+        //};
+        //ocrImage.ToolTip = "Extract Text from this Image (Preview Only)";
+        //ocrImage.Bind<ImageSource, ImageSource>(MenuItem.TagProperty, x => x);
+        //ocrImage.Click += OCRImage_Click;
+        //itemsControl.ContextMenu.Items.Add(ocrImage);
+        
         itemsControl.ItemTemplate = TemplateGenerator.CreateDataTemplate(() =>
         {
+            var grid = new Grid();
+            
+            System.Windows.Point? dragStartPoint = null;
+            
+            var dragObject = new Border()
+            {
+                BorderThickness = new Thickness(0.75), 
+                BorderBrush = System.Windows.Media.Brushes.Firebrick, 
+                Background = new SolidColorBrush(Colors.Red)
+                {
+                    Opacity = 0.2
+                },
+                Visibility = Visibility.Collapsed
+            };
+            
+            ContextMenu dragMenu = new ContextMenu();
+
+            dragMenu.Closed += (s, e) =>
+            {
+                Logger.Send(LogType.Information,"","DragMenu Closed");
+                dragStartPoint = null;
+                dragObject.Visibility = Visibility.Collapsed;
+            };
+            
             var img = new Image();
             img.Bind<ImageSource, ImageSource>(Image.SourceProperty, x => x);
             img.Margin = new(0, 0, 0, 5);
             img.ContextMenu = itemsControl.ContextMenu;
-            img.MouseLeftButtonDown += Img_MouseLeftButtonDown;
-            return img;
-        });
+            
+            img.PreviewMouseLeftButtonDown += (sender, args) =>
+            {
+                if (sender is not Image image)
+                    return;
+                
+                if(args.ClickCount >= 2)
+                {
+                    OpenImageWindow(image.Source);
+                    args.Handled = true;
+                }
+                
+                if (processor == null || dragStartPoint != null || dragMenu.IsOpen)
+                    return;
+                
+                Logger.Send(LogType.Information,"",$"Starting Drag - {sender.GetType()}");
+                dragStartPoint = args.GetPosition(img);
+                Logger.Send(LogType.Information,"",$"Setting DragObject to be visible");
+                dragObject.Visibility = Visibility.Visible;
+                Logger.Send(LogType.Information,"",$"Set DragObject to be visible");
+                dragObject.Margin = new Thickness(dragStartPoint.Value.X, dragStartPoint.Value.Y, img.ActualWidth-dragStartPoint.Value.X, img.ActualHeight-dragStartPoint.Value.Y);
+                
+            };
+        
+            img.PreviewMouseMove += (sender, args) =>
+            {
+                if (processor == null || dragStartPoint == null || dragMenu.IsOpen)
+                    return;
+                
+                Logger.Send(LogType.Information,"",$"MouseMove with DragObject set");
+
+                var point = args.GetPosition(img);
+                var top = Math.Min(point.Y, dragStartPoint.Value.Y);
+                var left = Math.Min(point.X, dragStartPoint.Value.X);
+                var bottom = Math.Max(point.Y, dragStartPoint.Value.Y);
+                var right = Math.Max(point.X, dragStartPoint.Value.X);
+                Logger.Send(LogType.Information,"",$"Drag Rectangle is {left},{top} -> {right},{bottom}");
+                dragObject.Margin = new Thickness(left+1, top+1, (img.ActualWidth-right)+1, (img.ActualHeight-bottom)+1);
+                
+            };
+
+            img.PreviewMouseLeftButtonUp += (sender, args) =>
+            {
+                if (processor == null)
+                    return;
+                
+                Logger.Send(LogType.Information,"",$"Hiding DragObject");
+                
+                if (dragStartPoint == null)
+                    return;
+                Logger.Send(LogType.Information,"",$"Ending Drag");
+                
+                
+                if (sender is not Image image || image.Source is not BitmapSource bitmapSource)
+                    return;
+                
+                var croprect = new RectangleF(
+                    (float)dragObject.Margin.Left-0.75F, 
+                    (float)dragObject.Margin.Top-0.75F,
+                    (float)(img.ActualWidth+0.75F-(dragObject.Margin.Left+dragObject.Margin.Right)), 
+                    (float)(img.ActualHeight+0.75F-(dragObject.Margin.Top+dragObject.Margin.Bottom))
+                );
+
+                if (croprect.Width < 5 || croprect.Height < 5)
+                {
+                    dragStartPoint = null;
+                    dragObject.Visibility = Visibility.Collapsed;
+                    return;
+                }
 
+                var bitmap = ImageUtils.BitmapSourceToBitmap(bitmapSource);
+                Bitmap bmp = bitmap.Clone(croprect, bitmap.PixelFormat);
+                bmp = Resize(bmp, bmp.Width * 10, bmp.Height * 10);
+                bmp = SetGrayscale(bmp);
+                bmp = RemoveNoise(bmp);
+                bmp.Save(Path.Combine(CoreUtils.GetPath(),"ocr.bmp"));
+                
+                string text = processor.PerformOCR(bmp, CoreUtils.GetPath()).Trim();
+                
+                if (string.IsNullOrWhiteSpace(text))
+                {
+                    dragStartPoint = null;
+                    dragObject.Visibility = Visibility.Collapsed;
+                    return;
+                }
+                
+                dragMenu.Items.Clear();
+                OCRContextMenuOpening?.Invoke(this, new DocumentOCRContextMenuArgs(dragMenu, text));
+                if (dragMenu.Items.Count == 0)
+                {
+                    dragStartPoint = null;
+                    dragObject.Visibility = Visibility.Collapsed;
+                    return;
+                }
+                
+                dragMenu.Items.Insert(0,new MenuItem() { Header = text, IsEnabled = false});
+                dragMenu.Items.Insert(1,new Separator());
+                dragMenu.IsOpen = true;
+                
+            };
+
+            img.MouseUp += (sender, args) =>
+            {
+                Logger.Send(LogType.Information, "", $"MouseUp");
+            };
+            
+            grid.Children.Add(img);
+            grid.Children.Add(dragObject);
+            return grid;
+        });
+        
         ZoomPanel.Content = itemsControl;
+
         border.Child = ZoomPanel;
         Content = border;
 
         BindingOperations.EnableCollectionSynchronization(ViewList, _viewListLock);
     }
-
+    
+    public event DocumentOCRContextMenuOpeningEvent OCRContextMenuOpening;
+    
     protected abstract Guid GetID(TDocument document);
     protected abstract Guid GetDocumentID(TDocument document);
     protected abstract string GetDocumentFileName(IEnumerable<TDocument> documents, Document document);
@@ -349,6 +525,259 @@ public abstract class DocumentViewList<TDocument> : UserControl, INotifyProperty
             doc.Data = loadeddoc.SaveToBytes();
         }
     }
+    
+    
+    public Bitmap Resize(Bitmap bmp, int newWidth, int newHeight)
+    {
+        
+        Bitmap result = new Bitmap(newWidth, newHeight);
+        using (Graphics g = Graphics.FromImage(result))
+        {
+            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
+            g.DrawImage(bmp, 0, 0, newWidth, newHeight);
+        }
+        return result;
+     
+            // Bitmap temp = (Bitmap)bmp;
+            //
+            // Bitmap bmap = new Bitmap(newWidth, newHeight, temp.PixelFormat);
+            //
+            // double nWidthFactor = (double)temp.Width / (double)newWidth;
+            // double nHeightFactor = (double)temp.Height / (double)newHeight;
+            //
+            // double fx, fy, nx, ny;
+            // int cx, cy, fr_x, fr_y;
+            // Color color1 = new Color();
+            // Color color2 = new Color();
+            // Color color3 = new Color();
+            // Color color4 = new Color();
+            // byte nRed, nGreen, nBlue;
+            //
+            // byte bp1, bp2;
+            //
+            // for (int x = 0; x < bmap.Width; ++x)
+            // {
+            //     for (int y = 0; y < bmap.Height; ++y)
+            //     {
+            //
+            //         fr_x = (int)Math.Floor(x * nWidthFactor);
+            //         fr_y = (int)Math.Floor(y * nHeightFactor);
+            //         cx = fr_x + 1;
+            //         if (cx >= temp.Width) cx = fr_x;
+            //         cy = fr_y + 1;
+            //         if (cy >= temp.Height) cy = fr_y;
+            //         fx = x * nWidthFactor - fr_x;
+            //         fy = y * nHeightFactor - fr_y;
+            //         nx = 1.0 - fx;
+            //         ny = 1.0 - fy;
+            //
+            //         color1 = temp.GetPixel(fr_x, fr_y);
+            //         color2 = temp.GetPixel(cx, fr_y);
+            //         color3 = temp.GetPixel(fr_x, cy);
+            //         color4 = temp.GetPixel(cx, cy);
+            //
+            //         // Blue
+            //         bp1 = (byte)(nx * color1.B + fx * color2.B);
+            //
+            //         bp2 = (byte)(nx * color3.B + fx * color4.B);
+            //
+            //         nBlue = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
+            //
+            //         // Green
+            //         bp1 = (byte)(nx * color1.G + fx * color2.G);
+            //
+            //         bp2 = (byte)(nx * color3.G + fx * color4.G);
+            //
+            //         nGreen = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
+            //
+            //         // Red
+            //         bp1 = (byte)(nx * color1.R + fx * color2.R);
+            //
+            //         bp2 = (byte)(nx * color3.R + fx * color4.R);
+            //
+            //         nRed = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
+            //
+            //         bmap.SetPixel(x, y, System.Drawing.Color.FromArgb
+            // (255, nRed, nGreen, nBlue));
+            //     }
+            // }
+            //
+            //
+            //
+            // return bmap;
+        
+    }
+    
+    public Bitmap SetGrayscale(Bitmap img)
+    {
+    
+        Bitmap temp = (Bitmap)img;
+        Bitmap bmap = (Bitmap)temp.Clone();
+        Color c;
+        for (int i = 0; i < bmap.Width; i++)
+        {
+            for (int j = 0; j < bmap.Height; j++)
+            {
+                c = bmap.GetPixel(i, j);
+                byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);
+    
+                bmap.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
+            }
+        }
+        return (Bitmap)bmap.Clone();
+    
+    }
+    
+    public Bitmap RemoveNoise(Bitmap bmap)
+    {
+    
+        for (var x = 0; x < bmap.Width; x++)
+        {
+            for (var y = 0; y < bmap.Height; y++)
+            {
+                var pixel = bmap.GetPixel(x, y);
+                if (pixel.R < 162 && pixel.G < 162 && pixel.B < 162)
+                    bmap.SetPixel(x, y, Color.Black);
+                else if (pixel.R > 162 && pixel.G > 162 && pixel.B > 162)
+                    bmap.SetPixel(x, y, Color.White);
+            }
+        }
+    
+        return bmap;
+    }
+
+
+    // private async void OCRImage_Click(object sender, RoutedEventArgs e)
+    // {
+    //     if (sender is not MenuItem item || item.Tag is not ImageSource image) return;
+    //
+    //     var document = ViewDocuments.FirstOrDefault(x => x.Image == image);
+    //     if (document is null)
+    //     {
+    //         MessageWindow.ShowError("An error occurred", "Document does not exist in ViewDocuments list");
+    //         return;
+    //     }
+    //     
+    //     var doc = LoadDocuments(CoreUtils.One(GetDocumentID(document.Document))).First();
+    //     
+    //     if (doc.FileName.ToLower().EndsWith(".txt"))
+    //     {
+    //         var text = System.Text.Encoding.UTF8.GetString(doc.Data);
+    //         File.WriteAllText(Path.Combine(CoreUtils.GetPath(),"txt-ocr.txt"),text);
+    //         return;
+    //     }
+    //     else
+    //     {
+    //         List<String> pagetexts = new List<String>();
+    //         var images = doc.FileName.ToLower().EndsWith(".pdf")
+    //             ? ImageUtils.RenderPDFToImageBytes(doc.Data, ImageUtils.ImageEncoding.PNG).ToList()
+    //             : new List<byte[]>() { doc.Data };
+    //         
+    //         using (var fontStream = new FileStream(Path.Combine(CoreUtils.GetPath(), "ARIALUNI.ttf"), FileMode.Open))
+    //         {
+    //             using (var font = new PdfTrueTypeFont(fontStream, 8))
+    //             {
+    //                 using (OCRProcessor processor = new OCRProcessor(CoreUtils.GetPath()))
+    //                 {
+    //                     processor.Settings.Language = Languages.English;
+    //                     processor.Settings.PageSegment = PageSegMode.SparseText;
+    //                     processor.Settings.OCREngineMode = OCREngineMode.LSTMOnly;
+    //                     //processor.UnicodeFont = font;
+    //                     foreach (var img in images)
+    //                     {
+    //                         using (var ms = new MemoryStream(img))
+    //                         {
+    //                             using (var bitmap = new Bitmap(ms))
+    //                             {
+    //                                 var bmp = Resize(bitmap, bitmap.Width * 3, bitmap.Height * 3);      
+    //                                 bmp = SetGrayscale(bmp);
+    //                                 bmp = RemoveNoise(bmp);
+    //                                 bmp.Save(Path.Combine(CoreUtils.GetPath(),"ocr.bmp"));
+    //                                 string text = processor.PerformOCR(bitmap, CoreUtils.GetPath());
+    //                                 pagetexts.Add(text);
+    //                             }
+    //                         }
+    //                         
+    //                     }
+    //                 }
+    //             }
+    //         
+    //             fontStream.Close();
+    //         }
+    //         File.WriteAllText(Path.Combine(CoreUtils.GetPath(), "image-ocr.txt"), String.Join("\n\n",pagetexts));
+    //         
+    //     }
+    //     
+    //     try
+    //     {
+    //         // TextRecognizer? _textRecognizer;
+    //         // var readyState = TextRecognizer.GetReadyState();
+    //         // if (readyState is AIFeatureReadyState.NotSupportedOnCurrentSystem or AIFeatureReadyState.DisabledByUser)
+    //         //     return;
+    //         //
+    //         // if (readyState == AIFeatureReadyState.NotReady)
+    //         //     await TextRecognizer.EnsureReadyAsync();
+    //         //     
+    //         // _textRecognizer = await TextRecognizer.CreateAsync();
+    //         // var bitmap = await ByteArrayToSoftwareBitmapAsync(doc.Data);
+    //         //
+    //         // SoftwareBitmap displayableImage = SoftwareBitmap.Convert(bitmap,
+    //         //     BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
+    //         //
+    //         // var imageBuffer = ImageBuffer.CreateForSoftwareBitmap(displayableImage);
+    //         // var result = _textRecognizer.RecognizeTextFromImage(imageBuffer);
+    //         // File.WriteAllText(Path.Combine(CoreUtils.GetPath(),"ai-ocr.txt"),String.Join("\n",result.Lines.Select(x=>x.Text)));
+    //         
+    //         
+    //         // else 
+    //         // {
+    //         //     var images = doc.FileName.ToLower().EndsWith(".pdf")
+    //         //         ? ImageUtils.RenderPDFToImageBytes(doc.Data).ToList()
+    //         //         : new List<byte[]>() { doc.Data };
+    //         //     
+    //         //     using (OCRProcessor processor = new OCRProcessor(CoreUtils.GetPath()))
+    //         //     {
+    //         //         PdfLoadedDocument pdfLoadedDocument = new PdfLoadedDocument(doc.Data);
+    //         //         processor.Settings.Language = Languages.English;
+    //         //         var text = processor.PerformOCR(pdfLoadedDocument);
+    //         //         File.WriteAllText(Path.Combine(CoreUtils.GetPath(),"pdf-ocr.txt"),text);
+    //         //     }
+    //         // }
+    //         // else
+    //         // {
+    //         //     using (var stream = new MemoryStream(doc.Data))
+    //         //     {
+    //         //         using (var bmp = new Bitmap(stream))
+    //         //         {
+    //         //             using (var fontStream = new FileStream(Path.Combine(CoreUtils.GetPath(), "ARIALUNI.ttf"),
+    //         //                        FileMode.Open))
+    //         //             {
+    //         //                 using (var font = new PdfTrueTypeFont(fontStream, 8))
+    //         //                 {
+    //         //                     using (OCRProcessor processor = new OCRProcessor(CoreUtils.GetPath()))
+    //         //                     {
+    //         //                         processor.Settings.Language = Languages.English;
+    //         //                         processor.UnicodeFont = font;
+    //         //                         string text = processor.PerformOCR(bmp, CoreUtils.GetPath());
+    //         //                         File.WriteAllText(Path.Combine(CoreUtils.GetPath(), "image-ocr.txt"), text);
+    //         //                     }
+    //         //                 }
+    //         //
+    //         //                 fontStream.Close();
+    //         //             }
+    //         //         }
+    //         //     }
+    //         // }}
+    //     }
+    //     catch(Exception err)
+    //     {
+    //         MessageWindow.ShowError("Something went wrong while trying to scan this document.", err);
+    //         return;
+    //     }
+    //     
+    // }
+
+    
 
     private void RotateImage_Click(object sender, RoutedEventArgs e)
     {
@@ -436,17 +865,7 @@ public abstract class DocumentViewList<TDocument> : UserControl, INotifyProperty
         if (sender is not DataEntryDocumentWindow window) return;
         OpenWindows.Remove(window);
     }
-
-    private void Img_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
-    {
-        if (sender is not Image image) return;
-
-        if(e.ClickCount >= 2)
-        {
-            OpenImageWindow(image.Source);
-            e.Handled = true;
-        }
-    }
+    
 
     #endregion
 
@@ -456,27 +875,4 @@ public abstract class DocumentViewList<TDocument> : UserControl, INotifyProperty
     {
         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
     }
-}
-
-public class DataEntryViewList : DocumentViewList<DataEntryDocument>
-{
-    protected override IEnumerable<Document> LoadDocuments(IEnumerable<Guid> ids)
-    {
-        return DataEntryCache.Cache.LoadDocuments(ids, checkTimestamp: true);
-    }
-
-    protected override Guid GetID(DataEntryDocument document)
-    {
-        return document.ID;
-    }
-
-    protected override Guid GetDocumentID(DataEntryDocument document)
-    {
-        return document.Document.ID;
-    }
-    
-    protected override string GetDocumentFileName(IEnumerable<DataEntryDocument> documents, Document document)
-    {
-        return Documents.FirstOrDefault(x => x.Document.ID == document.ID)?.Document.FileName ?? "";
-    }
-}
+}

二進制
prs.desktop/Tesseract/ARIALUNI.TTF


二進制
prs.desktop/Tesseract/eng.traineddata


二進制
prs.desktop/Tesseract/leptonica-1.80.0.dll


二進制
prs.desktop/Tesseract/libSyncfusionTesseract.dll