Skip to content

Commit a8c8f3f

Browse files
committed
v3.0.0: .NET 10 LTS, improved test coverage, and NuGet optimization
1 parent bb48efc commit a8c8f3f

9 files changed

Lines changed: 58 additions & 41 deletions

File tree

CreatePdf.NET.Tests/CreatePdf.NET.Tests.csproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,20 @@
1717
<!-- Test Framework -->
1818
<PackageReference Include="xunit" Version="2.9.3"/>
1919
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5"/>
20-
<PackageReference Include="xunit.analyzers" Version="1.18.0">
20+
<PackageReference Include="xunit.analyzers" Version="1.25.0">
2121
<PrivateAssets>all</PrivateAssets>
22-
<IncludeAssets>analyzers; buildtransitive</IncludeAssets>
22+
<IncludeAssets>build; analyzers; buildtransitive</IncludeAssets>
2323
</PackageReference>
2424

2525
<!-- Test Utilities -->
2626
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1"/>
2727
<PackageReference Include="AwesomeAssertions" Version="9.3.0"/>
2828
<PackageReference Include="NSubstitute" Version="5.3.0"/>
2929
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
30+
<PackageReference Update="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
31+
<PrivateAssets>all</PrivateAssets>
32+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
33+
</PackageReference>
3034
</ItemGroup>
3135

3236
<ItemGroup>

CreatePdf.NET.Tests/OcrServiceUnitTests.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ public class OcrServiceUnitTests
99
private const string TestPdfPath = "test.pdf";
1010
private const string ExpectedText = "Extracted Text Content";
1111

12-
private readonly IPdfOcrEngine _ocrEngine;
12+
private readonly IOcrProvider _ocrProvider;
1313
private readonly OcrService _sut; // System Under Test
1414

1515
public OcrServiceUnitTests()
1616
{
17-
_ocrEngine = Substitute.For<IPdfOcrEngine>();
18-
_sut = new OcrService(_ocrEngine);
17+
_ocrProvider = Substitute.For<IOcrProvider>();
18+
_sut = new OcrService(_ocrProvider);
1919
}
2020

2121
[Fact]
2222
public async Task ProcessPdfAsync_WhenEngineSucceeds_ReturnsExtractedText()
2323
{
2424
// Arrange
25-
_ocrEngine.ExtractTextFromImageAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<OcrOptions>(),
25+
_ocrProvider.ExtractTextFromImageAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<OcrOptions>(),
2626
Arg.Any<CancellationToken>())
2727
.Returns(Task.FromResult(ExpectedText));
2828

@@ -32,14 +32,14 @@ public async Task ProcessPdfAsync_WhenEngineSucceeds_ReturnsExtractedText()
3232
// Assert
3333
result.Should().Be(ExpectedText);
3434

35-
// Verify the engine methods were called in order
36-
await _ocrEngine.Received(1).RasterizePdfToPngAsync(
35+
// Verify the provider methods were called in order
36+
await _ocrProvider.Received(1).RasterizePdfToPngAsync(
3737
Arg.Is(TestPdfPath),
3838
Arg.Is<string>(s => s.EndsWith(".png", StringComparison.Ordinal)),
3939
Arg.Any<OcrOptions>(),
4040
Arg.Any<CancellationToken>()).ConfigureAwait(true);
4141

42-
await _ocrEngine.Received(1).ExtractTextFromImageAsync(
42+
await _ocrProvider.Received(1).ExtractTextFromImageAsync(
4343
Arg.Is<string>(s => s.EndsWith(".png", StringComparison.Ordinal)),
4444
Arg.Is<string>(s => s.EndsWith(".txt", StringComparison.Ordinal)),
4545
Arg.Any<OcrOptions>(),
@@ -50,7 +50,7 @@ await _ocrEngine.Received(1).ExtractTextFromImageAsync(
5050
public async Task ProcessPdfAsync_WhenConversionFails_CleansUpFilesAndThrows()
5151
{
5252
// Arrange
53-
_ocrEngine.RasterizePdfToPngAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<OcrOptions>(),
53+
_ocrProvider.RasterizePdfToPngAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<OcrOptions>(),
5454
Arg.Any<CancellationToken>())
5555
.Throws(new InvalidOperationException("Conversion failed"));
5656

@@ -62,7 +62,7 @@ await act.Should().ThrowAsync<InvalidOperationException>()
6262
.WithMessage("Conversion failed").ConfigureAwait(true);
6363

6464
// Verify OCR was NOT attempted
65-
await _ocrEngine.DidNotReceive().ExtractTextFromImageAsync(
65+
await _ocrProvider.DidNotReceive().ExtractTextFromImageAsync(
6666
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<OcrOptions>(), Arg.Any<CancellationToken>())
6767
.ConfigureAwait(true);
6868
}
@@ -73,7 +73,7 @@ public async Task ProcessPdfStreamAsync_WhenEngineSucceeds_ReturnsExtractedText(
7373
// Arrange
7474
using var stream = new MemoryStream([1, 2, 3]); // Dummy content
7575

76-
_ocrEngine.ExtractTextFromImageAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<OcrOptions>(),
76+
_ocrProvider.ExtractTextFromImageAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<OcrOptions>(),
7777
Arg.Any<CancellationToken>())
7878
.Returns(Task.FromResult(ExpectedText));
7979

@@ -84,7 +84,7 @@ public async Task ProcessPdfStreamAsync_WhenEngineSucceeds_ReturnsExtractedText(
8484
result.Should().Be(ExpectedText);
8585

8686
// Verify conversion happened with a temp file
87-
await _ocrEngine.Received(1).RasterizePdfToPngAsync(
87+
await _ocrProvider.Received(1).RasterizePdfToPngAsync(
8888
Arg.Any<string>(), // The temp file path is random, so just check it was called
8989
Arg.Any<string>(),
9090
Arg.Any<OcrOptions>(),

CreatePdf.NET.Tests/TesseractOcrEngineTests.cs renamed to CreatePdf.NET.Tests/TesseractOcrProviderTests.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace CreatePdf.NET.Tests;
55

6-
public class TesseractOcrEngineTests
6+
public class TesseractOcrProviderTests
77
{
88
private const string AppleSiliconPath = "/opt/homebrew/bin/tesseract";
99
private const string IntelMacPath = "/usr/local/bin/tesseract";
@@ -13,7 +13,7 @@ public async Task ExtractTextFromImageAsync_WhenOutputIsMissing_ThrowsFileNotFou
1313
{
1414
var processRunner = new FakeProcessRunner();
1515
var environment = new FakeSystemEnvironment { FileExistsImpl = _ => false };
16-
var engine = new TesseractOcrEngine(environment, processRunner);
16+
var engine = new TesseractOcrProvider(environment, processRunner);
1717

1818
var act = () => engine.ExtractTextFromImageAsync(
1919
"input.png",
@@ -32,7 +32,7 @@ await act.Should().ThrowAsync<FileNotFoundException>()
3232
[Fact]
3333
public void GetPdfRasterizerExecutable_UsesExplicitConverterPath()
3434
{
35-
var engine = new TesseractOcrEngine(new FakeSystemEnvironment(), new FakeProcessRunner());
35+
var engine = new TesseractOcrProvider(new FakeSystemEnvironment(), new FakeProcessRunner());
3636
var options = new OcrOptions { PdfConverterPath = "/custom/gs" };
3737

3838
engine.GetPdfRasterizerExecutable(options).Should().Be("/custom/gs");
@@ -41,7 +41,7 @@ public void GetPdfRasterizerExecutable_UsesExplicitConverterPath()
4141
[Fact]
4242
public void GetPdfRasterizerExecutable_MacOs_ReturnsSips()
4343
{
44-
var engine = new TesseractOcrEngine(new FakeSystemEnvironment { IsMacOS = true }, new FakeProcessRunner());
44+
var engine = new TesseractOcrProvider(new FakeSystemEnvironment { IsMacOS = true }, new FakeProcessRunner());
4545

4646
engine.GetPdfRasterizerExecutable(new OcrOptions()).Should().Be("/usr/bin/sips");
4747
}
@@ -52,23 +52,23 @@ public void GetPdfRasterizerExecutable_MacOs_ReturnsSips()
5252
public void GetPdfRasterizerExecutable_Windows_SelectsBitnessSpecificCommand(bool is64Bit, string expected)
5353
{
5454
var environment = new FakeSystemEnvironment { IsWindows = true, Is64BitOperatingSystem = is64Bit };
55-
var engine = new TesseractOcrEngine(environment, new FakeProcessRunner());
55+
var engine = new TesseractOcrProvider(environment, new FakeProcessRunner());
5656

5757
engine.GetPdfRasterizerExecutable(new OcrOptions()).Should().Be(expected);
5858
}
5959

6060
[Fact]
6161
public void GetPdfRasterizerExecutable_DefaultsToUnixCommand()
6262
{
63-
var engine = new TesseractOcrEngine(new FakeSystemEnvironment(), new FakeProcessRunner());
63+
var engine = new TesseractOcrProvider(new FakeSystemEnvironment(), new FakeProcessRunner());
6464

6565
engine.GetPdfRasterizerExecutable(new OcrOptions()).Should().Be("gs");
6666
}
6767

6868
[Fact]
6969
public void GetTesseractExecutable_UsesProvidedPath()
7070
{
71-
var engine = new TesseractOcrEngine(new FakeSystemEnvironment(), new FakeProcessRunner());
71+
var engine = new TesseractOcrProvider(new FakeSystemEnvironment(), new FakeProcessRunner());
7272
var options = new OcrOptions { TesseractPath = "/custom/tesseract" };
7373

7474
engine.GetTesseractExecutable(options).Should().Be("/custom/tesseract");
@@ -82,7 +82,7 @@ public void GetTesseractExecutable_MacOsPrefersAppleSiliconBinary()
8282
IsMacOS = true,
8383
FileExistsImpl = path => string.Equals(path, AppleSiliconPath, StringComparison.Ordinal)
8484
};
85-
var engine = new TesseractOcrEngine(environment, new FakeProcessRunner());
85+
var engine = new TesseractOcrProvider(environment, new FakeProcessRunner());
8686

8787
engine.GetTesseractExecutable(new OcrOptions()).Should().Be(AppleSiliconPath);
8888
}
@@ -95,15 +95,15 @@ public void GetTesseractExecutable_MacOsFallsBackToIntelBinary()
9595
IsMacOS = true,
9696
FileExistsImpl = path => string.Equals(path, IntelMacPath, StringComparison.Ordinal)
9797
};
98-
var engine = new TesseractOcrEngine(environment, new FakeProcessRunner());
98+
var engine = new TesseractOcrProvider(environment, new FakeProcessRunner());
9999

100100
engine.GetTesseractExecutable(new OcrOptions()).Should().Be(IntelMacPath);
101101
}
102102

103103
[Fact]
104104
public void GetTesseractExecutable_UsesFallbackWhenNoMacBinaryFound()
105105
{
106-
var engine = new TesseractOcrEngine(
106+
var engine = new TesseractOcrProvider(
107107
new FakeSystemEnvironment { IsMacOS = true },
108108
new FakeProcessRunner());
109109

@@ -113,7 +113,7 @@ public void GetTesseractExecutable_UsesFallbackWhenNoMacBinaryFound()
113113
[Fact]
114114
public void GetRasterizationArguments_MacOs_UsesSipsFormat()
115115
{
116-
var engine = new TesseractOcrEngine(new FakeSystemEnvironment { IsMacOS = true }, new FakeProcessRunner());
116+
var engine = new TesseractOcrProvider(new FakeSystemEnvironment { IsMacOS = true }, new FakeProcessRunner());
117117

118118
engine.GetRasterizationArguments("file.pdf", "file.png", new OcrOptions { Dpi = 150 })
119119
.Should()
@@ -123,7 +123,7 @@ public void GetRasterizationArguments_MacOs_UsesSipsFormat()
123123
[Fact]
124124
public void GetRasterizationArguments_NonMac_UsesGhostscriptFormat()
125125
{
126-
var engine = new TesseractOcrEngine(new FakeSystemEnvironment(), new FakeProcessRunner());
126+
var engine = new TesseractOcrProvider(new FakeSystemEnvironment(), new FakeProcessRunner());
127127

128128
engine.GetRasterizationArguments("file.pdf", "file.png", new OcrOptions { Dpi = 200 })
129129
.Should()

CreatePdf.NET/CreatePdf.NET.csproj

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@
66
<ImplicitUsings>enable</ImplicitUsings>
77

88
<PackageId>CreatePdf.NET</PackageId>
9-
<Version>3.0.0</Version>
9+
<Title>CreatePdf.NET - Simple PDF Creation Library</Title>
10+
<Version>3.0.1</Version>
1011
<Authors>Alexander Nachtmann</Authors>
11-
<Description>A simple, .NET library for PDF creation with text and bitmap rendering, plus optional OCR functionality for text extraction.</Description>
12-
<PackageTags>pdf;document;generation;lightweight;net10;ocr</PackageTags>
12+
<Summary>Lightweight PDF generation library for .NET 10/9/8 with text rendering, bitmap graphics, and OCR</Summary>
13+
<Description>CreatePdf.NET is a simple, fast PDF creation library for .NET applications. Generate PDF documents with text rendering, bitmap graphics, and optional OCR text extraction. Lightweight, dependency-free, 100% test coverage. Perfect for creating reports, invoices, receipts, labels, and documents. Supports .NET 10, .NET 9, and .NET 8 with fluent C# API.</Description>
14+
<PackageTags>pdf pdf-generation pdf-generator pdf-creator pdf-library create-pdf generate-pdf document-generation csharp dotnet net10 net9 net8 text-rendering bitmap graphics ocr tesseract report-generation invoice-generation invoice receipt label reports invoices lightweight fast dependency-free managed-code cross-platform linux docker azure aws-lambda microservices</PackageTags>
1315
<RepositoryUrl>https://github.com/ANcpLua/CreatePdf.NET</RepositoryUrl>
1416
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1517
<PackageReadmeFile>README.md</PackageReadmeFile>
18+
<PackageIcon>icon.png</PackageIcon>
1619
<PackageProjectUrl>https://github.com/ANcpLua/CreatePdf.NET</PackageProjectUrl>
20+
<Copyright>Copyright © 2025 Alexander Nachtmann</Copyright>
21+
<PackageReleaseNotes>https://github.com/ANcpLua/CreatePdf.NET/releases</PackageReleaseNotes>
1722

1823
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
1924
<IncludeSymbols>true</IncludeSymbols>
@@ -39,6 +44,14 @@
3944

4045
<ItemGroup>
4146
<None Include="..\README.md" Pack="true" PackagePath="\"/>
47+
<None Include="..\icon.png" Pack="true" PackagePath="\"/>
48+
</ItemGroup>
49+
50+
<ItemGroup>
51+
<PackageReference Update="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
52+
<PrivateAssets>all</PrivateAssets>
53+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
54+
</PackageReference>
4255
</ItemGroup>
4356

4457
</Project>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
namespace CreatePdf.NET.Internal;
22

33
/// <summary>
4-
/// Defines the contract for an OCR engine capable of rasterizing PDF pages and extracting text from images.
4+
/// Defines the contract for an OCR provider capable of rasterizing PDF pages and extracting text from images.
55
/// </summary>
6-
internal interface IPdfOcrEngine
6+
internal interface IOcrProvider
77
{
88
/// <summary>
99
/// Rasterizes a PDF page to a PNG image.

CreatePdf.NET/Internal/OcrService.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ namespace CreatePdf.NET.Internal;
22

33
internal sealed class OcrService
44
{
5-
private readonly IPdfOcrEngine _engine;
5+
private readonly IOcrProvider _provider;
66

7-
public OcrService() : this(new TesseractOcrEngine())
7+
public OcrService() : this(new TesseractOcrProvider())
88
{
99
}
1010

11-
internal OcrService(IPdfOcrEngine engine)
11+
internal OcrService(IOcrProvider provider)
1212
{
13-
_engine = engine;
13+
_provider = provider;
1414
}
1515

1616
public async Task<string> ProcessPdfAsync(string pdfPath, OcrOptions options,
@@ -23,8 +23,8 @@ public async Task<string> ProcessPdfAsync(string pdfPath, OcrOptions options,
2323

2424
try
2525
{
26-
await _engine.RasterizePdfToPngAsync(pdfPath, pngPath, options, cancellationToken).ConfigureAwait(false);
27-
return await _engine.ExtractTextFromImageAsync(pngPath, txtPath, options, cancellationToken)
26+
await _provider.RasterizePdfToPngAsync(pdfPath, pngPath, options, cancellationToken).ConfigureAwait(false);
27+
return await _provider.ExtractTextFromImageAsync(pngPath, txtPath, options, cancellationToken)
2828
.ConfigureAwait(false);
2929
}
3030
finally

CreatePdf.NET/Internal/TesseractOcrEngine.cs renamed to CreatePdf.NET/Internal/TesseractOcrProvider.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
namespace CreatePdf.NET.Internal;
55

66
/// <summary>
7-
/// A concrete implementation of <see cref="IPdfOcrEngine" /> that uses external processes (Ghostscript/SIPS and
7+
/// A concrete implementation of <see cref="IOcrProvider" /> that uses external processes (Ghostscript/SIPS and
88
/// Tesseract) to perform OCR.
99
/// </summary>
10-
internal sealed class TesseractOcrEngine : IPdfOcrEngine
10+
internal sealed class TesseractOcrProvider : IOcrProvider
1111
{
1212
private const string SipsUniversalPath = "/usr/bin/sips";
1313
private const string TesseractAppleSiliconPath = "/opt/homebrew/bin/tesseract";
@@ -20,12 +20,12 @@ internal sealed class TesseractOcrEngine : IPdfOcrEngine
2020
private readonly IProcessRunner _processRunner;
2121
private readonly ISystemEnvironment _systemEnvironment;
2222

23-
internal TesseractOcrEngine()
23+
internal TesseractOcrProvider()
2424
: this(RuntimeSystemEnvironment.Instance, ProcessRunner.Instance)
2525
{
2626
}
2727

28-
internal TesseractOcrEngine(ISystemEnvironment systemEnvironment, IProcessRunner processRunner)
28+
internal TesseractOcrProvider(ISystemEnvironment systemEnvironment, IProcessRunner processRunner)
2929
{
3030
ArgumentNullException.ThrowIfNull(systemEnvironment);
3131
ArgumentNullException.ThrowIfNull(processRunner);

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[![codecov](https://codecov.io/gh/ANcpLua/CreatePdf.NET/branch/main/graph/badge.svg?token=lgxIXBnFrn)](https://codecov.io/gh/ANcpLua/CreatePdf.NET)
2-
[![.NET](https://img.shields.io/badge/.NET-10.0%20|%209.0%20|%208.0-512BD4)](https://dotnet.microsoft.com/)
2+
[![.NET 10](https://img.shields.io/badge/.NET-10.0-7C3AED)](https://dotnet.microsoft.com/download/dotnet/10.0)[![.NET 9](https://img.shields.io/badge/-9.0-6366F1)](https://dotnet.microsoft.com/download/dotnet/9.0)[![.NET 8](https://img.shields.io/badge/-8.0-512BD4)](https://dotnet.microsoft.com/download/dotnet/8.0)
33
[![NuGet](https://img.shields.io/nuget/v/CreatePdf.NET?label=NuGet&color=0891B2)](https://www.nuget.org/packages/CreatePdf.NET/)
44
[![License](https://img.shields.io/github/license/ANcpLua/CreatePdf.NET?label=License&color=white)](https://github.com/ANcpLua/CreatePdf.NET/blob/main/LICENSE)
55
[![Docker](https://img.shields.io/docker/v/ancplua/createpdf.net?label=Docker&color=0C4A6E)](https://hub.docker.com/r/ancplua/createpdf.net)

icon.png

94.2 KB
Loading

0 commit comments

Comments
 (0)