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

org.jpedal.parser.FormFlattener 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-2015 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


 *
 * ---------------
 * FormFlattener.java
 * ---------------
 */
package org.jpedal.parser;

import org.jpedal.exception.PdfException;
import org.jpedal.objects.GraphicsState;
import org.jpedal.objects.TextState;
import org.jpedal.objects.acroforms.creation.PopupFactory;
import org.jpedal.objects.acroforms.overridingImplementations.ReadOnlyTextIcon;
import org.jpedal.objects.raw.*;
import org.jpedal.parser.image.MaskUtils;

import java.awt.*;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import org.jpedal.render.DynamicVectorRenderer;

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 {

        /**
         * 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) {
            PopupFactory.renderFlattenedAnnotation(form, pdfStreamDecoder.current, pdfStreamDecoder.parserOptions.getPageNumber(), pdfStreamDecoder.pageData.getRotation(pdfStreamDecoder.parserOptions.getPageNumber()));
            return;
        }

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

        pdfStreamDecoder.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);
        Map otherValues=new HashMap();
        if(APobjN!=null){
            otherValues=APobjN.getOtherDictionaries();
        }

        final String defaultState = form.getName(PdfDictionary.AS);
        if (defaultState != null && defaultState.equals(((FormObject)form).getNormalOnState())) {
            //use the selected appearance stream
            if(APobjN.getDictionary(PdfDictionary.On) !=null){
                imgObj = APobjN.getDictionary(PdfDictionary.On);
            }else if(APobjN.getDictionary(PdfDictionary.Off) !=null && defaultState != null && defaultState.equals("Off")){
                imgObj = APobjN.getDictionary(PdfDictionary.Off);
            }else if(otherValues!=null && defaultState != null){

                imgObj=(PdfObject)otherValues.get(defaultState);
            }else {
                if(otherValues!=null && !otherValues.isEmpty()){
                    /**final Iterator keys=otherValues.keySet().iterator();
                    final PdfObject val;
                    final String key;
                    //while(keys.hasNext()){
                    key=(String)keys.next();
                    val=(PdfObject)otherValues.get(key);
                    //System.out.println("key="+key+" "+val.getName(PdfDictionary.AS));
                    imgObj = val;
                    /**/
                    imgObj=(PdfObject) otherValues.entrySet().iterator().next();
                    //}
                }
            }
        }else {
            //use the normal appearance Stream
            if(APobjN!=null || form.getDictionary(PdfDictionary.MK).getDictionary(PdfDictionary.I) !=null){

                //if we have a root stream then it is the off value
                //check in order of N Off, MK I, then N
                //as N Off overrides others and MK I is in preference to N
                if(APobjN!=null && APobjN.getDictionary(PdfDictionary.Off) !=null){
                    imgObj = APobjN.getDictionary(PdfDictionary.Off);

                }else if(form.getDictionary(PdfDictionary.MK).getDictionary(PdfDictionary.I) !=null
                        && form.getDictionary(PdfDictionary.MK).getDictionary(PdfDictionary.IF)==null){
                    //look here for MK IF
                    //if we have an IF inside the MK then use the MK I as some files shown that this value is there
                    //only when the MK I value is not as important as the AP N.
                    imgObj = form.getDictionary(PdfDictionary.MK).getDictionary(PdfDictionary.I);

                }else if(APobjN!=null && APobjN.getDecodedStream()!=null){
                    imgObj = APobjN;
                }
            }
        }

        /**
         * 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")) {
                    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){

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

            //if(V==null)
            //    V=form.getTextStreamValue(PdfDictionary.T);

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

                final ReadOnlyTextIcon textIcon = new ReadOnlyTextIcon(form,0, pdfStreamDecoder.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) {
                    PopupFactory.renderFlattenedAnnotation(form, pdfStreamDecoder.current, pdfStreamDecoder.parserOptions.getPageNumber(), pdfStreamDecoder.pageData.getRotation(pdfStreamDecoder.parserOptions.getPageNumber()));
                }

                return;
            }
        }

        if(imgObj!=null) {
            pdfStreamDecoder.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);
            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};
        }

        if (pdfStreamDecoder.pageData.getRotation(pdfStreamDecoder.parserOptions.getPageNumber()) == 0) {
            if (BBox[1] > BBox[3]) {
                float t = BBox[1];
                BBox[1] = BBox[3];
                BBox[3] = t;
            }

            if (BBox[0] > BBox[2]) {
                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(pdfStreamDecoder.parserOptions.isFlattenedForm()){
            pdfStreamDecoder.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);
        }
        
        //we need to factor in this to calculations
        final int pageRotation= pdfStreamDecoder.pageData.getRotation(pdfStreamDecoder.parserOptions.getPageNumber());

        //System.out.println("pageRot="+pageRotation);
        //System.out.println("BBox="+BBox[0]+" "+BBox[1]+" "+BBox[2]+" "+BBox[3]);
        //System.out.println("matrix="+matrix[0]+" "+matrix[1]+" "+matrix[2]+" "+matrix[3]);
        float x = BBox[0],y = BBox[1];
        Area newClip=null;

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

            float yScale = 1;

            //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 (BoundingBox[1] > BoundingBox[3]) {
//                            float t = BoundingBox[1];
//                            BoundingBox[1] = BoundingBox[3];
//                            BoundingBox[3] = t;
//                        }
//
//                        if (BoundingBox[0] > BoundingBox[2]) {
//                            float t = BoundingBox[0];
//                            BoundingBox[0] = BoundingBox[2];
//                            BoundingBox[2] = t;
//                        }
                        //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 x to fit form size
//            				if(pageRotation!=0 && (int)bbw!=(int)imw){
//            					float xScale = bbw/imw;
//
//            					//Move form up instead of drawing top at bottom of area
//            					x-=(imw*xScale);
//            				}
                                //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],(int)BBox[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{
            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]));
        }

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

    void drawForm(PdfObject imgObj, final PdfObject form, final PdfStreamDecoder pdfStreamDecoder, Area newClip, final boolean isHTML, float[] BBox, float x, float y, 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, 0, w, h, true, 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;

                //-3 tells it to render to background image and thumbnail if present
                pdfStreamDecoder.current.drawImage(pdfStreamDecoder.parserOptions.getPageNumber(), image, pdfStreamDecoder.gs, false, form.getObjectRefAsString(), 0, -3);
            
                //add to SVG as external image if needed
                if(pdfStreamDecoder.current.getBooleanValue(DynamicVectorRenderer.IsSVGMode)){
                    pdfStreamDecoder.current.drawImage(pdfStreamDecoder.parserOptions.getPageNumber(), image, pdfStreamDecoder.gs, false, form.getObjectRefAsString(), 0, -2);            
                }
            
            }

        }else{

            /**decode the stream*/
            if(formData!=null){
                pdfStreamDecoder.BBox=BBox;

                //we can potentially have a local Resource object we need to read for GS and font values (see 18343)
                if(APobjN!=null){
                    final PdfObject res=(PdfObject) APobjN.getOtherDictionaries().get("Resources");
                    if(res!=null){
                        pdfStreamDecoder.currentPdfFile.checkResolved(res);
                        pdfStreamDecoder.readResources(res, false);

                    }
                }

                pdfStreamDecoder.decodeStreamIntoObjects(formData, false);
                pdfStreamDecoder.BBox=null;
            }

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




© 2015 - 2025 Weber Informatics LLC | Privacy Policy