44using System ;
55using System . IO ;
66using System . Reflection ;
7+ using System . Threading . Tasks ;
78
89namespace Synercoding . FileFormats . Pdf
910{
@@ -19,6 +20,8 @@ public sealed class PdfWriter : IDisposable
1920 private readonly PageTree _pageTree ;
2021 private readonly Catalog _catalog ;
2122
23+ private bool _endingWritten = false ;
24+
2225 /// <summary>
2326 /// Constructor for <see cref="PdfWriter"/>
2427 /// </summary>
@@ -54,13 +57,21 @@ public PdfWriter(Stream stream, bool ownsStream)
5457 /// </summary>
5558 public DocumentInformation DocumentInformation { get ; }
5659
60+ /// <summary>
61+ /// Returns the number of pages already added to the writer
62+ /// </summary>
63+ public int PageCount
64+ => _pageTree . PageCount ;
65+
5766 /// <summary>
5867 /// Set meta information for this document
5968 /// </summary>
6069 /// <param name="infoAction">Action used to set meta data</param>
6170 /// <returns>Returns this <see cref="PdfWriter"/> to chain calls</returns>
6271 public PdfWriter SetDocumentInfo ( Action < DocumentInformation > infoAction )
6372 {
73+ _throwWhenEndingWritten ( ) ;
74+
6475 infoAction ( DocumentInformation ) ;
6576
6677 return this ;
@@ -82,6 +93,8 @@ public PdfWriter AddPage(Action<PdfPage> pageAction)
8293 /// <returns>Returns this <see cref="PdfWriter"/> to chain calls</returns>
8394 public PdfWriter AddPage < T > ( T data , Action < T , PdfPage > pageAction )
8495 {
96+ _throwWhenEndingWritten ( ) ;
97+
8598 using ( var page = new PdfPage ( _tableBuilder , _pageTree ) )
8699 {
87100 pageAction ( data , page ) ;
@@ -92,13 +105,43 @@ public PdfWriter AddPage<T>(T data, Action<T, PdfPage> pageAction)
92105 return this ;
93106 }
94107
108+ /// <summary>
109+ /// Add a page to the pdf file
110+ /// </summary>
111+ /// <param name="pageAction">Action used to setup the page</param>
112+ /// <returns>Returns an awaitable task that resolves into this <see cref="PdfWriter"/> to chain calls</returns>
113+ public async Task < PdfWriter > AddPageAsync ( Func < PdfPage , Task > pageAction )
114+ => await AddPageAsync ( pageAction , static async ( action , page ) => await action ( page ) ) ;
115+
116+ /// <summary>
117+ /// Add a page to the pdf file
118+ /// </summary>
119+ /// <param name="data">Data passed into the action</param>
120+ /// <param name="pageAction">Action used to setup the page</param>
121+ /// <returns>Returns an awaitable task that resolves into this <see cref="PdfWriter"/> to chain calls</returns>
122+ public async Task < PdfWriter > AddPageAsync < T > ( T data , Func < T , PdfPage , Task > pageAction )
123+ {
124+ _throwWhenEndingWritten ( ) ;
125+
126+ using ( var page = new PdfPage ( _tableBuilder , _pageTree ) )
127+ {
128+ await pageAction ( data , page ) ;
129+
130+ page . WriteToStream ( _stream ) ;
131+ }
132+
133+ return this ;
134+ }
135+
95136 /// <summary>
96137 /// Add an <see cref="SixLabors.ImageSharp.Image"/> to the pdf file and get the <see cref="Image"/> reference returned
97138 /// </summary>
98139 /// <param name="image">The image that needs to be added.</param>
99140 /// <returns>The image reference that can be used in pages</returns>
100141 public Image AddImage ( SixLabors . ImageSharp . Image image )
101142 {
143+ _throwWhenEndingWritten ( ) ;
144+
102145 var id = _tableBuilder . ReserveId ( ) ;
103146
104147 var pdfImage = new Image ( id , image ) ;
@@ -123,6 +166,8 @@ public Image AddImage(SixLabors.ImageSharp.Image image)
123166 /// <returns>The image reference that can be used in pages</returns>
124167 public Image AddJpgImageUnsafe ( Stream jpgStream , int originalWidth , int originalHeight )
125168 {
169+ _throwWhenEndingWritten ( ) ;
170+
126171 var id = _tableBuilder . ReserveId ( ) ;
127172
128173 var pdfImage = new Image ( id , jpgStream , originalWidth , originalHeight ) ;
@@ -135,9 +180,17 @@ public Image AddJpgImageUnsafe(Stream jpgStream, int originalWidth, int original
135180 return pdfImage ;
136181 }
137182
138- /// <inheritdoc />
139- public void Dispose ( )
183+ /// <summary>
184+ /// Write the PDF trailer; indicates that the PDF is done.
185+ /// </summary>
186+ /// <remarks>
187+ /// Other calls to this <see cref="PdfWriter"/> will throw or have no effect after call this.
188+ /// </remarks>
189+ public void WriteTrailer ( )
140190 {
191+ if ( _endingWritten )
192+ return ;
193+
141194 _writePageTree ( ) ;
142195
143196 _writeCatalog ( ) ;
@@ -148,12 +201,27 @@ public void Dispose()
148201
149202 _stream . Flush ( ) ;
150203
204+ _endingWritten = true ;
205+ }
206+
207+ /// <inheritdoc />
208+ public void Dispose ( )
209+ {
210+ WriteTrailer ( ) ;
211+
212+ _stream . Flush ( ) ;
213+
151214 if ( _ownsStream )
152215 {
153216 _stream . Dispose ( ) ;
154217 }
155218 }
156219
220+ private void _throwWhenEndingWritten ( )
221+ {
222+ if ( _endingWritten ) throw new InvalidOperationException ( "Can't change document information when PDF trailer is written to the stream." ) ;
223+ }
224+
157225 private static void _writeHeader ( PdfStream stream )
158226 {
159227 stream . WriteByte ( 0x25 ) ; // %
@@ -204,47 +272,27 @@ private void _writePdfEnding()
204272 var xRefTable = _tableBuilder . GetXRefTable ( ) ;
205273 uint xRefPosition = xRefTable . WriteToStream ( _stream ) ;
206274
207- var trailer = new Trailer ( xRefPosition , xRefTable . Section . ObjectCount , _catalog , DocumentInformation ) ;
208- trailer . WriteToStream ( _stream ) ;
275+ _writeTrailer ( _stream , xRefPosition , xRefTable . Section . ObjectCount , _catalog . Reference , DocumentInformation . Reference ) ;
209276 }
210277
211- private readonly struct Trailer
278+ private void _writeTrailer ( PdfStream stream , uint startXRef , int size , PdfReference root , PdfReference documentInfo )
212279 {
213- public Trailer ( uint startXRef , int size , Catalog root , DocumentInformation documentInfo )
214- {
215- StartXRef = startXRef ;
216- Size = size ;
217- Root = root . Reference ;
218- DocumentInfo = documentInfo . Reference ;
219- }
220-
221- public uint StartXRef { get ; }
222- public int Size { get ; }
223- public PdfReference Root { get ; }
224- public PdfReference DocumentInfo { get ; }
225-
226- internal uint WriteToStream ( PdfStream stream )
227- {
228- var position = ( uint ) stream . Position ;
229-
230- stream
231- . Write ( "trailer" )
232- . NewLine ( )
233- . Dictionary ( this , static ( trailer , dictionary ) =>
234- {
235- dictionary
236- . Write ( PdfName . Get ( "Size" ) , trailer . Size )
237- . Write ( PdfName . Get ( "Root" ) , trailer . Root )
238- . Write ( PdfName . Get ( "Info" ) , trailer . DocumentInfo ) ;
239- } )
240- . Write ( "startxref" )
241- . NewLine ( )
242- . Write ( StartXRef )
243- . NewLine ( )
244- . Write ( "%%EOF" ) ;
245-
246- return position ;
247- }
280+ stream
281+ . Write ( "trailer" )
282+ . NewLine ( )
283+ . Dictionary ( ( size , root , documentInfo ) , static ( triple , dictionary ) =>
284+ {
285+ var ( size , root , documentInfo ) = triple ;
286+ dictionary
287+ . Write ( PdfName . Get ( "Size" ) , size )
288+ . Write ( PdfName . Get ( "Root" ) , root )
289+ . Write ( PdfName . Get ( "Info" ) , documentInfo ) ;
290+ } )
291+ . Write ( "startxref" )
292+ . NewLine ( )
293+ . Write ( startXRef )
294+ . NewLine ( )
295+ . Write ( "%%EOF" ) ;
248296 }
249297 }
250298}
0 commit comments