< iOS samples

PDFLayersTest - Swift

This sample demonstrates how to create PDF layers (also known as Optional Content Groups - OCGs). The sample also shows how to extract and render PDF layers.

//---------------------------------------------------------------------------------------
// Copyright (c) 2001-2017 by PDFTron Systems Inc. All Rights Reserved.
// Consult legal.txt regarding legal and license information.
//---------------------------------------------------------------------------------------

import PDFNet
import Foundation

//-----------------------------------------------------------------------------------
// This sample demonstrates how to create layers in PDF.
// The sample also shows how to extract and render PDF layers in documents
// that contain optional content groups (OCGs)
//
// With the introduction of PDF version 1.5 came the concept of Layers.
// Layers, or as they are more formally known Optional Content Groups (OCGs),
// refer to sections of content in a PDF document that can be selectively
// viewed or hidden by document authors or consumers. This capability is useful
// in CAD drawings, layered artwork, maps, multi-language documents etc.
//
// Notes:
// ---------------------------------------
// - This sample is using CreateLayer() utility method to create new OCGs.
//   CreateLayer() is relatively basic, however it can be extended to set
//   other optional entries in the 'OCG' and 'OCProperties' dictionary. For
//   a complete listing of possible entries in OC dictionary please refer to
//   section 4.10 'Optional Content' in the PDF Reference Manual.
// - The sample is grouping all layer content into separate Form XObjects.
//   Although using PDFNet is is also possible to specify Optional Content in
//   Content Streams (Section 4.10.2 in PDF Reference), Optional Content in
//   XObjects results in PDFs that are cleaner, less-error prone, and faster
//   to process.
//-----------------------------------------------------------------------------------

func runPDFLayersTest() -> Int {
    return autoreleasepool {
        var ret = 0
        
        
        do {
            try PTPDFNet.catchException {
                let doc: PTPDFDoc = PTPDFDoc()
                
                // Create three layers...
                let image_layer: PTGroup = CreateLayer(doc: doc, layer_name: "Image Layer")
                let text_layer: PTGroup = CreateLayer(doc: doc, layer_name: "Text Layer")
                let vector_layer: PTGroup = CreateLayer(doc: doc, layer_name: "Vector Layer")
                
                // Start a new page ------------------------------------
                let page: PTPage = doc.pageCreate(PTPDFRect(x1: 0, y1: 0, x2: 612, y2: 792))
                
                let builder: PTElementBuilder = PTElementBuilder()    // ElementBuilder is used to build new Element objects
                let writer: PTElementWriter = PTElementWriter()      // ElementWriter is used to write Elements to the page
                writer.writerBegin(with: page, placement: e_ptoverlay, page_coord_sys: true, compress: true)    // Begin writing to the page
                
                // Add new content to the page and associate it with one of the layers.
                var element: PTElement! = builder.createForm(with: CreateGroup1(doc: doc, layer: image_layer.getSDFObj()))
                writer.write(element)
                
                element = builder.createForm(with: CreateGroup2(doc: doc, layer: vector_layer.getSDFObj()))
                writer.write(element)
                
                // Add the text layer to the page...
                if false {  // set to true to enable 'ocmd' example
                    // A bit more advanced example of how to create an OCMD text layer that
                    // is visible only if text, image and path layers are all 'ON'.
                    // An example of how to set 'Visibility Policy' in OCMD.
//                    let ocgs: PTObj = doc.createIndirectArray()
//                    ocgs.pushBack(image_layer.getSDFObj())
//                    ocgs.pushBack(vector_layer.getSDFObj())
//                    ocgs.pushBack(text_layer.getSDFObj())
//                    let text_ocmd: PTOCMD = PTOCMD.create(doc, ocgs: ocgs, vis_policy: e_ptAllOn)
//                    element = builder.createForm(with: CreateGroup3(doc: doc, layer: text_ocmd.getSDFObj()))
                }
                else {
                    element = builder.createForm(with: CreateGroup3(doc: doc, layer: text_layer.getSDFObj()))
                }
                writer.write(element)
                
                // Add some content to the page that does not belong to any layer...
                // In this case this is a rectangle representing the page border.
                element = builder.createRect(0, y: 0, width: page.getWidth(e_ptcrop), height: page.getHeight(e_ptcrop))
                element.setPathFill(false)
                element.setPathStroke(true)
                element.getGState().setLineWidth(40)
                writer.write(element)
                
                writer.end()    // save changes to the current page
                doc.pagePushBack(page)
                
                // Set the default viewing preference to display 'Layer' tab.
                let prefs: PTPDFDocViewPrefs = doc.getViewPrefs()
                prefs.setPageMode(e_ptUseOC)
                
                doc.save(toFile: URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]).appendingPathComponent("pdf_layers.pdf").path, flags: e_ptlinearized.rawValue)
                print("Done.")
            }
        } catch let e as NSError {
            print("\(e)")
            ret = 1
        }
        
        // The following is a code snippet shows how to selectively render
        // and export PDF layers.
        do {
            try PTPDFNet.catchException {
                let doc: PTPDFDoc = PTPDFDoc(filepath: URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]).appendingPathComponent("pdf_layers.pdf").path)
                doc.initSecurityHandler()
                
                if !doc.hasOC() {
                    print("The document does not contain 'Optional Content'")
                }
                else {
                    let init_cfg: PTConfig = doc.getOCGConfig()
                    let ctx: PTContext = PTContext(config: init_cfg)
                    
                    let pdfdraw: PTPDFDraw = PTPDFDraw(dpi: 92)
                    pdfdraw.setImageSize(1000, height: 1000, preserve_aspect_ratio: true)
                    pdfdraw.setOCGContext(ctx)    // Render the page using the given OCG context.
                    
                    let page: PTPage = doc.getPage(1)  // Get the first page in the document.
                    pdfdraw.export(page, filename: URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]).appendingPathComponent("pdf_layers_default.png").path, format: "PNG")
                    
                    // Disable drawing of content that is not optional (i.e. is not part of any layer).
                    ctx.setNonOCDrawing(false)
                    
                    // Now render each layer in the input document to a separate image.
                    if let ocgs: PTObj = doc.getOCGs() {    // Get the array of all OCGs in the document.
                        var _: UInt = 0
                        let sz: UInt = ocgs.size()
                        for i in 0..<sz {
                            let ocg: PTGroup = PTGroup(ocg: ocgs.getAt(i))
                            ctx.resetStates(false)
                            ctx.setState(ocg, state: true)
                            //let fname: String = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]).appendingPathComponent("pdf_layers_").path + ("\(ocg.getName()).png")
                            //pdfdraw.export(page, filename: fname, format: "PNG")
                        }
                    }
                    
                    // Now draw content that is not part of any layer...
                    ctx.setNonOCDrawing(true)
                    ctx.setOCDrawMode(e_ptNoOC)
                    //pdfdraw.export(page, filename: URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]).appendingPathComponent("pdf_layers_non_oc.png").path, format: "PNG")
                }
                print("Done.")
            }
        } catch let e as NSError {
            print("\(e)")
            ret = 1
        }
        
        return ret
    }
}

// A utility function used to add new Content Groups (Layers) to the document.
func CreateLayer(doc: PTPDFDoc, layer_name: String) -> PTGroup {
    let grp: PTGroup = PTGroup.create(doc, name: layer_name)
    var cfg: PTConfig = doc.getOCGConfig()
    if !cfg.isValid() {
        cfg = PTConfig.create(doc, default_config: true)
        cfg.setName("Default")
    }
    
    // Add the new OCG to the list of layers that should appear in PDF viewer GUI.
    var layer_order_array: PTObj
    if let opt_layer_order_array = cfg.getOrder() {
        layer_order_array = opt_layer_order_array
    } else {
        layer_order_array = doc.createIndirectArray()
        cfg.setOrder(layer_order_array)
    }
    
    layer_order_array.pushBack(grp.getSDFObj())
    
    return grp
}

// Creates some content (3 images) and associate them with the image layer
func CreateGroup1(doc: PTPDFDoc, layer: PTObj) -> PTObj {
    let writer: PTElementWriter = PTElementWriter()
    writer.writerBegin(with: doc.getSDFDoc(), compress: true)
    
    // Create an Image that can be reused in the document or on the same page.
    let img: PTImage = PTImage.create(doc.getSDFDoc(), filename: Bundle.main.path(forResource: "peppers", ofType: "jpg"))
    
    let builder: PTElementBuilder = PTElementBuilder()
    let element: PTElement = builder.createImage(withMatrix: img, mtx: PTMatrix2D(a: Double(img.getWidth() / 2), b: -145, c: 20, d: Double(img.getHeight() / 2), h: 200, v: 150))
    writer.writePlacedElement(element)
    
    let gstate: PTGState = element.getGState()    // use the same image (just change its matrix)
    gstate.setTransform(200, b: 0, c: 0, d: 300, h: 50, v: 450)
    writer.writePlacedElement(element)
    
    // use the same image again (just change its matrix).
    writer.writePlacedElement(builder.createImage(withCornerAndScale: img, x: 300, y: 600, hscale: 200, vscale: -150))
    let grp_obj: PTObj = writer.end()
    
    // Indicate that this form (content group) belongs to the given layer (OCG).
    grp_obj.putName("Subtype", name: "Form")
    grp_obj.put("OC", obj: layer)
    grp_obj.putRect("BBox", x1: 0, y1: 0, x2: 1000, y2: 1000)  // Set the clip box for the content.
    
    return grp_obj
}

// Creates some content (a path in the shape of a heart) and associate it with the vector layer
func CreateGroup2(doc: PTPDFDoc, layer: PTObj) -> PTObj {
    let writer: PTElementWriter = PTElementWriter()
    writer.writerBegin(with: doc.getSDFDoc(), compress: true)
    
    // Create a path object in the shape of a heart.
    let builder: PTElementBuilder = PTElementBuilder()
    builder.pathBegin() // start constructing the path
    builder.move(to: 306, y: 396)
    builder.curve(to: 681, cy1: 771, cx2: 399.75, cy2: 864.75, x2: 306, y2: 771)
    builder.curve(to: 212.25, cy1: 864.75, cx2: -69, cy2: 771, x2: 306, y2: 396)
    builder.closePath()
    let element: PTElement = builder.pathEnd() // the path geometry is now specified.
    
    // Set the path FILL color space and color.
    element.setPathFill(true)
    let gstate: PTGState = element.getGState()
    gstate.setFill(PTColorSpace.createDeviceCMYK())
    gstate.setFillColor(with: PTColorPt(x: 1, y: 0, z: 0, w: 0))   // cyan
    
    // Set the path STROKE color space and color.
    element.setPathStroke(true)
    
    gstate.setStroke(PTColorSpace.createDeviceRGB())
    gstate.setStrokeColor(with: PTColorPt(x: 1, y: 0, z: 0, w: 0)) // red
    gstate.setLineWidth(20)
    
    gstate.setTransform(0.5, b: 0, c: 0, d: 0.5, h: 280, v: 300)
    
    writer.write(element)
    
    let grp_obj: PTObj = writer.end()
    
    // Indicate that this form (content group) belongs to the given layer (OCG).
    grp_obj.putName("Subtype", name: "Form")
    grp_obj.put("OC", obj: layer)
    grp_obj.putRect("BBox", x1: 0, y1: 0, x2: 1000, y2: 1000)  // Set the clip box for the content.
    
    return grp_obj
}

// Creates some text and associate it with the text layer
func CreateGroup3(doc: PTPDFDoc, layer: PTObj) -> PTObj {
    let writer: PTElementWriter = PTElementWriter()
    writer.writerBegin(with: doc.getSDFDoc(), compress: true)
    
    // Create a path object in the shape of a heart.
    let builder: PTElementBuilder = PTElementBuilder()
    
    // Begin writing a block of text
    var element: PTElement = builder.createTextBegin(with: PTFont.create(doc.getSDFDoc(), type: e_pttimes_roman, embed: false), font_sz: 120)
    writer.write(element)
    
    element = builder.createTextRun("A text layer!")
    
    // Rotate text 45 degrees, than translate 180 pts horizontally and 100 pts vertically.
    let transform: PTMatrix2D = PTMatrix2D.rotationMatrix(-45 * (3.1415 / 180.0))
    transform.concat(1, b: 0, c: 0, d: 1, h: 180, v: 100)
    element.setTextMatrix(with: transform)
    
    writer.write(element)
    writer.write(builder.createTextEnd())
    
    let grp_obj: PTObj = writer.end()
    
    // Indicate that this form (content group) belongs to the given layer (OCG).
    grp_obj.putName("Subtype", name: "Form")
    grp_obj.put("OC", obj: layer)
    grp_obj.putRect("BBox", x1: 0, y1: 0, x2: 1000, y2: 1000)  // Set the clip box for the content.
    
    return grp_obj
}