Import and Export Annotations from Instant JSON on iOS

Instant JSON is our approach to bringing annotations into a modern format while keeping all important properties to make the Instant JSON spec work with PDF. It’s fully documented and supports long-term storage.

Instant JSON stores a list of new or updated annotations. Annotations follow the format for Instant JSON for annotations. When an annotation contains a pdfObjectId, it’s considered to be an update to one of the annotations of the original PDF. For newly created annotations, this key won’t be set.

There are some limitations with Instant JSON, in that not all annotation types are currently supported, and only the properties that can be handled correctly across all of PSPDFKit’s supported platforms (iOS, Android, Web, Windows, and Document Engine) are serialized. For more information, check out the detailed JSON format guide.

Instant Annotation JSON API

Annotation JSON is Instant’s representation of a single annotation. This can be generated by using the methods available in Annotation. For generation, use Annotation.generateInstantJSON():

let annotation = ...
let instantJSONData = try annotation.generateInstantJSON()

// The annotation's Instant JSON data can be saved to an external file.
PSPDFAnnotation *annotation = ...
NSError *instantJSONGenerationError;
NSData *instantJSON = [annotation generateInstantJSONWithError:&instantJSONGenerationError];
if (instantJSON == nil) {
    // Handle error.
}

// The annotation's Instant JSON data can be saved to an external file.
Information

When working with documents that have multiple document providers, the value of pageIndex when creating an annotation JSON from an annotation is always relative to its own document provider and not the entire document.

For creating an annotation from the JSON payload, use PDFDocumentProvider.addAnnotation(fromInstantJSON:attachmentDataProvider:), like this:

// Load the document.
let document: Document = ...
let loadedInstantJSONData = ... // The Instant JSON data can be loaded from an external file.

let documentProvider = document.documentProviders[0]
// Create an annotation from Instant JSON data and add the annotation to the document.
let annotation = try documentProvider.addAnnotation(fromInstantJSON: loadedInstantJSONData)
// Load the document.
PSPDFDocument *document = ...
NSData *loadedInstantJSONData = ... // The Instant JSON data can be loaded from an external file.

// Create an annotation from Instant JSON data and add the annotation to the document.
NSError *annotationCreationError;
PSPDFAnnotation *annotation = [document.documentProviders[0] addAnnotationFromInstantJSON:loadedInstantJSONData attachmentDataProvider:nil error:&annotationCreationError];
if (annotation == nil) {
    // Handle error.
}

For more information, check out the detailed JSON format guide and the Instant JSON — Annotation example from InstantJSONExamples.swift in PSPDFKit Catalog.

Here’s an Instant JSON sample payload for an ink annotation:

{
	"v": 1,
	"type": "pspdfkit/ink",
	"bbox": [89, 98, 143, 207],
	"blendMode": "normal",
	"createdAt": "2018-07-03T13:53:03Z",
	"isDrawnNaturally": false,
	"lineWidth": 5,
	"lines": {
		"intensities": [
			[0.5, 0.5, 0.5],
			[0.5, 0.5, 0.5]
		],
		"points": [
			[
				[92, 101],
				[92, 202],
				[138, 303]
			],
			[
				[184, 101],
				[184, 202],
				[230, 303]
			]
		]
	},
	"opacity": 1,
	"pageIndex": 0,
	"strokeColor": "#AA47BE",
	"updatedAt": "2018-07-03T13:53:03Z"
}

Instant Document JSON API

Document JSON is a serializable representation of the current changes to a document, i.e. a diff between the Document’s saved and unsaved changes. This can be used to transfer a set of changes across devices without having to send the entire PDF, which could potentially be very large. PSPDFKit for Web uses this in standalone deployment to reduce bandwidth use. Currently, the generated JSON only contains changes to annotations.

To generate Instant JSON for documents, call Document.generateInstantJSON(from:) on the document from which you wish to retrieve currently unsaved changes in JSON form. Note that this method will return nil if there are no unsaved changes in the document:

let data = try document.generateInstantJSON(from: document.documentProviders[0])
NSError *error;
NSData *data = [document generateInstantJSONFromDocumentProvider:document.documentProviders[0] error:&error];
if (data == nil) {
  // Handle error.
}

This generated JSON can then be applied to a document using Document.applyInstantJSON(fromDataProvider:to:lenient:). If you have an NSData object containing the Document JSON data, create a DataContainerProvider and pass that as the dataProvider argument to the method, like this:

let document: Document = ...
let jsonContainer = DataContainerProvider(data: data)
try document.applyInstantJSON(fromDataProvider: jsonContainer, to: document.documentProviders[0], lenient: false)
PSPDFDocument *document = ...
PSPDFDataContainerProvider *jsonContainer = [[PSPDFDataContainerProvider alloc] initWithData:data];
NSError *error;
BOOL success = [document applyInstantJSONFromDataProvider:jsonContainer toDocumentProvider:document.documentProviders[0] lenient:NO error:&error];
if (!success) {
    // Handle error.
}

For more details, see the Instant JSON — Document example from InstantJSONExamples.swift in PSPDFKit Catalog.

Here’s an Instant JSON sample payload for a document with an ink annotation and a bookmark:

{
	"annotations": [
		{
			"v": 1,
			"type": "pspdfkit/ink",
			"id": "01CHG7QMTDT1JFYQ8BSKGT6F3P",
			"bbox": [97.5, 97.5, 155, 205],
			"blendMode": "normal",
			"createdAt": "2018-07-03T14:11:21Z",
			"creatorName": "John Appleseed",
			"isDrawnNaturally": false,
			"lineWidth": 5,
			"lines": {
				"intensities": [
					[0.5, 0.5, 0.5],
					[0.5, 0.5, 0.5]
				],
				"points": [
					[
						[100, 100],
						[100, 200],
						[150, 300]
					],
					[
						[200, 100],
						[200, 200],
						[250, 300]
					]
				]
			},
			"name": "A167811E-6D10-4546-A147-B7AD775FE8AC",
			"note": "",
			"opacity": 1,
			"pageIndex": 0,
			"strokeColor": "#AA47BE",
			"updatedAt": "2018-07-03T14:11:21Z"
		}
	],
	"bookmarks": [
		{
			"action": {
				"pageIndex": 1,
				"type": "goTo"
			},
			"type": "pspdfkit/bookmark",
			"v": 1
		}
	],
	"format": "https://pspdfkit.com/instant-json/v1",
	"pdfId": {
		"changing": "wljL9fB/TPOAuGjHAsAsHg==",
		"permanent": "qTwUmg5VSm6ysfzcPlFvnQ=="
	},
	"skippedPdfObjectIds": [557]
}

Instant JSON Annotation Attachment API

The Instant JSON Annotation Attachment API enables you to represent an annotation’s binary attachments as data blobs. For example, a stamp annotation with an image has an attachment.

Writing

You can export an Instant JSON binary for a stamp annotation as NSData and persist it to an external file using Annotation.writeBinaryInstantJSONAttachment(to:), like this:

let stampAnnotation = ...
let dataSink = DataContainerSink(data: nil)
var contentType: String?
if stampAnnotation.hasBinaryInstantJSONAttachment {
    contentType = try stampAnnotation.writeBinaryInstantJSONAttachment(to: dataSink)
}

// Persist the data sink's data and the content type onto an external file so it can be attached later.
PSPDFStampAnnotation *stampAnnotation = ...
PSPDFDataContainerSink dataSink = [[PSPDFDataContainerSink alloc] initWithData:nil];
NSError *error;
NSString *contentType;

if (stampAnnotation.hasBinaryInstantJSONAttachment){
    contentType = [stampAnnotation writeBinaryInstantJSONAttachmentToDataSink:dataSink error:&error];
    if (contentType == nil) {
    	// Handle error.
        return;
    }
}

// Persist the data sink's data and the content type onto an external file so it can be attached later.

Attaching

You can import and attach the Instant JSON binary attachment data for a stamp annotation from an external file using an optional attachmentDataProvider parameter, like this:

// Load the document.
let document: Document = ...
let instantJSONData: Data = ... // The Instant JSON data can be loaded from an external file.
let attachmentFileURL: URL = ... // The Instant JSON binary attachment data can be loaded from an external file.

// Create the provider for the attachment data.
let attachmentDataProvider = FileDataProvider(fileURL: attachmentFileURL)
let documentProvider = document.documentProviders[0]

// Create an annotation from Instant JSON data, add it to the document, and add the attachment.
let stampAnnotation = try documentProvider.addAnnotation(fromInstantJSON: instantJSONData, attachmentDataProvider: attachmentDataProvider)
// Load the document.
PSPDFDocument *document = ...
NSData *instantJSONData = ... // The Instant JSON data can be loaded from an external file.
NSURL *attachmentFileURL = ... // The Instant JSON binary attachment data can be loaded from an external file.

// Create the provider for the attachment data.
id<PSPDFDataProviding> attachmentDataProvider = [[PSPDFFileDataProvider alloc] initWithFileURL:attachmentFileURL];

// Create an annotation from Instant JSON data, add it to the document, and add the attachment.
NSError *annotationCreationError;
PSPDFAnnotation *annotation = [document.documentProviders[0] addAnnotationFromInstantJSON:instantJSONData attachmentDataProvider:attachmentDataProvider error:&annotationCreationError];
if (annotation == nil) {
    // Handle error.
}

For more details, refer to the Instant JSON — Attachment example from InstantJSONExamples.swift in PSPDFKit Catalog.