mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
[TextExtractor]Version 2.0 with table scan and many QOL improvements (#28358)
* Remove unsed code and organize. * If clicking a word hide the overlay to do the OCR Code formatting * spelling * Add ResultTable * Result Table working * Spelling Fixes * Initial Get UX for new Overlay UX working * Basic top buttons working and loading correctly * use Single Line and Table Toggle buttons when doing OCR * Code style and error and warning removal * Dispose Wrapping Stream as much as possible * Fix spelling catches * Use WPF UI 3.0.0-Preview.4 * Revert changes to ColorPicker UI * Add Settings DeepLink * Use Accent Color where possible * Remove redundant button styles, fix table click event * Fix disposing too early
This commit is contained in:
2
.github/actions/spell-check/expect.txt
vendored
2
.github/actions/spell-check/expect.txt
vendored
@@ -784,6 +784,7 @@ IFACEMETHODIMP
|
||||
IFile
|
||||
IFilter
|
||||
IGraphics
|
||||
IGT
|
||||
iid
|
||||
Iindex
|
||||
IIO
|
||||
@@ -1496,6 +1497,7 @@ qit
|
||||
QITAB
|
||||
QITABENT
|
||||
qps
|
||||
Quarternary
|
||||
QUERYENDSESSION
|
||||
QUERYOPEN
|
||||
QUEUESYNC
|
||||
|
||||
@@ -3,8 +3,18 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PowerOCR"
|
||||
ShutdownMode="OnExplicitShutdown"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
Exit="Application_Exit"
|
||||
ShutdownMode="OnExplicitShutdown"
|
||||
Startup="Application_Startup">
|
||||
<Application.Resources />
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemesDictionary Theme="Dark" />
|
||||
<ui:ControlsDictionary />
|
||||
<ResourceDictionary Source="/Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="/Styles/ButtonStyles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -6,7 +6,6 @@ using System;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using ManagedCommon;
|
||||
using PowerOCR.Helpers;
|
||||
using PowerOCR.Keyboard;
|
||||
using PowerOCR.Settings;
|
||||
|
||||
@@ -67,10 +66,10 @@ public partial class App : Application, IDisposable
|
||||
{
|
||||
Logger.LogInfo("PowerToys Runner exited. Exiting TextExtractor");
|
||||
NativeThreadCTS.Cancel();
|
||||
Application.Current.Dispatcher.Invoke(() => Shutdown());
|
||||
Current.Dispatcher.Invoke(() => Shutdown());
|
||||
});
|
||||
var userSettings = new UserSettings(new Helpers.ThrottledActionInvoker());
|
||||
eventMonitor = new EventMonitor(Application.Current.Dispatcher, NativeThreadCTS.Token);
|
||||
eventMonitor = new EventMonitor(Current.Dispatcher, NativeThreadCTS.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -89,11 +88,7 @@ public partial class App : Application, IDisposable
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
if (_instanceMutex != null)
|
||||
{
|
||||
_instanceMutex.ReleaseMutex();
|
||||
}
|
||||
|
||||
_instanceMutex?.ReleaseMutex();
|
||||
base.OnExit(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,19 +38,17 @@ internal sealed class ImageMethods
|
||||
int height = Math.Max(image.Height + 16, minH + 16);
|
||||
|
||||
// Create a compatible bitmap
|
||||
Bitmap dest = new(width, height, image.PixelFormat);
|
||||
using Graphics gd = Graphics.FromImage(dest);
|
||||
Bitmap destination = new(width, height, image.PixelFormat);
|
||||
using Graphics gd = Graphics.FromImage(destination);
|
||||
|
||||
gd.Clear(image.GetPixel(0, 0));
|
||||
gd.DrawImageUnscaled(image, 8, 8);
|
||||
|
||||
return dest;
|
||||
return destination;
|
||||
}
|
||||
|
||||
internal static ImageSource GetWindowBoundsImage(Window passedWindow)
|
||||
{
|
||||
bool isGrabFrame = false;
|
||||
|
||||
DpiScale dpi = VisualTreeHelper.GetDpi(passedWindow);
|
||||
int windowWidth = (int)(passedWindow.ActualWidth * dpi.DpiScaleX);
|
||||
int windowHeight = (int)(passedWindow.ActualHeight * dpi.DpiScaleY);
|
||||
@@ -59,14 +57,6 @@ internal sealed class ImageMethods
|
||||
int thisCorrectedLeft = (int)absPosPoint.X;
|
||||
int thisCorrectedTop = (int)absPosPoint.Y;
|
||||
|
||||
if (isGrabFrame == true)
|
||||
{
|
||||
thisCorrectedLeft += (int)(2 * dpi.DpiScaleX);
|
||||
thisCorrectedTop += (int)(26 * dpi.DpiScaleY);
|
||||
windowWidth -= (int)(4 * dpi.DpiScaleX);
|
||||
windowHeight -= (int)(70 * dpi.DpiScaleY);
|
||||
}
|
||||
|
||||
using Bitmap bmp = new(windowWidth, windowHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
using Graphics g = Graphics.FromImage(bmp);
|
||||
|
||||
@@ -74,19 +64,67 @@ internal sealed class ImageMethods
|
||||
return BitmapToImageSource(bmp);
|
||||
}
|
||||
|
||||
internal static async Task<string> GetRegionsText(Window? passedWindow, Rectangle selectedRegion, Language? preferredLanguage)
|
||||
internal static Bitmap GetWindowBoundsBitmap(Window passedWindow)
|
||||
{
|
||||
Bitmap bmp = new(selectedRegion.Width, selectedRegion.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
DpiScale dpi = VisualTreeHelper.GetDpi(passedWindow);
|
||||
int windowWidth = (int)(passedWindow.ActualWidth * dpi.DpiScaleX);
|
||||
int windowHeight = (int)(passedWindow.ActualHeight * dpi.DpiScaleY);
|
||||
|
||||
System.Windows.Point absPosPoint = passedWindow.GetAbsolutePosition();
|
||||
int thisCorrectedLeft = (int)absPosPoint.X;
|
||||
int thisCorrectedTop = (int)absPosPoint.Y;
|
||||
|
||||
Bitmap bmp = new(
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
using Graphics g = Graphics.FromImage(bmp);
|
||||
|
||||
System.Windows.Point absPosPoint = passedWindow == null ? default(System.Windows.Point) : passedWindow.GetAbsolutePosition();
|
||||
g.CopyFromScreen(
|
||||
thisCorrectedLeft,
|
||||
thisCorrectedTop,
|
||||
0,
|
||||
0,
|
||||
bmp.Size,
|
||||
CopyPixelOperation.SourceCopy);
|
||||
|
||||
return bmp;
|
||||
}
|
||||
|
||||
internal static Bitmap GetRegionAsBitmap(Window passedWindow, Rectangle selectedRegion)
|
||||
{
|
||||
Bitmap bmp = new(
|
||||
selectedRegion.Width,
|
||||
selectedRegion.Height,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
|
||||
using Graphics g = Graphics.FromImage(bmp);
|
||||
|
||||
System.Windows.Point absPosPoint = passedWindow.GetAbsolutePosition();
|
||||
|
||||
int thisCorrectedLeft = (int)absPosPoint.X + selectedRegion.Left;
|
||||
int thisCorrectedTop = (int)absPosPoint.Y + selectedRegion.Top;
|
||||
|
||||
g.CopyFromScreen(thisCorrectedLeft, thisCorrectedTop, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
|
||||
g.CopyFromScreen(
|
||||
thisCorrectedLeft,
|
||||
thisCorrectedTop,
|
||||
0,
|
||||
0,
|
||||
bmp.Size,
|
||||
CopyPixelOperation.SourceCopy);
|
||||
|
||||
bmp = PadImage(bmp);
|
||||
return bmp;
|
||||
}
|
||||
|
||||
internal static async Task<string> GetRegionsText(Window? passedWindow, Rectangle selectedRegion, Language? preferredLanguage)
|
||||
{
|
||||
if (passedWindow is null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
Bitmap bmp = GetRegionAsBitmap(passedWindow, selectedRegion);
|
||||
string? resultText = await ExtractText(bmp, preferredLanguage);
|
||||
|
||||
return resultText != null ? resultText.Trim() : string.Empty;
|
||||
@@ -104,7 +142,7 @@ internal sealed class ImageMethods
|
||||
|
||||
g.CopyFromScreen(thisCorrectedLeft, thisCorrectedTop, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
|
||||
|
||||
System.Windows.Point adjustedPoint = new System.Windows.Point(clickedPoint.X, clickedPoint.Y);
|
||||
System.Windows.Point adjustedPoint = new(clickedPoint.X, clickedPoint.Y);
|
||||
|
||||
string resultText = await ExtractText(bmp, preferredLanguage, adjustedPoint);
|
||||
return resultText.Trim();
|
||||
@@ -145,6 +183,8 @@ internal sealed class ImageMethods
|
||||
OcrEngine ocrEngine = OcrEngine.TryCreateFromLanguage(selectedLanguage);
|
||||
OcrResult ocrResult = await ocrEngine.RecognizeAsync(softwareBmp);
|
||||
|
||||
await memoryStream.DisposeAsync();
|
||||
await wrappingStream.DisposeAsync();
|
||||
GC.Collect();
|
||||
|
||||
if (singlePoint == null)
|
||||
@@ -156,7 +196,7 @@ internal sealed class ImageMethods
|
||||
}
|
||||
else
|
||||
{
|
||||
Windows.Foundation.Point fPoint = new Windows.Foundation.Point(singlePoint.Value.X, singlePoint.Value.Y);
|
||||
Windows.Foundation.Point fPoint = new(singlePoint.Value.X, singlePoint.Value.Y);
|
||||
foreach (OcrLine ocrLine in ocrResult.Lines)
|
||||
{
|
||||
foreach (OcrWord ocrWord in ocrLine.Words)
|
||||
@@ -188,11 +228,9 @@ internal sealed class ImageMethods
|
||||
|
||||
return text.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return text.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static Bitmap ScaleBitmapUniform(Bitmap passedBitmap, double scale)
|
||||
{
|
||||
@@ -200,18 +238,21 @@ internal sealed class ImageMethods
|
||||
using WrappingStream wrappingStream = new(memoryStream);
|
||||
passedBitmap.Save(wrappingStream, ImageFormat.Bmp);
|
||||
wrappingStream.Position = 0;
|
||||
BitmapImage bitmapimage = new();
|
||||
bitmapimage.BeginInit();
|
||||
bitmapimage.StreamSource = wrappingStream;
|
||||
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapimage.EndInit();
|
||||
bitmapimage.Freeze();
|
||||
BitmapImage bitmapImage = new();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.StreamSource = wrappingStream;
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
TransformedBitmap transformedBmp = new();
|
||||
transformedBmp.BeginInit();
|
||||
transformedBmp.Source = bitmapimage;
|
||||
transformedBmp.Source = bitmapImage;
|
||||
transformedBmp.Transform = new ScaleTransform(scale, scale);
|
||||
transformedBmp.EndInit();
|
||||
transformedBmp.Freeze();
|
||||
|
||||
memoryStream.Dispose();
|
||||
wrappingStream.Dispose();
|
||||
GC.Collect();
|
||||
return BitmapSourceToBitmap(transformedBmp);
|
||||
}
|
||||
@@ -243,14 +284,17 @@ internal sealed class ImageMethods
|
||||
|
||||
bitmap.Save(wrappingStream, ImageFormat.Bmp);
|
||||
wrappingStream.Position = 0;
|
||||
BitmapImage bitmapimage = new();
|
||||
bitmapimage.BeginInit();
|
||||
bitmapimage.StreamSource = wrappingStream;
|
||||
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapimage.EndInit();
|
||||
bitmapimage.Freeze();
|
||||
BitmapImage bitmapImage = new();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.StreamSource = wrappingStream;
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
memoryStream.Dispose();
|
||||
wrappingStream.Dispose();
|
||||
GC.Collect();
|
||||
return bitmapimage;
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
public static Language? GetOCRLanguage()
|
||||
|
||||
@@ -2,8 +2,19 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using PowerOCR.Models;
|
||||
using Windows.Globalization;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Media.Ocr;
|
||||
|
||||
namespace PowerOCR.Helpers
|
||||
@@ -50,5 +61,47 @@ namespace PowerOCR.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> GetRegionsTextAsTableAsync(Window passedWindow, Rectangle regionScaled, Language? language)
|
||||
{
|
||||
if (language is null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
Bitmap bmp = ImageMethods.GetRegionAsBitmap(passedWindow, regionScaled);
|
||||
|
||||
bool scaleBMP = true;
|
||||
|
||||
if (bmp.Width * 1.5 > OcrEngine.MaxImageDimension)
|
||||
{
|
||||
scaleBMP = false;
|
||||
}
|
||||
|
||||
using Bitmap scaledBitmap = scaleBMP ? ImageMethods.ScaleBitmapUniform(bmp, 1.5) : ImageMethods.ScaleBitmapUniform(bmp, 1.0);
|
||||
DpiScale dpiScale = VisualTreeHelper.GetDpi(passedWindow);
|
||||
|
||||
OcrResult ocrResult = await GetOcrResultFromImageAsync(scaledBitmap, language);
|
||||
List<WordBorder> wordBorders = ResultTable.ParseOcrResultIntoWordBorders(ocrResult, dpiScale);
|
||||
return ResultTable.GetWordsAsTable(wordBorders, dpiScale, LanguageHelper.IsLanguageSpaceJoining(language));
|
||||
}
|
||||
|
||||
internal static async Task<OcrResult> GetOcrResultFromImageAsync(Bitmap bmp, Language language)
|
||||
{
|
||||
await using MemoryStream memoryStream = new();
|
||||
using WrappingStream wrappingStream = new(memoryStream);
|
||||
|
||||
bmp.Save(wrappingStream, ImageFormat.Bmp);
|
||||
wrappingStream.Position = 0;
|
||||
|
||||
BitmapDecoder bmpDecoder = await BitmapDecoder.CreateAsync(wrappingStream.AsRandomAccessStream());
|
||||
SoftwareBitmap softwareBmp = await bmpDecoder.GetSoftwareBitmapAsync();
|
||||
|
||||
await memoryStream.DisposeAsync();
|
||||
await wrappingStream.DisposeAsync();
|
||||
|
||||
OcrEngine ocrEngine = OcrEngine.TryCreateFromLanguage(language);
|
||||
return await ocrEngine.RecognizeAsync(softwareBmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
src/modules/PowerOCR/PowerOCR/Helpers/StringHelpers.cs
Normal file
44
src/modules/PowerOCR/PowerOCR/Helpers/StringHelpers.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PowerOCR.Helpers;
|
||||
|
||||
internal static class StringHelpers
|
||||
{
|
||||
public static string MakeStringSingleLine(this string textToEdit)
|
||||
{
|
||||
if (!textToEdit.Contains('\n')
|
||||
&& !textToEdit.Contains('\r'))
|
||||
{
|
||||
return textToEdit;
|
||||
}
|
||||
|
||||
StringBuilder workingString = new(textToEdit);
|
||||
|
||||
workingString.Replace("\r\n", " ");
|
||||
workingString.Replace(Environment.NewLine, " ");
|
||||
workingString.Replace('\n', ' ');
|
||||
workingString.Replace('\r', ' ');
|
||||
|
||||
Regex regex = new("[ ]{2,}");
|
||||
string temp = regex.Replace(workingString.ToString(), " ");
|
||||
workingString.Clear();
|
||||
workingString.Append(temp);
|
||||
if (workingString[0] == ' ')
|
||||
{
|
||||
workingString.Remove(0, 1);
|
||||
}
|
||||
|
||||
if (workingString[workingString.Length - 1] == ' ')
|
||||
{
|
||||
workingString.Remove(workingString.Length - 1, 1);
|
||||
}
|
||||
|
||||
return workingString.ToString();
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,12 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using PowerOCR.Helpers;
|
||||
|
||||
namespace PowerOCR.Utilities;
|
||||
|
||||
@@ -20,33 +21,11 @@ public static class WindowUtilities
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Adding Overlays for each screen");
|
||||
foreach (Screen screen in Screen.AllScreens)
|
||||
{
|
||||
OCROverlay overlay = new OCROverlay()
|
||||
{
|
||||
WindowStartupLocation = WindowStartupLocation.Manual,
|
||||
Width = 200,
|
||||
Height = 200,
|
||||
WindowState = WindowState.Normal,
|
||||
};
|
||||
|
||||
if (screen.WorkingArea.Left >= 0)
|
||||
{
|
||||
overlay.Left = screen.WorkingArea.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
overlay.Left = screen.WorkingArea.Left + (screen.WorkingArea.Width / 2);
|
||||
}
|
||||
|
||||
if (screen.WorkingArea.Top >= 0)
|
||||
{
|
||||
overlay.Top = screen.WorkingArea.Top;
|
||||
}
|
||||
else
|
||||
{
|
||||
overlay.Top = screen.WorkingArea.Top + (screen.WorkingArea.Height / 2);
|
||||
}
|
||||
Logger.LogInfo($"screen {screen}");
|
||||
OCROverlay overlay = new(screen.Bounds);
|
||||
|
||||
overlay.Show();
|
||||
ActivateWindow(overlay);
|
||||
@@ -61,7 +40,7 @@ public static class WindowUtilities
|
||||
|
||||
foreach (Window window in allWindows)
|
||||
{
|
||||
if (window is OCROverlay overlay)
|
||||
if (window is OCROverlay)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -82,6 +61,8 @@ public static class WindowUtilities
|
||||
}
|
||||
}
|
||||
|
||||
GC.Collect();
|
||||
|
||||
// TODO: Decide when to close the process
|
||||
// System.Windows.Application.Current.Shutdown();
|
||||
}
|
||||
@@ -105,4 +86,23 @@ public static class WindowUtilities
|
||||
OSInterop.SetForegroundWindow(handle);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void OcrOverlayKeyDown(Key key, bool? isActive = null)
|
||||
{
|
||||
WindowCollection allWindows = System.Windows.Application.Current.Windows;
|
||||
|
||||
if (key == Key.Escape)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new PowerOCR.Telemetry.PowerOCRCancelledEvent());
|
||||
CloseAllOCROverlays();
|
||||
}
|
||||
|
||||
foreach (Window window in allWindows)
|
||||
{
|
||||
if (window is OCROverlay overlay)
|
||||
{
|
||||
overlay.KeyPressed(key, isActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
src/modules/PowerOCR/PowerOCR/Models/ResultColumn.cs
Normal file
16
src/modules/PowerOCR/PowerOCR/Models/ResultColumn.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace PowerOCR.Models;
|
||||
|
||||
public struct ResultColumn
|
||||
{
|
||||
public double Width { get; set; }
|
||||
|
||||
public double Left { get; set; }
|
||||
|
||||
public double Right { get; set; }
|
||||
|
||||
public int ID { get; set; }
|
||||
}
|
||||
16
src/modules/PowerOCR/PowerOCR/Models/ResultRow.cs
Normal file
16
src/modules/PowerOCR/PowerOCR/Models/ResultRow.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace PowerOCR.Models;
|
||||
|
||||
public struct ResultRow
|
||||
{
|
||||
public double Height { get; set; }
|
||||
|
||||
public double Top { get; set; }
|
||||
|
||||
public double Bottom { get; set; }
|
||||
|
||||
public int ID { get; set; }
|
||||
}
|
||||
642
src/modules/PowerOCR/PowerOCR/Models/ResultTable.cs
Normal file
642
src/modules/PowerOCR/PowerOCR/Models/ResultTable.cs
Normal file
@@ -0,0 +1,642 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using PowerOCR.Helpers;
|
||||
using Windows.Media.Ocr;
|
||||
using Rect = System.Windows.Rect;
|
||||
|
||||
namespace PowerOCR.Models;
|
||||
|
||||
public class ResultTable
|
||||
{
|
||||
public List<ResultColumn> Columns { get; set; } = new();
|
||||
|
||||
public List<ResultRow> Rows { get; set; } = new();
|
||||
|
||||
public Rect BoundingRect { get; set; }
|
||||
|
||||
public List<int> ColumnLines { get; set; } = new();
|
||||
|
||||
public List<int> RowLines { get; set; } = new();
|
||||
|
||||
public Canvas? TableLines { get; set; }
|
||||
|
||||
public ResultTable(ref List<WordBorder> wordBorders, DpiScale dpiScale)
|
||||
{
|
||||
int borderBuffer = 3;
|
||||
var leftsMin = wordBorders.Select(x => x.Left).Min();
|
||||
var topsMin = wordBorders.Select(x => x.Top).Min();
|
||||
var rightsMax = wordBorders.Select(x => x.Right).Max();
|
||||
var bottomsMax = wordBorders.Select(x => x.Bottom).Max();
|
||||
|
||||
Rectangle bordersBorder = new()
|
||||
{
|
||||
X = (int)leftsMin - borderBuffer,
|
||||
Y = (int)topsMin - borderBuffer,
|
||||
Width = (int)(rightsMax + borderBuffer),
|
||||
Height = (int)(bottomsMax + borderBuffer),
|
||||
};
|
||||
|
||||
bordersBorder.Width = (int)(bordersBorder.Width * dpiScale.DpiScaleX);
|
||||
bordersBorder.Height = (int)(bordersBorder.Height * dpiScale.DpiScaleY);
|
||||
|
||||
AnalyzeAsTable(wordBorders, bordersBorder);
|
||||
}
|
||||
|
||||
private void ParseRowAndColumnLines()
|
||||
{
|
||||
// Draw Bounding Rect
|
||||
int topBound = 0;
|
||||
int bottomBound = topBound;
|
||||
int leftBound = 0;
|
||||
int rightBound = leftBound;
|
||||
|
||||
if (Rows.Count >= 1)
|
||||
{
|
||||
topBound = (int)Rows[0].Top;
|
||||
bottomBound = (int)Rows[Rows.Count - 1].Bottom;
|
||||
}
|
||||
|
||||
if (Columns.Count >= 1)
|
||||
{
|
||||
leftBound = (int)Columns[0].Left;
|
||||
rightBound = (int)Columns[Columns.Count - 1].Right;
|
||||
}
|
||||
|
||||
BoundingRect = new()
|
||||
{
|
||||
Width = (rightBound - leftBound) + 10,
|
||||
Height = (bottomBound - topBound) + 10,
|
||||
X = leftBound - 5,
|
||||
Y = topBound - 5,
|
||||
};
|
||||
|
||||
// parse columns
|
||||
ColumnLines = new();
|
||||
|
||||
for (int i = 0; i < Columns.Count - 1; i++)
|
||||
{
|
||||
int columnMid = (int)(Columns[i].Right + Columns[i + 1].Left) / 2;
|
||||
ColumnLines.Add(columnMid);
|
||||
}
|
||||
|
||||
// parse rows
|
||||
RowLines = new();
|
||||
|
||||
for (int i = 0; i < Rows.Count - 1; i++)
|
||||
{
|
||||
int rowMid = (int)(Rows[i].Bottom + Rows[i + 1].Top) / 2;
|
||||
RowLines.Add(rowMid);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<WordBorder> ParseOcrResultIntoWordBorders(OcrResult ocrResult, DpiScale dpi)
|
||||
{
|
||||
List<WordBorder> wordBorders = new();
|
||||
int lineNumber = 0;
|
||||
|
||||
foreach (OcrLine ocrLine in ocrResult.Lines)
|
||||
{
|
||||
double top = ocrLine.Words.Select(x => x.BoundingRect.Top).Min();
|
||||
double bottom = ocrLine.Words.Select(x => x.BoundingRect.Bottom).Max();
|
||||
double left = ocrLine.Words.Select(x => x.BoundingRect.Left).Min();
|
||||
double right = ocrLine.Words.Select(x => x.BoundingRect.Right).Max();
|
||||
|
||||
Rect lineRect = new()
|
||||
{
|
||||
X = left,
|
||||
Y = top,
|
||||
Width = Math.Abs(right - left),
|
||||
Height = Math.Abs(bottom - top),
|
||||
};
|
||||
|
||||
StringBuilder lineText = new();
|
||||
ocrLine.GetTextFromOcrLine(true, lineText);
|
||||
|
||||
WordBorder wordBorderBox = new()
|
||||
{
|
||||
Width = lineRect.Width / dpi.DpiScaleX,
|
||||
Height = lineRect.Height / dpi.DpiScaleY,
|
||||
Top = lineRect.Y,
|
||||
Left = lineRect.X,
|
||||
Word = lineText.ToString().Trim(),
|
||||
LineNumber = lineNumber,
|
||||
};
|
||||
wordBorders.Add(wordBorderBox);
|
||||
|
||||
lineNumber++;
|
||||
}
|
||||
|
||||
return wordBorders;
|
||||
}
|
||||
|
||||
public void AnalyzeAsTable(ICollection<WordBorder> wordBorders, Rectangle rectCanvasSize)
|
||||
{
|
||||
int hitGridSpacing = 3;
|
||||
|
||||
int numberOfVerticalLines = rectCanvasSize.Width / hitGridSpacing;
|
||||
int numberOfHorizontalLines = rectCanvasSize.Height / hitGridSpacing;
|
||||
|
||||
Canvas tableIntersectionCanvas = new();
|
||||
|
||||
List<int> rowAreas = CalculateRowAreas(rectCanvasSize, hitGridSpacing, numberOfHorizontalLines, tableIntersectionCanvas, wordBorders);
|
||||
List<ResultRow> resultRows = CalculateResultRows(hitGridSpacing, rowAreas);
|
||||
|
||||
List<int> columnAreas = CalculateColumnAreas(rectCanvasSize, hitGridSpacing, numberOfVerticalLines, tableIntersectionCanvas, wordBorders);
|
||||
List<ResultColumn> resultColumns = CalculateResultColumns(hitGridSpacing, columnAreas);
|
||||
|
||||
Rect tableBoundingRect = new()
|
||||
{
|
||||
X = columnAreas.FirstOrDefault(),
|
||||
Y = rowAreas.FirstOrDefault(),
|
||||
Width = columnAreas.LastOrDefault() - columnAreas.FirstOrDefault(),
|
||||
Height = rowAreas.LastOrDefault() - rowAreas.FirstOrDefault(),
|
||||
};
|
||||
|
||||
CombineOutliers(wordBorders, resultRows, tableIntersectionCanvas, resultColumns, tableBoundingRect);
|
||||
|
||||
Rows.Clear();
|
||||
Rows.AddRange(resultRows);
|
||||
Columns.Clear();
|
||||
Columns.AddRange(resultColumns);
|
||||
|
||||
ParseRowAndColumnLines();
|
||||
DrawTable();
|
||||
}
|
||||
|
||||
private static List<ResultRow> CalculateResultRows(int hitGridSpacing, List<int> rowAreas)
|
||||
{
|
||||
List<ResultRow> resultRows = new();
|
||||
int rowTop = 0;
|
||||
int rowCount = 0;
|
||||
for (int i = 0; i < rowAreas.Count; i++)
|
||||
{
|
||||
int thisLine = rowAreas[i];
|
||||
|
||||
// check if should set this as top
|
||||
if (i == 0)
|
||||
{
|
||||
rowTop = thisLine;
|
||||
}
|
||||
else if (i - 1 > 0)
|
||||
{
|
||||
int prevRow = rowAreas[i - 1];
|
||||
if (thisLine - prevRow != hitGridSpacing)
|
||||
{
|
||||
rowTop = thisLine;
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if at bottom of row
|
||||
if (i == rowAreas.Count - 1)
|
||||
{
|
||||
resultRows.Add(new ResultRow { Top = rowTop, Bottom = thisLine, ID = rowCount });
|
||||
rowCount++;
|
||||
}
|
||||
else if (i + 1 < rowAreas.Count)
|
||||
{
|
||||
int nextRow = rowAreas[i + 1];
|
||||
if (nextRow - thisLine != hitGridSpacing)
|
||||
{
|
||||
resultRows.Add(new ResultRow { Top = rowTop, Bottom = thisLine, ID = rowCount });
|
||||
rowCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resultRows;
|
||||
}
|
||||
|
||||
private static List<int> CalculateRowAreas(Rectangle rectCanvasSize, int hitGridSpacing, int numberOfHorizontalLines, Canvas tableIntersectionCanvas, ICollection<WordBorder> wordBorders)
|
||||
{
|
||||
List<int> rowAreas = new();
|
||||
|
||||
for (int i = 0; i < numberOfHorizontalLines; i++)
|
||||
{
|
||||
Border horizontalLine = new()
|
||||
{
|
||||
Height = 1,
|
||||
Width = rectCanvasSize.Width,
|
||||
Opacity = 0,
|
||||
Background = new SolidColorBrush(Colors.Gray),
|
||||
};
|
||||
Rect horizontalLineRect = new(0, i * hitGridSpacing, horizontalLine.Width, horizontalLine.Height);
|
||||
_ = tableIntersectionCanvas.Children.Add(horizontalLine);
|
||||
Canvas.SetTop(horizontalLine, i * 3);
|
||||
|
||||
CheckIntersectionsWithWordBorders(hitGridSpacing, wordBorders, rowAreas, i, horizontalLineRect);
|
||||
}
|
||||
|
||||
return rowAreas;
|
||||
}
|
||||
|
||||
private static void CheckIntersectionsWithWordBorders(int hitGridSpacing, ICollection<WordBorder> wordBorders, ICollection<int> rowAreas, int i, Rect horizontalLineRect)
|
||||
{
|
||||
foreach (WordBorder wb in wordBorders)
|
||||
{
|
||||
if (wb.IntersectsWith(horizontalLineRect))
|
||||
{
|
||||
rowAreas.Add(i * hitGridSpacing);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CombineOutliers(ICollection<WordBorder> wordBorders, List<ResultRow> resultRows, Canvas tableIntersectionCanvas, List<ResultColumn> resultColumns, Rect tableBoundingRect)
|
||||
{
|
||||
// try 4 times to refine the rows and columns for outliers
|
||||
// on the fifth time set the word boundary properties
|
||||
for (int r = 0; r < 5; r++)
|
||||
{
|
||||
int outlierThreshold = 2;
|
||||
List<int> outlierRowIDs = FindOutlierRowIds(wordBorders, resultRows, tableIntersectionCanvas, tableBoundingRect, r, outlierThreshold);
|
||||
|
||||
if (outlierRowIDs.Count > 0)
|
||||
{
|
||||
MergeTheseRowIDs(resultRows, outlierRowIDs);
|
||||
}
|
||||
|
||||
List<int> outlierColumnIDs = FindOutlierColumnIds(wordBorders, tableIntersectionCanvas, resultColumns, tableBoundingRect, outlierThreshold);
|
||||
|
||||
if (outlierColumnIDs.Count > 0 && r != 4)
|
||||
{
|
||||
MergeTheseColumnIDs(resultColumns, outlierColumnIDs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<int> FindOutlierRowIds(
|
||||
ICollection<WordBorder> wordBorders,
|
||||
ICollection<ResultRow> resultRows,
|
||||
Canvas tableIntersectionCanvas,
|
||||
Rect tableBoundingRect,
|
||||
int r,
|
||||
int outlierThreshold)
|
||||
{
|
||||
List<int> outlierRowIDs = new();
|
||||
|
||||
foreach (ResultRow row in resultRows)
|
||||
{
|
||||
int numberOfIntersectingWords = 0;
|
||||
Border rowBorder = new()
|
||||
{
|
||||
Height = row.Bottom - row.Top,
|
||||
Width = tableBoundingRect.Width,
|
||||
Background = new SolidColorBrush(Colors.Red),
|
||||
Tag = row.ID,
|
||||
};
|
||||
tableIntersectionCanvas.Children.Add(rowBorder);
|
||||
Canvas.SetLeft(rowBorder, tableBoundingRect.X);
|
||||
Canvas.SetTop(rowBorder, row.Top);
|
||||
|
||||
Rect rowRect = new(tableBoundingRect.X, row.Top, rowBorder.Width, rowBorder.Height);
|
||||
|
||||
foreach (WordBorder wb in wordBorders)
|
||||
{
|
||||
if (wb.IntersectsWith(rowRect))
|
||||
{
|
||||
numberOfIntersectingWords++;
|
||||
wb.ResultRowID = row.ID;
|
||||
}
|
||||
}
|
||||
|
||||
if (numberOfIntersectingWords <= outlierThreshold && r != 4)
|
||||
{
|
||||
outlierRowIDs.Add(row.ID);
|
||||
}
|
||||
}
|
||||
|
||||
return outlierRowIDs;
|
||||
}
|
||||
|
||||
private static List<int> FindOutlierColumnIds(
|
||||
ICollection<WordBorder> wordBorders,
|
||||
Canvas tableIntersectionCanvas,
|
||||
List<ResultColumn> resultColumns,
|
||||
Rect tableBoundingRect,
|
||||
int outlierThreshold)
|
||||
{
|
||||
List<int> outlierColumnIDs = new();
|
||||
|
||||
foreach (ResultColumn column in resultColumns)
|
||||
{
|
||||
int numberOfIntersectingWords = 0;
|
||||
Border columnBorder = new()
|
||||
{
|
||||
Height = tableBoundingRect.Height,
|
||||
Width = column.Right - column.Left,
|
||||
Background = new SolidColorBrush(Colors.Blue),
|
||||
Opacity = 0.2,
|
||||
Tag = column.ID,
|
||||
};
|
||||
tableIntersectionCanvas.Children.Add(columnBorder);
|
||||
Canvas.SetLeft(columnBorder, column.Left);
|
||||
Canvas.SetTop(columnBorder, tableBoundingRect.Y);
|
||||
|
||||
Rect columnRect = new(column.Left, tableBoundingRect.Y, columnBorder.Width, columnBorder.Height);
|
||||
foreach (WordBorder wb in wordBorders)
|
||||
{
|
||||
if (wb.IntersectsWith(columnRect))
|
||||
{
|
||||
numberOfIntersectingWords++;
|
||||
wb.ResultColumnID = column.ID;
|
||||
}
|
||||
}
|
||||
|
||||
if (numberOfIntersectingWords <= outlierThreshold)
|
||||
{
|
||||
outlierColumnIDs.Add(column.ID);
|
||||
}
|
||||
}
|
||||
|
||||
return outlierColumnIDs;
|
||||
}
|
||||
|
||||
private static List<ResultColumn> CalculateResultColumns(int hitGridSpacing, List<int> columnAreas)
|
||||
{
|
||||
List<ResultColumn> resultColumns = new();
|
||||
int columnLeft = 0;
|
||||
int columnCount = 0;
|
||||
for (int i = 0; i < columnAreas.Count; i++)
|
||||
{
|
||||
int thisLine = columnAreas[i];
|
||||
|
||||
// check if should set this as top
|
||||
if (i == 0)
|
||||
{
|
||||
columnLeft = thisLine;
|
||||
}
|
||||
else if (i - 1 > 0)
|
||||
{
|
||||
int prevColumn = columnAreas[i - 1];
|
||||
if (thisLine - prevColumn != hitGridSpacing)
|
||||
{
|
||||
columnLeft = thisLine;
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if at last Column
|
||||
if (i == columnAreas.Count - 1)
|
||||
{
|
||||
resultColumns.Add(new ResultColumn { Left = columnLeft, Right = thisLine, ID = columnCount });
|
||||
columnCount++;
|
||||
}
|
||||
else if (i + 1 < columnAreas.Count)
|
||||
{
|
||||
int nextColumn = columnAreas[i + 1];
|
||||
if (nextColumn - thisLine != hitGridSpacing)
|
||||
{
|
||||
resultColumns.Add(new ResultColumn { Left = columnLeft, Right = thisLine, ID = columnCount });
|
||||
columnCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resultColumns;
|
||||
}
|
||||
|
||||
private static List<int> CalculateColumnAreas(Rectangle rectCanvasSize, int hitGridSpacing, int numberOfVerticalLines, Canvas tableIntersectionCanvas, ICollection<WordBorder> wordBorders)
|
||||
{
|
||||
List<int> columnAreas = new();
|
||||
for (int i = 0; i < numberOfVerticalLines; i++)
|
||||
{
|
||||
Border vertLine = new()
|
||||
{
|
||||
Height = rectCanvasSize.Height,
|
||||
Width = 1,
|
||||
Opacity = 0,
|
||||
Background = new SolidColorBrush(Colors.Gray),
|
||||
};
|
||||
_ = tableIntersectionCanvas.Children.Add(vertLine);
|
||||
Canvas.SetLeft(vertLine, i * hitGridSpacing);
|
||||
|
||||
Rect vertLineRect = new(i * hitGridSpacing, 0, vertLine.Width, vertLine.Height);
|
||||
|
||||
foreach (WordBorder wb in wordBorders)
|
||||
{
|
||||
if (wb.IntersectsWith(vertLineRect))
|
||||
{
|
||||
columnAreas.Add(i * hitGridSpacing);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return columnAreas;
|
||||
}
|
||||
|
||||
private static void MergeTheseColumnIDs(List<ResultColumn> resultColumns, List<int> outlierColumnIDs)
|
||||
{
|
||||
for (int i = 0; i < outlierColumnIDs.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < resultColumns.Count; j++)
|
||||
{
|
||||
ResultColumn column = resultColumns[j];
|
||||
if (column.ID == outlierColumnIDs[i])
|
||||
{
|
||||
if (j == 0)
|
||||
{
|
||||
// merge with next column if possible
|
||||
if (j + 1 < resultColumns.Count)
|
||||
{
|
||||
ResultColumn nextColumn = resultColumns[j + 1];
|
||||
nextColumn.Left = column.Left;
|
||||
}
|
||||
}
|
||||
else if (j == resultColumns.Count - 1)
|
||||
{
|
||||
// merge with previous column
|
||||
if (j - 1 >= 0)
|
||||
{
|
||||
ResultColumn prevColumn = resultColumns[j - 1];
|
||||
prevColumn.Right = column.Right;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// merge with closet column
|
||||
ResultColumn prevColumn = resultColumns[j - 1];
|
||||
ResultColumn nextColumn = resultColumns[j + 1];
|
||||
int distanceToPrev = (int)(column.Left - prevColumn.Right);
|
||||
int distanceToNext = (int)(nextColumn.Left - column.Right);
|
||||
|
||||
if (distanceToNext < distanceToPrev)
|
||||
{
|
||||
// merge with next column
|
||||
nextColumn.Left = column.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
// merge with prev column
|
||||
prevColumn.Right = column.Right;
|
||||
}
|
||||
}
|
||||
|
||||
resultColumns.RemoveAt(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetTextFromTabledWordBorders(StringBuilder stringBuilder, List<WordBorder> wordBorders, bool isSpaceJoining)
|
||||
{
|
||||
List<WordBorder>? selectedBorders = wordBorders.Where(w => w.IsSelected).ToList();
|
||||
|
||||
if (selectedBorders.Count == 0)
|
||||
{
|
||||
selectedBorders.AddRange(wordBorders);
|
||||
}
|
||||
|
||||
List<string> lineList = new();
|
||||
int? lastLineNum = 0;
|
||||
int lastColumnNum = 0;
|
||||
|
||||
if (selectedBorders.FirstOrDefault() != null)
|
||||
{
|
||||
lastLineNum = selectedBorders.FirstOrDefault()!.LineNumber;
|
||||
}
|
||||
|
||||
selectedBorders = selectedBorders.OrderBy(x => x.ResultColumnID).ToList();
|
||||
selectedBorders = selectedBorders.OrderBy(x => x.ResultRowID).ToList();
|
||||
|
||||
int numberOfDistinctRows = selectedBorders.Select(x => x.ResultRowID).Distinct().Count();
|
||||
|
||||
foreach (WordBorder border in selectedBorders)
|
||||
{
|
||||
if (lineList.Count == 0)
|
||||
{
|
||||
lastLineNum = border.ResultRowID;
|
||||
}
|
||||
|
||||
if (border.ResultRowID != lastLineNum)
|
||||
{
|
||||
if (isSpaceJoining)
|
||||
{
|
||||
stringBuilder.Append(string.Join(' ', lineList));
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(string.Join(string.Empty, lineList));
|
||||
}
|
||||
|
||||
stringBuilder.Replace(" \t ", "\t");
|
||||
stringBuilder.Replace("\t ", "\t");
|
||||
stringBuilder.Replace(" \t", "\t");
|
||||
stringBuilder.Append(Environment.NewLine);
|
||||
lineList.Clear();
|
||||
lastLineNum = border.ResultRowID;
|
||||
}
|
||||
|
||||
if (border.ResultColumnID != lastColumnNum && numberOfDistinctRows > 1)
|
||||
{
|
||||
string borderWord = border.Word;
|
||||
int numberOfOffColumns = border.ResultColumnID - lastColumnNum;
|
||||
if (numberOfOffColumns < 0)
|
||||
{
|
||||
lastColumnNum = 0;
|
||||
}
|
||||
|
||||
numberOfOffColumns = border.ResultColumnID - lastColumnNum;
|
||||
|
||||
if (numberOfOffColumns > 0)
|
||||
{
|
||||
lineList.Add(new string('\t', numberOfOffColumns));
|
||||
}
|
||||
}
|
||||
|
||||
lastColumnNum = border.ResultColumnID;
|
||||
|
||||
lineList.Add(border.Word);
|
||||
}
|
||||
|
||||
stringBuilder.Append(string.Join(string.Empty, lineList));
|
||||
}
|
||||
|
||||
private static void MergeTheseRowIDs(List<ResultRow> resultRows, List<int> outlierRowIDs)
|
||||
{
|
||||
}
|
||||
|
||||
private void DrawTable()
|
||||
{
|
||||
// Draw the lines and bounds of the table
|
||||
SolidColorBrush tableColor = new(System.Windows.Media.Color.FromArgb(255, 40, 118, 126));
|
||||
|
||||
TableLines = new Canvas()
|
||||
{
|
||||
Tag = "TableLines",
|
||||
};
|
||||
|
||||
Border tableOutline = new()
|
||||
{
|
||||
Width = this.BoundingRect.Width,
|
||||
Height = this.BoundingRect.Height,
|
||||
BorderThickness = new Thickness(3),
|
||||
BorderBrush = tableColor,
|
||||
};
|
||||
TableLines.Children.Add(tableOutline);
|
||||
Canvas.SetTop(tableOutline, this.BoundingRect.Y);
|
||||
Canvas.SetLeft(tableOutline, this.BoundingRect.X);
|
||||
|
||||
foreach (int columnLine in this.ColumnLines)
|
||||
{
|
||||
Border vertLine = new()
|
||||
{
|
||||
Width = 2,
|
||||
Height = this.BoundingRect.Height,
|
||||
Background = tableColor,
|
||||
};
|
||||
TableLines.Children.Add(vertLine);
|
||||
Canvas.SetTop(vertLine, this.BoundingRect.Y);
|
||||
Canvas.SetLeft(vertLine, columnLine);
|
||||
}
|
||||
|
||||
foreach (int rowLine in this.RowLines)
|
||||
{
|
||||
Border horizontalLine = new()
|
||||
{
|
||||
Height = 2,
|
||||
Width = this.BoundingRect.Width,
|
||||
Background = tableColor,
|
||||
};
|
||||
TableLines.Children.Add(horizontalLine);
|
||||
Canvas.SetTop(horizontalLine, rowLine);
|
||||
Canvas.SetLeft(horizontalLine, this.BoundingRect.X);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetWordsAsTable(List<WordBorder> wordBorders, DpiScale dpiScale, bool isSpaceJoining)
|
||||
{
|
||||
List<WordBorder> smallerBorders = new();
|
||||
foreach (WordBorder originalWB in wordBorders)
|
||||
{
|
||||
WordBorder newWB = new()
|
||||
{
|
||||
Word = originalWB.Word,
|
||||
Left = originalWB.Left,
|
||||
Top = originalWB.Top,
|
||||
Width = originalWB.Width > 10 ? originalWB.Width - 6 : originalWB.Width,
|
||||
Height = originalWB.Height > 10 ? originalWB.Height - 6 : originalWB.Height,
|
||||
ResultRowID = originalWB.ResultRowID,
|
||||
ResultColumnID = originalWB.ResultColumnID,
|
||||
};
|
||||
smallerBorders.Add(newWB);
|
||||
}
|
||||
|
||||
ResultTable resultTable = new(ref smallerBorders, dpiScale);
|
||||
StringBuilder stringBuilder = new();
|
||||
GetTextFromTabledWordBorders(
|
||||
stringBuilder,
|
||||
smallerBorders,
|
||||
isSpaceJoining);
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
42
src/modules/PowerOCR/PowerOCR/Models/WordBorder.cs
Normal file
42
src/modules/PowerOCR/PowerOCR/Models/WordBorder.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Windows;
|
||||
|
||||
namespace PowerOCR.Models;
|
||||
|
||||
public class WordBorder
|
||||
{
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public string Word { get; set; } = string.Empty;
|
||||
|
||||
public double Top { get; set; }
|
||||
|
||||
public double Left { get; set; }
|
||||
|
||||
public double Width { get; set; }
|
||||
|
||||
public double Height { get; set; }
|
||||
|
||||
public int LineNumber { get; set; }
|
||||
|
||||
public double Right => Left + Width;
|
||||
|
||||
public double Bottom => Top + Height;
|
||||
|
||||
public int ResultRowID { get; set; }
|
||||
|
||||
public int ResultColumnID { get; set; }
|
||||
|
||||
public Rect AsRect()
|
||||
{
|
||||
return new Rect(Left, Top, Width, Height);
|
||||
}
|
||||
|
||||
public bool IntersectsWith(Rect rect)
|
||||
{
|
||||
return rect.IntersectsWith(AsRect());
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,40 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:PowerOCR"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
Title="TextExtractor"
|
||||
Width="800"
|
||||
Height="450"
|
||||
ShowActivated="False"
|
||||
ShowInTaskbar="False"
|
||||
Width="200"
|
||||
Height="200"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
Loaded="Window_Loaded"
|
||||
Unloaded="Window_Unloaded"
|
||||
ResizeMode="NoResize"
|
||||
ShowActivated="False"
|
||||
ShowInTaskbar="False"
|
||||
Topmost="True"
|
||||
Unloaded="Window_Unloaded"
|
||||
WindowStartupLocation="Manual"
|
||||
WindowState="Normal"
|
||||
WindowStyle="None"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Window.Resources>
|
||||
<Style x:Key="SymbolTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="Segoe MDL2 Assets" />
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="Margin" Value="4" />
|
||||
</Style>
|
||||
<Style TargetType="ToggleButton">
|
||||
<Setter Property="Margin" Value="2,0" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="Width" Value="30" />
|
||||
<Setter Property="Height" Value="30" />
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Viewbox>
|
||||
<Image x:Name="BackgroundImage" Stretch="UniformToFill" />
|
||||
@@ -45,8 +66,115 @@
|
||||
Color="Black" />
|
||||
</Canvas.Background>
|
||||
<Canvas.ContextMenu>
|
||||
<ContextMenu x:Name="CanvasContextMenu" />
|
||||
<ContextMenu x:Name="CanvasContextMenu">
|
||||
<MenuItem
|
||||
Name="SingleLineMenuItem"
|
||||
Click="SingleLineMenuItem_Click"
|
||||
Header="Make Result Text Single Line"
|
||||
IsCheckable="True" />
|
||||
<MenuItem
|
||||
Name="TableMenuItem"
|
||||
Click="TableToggleButton_Click"
|
||||
Header="OCR text as a table"
|
||||
IsCheckable="True" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Name="SettingsMenuItem"
|
||||
Click="SettingsMenuItem_Click"
|
||||
Header="Settings" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Name="CancelMenuItem"
|
||||
Click="CancelMenuItem_Click"
|
||||
Header="Cancel" />
|
||||
</ContextMenu>
|
||||
</Canvas.ContextMenu>
|
||||
</Canvas>
|
||||
<Border
|
||||
x:Name="TopButtonsStackPanel"
|
||||
Margin="12"
|
||||
Padding="4,8,12,8"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
d:Background="White"
|
||||
d:Visibility="Visible"
|
||||
Background="{DynamicResource ApplicationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Visibility="Collapsed">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect
|
||||
BlurRadius="32"
|
||||
Direction="-90"
|
||||
Opacity="0.6"
|
||||
RenderingBias="Performance" />
|
||||
</Border.Effect>
|
||||
<StackPanel
|
||||
Margin="2,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Horizontal">
|
||||
<ComboBox
|
||||
x:Name="LanguagesComboBox"
|
||||
Margin="2,0"
|
||||
Padding="4,2,0,2"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
SelectionChanged="LanguagesComboBox_SelectionChanged">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="Segoe UI"
|
||||
Style="{StaticResource SymbolTextStyle}"
|
||||
Text="{Binding NativeName}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<ToggleButton
|
||||
x:Name="SingleLineToggleButton"
|
||||
Width="34"
|
||||
Height="34"
|
||||
Margin="2,0"
|
||||
d:IsChecked="True"
|
||||
Click="SingleLineMenuItem_Click"
|
||||
IsChecked="{Binding IsChecked, ElementName=SingleLineMenuItem, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSymbolButton}"
|
||||
ToolTip="(S) Make result a single line">
|
||||
<TextBlock Style="{StaticResource SymbolTextStyle}" Text="" />
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
x:Name="TableToggleButton"
|
||||
Width="34"
|
||||
Height="34"
|
||||
Margin="2,0"
|
||||
d:IsChecked="True"
|
||||
Click="TableToggleButton_Click"
|
||||
IsChecked="{Binding IsChecked, ElementName=TableMenuItem, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSymbolButton}"
|
||||
ToolTip="(T) OCR text as a table">
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" />
|
||||
</ToggleButton>
|
||||
<Button
|
||||
x:Name="SettingsButton"
|
||||
Width="34"
|
||||
Height="34"
|
||||
Margin="2,0"
|
||||
Click="SettingsMenuItem_Click"
|
||||
Style="{StaticResource SymbolButton}"
|
||||
ToolTip="Settings">
|
||||
<TextBlock Style="{StaticResource SymbolTextStyle}" Text="" />
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="CancelButton"
|
||||
Width="34"
|
||||
Height="34"
|
||||
Margin="2,0,0,0"
|
||||
Click="CancelMenuItem_Click"
|
||||
Style="{StaticResource SymbolButton}"
|
||||
ToolTip="(Esc) Cancel">
|
||||
<TextBlock Style="{StaticResource SymbolTextStyle}" Text="" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -7,8 +7,10 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Common.UI;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using PowerOCR.Helpers;
|
||||
@@ -27,37 +29,32 @@ public partial class OCROverlay : Window
|
||||
private bool isShiftDown;
|
||||
private Point clickedPoint;
|
||||
private Point shiftPoint;
|
||||
private Border selectBorder = new();
|
||||
private Language? selectedLanguage;
|
||||
|
||||
private bool IsSelecting { get; set; }
|
||||
|
||||
private Border selectBorder = new();
|
||||
|
||||
private DpiScale? dpiScale;
|
||||
|
||||
private Point GetMousePos() => PointToScreen(Mouse.GetPosition(this));
|
||||
|
||||
private Language? selectedLanguage;
|
||||
private MenuItem cancelMenuItem;
|
||||
|
||||
private System.Windows.Forms.Screen? CurrentScreen
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private double selectLeft;
|
||||
private double selectTop;
|
||||
|
||||
private double xShiftDelta;
|
||||
private double yShiftDelta;
|
||||
|
||||
private bool isComboBoxReady;
|
||||
private const double ActiveOpacity = 0.4;
|
||||
private readonly UserSettings userSettings = new(new ThrottledActionInvoker());
|
||||
|
||||
public OCROverlay()
|
||||
public OCROverlay(System.Drawing.Rectangle screenRectangle)
|
||||
{
|
||||
Left = screenRectangle.Left >= 0 ? screenRectangle.Left : screenRectangle.Left + (screenRectangle.Width / 2);
|
||||
Top = screenRectangle.Top >= 0 ? screenRectangle.Top : screenRectangle.Top + (screenRectangle.Height / 2);
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
var userSettings = new UserSettings(new ThrottledActionInvoker());
|
||||
PopulateLanguageMenu();
|
||||
}
|
||||
|
||||
private void PopulateLanguageMenu()
|
||||
{
|
||||
string? selectedLanguageName = userSettings.PreferredLanguage.Value;
|
||||
|
||||
// build context menu
|
||||
@@ -68,25 +65,26 @@ public partial class OCROverlay : Window
|
||||
}
|
||||
|
||||
List<Language> possibleOcrLanguages = OcrEngine.AvailableRecognizerLanguages.ToList();
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (Language language in possibleOcrLanguages)
|
||||
{
|
||||
MenuItem menuItem = new() { Header = language.NativeName, Tag = language, IsCheckable = true };
|
||||
menuItem.IsChecked = language.DisplayName.Equals(selectedLanguageName, StringComparison.Ordinal);
|
||||
LanguagesComboBox.Items.Add(language);
|
||||
if (language.DisplayName.Equals(selectedLanguageName, StringComparison.Ordinal))
|
||||
{
|
||||
selectedLanguage = language;
|
||||
LanguagesComboBox.SelectedIndex = count;
|
||||
}
|
||||
|
||||
menuItem.Click += LanguageMenuItem_Click;
|
||||
CanvasContextMenu.Items.Add(menuItem);
|
||||
count++;
|
||||
}
|
||||
|
||||
CanvasContextMenu.Items.Add(new Separator());
|
||||
|
||||
// ResourceLoader resourceLoader = ResourceLoader.GetForViewIndependentUse(); // resourceLoader.GetString("TextExtractor_Cancel")
|
||||
cancelMenuItem = new MenuItem() { Header = "cancel" };
|
||||
cancelMenuItem.Click += CancelMenuItem_Click;
|
||||
CanvasContextMenu.Items.Add(cancelMenuItem);
|
||||
isComboBoxReady = true;
|
||||
}
|
||||
|
||||
private void LanguageMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
@@ -101,6 +99,7 @@ public partial class OCROverlay : Window
|
||||
}
|
||||
|
||||
selectedLanguage = menuItem.Tag as Language;
|
||||
LanguagesComboBox.SelectedItem = selectedLanguage;
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
@@ -112,6 +111,12 @@ public partial class OCROverlay : Window
|
||||
|
||||
BackgroundImage.Source = ImageMethods.GetWindowBoundsImage(this);
|
||||
BackgroundBrush.Opacity = ActiveOpacity;
|
||||
|
||||
TopButtonsStackPanel.Visibility = Visibility.Visible;
|
||||
|
||||
#if DEBUG
|
||||
Topmost = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Window_Unloaded(object sender, RoutedEventArgs e)
|
||||
@@ -119,9 +124,6 @@ public partial class OCROverlay : Window
|
||||
BackgroundImage.Source = null;
|
||||
BackgroundImage.UpdateLayout();
|
||||
|
||||
CurrentScreen = null;
|
||||
dpiScale = null;
|
||||
|
||||
KeyDown -= MainWindow_KeyDown;
|
||||
KeyUp -= MainWindow_KeyUp;
|
||||
|
||||
@@ -131,8 +133,6 @@ public partial class OCROverlay : Window
|
||||
RegionClickCanvas.MouseDown -= RegionClickCanvas_MouseDown;
|
||||
RegionClickCanvas.MouseUp -= RegionClickCanvas_MouseUp;
|
||||
RegionClickCanvas.MouseMove -= RegionClickCanvas_MouseMove;
|
||||
|
||||
cancelMenuItem.Click -= CancelMenuItem_Click;
|
||||
}
|
||||
|
||||
private void MainWindow_KeyUp(object sender, KeyEventArgs e)
|
||||
@@ -151,15 +151,7 @@ public partial class OCROverlay : Window
|
||||
|
||||
private void MainWindow_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Escape:
|
||||
WindowUtilities.CloseAllOCROverlays();
|
||||
PowerToysTelemetry.Log.WriteEvent(new PowerOCR.Telemetry.PowerOCRCancelledEvent());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
WindowUtilities.OcrOverlayKeyDown(e.Key);
|
||||
}
|
||||
|
||||
private void RegionClickCanvas_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
@@ -169,6 +161,7 @@ public partial class OCROverlay : Window
|
||||
return;
|
||||
}
|
||||
|
||||
TopButtonsStackPanel.Visibility = Visibility.Collapsed;
|
||||
RegionClickCanvas.CaptureMouse();
|
||||
|
||||
CursorClipper.ClipCursor(this);
|
||||
@@ -176,8 +169,6 @@ public partial class OCROverlay : Window
|
||||
selectBorder.Height = 1;
|
||||
selectBorder.Width = 1;
|
||||
|
||||
dpiScale = VisualTreeHelper.GetDpi(this);
|
||||
|
||||
try
|
||||
{
|
||||
RegionClickCanvas.Children.Remove(selectBorder);
|
||||
@@ -193,17 +184,6 @@ public partial class OCROverlay : Window
|
||||
Canvas.SetLeft(selectBorder, clickedPoint.X);
|
||||
Canvas.SetTop(selectBorder, clickedPoint.Y);
|
||||
|
||||
var screens = System.Windows.Forms.Screen.AllScreens;
|
||||
System.Drawing.Point formsPoint = new((int)clickedPoint.X, (int)clickedPoint.Y);
|
||||
foreach (var scr in screens)
|
||||
{
|
||||
if (scr.Bounds.Contains(formsPoint))
|
||||
{
|
||||
CurrentScreen = scr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IsSelecting = true;
|
||||
}
|
||||
|
||||
@@ -232,18 +212,6 @@ public partial class OCROverlay : Window
|
||||
double leftValue = selectLeft + xShiftDelta;
|
||||
double topValue = selectTop + yShiftDelta;
|
||||
|
||||
if (CurrentScreen is not null && dpiScale is not null)
|
||||
{
|
||||
double currentScreenLeft = CurrentScreen.Bounds.Left; // Should always be 0
|
||||
double currentScreenRight = CurrentScreen.Bounds.Right / dpiScale.Value.DpiScaleX;
|
||||
double currentScreenTop = CurrentScreen.Bounds.Top; // Should always be 0
|
||||
double currentScreenBottom = CurrentScreen.Bounds.Bottom / dpiScale.Value.DpiScaleY;
|
||||
|
||||
// this is giving issues on different monitors
|
||||
// leftValue = Math.Clamp(leftValue, currentScreenLeft, currentScreenRight - selectBorder.Width);
|
||||
// topValue = Math.Clamp(topValue, currentScreenTop, currentScreenBottom - selectBorder.Height);
|
||||
}
|
||||
|
||||
clippingGeometry.Rect = new Rect(
|
||||
new Point(leftValue, topValue),
|
||||
new Size(selectBorder.Width, selectBorder.Height));
|
||||
@@ -276,14 +244,13 @@ public partial class OCROverlay : Window
|
||||
return;
|
||||
}
|
||||
|
||||
TopButtonsStackPanel.Visibility = Visibility.Visible;
|
||||
IsSelecting = false;
|
||||
|
||||
CurrentScreen = null;
|
||||
CursorClipper.UnClipCursor();
|
||||
RegionClickCanvas.ReleaseMouseCapture();
|
||||
Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
|
||||
|
||||
Point mPt = GetMousePos();
|
||||
Point movingPoint = e.GetPosition(this);
|
||||
movingPoint.X *= m.M11;
|
||||
movingPoint.Y *= m.M22;
|
||||
@@ -313,15 +280,36 @@ public partial class OCROverlay : Window
|
||||
|
||||
if (regionScaled.Width < 3 || regionScaled.Height < 3)
|
||||
{
|
||||
BackgroundBrush.Opacity = 0;
|
||||
Logger.LogInfo($"Getting clicked word, {selectedLanguage?.LanguageTag}");
|
||||
grabbedText = await ImageMethods.GetClickedWord(this, new Point(xDimScaled, yDimScaled), selectedLanguage);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TableMenuItem.IsChecked)
|
||||
{
|
||||
Logger.LogInfo($"Getting region as table, {selectedLanguage?.LanguageTag}");
|
||||
grabbedText = await OcrExtensions.GetRegionsTextAsTableAsync(this, regionScaled, selectedLanguage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo($"Standard region capture, {selectedLanguage?.LanguageTag}");
|
||||
grabbedText = await ImageMethods.GetRegionsText(this, regionScaled, selectedLanguage);
|
||||
|
||||
if (SingleLineMenuItem.IsChecked)
|
||||
{
|
||||
Logger.LogInfo($"Making grabbed text single line");
|
||||
grabbedText = grabbedText.MakeStringSingleLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(grabbedText) == false)
|
||||
if (string.IsNullOrWhiteSpace(grabbedText))
|
||||
{
|
||||
BackgroundBrush.Opacity = ActiveOpacity;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(grabbedText);
|
||||
@@ -334,11 +322,156 @@ public partial class OCROverlay : Window
|
||||
WindowUtilities.CloseAllOCROverlays();
|
||||
PowerToysTelemetry.Log.WriteEvent(new PowerOCR.Telemetry.PowerOCRCaptureEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowUtilities.CloseAllOCROverlays();
|
||||
PowerToysTelemetry.Log.WriteEvent(new PowerOCR.Telemetry.PowerOCRCancelledEvent());
|
||||
}
|
||||
|
||||
private void LanguagesComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is not ComboBox languageComboBox || !isComboBoxReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Set the preferred language based upon what was chosen here
|
||||
int selection = languageComboBox.SelectedIndex;
|
||||
selectedLanguage = languageComboBox.SelectedItem as Language;
|
||||
|
||||
Logger.LogError($"Changed language to {selectedLanguage?.LanguageTag}");
|
||||
|
||||
// Set the language in the context menu
|
||||
foreach (var item in CanvasContextMenu.Items)
|
||||
{
|
||||
if (item is MenuItem menuItemLoop)
|
||||
{
|
||||
menuItemLoop.IsChecked = menuItemLoop.Tag as Language == selectedLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
switch (selection)
|
||||
{
|
||||
case 0:
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.D1);
|
||||
break;
|
||||
case 1:
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.D2);
|
||||
break;
|
||||
case 2:
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.D3);
|
||||
break;
|
||||
case 3:
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.D4);
|
||||
break;
|
||||
case 4:
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.D5);
|
||||
break;
|
||||
case 5:
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.D6);
|
||||
break;
|
||||
case 6:
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.D7);
|
||||
break;
|
||||
case 7:
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.D8);
|
||||
break;
|
||||
case 8:
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.D9);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SingleLineMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool isActive = CheckIfCheckingOrUnchecking(sender);
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.S, isActive);
|
||||
}
|
||||
|
||||
private void TableToggleButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool isActive = CheckIfCheckingOrUnchecking(sender);
|
||||
WindowUtilities.OcrOverlayKeyDown(Key.T, isActive);
|
||||
}
|
||||
|
||||
private void SettingsMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowUtilities.CloseAllOCROverlays();
|
||||
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.PowerOCR, false);
|
||||
}
|
||||
|
||||
private static bool CheckIfCheckingOrUnchecking(object? sender)
|
||||
{
|
||||
if (sender is ToggleButton tb && tb.IsChecked is not null)
|
||||
{
|
||||
return tb.IsChecked.Value;
|
||||
}
|
||||
|
||||
if (sender is MenuItem mi)
|
||||
{
|
||||
return mi.IsChecked;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void KeyPressed(Key key, bool? isActive)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
// This case is handled in the WindowUtilities.OcrOverlayKeyDown
|
||||
// case Key.Escape:
|
||||
// WindowUtilities.CloseAllFullscreenGrabs();
|
||||
// break;
|
||||
case Key.S:
|
||||
if (isActive is null)
|
||||
{
|
||||
SingleLineMenuItem.IsChecked = !SingleLineMenuItem.IsChecked;
|
||||
}
|
||||
else
|
||||
{
|
||||
SingleLineMenuItem.IsChecked = isActive.Value;
|
||||
}
|
||||
|
||||
// Possibly save this in settings later and remember this preference
|
||||
break;
|
||||
case Key.T:
|
||||
if (isActive is null)
|
||||
{
|
||||
TableToggleButton.IsChecked = !TableToggleButton.IsChecked;
|
||||
}
|
||||
else
|
||||
{
|
||||
TableToggleButton.IsChecked = isActive.Value;
|
||||
}
|
||||
|
||||
break;
|
||||
case Key.D1:
|
||||
case Key.D2:
|
||||
case Key.D3:
|
||||
case Key.D4:
|
||||
case Key.D5:
|
||||
case Key.D6:
|
||||
case Key.D7:
|
||||
case Key.D8:
|
||||
case Key.D9:
|
||||
int numberPressed = (int)key - 34; // D1 casts to 35, D2 to 36, etc.
|
||||
int numberOfLanguages = LanguagesComboBox.Items.Count;
|
||||
|
||||
if (numberPressed <= numberOfLanguages
|
||||
&& numberPressed - 1 >= 0
|
||||
&& numberPressed - 1 != LanguagesComboBox.SelectedIndex
|
||||
&& isComboBoxReady)
|
||||
{
|
||||
LanguagesComboBox.SelectedIndex = numberPressed - 1;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<PackageReference Include="Microsoft.Windows.CsWinRT" />
|
||||
<PackageReference Include="System.ComponentModel.Composition" />
|
||||
<PackageReference Include="System.Drawing.Common" />
|
||||
<PackageReference Include="WPF-UI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace PowerOCR.Settings
|
||||
private const int SettingsReadOnChangeDelayInMs = 300;
|
||||
|
||||
private readonly IFileSystemWatcher _watcher;
|
||||
private readonly object _loadingSettingsLock = new object();
|
||||
private readonly object _loadingSettingsLock = new();
|
||||
|
||||
[ImportingConstructor]
|
||||
public UserSettings(Helpers.IThrottledActionInvoker throttledActionInvoker)
|
||||
@@ -113,7 +113,7 @@ namespace PowerOCR.Settings
|
||||
// var telemetrySettings = new Telemetry.PowerOcrSettings(properties.VisibleColorFormats)
|
||||
// {
|
||||
// ActivationShortcut = properties.ActivationShortcut.ToString(),
|
||||
// ActivationBehaviour = properties.ActivationAction.ToString(),
|
||||
// ActivationBehavior = properties.ActivationAction.ToString(),
|
||||
// ColorFormatForClipboard = properties.CopiedColorRepresentation.ToString(),
|
||||
// ShowColorName = properties.ShowColorName,
|
||||
// };
|
||||
|
||||
695
src/modules/PowerOCR/PowerOCR/Styles/ButtonStyles.xaml
Normal file
695
src/modules/PowerOCR/PowerOCR/Styles/ButtonStyles.xaml
Normal file
@@ -0,0 +1,695 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="Menu.Static.Background" Color="{DynamicResource SolidBackgroundFillColorSecondaryBrush}" />
|
||||
<SolidColorBrush x:Key="Menu.Static.Border" Color="{DynamicResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="Menu.Static.Foreground" Color="White" />
|
||||
<SolidColorBrush x:Key="Menu.Static.Separator" Color="{DynamicResource SystemAccentColorSecondary}" />
|
||||
<SolidColorBrush x:Key="Menu.Disabled.Foreground" Color="#FF707070" />
|
||||
<SolidColorBrush x:Key="MenuItem.Selected.Background" Color="#3D26A0DA" />
|
||||
<SolidColorBrush x:Key="MenuItem.Selected.Border" Color="{DynamicResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="MenuItem.Highlight.Background" Color="#3D26A0DA" />
|
||||
<SolidColorBrush x:Key="MenuItem.Highlight.Border" Color="{DynamicResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="MenuItem.Highlight.Disabled.Background" Color="#0A000000" />
|
||||
<SolidColorBrush x:Key="MenuItem.Highlight.Disabled.Border" Color="#21000000" />
|
||||
<MenuScrollingVisibilityConverter x:Key="MenuScrollingVisibilityConverter" />
|
||||
<Geometry x:Key="DownArrow">M 0,0 L 3.5,4 L 7,0 Z</Geometry>
|
||||
<Geometry x:Key="UpArrow">M 0,4 L 3.5,0 L 7,4 Z</Geometry>
|
||||
<Geometry x:Key="RightArrow">M 0,0 L 4,3.5 L 0,7 Z</Geometry>
|
||||
<Geometry x:Key="Checkmark">F1 M 10.0,1.2 L 4.7,9.1 L 4.5,9.1 L 0,5.2 L 1.3,3.5 L 4.3,6.1L 8.3,0 L 10.0,1.2 Z</Geometry>
|
||||
<Style x:Key="FocusVisual">
|
||||
<Setter Property="Control.Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Rectangle
|
||||
Margin="2"
|
||||
SnapsToDevicePixels="true"
|
||||
Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"
|
||||
StrokeDashArray="1 2"
|
||||
StrokeThickness="1" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="Button.Static.Background" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="Button.Static.Border" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="Button.MouseOver.Background" Color="{DynamicResource SystemAccentColorSecondaryBrush}" />
|
||||
<SolidColorBrush x:Key="Button.MouseOver.Border" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="Button.Pressed.Background" Color="#071818" />
|
||||
<SolidColorBrush x:Key="Button.Pressed.Border" Color="#071818" />
|
||||
<SolidColorBrush x:Key="Button.Disabled.Background" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="Button.Disabled.Border" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383" />
|
||||
|
||||
|
||||
<Style x:Key="ToggleSymbolButton" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}" />
|
||||
<Setter Property="Background" Value="{StaticResource Button.Static.Background}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Border
|
||||
x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
SnapsToDevicePixels="true">
|
||||
<ContentPresenter
|
||||
x:Name="contentPresenter"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Focusable="False"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="Button.IsDefaulted" Value="true">
|
||||
<Setter TargetName="border" Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="true">
|
||||
<Setter TargetName="border" Property="Background" Value="{DynamicResource SystemAccentColorBrush}" />
|
||||
<Setter TargetName="border" Property="BorderBrush" Value="{StaticResource Button.Pressed.Border}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="border" Property="Background" Value="{DynamicResource SystemAccentColorSecondaryBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="false">
|
||||
<Setter TargetName="border" Property="Background" Value="{StaticResource Button.Disabled.Background}" />
|
||||
<Setter TargetName="border" Property="BorderBrush" Value="{StaticResource Button.Disabled.Border}" />
|
||||
<Setter TargetName="contentPresenter" Property="TextElement.Foreground" Value="{StaticResource Button.Disabled.Foreground}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter TargetName="border" Property="Background" Value="{DynamicResource SystemAccentColorSecondaryBrush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="MenuScrollButton"
|
||||
BasedOn="{x:Null}"
|
||||
TargetType="{x:Type RepeatButton}">
|
||||
<Setter Property="ClickMode" Value="Hover" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type RepeatButton}">
|
||||
<Border
|
||||
x:Name="templateRoot"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="1"
|
||||
SnapsToDevicePixels="true">
|
||||
<ContentPresenter
|
||||
Margin="6"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<ControlTemplate x:Key="{ComponentResourceKey ResourceId=TopLevelItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}" TargetType="{x:Type MenuItem}">
|
||||
<Border
|
||||
x:Name="templateRoot"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
SnapsToDevicePixels="true">
|
||||
<Grid VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="Icon"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="3"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ContentSource="Icon"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<Path
|
||||
x:Name="GlyphPanel"
|
||||
Margin="3"
|
||||
VerticalAlignment="Center"
|
||||
Data="{StaticResource Checkmark}"
|
||||
Fill="{StaticResource Menu.Static.Foreground}"
|
||||
FlowDirection="LeftToRight"
|
||||
Visibility="Collapsed" />
|
||||
<ContentPresenter
|
||||
Grid.Column="1"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
ContentSource="Header"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="Icon" Value="{x:Null}">
|
||||
<Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="true">
|
||||
<Setter TargetName="GlyphPanel" Property="Visibility" Value="Visible" />
|
||||
<Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="templateRoot" Property="Background" Value="{StaticResource MenuItem.Highlight.Background}" />
|
||||
<Setter TargetName="templateRoot" Property="BorderBrush" Value="{StaticResource MenuItem.Highlight.Border}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="templateRoot" Property="TextElement.Foreground" Value="{StaticResource Menu.Disabled.Foreground}" />
|
||||
<Setter TargetName="GlyphPanel" Property="Fill" Value="{StaticResource Menu.Disabled.Foreground}" />
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsHighlighted" Value="True" />
|
||||
<Condition Property="IsEnabled" Value="False" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="templateRoot" Property="Background" Value="{StaticResource MenuItem.Highlight.Disabled.Background}" />
|
||||
<Setter TargetName="templateRoot" Property="BorderBrush" Value="{StaticResource MenuItem.Highlight.Disabled.Border}" />
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="{ComponentResourceKey ResourceId=TopLevelHeaderTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}" TargetType="{x:Type MenuItem}">
|
||||
<Border
|
||||
x:Name="templateRoot"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
SnapsToDevicePixels="true">
|
||||
<Grid VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="Icon"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="3"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ContentSource="Icon"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<Path
|
||||
x:Name="GlyphPanel"
|
||||
Margin="3"
|
||||
VerticalAlignment="Center"
|
||||
Data="{StaticResource Checkmark}"
|
||||
Fill="{TemplateBinding Foreground}"
|
||||
FlowDirection="LeftToRight"
|
||||
Visibility="Collapsed" />
|
||||
<ContentPresenter
|
||||
Grid.Column="1"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
ContentSource="Header"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<Popup
|
||||
x:Name="PART_Popup"
|
||||
AllowsTransparency="true"
|
||||
Focusable="false"
|
||||
IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=templateRoot}"
|
||||
PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
|
||||
<Border
|
||||
x:Name="SubMenuBorder"
|
||||
Padding="2"
|
||||
Background="{StaticResource Menu.Static.Background}"
|
||||
BorderBrush="{StaticResource Menu.Static.Border}"
|
||||
BorderThickness="1">
|
||||
<ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
|
||||
<Grid RenderOptions.ClearTypeHint="Enabled">
|
||||
<Canvas
|
||||
Width="0"
|
||||
Height="0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<Rectangle
|
||||
x:Name="OpaqueRect"
|
||||
Width="{Binding ActualWidth, ElementName=SubMenuBorder}"
|
||||
Height="{Binding ActualHeight, ElementName=SubMenuBorder}"
|
||||
Fill="{Binding Background, ElementName=SubMenuBorder}" />
|
||||
</Canvas>
|
||||
<Rectangle
|
||||
Width="1"
|
||||
Margin="29,2,0,2"
|
||||
HorizontalAlignment="Left"
|
||||
Fill="{StaticResource Menu.Static.Separator}" />
|
||||
<ItemsPresenter
|
||||
x:Name="ItemsPresenter"
|
||||
Grid.IsSharedSizeScope="true"
|
||||
KeyboardNavigation.DirectionalNavigation="Cycle"
|
||||
KeyboardNavigation.TabNavigation="Cycle"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSuspendingPopupAnimation" Value="true">
|
||||
<Setter TargetName="PART_Popup" Property="PopupAnimation" Value="None" />
|
||||
</Trigger>
|
||||
<Trigger Property="Icon" Value="{x:Null}">
|
||||
<Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="true">
|
||||
<Setter TargetName="GlyphPanel" Property="Visibility" Value="Visible" />
|
||||
<Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="templateRoot" Property="Background" Value="{StaticResource MenuItem.Highlight.Background}" />
|
||||
<Setter TargetName="templateRoot" Property="BorderBrush" Value="{StaticResource MenuItem.Highlight.Border}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="templateRoot" Property="TextElement.Foreground" Value="{StaticResource Menu.Disabled.Foreground}" />
|
||||
<Setter TargetName="GlyphPanel" Property="Fill" Value="{StaticResource Menu.Disabled.Foreground}" />
|
||||
</Trigger>
|
||||
<Trigger SourceName="SubMenuScrollViewer" Property="ScrollViewer.CanContentScroll" Value="false">
|
||||
<Setter TargetName="OpaqueRect" Property="Canvas.Top" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}" />
|
||||
<Setter TargetName="OpaqueRect" Property="Canvas.Left" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="{ComponentResourceKey ResourceId=SubmenuItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}" TargetType="{x:Type MenuItem}">
|
||||
<Border
|
||||
x:Name="templateRoot"
|
||||
Height="22"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
SnapsToDevicePixels="true">
|
||||
<Grid Margin="-1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition
|
||||
Width="Auto"
|
||||
MinWidth="22"
|
||||
SharedSizeGroup="MenuItemIconColumnGroup" />
|
||||
<ColumnDefinition Width="13" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="30" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGTColumnGroup" />
|
||||
<ColumnDefinition Width="20" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="Icon"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="3"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ContentSource="Icon"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<Border
|
||||
x:Name="GlyphPanel"
|
||||
Width="22"
|
||||
Height="22"
|
||||
Margin="-1,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="{StaticResource MenuItem.Selected.Background}"
|
||||
BorderBrush="{StaticResource MenuItem.Selected.Border}"
|
||||
BorderThickness="1"
|
||||
ClipToBounds="False"
|
||||
Visibility="Hidden">
|
||||
<Path
|
||||
x:Name="Glyph"
|
||||
Width="10"
|
||||
Height="11"
|
||||
Data="{StaticResource Checkmark}"
|
||||
Fill="{StaticResource Menu.Static.Foreground}"
|
||||
FlowDirection="LeftToRight" />
|
||||
</Border>
|
||||
<ContentPresenter
|
||||
x:Name="menuHeaderContainer"
|
||||
Grid.Column="2"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
ContentSource="Header"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<TextBlock
|
||||
x:Name="menuGestureText"
|
||||
Grid.Column="4"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
VerticalAlignment="Center"
|
||||
Opacity="0.7"
|
||||
Text="{TemplateBinding InputGestureText}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="Icon" Value="{x:Null}">
|
||||
<Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="GlyphPanel" Property="Visibility" Value="Visible" />
|
||||
<Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="templateRoot" Property="Background" Value="{StaticResource MenuItem.Highlight.Background}" />
|
||||
<Setter TargetName="templateRoot" Property="BorderBrush" Value="{StaticResource MenuItem.Highlight.Border}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="templateRoot" Property="TextElement.Foreground" Value="{StaticResource Menu.Disabled.Foreground}" />
|
||||
<Setter TargetName="Glyph" Property="Fill" Value="{StaticResource Menu.Disabled.Foreground}" />
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsHighlighted" Value="True" />
|
||||
<Condition Property="IsEnabled" Value="False" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="templateRoot" Property="Background" Value="{StaticResource MenuItem.Highlight.Disabled.Background}" />
|
||||
<Setter TargetName="templateRoot" Property="BorderBrush" Value="{StaticResource MenuItem.Highlight.Disabled.Border}" />
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="{ComponentResourceKey ResourceId=SubmenuHeaderTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}" TargetType="{x:Type MenuItem}">
|
||||
<Border
|
||||
x:Name="templateRoot"
|
||||
Height="22"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
SnapsToDevicePixels="true">
|
||||
<Grid Margin="-1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition
|
||||
Width="Auto"
|
||||
MinWidth="22"
|
||||
SharedSizeGroup="MenuItemIconColumnGroup" />
|
||||
<ColumnDefinition Width="13" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="30" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGTColumnGroup" />
|
||||
<ColumnDefinition Width="20" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="Icon"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="3"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ContentSource="Icon"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<Border
|
||||
x:Name="GlyphPanel"
|
||||
Width="22"
|
||||
Height="22"
|
||||
Margin="-1,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Background="{StaticResource MenuItem.Highlight.Background}"
|
||||
BorderBrush="{StaticResource MenuItem.Highlight.Border}"
|
||||
BorderThickness="1"
|
||||
Visibility="Hidden">
|
||||
<Path
|
||||
x:Name="Glyph"
|
||||
Width="9"
|
||||
Height="11"
|
||||
Data="{DynamicResource Checkmark}"
|
||||
Fill="{StaticResource Menu.Static.Foreground}"
|
||||
FlowDirection="LeftToRight" />
|
||||
</Border>
|
||||
<ContentPresenter
|
||||
Grid.Column="2"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
ContentSource="Header"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<TextBlock
|
||||
Grid.Column="4"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
VerticalAlignment="Center"
|
||||
Opacity="0.7"
|
||||
Text="{TemplateBinding InputGestureText}" />
|
||||
<Path
|
||||
x:Name="RightArrow"
|
||||
Grid.Column="5"
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Data="{StaticResource RightArrow}"
|
||||
Fill="{StaticResource Menu.Static.Foreground}" />
|
||||
<Popup
|
||||
x:Name="PART_Popup"
|
||||
AllowsTransparency="true"
|
||||
Focusable="false"
|
||||
HorizontalOffset="-2"
|
||||
IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Placement="Right"
|
||||
PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}"
|
||||
VerticalOffset="-3">
|
||||
<Border
|
||||
x:Name="SubMenuBorder"
|
||||
Padding="2"
|
||||
Background="{StaticResource Menu.Static.Background}"
|
||||
BorderBrush="{StaticResource Menu.Static.Border}"
|
||||
BorderThickness="1">
|
||||
<ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
|
||||
<Grid RenderOptions.ClearTypeHint="Enabled">
|
||||
<Canvas
|
||||
Width="0"
|
||||
Height="0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<Rectangle
|
||||
x:Name="OpaqueRect"
|
||||
Width="{Binding ActualWidth, ElementName=SubMenuBorder}"
|
||||
Height="{Binding ActualHeight, ElementName=SubMenuBorder}"
|
||||
Fill="{Binding Background, ElementName=SubMenuBorder}" />
|
||||
</Canvas>
|
||||
<Rectangle
|
||||
Width="1"
|
||||
Margin="29,2,0,2"
|
||||
HorizontalAlignment="Left"
|
||||
Fill="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}" />
|
||||
<ItemsPresenter
|
||||
x:Name="ItemsPresenter"
|
||||
Grid.IsSharedSizeScope="true"
|
||||
KeyboardNavigation.DirectionalNavigation="Cycle"
|
||||
KeyboardNavigation.TabNavigation="Cycle"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSuspendingPopupAnimation" Value="true">
|
||||
<Setter TargetName="PART_Popup" Property="PopupAnimation" Value="None" />
|
||||
</Trigger>
|
||||
<Trigger Property="Icon" Value="{x:Null}">
|
||||
<Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="GlyphPanel" Property="Visibility" Value="Visible" />
|
||||
<Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="templateRoot" Property="Background" Value="Transparent" />
|
||||
<Setter TargetName="templateRoot" Property="BorderBrush" Value="{StaticResource MenuItem.Highlight.Border}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="templateRoot" Property="TextElement.Foreground" Value="{StaticResource Menu.Disabled.Foreground}" />
|
||||
<Setter TargetName="Glyph" Property="Fill" Value="{StaticResource Menu.Disabled.Foreground}" />
|
||||
<Setter TargetName="RightArrow" Property="Fill" Value="{StaticResource Menu.Disabled.Foreground}" />
|
||||
</Trigger>
|
||||
<Trigger SourceName="SubMenuScrollViewer" Property="ScrollViewer.CanContentScroll" Value="false">
|
||||
<Setter TargetName="OpaqueRect" Property="Canvas.Top" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}" />
|
||||
<Setter TargetName="OpaqueRect" Property="Canvas.Left" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
<Style x:Key="DarkMenuItemStyle" TargetType="{x:Type MenuItem}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="ScrollViewer.PanningMode" Value="Both" />
|
||||
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
|
||||
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=SubmenuItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Role" Value="TopLevelHeader">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{StaticResource Menu.Static.Foreground}" />
|
||||
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=TopLevelHeaderTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}" />
|
||||
<Setter Property="Padding" Value="6,0" />
|
||||
</Trigger>
|
||||
<Trigger Property="Role" Value="TopLevelItem">
|
||||
<Setter Property="Background" Value="{StaticResource Menu.Static.Background}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Menu.Static.Border}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource Menu.Static.Foreground}" />
|
||||
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=TopLevelItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}" />
|
||||
<Setter Property="Padding" Value="6,0" />
|
||||
</Trigger>
|
||||
<Trigger Property="Role" Value="SubmenuHeader">
|
||||
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=SubmenuHeaderTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="TitleBarCloseButtonStyle" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True" />
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border
|
||||
x:Name="border"
|
||||
BorderThickness="0"
|
||||
SnapsToDevicePixels="true">
|
||||
<ContentPresenter
|
||||
x:Name="contentPresenter"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="False"
|
||||
RecognizesAccessKey="True" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter TargetName="border" Property="Background" Value="Red" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="true">
|
||||
<Setter TargetName="border" Property="Background" Value="DarkRed" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="TitleBarButtonStyle" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True" />
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border
|
||||
x:Name="border"
|
||||
BorderThickness="0"
|
||||
SnapsToDevicePixels="true">
|
||||
<ContentPresenter
|
||||
x:Name="contentPresenter"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="False"
|
||||
RecognizesAccessKey="True" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter TargetName="border" Property="Background" Value="{StaticResource Menu.Static.Border}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="true">
|
||||
<Setter TargetName="border" Property="Background" Value="{StaticResource Menu.Static.Separator}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="TealColor" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="Background" Value="{DynamicResource SystemAccentColorSecondaryBrush}" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True" />
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border
|
||||
x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="4"
|
||||
SnapsToDevicePixels="true">
|
||||
<ContentPresenter
|
||||
x:Name="contentPresenter"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="False"
|
||||
RecognizesAccessKey="True" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Background" Value="{DynamicResource SystemAccentColorSecondaryBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Background" Value="#071818" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.4" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="SymbolButton" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Padding" Value="6" />
|
||||
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True" />
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border
|
||||
x:Name="border"
|
||||
CornerRadius="4"
|
||||
SnapsToDevicePixels="true">
|
||||
<ContentPresenter
|
||||
x:Name="contentPresenter"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="False"
|
||||
RecognizesAccessKey="True" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter TargetName="border" Property="Background" Value="{DynamicResource SystemAccentColorSecondaryBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="true">
|
||||
<Setter TargetName="border" Property="Background" Value="#071818" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColorSecondaryBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.3" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
6
src/modules/PowerOCR/PowerOCR/Styles/Colors.xaml
Normal file
6
src/modules/PowerOCR/PowerOCR/Styles/Colors.xaml
Normal file
@@ -0,0 +1,6 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="DarkBackground" Color="{DynamicResource ApplicationBackgroundColor}" />
|
||||
<SolidColorBrush x:Key="DarkControlBackground" Color="{DynamicResource SolidBackgroundFillColorQuarternaryBrushColor}" />
|
||||
<SolidColorBrush x:Key="DarkTeal" Color="{DynamicResource SystemAccentColorSecondary}" />
|
||||
<SolidColorBrush x:Key="Teal" Color="{DynamicResource SystemAccentColor}" />
|
||||
</ResourceDictionary>
|
||||
Reference in New Issue
Block a user