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

org.jpedal.parser.FormFlattener Maven / Gradle / Ivy

There is a newer version: 7.15.25
Show newest version
/*
 * ===========================================
 * 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
 *
 @LICENSE@
 *
 * ---------------
 * FormFlattener.java
 * ---------------
 */
package org.jpedal.parser;

import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;

import org.jpedal.exception.PdfException;
import org.jpedal.io.PdfObjectReader;
import org.jpedal.objects.GraphicsState;
import org.jpedal.objects.TextState;
import org.jpedal.objects.acroforms.creation.AnnotationFactory;
import org.jpedal.objects.acroforms.overridingImplementations.ReadOnlyTextIcon;
import org.jpedal.objects.raw.FormObject;
import org.jpedal.objects.raw.FormStream;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfObject;
import org.jpedal.parser.image.mask.MaskUtils;
import org.jpedal.render.ClipUtils;

public class FormFlattener {
    /**
     * routine to decode an XForm stream
     *
     * @param pdfStreamDecoder
     * @param form
     * @param isHTML
     * @param AcroRes
     * @throws org.jpedal.exception.PdfException
     */
    public void drawFlattenedForm(final PdfStreamDecoder pdfStreamDecoder, final PdfObject form, final boolean isHTML, final PdfObject AcroRes) throws PdfException {

        //Check org.jpedal.removeForms and stop rendering if form should be removed
        if (exclusionOption != FormExclusion.ExcludeNone && !showForm(form)) {
            return;
        }

        final ParserOptions parserOptions = pdfStreamDecoder.parserOptions;
        final int pageNumber = parserOptions.getPageNumber();
        final PdfObjectReader currentPdfFile = pdfStreamDecoder.currentPdfFile;
        
        /*
         * ignore if not going to be drawn
         */
        //int type=form.getParameterConstant(PdfDictionary.Subtype);
        //if(type==PdfDictionary.Link)
        //    return;
        final int type = form.getParameterConstant(PdfDictionary.Subtype);
        if (type == PdfDictionary.Highlight) {
            AnnotationFactory.renderFlattenedAnnotation(form, pdfStreamDecoder.current, pageNumber, pdfStreamDecoder.pageData.getRotation(pageNumber));
            return;
        }

        //save and use new version so we can ignore changes
        final GraphicsState oldGS = pdfStreamDecoder.gs;
        pdfStreamDecoder.gs = new GraphicsState();

        parserOptions.setFlattenedForm(true);

        //check if this form should be displayed
        final boolean[] characteristic = ((FormObject) form).getCharacteristics();
        if (characteristic[0] || characteristic[1] || characteristic[5] ||
                (!form.getBoolean(PdfDictionary.Open) &&
                        form.getParameterConstant(PdfDictionary.Subtype) == PdfDictionary.Popup)) {
            //this form should be hidden
            return;
        }

        PdfObject imgObj = null;

        final PdfObject APobjN = form.getDictionary(PdfDictionary.AP).getDictionary(PdfDictionary.N);

        if (APobjN != null || form.getDictionary(PdfDictionary.MK).getDictionary(PdfDictionary.I) != null) {

            final String defaultState = form.getName(PdfDictionary.AS);
            final Object[] values = FormStream.getNormalKeyValues(form, pdfStreamDecoder.currentPdfFile.getObjectReader());
            if (defaultState != null && defaultState.equals(((FormObject) form).getNormalOnState())) {
                ((FormObject) form).setNormalOnState((String) values[0]);
                imgObj = (PdfObject) values[1];
            } else {
                imgObj = (PdfObject) values[2];
            }
        }
       
        /*
         * we have some examples where no text inside AP datastream so we ignore in this case
         * and use the text
         */
        if (imgObj != null) {

            final byte[] formData = imgObj.getDecodedStream(); //get from the AP

            if (formData != null) {
                final String str = new String(formData);

                //if((str.contains("BMC")&& !str.contains("BT"))) {
                if (str.isEmpty() || (str.contains("BMC") && !str.contains("BT"))) {
                    imgObj = null;
                }
            }
        }

        /*
         * alternative to draw image for icon
         */
        final byte[] DA = form.getTextStreamValueAsByte(PdfDictionary.DA);

        /*
         * if no object present try to create a fake one using Swing code
         * for readonly text icons
         */
        if (imgObj == null) {

            String V = form.getTextStreamValue(PdfDictionary.V);

            if (V == null && type == PdfDictionary.FreeText) {
                V = form.getTextStreamValue(PdfDictionary.Contents);
            }

            if (DA != null || (V != null && !V.isEmpty())) {

                final ReadOnlyTextIcon textIcon = new ReadOnlyTextIcon(form, 0, currentPdfFile, AcroRes);
                textIcon.decipherAppObject((FormObject) form);
                if (V != null) {
                    textIcon.setText(V);

                    imgObj = textIcon.getFakeObject();
                } else if (DA != null) {

                    imgObj = textIcon.getFakeObject();
                    imgObj.setDecodedStream(DA);
                }
            }

            if (imgObj == null && DA == null) {

                //if(form.getParameterConstant(PdfDictionary.Subtype)!=PdfDictionary.Link)
                //    System.out.println("missing image "+PdfDictionary.showAsConstant(form.getParameterConstant(PdfDictionary.Subtype))+" "+form.getObjectRefAsString());

                //add in Popup Icon for text Annotation
//                int type = form.getParameterConstant(PdfDictionary.Subtype);
                if (type == PdfDictionary.Text) {
                    AnnotationFactory.renderFlattenedAnnotation(form, pdfStreamDecoder.current, pageNumber, pdfStreamDecoder.pageData.getRotation(pageNumber));
                }

                return;
            }
        }

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

        byte[] formData = null; //get from the Fake obj
        if (imgObj != null) {
            formData = imgObj.getDecodedStream(); //get from the DA
        }

        //debug code for mark, for the flattern case 10295
//        System.out.println("ref="+form.getObjectRefAsString()+" stream="+new String(formData));

        //might be needed to pick up fonts
        if (imgObj != null) {
            final PdfObject resources = imgObj.getDictionary(PdfDictionary.Resources);

            if (resources != null) {
                pdfStreamDecoder.readResources(resources, false);
            }
        }

        /*
         * see if bounding box and set
         */
        float[] BBox = form.getFloatArray(PdfDictionary.Rect);

        if (imgObj != null && imgObj.getObjectType() == PdfDictionary.XFA_APPEARANCE) {
            final Rectangle rect = ((FormObject) form).getBoundingRectangle();
            if (rect != null) {
                BBox = new float[]{rect.x, rect.y, rect.width, rect.height};
            }
        }

        if (BBox == null) {
            BBox = new float[]{0, 0, 1, 1};
        }

        //we need to factor in this to calculations
        final int pageRotation = pdfStreamDecoder.pageData.getRotation(pageNumber);

        if (pageRotation == 0) {
            if (BBox[1] > BBox[3]) {
                final float t = BBox[1];
                BBox[1] = BBox[3];
                BBox[3] = t;
            }

            if (BBox[0] > BBox[2]) {
                final float t = BBox[0];
                BBox[0] = BBox[2];
                BBox[2] = t;
            }
        }

        //if we flatten form objects with XForms, we need to use diff calculation
        if (parserOptions.isFlattenedForm()) {
            parserOptions.setOffsets(BBox[0], BBox[1]);
        }

        //please dont delete through merge this fixes most of the flatten form positionsing.
        float[] matrix = {1, 0, 0, 1, 0, 0};

        if (imgObj != null) {
            matrix = imgObj.getFloatArray(PdfDictionary.Matrix);
        }

        float x = BBox[0], y = BBox[1];
        Area newClip = null;

        //check for null and then recalculate insets
        if (matrix != null) {

            float yScale = 1;

            if (imgObj != null && pageRotation == 0 && matrix[4] > 0 && matrix[5] > 0) {

                final float[] BoundingBox = imgObj.getFloatArray(PdfDictionary.BBox);
                if (BoundingBox[1] > BoundingBox[3]) {
                    final float t = BoundingBox[1];
                    BoundingBox[1] = BoundingBox[3];
                    BoundingBox[3] = t;
                }

                if (BoundingBox[0] > BoundingBox[2]) {
                    final float t = BoundingBox[0];
                    BoundingBox[0] = BoundingBox[2];
                    BoundingBox[2] = t;
                }

                matrix[0] = (BBox[2] - BBox[0]) / (BoundingBox[2] - BoundingBox[0]);
                matrix[1] = 0;
                matrix[2] = 0;
                matrix[3] = (BBox[3] - BBox[1]) / (BoundingBox[3] - BoundingBox[1]);
                matrix[4] = (BBox[0] - BoundingBox[0]);
                matrix[5] = (BBox[1] - BoundingBox[1]);

                pdfStreamDecoder.gs.CTM = new float[][]{{matrix[0], matrix[1], 0}, {matrix[2], matrix[3], 0}, {matrix[4], matrix[5], 1}};
                newClip = new Area(new Rectangle((int) BBox[0], (int) BBox[1], (int) ((BBox[2] - BBox[0]) + 2), (int) ((BBox[3] - BBox[1]) + 2)));

                //Set variables for draw form call
                x = (matrix[4]);
                y = (matrix[5]);
            } else {

                //Check for appearnce stream
                PdfObject temp = form.getDictionary(PdfDictionary.AP);
                if (temp != null) {

                    //Check for N object
                    temp = temp.getDictionary(PdfDictionary.N);
                    if (temp != null) {

                        //Check for a bounding box of this object
                        final float[] BoundingBox = temp.getFloatArray(PdfDictionary.BBox);
                        if (BoundingBox != null) {

                            //If different from BB provided and matrix is standard than add scaling
                            if (BBox[0] != BoundingBox[0] && BBox[1] != BoundingBox[1]
                                    && BBox[2] != BoundingBox[2] && BBox[3] != BoundingBox[3]) {

                                if (//Check matrix is standard
                                        matrix[0] * matrix[3] == 1.0f
                                                && matrix[1] * matrix[2] == 0.0f) {

                                    //float bbw = BBox[2]-BBox[0];
                                    final float bbh = BBox[3] - BBox[1];
                                    //float imw = BoundingBox[2]-BoundingBox[0];
                                    final float imh = BoundingBox[3] - BoundingBox[1];

                                    //Adjust scale on the y to fit form size
                                    if ((int) bbh != (int) imh) {
                                        yScale = bbh / imh;
                                    }
                                } else {
                                    //-90 rotation
                                    if (matrix[0] * matrix[3] == 0.0f
                                            && matrix[1] * matrix[2] == -1.0f) {
                                        //float bbw = BBox[2]-BBox[0];

                                        //Handle 0 rot case here
                                        float bbh = BBox[2] - BBox[0];
                                        switch (pageRotation) {

                                            case 90:
                                                bbh = BBox[2] - BBox[0];
                                                break;

                                            case 180:
                                                break;

                                            case 270:
                                                bbh = BBox[2] - BBox[0];
                                                break;
                                        }
                                        //float imw = BoundingBox[2]-BoundingBox[0];
                                        final float imh = BoundingBox[3] - BoundingBox[1];

                                        //Adjust scale on the y to fit form size
                                        if ((int) bbh != (int) imh) {
                                            yScale = bbh / imh;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                switch (pageRotation) {
                    case 90:

                        //Allow for rotated forms, form requires lowest value
                        if (BBox[0] < BBox[2]) {
                            x = BBox[0] + (matrix[4] * yScale);
                        } else {
                            x = BBox[2] + (matrix[4] * yScale);
                        }

                        //Added code to fix ny1981.pdf and 133419-without-annotations-p2.pdf
                        if (matrix[4] < 0) {
                            x = BBox[0] + (matrix[4] * yScale);
                        }
                        //newClip=new Area(new Rectangle((int)BBox[2],(int)BBox[1],(int)BBox[0],(int)BBox[3]));
                        break;
                    default:
                        x = BBox[0] + (matrix[4] * yScale);
                        newClip = new Area(new Rectangle((int) (BBox[0] - 1), (int) (BBox[1] - 1), (int) ((BBox[2] - BBox[0]) + 2), (int) ((BBox[3] - BBox[1]) + 2)));
                        break;
                }

                y = BBox[1] + (matrix[5] * yScale);

                //set gs.CTM to form coords (probably {1,0,0}{0,1,0}{x,y,1} at a guess
                pdfStreamDecoder.gs.CTM = new float[][]{{matrix[0] * yScale, matrix[1] * yScale, 0}, {matrix[2] * yScale, matrix[3] * yScale, 0}, {x, y, 1}};
            }
        } else {
            newClip = setClipAndCTM(pdfStreamDecoder, form, imgObj, BBox, x, y);
        }

        //Convert clip here
        newClip = ClipUtils.convertPDFClipToJavaClip(newClip);

        drawForm(imgObj, form, pdfStreamDecoder, newClip, isHTML, BBox, x, y, formData, APobjN, oldGS);

    }

    Area setClipAndCTM(final PdfStreamDecoder pdfStreamDecoder, final PdfObject form, final PdfObject imgObj, final float[] BBox, final float x, final float y) {
        final Area newClip; //If using AP, is a widget and not a signature there is a chance of incorrect AP scaling.
        //Set CTM based on different between Form bounds and AP bounds
        if (imgObj != null &&
                form.getParameterConstant(PdfDictionary.Subtype) == PdfDictionary.Widget) {

            final float[] values2 = imgObj.getFloatArray(PdfDictionary.BBox);
            if (values2 != null) {
                float xScale = (BBox[2] - BBox[0]) / (values2[2] - values2[0]);
                if (xScale < 0) {
                    xScale = -xScale;
                }
                float yScale = (BBox[3] - BBox[1]) / (values2[3] - values2[1]);
                if (yScale < 0) {
                    yScale = -yScale;
                }
                pdfStreamDecoder.gs.CTM = new float[][]{{xScale, 0, 0}, {0, yScale, 0}, {x, y, 1}};
            } else {
                pdfStreamDecoder.gs.CTM = new float[][]{{1, 0, 0}, {0, 1, 0}, {x, y, 1}};
            }
            newClip = new Area(new Rectangle((int) BBox[0], (int) BBox[1], (int) BBox[2], (int) BBox[3]));
        } else {
            if (form.getParameterConstant(PdfDictionary.Subtype) == PdfDictionary.Ink) {
                pdfStreamDecoder.gs.CTM = new float[][]{{1, 0, 0}, {0, 1, 0}, {x - BBox[0], y - BBox[1], 1}};
            } else {
                pdfStreamDecoder.gs.CTM = new float[][]{{1, 0, 0}, {0, 1, 0}, {x, y, 1}};
            }
            newClip = new Area(new Rectangle((int) BBox[0], (int) BBox[1], (int) BBox[2], (int) BBox[3]));
        }
        return newClip;
    }

    void drawForm(final PdfObject imgObj, final PdfObject form, final PdfStreamDecoder pdfStreamDecoder, final Area newClip, final boolean isHTML, final float[] BBox, final float x, final float y, final byte[] formData, final PdfObject APobjN, final GraphicsState oldGS) throws PdfException {

        //set clip to match bounds on form
        if (newClip != null) {
            pdfStreamDecoder.gs.updateClip(new Area(newClip));
        }
        pdfStreamDecoder.current.drawClip(pdfStreamDecoder.gs, pdfStreamDecoder.parserOptions.defaultClip, false);
        /*
         * avoid values in main stream
         */
        final TextState oldState = pdfStreamDecoder.gs.getTextState();
        pdfStreamDecoder.gs.setTextState(new TextState());
        /*
         * write out forms as images in HTML mode - hooks into flatten forms mode
         */
        if (isHTML) {

            //create the image from the form data
            final int w = (int) (BBox[2] - BBox[0]);
            final int h = (int) (BBox[3] - BBox[1]);

            if (w > 0 && h > 0) {
                final BufferedImage image = MaskUtils.createTransparentForm(imgObj, 0, w, h, pdfStreamDecoder.currentPdfFile, pdfStreamDecoder.parserOptions, pdfStreamDecoder.formLevel, pdfStreamDecoder.multiplyer);

                //draw the image to HTML

                //4 needed as we upsample by a factor of 4
                pdfStreamDecoder.gs.CTM = new float[][]{{image.getWidth() / 4, 0, 1}, {0, image.getHeight() / 4, 1}, {0, 0, 0}};

                pdfStreamDecoder.gs.x = x;
                pdfStreamDecoder.gs.y = y;

                //draw onto image
                pdfStreamDecoder.gs.CTM[2][0] = x;
                pdfStreamDecoder.gs.CTM[2][1] = y;

                pdfStreamDecoder.current.drawImage(pdfStreamDecoder.parserOptions.getPageNumber(), image, pdfStreamDecoder.gs, false, form.getObjectRefAsString(), -2);
            }

        } else {

            if (formData != null) {
                pdfStreamDecoder.decodeStreamIntoObjects(formData, false);
            }

        }
        /*
         * we need to reset clip otherwise items drawn afterwards
         * like forms data in image or print will not appear.
         */
        pdfStreamDecoder.gs.updateClip(null);
        pdfStreamDecoder.current.drawClip(pdfStreamDecoder.gs, null, true);
        //restore
        pdfStreamDecoder.gs = oldGS;
        pdfStreamDecoder.gs.setTextState(oldState);

    }

    private enum FormExclusion {
        ExcludeNone, ExcludeForms, ExcludeAnnotations, ExcludeFormsAndAnnotations
    }

    private static FormExclusion exclusionOption = FormExclusion.ExcludeNone;

    static {
        final String value = System.getProperty("org.jpedal.removeForms");
        if (value != null && !value.isEmpty()) {
            exclusionOption = FormExclusion.valueOf(value);

        }
    }

    private static boolean showForm(final PdfObject form) {

        switch (exclusionOption) {
            case ExcludeFormsAndAnnotations:
                //Show no annotations or forms
                return false;
            case ExcludeAnnotations:
                //Show only forms
                if (form.getParameterConstant(PdfDictionary.Type) == PdfDictionary.Annot && form.getNameAsConstant(PdfDictionary.FT) == -1) {
                    return false;
                }
                break;
            case ExcludeForms:
                //Show only annotations
                if (form.getNameAsConstant(PdfDictionary.FT) != -1) {
                    return false;
                }
                break;
            case ExcludeNone:
                //Show both annotations and forms
                break;
        }

        return true;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy