Remove GUI-related dependencies from NAPS2.Sdk and have unit tests specify all their own paths (rather than using the Paths constants that reference the real user appdata etc)

This commit is contained in:
Ben Olden-Cooligan 2022-06-18 19:09:21 -07:00
parent 2f2211e1fc
commit ba16ac845e
47 changed files with 236 additions and 139 deletions

View File

@ -19,7 +19,7 @@ public static class ServerEntryPoint
try
{
// Initialize Ninject (the DI framework)
var kernel = new StandardKernel(new CommonModule(), new WinFormsModule(), new StaticDefaultsModule(), new RecoveryModule());
var kernel = new StandardKernel(new CommonModule(), new WinFormsModule(), new PathsModule(), new RecoveryModule());
// Start a pending worker process
kernel.Get<IWorkerFactory>().Init();

View File

@ -1,14 +1,16 @@
using Eto.Drawing;
namespace NAPS2.Config;
public class FormState
{
public string? Name { get; set; }
public Point Location { get; set; }
public FormLocation Location { get; set; }
public Size Size { get; set; }
public FormSize Size { get; set; }
public bool Maximized { get; set; }
public record FormLocation(int X, int Y);
public record FormSize(int Width, int Height);
}

View File

@ -43,12 +43,14 @@ public class ProfileManager : IProfileManager
public void Mutate(ListMutation<ScanProfile> mutation, ISelectable<ScanProfile> selectable)
{
Load();
mutation.Apply(_profiles, selectable);
Save();
}
public void Mutate(ListMutation<ScanProfile> mutation, ListSelection<ScanProfile> selection)
{
Load();
mutation.Apply(_profiles, ref selection);
Save();
}
@ -77,8 +79,8 @@ public class ProfileManager : IProfileManager
profile.IsDefault = false;
}
value.IsDefault = true;
Save();
}
Save();
}
}

View File

@ -58,7 +58,7 @@ public class SaveImagesOperation : OperationBase
{
if (File.Exists(subFileName))
{
if (_overwritePrompt.ConfirmOverwrite(subFileName) != DialogResult.Yes)
if (_overwritePrompt.ConfirmOverwrite(subFileName) != OverwriteResponse.Yes)
{
return false;
}
@ -81,12 +81,12 @@ public class SaveImagesOperation : OperationBase
if (images.Count == 1 && File.Exists(subFileName))
{
var dialogResult = _overwritePrompt.ConfirmOverwrite(subFileName);
if (dialogResult == DialogResult.No)
var overwriteResponse = _overwritePrompt.ConfirmOverwrite(subFileName);
if (overwriteResponse == OverwriteResponse.No)
{
continue;
}
if (dialogResult == DialogResult.Cancel)
if (overwriteResponse == OverwriteResponse.Abort)
{
return false;
}

View File

@ -45,7 +45,7 @@ public class SavePdfOperation : OperationBase
}
if (File.Exists(subFileName))
{
if (_overwritePrompt.ConfirmOverwrite(subFileName) != DialogResult.Yes)
if (_overwritePrompt.ConfirmOverwrite(subFileName) != OverwriteResponse.Yes)
{
return false;
}

View File

@ -29,7 +29,6 @@ public class CommonModule : NinjectModule
// Export
Bind<PdfExporter>().To<PdfSharpExporter>();
Bind<IScannedImagePrinter>().To<PrintDocumentPrinter>();
Bind<IEmailProviderFactory>().To<NinjectEmailProviderFactory>();
Bind<IMapiWrapper>().To<MapiWrapper>();
Bind<OcrRequestQueue>().ToSelf().InSingletonScope();
@ -46,11 +45,14 @@ public class CommonModule : NinjectModule
Bind<NetworkScanBridge>().ToSelf();
// Config
var config = new ScopedConfig(Path.Combine(Paths.Executable, "appsettings.xml"), Path.Combine(Paths.AppData, "config.xml"));
Bind<ScopedConfig>().ToConstant(config);
Bind<ScopedConfig>().ToMethod(_ =>
new ScopedConfig(Path.Combine(Paths.Executable, "appsettings.xml"),
Path.Combine(Paths.AppData, "config.xml"))).InSingletonScope();
Bind<IConfigProvider<PdfSettings>>().ToMethod(ctx => ctx.Kernel.Get<ScopedConfig>().Child(c => c.PdfSettings));
Bind<IConfigProvider<ImageSettings>>().ToMethod(ctx => ctx.Kernel.Get<ScopedConfig>().Child(c => c.ImageSettings));
Bind<IConfigProvider<EmailSettings>>().ToMethod(ctx => ctx.Kernel.Get<ScopedConfig>().Child(c => c.EmailSettings));
Bind<IConfigProvider<ImageSettings>>()
.ToMethod(ctx => ctx.Kernel.Get<ScopedConfig>().Child(c => c.ImageSettings));
Bind<IConfigProvider<EmailSettings>>()
.ToMethod(ctx => ctx.Kernel.Get<ScopedConfig>().Child(c => c.EmailSettings));
Bind<IConfigProvider<EmailSetup>>().ToMethod(ctx => ctx.Kernel.Get<ScopedConfig>().Child(c => c.EmailSetup));
// Host
@ -68,25 +70,35 @@ public class CommonModule : NinjectModule
//Kernel.Get<ImageContext>().PdfRenderer = Kernel.Get<PdfiumWorkerCoordinator>();
var profileManager = new ProfileManager(
Path.Combine(Paths.AppData, "profiles.xml"),
Path.Combine(Paths.Executable, "profiles.xml"),
config.Get(c => c.LockSystemProfiles),
config.Get(c => c.LockUnspecifiedDevices),
config.Get(c => c.NoUserProfiles));
Bind<IProfileManager>().ToConstant(profileManager);
Bind<IProfileManager>().ToMethod(ctx =>
{
var config = ctx.Kernel.Get<ScopedConfig>();
return new ProfileManager(
Path.Combine(Paths.AppData, "profiles.xml"),
Path.Combine(Assembly.GetEntryAssembly().Location, "profiles.xml"),
config.Get(c => c.LockSystemProfiles),
config.Get(c => c.LockUnspecifiedDevices),
config.Get(c => c.NoUserProfiles));
}).InSingletonScope();
var customComponentsPath = config.Get(c => c.ComponentsPath);
var componentsPath = string.IsNullOrWhiteSpace(customComponentsPath)
? Paths.Components
: Environment.ExpandEnvironmentVariables(customComponentsPath);
var tesseractLanguageManager = new TesseractLanguageManager(componentsPath);
var naps2Folder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
// TODO: Linux/mac. Also maybe generalize this (see also PdfiumNativeLibrary).
var tesseractPath =
Path.Combine(naps2Folder!, Environment.Is64BitProcess ? "_win64" : "_win32", "tesseract.exe");
var tesseractOcrEngine = new TesseractOcrEngine(tesseractPath, tesseractLanguageManager.TessdataBasePath);
Bind<TesseractLanguageManager>().ToConstant(tesseractLanguageManager);
Bind<IOcrEngine>().ToConstant(tesseractOcrEngine);
Bind<TesseractLanguageManager>().ToMethod(ctx =>
{
var config = ctx.Kernel.Get<ScopedConfig>();
var customComponentsPath = config.Get(c => c.ComponentsPath);
var componentsPath = string.IsNullOrWhiteSpace(customComponentsPath)
? Paths.Components
: Environment.ExpandEnvironmentVariables(customComponentsPath);
return new TesseractLanguageManager(componentsPath);
}).InSingletonScope();
Bind<IOcrEngine>().ToMethod(ctx =>
{
var tesseractPath = PlatformCompat.System.UseSystemTesseract
? "tesseract"
: Path.Combine(Paths.Executable, PlatformCompat.System.TesseractExecutablePath!);
return new TesseractOcrEngine(
tesseractPath,
ctx.Kernel.Get<TesseractLanguageManager>().TessdataBasePath,
Paths.Temp);
}).InSingletonScope();
}
}

View File

@ -0,0 +1,13 @@
using NAPS2.Scan;
using Ninject;
using Ninject.Modules;
namespace NAPS2.Modules;
public class PathsModule : NinjectModule
{
public override void Load()
{
Kernel.Get<ScanningContext>().TempFolderPath = Paths.Temp;
}
}

View File

@ -1,12 +0,0 @@
using Ninject;
using Ninject.Modules;
namespace NAPS2.Modules;
public class StaticDefaultsModule : NinjectModule
{
public override void Load()
{
OperationProgress.Default = Kernel.Get<OperationProgress>();
}
}

View File

@ -1,10 +1,10 @@
using System.Windows.Forms;
using System.Reflection;
namespace NAPS2;
public static class Paths
{
private static readonly string ExecutablePath = Application.StartupPath;
private static readonly string ExecutablePath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
#if STANDALONE
private static readonly string AppDataPath = Path.Combine(ExecutablePath, "..", "Data");

View File

@ -0,0 +1,5 @@
// https://sergiopedri.medium.com/enabling-and-using-c-9-features-on-older-and-unsupported-runtimes-ce384d8debb
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices;
internal static class IsExternalInit {}

View File

@ -14,16 +14,16 @@ public class ConsoleOverwritePrompt : OverwritePrompt
_errorOutput = errorOutput;
}
public override DialogResult ConfirmOverwrite(string path)
public override OverwriteResponse ConfirmOverwrite(string path)
{
if (ForceOverwrite)
{
return DialogResult.Yes;
return OverwriteResponse.Yes;
}
else
{
_errorOutput.DisplayError(string.Format(ConsoleResources.FileAlreadyExists, path));
return DialogResult.No;
return OverwriteResponse.No;
}
}
}

View File

@ -14,7 +14,7 @@ public static class ConsoleEntryPoint
public static void Run(string[] args)
{
// Initialize Ninject (the DI framework)
var kernel = new StandardKernel(new CommonModule(), new ConsoleModule(), new StaticDefaultsModule(), new RecoveryModule());
var kernel = new StandardKernel(new CommonModule(), new ConsoleModule(), new PathsModule(), new RecoveryModule());
Paths.ClearTemp();

View File

@ -18,6 +18,7 @@
<!-- TODO: Do we want to remove this reference? -->
<ProjectReference Include="..\NAPS2.Lib.WinForms\NAPS2.Lib.WinForms.csproj" />
<Reference Include="System.Windows.Forms" />
<PackageReference Include="CommandLineParser" Version="1.9.71" />
</ItemGroup>
</Project>

View File

@ -1,13 +1,16 @@
using System.Drawing;
using System.Reflection;
using System.Text;
using NAPS2.Automation;
using NAPS2.Modules;
using NAPS2.Ocr;
using NAPS2.Scan;
using NAPS2.Scan.Internal;
using NAPS2.Sdk.Tests;
using NAPS2.Sdk.Tests.Asserts;
using NAPS2.Sdk.Tests.Images;
using NAPS2.Sdk.Tests.Mocks;
using NAPS2.Sdk.Tests.Ocr;
using Ninject;
using Ninject.Modules;
using Ninject.Parameters;
@ -28,7 +31,8 @@ public class CommandLineIntegrationTests : ContextualTexts
private async Task RunCommand(AutomatedScanningOptions options, params Bitmap[] imagesToScan)
{
var scanDriverFactory = new ScanDriverFactoryBuilder().WithScannedImages(imagesToScan).Build();
var kernel = new StandardKernel(new CommonModule(), new ConsoleModule(), new TestModule(ImageContext, scanDriverFactory, _testOutputHelper, FolderPath));
var kernel = new StandardKernel(new CommonModule(), new ConsoleModule(),
new TestModule(ScanningContext, ImageContext, scanDriverFactory, _testOutputHelper, FolderPath));
var automatedScanning = kernel.Get<AutomatedScanning>(new ConstructorArgument("options", options));
await automatedScanning.Execute();
}
@ -36,15 +40,17 @@ public class CommandLineIntegrationTests : ContextualTexts
[Fact]
public async Task ScanSanity()
{
var path = $"{FolderPath}/test.pdf";
await RunCommand(
new AutomatedScanningOptions
{
Number = 1,
OutputPath = $"{FolderPath}/test.pdf",
OutputPath = path,
Verbose = true
},
SharedData.color_image);
PdfAsserts.AssertPageCount(1, $"{FolderPath}/test.pdf");
Assert.True(File.Exists(path));
PdfAsserts.AssertPageCount(1, path);
AssertRecoveryCleanedUp();
}
@ -77,16 +83,23 @@ public class CommandLineIntegrationTests : ContextualTexts
[Fact]
public async Task ScanWithOcr()
{
var fast = Path.Combine(FolderPath, "fast");
Directory.CreateDirectory(fast);
CopyResourceToFile(TesseractResources.tesseract_x64, FolderPath, "tesseract.exe");
CopyResourceToFile(TesseractResources.eng_traineddata, fast, "eng.traineddata");
var path = $"{FolderPath}/test.pdf";
await RunCommand(
new AutomatedScanningOptions
{
Number = 1,
OutputPath = $"{FolderPath}/test.pdf",
OutputPath = path,
Verbose = true,
OcrLang = "eng"
},
SharedData.ocr_test);
PdfAsserts.AssertContainsText("ADVERTISEMENT.", $"{FolderPath}/test.pdf");
Assert.True(File.Exists(path));
PdfAsserts.AssertContainsText("ADVERTISEMENT.", path);
AssertRecoveryCleanedUp();
}
@ -99,14 +112,17 @@ public class CommandLineIntegrationTests : ContextualTexts
private class TestModule : NinjectModule
{
private readonly ScanningContext _scanningContext;
private readonly ImageContext _imageContext;
private readonly IScanDriverFactory _scanDriverFactory;
private readonly ITestOutputHelper _testOutputHelper;
private readonly string _folderPath;
public TestModule(ImageContext imageContext, IScanDriverFactory scanDriverFactory,
public TestModule(ScanningContext scanningContext, ImageContext imageContext,
IScanDriverFactory scanDriverFactory,
ITestOutputHelper testOutputHelper, string folderPath)
{
_scanningContext = scanningContext;
_imageContext = imageContext;
_scanDriverFactory = scanDriverFactory;
_testOutputHelper = testOutputHelper;
@ -116,16 +132,49 @@ public class CommandLineIntegrationTests : ContextualTexts
public override void Load()
{
Rebind<ImageContext>().ToConstant(_imageContext);
// TODO: Bind TesseractLanguageManager or at least the language data path
Rebind<IScanDriverFactory>().ToConstant(_scanDriverFactory);
Rebind<IScanBridgeFactory>().To<InProcScanBridgeFactory>();
Rebind<ConsoleOutput>().ToSelf().WithConstructorArgument("writer", new TestOutputTextWriter(_testOutputHelper));
Rebind<ConsoleOutput>().ToSelf()
.WithConstructorArgument("writer", new TestOutputTextWriter(_testOutputHelper));
Rebind<ScopedConfig>().ToMethod(_ =>
{
var appConfigPath = Path.Combine(_folderPath, "appsettings.xml");
var userConfigPath = Path.Combine(_folderPath, "config.xml");
return new ScopedConfig(appConfigPath, userConfigPath);
}).InSingletonScope();
Rebind<IProfileManager>().ToMethod(_ =>
{
var userPath = Path.Combine(_folderPath, "profiles.xml");
var systemPath = Path.Combine(_folderPath, "sysprofiles.xml");
var profileManager = new ProfileManager(userPath, systemPath, false, false, false);
var defaultProfile = new ScanProfile
{
IsDefault = true,
Device = new ScanDevice("001", "Some Scanner")
};
profileManager.Mutate(
new ListMutation<ScanProfile>.Append(defaultProfile),
new Selectable<ScanProfile>());
return profileManager;
}).InSingletonScope();
Rebind<TesseractLanguageManager>().ToMethod(_ =>
{
var componentsPath = Path.Combine(_folderPath, "components");
Directory.CreateDirectory(componentsPath);
return new TesseractLanguageManager(componentsPath);
}).InSingletonScope();
Rebind<IOcrEngine>().ToMethod(ctx => new TesseractOcrEngine(
Path.Combine(_folderPath, "tesseract.exe"),
_folderPath,
_folderPath)).InSingletonScope();
string recoveryFolderPath = Path.Combine(_folderPath, "recovery");
var recoveryStorageManager = RecoveryStorageManager.CreateFolder(recoveryFolderPath);
var fileStorageManager = new FileStorageManager(recoveryFolderPath);
Kernel.Bind<RecoveryStorageManager>().ToConstant(recoveryStorageManager);
Kernel.Bind<FileStorageManager>().ToConstant(fileStorageManager);
Kernel.Get<ScanningContext>().TempFolderPath = _scanningContext.TempFolderPath;
}
}
@ -137,6 +186,7 @@ public class CommandLineIntegrationTests : ContextualTexts
{
_output = output;
}
public override Encoding Encoding => Encoding.UTF8;
public override void WriteLine(string message) => _output.WriteLine(message);

View File

@ -16,7 +16,7 @@ public static class WinFormsEntryPoint
public static void Run(string[] args)
{
// Initialize Ninject (the DI framework)
var kernel = new StandardKernel(new CommonModule(), new WinFormsModule(), new StaticDefaultsModule(), new RecoveryModule());
var kernel = new StandardKernel(new CommonModule(), new WinFormsModule(), new PathsModule(), new RecoveryModule());
Paths.ClearTemp();

View File

@ -25,7 +25,7 @@ public static class WorkerEntryPoint
#endif
// Initialize Ninject (the DI framework)
var kernel = new StandardKernel(new CommonModule(), new WinFormsModule(), new StaticDefaultsModule());
var kernel = new StandardKernel(new CommonModule(), new WinFormsModule(), new PathsModule());
// Verify that the recovery is always initialized by the parent process before creating images
// TODO: Replace this with something maybe
// kernel.Get<ImageContext>().UseFileStorage(new NotToBeUsedStorageManager());

View File

@ -1,3 +1,4 @@
using Eto.Drawing;
using Eto.Forms;
namespace NAPS2.EtoForms;
@ -46,18 +47,20 @@ public class FormStateController : IFormStateController
{
throw new InvalidOperationException();
}
if (!_formState.Location.IsZero)
var location = new Point(_formState.Location.X, _formState.Location.Y);
var size = new Size(_formState.Size.Width, _formState.Size.Height);
if (!location.IsZero)
{
if (Screen.Screens.Any(x => x.WorkingArea.Contains(_formState.Location)))
if (Screen.Screens.Any(x => x.WorkingArea.Contains(location)))
{
// Only move to the specified location if it's onscreen
// It might be offscreen if the user has disconnected a monitor
_window.Location = _formState.Location;
_window.Location = location;
}
}
if (!_formState.Size.IsEmpty)
if (!size.IsEmpty)
{
_window.Size = _formState.Size;
_window.Size = size;
}
if (_formState.Maximized)
{
@ -72,7 +75,7 @@ public class FormStateController : IFormStateController
_formState.Maximized = (_window.WindowState == WindowState.Maximized);
if (_window.WindowState == WindowState.Normal)
{
_formState.Size = _window.Size;
_formState.Size = new FormState.FormSize(_window.Size.Width, _window.Size.Height);
}
}
}
@ -83,7 +86,7 @@ public class FormStateController : IFormStateController
{
if (_window.WindowState == WindowState.Normal)
{
_formState.Location = _window.Location;
_formState.Location = new FormState.FormLocation(_window.Location.X, _window.Location.Y);
}
}
}

View File

@ -1,6 +1,7 @@
using NAPS2.Dependencies;
using NAPS2.EtoForms;
using NAPS2.EtoForms.WinForms;
using NAPS2.ImportExport;
using NAPS2.ImportExport.Pdf;
using NAPS2.Scan.Batch;
using NAPS2.WinForms;
@ -23,5 +24,6 @@ public class WinFormsModule : NinjectModule
Bind<IEtoPlatform>().To<WinFormsEtoPlatform>();
Bind<NotificationManager>().ToSelf().InSingletonScope();
Bind<ISaveNotify>().ToMethod(ctx => ctx.Kernel.Get<NotificationManager>());
Bind<IScannedImagePrinter>().To<PrintDocumentPrinter>();
}
}

View File

@ -19,6 +19,8 @@
</Reference>
<ProjectReference Include="..\NAPS2.Lib.Common\NAPS2.Lib.Common.csproj" />
<Reference Include="System.Windows.Forms" />
<PackageReference Include="Eto.Forms" Version="2.5.10" />
<PackageReference Include="Eto.Platform.Windows" Version="2.5.10" />
</ItemGroup>
<ItemGroup>

View File

@ -1,4 +1,5 @@
using System.Globalization;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
using Eto;
using NAPS2.EtoForms;
@ -145,18 +146,20 @@ public class FormBase : Form, IInvoker, IFormBase
protected void DoRestoreFormState()
{
if (!_formState.Location.IsZero)
var location = new Point(_formState.Location.X, _formState.Location.Y);
var size = new Size(_formState.Size.Width, _formState.Size.Height);
if (!location.IsEmpty)
{
if (Screen.AllScreens.Any(x => x.WorkingArea.Contains(_formState.Location.ToSD())))
if (Screen.AllScreens.Any(x => x.WorkingArea.Contains(location)))
{
// Only move to the specified location if it's onscreen
// It might be offscreen if the user has disconnected a monitor
Location = _formState.Location.ToSD();
Location = location;
}
}
if (!_formState.Size.IsEmpty)
if (!size.IsEmpty)
{
Size = _formState.Size.ToSD();
Size = size;
}
if (_formState.Maximized)
{
@ -171,7 +174,7 @@ public class FormBase : Form, IInvoker, IFormBase
_formState.Maximized = (WindowState == FormWindowState.Maximized);
if (WindowState == FormWindowState.Normal)
{
_formState.Size = Size.ToEto();
_formState.Size = new FormState.FormSize(Size.Width, Size.Height);
}
}
}
@ -182,7 +185,7 @@ public class FormBase : Form, IInvoker, IFormBase
{
if (WindowState == FormWindowState.Normal)
{
_formState.Location = Location.ToEto();
_formState.Location = new FormState.FormLocation(Location.X, Location.Y);
}
}
}

View File

@ -4,11 +4,16 @@ namespace NAPS2.WinForms;
public class WinFormsOverwritePrompt : OverwritePrompt
{
public override DialogResult ConfirmOverwrite(string path)
public override OverwriteResponse ConfirmOverwrite(string path)
{
string fileName = Path.GetFileName(path);
var dialogResult = MessageBox.Show(string.Format(MiscResources.ConfirmOverwriteFile, fileName),
MiscResources.OverwriteFile, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
return dialogResult;
return dialogResult switch
{
DialogResult.Yes => OverwriteResponse.Yes,
DialogResult.No => OverwriteResponse.No,
_ => OverwriteResponse.Abort
};
}
}

View File

@ -14,10 +14,12 @@ public class ContextualTexts : IDisposable
ImageContext = new GdiImageContext();
ScanningContext = new ScanningContext(ImageContext);
ScanningContext.TempFolderPath = Path.Combine(FolderPath, "temp");
Directory.CreateDirectory(ScanningContext.TempFolderPath);
}
public ImageContext ImageContext { get; }
public ScanningContext ScanningContext { get; }
public string FolderPath { get; }
@ -28,6 +30,18 @@ public class ContextualTexts : IDisposable
{
return ScanningContext.CreateProcessedImage(new GdiImage(new Bitmap(100, 100)));
}
public string CopyResourceToFile(byte[] resource, string folder, string fileName)
{
string path = Path.Combine(folder, fileName);
File.WriteAllBytes(path, resource);
return path;
}
public string CopyResourceToFile(byte[] resource, string fileName)
{
return CopyResourceToFile(resource, FolderPath, fileName);
}
public virtual void Dispose()
{
@ -39,7 +53,13 @@ public class ContextualTexts : IDisposable
catch (IOException)
{
Thread.Sleep(100);
Directory.Delete(FolderPath, true);
try
{
Directory.Delete(FolderPath, true);
}
catch (IOException)
{
}
}
}
}

View File

@ -17,25 +17,13 @@ public class TesseractOcrEngineTests : ContextualTexts
var fast = Path.Combine(FolderPath, "fast");
Directory.CreateDirectory(fast);
var exePath = CopyResourceToFile(TesseractResources.tesseract_x64, "tesseract.exe");
var tesseractPath = CopyResourceToFile(TesseractResources.tesseract_x64, FolderPath, "tesseract.exe");
CopyResourceToFile(TesseractResources.eng_traineddata, fast, "eng.traineddata");
CopyResourceToFile(TesseractResources.heb_traineddata, fast, "heb.traineddata");
_testImagePath = CopyResourceToFile(TesseractResources.ocr_test, "ocr_test.jpg");
_testImagePathHebrew = CopyResourceToFile(TesseractResources.ocr_test_hebrew, "ocr_test_hebrew.jpg");
_engine = new TesseractOcrEngine(exePath, FolderPath);
}
private string CopyResourceToFile(byte[] resource, string folder, string fileName)
{
string path = Path.Combine(folder, fileName);
File.WriteAllBytes(path, resource);
return path;
}
private string CopyResourceToFile(byte[] resource, string fileName)
{
return CopyResourceToFile(resource, FolderPath, fileName);
_engine = new TesseractOcrEngine(tesseractPath, FolderPath, FolderPath);
}
[Fact]

View File

@ -22,7 +22,7 @@ namespace NAPS2.Sdk.Tests.Ocr {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class TesseractResources {
public class TesseractResources {
private static global::System.Resources.ResourceManager resourceMan;

View File

@ -1,6 +1,5 @@
using System.Globalization;
using System.Threading;
using NAPS2.Images.Gdi;
using NAPS2.Ocr;
using NAPS2.Scan;
using PdfSharp.Drawing;
@ -193,7 +192,7 @@ public class PdfSharpExporter : PdfExporter
page = document.AddPage();
}
string tempImageFilePath = Path.Combine(Paths.Temp, Path.GetRandomFileName());
string tempImageFilePath = Path.Combine(_scanningContext.TempFolderPath, Path.GetRandomFileName());
var format = image.Metadata.Lossless ? ImageFileFormat.Png : ImageFileFormat.Jpeg;
using (var renderedImage = _scanningContext.ImageContext.Render(image))

View File

@ -45,12 +45,8 @@
<Reference Include="PdfSharp">
<HintPath>..\NAPS2.Setup\lib\PdfSharp.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<PackageReference Include="BouncyCastle" Version="1.8.4" />
<PackageReference Include="CommandLineParser" Version="1.9.71" />
<PackageReference Include="Eto.Forms" Version="2.5.10" />
<PackageReference Include="Eto.Platform.Windows" Version="2.5.10" />
<PackageReference Include="Grpc.Tools" Version="2.26.0" PrivateAssets="all" />
<PackageReference Include="GrpcDotNetNamedPipes" Version="1.4.2" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />

View File

@ -5,24 +5,26 @@ namespace NAPS2.Ocr;
public class TesseractOcrEngine : IOcrEngine
{
private readonly string _exePath;
private readonly string _tesseractPath;
private readonly string? _languageDataBasePath;
private readonly string _tempFolder;
public TesseractOcrEngine(string exePath, string? languageDataBasePath)
public TesseractOcrEngine(string tesseractPath, string? languageDataBasePath, string tempFolder)
{
_exePath = exePath;
_tesseractPath = tesseractPath;
_languageDataBasePath = languageDataBasePath;
_tempFolder = tempFolder;
}
public async Task<OcrResult?> ProcessImage(string imagePath, OcrParams ocrParams, CancellationToken cancelToken)
{
string tempHocrFilePath = Path.Combine(Paths.Temp, Path.GetRandomFileName());
string tempHocrFilePath = Path.Combine(_tempFolder, Path.GetRandomFileName());
string tempHocrFilePathWithExt = tempHocrFilePath + ".hocr";
try
{
var startInfo = new ProcessStartInfo
{
FileName = _exePath,
FileName = _tesseractPath,
Arguments = $"\"{imagePath}\" \"{tempHocrFilePath}\" -l {ocrParams.LanguageCode} hocr",
UseShellExecute = false,
CreateNoWindow = true,

View File

@ -5,18 +5,6 @@
/// </summary>
public abstract class OperationProgress
{
private static OperationProgress _default = new StubOperationProgress();
public static OperationProgress Default
{
get
{
TestingContext.NoStaticDefaults();
return _default;
}
set => _default = value ?? throw new ArgumentNullException(nameof(value));
}
public abstract void Attach(IOperation op);
public abstract void ShowProgress(IOperation op);

View File

@ -14,6 +14,10 @@ public interface ISystemCompat
bool IsWia20Supported { get; }
bool UseSystemTesseract { get; }
string? TesseractExecutablePath { get; }
string PdfiumLibraryPath { get; }
IntPtr LoadLibrary(string path);

View File

@ -19,6 +19,10 @@ public class LinuxSystemCompat : ISystemCompat
public bool UseUnixFontResolver => true;
public bool UseSystemTesseract => true;
public string? TesseractExecutablePath => null;
public string PdfiumLibraryPath => "_linux/libpdfium.so";
public IntPtr LoadLibrary(string path) => LinuxInterop.dlopen(path, RTLD_LAZY | RTLD_GLOBAL);

View File

@ -19,6 +19,10 @@ public class MacSystemCompat : ISystemCompat
public bool UseUnixFontResolver => true;
public bool UseSystemTesseract => true;
public string? TesseractExecutablePath => null;
public string PdfiumLibraryPath => "_osx/libpdfium.dylib";
public IntPtr LoadLibrary(string path) => OsxInterop.dlopen(path, RTLD_LAZY | RTLD_GLOBAL);

View File

@ -10,12 +10,6 @@ public static class Win32
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetScrollPos(IntPtr hWnd, System.Windows.Forms.Orientation nBar);
[DllImport("user32.dll")]
public static extern int SetScrollPos(IntPtr hWnd, System.Windows.Forms.Orientation nBar, int nPos, bool bRedraw);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hWnd);

View File

@ -4,6 +4,8 @@ namespace NAPS2.Platform;
public class Windows32SystemCompat : WindowsSystemCompat
{
public override string TesseractExecutablePath => "_win32/tesseract.exe";
public override string PdfiumLibraryPath => "_win32/pdfium.dll";
public override IntPtr LoadSymbol(IntPtr libraryHandle, string symbol)

View File

@ -4,6 +4,8 @@ namespace NAPS2.Platform;
public class Windows64SystemCompat : WindowsSystemCompat
{
public override string TesseractExecutablePath => "_win64/tesseract.exe";
public override string PdfiumLibraryPath => "_win64/pdfium.dll";
public override IntPtr LoadSymbol(IntPtr libraryHandle, string symbol)

View File

@ -17,6 +17,10 @@ public abstract class WindowsSystemCompat : ISystemCompat
public bool UseUnixFontResolver => false;
public bool UseSystemTesseract => false;
public abstract string? TesseractExecutablePath { get; }
public abstract string PdfiumLibraryPath { get; }
public IntPtr LoadLibrary(string path) => Win32.LoadLibrary(path);

View File

@ -1,5 +1,4 @@
using System.Collections.Immutable;
using System.Windows.Forms;
using NAPS2.Ocr;
using NAPS2.Remoting.Worker;
@ -33,8 +32,7 @@ public class ScanningContext : IDisposable
public FileStorageManager? FileStorageManager { get; set; }
// TODO: Rethink how this works.
public string TempFolderPath { get; set; }
public string TempFolderPath { get; set; } = Path.GetTempPath();
public IWorkerFactory WorkerFactory { get; set; }

View File

@ -64,7 +64,7 @@ public interface ISelectable<T>
ListSelection<T> Selection { get; set; }
}
public class Selectable<T>
public class Selectable<T> : ISelectable<T>
{
private ListSelection<T> _selection = ListSelection.Empty<T>();

View File

@ -1,6 +1,4 @@
using System.Windows.Forms;
namespace NAPS2.Util;
namespace NAPS2.Util;
// TODO: Refactor to Eto and move to NAPS2.EtoForms (or something non-eto and non-winforms if I want operations in the Sdk...)
/// <summary>
@ -17,5 +15,5 @@ public abstract class OverwritePrompt
/// </summary>
/// <param name="path">The path of the file to overwrite.</param>
/// <returns>Yes, No, or Cancel.</returns>
public abstract DialogResult ConfirmOverwrite(string path);
public abstract OverwriteResponse ConfirmOverwrite(string path);
}

View File

@ -0,0 +1,8 @@
namespace NAPS2.Util;
public enum OverwriteResponse
{
Yes,
No,
Abort
}

View File

@ -1,8 +1,6 @@
using System.Windows.Forms;
namespace NAPS2.Util;
namespace NAPS2.Util;
public class StubOverwritePrompt : OverwritePrompt
{
public override DialogResult ConfirmOverwrite(string path) => DialogResult.No;
public override OverwriteResponse ConfirmOverwrite(string path) => OverwriteResponse.No;
}