Flatten Annotations in iOS

Annotations in a PDF document can be flattened in the PSPDFKit UI by sharing (exporting) the document and choosing the Flatten Annotations option. They can also be flattened programmatically using the Processor class.

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 is no longer editable by your users or by your app. This can be used to, for example, fix annotations onto your document, or to make annotations visible to viewers that cannot show annotations (like Safari on iOS). If not otherwise specified, the processor will keep all annotations as they are.

To change how annotations are processed, use Processor.Configuration.modifyAnnotations(ofTypes:change:). With the AnnotationChange enum, you can choose between flattening annotations, removing annotations, or saving annotations into the document (which is the default). In most cases, you’ll want to use Annotation.Kind.all to apply this change on all annotations, including forms. In some cases, excluding links but flattening all other types might be desirable. This can be achieved with the following:

// By default, a newly initialized `Processor.Configuration` results in an exported document that's the same as the input.
guard let processorConfiguration = Processor.Configuration(document: originalDocument) else { return }

// Flatten all annotations except links.
var types = Annotation.Kind.all
types.remove(.link)
processorConfiguration.modifyAnnotations(ofTypes: types, change: .flatten)

do {
    let processor = Processor(configuration: processorConfiguration, securityOptions: nil)
    try processor.write(toFileURL: outputFileURL)
} catch {
    // Handle error.
}

let flattenedDocument = Document(url: outputFileURL)
// By default, a newly initialized `PSPDFProcessorConfiguration` results in an exported document that's the same as the input.
PSPDFProcessorConfiguration *processorConfiguration = [[PSPDFProcessorConfiguration alloc] initWithDocument:originalDocument];

// Flatten all annotations except links.
[processorConfiguration modifyAnnotationsOfTypes:PSPDFAnnotationTypeAll & ~PSPDFAnnotationTypeLink change:PSPDFAnnotationChangeFlatten];

PSPDFProcessor *processor = [[PSPDFProcessor alloc] initWithConfiguration:processorConfiguration securityOptions:nil];
NSError *error;
if (![processor writeToFileURL:outputFileURL error:&error]) {
    // Handle error.
}

PSPDFDocument *flattenedDocument = [[PSPDFDocument alloc] initWithURL:outputFileURL];

Flattening for Print

If a document is being flattened for the purposes of printing to paper, flatten using the .print annotation change instead of .flatten. This ensures that the .print visibility flag is respected.

Creating a Document with Restricted Permissions

When a document is flattened, it’s often desirable to prevent future editing of it. A document can be configured to lock some permissions behind an owner password to, for example, restrict form filling/any modification. In such a scenario, you can accomplish this by passing an owner password, a nil user password, and the desired permissions to Document.SecurityOptions(ownerPassword:userPassword:keyLength:), like so:

guard let processorConfiguration = Processor.Configuration(document: originalDocument) else { return }

// Flatten all annotations.
processorConfiguration.modifyAnnotations(ofTypes: .all, change: .flatten)

do {
    // Create the document security options with a proper owner password, a `nil` user password, and the permissions.
    let documentSecurityOptions = try Document.SecurityOptions(ownerPassword: password, userPassword: nil, keyLength: Document.SecurityOptionsKeyLengthAutomatic, permissions: [.printing, .printHighQuality])

    // Initialize the processor with the configuration and the security options.
    let processor = Processor(configuration: processorConfiguration, securityOptions: documentSecurityOptions)
    try processor.write(toFileURL: outputFileURL)
} catch {
    // Handle error.
}

// Initialize the password-protected document.
let passwordProtectedDocument = Document(url: outputFileURL)
PSPDFProcessorConfiguration *processorConfiguration = [[PSPDFProcessorConfiguration alloc] initWithDocument:originalDocument];

// Flatten all annotations.
[processorConfiguration modifyAnnotationsOfTypes:PSPDFAnnotationTypeAll change:PSPDFAnnotationChangeFlatten];

// Create the document security options with a proper owner password, a `nil` user password, and the permissions.
PSPDFDocumentSecurityOptions *documentSecurityOptions = [[PSPDFDocumentSecurityOptions alloc] initWithOwnerPassword:password userPassword:nil keyLength:PSPDFDocumentSecurityOptionsKeyLengthAutomatic permissions:PSPDFDocumentPermissionsPrinting | PSPDFDocumentPermissionsPrintHighQuality error:&error];

// Initialize the processor with the configuration and the security options.
PSPDFProcessor *processor = [[PSPDFProcessor alloc] initWithConfiguration:processorConfiguration securityOptions:documentSecurityOptions];
NSError *error;
if (![processor writeToFileURL:outputFileURL error:&error]) {
    // Handle error.
}

// Initialize the password-protected document.
PSPDFDocument *passwordProtectedDocument = [[PSPDFDocument alloc] initWithURL:outputFileURL];

ℹ️ Note: Software such as PSPDFKit or Adobe Acrobat, which comply with the PDF specification, honors document permissions. Some third-party viewers, such as Apple’s Preview app, only have limited handling of permissions and might still allow editing or throw errors after an edit should be saved.

For more details, please refer to our document permissions guide.