Instant JSON
Instant JSON is our approach to bringing annotations and bookmarks 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 PDF changes like annotations and bookmarks in a separate JSON file. This means that a PDF document will only need to be transferred once and all changes will be added as an overlay to the existing PDF. This approach significantly reduces the bandwidth since you only need to transfer the JSON instead of the complete PDF.
Conceptually, Instant JSON defines a list of skippedPdfObjectIds
. These point to the PDF’s internal object IDs for annotations. Whenever an object ID is marked as skipped, it’ll no longer be loaded from the original PDF. Instead, it could be defined inside the annotations
array with the same pdfObjectId
. If this is the case, the PDF viewer will display the new annotation, which signals an update to the original one. If an object ID is marked as skipped but the annotations
array doesn’t contain an annotation with the same pdfObjectId
, it’ll be interpreted as a deleted annotation. An annotation inside the annotations
array without the pdfObjectId
property is interpreted as a newly created annotation.
All annotations in the annotations
array have a unique id
field. For updated annotations that were in the original PDF, this field will be the stringified pdfObjectId
. Newly created annotations will get a newly generated ULID.
An “empty” Instant JSON contains neither skippedPdfObjectIds
nor annotations
, which means the original PDF is untouched. All annotations in the initial PDF are still shown.
Instant JSON also defines a list of skippedPdfBookmarkIds
that follow the same principals of skippedPdfObjectIds
, but instead they contain a list of bookmarks that no longer need to be loaded from the original document.
The Format
We use Flow type declarations to specify the format of Instant JSON:
1 2 3 4 5 6 7 8 9 10 11 12 | declare type InstantJSON = { format: "https://pspdfkit.com/instant-json/v1", pdfId?: { permanent: string, changing: string }, skippedPdfObjectIds?: number[], annotations?: Object[], bookmarks?: Object[], skippedPdfBookmarkIds?: string[], formFieldValues?: Object[] }; |
format
This is a literal string that includes the version information.
pdfId
This optional key contains an object of a permanent
PDF ID and a changing
PDF ID. According to the PDF spec, a PDF document must contain these IDs. We use the permanent
ID to verify that the PDF you’ve opened together with this Instant JSON is indeed the correct one. The changing
PDF ID will be updated whenever the PDF file is updated (for example, when saved with different annotations). Since Instant JSON only works with an immutable PDF, the state will be invalid when used with a changed PDF.
Not every PDF will have a valid permanent
or changing
ID. As such, this field might not be set. We recommend you take care to always use the same PDF.
skippedPdfObjectIds
This is an array of PDF object IDs that will be ignored when importing annotations from the original PDF document. If this array is empty, the key shouldn’t be set.
annotations
This is 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.
bookmarks
This is a list that contains any new bookmarks added to the PDF. Bookmarks follow the format for Instant JSON for bookmarks.
skippedPdfBookmarkIds
This is an array of bookmark object IDs from the PDF that will be ignored when importing annotations from the original PDF document. If this array is empty, the key shouldn’t be set.
formFieldValues
This is a list of modified form field values. Objects follow the Instant form field value JSON format. This list won’t be preset when no form field values have been modified.
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()
:
1 2 3 4 | let annotation = ... let instantJSONData = try? annotation.generateInstantJSON() // The annotation's Instant JSON data can be saved to an external file. |
1 2 3 4 5 6 7 8 | PSPDFAnnotation *annotation = ... NSError *instantJSONGenerationError; NSData *instantJSON = [annotation generateInstantJSONWithError:&instantJSONGenerationError]; if (!instantJSON) { // Handle error. } // The annotation's Instant JSON data can be saved to an external file. |
ℹ️ Note: 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, use Annotation(fromInstantJSON:documentProvider:)
, like so:
1 2 3 4 5 6 7 8 9 10 | // Load the document. let document = ... let loadedInstantJSONData = ... // The Instant JSON data can be loaded from an external file. let documentProvider = document.documentProviders.first! // Create an annotation from Instant JSON data. let annotation = try! Annotation(fromInstantJSON: loadedInstantJSONData, documentProvider: documentProvider) // Add the newly created annotation to the document. document.add([annotation]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 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. NSError *annotationCreationError; PSPDFAnnotation *annotation = [PSPDFAnnotation annotationFromInstantJSON:loadedInstantJSONData documentProvider:document.documentProviders.firstObject error:&annotationCreationError]; if (!annotation) { // Handle error. } // Add the newly created annotation to the document. [document addAnnotations:@[annotation] options:nil]; |
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, Libraries) are serialized. 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | { "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:
1 | let data = try? document.generateInstantJSON(from: document.documentProviders.first) |
1 2 3 4 5 | NSError *error; NSData *data = [document generateInstantJSONFromDocumentProvider:document.documentProviders.firstObject error:&error]; if (!data) { // 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:
1 2 3 | let document = ... let jsonContainer = DataContainerProvider(data:data!) try! document.applyInstantJSON(fromDataProvider: jsonContainer, to: document.documentProviders.first, lenient: false) |
1 2 3 4 5 6 7 | PSPDFDocument *document = ... PSPDFDataContainerProvider *jsonContainer = [[PSPDFDataContainerProvider alloc] initWithData:data]; NSError *error; BOOL success = [document applyInstantJSONFromDataProvider:jsonContainer toDocumentProvider:document.documentProviders.firstObject 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:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 | { "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] } |
ℹ️ Note: There’s currently no dedicated API like Instant Annotation JSON API for importing and exporting bookmarks alone. Please use the above Instant Document JSON API for this purpose.
Instant JSON Annotation Attachment API
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:
1 2 3 4 5 6 7 8 | 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. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | PSPDFStampAnnotation *stampAnnotation = ... PSPDFDataContainerSink dataSink = [[PSPDFDataContainerSink alloc] initWithData:nil]; NSError *error; NSString *contentType; if (stampAnnotation.hasBinaryInstantJSONAttachment){ contentType = [stampAnnotation writeBinaryInstantJSONAttachmentToDataSink:dataSink error:&error]; if (!contentType) { // 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 Annotation.attachBinaryInstantJSONAttachment(fromDataProvider:)
, like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Load the document. let document = ... let instantJSONData = ... // The Instant JSON data can be loaded from an external file. let attachmentData = ... // The Instant JSON binary attachment data can be loaded from an external file. // Create the data container provider using the attachment data. let dataContainerProvider = DataContainerProvider(data: attachmentData) let documentProvider = document.documentProviders.first! // Create an annotation from Instant JSON data. let stampAnnotation = try! Annotation(fromInstantJSON: instantJSONData!, documentProvider: documentProvider) // Attach the attachment data. try! stampAnnotation.attachBinaryInstantJSONAttachment(fromDataProvider: dataContainerProvider) // Add the newly loaded annotation to the document. document.add([stampAnnotation]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Load the document. PSPDFDocument *document = ... NSData *instantJSONData = ... // The Instant JSON data can be loaded from an external file. NSData *attachmentData = ... // The Instant JSON binary attachment data can be loaded from an external file. // Create an annotation from Instant JSON data. NSError *annotationCreationError; PSPDFAnnotation *stampAnnotation = [PSPDFAnnotation annotationFromInstantJSON:instantJSONData documentProvider:document.documentProviders.firstObject error:&annotationCreationError]; if (!stampAnnotation) { // Handle error. } // Create the data container provider using the attachment data. PSPDFDataContainerProvider *dataContainerProvider = [[PSPDFDataContainerProvider alloc] initWithData:attachmentData]; // Attach the attachment data. NSError *attachBinaryError; BOOL success = [stampAnnotation attachBinaryInstantJSONAttachmentFromDataProvider:dataContainerProvider error:&attachBinaryError]; if (!success) { // Handle error. } // Add the newly loaded annotation to the document. [document addAnnotations:@[stampAnnotation] options:nil]; |
For more details, please take a look at the Instant JSON — Attachment example from InstantJSONExamples.swift
in PSPDFKit Catalog.