PDFlib Cookbook

cookbook

pdfa/clone_pdfa

Clone PDF/A and PDF/X standard documents.

Download Java Code  Switch to PHP Code  Show Output  Show Input (PLOP-datasheet-PDFA-1b.pdf) 

/*
 * Clone PDF/A, PDF/UA and PDF/X standard documents
 * This is useful as basis for additional processing,
 * such as stamping, adding XMP metadata, adding page content, etc.
 *
 * The following aspects of the input document are cloned:
 * - PDF/A, PDF/UA and PDF/X version
 * - PDF/A or PDF/X output intent (if present)
 * - document language (if present)
 * - all pages including page geometry, i.e. page boxes and Rotate key
 * - the structure elements (tags); if required, an additional
 *   tag is inserted on top of the imported page elements
 * - XMP document metadata
 *   This will generally also clone document info fields since these are
 *   synchronized with XMP in the majority of modern PDF documents.
 *
 * To demonstrate coordinate transformations which may be required
 * to add new page content this topic adds a stamp across all pages.
 *
 * Input documents may conform to any combination of PDF/A, PDF/UA 
 * and PDF/X simultaneously.
 *
 * Note: Except for the names of the input and output documents the three
 * Cookbook topics clone_pdfa, clone_pdfua and clone_pdfx are exact copies.
 * They are included multiply so that they can easily be found in the
 * PDF/A, PDF/UA and PDF/X categories.
 *
 * required software: PDFlib+PDI/PPS 10
 * required data: PDF/A, PDF/UA or PDF/X input document
 */

package com.pdflib.cookbook.pdflib.pdfa;

import com.pdflib.pdflib;
import com.pdflib.PDFlibException;

public class clone_pdfa {
    public static void main(String argv[]) {
        /* This is where the data files are. Adjust as necessary. */
        String searchpath = "../input";

        /* By default annotations are also imported. In some cases this
         * requires the Noto fonts for creating annotation appearance streams.
         * We therefore set the searchpath to also point to the font directory.
         */
        String fontpath = "../resource/font";

        pdflib p = null;
        
        String pdfinputfile = "PLOP-datasheet-PDFA-1b.pdf";
        String title = "clone_pdfa";
        
        String pdfaversion, pdfuaversion, pdfxversion, inputlang;
        boolean taggedinput;
        String optlist = "";
        byte xmp[];

        /* The following standard flavors can be cloned: */
        String supportedflavors[] = {
            "PDF/A-1a:2005", "PDF/A-1b:2005", 
            "PDF/A-2a", "PDF/A-2b", "PDF/A-2u",
            "PDF/A-3a", "PDF/A-3b", "PDF/A-3u",
            
            "PDF/X-3:2003",
            "PDF/X-4", "PDF/X-4p", 
            "PDF/X-5g", "PDF/X-5pg", "PDF/X-5n",
            
            "PDF/UA-1",
            "none",
        };

        int indoc, endpage, pageno, i;
        double x, y, phi, width, height;
        int exitcode = 0;

        try {
            p = new pdflib();

            p.set_option("searchpath={" + searchpath + "}");

            p.set_option("searchpath={" + fontpath + "}");

            /* This means we must check return values of load_font() etc. */
            p.set_option("errorpolicy=return");

            /*
             * Open the input PDF, preserve tags if present (for cloning
             * PDF/A-1/2/3a and PDF/UA)
             */
            indoc = p.open_pdi_document(pdfinputfile, "usetags=true");
            if (indoc == -1) {
                throw new Exception("Error: " + p.get_apiname() + ": "
                    + p.get_errmsg());
            }

            /*
             * Read PDF/A, PDF/UA and PDF/X version of the input document
             */
            pdfaversion = p.pcos_get_string(indoc, "pdfa");
            pdfuaversion = p.pcos_get_string(indoc, "pdfua");
            pdfxversion = p.pcos_get_string(indoc, "pdfx");

            for (i = 0; i < supportedflavors.length; i++) {
                if (pdfaversion.equals(supportedflavors[i])) {
                    optlist += " pdfa=" + pdfaversion;
                    break;
                }
            }
            if (i == supportedflavors.length)
                System.err.println("Error: Cannot clone " + pdfaversion
                    + " documents");

            for (i = 0; i < supportedflavors.length; i++) {
                if (pdfuaversion.equals(supportedflavors[i])) {
                    optlist += " pdfua=" + pdfuaversion;
                    break;
                }
            }
            if (i == supportedflavors.length)
                System.err.println("Error: Cannot clone " + pdfuaversion
                    + " documents");

            for (i = 0; i < supportedflavors.length; i++) {
                if (pdfxversion.equals(supportedflavors[i])) {
                    optlist += " pdfx=" + pdfxversion;
                    break;
                }
            }
            if (i == supportedflavors.length)
                System.err.println("Error: Cannot clone " + pdfxversion
                    + " documents");

            /*
             * Read language entry of the input document if present
             */

            if (p.pcos_get_string(indoc, "type:/Root/Lang").equals("string")) {
                inputlang = p.pcos_get_string(indoc, "/Root/Lang");
                optlist += " lang=" + inputlang;
            }
            else if (pdfuaversion.equals("PDF/UA-1"))
            {
                /* PDF/UA documents don't necessarily need the /Lang entry
                 * in the Catalog, but PDFlib requires the "lang" option.
                 * We supply a default language (which may be wrong) to
                 * ensure that such documents can be cloned nevertheless.
                 */
            	final String DEFAULT_LANGUAGE = "en";
            	optlist += " lang=" + DEFAULT_LANGUAGE;
            }
            else
                inputlang = "";

            /*
             * Clone XMP metadata of input document if present
             */
            if (p.pcos_get_string(indoc, "type:/Root/Metadata")
                                                    .equals("stream")) {
                xmp = p.pcos_get_stream(indoc, "", "/Root/Metadata");
                p.create_pvf("/xmp/document.xmp", xmp, "");
                optlist += " metadata={filename=/xmp/document.xmp}";
            }

            /*
             * Read Tagged status of input document
             */
            taggedinput = p.pcos_get_string(indoc, "tagged").equals("true");

            if (taggedinput)
                optlist += " tag={tagname=Document}";

            /*
             * Create a new document and clone PDF/A, PDF/UA and PDF/X status
             */
            if (p.begin_document(title + ".pdf", optlist) == -1)
                throw new Exception("Error: " + p.get_apiname() + ": "
                                                        + p.get_errmsg());

            p.set_info("Creator", "PDFlib Cookbook");
            p.set_info("Title", title);

            /*
             * Clone PDF/A or PDF/X output intent
             */
            if (p.process_pdi(indoc, -1, "action=copyoutputintent") == -1)
                throw new Exception("Error: " + p.get_apiname() + ": "
                    + p.get_errmsg());

            /* Create a bookmark with the name of the input document */
            p.create_bookmark(pdfinputfile, "");

            endpage = (int) p.pcos_get_number(indoc, "length:pages");

            /* Copy all pages of the input document */
            for (pageno = 1; pageno <= endpage; pageno++) {
                String lowerleftcorner[][] = {
                    { "x1", "y1" }, /* 0 degrees */
                    { "x2", "y2" }, /* 90 degrees */
                    { "x3", "y3" }, /* 180 degrees */
                    { "x4", "y4" }, /* 270 degrees */
                };

                int page = p.open_pdi_page(indoc, pageno, "cloneboxes");

                if (page == -1) {
                    System.err.println("Error: " + p.get_errmsg());
                    continue;
                }

                /*
                 * Query the geometry of the cloned page. This is required to
                 * account for translated or rotated pages if we want to add
                 * more contents to the page.
                 */
                phi = p.info_pdi_page(page, "rotate", "");

                /*
                 * Select the lower left corner depending on the rotation angle
                 */
                x = p.info_pdi_page(page, lowerleftcorner[(int) (phi / 90)][0],
                    "");
                y = p.info_pdi_page(page, lowerleftcorner[(int) (phi / 90)][1],
                    "");

                boolean fittingpossible = true;
                String additionaltag = "";

                if (taggedinput) {
                    int topleveltagcount = (int) p.info_pdi_page(page,
                        "topleveltagcount", "");

                    if (topleveltagcount == 0) {
                        /*
                         * The page doesn't contain any structure elements, i.e.
                         * it is empty or contains only Artifacts. Some
                         * applications may decide to skip such pages.
                         * 
                         * We add an "Artifact" tag to work around an Acrobat
                         * bug.
                         */
                        additionaltag = "tag={tagname=Artifact} ";
                    }
                    else if (p.info_pdi_page(page, "fittingpossible", "")
                                                                        == 0) {
                        /*
                         * Try to place the page without any additional tag; if
                         * this doesn't work we insert another tag.
                         */
                        additionaltag = "tag={tagname=P} ";
                        if (p.info_pdi_page(page, "fittingpossible",
                            additionaltag) == 0) {
                            fittingpossible = false;
                        }
                    }
                }

                if (fittingpossible) {
                    /* Page size will be adjusted by "cloneboxes" option */
                    p.begin_page_ext(0, 0, "width=a4.width height=a4.height");

                    optlist = "cloneboxes ";
                    if (taggedinput)
                        optlist += additionaltag;

                    p.fit_pdi_page(page, 0, 0, optlist);

                    /*
                     * Adjust the coordinate system to facilitate adding new
                     * page content on top of the cloned page.
                     */
                    p.translate(x, y);
                    p.rotate(phi);

                    width = p.info_pdi_page(page, "pagewidth", "");
                    height = p.info_pdi_page(page, "pageheight", "");

                    /*
                     * Add some text on each page and tag it as Artifact.
                     */
                    optlist = "fontname=NotoSerif-Regular "
                        + "textrendering=1 stamp=ll2ur "
                        + "boxsize={" + width + " " + height + "}";

                    if (taggedinput)
                        optlist += " tag={tagname=Artifact}";

                    p.fit_textline("Cloned page", 0, 0, optlist);

                    p.end_page_ext("");
                }
                else {
                    System.err.println("Skipping page " + pageno + " of '"
                        + pdfinputfile + "': " + p.get_errmsg());
                }
                
                p.close_pdi_page(page);
            }

            p.end_document("");
            p.delete_pvf("/xmp/document.xmp");
            p.close_pdi_document(indoc);
        }
        catch (PDFlibException e) {
            System.err.println("PDFlib exception occurred:");
            System.err.println("[" + e.get_errnum() + "] " + e.get_apiname() +
                ": " + e.get_errmsg());
            exitcode = 1;
        }
        catch (Exception e) {
            System.err.println(e);
            exitcode = 1;
        }
        finally {
            if (p != null) {
                p.delete();
            }
            System.exit(exitcode);
        }
    }
}