Document Processing

PSPDFKit allows splitting and merging of documents as well as annotation flattening using the PSPDFProcessor class.

Page Extraction

PSPDFProcessor can extract pages from one document into another document. You can select to either extract a single page, a range of pages, or even multiple page ranges. In most cases, you want to use PSPDFProcessor#generatePDFFromConfiguration:securityOptions:outputFileURL:progressBlock:error: to create a new PDF document on disk based on a current PSPDFDocument.

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Create default configuration
guard let configuration = PSPDFProcessorConfiguration(document: document) else {
    // Handle failure
    abort()
}
// Change all annotations to be flattened (instead of saved as annotations)
configuration.modifyAnnotations(ofTypes: .all, change: .flatten)

// Only extract pages 0, 1 and 2.
configuration.includeOnlyIndexes(IndexSet(integersIn: 0...2))

do {
    // Start the conversion from `document` to `splitURL`
    try PSPDFProcessor.generatePDF(from: configuration, securityOptions: nil, outputFileURL: splitURL, progressBlock: nil)
} catch {
    // Handle failure
    abort()
}
Copy
1
2
3
4
5
6
7
8
// Create default configuration
PSPDFProcessorConfiguration *configuration = [[PSPDFProcessorConfiguration alloc] initWithDocument:document];
// Change all annotations to be flattened (instead of saved as annotations)
[configuration modifyAnnotationsOfTypes:PSPDFAnnotationTypeAll change:PSPDFAnnotationChangeFlatten];
// Only extract pages 0, 1 and 2.
[configuration includeOnlyIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]];
// Start the conversion from `document` to `splitURL`
[PSPDFProcessor generatePDFFromConfiguration:configuration securityOptions:nil outputFileURL:splitURL progressBlock:nil error:NULL];

Note: In PSPDFKit 5.2 and earlier, this was called generatePDFFromDocument:pageRanges:outputFileURL:options:progressBlock:error: and was configured via a dictionary with predefined keys. We replaced this with PSPDFProcessorConfiguration to have an API that is simpler and easier to understand, and at the same time we made it more powerful.

Merging Multiple Documents

A document can be created from one or multiple files - so the processor can be used to merge multiple files into one without having to configure anything on PSPDFProcessorConfiguration. To achieve this you have to create multiple data providers and then initialize the PSPDFDocument using initWithDataProviders:.

Copy
1
2
3
4
5
6
// Create PSPDFFileDataProvider number one with a URL that points to a PDF
let fileProvider1 = PSPDFFileDataProvider(fileURL: writableURL1)
// Create PSPDFFileDataProvider number two with a URL that points to a PDF
let fileProvider2 = PSPDFFileDataProvider(fileURL: writableURL2)
// Initialize the PSPDFDocument with an array of data providers
let document = PSPDFDocument(dataProviders:[fileProvider1, fileProvider2])
Copy
1
2
3
4
5
6
// Create PSPDFFileDataProvider number one with a URL that points to a PDF
PSPDFFileDataProvider *fileProvider1 = [[PSPDFFileDataProvider alloc] initWithFileURL:writableURL1];
// Create PSPDFFileDataProvider number two with a URL that points to a PDF
PSPDFFileDataProvider *fileProvider2 = [[PSPDFFileDataProvider alloc] initWithFileURL:writableURL2];
// Initialize the PSPDFDocument with an array of data providers
PSPDFDocument *document = [[PSPDFDocument alloc] initWithDataProviders:@[fileProvider1, fileProvider2]];

Annotation Flattening

When flattening an annotation, the annotation is removed from the document while its visual representation is kept intact. A flattened annotation is still visible but no longer editable by your users or by your app. This can be used to, for example, fixate annotations onto your document or to make annotations visible to viewers that cannot show annotations otherwise (like Safari on iOS). If not otherwise specified, the processor will keep all annotations as they are.

To change how annotations are processed, use PSPDFProcessorConfiguration#modifyAnnotationsOfTypes:change:. With the PSPDFAnnotationChange enum you can choose between flattening annotations, removing annotations or saving annotations into the document (which is the default). In most cases you want to use PSPDFAnnotationTypeAll to apply this change on all annotations. In some cases excluding links but flattening all other types might be desirable, which can be achieved with:

1
2
var types = PSPDFAnnotationType.all
types.remove(.link)
1
PSPDFAnnotationTypeAll & ~PSPDFAnnotationTypeLink

Form Flattening

Form elements are of special annotation type PSPDFAnnotationTypeWidget. You can use the above mentioned method to control flattening for all the form types, or you can change them by PSPDFFormFieldType as well, using PSPDFProcessorConfiguration#modifyFormsOfType:change:.

For example, you might not want to flatten a signature annotation, as only the visual representation of the digital signature would be included in the resulting document, not the actual digital signature.

Page Scaling

Note: This feature is only available if you have the Document Editor Component enabled for your license.

You can also use PSPDFProcessor to scale pages of a document:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Create default configuration
guard let configuration = PSPDFProcessorConfiguration(document: document) else {
    // Handle failure
    abort()
}

// Scale page down to half it's size
let pageInfo = document.pageInfoForPage(at: page)!
let pageSize = pageInfo.rect.size
let newPageSize = CGSize(width: pageSize.width / 2, height: pageSize.height / 2)
configuration.scalePage(page, to: newPageSize)

do {
    // Start the conversion from `document` to `scaledDocumentURL`
    try PSPDFProcessor.generatePDF(from: configuration, securityOptions: nil, outputFileURL: scaledDocumentURL)
} catch {
    // Handle failure
    abort()
}
Copy
1
2
3
4
5
6
7
8
9
10
// Create default configuration
PSPDFProcessorConfiguration *configuration = [[PSPDFProcessorConfiguration alloc] initWithDocument:document];

PSPDFPageInfo *pageInfo = [document pageInfoForPageAtIndex:page];
CGSize pageSize = pageInfo.rect.size;
CGSize newPageSize = CGSizeMake(pageSize.width/2.f, pageSize.height/2.f);
[configuration scalePage:page toSize:newPageSize];

// Start the conversion from `document` to `scaledDocumentURL`
[PSPDFProcessor generatePDFFromConfiguration:configuration securityOptions:nil outputFileURL:scaledDocumentURL progressBlock:nil error:NULL];

Adding Watermarks

PSPDFProcessor lets you draw on all pages of the document. This is useful for watermarks or footers:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Create default configuration
guard let configuration = PSPDFProcessorConfiguration(document: document) else {
    // Handle failure
    abort()
}

let renderDrawBlock: PSPDFRenderDrawBlock = { context, page, cropBox, _, _ in
    // Careful, this code is executed on background threads. Only use thread-safe drawing methods.
    let text = "PSPDF Live Watermark On Page \(page + 1)"
    let stringDrawingContext = NSStringDrawingContext()
    stringDrawingContext.minimumScaleFactor = 0.1

    // Add text over the diagonal of the page
    context.translateBy(x: 0, y: cropBox.size.height / 2)
    context.rotate(by: -.pi / 4)
    let attributes = [
        NSFontAttributeName: UIFont.boldSystemFont(ofSize: 30),
        NSForegroundColorAttributeName: UIColor.red.withAlphaComponent(0.5)
    ]
    text.draw(with: cropBox, options: .usesLineFragmentOrigin, attributes: attributes, context: stringDrawingContext)
}

configuration.draw(onAllCurrentPages: renderDrawBlock)

do {
    // Start the conversion from `document` to `processedDocumentURL`
    try PSPDFProcessor.generatePDF(from: configuration, securityOptions: nil, outputFileURL: processedDocumentURL)
} catch {
    // Handle failure
    abort()
}
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Create default configuration
PSPDFProcessorConfiguration *configuration = [[PSPDFProcessorConfiguration alloc] initWithDocument:document];

PSPDFRenderDrawBlock renderBlock = ^(CGContextRef context, NSUInteger page, CGRect cropBox, NSUInteger rotation, NSDictionary *options) {
    // Careful, this code is executed on background threads. Only use thread-safe drawing methods.
    NSString *text = [NSString stringWithFormat:@"PSPDFKit Live Watermark on page %d", page + 1];
    NSStringDrawingContext *stringDrawingContext = [NSStringDrawingContext new];
    stringDrawingContext.minimumScaleFactor = 0.1f;

    // Add text over the diagonal of the page
    CGContextTranslateCTM(context, 0.f, cropBox.size.height/2.f);
    CGContextRotateCTM(context, -(CGFloat)M_PI / 4.f);
    [text drawWithRect:cropBox
               options:NSStringDrawingUsesLineFragmentOrigin
            attributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:100],
                         NSForegroundColorAttributeName: [UIColor.redColor colorWithAlphaComponent:0.5f]}
               context:stringDrawingContext];
};

[configuration drawOnAllCurrentPages:renderBlock];

// Start the conversion from `document` to `processedDocumentURL`
[PSPDFProcessor generatePDFFromConfiguration:configuration securityOptions:nil outputFileURL:processedDocumentURL progressBlock:nil error:NULL];

This can also be added as a rendering on the document without generating a new document. Please also have a look at the PSCExportWatermarkExample in the PSPDFCatalog, where this is shown in action.

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typealias RenderDrawBlock = @convention(block) (CGContext, UInt, CGRect, UInt, [String : Any]?) -> Void

let renderDrawBlock: RenderDrawBlock = { context, page, cropBox, _, _ in
    // Careful, this code is executed on background threads. Only use thread-safe drawing methods.
    let text = "PSPDF Live Watermark On Page \(page + 1)"
    let stringDrawingContext = NSStringDrawingContext()
    stringDrawingContext.minimumScaleFactor = 0.1

    // Add text over the diagonal of the page
    context.translateBy(x: 0, y: cropBox.size.height / 2)
    context.rotate(by: -.pi / 4)
    let attributes = [
        NSFontAttributeName: UIFont.boldSystemFont(ofSize: 30),
        NSForegroundColorAttributeName: UIColor.red.withAlphaComponent(0.5)
    ]
    text.draw(with: cropBox, options: .usesLineFragmentOrigin, attributes: attributes, context: stringDrawingContext)
}

let blockObject = unsafeBitCast(renderDrawBlock, to: AnyObject.self)
document.updateRenderOptions([PSPDFRenderOption.drawBlockKey: blockObject], type: PSPDFRenderType.all)
Copy
1
2
3
4
5
6
7
8
9
10
11
const PSPDFRenderDrawBlock drawBlock = ^(CGContextRef context, NSUInteger page, CGRect cropBox, NSUInteger rotation, NSDictionary *options) {
    // Careful, this code is executed on background threads. Only use thread-safe drawing methods.
    NSString *text = @"PSPDFKit Live Watermark";
    NSStringDrawingContext *stringDrawingContext = [NSStringDrawingContext new];
    stringDrawingContext.minimumScaleFactor = 0.1f;

    CGContextTranslateCTM(context, 0.f, cropBox.size.height / 2.f);
    CGContextRotateCTM(context, -(CGFloat)M_PI / 4.f);
    [text drawWithRect:cropBox options:NSStringDrawingUsesLineFragmentOrigin attributes:@{ NSFontAttributeName: [UIFont boldSystemFontOfSize:100], NSForegroundColorAttributeName: [UIColor.redColor colorWithAlphaComponent:0.5f] } context:stringDrawingContext];
};
[document updateRenderOptions:@{ PSPDFRenderOptionDrawBlockKey: drawBlock } type:PSPDFRenderTypeAll];

Additional Features

Some options in the processor are only available if you have the Document Editor Component enabled for your license, such as adding pages, rotating pages via PSPDFProcessorConfiguration#rotatePage:by: or moving pages via PSPDFProcessorConfiguration#movePages:toDestinationIndex:. Review the header or API documentation for details.

Website and Office Conversion

PSPDFKit also has the ability to convert Websites and Office documents to PDF via +PSPDFProcessor#generatePDFFromURL:outputFileURL:options:completionBlock:. This is an experimental feature and while it works reasonably well, we cannot offer support for it. It uses many frameworks that Apple intended for printing support. Since we do not own these frameworks nor do we have access to the source code, we are unable to fix bugs in Apple's parsers. If you require a pixel perfect conversion, we recommend doing so on a server. (This is what large companies such as Box, Dropbox or Microsoft are doing.)

The Microsoft Office file format is insanely complex and can only correctly supported by Microsoft Office itself. The file format is open, but this doesn't help in rendering all the details correctly. In many ways, it's more complex than PDF.

HTML Conversion

PSPDFProcessor can also convert HTML into a PDF. You can use +PSPDFProcessor#+generatePDFFromHTMLString:outputFileURL:options:completionBlock: for that. Note that it only allows simple HTML tags and doesn't work with complex HTML pages.

Creating a Password-Protected Document

PSPDFProcessor can generate a password-protected document from another document. You can to use PSPDFProcessor#generatePDFFromConfiguration:securityOptions:outputFileURL:progressBlock:error: to create a new password-protected PDF document on disk based on a current PSPDFDocument. See CreatePasswordProtectedDocumentExample from the catalog app for a complete example of how to create a password-protected document.

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// By default, a newly initialized `PSPDFProcessorConfiguration` results in an exported Document that is the same as the input.
let processorConfiguration = PSPDFProcessorConfiguration(document: originalDocument)

// Set the proper password and key length in the `PSPDFDocumentSecurityOptions`
let documentSecurityOptions = PSPDFDocumentSecurityOptions(ownerPassword: ownerPassword, userPassword: userPassword, keyLength: PSPDFDocumentSecurityOptionsKeyLengthAutomatic)
do {
    try PSPDFProcessor.generatePDF(from: processorConfiguration!, securityOptions: documentSecurityOptions, outputFileURL: ouputFileURL)
} catch {
    // Handle failure
    abort()
}

// Initialize the password-protected document
let passwordProtectedDocument = PSPDFDocument(url: ouputFileURL)
Copy
1
2
3
4
5
6
7
8
9
10
11
// By default, a newly initialized `PSPDFProcessorConfiguration` results in an exported Document that is the same as the input.
PSPDFProcessorConfiguration *processorConfiguration = [[PSPDFProcessorConfiguration alloc] initWithDocument:originalDocument];

// Set the proper password and key length in the `PSPDFDocumentSecurityOptions`
PSPDFDocumentSecurityOptions *documentSecurityOptions = [[PSPDFDocumentSecurityOptions alloc] initWithOwnerPassword:userPassword userPassword:ownerPassword keyLength:PSPDFDocumentSecurityOptionsKeyLengthAutomatic];

PSPDFProcessor generatePDFFromConfiguration:processorConfiguration securityOptions:documentSecurityOptions outputFileURL:ouputFileURL progressBlock:nil error:NULL];

// Initialize the password-protected document
PSPDFDocument *passwordProtectedDocument = [[PSPDFDocument alloc] initWithURL:ouputFileURL];
PSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:passwordProtectedDocument];