diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index 5700da9b..3406458e 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -62,7 +62,8 @@ jobs: os: - "windows-2022" dotnet-version: - - "net482" + - "net472" + - "net48" - "net6.0" - "net10.0" runs-on: ${{ matrix.os }} diff --git a/Directory.Build.props b/Directory.Build.props index 2c9edae4..cdf6059e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -41,4 +41,44 @@ true false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mindee.Cli/Commands/PredictCommand.cs b/src/Mindee.Cli/Commands/PredictCommand.cs index c43154ea..90f471e0 100644 --- a/src/Mindee.Cli/Commands/PredictCommand.cs +++ b/src/Mindee.Cli/Commands/PredictCommand.cs @@ -1,6 +1,4 @@ using System.CommandLine; -using System.CommandLine.Invocation; -using System.CommandLine.IO; using System.Text.Json; using Mindee.Input; using Mindee.Parsing; @@ -31,103 +29,134 @@ class PredictCommand : Command where TPage : IPrediction, new() where TInferenceModel : Inference, new() { + private readonly Option _outputOption; + private readonly Option? _allWordsOption; + private readonly Option? _fullTextOption; + private readonly Option? _asyncOption; + private readonly Argument _pathArgument; + public PredictCommand(CommandOptions options) : base(options.Name, options.Description) { - AddOption(new Option(["-o", "--output", "output"], - "Specify how to output the data. \n" + - "- summary: a basic summary (default)\n" + - "- raw: full JSON object\n")); + _outputOption = new Option("--output", "-o") + { + Description = "Specify how to output the data. \n" + + "- summary: a basic summary (default)\n" + + "- raw: full JSON object\n", + DefaultValueFactory = _ => OutputType.Summary + }; + Options.Add(_outputOption); + if (options.AllWords) { - var option = new Option(["-w", "--all-words", "allWords"], - "To get all the words in the current document. False by default."); - AddOption(option); + _allWordsOption = new Option("--all-words", "-w") + { + Description = "To get all the words in the current document. False by default.", + DefaultValueFactory = _ => false + }; + Options.Add(_allWordsOption); } if (options.FullText) { - var option = new Option(["-f", "--full-text", "fullText"], - "To get all the words in the current document. False by default."); - AddOption(option); + _fullTextOption = new Option("--full-text", "-f") + { + Description = "To get all the words in the current document. False by default.", + DefaultValueFactory = _ => false + }; + Options.Add(_fullTextOption); } switch (options.Async) { case true when !options.Sync: { - // Inject an "option" not changeable by the user. - // This will set the `Handler.Async` property to always be `true`. - var option = new Option("async", () => true) { IsHidden = true }; - AddOption(option); + _asyncOption = new Option("async") + { + Hidden = true, + DefaultValueFactory = _ => true + }; + Options.Add(_asyncOption); break; } case true when options.Sync: - AddOption(new Option(["--async"], - "Process the file asynchronously. False by default.")); + _asyncOption = new Option("--async") + { + Description = "Process the file asynchronously. False by default.", + DefaultValueFactory = _ => false + }; + Options.Add(_asyncOption); break; } - AddArgument(new Argument("path", "The path of the file to parse")); + _pathArgument = new Argument("path") { Description = "The path of the file to parse" }; + Arguments.Add(_pathArgument); } - public new class Handler(MindeeClient mindeeClient) : ICommandHandler + public void ConfigureAction(MindeeClient mindeeClient) { - private readonly JsonSerializerOptions _jsonSerializerOptions = new() { WriteIndented = true }; - - public string Path { get; set; } = null!; - public bool AllWords { get; set; } = false; - public bool FullText { get; set; } = false; - public OutputType Output { get; set; } = OutputType.Full; - public bool Async { get; set; } = false; + this.SetAction(parseResult => + { + var path = parseResult.GetValue(_pathArgument)!; + var allWords = _allWordsOption != null && parseResult.GetValue(_allWordsOption); + var fullText = _fullTextOption != null && parseResult.GetValue(_fullTextOption); + var output = parseResult.GetValue(_outputOption); + var isAsync = _asyncOption != null && parseResult.GetValue(_asyncOption); + + var handler = new Handler(mindeeClient); + return handler.InvokeAsync(path, allWords, fullText, output, isAsync).GetAwaiter().GetResult(); + }); + } - public int Invoke(InvocationContext context) + public class Handler(MindeeClient mindeeClient) + { + private readonly JsonSerializerOptions _jsonSerializerOptions = new() { - return InvokeAsync(context).GetAwaiter().GetResult(); - } + WriteIndented = true, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; - public async Task InvokeAsync(InvocationContext context) + public async Task InvokeAsync(string path, bool allWords, bool fullText, OutputType output, bool isAsync) { - var options = - new ParseOptions(Path, AllWords, FullText, Output); - if (Async) + var options = new ParseOptions(path, allWords, fullText, output); + if (isAsync) { - return await EnqueueAndParseAsync(context, options); + return await EnqueueAndParseAsync(options); } - return await ParseAsync(context, options); + return await ParseAsync(options); } - private async Task ParseAsync(InvocationContext context, ParseOptions options) + private async Task ParseAsync(ParseOptions options) { var response = await mindeeClient.ParseAsync( new LocalInputSource(options.Path), - new PredictOptions(AllWords, FullText)); + new PredictOptions(options.AllWords, options.FullText)); if (response == null) { - context.Console.Out.Write("null"); + await Console.Out.WriteAsync("null"); return 1; } - PrintToConsole(context.Console.Out, options, response); + PrintToConsole(Console.Out, options, response); return 0; } - private async Task EnqueueAndParseAsync(InvocationContext context, ParseOptions options) + private async Task EnqueueAndParseAsync(ParseOptions options) { var response = await mindeeClient.EnqueueAndParseAsync( new LocalInputSource(options.Path), - new PredictOptions(AllWords, FullText), + new PredictOptions(options.AllWords, options.FullText), null, new AsyncPollingOptions()); - PrintToConsole(context.Console.Out, options, response); + PrintToConsole(Console.Out, options, response); return 0; } private void PrintToConsole( - IStandardStreamWriter console, + TextWriter console, ParseOptions options, PredictResponse response) { diff --git a/src/Mindee.Cli/Mindee.Cli.csproj b/src/Mindee.Cli/Mindee.Cli.csproj index 79b608a0..da4ac94b 100644 --- a/src/Mindee.Cli/Mindee.Cli.csproj +++ b/src/Mindee.Cli/Mindee.Cli.csproj @@ -7,6 +7,10 @@ enable + + + + PreserveNewest @@ -28,21 +32,8 @@ - - - - - - - - - + - - - - - diff --git a/src/Mindee.Cli/Program.cs b/src/Mindee.Cli/Program.cs index af0e071e..b92645cd 100644 --- a/src/Mindee.Cli/Program.cs +++ b/src/Mindee.Cli/Program.cs @@ -1,10 +1,8 @@ using System.CommandLine; -using System.CommandLine.Builder; -using System.CommandLine.Hosting; -using System.CommandLine.Parsing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Mindee; using Mindee.Cli.Commands; -// ReSharper disable once RedundantUsingDirective using Mindee.Extensions.DependencyInjection; using PredictBankAccountDetailsCommand = Mindee.Cli.Commands.PredictCommand< Mindee.Product.Fr.BankAccountDetails.BankAccountDetailsV2, @@ -82,98 +80,125 @@ Mindee.Product.Receipt.ReceiptV5Document >; -var runner = BuildCommandLine() - .UseHost(_ => Host.CreateDefaultBuilder(args), builder => +// Setup dependency injection +var host = Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => { - builder - .ConfigureServices((_, services) => - { - services.AddMindeeClient(); - services.AddMindeeClientV2(); - }) - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - ; + services.AddMindeeClient(); + services.AddMindeeClientV2(); }) - .UseHelp() - .UseParseErrorReporting() - .CancelOnProcessTermination() - .UseEnvironmentVariableDirective() - .UseParseDirective() - .UseSuggestDirective() - .UseTypoCorrections() - .UseExceptionHandler() .Build(); -return await runner.InvokeAsync(args); +var root = BuildCommandLine(host.Services); -static CommandLineBuilder BuildCommandLine() +return await root.Parse(args).InvokeAsync(); + +static RootCommand BuildCommandLine(IServiceProvider services) { var root = new RootCommand(); - root.AddCommand(new PredictBarcodeReaderCommand(new CommandOptions( + var mindeeClient = services.GetRequiredService(); + + var barcodeReaderCmd = new PredictBarcodeReaderCommand(new CommandOptions( "barcode-reader", "Barcode Reader", - false, false, true, false))); - root.AddCommand(new PredictCropperCommand(new CommandOptions( + false, false, true, false)); + barcodeReaderCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(barcodeReaderCmd); + + var cropperCmd = new PredictCropperCommand(new CommandOptions( "cropper", "Cropper", - false, false, true, false))); - root.AddCommand(new PredictFinancialDocumentCommand(new CommandOptions( + false, false, true, false)); + cropperCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(cropperCmd); + + var financialDocumentCmd = new PredictFinancialDocumentCommand(new CommandOptions( "financial-document", "Financial Document", - true, false, true, true))); - root.AddCommand(new PredictBankAccountDetailsCommand(new CommandOptions( + true, false, true, true)); + financialDocumentCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(financialDocumentCmd); + + var bankAccountDetailsCmd = new PredictBankAccountDetailsCommand(new CommandOptions( "fr-bank-account-details", "FR Bank Account Details", - false, false, true, false))); - root.AddCommand(new PredictCarteGriseCommand(new CommandOptions( + false, false, true, false)); + bankAccountDetailsCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(bankAccountDetailsCmd); + + var carteGriseCmd = new PredictCarteGriseCommand(new CommandOptions( "fr-carte-grise", "FR Carte Grise", - false, false, true, false))); - root.AddCommand(new PredictHealthCardCommand(new CommandOptions( + false, false, true, false)); + carteGriseCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(carteGriseCmd); + + var healthCardCmd = new PredictHealthCardCommand(new CommandOptions( "fr-health-card", "FR Health Card", - false, false, false, true))); - root.AddCommand(new PredictIdCardCommand(new CommandOptions( + false, false, false, true)); + healthCardCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(healthCardCmd); + + var idCardCmd = new PredictIdCardCommand(new CommandOptions( "fr-carte-nationale-d-identite", "FR Carte Nationale d'Identité", - false, false, true, false))); - root.AddCommand(new PredictPayslipCommand(new CommandOptions( + false, false, true, false)); + idCardCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(idCardCmd); + + var payslipCmd = new PredictPayslipCommand(new CommandOptions( "fr-payslip", "FR Payslip", - false, false, false, true))); - root.AddCommand(new PredictInternationalIdCommand(new CommandOptions( + false, false, false, true)); + payslipCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(payslipCmd); + + var internationalIdCmd = new PredictInternationalIdCommand(new CommandOptions( "international-id", "International ID", - false, true, false, true))); - root.AddCommand(new PredictInvoiceCommand(new CommandOptions( + false, true, false, true)); + internationalIdCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(internationalIdCmd); + + var invoiceCmd = new PredictInvoiceCommand(new CommandOptions( "invoice", "Invoice", - true, false, true, true))); - root.AddCommand(new PredictInvoiceSplitterCommand(new CommandOptions( + true, false, true, true)); + invoiceCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(invoiceCmd); + + var invoiceSplitterCmd = new PredictInvoiceSplitterCommand(new CommandOptions( "invoice-splitter", "Invoice Splitter", - false, false, false, true))); - root.AddCommand(new PredictMultiReceiptsDetectorCommand(new CommandOptions( + false, false, false, true)); + invoiceSplitterCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(invoiceSplitterCmd); + + var multiReceiptsDetectorCmd = new PredictMultiReceiptsDetectorCommand(new CommandOptions( "multi-receipts-detector", "Multi Receipts Detector", - false, false, true, false))); - root.AddCommand(new PredictPassportCommand(new CommandOptions( + false, false, true, false)); + multiReceiptsDetectorCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(multiReceiptsDetectorCmd); + + var passportCmd = new PredictPassportCommand(new CommandOptions( "passport", "Passport", - false, false, true, false))); - root.AddCommand(new PredictReceiptCommand(new CommandOptions( + false, false, true, false)); + passportCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(passportCmd); + + var receiptCmd = new PredictReceiptCommand(new CommandOptions( "receipt", "Receipt", - true, false, true, true))); - root.AddCommand(new PredictBankCheckCommand(new CommandOptions( + true, false, true, true)); + receiptCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(receiptCmd); + + var bankCheckCmd = new PredictBankCheckCommand(new CommandOptions( "us-bank-check", "US Bank Check", - false, false, true, false))); + false, false, true, false)); + bankCheckCmd.ConfigureAction(mindeeClient); + root.Subcommands.Add(bankCheckCmd); + + var silentOption = new Option("--silent") + { + Description = "Disables diagnostics output" + }; + root.Options.Add(silentOption); - root.AddGlobalOption(new Option("--silent", "Disables diagnostics output")); - root.SetHandler(() => + root.SetAction(parseResult => { - root.InvokeAsync("--help"); + Console.WriteLine("Please specify a subcommand. Use --help for more information."); + return 1; }); - return new CommandLineBuilder(root); + return root; } diff --git a/src/Mindee/Extensions/DependencyInjection/ServiceCollectionsExtensions.cs b/src/Mindee/Extensions/DependencyInjection/ServiceCollectionsExtensions.cs index 280e937c..9214b363 100644 --- a/src/Mindee/Extensions/DependencyInjection/ServiceCollectionsExtensions.cs +++ b/src/Mindee/Extensions/DependencyInjection/ServiceCollectionsExtensions.cs @@ -16,10 +16,28 @@ namespace Mindee.Extensions.DependencyInjection { #if !NET6_0_OR_GREATER + /// + /// Wrapper for V1 RestClient to work around lack of keyed services in .NET Framework + /// + internal sealed class MindeeV1RestClientWrapper : IDisposable + { + public RestClient Client { get; } + + public MindeeV1RestClientWrapper(RestClient client) + { + Client = client; + } + + public void Dispose() + { + Client.Dispose(); + } + } + /// /// Wrapper for V2 RestClient to work around lack of keyed services in .NET Framework /// - internal sealed class MindeeV2RestClientWrapper + internal sealed class MindeeV2RestClientWrapper : IDisposable { public RestClient Client { get; } @@ -27,6 +45,11 @@ public MindeeV2RestClientWrapper(RestClient client) { Client = client; } + + public void Dispose() + { + Client.Dispose(); + } } #endif @@ -53,7 +76,11 @@ public static void AddMindeeApi( services.AddSingleton(serviceProvider => { var settings = serviceProvider.GetRequiredService>(); - var restClient = serviceProvider.GetRequiredService(); +#if NET6_0_OR_GREATER + var restClient = serviceProvider.GetRequiredService(); +#else + var restClient = serviceProvider.GetRequiredService().Client; +#endif var logger = serviceProvider.GetService()?.CreateLogger(); return new MindeeApi(settings, restClient, logger); }); @@ -101,6 +128,7 @@ public static void AddMindeeApiV2( private static void RegisterV1RestSharpClient(IServiceCollection services, bool throwOnError) { +#if NET6_0_OR_GREATER services.AddSingleton(provider => { var settings = provider.GetRequiredService>().Value; @@ -131,6 +159,38 @@ private static void RegisterV1RestSharpClient(IServiceCollection services, bool }; return new RestClient(clientOptions); }); +#else + services.AddSingleton(provider => + { + var settings = provider.GetRequiredService>().Value; + settings.MindeeBaseUrl = Environment.GetEnvironmentVariable("Mindee__BaseUrl"); + if (string.IsNullOrEmpty(settings.MindeeBaseUrl)) + { + settings.MindeeBaseUrl = "https://api.mindee.net"; + } + + if (settings.RequestTimeoutSeconds <= 0) + { + settings.RequestTimeoutSeconds = 120; + } + + if (string.IsNullOrEmpty(settings.ApiKey)) + { + settings.ApiKey = Environment.GetEnvironmentVariable("Mindee__ApiKey"); + } + + var clientOptions = new RestClientOptions(settings.MindeeBaseUrl) + { + FollowRedirects = false, + Timeout = TimeSpan.FromSeconds(settings.RequestTimeoutSeconds), + UserAgent = BuildUserAgent(), + Expect100Continue = false, + CachePolicy = new CacheControlHeaderValue { NoCache = true, NoStore = true }, + ThrowOnAnyError = throwOnError + }; + return new MindeeV1RestClientWrapper(new RestClient(clientOptions)); + }); +#endif } private static void RegisterV2RestSharpClient(IServiceCollection services, bool throwOnError) @@ -240,7 +300,17 @@ public static IServiceCollection AddMindeeClient( string sectionName = "Mindee") { services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(provider => + { + var settings = provider.GetRequiredService>(); +#if NET6_0_OR_GREATER + var restClient = provider.GetRequiredService(); +#else + var restClient = provider.GetRequiredService().Client; +#endif + var logger = provider.GetService()?.CreateLogger(); + return new MindeeApi(settings, restClient, logger); + }); services.AddOptions() .BindConfiguration(sectionName) .Validate(settings => !string.IsNullOrEmpty(settings.ApiKey), "The Mindee V1 API key is missing"); @@ -264,7 +334,17 @@ public static IServiceCollection AddMindeeClientV2( string sectionName = "MindeeV2") { services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(provider => + { + var settings = provider.GetRequiredService>(); +#if NET6_0_OR_GREATER + var restClient = provider.GetRequiredKeyedService("MindeeV2RestClient"); +#else + var restClient = provider.GetRequiredService().Client; +#endif + var logger = provider.GetService()?.CreateLogger(); + return new MindeeApiV2(settings, restClient, logger); + }); services.AddOptions() .BindConfiguration(sectionName) .Validate(settings => !string.IsNullOrEmpty(settings.ApiKey), "The Mindee V2 API key is missing"); diff --git a/src/Mindee/Image/ImageCompressor.cs b/src/Mindee/Image/ImageCompressor.cs index 561782fc..414c8cc1 100644 --- a/src/Mindee/Image/ImageCompressor.cs +++ b/src/Mindee/Image/ImageCompressor.cs @@ -19,8 +19,9 @@ public static byte[] CompressImage(SKBitmap original, int quality, int finalWidt { using var image = SKImage.FromBitmap(original); using var compressedBitmap = SKBitmap.FromImage(image); + var samplingOptions = new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.None); using var finalImage = - compressedBitmap.Resize(new SKImageInfo(finalWidth, finalHeight), SKFilterQuality.Low); + compressedBitmap.Resize(new SKImageInfo(finalWidth, finalHeight), samplingOptions); return finalImage.Encode(SKEncodedImageFormat.Jpeg, quality).ToArray(); } diff --git a/src/Mindee/Mindee.csproj b/src/Mindee/Mindee.csproj index 50e08c21..44272c73 100644 --- a/src/Mindee/Mindee.csproj +++ b/src/Mindee/Mindee.csproj @@ -4,40 +4,13 @@ net472;net48;net6.0;net7.0;net8.0;net9.0;net10.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_Parameter1>Mindee.UnitTests + + <_Parameter1>Mindee.IntegrationTests + diff --git a/src/Mindee/Pdf/PdfCompressor.cs b/src/Mindee/Pdf/PdfCompressor.cs index 084352a5..ff9a69de 100644 --- a/src/Mindee/Pdf/PdfCompressor.cs +++ b/src/Mindee/Pdf/PdfCompressor.cs @@ -45,7 +45,12 @@ public static byte[] CompressPdf( using var docReader = DocLib.Instance.GetDocReader(pdfData, new PageDimensions(1)); var outputStream = new MemoryStream(); - using (var document = SKDocument.CreatePdf(outputStream)) + var metadata = new SKDocumentPdfMetadata + { + EncodingQuality = imageQuality + }; + + using (var document = SKDocument.CreatePdf(outputStream, metadata)) { ProcessPages(docReader, document, imageQuality, disableSourceText); } diff --git a/src/Mindee/Pdf/PdfUtils.cs b/src/Mindee/Pdf/PdfUtils.cs index 7cf3c2f3..1aff8156 100644 --- a/src/Mindee/Pdf/PdfUtils.cs +++ b/src/Mindee/Pdf/PdfUtils.cs @@ -60,12 +60,8 @@ public static SKBitmap GeneratePageBitmap(int imageQuality, IPageReader pageRead var compressedImage = ImageCompressor.CompressImage(initialBitmap, imageQuality, width, height); - var colorType = SKColorType.Argb4444; using var compressedBitmap = SKBitmap.Decode(compressedImage); - if (imageQuality > 85) - { - colorType = SKColorType.Rgb565; - } + const SKColorType colorType = SKColorType.Rgb565; using var surface = SKSurface.Create(new SKImageInfo(width, height, colorType)); @@ -93,8 +89,9 @@ public static SKBitmap GeneratePageBitmap(int imageQuality, IPageReader pageRead private static void WriteTextToCanvas(SKBitmap bitmap, Character character, SKCanvas canvas) { using var paint = new SKPaint(); + using var font = new SKFont(); var textColor = ImageUtils.InferTextColor(bitmap, character.Box); - paint.TextSize = (float)character.FontSize * (72f / 96f); + font.Size = (float)character.FontSize * (72f / 96f); paint.Color = textColor; var fontManager = SKFontManager.Default; @@ -105,14 +102,12 @@ private static void WriteTextToCanvas(SKBitmap bitmap, Character character, SKCa string.Equals(fontManager.MatchFamily(tmpFontName).FamilyName, tmpFontName, StringComparison.OrdinalIgnoreCase) ) ?? "Liberation Sans"; - paint.Typeface = SKTypeface.FromFamilyName(fontName); - - paint.TextAlign = SKTextAlign.Left; + font.Typeface = SKTypeface.FromFamilyName(fontName); var text = character.Char.ToString(); var lines = text.Split(["\r\n", "\n"], StringSplitOptions.None); - var lineHeight = paint.FontSpacing; + var lineHeight = font.Spacing; var boxCenterX = (character.Box.Left + character.Box.Right) / 2f; float boxBottom = character.Box.Bottom; @@ -124,8 +119,7 @@ private static void WriteTextToCanvas(SKBitmap bitmap, Character character, SKCa continue; } - var lineBounds = new SKRect(); - paint.MeasureText(line, ref lineBounds); + font.MeasureText(line, out var lineBounds); var x = boxCenterX - (lineBounds.Width / 2f); var y = boxBottom - ((lines.Length - i) * lineHeight); @@ -139,8 +133,8 @@ private static void WriteTextToCanvas(SKBitmap bitmap, Character character, SKCa } var charString = c.ToString(); - canvas.DrawText(charString, x, y, paint); - x += paint.MeasureText(charString); + canvas.DrawText(charString, x, y, font, paint); + x += font.MeasureText(charString); } } } diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 00000000..25c2506d --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,40 @@ + + + + latest + false + true + false + enable + $(NoWarn);1591 + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + PreserveNewest + Resources\%(RecursiveDir)%(Filename)%(Extension) + + + + + Resources\async + + + + diff --git a/tests/Mindee.IntegrationTests/Mindee.IntegrationTests.csproj b/tests/Mindee.IntegrationTests/Mindee.IntegrationTests.csproj index 1cd32e3f..037b40f1 100644 --- a/tests/Mindee.IntegrationTests/Mindee.IntegrationTests.csproj +++ b/tests/Mindee.IntegrationTests/Mindee.IntegrationTests.csproj @@ -1,49 +1,18 @@ - net472;net48;net6.0;net7.0;net8.0;net9.0;net10.0 enable - enable - $(NoWarn);1591 - false - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + - - - - - + + - - - - - PreserveNewest - - - - - - - diff --git a/tests/Mindee.UnitTests/Mindee.UnitTests.csproj b/tests/Mindee.UnitTests/Mindee.UnitTests.csproj index 034220a4..1c1850f8 100644 --- a/tests/Mindee.UnitTests/Mindee.UnitTests.csproj +++ b/tests/Mindee.UnitTests/Mindee.UnitTests.csproj @@ -1,52 +1,13 @@ - net472;net48;net6.0;net7.0;net8.0;net9.0;net10.0 enable - enable - $(NoWarn);1591 - false - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - PreserveNewest - + - - - - Resources\async - - - - diff --git a/tests/Mindee.UnitTests/UnitTestBase.cs b/tests/Mindee.UnitTests/UnitTestBase.cs index fb6ddaa3..89661952 100644 --- a/tests/Mindee.UnitTests/UnitTestBase.cs +++ b/tests/Mindee.UnitTests/UnitTestBase.cs @@ -59,11 +59,16 @@ public static ServiceProvider InitServiceProviderV1(HttpStatusCode statusCode, s options.RequestTimeoutSeconds = 120; }); - services.AddSingleton(new RestClient(new RestClientOptions + var restClient = new RestClient(new RestClientOptions { BaseUrl = new Uri("https://api.mindee.net"), ConfigureMessageHandler = _ => mockHttpMessageHandler.Object - })); + }); +#if NET6_0_OR_GREATER + services.AddSingleton(restClient); +#else + services.AddSingleton(new MindeeV1RestClientWrapper(restClient)); +#endif return services.BuildServiceProvider(); } @@ -98,12 +103,16 @@ private static ServiceProvider InitServiceProviderV2(HttpStatusCode statusCode, options.RequestTimeoutSeconds = 120; }); - services.AddKeyedSingleton("MindeeV2RestClient", - new RestClient(new RestClientOptions - { - BaseUrl = new Uri("https://api.mindee.net"), - ConfigureMessageHandler = _ => mockHttpMessageHandler.Object - })); + var restClient = new RestClient(new RestClientOptions + { + BaseUrl = new Uri("https://api.mindee.net"), + ConfigureMessageHandler = _ => mockHttpMessageHandler.Object + }); +#if NET6_0_OR_GREATER + services.AddKeyedSingleton("MindeeV2RestClient", restClient); +#else + services.AddSingleton(new MindeeV2RestClientWrapper(restClient)); +#endif return services.BuildServiceProvider(); }