diff --git a/src/Docnet.Core/Bindings/DocumentWrapper.cs b/src/Docnet.Core/Bindings/DocumentWrapper.cs index c69b94d..7e81bd0 100644 --- a/src/Docnet.Core/Bindings/DocumentWrapper.cs +++ b/src/Docnet.Core/Bindings/DocumentWrapper.cs @@ -10,9 +10,6 @@ internal sealed class DocumentWrapper : IDisposable public FpdfDocumentT Instance { get; private set; } - private FPDF_FORMFILLINFO _formInfo; - private FpdfFormHandleT _formHandle; - public DocumentWrapper(string filePath, string password) { Instance = fpdf_view.FPDF_LoadDocument(filePath, password); @@ -47,30 +44,6 @@ public DocumentWrapper(FpdfDocumentT instance) } } - public FpdfFormHandleT GetFormHandle() - { - if (_formHandle != null) - { - return _formHandle; - } - - _formInfo = new FPDF_FORMFILLINFO(); - - for (var i = 1; i <= 2; i++) - { - _formInfo.version = i; - - _formHandle = fpdf_view.FPDFDOCInitFormFillEnvironment(Instance, _formInfo); - - if (_formHandle != null) - { - break; - } - } - - return _formHandle; - } - public void Dispose() { if (Instance == null) @@ -78,11 +51,6 @@ public void Dispose() return; } - if (_formHandle != null) - { - fpdf_view.FPDF_ExitFormFillEnvironment(_formHandle); - } - fpdf_view.FPDF_CloseDocument(Instance); Marshal.FreeHGlobal(_ptr); diff --git a/src/Docnet.Core/Bindings/FormWrapper.cs b/src/Docnet.Core/Bindings/FormWrapper.cs new file mode 100644 index 0000000..05fa611 --- /dev/null +++ b/src/Docnet.Core/Bindings/FormWrapper.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.InteropServices; + +namespace Docnet.Core.Bindings +{ + internal sealed class FormWrapper : IDisposable + { + private readonly IntPtr _ptr; + + public FormWrapper(DocumentWrapper docWrapper) + { + var formInfo = new FPDF_FORMFILLINFO(); + + _ptr = Marshal.AllocHGlobal(Marshal.SizeOf(formInfo)); + + for (var i = 1; i <= 2; i++) + { + formInfo.version = i; + + Marshal.StructureToPtr(formInfo, _ptr, false); + + Instance = fpdf_view.FPDFDOCInitFormFillEnvironment(docWrapper.Instance, _ptr); + + if (Instance != null) + { + break; + } + } + } + + public FpdfFormHandleT Instance { get; private set; } + + public void Dispose() + { + if (Instance != null) + { + fpdf_view.FPDF_ExitFormFillEnvironment(Instance); + } + + Marshal.FreeHGlobal(_ptr); + + Instance = null; + } + } +} diff --git a/src/Docnet.Core/Bindings/PdfiumWrapper.cs b/src/Docnet.Core/Bindings/PdfiumWrapper.cs index f6bd34a..ab7c9e3 100644 --- a/src/Docnet.Core/Bindings/PdfiumWrapper.cs +++ b/src/Docnet.Core/Bindings/PdfiumWrapper.cs @@ -1833,7 +1833,7 @@ internal static extern IntPtr FPDF_LoadDocument( [SuppressUnmanagedCodeSecurity] [DllImport("pdfium", CallingConvention = CallingConvention.Cdecl, EntryPoint = "FPDFDOC_InitFormFillEnvironment")] - internal static extern IntPtr FPDFDOC_InitFormFillEnvironment(IntPtr document, FPDF_FORMFILLINFO formInfo); + internal static extern IntPtr FPDFDOC_InitFormFillEnvironment(IntPtr document, IntPtr formInfo); [SuppressUnmanagedCodeSecurity] [DllImport("pdfium", CallingConvention = CallingConvention.Cdecl, @@ -2325,7 +2325,7 @@ public static FpdfBitmapT FPDFBitmapCreate(int width, int height, int alpha) return __result0; } - public static FpdfFormHandleT FPDFDOCInitFormFillEnvironment(FpdfDocumentT document, FPDF_FORMFILLINFO formInfo) + public static FpdfFormHandleT FPDFDOCInitFormFillEnvironment(FpdfDocumentT document, IntPtr formInfo) { var __arg0 = ReferenceEquals(document, null) ? IntPtr.Zero : document.__Instance; diff --git a/src/Docnet.Core/Models/RenderFlags.cs b/src/Docnet.Core/Models/RenderFlags.cs index 3ca9a4e..a7a49b0 100644 --- a/src/Docnet.Core/Models/RenderFlags.cs +++ b/src/Docnet.Core/Models/RenderFlags.cs @@ -5,6 +5,7 @@ namespace Docnet.Core.Models [Flags] public enum RenderFlags { + None = 0x00, // None RenderAnnotations = 0x01, // FPDF_ANNOT: Set if annotations are to be rendered. OptimizeTextForLcd = 0x02, // FPDF_LCD_TEXT: Set if using text rendering optimized for LCD display. This flag will only take effect if anti-aliasing is enabled for text. NoNativeText = 0x04, // FPDF_NO_NATIVETEXT: Don't use the native text output available on some platforms diff --git a/src/Docnet.Core/Readers/PageReader.cs b/src/Docnet.Core/Readers/PageReader.cs index 236671e..b87dde5 100644 --- a/src/Docnet.Core/Readers/PageReader.cs +++ b/src/Docnet.Core/Readers/PageReader.cs @@ -233,6 +233,8 @@ private byte[] WriteImageToBufferInternal(RenderFlags flags, byte[] result = nul throw new DocnetException($"result array length should be greater or equal than {length}"); } + FormWrapper formWrapper = null; + try { // | | a b 0 | @@ -253,18 +255,16 @@ private byte[] WriteImageToBufferInternal(RenderFlags flags, byte[] result = nul clipping.Bottom = 0; clipping.Top = height; - FpdfFormHandleT formHandle = null; - if (flags.HasFlag(RenderFlags.RenderAnnotations)) { - formHandle = _docWrapper.GetFormHandle(); + formWrapper = new FormWrapper(_docWrapper); } fpdf_view.FPDF_RenderPageBitmapWithMatrix(bitmap, _page, matrix, clipping, (int)flags); - if (flags.HasFlag(RenderFlags.RenderAnnotations) && formHandle != null) + if (flags.HasFlag(RenderFlags.RenderAnnotations) && formWrapper?.Instance != null) { - fpdf_view.FPDFFFLDraw(formHandle, bitmap, _page, 0, 0, width, height, PageRotate.Normal, flags); + fpdf_view.FPDFFFLDraw(formWrapper.Instance, bitmap, _page, 0, 0, width, height, PageRotate.Normal, flags); } var buffer = fpdf_view.FPDFBitmapGetBuffer(bitmap); @@ -278,6 +278,8 @@ private byte[] WriteImageToBufferInternal(RenderFlags flags, byte[] result = nul } finally { + formWrapper?.Dispose(); + fpdf_view.FPDFBitmapDestroy(bitmap); } } diff --git a/src/Docnet.Tests.Integration/Docs/annotation_1.pdf b/src/Docnet.Tests.Integration/Docs/annotation_1.pdf new file mode 100644 index 0000000..2defdf6 Binary files /dev/null and b/src/Docnet.Tests.Integration/Docs/annotation_1.pdf differ diff --git a/src/Docnet.Tests.Integration/Docs/simple_7.pdf b/src/Docnet.Tests.Integration/Docs/simple_7.pdf new file mode 100644 index 0000000..77bff86 Binary files /dev/null and b/src/Docnet.Tests.Integration/Docs/simple_7.pdf differ diff --git a/src/Docnet.Tests.Integration/PageReaderTests.cs b/src/Docnet.Tests.Integration/PageReaderTests.cs index 4d17e1e..7311543 100644 --- a/src/Docnet.Tests.Integration/PageReaderTests.cs +++ b/src/Docnet.Tests.Integration/PageReaderTests.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.IO; using System.Linq; using System.Threading.Tasks; using Docnet.Core.Converters; @@ -110,6 +111,11 @@ public void PageIndex_WhenCalled_ShouldReturnCorrectIndex(Input type) ExecuteForDocument(type, "Docs/simple_0.pdf", null, 10, 10, index, pageReader => { + if (pageReader == null) + { + throw new ArgumentNullException(nameof(pageReader)); + } + Assert.Equal(index, pageReader.PageIndex); }); } @@ -409,6 +415,63 @@ public void WriteImageToBufferWithResultFromArrayPoolNoRenderFlags_WhenCalled_Sh }); } + [Theory] + [InlineData(RenderFlags.RenderAnnotations, "Docs/annotation_1.pdf", null, 0, 0)] + [InlineData(RenderFlags.RenderAnnotations, "Docs/annotation_1.pdf", null, 0, 1)] + [InlineData(RenderFlags.RenderAnnotations, "Docs/annotation_1.pdf", null, 0, 2)] + [InlineData(RenderFlags.RenderAnnotations, "Docs/annotation_1.pdf", null, 0, 3)] + [InlineData(RenderFlags.RenderAnnotations, "Docs/annotation_1.pdf", null, 0, 4)] + [InlineData(RenderFlags.RenderAnnotations, "Docs/annotation_1.pdf", null, 0, 5)] + public void Parallel_GetImage_WhenCalledWithFlags_ShouldWork(RenderFlags flags, string filePath, string password, int pageIndex, int run) + { + var numbers = Enumerable.Range(1, 1000); + + Parallel.ForEach(numbers, i => + { + ExecuteForDocument(Input.FromFile, filePath, password, 1, pageIndex, pageReader => + { + var bytes = pageReader.GetImage(flags).ToArray(); + + Assert.NotEmpty(bytes); + + // WriteToPPM($"{run}_{i}_image", bytes, pageReader.GetPageHeight(), pageReader.GetPageWidth()); + }); + }); + } + + /// + /// BGRA data to PPM file + /// Alpha channel discarded + /// https://netpbm.sourceforge.net/doc/ppm.html + /// + private static void WriteToPPM(string fileName, byte[] data, int height, int width) + { + fileName = $"{fileName}.ppm"; + + using (var writer = new StreamWriter(fileName)) + { + writer.WriteLine("P6"); + writer.WriteLine($"{width} {height}"); + writer.WriteLine("255"); + } + + using var writerB = new BinaryWriter(new FileStream(fileName, FileMode.Append)); + + for (var i = 0; i < data.Length / 4; i++) + { + var j = i * 4; + + var blue = data[j]; + var green = data[j + 1]; + var red = data[j + 2]; + var alpha = data[j + 3]; + + writerB.Write((byte)((green * alpha + byte.MaxValue * (255 - alpha)) >> 8)); // G + writerB.Write((byte)((blue * alpha + byte.MaxValue * (255 - alpha)) >> 8)); // B + writerB.Write((byte)((red * alpha + byte.MaxValue * (255 - alpha)) >> 8)); // R + } + } + private static int GetNonZeroByteCount(Input type, string filePath, LibFixture fixture) { using (var reader = fixture.GetDocReader(type, filePath, null, 1000, 1000))