Skip to content

Commit dc1cf12

Browse files
committed
Simplify API: Replace LoadFromStreamEx with improved LoadFromStream
- Remove LoadFromStreamEx method (was added today) - Replace LoadFromStream implementation with efficient streaming version - LoadFromStream now uses FPDF_LoadCustomDocument for true streaming - Add AOwnsStream parameter to LoadFromStream - Remove FMemoryBuffer field (no longer needed) - Update all tests to use new LoadFromStream signature - Update examples and documentation - All 19 tests passing
1 parent 33ba567 commit dc1cf12

File tree

5 files changed

+43
-180
lines changed

5 files changed

+43
-180
lines changed

examples/StreamLoadingComparison.pas

Lines changed: 22 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
unit StreamLoadingComparison;
22

33
{*******************************************************************************
4-
Demonstrates the three different methods for loading PDFs in DX.Pdfium4D:
4+
Demonstrates the two different methods for loading PDFs in DX.Pdfium4D:
55
1. LoadFromFile - Direct file loading
6-
2. LoadFromStream - Legacy stream loading (loads entire PDF into memory)
7-
3. LoadFromStreamEx - True streaming support (efficient, on-demand loading)
6+
2. LoadFromStream - Efficient streaming support (on-demand loading)
87
*******************************************************************************}
98

109
interface
@@ -24,14 +23,9 @@ TPdfLoadingExamples = class
2423
class procedure LoadFromFileExample(APdfViewer: TPdfViewer);
2524

2625
/// <summary>
27-
/// Method 2: Load from stream - LEGACY (loads entire PDF into memory)
26+
/// Method 2: Load from stream with efficient streaming support
2827
/// </summary>
29-
class procedure LoadFromStreamLegacyExample(APdfViewer: TPdfViewer);
30-
31-
/// <summary>
32-
/// Method 3: Load from stream - NEW (true streaming, efficient)
33-
/// </summary>
34-
class procedure LoadFromStreamExExample(APdfViewer: TPdfViewer);
28+
class procedure LoadFromStreamExample(APdfViewer: TPdfViewer);
3529

3630
/// <summary>
3731
/// Comparison: Memory usage for different methods
@@ -50,69 +44,39 @@ class procedure TPdfLoadingExamples.LoadFromFileExample(APdfViewer: TPdfViewer);
5044
// - PDFium handles file access internally
5145
// - Implementation unknown (could be memory-mapped, buffered, or streaming)
5246
// - Best for: Local files that already exist on disk
53-
47+
5448
APdfViewer.LoadFromFile('C:\Documents\report.pdf');
55-
56-
// That's it! No stream management needed.
57-
end;
5849

59-
class procedure TPdfLoadingExamples.LoadFromStreamLegacyExample(APdfViewer: TPdfViewer);
60-
var
61-
LStream: TMemoryStream;
62-
begin
63-
// ⚠️ Method 2: LoadFromStream (LEGACY - DEPRECATED)
64-
// - Loads ENTIRE stream into memory (TBytes buffer)
65-
// - Buffer remains in memory for document lifetime
66-
// - Uses FPDF_LoadMemDocument internally
67-
// - Best for: Small PDFs only (<10 MB)
68-
// - NOT recommended for large files!
69-
70-
LStream := TMemoryStream.Create;
71-
try
72-
// Example: Load from HTTP
73-
// LHttpClient.Get('https://example.com/document.pdf', LStream);
74-
LStream.LoadFromFile('C:\Documents\report.pdf');
75-
LStream.Position := 0;
76-
77-
// ⚠️ WARNING: Entire stream is copied into internal buffer!
78-
APdfViewer.LoadFromStream(LStream);
79-
80-
// Stream can be freed immediately - data is already copied
81-
finally
82-
LStream.Free;
83-
end;
84-
85-
// Memory impact: PDF size + internal buffer = 2x PDF size (temporarily)
86-
// After stream is freed: PDF size remains in TPdfDocument.FMemoryBuffer
50+
// That's it! No stream management needed.
8751
end;
8852

89-
class procedure TPdfLoadingExamples.LoadFromStreamExExample(APdfViewer: TPdfViewer);
53+
class procedure TPdfLoadingExamples.LoadFromStreamExample(APdfViewer: TPdfViewer);
9054
var
9155
LStream: TMemoryStream;
9256
begin
93-
// ✅ Method 3: LoadFromStreamEx (RECOMMENDED for streams)
57+
// ✅ Method 2: LoadFromStream (RECOMMENDED for streams)
9458
// - TRUE streaming support via FPDF_LoadCustomDocument
9559
// - PDFium reads blocks on-demand via callback
9660
// - Stream is NOT duplicated in memory
9761
// - Stream must remain valid for document lifetime
9862
// - Best for: Large PDFs, network streams, memory-constrained scenarios
99-
63+
10064
LStream := TMemoryStream.Create;
101-
65+
10266
// Example: Load from HTTP, database, encrypted container, etc.
10367
LStream.LoadFromFile('C:\Documents\large_report.pdf');
10468
LStream.Position := 0;
105-
69+
10670
// Option A: Keep ownership of stream (you manage lifetime)
107-
APdfViewer.LoadFromStreamEx(LStream, False); // AOwnsStream = False
71+
APdfViewer.LoadFromStream(LStream, False); // AOwnsStream = False
10872
// You MUST keep LStream alive until document is closed!
10973
// You MUST free LStream yourself later
110-
74+
11175
// Option B: Transfer ownership to viewer (recommended)
112-
// APdfViewer.LoadFromStreamEx(LStream, True); // AOwnsStream = True
76+
// APdfViewer.LoadFromStream(LStream, True); // AOwnsStream = True
11377
// Viewer will free the stream when document is closed
11478
// DO NOT free LStream yourself!
115-
79+
11680
// Memory impact: Only 1x PDF size (the stream itself)
11781
// PDFium reads blocks on-demand - no duplication!
11882
end;
@@ -130,25 +94,21 @@ class procedure TPdfLoadingExamples.CompareMemoryUsage;
13094
// │ LoadFromFile │ Unknown* │ ✅ Good for local │
13195
// │ │ │ files │
13296
// ├─────────────────────────────────────────────────────────────┤
133-
// │ LoadFromStream │ ~200 MB │ ⚠️ Only for small │
134-
// │ (Legacy) │ (2x during load)│ PDFs (<10 MB) │
135-
// │ │ ~100 MB after │ │
136-
// ├─────────────────────────────────────────────────────────────┤
137-
// │ LoadFromStreamEx │ ~100 MB │ ✅ Best for streams │
138-
// │ (Recommended) │ (1x, no copy) │ and large files │
97+
// │ LoadFromStream │ ~100 MB │ ✅ Best for streams │
98+
// │ │ (1x, no copy) │ and large files │
13999
// └─────────────────────────────────────────────────────────────┘
140100
//
141101
// * PDFium's internal implementation is not documented
142102
// Could be memory-mapped, buffered, or streaming
143-
103+
144104
LDocument := TPdfDocument.Create;
145105
LStream := TMemoryStream.Create;
146106
try
147107
LStream.LoadFromFile('large_file.pdf');
148-
149-
// Use LoadFromStreamEx for efficient streaming
150-
LDocument.LoadFromStreamEx(LStream, False);
151-
108+
109+
// Use LoadFromStream for efficient streaming
110+
LDocument.LoadFromStream(LStream, False);
111+
152112
// Stream is NOT duplicated - PDFium reads on-demand
153113
// Memory usage = stream size only
154114
finally

src/DX.Pdf.Document.pas

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ TPdfDocument = class
108108
FHandle: FPDF_DOCUMENT;
109109
FPageCount: Integer;
110110
FFileName: string;
111-
FMemoryBuffer: TBytes; // Buffer must remain valid while document is open!
112111
FStreamAdapter: TPdfStreamAdapter; // Stream adapter must remain valid while document is open!
113112
public
114113
constructor Create;
@@ -120,17 +119,7 @@ TPdfDocument = class
120119
procedure LoadFromFile(const AFileName: string; const APassword: string = '');
121120

122121
/// <summary>
123-
/// Loads a PDF document from a stream (legacy method - loads entire stream into memory)
124-
/// </summary>
125-
/// <remarks>
126-
/// DEPRECATED: Use LoadFromStreamEx for efficient streaming support.
127-
/// This method reads the entire stream content into memory and keeps it for the lifetime
128-
/// of the document. For large PDFs, use LoadFromStreamEx or LoadFromFile instead.
129-
/// </remarks>
130-
procedure LoadFromStream(AStream: TStream; const APassword: string = '');
131-
132-
/// <summary>
133-
/// Loads a PDF document from a stream with true streaming support (recommended)
122+
/// Loads a PDF document from a stream with efficient streaming support
134123
/// </summary>
135124
/// <remarks>
136125
/// This method uses PDFium's custom file access API for efficient streaming.
@@ -141,7 +130,7 @@ TPdfDocument = class
141130
/// <param name="AStream">Source stream (must support seeking)</param>
142131
/// <param name="AOwnsStream">If true, the document takes ownership and will free the stream on Close</param>
143132
/// <param name="APassword">Optional password for encrypted PDFs</param>
144-
procedure LoadFromStreamEx(AStream: TStream; AOwnsStream: Boolean = False; const APassword: string = '');
133+
procedure LoadFromStream(AStream: TStream; AOwnsStream: Boolean = False; const APassword: string = '');
145134

146135
/// <summary>
147136
/// Closes the currently loaded document
@@ -342,7 +331,6 @@ constructor TPdfDocument.Create;
342331
FHandle := nil;
343332
FPageCount := 0;
344333
FFileName := '';
345-
SetLength(FMemoryBuffer, 0);
346334
FStreamAdapter := nil;
347335
TPdfLibrary.Initialize;
348336
end;
@@ -383,40 +371,7 @@ procedure TPdfDocument.LoadFromFile(const AFileName: string; const APassword: st
383371
FPageCount := FPDF_GetPageCount(FHandle);
384372
end;
385373

386-
procedure TPdfDocument.LoadFromStream(AStream: TStream; const APassword: string = '');
387-
var
388-
LPasswordAnsi: AnsiString;
389-
LError: Cardinal;
390-
begin
391-
Close;
392-
393-
if AStream = nil then
394-
raise EPdfLoadException.Create('Stream is nil');
395-
396-
// IMPORTANT: Buffer must remain valid while document is open!
397-
// Store in FMemoryBuffer field instead of local variable
398-
SetLength(FMemoryBuffer, AStream.Size);
399-
AStream.Position := 0;
400-
AStream.ReadBuffer(FMemoryBuffer[0], AStream.Size);
401-
402-
if APassword <> '' then
403-
LPasswordAnsi := AnsiString(APassword)
404-
else
405-
LPasswordAnsi := '';
406-
407-
FHandle := FPDF_LoadMemDocument(@FMemoryBuffer[0], Length(FMemoryBuffer), FPDF_BYTESTRING(PAnsiChar(LPasswordAnsi)));
408-
409-
if FHandle = nil then
410-
begin
411-
LError := FPDF_GetLastError;
412-
raise EPdfLoadException.CreateFmt('Failed to load PDF from stream: %s', [FPDF_ErrorToString(LError)]);
413-
end;
414-
415-
FFileName := '';
416-
FPageCount := FPDF_GetPageCount(FHandle);
417-
end;
418-
419-
procedure TPdfDocument.LoadFromStreamEx(AStream: TStream; AOwnsStream: Boolean; const APassword: string);
374+
procedure TPdfDocument.LoadFromStream(AStream: TStream; AOwnsStream: Boolean; const APassword: string);
420375
var
421376
LPasswordAnsi: AnsiString;
422377
LError: Cardinal;
@@ -457,7 +412,6 @@ procedure TPdfDocument.Close;
457412
FPageCount := 0;
458413
FFileName := '';
459414
end;
460-
SetLength(FMemoryBuffer, 0); // Free memory buffer
461415
FreeAndNil(FStreamAdapter); // Free stream adapter (and optionally the stream)
462416
end;
463417

src/DX.Pdf.Viewer.FMX.pas

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,7 @@ TPdfViewer = class(TControl)
7777
procedure LoadFromFile(const AFileName: string; const APassword: string = '');
7878

7979
/// <summary>
80-
/// Loads a PDF document from a stream (legacy method - loads entire stream into memory)
81-
/// </summary>
82-
/// <remarks>
83-
/// DEPRECATED: Use LoadFromStreamEx for efficient streaming support.
84-
/// This method reads the entire stream content into memory and keeps it for the lifetime
85-
/// of the document. For large PDFs, use LoadFromStreamEx or LoadFromFile instead.
86-
/// </remarks>
87-
procedure LoadFromStream(AStream: TStream; const APassword: string = '');
88-
89-
/// <summary>
90-
/// Loads a PDF document from a stream with true streaming support (recommended)
80+
/// Loads a PDF document from a stream with efficient streaming support
9181
/// </summary>
9282
/// <remarks>
9383
/// This method uses PDFium's custom file access API for efficient streaming.
@@ -98,7 +88,7 @@ TPdfViewer = class(TControl)
9888
/// <param name="AStream">Source stream (must support seeking)</param>
9989
/// <param name="AOwnsStream">If true, the viewer takes ownership and will free the stream on Close</param>
10090
/// <param name="APassword">Optional password for encrypted PDFs</param>
101-
procedure LoadFromStreamEx(AStream: TStream; AOwnsStream: Boolean = False; const APassword: string = '');
91+
procedure LoadFromStream(AStream: TStream; AOwnsStream: Boolean = False; const APassword: string = '');
10292

10393
/// <summary>
10494
/// Closes the currently loaded document
@@ -304,20 +294,10 @@ procedure TPdfViewer.LoadFromFile(const AFileName: string; const APassword: stri
304294
FCurrentPageIndex := -1;
305295
end;
306296

307-
procedure TPdfViewer.LoadFromStream(AStream: TStream; const APassword: string = '');
308-
begin
309-
Close;
310-
FDocument.LoadFromStream(AStream, APassword);
311-
if FDocument.PageCount > 0 then
312-
SetCurrentPageIndex(0)
313-
else
314-
FCurrentPageIndex := -1;
315-
end;
316-
317-
procedure TPdfViewer.LoadFromStreamEx(AStream: TStream; AOwnsStream: Boolean; const APassword: string);
297+
procedure TPdfViewer.LoadFromStream(AStream: TStream; AOwnsStream: Boolean; const APassword: string);
318298
begin
319299
Close;
320-
FDocument.LoadFromStreamEx(AStream, AOwnsStream, APassword);
300+
FDocument.LoadFromStream(AStream, AOwnsStream, APassword);
321301
if FDocument.PageCount > 0 then
322302
SetCurrentPageIndex(0)
323303
else

0 commit comments

Comments
 (0)