From 694c02cd108c5b9714286ea1e7028be2e7997105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ars=C3=A8ne=20von=20Wyss?= Date: Tue, 29 Jul 2025 01:53:38 +0200 Subject: [PATCH 1/3] Cleaned up parameters for creating a PdfDocument from a stream --- PDFiumSharp/PDFium.cs | 5 ++--- PDFiumSharp/PdfDocument.cs | 10 +++++----- PDFiumSharp/Types/FPDF_FILEACCESS.cs | 8 ++++---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/PDFiumSharp/PDFium.cs b/PDFiumSharp/PDFium.cs index 4f8dd2e..c45e475 100644 --- a/PDFiumSharp/PDFium.cs +++ b/PDFiumSharp/PDFium.cs @@ -122,15 +122,14 @@ public static FPDF_DOCUMENT FPDF_LoadDocument(byte[] data, int index = 0, int co /// /// Loads a PDF document from '' bytes read from a stream. /// - /// /// /// The number of bytes to read from the . /// If the value is equal to or smaller than 0, the stream is read to the end. /// /// /// Pdf password - public static FPDF_DOCUMENT FPDF_LoadDocument(Stream stream, FPDF_FILEREAD fileRead, int count = 0, string password = null) - => FPDF_LoadCustomDocument(fileRead, password); + public static FPDF_DOCUMENT FPDF_LoadDocument(Stream stream, int count = 0, string password = null) + => FPDF_LoadCustomDocument(FPDF_FILEREAD.FromStream(stream, count), password); //public static string FPDF_VIEWERREF_GetName(FPDF_DOCUMENT document, string key) //{ diff --git a/PDFiumSharp/PdfDocument.cs b/PDFiumSharp/PdfDocument.cs index adae6ae..e1dbe82 100644 --- a/PDFiumSharp/PdfDocument.cs +++ b/PDFiumSharp/PdfDocument.cs @@ -138,15 +138,15 @@ public PdfDocument(byte[] data, int index = 0, int count = -1, string password = /// Loads a from '' bytes read from a . /// must be called in order to free unmanaged resources. /// - /// - /// - /// + /// Stream containing the bytes of the PDF document to load. Must remain open and accessible as long as the document is being used. + /// The number of bytes to copy from or a negative value to copy all bytes. + /// Password. /// The number of bytes to read from the . /// If the value is equal to or smaller than 0, the stream is read to the end. /// /// - public PdfDocument(Stream stream, FPDF_FILEREAD fileRead, int count = 0, string password = null) - : this(PDFium.FPDF_LoadDocument(stream, fileRead, count, password)) { } + public PdfDocument(Stream stream, int count = 0, string password = null) + : this(PDFium.FPDF_LoadDocument(stream, count, password)) { } /// /// Closes the and frees unmanaged resources. diff --git a/PDFiumSharp/Types/FPDF_FILEACCESS.cs b/PDFiumSharp/Types/FPDF_FILEACCESS.cs index c2c5526..37e7561 100644 --- a/PDFiumSharp/Types/FPDF_FILEACCESS.cs +++ b/PDFiumSharp/Types/FPDF_FILEACCESS.cs @@ -12,9 +12,9 @@ namespace PDFiumSharp.Types { [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool FileReadBlockHandler(IntPtr ignore, int position, IntPtr buffer, int size); + public delegate bool FileReadBlockHandler(IntPtr param, int position, IntPtr buffer, int size); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool FileWriteBlockHandler(IntPtr ignore, IntPtr data, int size); + public delegate bool FileWriteBlockHandler(IntPtr self, IntPtr data, int size); [StructLayout(LayoutKind.Sequential)] public class FPDF_FILEREAD @@ -26,11 +26,11 @@ public class FPDF_FILEREAD readonly IntPtr _param; - public FPDF_FILEREAD(int fileLength, FileReadBlockHandler readBlock) + public FPDF_FILEREAD(int fileLength, FileReadBlockHandler readBlock, IntPtr param = default) { _fileLength = fileLength; _readBlock = readBlock; - _param = IntPtr.Zero; + _param = param; } public static FPDF_FILEREAD FromStream(Stream stream, int count = 0) From a835705b943c8ba5b7a97c039056fe36301062b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ars=C3=A8ne=20von=20Wyss?= Date: Tue, 29 Jul 2025 01:56:01 +0200 Subject: [PATCH 2/3] Added optimization in FPDF_FILEREAD to direcly copy data from buffer if the stream is a MemoryStream --- PDFiumSharp/Types/FPDF_FILEACCESS.cs | 43 +++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/PDFiumSharp/Types/FPDF_FILEACCESS.cs b/PDFiumSharp/Types/FPDF_FILEACCESS.cs index 37e7561..044115c 100644 --- a/PDFiumSharp/Types/FPDF_FILEACCESS.cs +++ b/PDFiumSharp/Types/FPDF_FILEACCESS.cs @@ -38,17 +38,40 @@ public static FPDF_FILEREAD FromStream(Stream stream, int count = 0) if (count <= 0) count = (int)(stream.Length - stream.Position); var start = stream.Position; + + var memoryStream = stream as MemoryStream; + if (memoryStream != null) { + try { + memoryStream.GetBuffer(); + } catch (UnauthorizedAccessException) { + // Buffer is not public, and thus not directly usable + memoryStream = null; + } + } + byte[] data = null; - FPDF_FILEREAD fileRead = new FPDF_FILEREAD(count, (ignore, position, buffer, size) => - { - stream.Position = start + position; - if (data == null || data.Length < size) - data = new byte[size]; - if (stream.Read(data, 0, size) != size) - return false; - Marshal.Copy(data, 0, buffer, size); - return true; - }); + FPDF_FILEREAD fileRead = memoryStream != null + ? new FPDF_FILEREAD(count, (_, position, buffer, size) => + { + if (position < 0 || size <= 0 || start+position+size > memoryStream.Length) + return false; + var streamBuffer = memoryStream.GetBuffer(); + Marshal.Copy(streamBuffer, (int)start+position, buffer, size); + return true; + }) + : new FPDF_FILEREAD(count, (_, position, buffer, size) => + { + if (position < 0 || size <= 0) + return false; + stream.Seek(start + position, SeekOrigin.Begin); + if (data == null || data.Length < size) + data = new byte[size]; + if (stream.Read(data, 0, size) != size) + return false; + + Marshal.Copy(data, 0, buffer, size); + return true; + }); return fileRead; } } From 670a3ace66339be18153fc1a7a3b61ca25a0c8e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ars=C3=A8ne=20von=20Wyss?= Date: Tue, 29 Jul 2025 17:58:05 +0200 Subject: [PATCH 3/3] Keep reference to FileReadBlockHandler delegate to prevent it from being garbage collected --- PDFiumSharp/PDFium.cs | 6 +++--- PDFiumSharp/PdfDocument.cs | 20 ++++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/PDFiumSharp/PDFium.cs b/PDFiumSharp/PDFium.cs index c45e475..9f3acbe 100644 --- a/PDFiumSharp/PDFium.cs +++ b/PDFiumSharp/PDFium.cs @@ -126,10 +126,10 @@ public static FPDF_DOCUMENT FPDF_LoadDocument(byte[] data, int index = 0, int co /// The number of bytes to read from the . /// If the value is equal to or smaller than 0, the stream is read to the end. /// - /// + /// The FILEREAD structure ready for reading. /// Pdf password - public static FPDF_DOCUMENT FPDF_LoadDocument(Stream stream, int count = 0, string password = null) - => FPDF_LoadCustomDocument(FPDF_FILEREAD.FromStream(stream, count), password); + public static FPDF_DOCUMENT FPDF_LoadDocument(FPDF_FILEREAD fileRead, string password = null) + => FPDF_LoadCustomDocument(fileRead, password); //public static string FPDF_VIEWERREF_GetName(FPDF_DOCUMENT document, string key) //{ diff --git a/PDFiumSharp/PdfDocument.cs b/PDFiumSharp/PdfDocument.cs index e1dbe82..f2dac5f 100644 --- a/PDFiumSharp/PdfDocument.cs +++ b/PDFiumSharp/PdfDocument.cs @@ -19,6 +19,8 @@ public sealed class PdfDocument : NativeWrapper private FPDF_FORMFILLINFO formCallbacks; private GCHandle formCallbacksHandle; + + private FPDF_FILEREAD fileRead; private FPDF_FORMHANDLE form; @@ -139,14 +141,22 @@ public PdfDocument(byte[] data, int index = 0, int count = -1, string password = /// must be called in order to free unmanaged resources. /// /// Stream containing the bytes of the PDF document to load. Must remain open and accessible as long as the document is being used. - /// The number of bytes to copy from or a negative value to copy all bytes. - /// Password. + /// /// The number of bytes to read from the . /// If the value is equal to or smaller than 0, the stream is read to the end. /// - /// + /// Password. public PdfDocument(Stream stream, int count = 0, string password = null) - : this(PDFium.FPDF_LoadDocument(stream, count, password)) { } + : this(FPDF_FILEREAD.FromStream(stream, count), password) { } + + /// + /// Private overload which keeps the FILEREAD reference so that the delegate and its callback stub remain valid for the lifetime of the document. + /// + private PdfDocument(FPDF_FILEREAD fileRead, string password) + : this(PDFium.FPDF_LoadDocument(fileRead, password)) + { + this.fileRead = fileRead; + } /// /// Closes the and frees unmanaged resources. @@ -209,6 +219,8 @@ protected override void Dispose(FPDF_DOCUMENT handle) ((IDisposable)this.Pages).Dispose(); PDFium.FPDF_CloseDocument(handle); + + this.fileRead = null; } } } \ No newline at end of file