//---------------------------------------------------------------------------------------
// Copyright (c) 2001-2025 by Apryse Software Inc. All Rights Reserved.
// Consult legal.txt regarding legal and license information.
//---------------------------------------------------------------------------------------

import com.pdftron.common.Matrix2D;
import com.pdftron.common.PDFNetException;
import com.pdftron.pdf.*;
import com.pdftron.pdf.ocg.*;
import com.pdftron.sdf.*;


//-----------------------------------------------------------------------------------
// 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.
//
// Couple of notes regarding this sample: 
// ---------------------------------------
// - 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.
//-----------------------------------------------------------------------------------
public class PDFLayersTest {
    // Relative path to the folder containing test files.
    static String input_path = "../../TestFiles/";
    static String output_path = "../../TestFiles/Output/";

    public static void main(String[] args) {
        PDFNet.initialize(PDFTronLicense.Key());

        try (PDFDoc doc = new PDFDoc()) {
            // Create three layers...
            Group image_layer = createLayer(doc, "Image Layer");
            Group text_layer = createLayer(doc, "Text Layer");
            Group vector_layer = createLayer(doc, "Vector Layer");

            // Start a new page ------------------------------------
            Page page = doc.pageCreate();

            ElementBuilder builder = new ElementBuilder(); // ElementBuilder is used to build new Element objects
            ElementWriter writer = new ElementWriter(); // ElementWriter is used to write Elements to the page
            writer.begin(page);        // Begin writing to the page

            // Add new content to the page and associate it with one of the layers.
            Element element = builder.createForm(createGroup1(doc, image_layer.getSDFObj()));
            writer.writeElement(element);

            element = builder.createForm(createGroup2(doc, vector_layer.getSDFObj()));
            writer.writeElement(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.
                Obj ocgs = doc.createIndirectArray();
                ocgs.pushBack(image_layer.getSDFObj());
                ocgs.pushBack(vector_layer.getSDFObj());
                ocgs.pushBack(text_layer.getSDFObj());
                OCMD text_ocmd = OCMD.create(doc, ocgs, OCMD.e_AllOn);
                element = builder.createForm(createGroup3(doc, text_ocmd.getSDFObj()));
            } else {
                element = builder.createForm(createGroup3(doc, text_layer.getSDFObj()));
            }
            writer.writeElement(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, 0, page.getPageWidth(), page.getPageHeight());
            element.setPathFill(false);
            element.setPathStroke(true);
            element.getGState().setLineWidth(40);
            writer.writeElement(element);

            writer.end();  // save changes to the current page
            doc.pagePushBack(page);

            // Set the default viewing preference to display 'Layer' tab.
            PDFDocViewPrefs prefs = doc.getViewPrefs();
            prefs.setPageMode(PDFDocViewPrefs.e_UseOC);

            doc.save(output_path + "pdf_layers.pdf", SDFDoc.SaveMode.LINEARIZED, null);
            System.out.println("Done.");
        } catch (Exception e) {
            e.printStackTrace();
        }

        // The following is a code snippet shows how to selectively render
        // and export PDF layers.
        try (PDFDoc doc = new PDFDoc(output_path + "pdf_layers.pdf")) {
            doc.initSecurityHandler();

            if (doc.hasOC() == false) {
                System.out.println("The document does not contain 'Optional Content'");
            } else {
                Config init_cfg = doc.getOCGConfig();
                Context ctx = new Context(init_cfg);

                PDFDraw pdfdraw = new PDFDraw();
                pdfdraw.setImageSize(1000, 1000);
                pdfdraw.setOCGContext(ctx); // Render the page using the given OCG context.

                Page page = doc.getPage(1); // Get the first page in the document.
                pdfdraw.export(page, output_path + "pdf_layers_default.png");
                // output "pdf_layers_default.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.
                Obj ocgs = doc.getOCGs(); // Get the array of all OCGs in the document.
                if (ocgs != null) {
                    int i, sz = (int) ocgs.size();
                    for (i = 0; i < sz; ++i) {
                        Group ocg = new Group(ocgs.getAt(i));
                        ctx.resetStates(false);
                        ctx.setState(ocg, true);
                        String fname =  "pdf_layers_" + ocg.getName() + ".png";
                        System.out.println(fname);
                        pdfdraw.export(page, output_path + fname);
                        // output "pdf_layers_" + ocg.getName() + ".png"
                    }
                }

                // Now draw content that is not part of any layer...
                ctx.setNonOCDrawing(true);
                ctx.setOCDrawMode(Context.e_NoOC);
                pdfdraw.export(page, output_path + "pdf_layers_non_oc.png");
                // output "pdf_layers_non_oc.png"
            }

            System.out.println("Done.");
        } catch (Exception e) {
            e.printStackTrace();
        }

        PDFNet.terminate();
    }

    // A utility function used to add new Content Groups (Layers) to the document.
    static Group createLayer(PDFDoc doc, String layer_name) throws PDFNetException {
        Group grp = Group.create(doc, layer_name);
        Config cfg = doc.getOCGConfig();
        if (cfg == null) {
            cfg = Config.create(doc, true);
            cfg.setName("Default");
        }

        // Add the new OCG to the list of layers that should appear in PDF viewer GUI.
        Obj layer_order_array = cfg.getOrder();
        if (layer_order_array == null) {
            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
    static Obj createGroup1(PDFDoc doc, Obj layer) throws PDFNetException {
        ElementWriter writer = new ElementWriter();
        writer.begin(doc);

        // Create an Image that can be reused in the document or on the same page.
        Image img = Image.create(doc.getSDFDoc(), (input_path + "peppers.jpg"));

        ElementBuilder builder = new ElementBuilder();
        Element element = builder.createImage(img, new Matrix2D(img.getImageWidth() / 2, -145, 20, img.getImageHeight() / 2, 200, 150));
        writer.writePlacedElement(element);

        GState gstate = element.getGState();    // use the same image (just change its matrix)
        gstate.setTransform(200, 0, 0, 300, 50, 450);
        writer.writePlacedElement(element);

        // use the same image again (just change its matrix).
        writer.writePlacedElement(builder.createImage(img, 300, 600, 200, -150));

        Obj grp_obj = writer.end();

        // Indicate that this form (content group) belongs to the given layer (OCG).
        grp_obj.putName("Subtype", "Form");
        grp_obj.put("OC", layer);
        grp_obj.putRect("BBox", 0, 0, 1000, 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
    static Obj createGroup2(PDFDoc doc, Obj layer) throws PDFNetException {
        ElementWriter writer = new ElementWriter();
        writer.begin(doc);

        // Create a path object in the shape of a heart.
        ElementBuilder builder = new ElementBuilder();
        builder.pathBegin();        // start constructing the path
        builder.moveTo(306, 396);
        builder.curveTo(681, 771, 399.75, 864.75, 306, 771);
        builder.curveTo(212.25, 864.75, -69, 771, 306, 396);
        builder.closePath();
        Element element = builder.pathEnd(); // the path geometry is now specified.

        // Set the path FILL color space and color.
        element.setPathFill(true);
        GState gstate = element.getGState();
        gstate.setFillColorSpace(ColorSpace.createDeviceCMYK());
        gstate.setFillColor(new ColorPt(1, 0, 0, 0));  // cyan

        // Set the path STROKE color space and color.
        element.setPathStroke(true);
        gstate.setStrokeColorSpace(ColorSpace.createDeviceRGB());
        gstate.setStrokeColor(new ColorPt(1, 0, 0));  // red
        gstate.setLineWidth(20);

        gstate.setTransform(0.5, 0, 0, 0.5, 280, 300);

        writer.writeElement(element);

        Obj grp_obj = writer.end();

        // Indicate that this form (content group) belongs to the given layer (OCG).
        grp_obj.putName("Subtype", "Form");
        grp_obj.put("OC", layer);
        grp_obj.putRect("BBox", 0, 0, 1000, 1000);    // Set the clip box for the content.

        return grp_obj;
    }

    // Creates some text and associate it with the text layer
    static Obj createGroup3(PDFDoc doc, Obj layer) throws PDFNetException {
        ElementWriter writer = new ElementWriter();
        writer.begin(doc);

        // Create a path object in the shape of a heart.
        ElementBuilder builder = new ElementBuilder();

        // Begin writing a block of text
        Element element = builder.createTextBegin(Font.create(doc, Font.e_times_roman), 120);
        writer.writeElement(element);

        element = builder.createTextRun("A text layer!");

        // Rotate text 45 degrees, than translate 180 pts horizontally and 100 pts vertically.
        Matrix2D transform = Matrix2D.rotationMatrix(-45 * (3.1415 / 180.0));
        transform.concat(1, 0, 0, 1, 180, 100);
        element.setTextMatrix(transform);

        writer.writeElement(element);
        writer.writeElement(builder.createTextEnd());

        Obj grp_obj = writer.end();

        // Indicate that this form (content group) belongs to the given layer (OCG).
        grp_obj.putName("Subtype", "Form");
        grp_obj.put("OC", layer);
        grp_obj.putRect("BBox", 0, 0, 1000, 1000);    // Set the clip box for the content.

        return grp_obj;
    }
}
