All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jpedal.objects.acroforms.AcroRenderer Maven / Gradle / Ivy

/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/support/
 *
 * (C) Copyright 1997-2017 IDRsolutions and Contributors.
 *
 * This file is part of JPedal/JPDF2HTML5
 *
     This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


 *
 * ---------------
 * AcroRenderer.java
 * ---------------
 */
package org.jpedal.objects.acroforms;

import java.awt.image.BufferedImage;
import java.util.*;

import org.jpedal.exception.PdfException;
import org.jpedal.external.ExternalHandlers;
import org.jpedal.io.PdfObjectReader;
import org.jpedal.objects.Javascript;
import org.jpedal.objects.PdfPageData;
import org.jpedal.objects.acroforms.actions.ActionHandler;
import org.jpedal.objects.acroforms.creation.FormFactory;
import org.jpedal.objects.acroforms.creation.SwingFormCreator;
import org.jpedal.objects.acroforms.utils.FormUtils;
import org.jpedal.objects.layers.PdfLayerList;
import org.jpedal.objects.raw.*;
import org.jpedal.parser.FormFlattener;
import org.jpedal.parser.PdfStreamDecoder;
import org.jpedal.parser.PdfStreamDecoderForPrinting;
import org.jpedal.parser.PrintStreamDecoder;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.StringUtils;


/**
 * Provides top level to forms handling, assisted by separate classes to
 * decode widgets (FormDecoder - default implements Swing set)
 * create Form widgets (implementation of FormFactory),
 * store and render widgets (GUIData),
 * handle Javascript and Actions (Javascript and ActionHandler)
 * and support for Signature object
 */
public class AcroRenderer {

    private static final boolean flattenForms, ignoreAllForms;

    static {

        String s = System.getProperty("org.jpedal.flattenForm");

        flattenForms = s != null && s.equalsIgnoreCase("true");

        s = System.getProperty("org.jpedal.ignoreAllForms");

        ignoreAllForms = s != null && s.equalsIgnoreCase("true");

    }

    public static boolean FROM_JPDF2FORMS;

    FormObject[] Fforms, Aforms;

    private PdfObject AcroRes;

    private float dpi = 72f;

    private Object[] CO;
    PdfArrayIterator fieldList;
    private PdfArrayIterator[] annotList;

    /**
     * flag to show we ignore forms
     */
    private boolean ignoreForms;

    private boolean alwaysUseXFA;

    /**
     * creates all GUI components from raw data in PDF and stores in GUIData instance
     */
    public FormFactory formFactory;

    /**
     * holder for all data (implementations to support Swing and ULC)
     */
    public GUIData compData;

    /**
     * holds sig object so we can easily retrieve
     */
    private Set sigObject;
    private Map sigKeys; //and allow us to trap multiple if in both Annot and Form

    /**
     * holds copy of object to access the mediaBox and cropBox values
     */
    private PdfPageData pageData;

    /**
     * number of entries in acroFormDataList, each entry can have a button group of more that one button
     * 'A' annot and 'F' form - A is per page, F is total hence 3 variables
     */
    private int[] AfieldCount;

    private int ATotalCount, FfieldCount;

    private int additionalOBjectRef;

    private boolean addedMissingPopup;

    /**
     * number of pages in current PDF document
     */
    int pageCount;

    /**
     * handle on object reader for decoding objects
     */
    PdfObjectReader currentPdfFile;

    /**
     * parses and decodes PDF data into generic data for converting to widgets
     */
    FormStream fDecoder;

    /**
     * handles events like URLS, EMAILS
     */
    private ActionHandler formsActionHandler;

    /**
     * handles Javascript events
     */
    private Javascript javascript;

    /*flag to show if XFA or FDF*/
    boolean hasXFA;
    private boolean isContainXFAStream;

    /**
     * flag to show if we use XFA
     */
    private boolean useXFA;

    /**
     * allow us to differentiate underlying PDF form type
     */
    Enum PDFformType;

    private SwingFormCreator formCreator;

    /**
     * used to create version without XFA support in XFA version.
     * Should not be used otherwise.
     */
    public AcroRenderer() {
    }

    public void useXFAIfAvailable(final boolean useXFA) {

        this.useXFA = useXFA;

    }

    /**
     * reset handler (must be called Before page opened)
     * - null Object resets to default
     */
    public void resetHandler(final ActionHandler formsActionHandler, final float dpi, final Javascript javascript) {

        this.formsActionHandler = formsActionHandler;

        this.dpi = dpi;

        this.javascript = javascript;

        //pass values down
        if (formFactory != null) {
            formFactory.reset(this.getFormResources(), formsActionHandler, pageData, currentPdfFile);
        }
    }

    /**
     * make all components invisible on all pages by removing from Display
     */
    public void removeDisplayComponentsFromScreen() {

        if (compData != null) {
            compData.removeAllComponentsFromScreen();
        }

    }

    /**
     * initialise holders and variables, data structures and get a handle on data object
     */
    public int openFile(final int pageCount, final int insetW, final int insetH, final PdfPageData pageData, final PdfObjectReader currentPdfFile, final PdfObject acroObj) {

        this.pageCount = pageCount;
        this.currentPdfFile = currentPdfFile;
        this.pageData = pageData;

        compData.flushFormData();

        //explicitly flush
        sigObject = null;
        sigKeys = null;

        //track inset on page
        compData.setPageData(pageData, insetW, insetH);

        if (acroObj == null) {

            FfieldCount = 0;

            fieldList = null;
        } else {

            //handle XFA
            final PdfObject XFAasStream;
            PdfArrayIterator XFAasArray = null;

            XFAasStream = acroObj.getDictionary(PdfDictionary.XFA);
            if (XFAasStream == null) {
                XFAasArray = acroObj.getMixedArray(PdfDictionary.XFA);

                //empty array
                if (XFAasArray != null && XFAasArray.getTokenCount() == 0) {
                    XFAasArray = null;
                }
            }

            hasXFA = XFAasStream != null || XFAasArray != null;
            isContainXFAStream = hasXFA;

            /*
             * now read the fields
             */
            fieldList = acroObj.getMixedArray(PdfDictionary.Fields);
            CO = acroObj.getObjectArray(PdfDictionary.CO);

            if (fieldList != null) {
                FfieldCount = fieldList.getTokenCount();

                AcroRes = acroObj.getDictionary(PdfDictionary.DR);

                if (AcroRes != null) {
                    currentPdfFile.checkResolved(AcroRes);
                }

            } else {
                FfieldCount = 0;
                AcroRes = null;
            }
            
            /*
             * choose correct decoder for form data
             */
            if (hasXFA && useXFA) {
                processXFAFields(acroObj, currentPdfFile, pageData);
            }

            //we need to read list if FDF
            //or redo list if Legacy XFA
            if (!hasXFA) {
                resolveIndirectFieldList(false);
            }

        }

        resetContainers(true);

        return pageCount;
    }

    /**
     * empty implementation in non-XFA AcroRenderer
     *
     * @param acroObj1
     * @param currentPdfFile1
     * @param pageData1
     */
    void processXFAFields(final PdfObject acroObj1, final PdfObjectReader currentPdfFile1, final PdfPageData pageData1) {
        throw new RuntimeException("This code (processXFAFields) should never be called");
    }

    void resolveIndirectFieldList(final boolean resolveParents) {

        //allow for indirect
        while (FfieldCount == 1) {

            //may have been read before so reset
            fieldList.resetToStart();

            final String key = fieldList.getNextValueAsString(false);

            final FormObject kidObject = new FormObject(key);
            currentPdfFile.readObject(kidObject);

            final byte[][] childList = getKid(kidObject, resolveParents);

            if (childList == null) {
                break;
            }

            fieldList = new PdfArrayIterator(childList);
            FfieldCount = fieldList.getTokenCount();
        }
    }

    /**
     * initialise holders and variables and get a handle on data object
     * 
* Complicated as Annotations stored on a PAGE basis whereas FORMS stored on * a file basis */ public void resetAnnotData(final int insetW, final int insetH, final PdfPageData pageData, final int page, final PdfObjectReader currentPdfFile, final byte[][] currentAnnotList) { this.currentPdfFile = currentPdfFile; this.pageData = pageData; boolean resetToEmpty = true; addedMissingPopup = false; //track inset on page compData.setPageData(pageData, insetW, insetH); if (currentAnnotList == null) { AfieldCount = null; ATotalCount = 0; if (annotList != null) { annotList[page] = null; } annotList = null; } else { int pageCount = pageData.getPageCount() + 1; if (pageCount <= page) { pageCount = page + 1; } if (annotList == null) { annotList = new PdfArrayIterator[pageCount]; AfieldCount = new int[pageCount]; } else if (page >= annotList.length) { final PdfArrayIterator[] tempList = annotList; final int[] tempCount = AfieldCount; AfieldCount = new int[pageCount]; annotList = new PdfArrayIterator[pageCount]; for (int ii = 0; ii < tempList.length; ii++) { AfieldCount[ii] = tempCount[ii]; annotList[ii] = tempList[ii]; } } else if (AfieldCount == null) { AfieldCount = new int[pageCount]; } annotList[page] = new PdfArrayIterator(currentAnnotList); final int size = annotList[page].getTokenCount(); AfieldCount[page] = size; ATotalCount += size; resetToEmpty = false; /* * choose correct decoder for form data */ if (fDecoder == null) { PDFformType = FormTypes.NON_XFA; fDecoder = new FormStream(); } } resetContainers(resetToEmpty); } /** * flush or resize data containers */ protected void resetContainers(final boolean resetToEmpty) { /*form or reset Annots*/ if (resetToEmpty) { compData.resetComponents(ATotalCount + FfieldCount, pageCount, false); } else { compData.resetComponents(ATotalCount + FfieldCount, pageCount, true); } if (formFactory == null) { formFactory = formCreator.createFormFactory(); formFactory.reset(this.getFormResources(), formsActionHandler, pageData, currentPdfFile); } else { //to keep customers formfactory usable formFactory.reset(this.getFormResources(), formsActionHandler, pageData, currentPdfFile); } } /** * build forms display using standard swing components */ public void createDisplayComponentsForPage(final int page, final PdfStreamDecoder current) { //check if we want to flatten forms if (flattenForms) { compData.setRasterizeForms(true); } /*see if already done*/ if (!ignoreAllForms && (!compData.hasformsOnPageDecoded(page) || (formsRasterizedForDisplay() && current != null))) { /* ensure space for all values*/ compData.initParametersForPage(pageData, page, formFactory, dpi); /* * think this needs to be revised, and different approach maybe storing, and reuse if respecified in file, * need to look at other files to work out solution. * files :- * lettreenvoi.pdf page 2+ no next page field * costena.pdf checkboxes not changable * * maybe if its just reset on multipage files */ //list of forms done final Map formsProcessed = new HashMap(); int Acount = 0; if (AfieldCount != null && AfieldCount.length > page) { Acount = AfieldCount[page]; } Fforms = new FormObject[FfieldCount]; Aforms = new FormObject[Acount]; FormObject formObject; String objRef; int i, count; { //scan list for all relevant values and add to array if valid //0 = forms, 1 = annots final int decodeToForm = 2; for (int forms = 0; forms < decodeToForm; forms++) { i = 0; if (forms == 0) { count = 0; if (fieldList != null) { fieldList.resetToStart(); count = fieldList.getTokenCount() - 1; } } else { if (annotList != null && annotList.length > page && annotList[page] != null) { if (!isContainXFAStream && formFactory.getType() == FormFactory.HTML) { annotList[page].resetToStart(); final Map annotOrder = new HashMap(); final int count2 = annotList[page].getTokenCount(); String val; for (int ii = 0; ii < count2; ii++) { val = annotList[page].getNextValueAsString(true); annotOrder.put(val, String.valueOf(ii + 1)); } formFactory.setAnnotOrder(annotOrder); } //We need to do this regardless annotList[page].resetToStart(); } count = Acount - 1; } for (int fieldNum = count; fieldNum > -1; fieldNum--) { objRef = null; if (forms == 0) { if (fieldList != null) { objRef = fieldList.getNextValueAsString(true); } } else { if (addedMissingPopup && !annotList[page].hasMoreTokens()) { //Ignore this as we have added our own object that does not need reading //with the PdfArrayIterator. Code positioned like this to explain. //Test File : baseline_screens\cid2\SampleError.pdf } else { if (annotList.length > page && annotList[page] != null) { objRef = annotList[page].getNextValueAsString(true); } } } if (objRef == null || ((formsProcessed.get(objRef) != null || objRef.isEmpty()))) { continue; } try { formObject = convertRefToFormObject(objRef, page); if (!isAnnotation(formObject)) { continue; } } catch (final Exception e) { LogWriter.writeLog("Exception " + e + " with " + objRef); continue; } /* * Only allows Annotations if in Annots page stream */ if (forms == 0 && formObject != null && formObject.getFormType() == -1) { continue; } if (allowsPopup(formObject) && formObject.getDictionary(PdfDictionary.Popup) == null) { final FormObject po = new FormObject(PdfDictionary.Annot); additionalOBjectRef++; po.setRef((-additionalOBjectRef) + " 0 X"); po.setIntNumber(PdfDictionary.F, 24); //Bit Flag for bits 4+5 (No Zoom, No Rotate) po.setBoolean(PdfDictionary.Open, formObject.getBoolean(PdfDictionary.Open)); po.setConstant(PdfDictionary.Subtype, PdfDictionary.Popup); final float[] rect = formObject.getFloatArray(PdfDictionary.Rect); if (pageData.getRotation(page) % 180 != 0) { po.setFloatArray(PdfDictionary.Rect, new float[]{rect[2], pageData.getCropBoxHeight(page) - 100, rect[2] + 160, pageData.getCropBoxHeight(page)}); } else { po.setFloatArray(PdfDictionary.Rect, new float[]{pageData.getCropBoxWidth(page), rect[3] - 100, pageData.getCropBoxWidth(page) + 160, rect[3]}); } po.setStringKey(PdfDictionary.Parent, formObject.getObjectRefAsString().getBytes()); po.setParentPdfObj(formObject); po.setPageNumber(page); formObject.setDictionary(PdfDictionary.Popup, po); final FormObject[] newForms = new FormObject[Aforms.length + 1]; for (int ii = 0; ii != Aforms.length; ii++) { newForms[ii] = Aforms[ii]; } newForms[Aforms.length] = po; Aforms = newForms; addedMissingPopup = true; } final byte[][] kids = formObject.getKeyArray(PdfDictionary.Kids); if (kids != null) //not 'proper' kids so process here { i = flattenKids(page, formsProcessed, formObject, i, forms); } else { i = processFormObject(page, formsProcessed, formObject, objRef, i, forms); } } } } final List unsortedForms = new ArrayList(); final List sortedForms = new ArrayList(); compData.setListForPage(page, unsortedForms, false); compData.setListForPage(page, sortedForms, true); final Map formsCreated = createAndStoreFormComponents(current, unsortedForms, sortedForms, page); if (!formsRasterizedForDisplay()) { // Go through all forms created and run through javascript for initiation try { //get current page object final String ref = currentPdfFile.getReferenceforPage(page); final PageObject pageObj = new PageObject(ref); currentPdfFile.readObject(pageObj); //call Page Level open commands if (javascript != null && formsActionHandler != null) { //NOTE: moved here as the Runnable can be executed after forms have been created which is too late. formsActionHandler.O(pageObj, PdfDictionary.AA); formsActionHandler.O(pageObj, PdfDictionary.A); //just in case formsActionHandler.PO(pageObj, PdfDictionary.AA); formsActionHandler.PO(pageObj, PdfDictionary.A); //just in case } if (formFactory.getType() != FormFactory.HTML && formFactory.getType() != FormFactory.SVG) { //then initilise each field initJSonFields(formsCreated); } } catch (final Exception e) { LogWriter.writeLog("Exception: " + e.getMessage()); } } } } private Map createAndStoreFormComponents(final PdfStreamDecoder current, final List unsortedForms, final List sortedForms, final int page) { final Map formsCreated = new HashMap(); FormObject[] xfaFormList = null; FormObject formObject; int count; if (hasXFA && useXFA) { xfaFormList = createXFADisplayComponentsForPage(xfaFormList, page); } //XFA, FDF FORMS then ANNOTS final int readToForm = 3; for (int forms = 0; forms < readToForm; forms++) { count = 0; if (forms == 0) { if (xfaFormList != null) { count = xfaFormList.length; } } else if (forms == 1) { if (Fforms == null) { count = 0; } else { //store current order of forms for printing for (final FormObject Fform : Fforms) { if (Fform != null) { unsortedForms.add(Fform); } } //sort forms into size order for display Fforms = FormUtils.sortGroupLargestFirst(Fforms); count = Fforms.length; } } else { //store current order of forms for printing for (final FormObject Aform : Aforms) { if (Aform != null) { unsortedForms.add(Aform); } } //sort forms into size order for display if (!formsRasterizedForDisplay()) { Aforms = FormUtils.sortGroupLargestFirst(Aforms); } if (isContainXFAStream) { final HashMap> tabMap = new HashMap>(); double maxY = 0; for (final FormObject obj : Aforms) { if (obj != null) { final int x = obj.getBoundingRectangle().x; final Double y = obj.getBounding2DRectangleForTabbing().getY(); maxY = Math.max(y, maxY); if (tabMap.containsKey(y)) { final ArrayList fList = tabMap.get(y); int insertion = -1; for (int z = 0; z < fList.size(); z++) { final int nextX = fList.get(z).getBoundingRectangle().x; if (nextX < x) { insertion = z; } } if (insertion == -1) { fList.add(0, obj); } else { fList.add(insertion + 1, obj); } } else { final ArrayList list = new ArrayList(); list.add(obj); tabMap.put(y, list); } } } final FormObject[] finalList = new FormObject[Aforms.length]; int objCount = 0; final Object[] keys = new Object[tabMap.size()]; int cc = 0; for (final Object k : tabMap.keySet().toArray()) { keys[cc] = k; cc++; } Arrays.sort(keys); for (int k = keys.length; k > 0; k--) { final ArrayList objList = tabMap.get(keys[k - 1]); if (objList != null) { for (final FormObject f : objList) { finalList[objCount] = f; objCount++; } } } Aforms = finalList; } count = Aforms.length; } boolean firstPopup = true; for (int k = 0; k < count; k++) { if (forms == 0) { formObject = xfaFormList[k]; } else if (forms == 1) { formObject = Fforms[k]; } else { formObject = Aforms[k]; } if (formObject != null && (formsCreated.get(formObject.getObjectRefAsString()) == null) && page == formObject.getPageNumber()) { // && !formObject.getObjectRefAsString().equals("216 0 R")){ final int type = formFactory.getType(); //NOTE: if this custom form needs redrawing more changediff ReadOnlyTextIcon.MAXSCALEFACTOR to 1; if ((formsRasterizedForDisplay() && current != null) || (type == FormFactory.SVG && formObject.getParameterConstant(PdfDictionary.Subtype) != PdfDictionary.Link) || (formFactory.flattenForms() && !AcroRenderer.isAnnotation(formObject))) { //rasterize any flattened PDF forms here try { getFormFlattener().drawFlattenedForm(current, formObject, type == FormFactory.HTML || type == FormFactory.SVG, (PdfObject) this.getFormResources()[0]); } catch (final PdfException e) { LogWriter.writeLog("Exception: " + e.getMessage()); } } else { createField(formObject); //now we turn the data into a Swing component //set the raw data here so that the field names are the fully qualified names compData.storeRawData(formObject); //store data so user can access formsCreated.put(formObject.getObjectRefAsString(), "x"); //original method we still use for HTML/SVG if (type == FormFactory.HTML || type == FormFactory.SVG || type == FormFactory.JAVAFX) { sortedForms.add(formObject); //neede in display to fix position issues } else if (formObject.getParameterConstant(PdfDictionary.Subtype) == PdfDictionary.Popup) { /* * To match examples the first popup is drawn over all * then we draw all other popups in the pdf order from * the bottom to the one before the top */ if (firstPopup) { sortedForms.add(formObject); firstPopup = false; } else { sortedForms.add(sortedForms.size() - 1, formObject); } } else { sortedForms.add(0, formObject); } } } } } return formsCreated; } /** * Utility method to check if formObject should have a popup * * @return True if popup should exist */ static boolean allowsPopup(final FormObject formObject) { switch (formObject.getParameterConstant(PdfDictionary.Subtype)) { case PdfDictionary.Text: // case PdfDictionary.Line : case PdfDictionary.Square: // case PdfDictionary.Circle : // case PdfDictionary.Polygon : // case PdfDictionary.PolyLine : case PdfDictionary.Highlight: case PdfDictionary.Underline: // case PdfDictionary.Squiggly : case PdfDictionary.StrickOut: case PdfDictionary.Stamp: // case PdfDictionary.Caret : // case PdfDictionary.Ink : // case PdfDictionary.FileAttachment : // case PdfDictionary.Sound : // case PdfDictionary.Movie : // case PdfDictionary.Widget : // case PdfDictionary.Screen : // case PdfDictionary.RichMedia: // case PdfDictionary.PrinterMark : // case PdfDictionary.TrapNet : // case PdfDictionary.Watermark : // case PdfDictionary.3D : return true; default: return false; } } /** * Utility method to ensure formObject is actually an annotation before we continue * * @return True if annotation */ public static boolean isAnnotation(final FormObject formObject) { if (formObject.getParameterConstant(PdfDictionary.Type) == PdfDictionary.Annot) { return true; } switch (formObject.getParameterConstant(PdfDictionary.Subtype)) { case PdfDictionary.Text: case PdfDictionary.Link: case PdfDictionary.FreeText: case PdfDictionary.Line: case PdfDictionary.Square: case PdfDictionary.Circle: case PdfDictionary.Polygon: case PdfDictionary.PolyLine: case PdfDictionary.Highlight: case PdfDictionary.Underline: case PdfDictionary.Squiggly: case PdfDictionary.StrickOut: case PdfDictionary.Stamp: case PdfDictionary.Caret: case PdfDictionary.Ink: case PdfDictionary.Popup: case PdfDictionary.FileAttachment: case PdfDictionary.Sound: case PdfDictionary.Movie: case PdfDictionary.Widget: case PdfDictionary.Screen: case PdfDictionary.PrinterMark: case PdfDictionary.TrapNet: case PdfDictionary.Watermark: case PdfDictionary.THREE_D: // From the Supplement to the PDF spec case PdfDictionary.RichMedia: case PdfDictionary.Projection: return true; default: return false; } } private void storeSignatures(final FormObject formObject, final int formType) { //if sig object set global sig object so we can access later if (formType == PdfDictionary.Sig) { if (sigObject == null) { //ensure initialised sigObject = new HashSet(); sigKeys = new HashMap(); } if (!sigKeys.containsKey(formObject.getObjectRefAsString())) { //avoid duplicates sigObject.add(formObject); sigKeys.put(formObject.getObjectRefAsString(), "x"); } } } private int flattenKids(final int page, final Map formsProcessed, final FormObject formObject, int i, final int forms) { final byte[][] kidList = formObject.getKeyArray(PdfDictionary.Kids); final int kidCount = kidList.length; //resize to fit if (forms == 0) { final int oldCount = Fforms.length; final FormObject[] temp = Fforms; Fforms = new FormObject[oldCount + kidCount - 1]; System.arraycopy(temp, 0, Fforms, 0, oldCount); } else { final int oldCount = Aforms.length; final FormObject[] temp = Aforms; Aforms = new FormObject[oldCount + kidCount - 1]; System.arraycopy(temp, 0, Aforms, 0, oldCount); } for (final byte[] aKidList : kidList) { //iterate through all parts final String key = new String(aKidList); //now we have inherited values, read final FormObject childObj = new FormObject(key); //inherit values childObj.copyInheritedValuesFromParent(formObject); currentPdfFile.readObject(childObj); childObj.setRef(key); if (childObj.getKeyArray(PdfDictionary.Kids) == null) { if (!childObj.isAppearanceUsed()) { new FormStream().createAppearanceString(childObj, currentPdfFile); } i = processFormObject(page, formsProcessed, childObj, key, i, forms); } else { i = flattenKids(page, formsProcessed, childObj, i, forms); } } return i; } private int processFormObject(final int page, final Map formsProcessed, final FormObject formObject, final String objRef, int i, final int forms) { boolean isOnPage = false; if (forms == 0) { //check page isOnPage = isFormObjectOnPage(formObject, page); } if (forms == 1 || isOnPage) { formObject.setPageNumber(page); final String parent = formObject.getStringKey(PdfDictionary.Parent); //clone parent to allow inheritance of values or create new if (parent != null) { final FormObject parentObj = getParent(parent, formObject); /* * Getting the parent will also make any associated popup objects. * If this is a popup the parent will instead make a copy so that * we have a copy in annot list and one in form object. This is causing * issue in annotation saving and deletion. */ //Overwrite created popup here with the one we already have. if (formObject.getParameterConstant(PdfDictionary.Subtype) == PdfDictionary.Popup) { parentObj.setDictionary(PdfDictionary.Popup, formObject); } //inherited values if (parentObj != null) { //all values are copied from the parent inside this call formObject.setParent(parent, parentObj, true); } } if (!formObject.isAppearanceUsed()) { fDecoder.createAppearanceString(formObject, currentPdfFile); } //Check that type returns as a valid value to lock out broken objects //Added for case 22215 if (formObject.getParameterConstant(PdfDictionary.Subtype) != -1) { if (parent != null) { formObject.setParent(parent); //parent object was added earlier } if (forms == 0) { Fforms[i++] = formObject; } else { Aforms[i++] = formObject; } //also flag if (objRef != null) { formsProcessed.put(objRef, "x"); } } } return i; } private boolean isFormObjectOnPage(final FormObject formObject, final int page) { PdfObject pageObj = formObject.getDictionary(PdfDictionary.P); byte[] pageRef = null; if (pageObj != null) { pageRef = pageObj.getUnresolvedData(); } if (pageRef == null || pageObj == null) { final String parent = formObject.getStringKey(PdfDictionary.Parent); if (parent != null) { final PdfObject parentObj = getParent(parent, null); pageObj = parentObj.getDictionary(PdfDictionary.P); if (pageObj != null) { pageRef = pageObj.getUnresolvedData(); } } } if (pageRef == null) { final byte[][] kidList = getKid(formObject, false); if (kidList != null && kidList.length > 0) { final int kidCount = kidList.length; FormObject kidObject; for (int jj = 0; jj < kidCount; jj++) { final String key = new String(kidList[jj]); kidObject = compData.getRawFormData().get(key); if (kidObject == null) { kidObject = new FormObject(key); currentPdfFile.readObject(kidObject); compData.storeRawData(kidObject); } pageObj = kidObject.getDictionary(PdfDictionary.P); if (pageObj != null) { pageRef = pageObj.getUnresolvedData(); } if (pageRef != null) { jj = kidCount; } } } } int objPage = -1; if (pageRef != null) { objPage = currentPdfFile.convertObjectToPageNumber(new String(pageRef)); } return objPage == page; } private FormObject getParent(final String parent, final FormObject formObj) { FormObject parentObj = compData.getRawFormData().get(parent); if (parentObj == null && parent != null) { //not yet read so read and cache parentObj = new FormObject(parent); currentPdfFile.readObject(parentObj); //remove kids in Parent parentObj.setKeyArray(PdfDictionary.Kids, null); compData.storeRawData(parentObj); } if (parentObj != null && formObj != null) { final byte[][] kidsInParent = parentObj.getRawKids(); formObj.setRawKids(kidsInParent); } return parentObj; } private byte[][] getKid(final FormObject formObject, final boolean ignoreParent) { final int formType = formObject.getNameAsConstant(PdfDictionary.FT); if (formType == PdfDictionary.Tx || formType == PdfDictionary.Btn) { return null; } byte[][] kidList = formObject.getKeyArray(PdfDictionary.Kids); if (kidList != null && !ignoreParent) { final String parentRef = formObject.getStringKey(PdfDictionary.Parent); final PdfObject parentObj = this.getFormObject(parentRef); if (parentObj != null && parentObj.getKeyArray(PdfDictionary.Kids) != null) { kidList = null; } } return kidList; } /** * display widgets on screen for range (inclusive) */ public void displayComponentsOnscreen(final int startPage, int endPage) { //On rare occurances compData can be null as dispose called during paint. //As dispose only called on viewer close we can lock issue out here if (compData != null) { //make sure this page is inclusive in loop endPage++; compData.displayComponents(startPage, endPage); //used in tests org.jpedal.DevFlags.formsLoaded = true; } } private void initJSonFields(final Map formsCreated) { //scan all fields for javascript actions for (final String ref : formsCreated.keySet()) { final FormObject formObject = getFormObject(ref); javascript.execute(formObject, PdfDictionary.K, ActionHandler.FOCUS_EVENT, ' '); } } /** * create a widget to handle fields */ private void createField(final FormObject formObject) { //avoid creation of Swing widgets if we have 2 copies in play on ULC //noinspection PointlessBooleanExpression if (ExternalHandlers.isULCPresent() && formFactory.getType() == FormFactory.SWING) { return; } final Integer widgetType; //no value set final Object retComponent = null; final int formType = formObject.getNameAsConstant(PdfDictionary.FT); //FT final int formFactoryType = formFactory.getType(); //if sig object set global sig object so we can access later storeSignatures(formObject, formType); //check if a popup is associated if (formObject.getDictionary(PdfDictionary.Popup) != null) { formObject.setActionFlag(FormObject.POPUP); } //flags used to alter interactivity of all fields final boolean readOnly; final boolean required; final boolean noexport; final boolean[] flags = formObject.getFieldFlags(); //Ff if (flags != null) { //noinspection UnusedAssignment readOnly = flags[FormObject.READONLY_ID]; //noinspection UnusedAssignment required = flags[FormObject.REQUIRED_ID]; //noinspection UnusedAssignment noexport = flags[FormObject.NOEXPORT_ID]; /* * boolean comb=flags[FormObject.COMB_ID]; * boolean comminOnSelChange=flags[FormObject.COMMITONSELCHANGE_ID]; * boolean donotScrole=flags[FormObject.DONOTSCROLL_ID]; * boolean doNotSpellCheck=flags[FormObject.DONOTSPELLCHECK_ID]; * boolean fileSelect=flags[FormObject.FILESELECT_ID]; * boolean isCombo=flags[FormObject.COMBO_ID]; * boolean isEditable=flags[FormObject.EDIT_ID]; * boolean isMultiline=flags[FormObject.MULTILINE_ID]; * boolean isPushButton=flags[FormObject.PUSHBUTTON_ID]; * boolean isRadio=flags[FormObject.RADIO_ID]; * boolean hasNoToggleToOff=flags[FormObject.NOTOGGLETOOFF_ID]; * boolean hasPassword=flags[FormObject.PASSWORD_ID]; * boolean multiSelect=flags[FormObject.MULTISELECT_ID]; * boolean radioinUnison=flags[FormObject.RADIOINUNISON_ID]; * boolean richtext=flags[FormObject.RICHTEXT_ID]; * boolean sort=flags[FormObject.SORT_ID]; */ } //hard-coded for HTML non-forms if (!ExternalHandlers.isXFAPresent() && (formFactoryType == FormFactory.HTML || formFactoryType == FormFactory.SVG)) { widgetType = FormFactory.ANNOTATION; } else if (formType == PdfDictionary.Btn) { //----------------------------------- BUTTON ---------------------------------------- widgetType = createButtonField(flags); } else { widgetType = createInteractiveField(flags, formType); } formObject.setFormType(widgetType); if (formFactory.getType() == FormFactory.HTML || formFactory.getType() == FormFactory.SVG) { compData.checkGUIObjectResolved(formObject); } else if (retComponent != null && formFactory.getType() != FormFactory.SWING) { formObject.setGUIComponent(retComponent, formFactory.getType()); compData.setGUIComp(formObject, retComponent); } } private int createInteractiveField(final boolean[] flags, final int formType) { final int widgetType; switch (formType) { case PdfDictionary.Tx: boolean isMultiline = false, hasPassword = false; // doNotScroll = false, richtext = false, fileSelect = false, doNotSpellCheck = false; if (flags != null) { isMultiline = flags[FormObject.MULTILINE_ID]; hasPassword = flags[FormObject.PASSWORD_ID]; //doNotScroll = flags[FormObject.DONOTSCROLL_ID]; //richtext = flags[FormObject.RICHTEXT_ID]; //fileSelect = flags[FormObject.FILESELECT_ID]; //doNotSpellCheck = flags[FormObject.DONOTSPELLCHECK_ID]; } if (isMultiline) { if (hasPassword) { widgetType = FormFactory.MULTILINEPASSWORD; } else { widgetType = FormFactory.MULTILINETEXT; } } else { //singleLine if (hasPassword) { widgetType = FormFactory.SINGLELINEPASSWORD; } else { widgetType = FormFactory.SINGLELINETEXT; } } break; case PdfDictionary.Ch: //----------------------------------------- CHOICE ---------------------------------------------- //flags used for choice types //20100212 (ms) Unused ones commented out boolean isCombo = false; // multiSelect = false, sort = false, isEditable = false, doNotSpellCheck = false, comminOnSelChange = false; if (flags != null) { isCombo = flags[FormObject.COMBO_ID]; //multiSelect = flags[FormObject.MULTISELECT_ID]; //sort = flags[FormObject.SORT_ID]; //isEditable = flags[FormObject.EDIT_ID]; //doNotSpellCheck = flags[FormObject.DONOTSPELLCHECK_ID]; //comminOnSelChange = flags[FormObject.COMMITONSELCHANGE_ID]; } if (isCombo) { // || (type==XFAFORM && ((XFAFormObject)formObject).choiceShown!=XFAFormObject.CHOICE_ALWAYS)){ widgetType = FormFactory.COMBOBOX; } else { //it is a list widgetType = FormFactory.LIST; } break; case PdfDictionary.Sig: widgetType = FormFactory.SIGNATURE; break; default: //assume annotation if (formType == ANNOTATION) { widgetType = FormFactory.ANNOTATION; break; } return widgetType; } private int createButtonField(final boolean[] flags) { final int widgetType; //flags used for button types //20100212 (ms) Unused ones commented out boolean isPushButton = false, isRadio = false; // hasNoToggleToOff = false, radioinUnison = false; if (flags != null) { isPushButton = flags[FormObject.PUSHBUTTON_ID]; isRadio = flags[FormObject.RADIO_ID]; //hasNoToggleToOff = flags[FormObject.NOTOGGLETOOFF_ID]; //radioinUnison = flags[FormObject.RADIOINUNISON_ID]; } if (isPushButton) { widgetType = FormFactory.PUSHBUTTON; } else if (isRadio) { widgetType = FormFactory.RADIOBUTTON; } else { widgetType = FormFactory.CHECKBOXBUTTON; } return widgetType; } /** * If possible we recommend you work with the Form Objects rather than * resolve GUI components *

* null key will return all values *

* if pageNumber is -1 it will process whole document, otherwise just that * page *

* For a full example of usage please see http://files.idrsolutions.com/samplecode/org/jpedal/examples/acroform/ExtractFormDataAsObject.java.html *

* Object[] will vary depending on what ReturnValues enum is passed in and could contain String (Names), FormObject or Component */ public Object[] getFormComponents(final String objectName, final ReturnValues value, final int pageNumber) { switch (value) { case EMBEDDED_FILES: if (currentPdfFile.getNamesLookup() == null) { return new Object[]{}; } return currentPdfFile.getNamesLookup().getEmbeddedFiles(); default: /*make sure all forms decoded*/ if (pageNumber == -1) { for (int p = 1; p < this.pageCount + 1; p++) //add init method and move scaling/rotation to it { createDisplayComponentsForPage(p, null); } } else { createDisplayComponentsForPage(pageNumber, null); } return compData.getFormComponents(objectName, value, pageNumber).toArray(); } } /** * setup object which creates all GUI objects */ public void setFormFactory(final FormFactory newFormFactory) { formFactory = newFormFactory; /* * allow user to create custom structure to hold data */ compData = formFactory.getCustomCompData(); } /** * get GUIData object with all widgets */ public GUIData getCompData() { return compData; } /** * return Signature as iterator with one or more objects or null */ public Iterator getSignatureObjects() { if (sigObject == null) { return null; } else { return sigObject.iterator(); } } public ActionHandler getActionHandler() { return formsActionHandler; } public FormFactory getFormFactory() { return formFactory; } public void setIgnoreForms(final boolean ignoreForms) { this.ignoreForms = ignoreForms; } public boolean ignoreForms() { return ignoreForms || ignoreAllForms; } public void dispose() { AfieldCount = null; fDecoder = null; formsActionHandler = null; if (javascript != null) { javascript.dispose(); } javascript = null; Fforms = null; Aforms = null; fieldList = null; annotList = null; formFactory = null; if (compData != null) { compData.dispose(); } compData = null; if (sigObject != null) { sigObject.clear(); } sigObject = null; if (sigKeys != null) { sigKeys.clear(); } sigKeys = null; pageData = null; if (currentPdfFile != null) { currentPdfFile.dispose(); } currentPdfFile = null; fDecoder = null; formCreator = null; } /** * get Iterator with list of all Annots on page or * return null if no Annots - no longer needs * call to decodePage beforehand as checks itself * * @deprecated - getFormComponents(String objectName, ReturnValues value,int pageNumber) recommended * as much more flexible */ @Deprecated public PdfArrayIterator getAnnotsOnPage(final int page) { //check annots decoded - will just return if done createDisplayComponentsForPage(page, null); if (annotList != null && annotList.length > page && annotList[page] != null) { annotList[page].resetToStart(); return annotList[page]; } else { return null; } } /** * returns false if not XFA (or XFA in Legacy mode) * and true if XFA using XFA data * * @return */ public boolean isXFA() { return hasXFA; } public boolean useXFA() { return useXFA; } public boolean hasFormsOnPage(final int page) { final boolean hasAnnots = (annotList != null && annotList.length > page && annotList[page] != null); final boolean hasForm = (hasXFA && useXFA && fDecoder.hasXFADataSet()) || fieldList != null; return hasAnnots || hasForm; } public Object[] getFormResources() { return new Object[]{AcroRes, CO}; } public boolean formsRasterizedForDisplay() { return compData.formsRasterizedForDisplay(); } /** * get FormObject * * @param ref * @return In all modes except HTML,SVG,JavaFX will decode other forms on pages if not * found */ public FormObject getFormObject(final String ref) { FormObject obj = compData.getRawFormData().get(ref); //if not found now decode all page and retry if (obj == null && formFactory.getType() != FormFactory.HTML && formFactory.getType() != FormFactory.SVG) { for (int ii = 1; ii < this.pageCount; ii++) { createDisplayComponentsForPage(ii, null); obj = compData.getRawFormData().get(ref); if (obj != null) { break; } } } return obj; } public void setInsets(final int width, final int height) { compData.setPageData(compData.pageData, width, height); } FormObject convertRefToFormObject(final String objRef, final int page) { FormObject formObject = compData.getRawFormData().get(objRef); if (formObject == null) { formObject = new FormObject(objRef); if (page != -1) { formObject.setPageRotation(pageData.getRotation(page)); } //formObject.setPDFRef((String)objRef); if (objRef.charAt(objRef.length() - 1) == 'R') { currentPdfFile.readObject(formObject); } else { //changed by Mark as cover <<>> as well as 1 0 R formObject.setStatus(PdfObject.UNDECODED_REF); formObject.setUnresolvedData(StringUtils.toBytes(objRef), -1); currentPdfFile.checkResolved(formObject); } compData.storeRawData(formObject); } return formObject; } /** * Allow user to get ENUM to show type of form * FormTypes (XFA_LEGACY, XFA_DYNAMIC, NON_XFA) * * @return */ public Enum getPDFformType() { return PDFformType; } public void alwaysuseXFA(final boolean alwaysUseXFA) { this.alwaysUseXFA = alwaysUseXFA; } public boolean alwaysuseXFA() { return alwaysUseXFA; } public void init(final SwingFormCreator formCreator) { this.formCreator = formCreator; compData = formCreator.getData(); } public PdfStreamDecoder getStreamDecoder(final PdfObjectReader currentPdfFile, final PdfLayerList layer, final boolean isFirst) { if (isFirst) { return new PdfStreamDecoder(currentPdfFile); } else { return new PdfStreamDecoder(currentPdfFile, layer); } } public boolean showFormWarningMessage(final int page) { boolean warnOnceOnForms = false; if (hasXFA) { warnOnceOnForms = true; System.out.println("[WARNING] This file contains XFA forms that are not supported by this version of JPDF2HTML5. To convert into functional HTML forms and display non-legacy mode page content, JPDF2HTML5 Forms Edition must be used."); } else if (hasFormsOnPage(page)) { warnOnceOnForms = true; System.out.println("[WARNING] This file contains form components that have been rasterized. To convert into functional HTML forms, JPDF2HTML5 Forms Edition must be used."); } return warnOnceOnForms; } FormObject[] createXFADisplayComponentsForPage(final FormObject[] xfaFormList, final int page) { throw new UnsupportedOperationException("createXFADisplayComponentsForPage should never be called"); } public HashMap getPageMapXFA() { throw new UnsupportedOperationException("getPageMapXFA should never be called"); } public byte[] getXMLContentAsBytes(final int dataType) { return null; } public void outputJavascriptXFA(final String path, final String name) { throw new UnsupportedOperationException("outputJavascriptXFA should never be called"); } public PrintStreamDecoder getStreamDecoderForPrinting(final PdfObjectReader currentPdfFile, final PdfLayerList pdfLayerList) { return new PdfStreamDecoderForPrinting(currentPdfFile, pdfLayerList); } public BufferedImage decode(final PdfObject pdfObject, final PdfObjectReader currentPdfFile, final PdfObject XObject, final int subtype, final int width, final int height, final int offsetImage, final float pageScaling) { LogWriter.writeLog("called decode(" + pdfObject + ", " + currentPdfFile + ", " + XObject + ", " + subtype + ", " + width + ", " + height + ", " + offsetImage + ", " + pageScaling); return null; } public FormFlattener getFormFlattener() { return new FormFlattener(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy