Table of Contents

Interface IPdfEditor

Namespace
DocToolkit.Extensions.DependencyInjection
Assembly
DocToolkit.Extensions.DependencyInjection.dll

Operations on a PDF that already exists — page count, merge, page extraction and document information. Registered by AddDocToolkit(IServiceCollection, Action<DocToolkitOptions>?).

public interface IPdfEditor

Remarks

The only injectable service here that reads a PDF; every other one writes or renders. Nothing on it re-renders, so pages keep the text, fonts and images they arrived with.

Methods

ExtractImages(byte[])

Each page's images, in document order. [0] is page 1.

IReadOnlyList<IReadOnlyList<PdfImage>> ExtractImages(byte[] pdf)

Parameters

pdf byte[]

Returns

IReadOnlyList<IReadOnlyList<PdfImage>>

Remarks

A page with no images returns an empty list, which is the ordinary case for a text document rather than a failure.

An image whose pixels could not be re-encoded arrives with a null PdfImage.Png rather than throwing, so one image in an unusual colour space does not cost the caller every other image in the document. Check that property before using it.

The bytes are a re-encoding, not the embedded file. They will not be byte-identical to an original PNG or JPEG, and the byte count usually differs. PdfImage.PixelWidth is the stored pixel size; Bounds.Width is how large the page draws it, in points.

Exceptions

ArgumentNullException

pdf is null.

ArgumentException

pdf is empty.

DocumentConversionException

The bytes are not a readable PDF, or it requires a password to open.

ExtractImagesAsync(Stream, CancellationToken)

Each page's images, in document order. [0] is page 1.

Task<IReadOnlyList<IReadOnlyList<PdfImage>>> ExtractImagesAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream
ct CancellationToken

Returns

Task<IReadOnlyList<IReadOnlyList<PdfImage>>>

Remarks

A page with no images returns an empty list, which is the ordinary case for a text document rather than a failure.

An image whose pixels could not be re-encoded arrives with a null PdfImage.Png rather than throwing, so one image in an unusual colour space does not cost the caller every other image in the document. Check that property before using it.

The bytes are a re-encoding, not the embedded file. They will not be byte-identical to an original PNG or JPEG, and the byte count usually differs. PdfImage.PixelWidth is the stored pixel size; Bounds.Width is how large the page draws it, in points.

Exceptions

ArgumentNullException

pdf is null.

ArgumentException

pdf is empty.

DocumentConversionException

The bytes are not a readable PDF, or it requires a password to open.

ArgumentException

source is not readable or was empty.

OperationCanceledException

ct was cancelled.

ExtractPages(byte[], int, int)

A new document holding count pages starting at firstPage.

byte[] ExtractPages(byte[] pdf, int firstPage, int count)

Parameters

pdf byte[]

The document to take pages out of. It is not modified.

firstPage int

1-based, because that is how a reader numbers pages.

count int

How many pages to take, starting at firstPage.

Returns

byte[]

Exceptions

ArgumentNullException

pdf is null.

ArgumentOutOfRangeException

The range is not entirely inside the document.

DocumentConversionException

The bytes are not a readable PDF.

ExtractPagesAsync(Stream, int, int, Stream, CancellationToken)

A new document holding count pages starting at firstPage.

Task ExtractPagesAsync(Stream source, int firstPage, int count, Stream destination, CancellationToken ct = default)

Parameters

source Stream
firstPage int

1-based, because that is how a reader numbers pages.

count int

How many pages to take, starting at firstPage.

destination Stream
ct CancellationToken

Returns

Task

Exceptions

ArgumentNullException

pdf is null.

ArgumentOutOfRangeException

The range is not entirely inside the document.

DocumentConversionException

The bytes are not a readable PDF.

OperationCanceledException

ct was cancelled.

ExtractText(byte[])

Each page's text, in document order. [0] is page 1.

IReadOnlyList<string> ExtractText(byte[] pdf)

Parameters

pdf byte[]

Returns

IReadOnlyList<string>

Remarks

The index is a list position, not a page number — deliberately unlike this interface's firstPage parameters, which are 1-based because that is how a reader numbers pages.

A page with no text layer returns an empty string. A scanned document is images, so this returns one empty string per page for one — that is what the file contains, not a failure, and OCR is out of scope. This is the commonest surprise in PDF text extraction, so it is stated here rather than left to be discovered.

Exceptions

ArgumentNullException

pdf is null.

ArgumentException

pdf is empty.

DocumentConversionException

The bytes are not a readable PDF, or it requires a password to open. A PDF that is merely permission-restricted (an owner password with no user password) is not covered by that: it opens with the default empty password and its text comes back like any other PDF.

ExtractTextAsync(Stream, CancellationToken)

Each page's text, in document order. [0] is page 1.

Task<IReadOnlyList<string>> ExtractTextAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream
ct CancellationToken

Returns

Task<IReadOnlyList<string>>

Remarks

The index is a list position, not a page number — deliberately unlike this interface's firstPage parameters, which are 1-based because that is how a reader numbers pages.

A page with no text layer returns an empty string. A scanned document is images, so this returns one empty string per page for one — that is what the file contains, not a failure, and OCR is out of scope. This is the commonest surprise in PDF text extraction, so it is stated here rather than left to be discovered.

Exceptions

ArgumentNullException

pdf is null.

ArgumentException

pdf is empty.

DocumentConversionException

The bytes are not a readable PDF, or it requires a password to open. A PDF that is merely permission-restricted (an owner password with no user password) is not covered by that: it opens with the default empty password and its text comes back like any other PDF.

ArgumentException

source is not readable or was empty.

OperationCanceledException

ct was cancelled.

ExtractWords(byte[])

Each page's words with their positions, in document order. [0] is page 1.

IReadOnlyList<IReadOnlyList<PdfWord>> ExtractWords(byte[] pdf)

Parameters

pdf byte[]

Returns

IReadOnlyList<IReadOnlyList<PdfWord>>

Remarks

ExtractText(byte[]) answers what a page says; this answers where it says it — for locating a total on an invoice, or checking a stamp landed inside the margin.

Coordinates are PDF user-space points measured from the page's BOTTOM-left, so a word near the top of A4 has a Bounds.Bottom near 842 rather than near 0. That is the PDF convention rather than a screen one, and it is the commonest surprise when these values are first plotted.

A page with no text layer returns an empty list. A scanned document is images, so this returns one empty list per page for one — that is what the file contains, not a failure, and OCR is out of scope.

Exceptions

ArgumentNullException

pdf is null.

ArgumentException

pdf is empty.

DocumentConversionException

The bytes are not a readable PDF, or it requires a password to open.

ExtractWordsAsync(Stream, CancellationToken)

Each page's words with their positions, in document order. [0] is page 1.

Task<IReadOnlyList<IReadOnlyList<PdfWord>>> ExtractWordsAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream
ct CancellationToken

Returns

Task<IReadOnlyList<IReadOnlyList<PdfWord>>>

Remarks

ExtractText(byte[]) answers what a page says; this answers where it says it — for locating a total on an invoice, or checking a stamp landed inside the margin.

Coordinates are PDF user-space points measured from the page's BOTTOM-left, so a word near the top of A4 has a Bounds.Bottom near 842 rather than near 0. That is the PDF convention rather than a screen one, and it is the commonest surprise when these values are first plotted.

A page with no text layer returns an empty list. A scanned document is images, so this returns one empty list per page for one — that is what the file contains, not a failure, and OCR is out of scope.

Exceptions

ArgumentNullException

pdf is null.

ArgumentException

pdf is empty.

DocumentConversionException

The bytes are not a readable PDF, or it requires a password to open.

ArgumentException

source is not readable or was empty.

OperationCanceledException

ct was cancelled.

InsertPages(byte[], byte[], int)

A copy of target with every page of source inserted so that the first of them becomes page atPage.

byte[] InsertPages(byte[] target, byte[] source, int atPage)

Parameters

target byte[]

The document to insert into. It is not modified.

source byte[]

The document whose pages are inserted. It is not modified.

atPage int

1-based position the first inserted page will occupy. PageCount + 1 appends, which is deliberately allowed - it is the obvious way to say "after everything".

Returns

byte[]

Exceptions

ArgumentNullException

Either document is null.

ArgumentOutOfRangeException

atPage is below 1 or more than one past the last page.

DocumentConversionException

The bytes are not a readable PDF.

InsertPagesAsync(Stream, Stream, int, Stream, CancellationToken)

A copy of target with every page of source inserted so that the first of them becomes page atPage.

Task InsertPagesAsync(Stream target, Stream source, int atPage, Stream destination, CancellationToken ct = default)

Parameters

target Stream

The document to insert into. It is not modified.

source Stream

The document whose pages are inserted. It is not modified.

atPage int

1-based position the first inserted page will occupy. PageCount + 1 appends, which is deliberately allowed - it is the obvious way to say "after everything".

destination Stream
ct CancellationToken

Returns

Task

Exceptions

ArgumentNullException

Either document is null.

ArgumentOutOfRangeException

atPage is below 1 or more than one past the last page.

DocumentConversionException

The bytes are not a readable PDF.

OperationCanceledException

ct was cancelled.

Merge(IEnumerable<byte[]>)

Joins pdfs into one document, keeping the order given.

byte[] Merge(IEnumerable<byte[]> pdfs)

Parameters

pdfs IEnumerable<byte[]>

Returns

byte[]

Exceptions

ArgumentNullException

pdfs is null.

ArgumentException

pdfs is empty; merging nothing would produce a zero-page PDF, which several readers refuse to open.

DocumentConversionException

One of the inputs is not a readable PDF.

MergeAsync(IEnumerable<Stream>, Stream, CancellationToken)

Joins pdfs into one document, keeping the order given.

Task MergeAsync(IEnumerable<Stream> sources, Stream destination, CancellationToken ct = default)

Parameters

sources IEnumerable<Stream>
destination Stream
ct CancellationToken

Returns

Task

Exceptions

ArgumentNullException

pdfs is null.

ArgumentException

pdfs is empty; merging nothing would produce a zero-page PDF, which several readers refuse to open.

DocumentConversionException

One of the inputs is not a readable PDF.

OperationCanceledException

ct was cancelled.

PageCount(byte[])

The number of pages in pdf.

int PageCount(byte[] pdf)

Parameters

pdf byte[]

Returns

int

Exceptions

ArgumentNullException

pdf is null.

DocumentConversionException

The bytes are not a readable PDF.

PageCountAsync(Stream, CancellationToken)

The number of pages in pdf.

Task<int> PageCountAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream
ct CancellationToken

Returns

Task<int>

Exceptions

ArgumentNullException

pdf is null.

DocumentConversionException

The bytes are not a readable PDF.

OperationCanceledException

ct was cancelled.

Protect(byte[], PdfProtection)

A copy of pdf encrypted with the passwords and permissions in protection.

byte[] Protect(byte[] pdf, PdfProtection protection)

Parameters

pdf byte[]
protection PdfProtection

Returns

byte[]

Remarks

Set UserPassword if the content must not be read. An owner password alone leaves the document openable by anyone - the permissions are a request a reader is asked to honour, not a lock.

The result cannot be passed back into the other members here; use Unprotect(byte[], string) first.

Exceptions

ArgumentNullException

pdf or protection is null.

ArgumentException

pdf is empty, or neither password is set.

DocumentConversionException

It could not be read or written.

ProtectAsync(Stream, Stream, PdfProtection, CancellationToken)

Reads a PDF from source and writes the encrypted copy to destination. Neither stream is disposed, closed or sought.

Task ProtectAsync(Stream source, Stream destination, PdfProtection protection, CancellationToken ct = default)

Parameters

source Stream
destination Stream
protection PdfProtection
ct CancellationToken

Returns

Task

Exceptions

ArgumentNullException

Either stream is null, or protection is null.

ArgumentException

A stream is unusable, or neither password is set.

OperationCanceledException

ct was cancelled.

DocumentConversionException

It could not be read or written.

ReadMetadata(byte[])

The document information pdf carries.

PdfMetadata ReadMetadata(byte[] pdf)

Parameters

pdf byte[]

Returns

PdfMetadata

Remarks

An absent entry reads back as null rather than an empty string, so "no title" stays distinguishable from "a title deliberately set to empty".

Exceptions

ArgumentNullException

pdf is null.

DocumentConversionException

The bytes are not a readable PDF.

ReadMetadataAsync(Stream, CancellationToken)

The document information pdf carries.

Task<PdfMetadata> ReadMetadataAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream

The stream the PDF is read from.

ct CancellationToken

Cancels the read.

Returns

Task<PdfMetadata>

Remarks

source is read to its end and is neither disposed, closed nor sought.

Exceptions

ArgumentNullException

source is null.

ArgumentException

source is not readable or held no bytes.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The PDF could not be read.

RemovePages(byte[], int, int)

A new document with count pages removed, starting at firstPage. The complement of ExtractPages(byte[], int, int): that one keeps the range, this one keeps everything else.

byte[] RemovePages(byte[] pdf, int firstPage, int count)

Parameters

pdf byte[]

The document to take pages out of. It is not modified.

firstPage int

1-based, because that is how a reader numbers pages.

count int

How many pages to drop, starting at firstPage.

Returns

byte[]

Exceptions

ArgumentNullException

pdf is null.

ArgumentOutOfRangeException

The range is not entirely inside the document, or it covers every page - removing everything would leave a zero-page file.

DocumentConversionException

The bytes are not a readable PDF.

RemovePagesAsync(Stream, int, int, Stream, CancellationToken)

A new document with count pages removed, starting at firstPage. The complement of ExtractPages(byte[], int, int): that one keeps the range, this one keeps everything else.

Task RemovePagesAsync(Stream source, int firstPage, int count, Stream destination, CancellationToken ct = default)

Parameters

source Stream
firstPage int

1-based, because that is how a reader numbers pages.

count int

How many pages to drop, starting at firstPage.

destination Stream
ct CancellationToken

Returns

Task

Exceptions

ArgumentNullException

pdf is null.

ArgumentOutOfRangeException

The range is not entirely inside the document, or it covers every page - removing everything would leave a zero-page file.

DocumentConversionException

The bytes are not a readable PDF.

OperationCanceledException

ct was cancelled.

ReorderPages(byte[], IEnumerable<int>)

A copy of pdf with its pages in the order given by order, which holds 1-based page numbers.

byte[] ReorderPages(byte[] pdf, IEnumerable<int> order)

Parameters

pdf byte[]

The document to reorder. It is not modified.

order IEnumerable<int>

A permutation of every page — the same pages, in a different order. Not a subset, and no repeats.

Returns

byte[]

Exceptions

ArgumentNullException

Either argument is null.

ArgumentException

order is not a permutation of 1..PageCount.

DocumentConversionException

The bytes are not a readable PDF.

ReorderPagesAsync(Stream, IEnumerable<int>, Stream, CancellationToken)

A copy of pdf with its pages in the order given by order, which holds 1-based page numbers.

Task ReorderPagesAsync(Stream source, IEnumerable<int> order, Stream destination, CancellationToken ct = default)

Parameters

source Stream
order IEnumerable<int>

A permutation of every page — the same pages, in a different order. Not a subset, and no repeats.

destination Stream
ct CancellationToken

Returns

Task

Exceptions

ArgumentNullException

Either argument is null.

ArgumentException

order is not a permutation of 1..PageCount.

DocumentConversionException

The bytes are not a readable PDF.

OperationCanceledException

ct was cancelled.

RotatePages(byte[], int, int, int)

A copy of pdf with count pages turned clockwise by degrees, starting at firstPage.

byte[] RotatePages(byte[] pdf, int firstPage, int count, int degrees)

Parameters

pdf byte[]

The document to turn pages in. It is not modified.

firstPage int

1-based, because that is how a reader numbers pages.

count int

How many pages to turn, starting at firstPage.

degrees int

How far to turn, clockwise, as a multiple of 90. Negative turns anticlockwise.

This is relative, not absolute — it adds to whatever rotation the page already carries, so calling it twice with 90 leaves the page at 180.

Returns

byte[]

Exceptions

ArgumentNullException

pdf is null.

ArgumentOutOfRangeException

The range is not entirely inside the document, or degrees is not a multiple of 90.

DocumentConversionException

The bytes are not a readable PDF.

RotatePagesAsync(Stream, int, int, int, Stream, CancellationToken)

A copy of pdf with count pages turned clockwise by degrees, starting at firstPage.

Task RotatePagesAsync(Stream source, int firstPage, int count, int degrees, Stream destination, CancellationToken ct = default)

Parameters

source Stream
firstPage int

1-based, because that is how a reader numbers pages.

count int

How many pages to turn, starting at firstPage.

degrees int

How far to turn, clockwise, as a multiple of 90. Negative turns anticlockwise.

This is relative, not absolute — it adds to whatever rotation the page already carries, so calling it twice with 90 leaves the page at 180.

destination Stream
ct CancellationToken

Returns

Task

Exceptions

ArgumentNullException

pdf is null.

ArgumentOutOfRangeException

The range is not entirely inside the document, or degrees is not a multiple of 90.

DocumentConversionException

The bytes are not a readable PDF.

OperationCanceledException

ct was cancelled.

Unprotect(byte[], string)

A copy of pdf with its encryption removed.

byte[] Unprotect(byte[] pdf, string password)

Parameters

pdf byte[]
password string

Returns

byte[]

Remarks

If the document has an owner password, that is the one required here, even if you also know the user password: removing protection is a modification, which the PDF format reserves for the owner.

Exceptions

ArgumentNullException

pdf or password is null.

ArgumentException

pdf is empty.

DocumentConversionException

The password was wrong, or it could not be read.

UnprotectAsync(Stream, Stream, string, CancellationToken)

Reads an encrypted PDF from source and writes the unprotected copy to destination. Neither stream is disposed, closed or sought.

Task UnprotectAsync(Stream source, Stream destination, string password, CancellationToken ct = default)

Parameters

source Stream
destination Stream
password string
ct CancellationToken

Returns

Task

Exceptions

ArgumentNullException

Either stream is null, or password is null.

ArgumentException

A stream is unusable.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The password was wrong, or it could not be read.

WithMetadata(byte[], PdfMetadata)

A copy of pdf carrying metadata.

byte[] WithMetadata(byte[] pdf, PdfMetadata metadata)

Parameters

pdf byte[]
metadata PdfMetadata

Returns

byte[]

Remarks

A null property leaves what the document already had in place, so stamping a title does not silently erase an author. Pass an empty string to clear one.

Exceptions

ArgumentNullException

Either argument is null.

DocumentConversionException

The bytes are not a readable PDF.

WithMetadataAsync(Stream, PdfMetadata, Stream, CancellationToken)

A copy of pdf carrying metadata.

Task WithMetadataAsync(Stream source, PdfMetadata metadata, Stream destination, CancellationToken ct = default)

Parameters

source Stream

The stream the PDF is read from.

metadata PdfMetadata

The properties to stamp.

destination Stream

The stream the updated PDF is written to.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Remarks

source is read to its end and destination is written; neither is disposed, closed or sought, and neither has to be seekable.

Exceptions

ArgumentNullException

An argument is null.

ArgumentException

source is not readable or held no bytes, or destination is not writable.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The PDF could not be read or written.