How can I place png data as a Key Value pair into an Annotation?

Product: PDFTron iOS
Product Version: 10.9.83803


I have a custom interactive annotation that contains a few key value pairs.
I need to place an image (stream of PNG Data) as a Key Value pair for a key: ‘IMAGE_A’

Key: Optional("IMAGE_A"), Value: Stream


How can I save this data as an image stream?


I haven’t been successful in finding the right method using functions such as PTFilter or createIndirectStream.

For example, in PDFKit I would create a custom annotation and do something like this to save it

if let imageAData = imageA.pngData() {
       self.setValue   (imageAData, forAnnotationKey:  PDFAnnotationKey(rawValue: "IMAGE_A") ) 
}

and then I can retrieve it by reading the Annotation from s PDF as follows…

let imageAData = (try container.decodeIfPresent(Data.self, forKey: PDFAnnotationKey(rawValue: "IMAGE_A"))) ?? defaultImageAData
let imageA = UIImage(data: imageAData)

Thank you…

1 Like

To create a new stream you would use PDFDoc.CreateIndirectStream, and then on your annotation call something like annot.GetSDFObj().Put("IMAGE_A", objFromCreateIndirectStream)

Here are some guides that will help you the rest of the way.

1 Like

Thank you for the quick response Ryan.
I have read through the docs and looked at the various techniques, tried various ways, but I am still not able to save an image.

The closest I get is with PTMemoryFilter to write the PNGData for the stream.

let filter = PTMemoryFilter(buf_sz: UInt(imageData.count), is_input: false)

But when calling the ‘createIndirectStream’, the system times out ( Terminated due to signal 9)

Is there any example of how to correctly pass the PNG byte data to set the stream up?

I’ve attached my testing func in hopes that I might be missing something obvious?

Thank you for your time…

P.

    static func addAnnotation(pdfAnnotation: ImageStampAnnotation, pageNumber: Int) throws {
        
        
        print("START")
        
        guard let ptDocument = document                                 else { throw PDFAnnotationError.documentNotFound }
        guard pageNumber > 0, pageNumber <= ptDocument.getPageCount()   else { throw PDFAnnotationError.invalidPageNumber }
        guard let ptPage = ptDocument.getPage(UInt32(pageNumber))       else { throw PDFAnnotationError.pageNotFound }
        
        guard let stamper = PTStamper(size_type: e_ptabsolute_size, a: pdfAnnotation.bounds.width, b: pdfAnnotation.bounds.height) else {
            throw PDFAnnotationError.stamperCreationFailed
        }
        
        // Use any small image - still cause error
        guard let image = pdfAnnotation.image                           else { throw PDFAnnotationError.missingAnnotationImage }
        guard let imageData = image.pngData()                           else { throw PDFAnnotationError.pngImageConversionError }
        
        //let bounds = pdfAnnotation.bounds
        
        
        
        // Set the stamper properties
        stamper.setAlignment(e_pthorizontal_left, vertical_alignment: e_ptvertical_top)
        
        // Apply the stamp to the PTPage
        let pageSet = PTPageSet(one_page: Int32(pageNumber))
        stamper.stampPage(ptDocument, src_page: ptPage, dest_pages: pageSet)
        
        // Testing / Set the main image for the annotation
        var imageStream: PTObj?                                         // use for adding the image to the Key/Value pair
        
        let colorSpace = PTColorSpace.createDeviceRGB()
        
        guard let img = PTImage.create(withData: ptDocument.getSDFDoc(), 
                                       buf:      imageData,               buf_size: UInt(imageData.count),
                                       width:    Int32(image.size.width), height:   Int32(image.size.height),
                                       bpc: 8,   color_space: colorSpace, encoder_hints: PTObj()) else {
            
            throw PDFAnnotationError.imageCreationFailed
        }
        
        // Create PTPageSet for the (1) page
        let page = PTPageSet(one_page: Int32(pageNumber))
        stamper.stampImage(ptDocument, src_img: img, dest_pages: page)
        
        
        print("Create a PTFilter")
        
        // Create a PTFilter (what type?)
        // let filter = PTFilter()
        //let filter = PTMemoryFilter()
        let filter = PTMemoryFilter(buf_sz: UInt(imageData.count), is_input: false)
                
        
        // Then create a PTFilterWriter to write the image data into the filter
        guard let filterWriter = PTFilterWriter(filter: filter) else { throw PDFAnnotationError.filterWriterError }
        
        
        print("Start filterWriter")
        
        filterWriter.writeBuffer(imageData) // ERROR! Data exists but get an error ? wrong format with filter?
        
        print("filterWriter Written")
        
        filterWriter.flush()
        
        print("flush")
        
        // Create the PTFilterReader from the filter
        guard let filterReader = PTFilterReader(filter: filter) else { throw PDFAnnotationError.filterReaderError }
        
        // Create the indirect stream object for the objects image data
        print("Start createIndirectStream")
        imageStream = ptDocument.getSDFDoc()?.createIndirectStream(with: filterReader, filter_chain: nil)
        
        print("createIndirectStream - Completed? Never completes....")
        
        stamper.setAlignment(e_pthorizontal_left, vertical_alignment: e_ptvertical_top) // Set the stamper properties
        stamper.stampPage(ptDocument, src_page: ptPage, dest_pages: page)               // Apply the stamp to the 'PTPage'
        
        
        
        // Retrieve the last annotation added to the page
        // AKA - The one we just added.... 'This' one to add the KeyValue Pairs
        
        let numAnnots = ptPage.getNumAnnots()
        guard let ptAnnotation = ptPage.getAnnot(UInt32(numAnnots - 1)) else { throw PDFAnnotationError.newAnnotationGetError }
        
        
        let sdfObj = ptAnnotation.getSDFObj()                       // Set custom properties
        
        if let imageStream = imageStream {                          // Load the same image (for now - for testing...)
            sdfObj?.put("imageA", obj: imageStream)
        }
                
        // Extra Clean Ups / Tests
        sdfObj?.put("T",        value: pdfAnnotation.userName)
        sdfObj?.put("Name",     value: pdfAnnotation.stampName)
        sdfObj?.put("AP",       value: "/Helvetica 12 Tf 0 g")
        
        ptDocument.refreshFieldAppearances()                        // Refresh field appearances

        print("completed")
        

        
    }
1 Like

I think this forum post is what you are looking for.

If the above doesn’t work for you, then please elaborate on the following.
What is the source of the PNG? Where does it come from?
Is loading the PNG from disk an option for you? If not, how come?

1 Like

Sorry for the misunderstanding.

The problem is not with converting the image into a PTImage.
The problem is to add the PNG Data (or any data) as a value for a Key/Value pair in an annotation.

Just to recap, the annotation has specific requirements in order to be compatible with other apps/projects. One requirement is to add PNG data in three keys. I am using PTStamper to create the annotation with a main image (imgX). I do not have trouble setting most of the keys. But there are a few issues. I have attached a simple sample function below as well.

This are the current issues

  • It is not possible to change the Name of the annotation. It reverts to ‘Draft’ even when the Keys are changed. For example ptObj.putText("Name", value: "日本語の Custom_Name") shows as draft after reloading the PDF.

  • The PNG data being saved is not causing any errors. But the data after saving to a file does not represent the correct data and consequently cannot be recognized by the other apps. Is there an example anywhere on how to store raw data (PTObj?).

  • FYI - Another approach is to use a Template PDF with the information and to construct the PDFs by copying annotations to a new document. I am able to isolate the Annotations (PTAnnot) or evening the PTObjs but I am unable to transfer annotations to a new document (or insert the objects to keys of the new annotation. I have not been successful with ImportObj and CreateIndirect.
    Are there any examples (preferably in Swift)?


Greatly appreciate your help!

    static func createStampAnnotation(inPDFDoc  doc:  PTPDFDoc, pageNum:  UInt32, atLocation bounds: CGRect,
                                      withImage imgX: UIImage,  subImageA imgA:   UIImage,
                                      subImageB imgB: UIImage,  subImageC imgC:   UIImage ) {
        
        do {
            
            try PTPDFNet.catchException {

                let imgX = imgX.getPTImage(doc: doc)  // Convert the images to PTImages
                let imgA = imgA.getPTImage(doc: doc)
                //let imgB = imgB.getPTImage(doc: doc)
                //let imgC = imgC.getPTImage(doc: doc)
                

                // Create the Stamp and use the main image (imageX)
                
                let s: PTStamper = PTStamper(size_type: e_ptabsolute_size, a: bounds.width, b: bounds.height)
   
                s.setAsAnnotation(true)
                s.setOpacity(1)

                s.setAlignment(e_pthorizontal_left, vertical_alignment: e_ptvertical_bottom)
                s.setPosition(bounds.origin.x,      vertical_distance:  bounds.origin.y, use_percentage: false)
                
                let ps = PTPageSet(one_page: Int32(pageNum))
                s.stampImage(doc, src_img: imgX, dest_pages: ps)        // Add the annotation to page 'pageNum'

                

                // Get the PTObj for the recently added annotation
                
                let page:         PTPage  = doc.getPage(pageNum)        // Get the PTPage
                let ptAnnotation: PTAnnot = page.getAnnot(0)            // To get the annotation
                let ptObj:        PTObj   = ptAnnotation.getSDFObj()    // And get the annotation's dictionary
                
                
                // Make the Image Streams (Only do 'imgA' or 'imgC' for now until it works)
                
                let builder: PTElementBuilder = PTElementBuilder()
                let writer:  PTElementWriter  = PTElementWriter()
                
                
                
                // This will write a PTImage - but need the raw data for a PNG file
                
                /*
                writer.writerBegin(with: doc.getSDFDoc(), compress: false)
                writer.writePlacedElement(builder.createImage(imgA))        // write ptImage 'imgA'
                let image_stream: PTObj = writer.end()
                */
                
                
                // This will write the date - but not recognized in other Frameworks (ie PDFKit, Adobe)
                
                if let pngData = imgC.pngData() {
                    let writer:  PTElementWriter  = PTElementWriter()
                    writer.writerBegin(with: doc.getSDFDoc(), compress: false)
                    writer.writeBuffer(pngData)
                    let image_stream: PTObj = writer.end()
                    ptObj.put(eKeyName.image.string, obj: image_stream)
                    // Validatation Text to see if the image_stream is a valid PNG
                    if let imageData = getDataFromPTObj(image_stream), isValidPNG(data: imageData) {
                        print("Is valid PNG Data - add to the annotation's PDF!")
                        ptObj.put(eKeyName.image.string, obj: image_stream)
                    } else {
                        print("Image key NOT added to the annotation's PDF. Reason: Invalid PNG image stream.")
                    }
                }
                
                

                
                
                // Add the Key Value pairs to change the /T /Name value from default 'Draft'
                
                ptObj.putText("Name",   value: "日本語の Custom_Name")      // Doesn't work, still says 'Draft'
                ptObj.putText("T",      value: "日本語の Custom_Name_T")    // Doesn't work, still says 'Draft'
                ptObj.putText("Sample", value: "日本語の Sample")           // Works okay

                
                // ptObj.putDate -  function not avaliable, how to put Date object into Key Value pairs?
 
                
                
                annotationDetails(annotation: ptAnnotation)    // loop through and print all the keys

                
                let docPath  = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
                let filePath = URL(fileURLWithPath: docPath).appendingPathComponent("document_out.pdf").path
                doc.save(toFile: filePath, flags: e_ptlinearized.rawValue)
                
                print("Saved to: \(filePath)")

                
            }
            
        } catch let e as NSError {
            print("\(e)")
            
        }

    }
1 Like

Thank you for clarifying.

You would want to do use the PTSDFDoc.CreateIndirectStreamWithbuf API to pass in what ever your buffer is.

https://docs.apryse.com/api/ios/Classes/PTSDFDoc.html#/c:objc(cs)PTSDFDoc(im)CreateIndirectStreamWithbuf:data_size:filter_chain:

By default the data is not compressed, but if it is a PNG image, then its already sufficiently compressed, and doing further compression, like zlib/flate, would be nearly meaningless. So just pass null to the last parameter of the function. To get the PTSDFDoc object you would use PTPDFDoc.GetSDFDoc API.

So something like.

PTSDFDoc sdfdoc = pdfdoc.GetSDFDoc()
PTObj stream = sdfdoc.CreateIndirectStreamWithbuf(data)
annotObj.Put("MyKey", stream)
1 Like