Package org.jpedal.render.output

Source Code of org.jpedal.render.output.OutputDisplay

/**
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info:  http://www.jpedal.org
*
* (C) Copyright 2007, IDRsolutions and Contributors.
*
*   This file is part of JPedal
*
     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


  *
  * ---------------

  * OutputDisplay.java
  * ---------------
  * (C) Copyright 2007, by IDRsolutions and Contributors.
  *
  *
  * --------------------------
*/
package org.jpedal.render.output;

import org.jpedal.color.ColorSpaces;
import org.jpedal.fonts.PdfFont;
import org.jpedal.fonts.StandardFonts;
import org.jpedal.fonts.glyph.PdfGlyph;
import org.jpedal.io.ObjectStore;
import org.jpedal.objects.GraphicsState;
import org.jpedal.objects.PdfPageData;
import org.jpedal.objects.TextState;
import org.jpedal.parser.Cmd;
import org.jpedal.render.BaseDisplay;
import org.jpedal.render.DynamicVectorRenderer;
import org.jpedal.utils.Matrix;
import org.jpedal.utils.repositories.Vector_Rectangle;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class OutputDisplay extends BaseDisplay{

    protected int[] pageoffsets;

    //offset in single page mode
    protected int newTotalHeight = 0;

    protected int pageGap=50;
   
    String lastGlyf="";

    final public static int TEXT_AS_TEXT =-1;
    final public static int TEXT_ON_CANVAS =1;

    protected String clip=null;

    //provides common static functions
    static protected org.jpedal.render.output.OutputHelper Helper=null;

    FontMapper fontMapper=null;
    String lastFontUsed="";

    protected boolean includeClip=false;

    protected int rasterizeText=-1;

    protected Map embeddedFontsByFontID=new HashMap();

    /**track if JS for glyf already inserted*/
    private Map glyfsRasterized=new HashMap();

    final public static int MaxNumberOfDecimalPlaces=0;
    final public static int FontMode=1;
    final public static int PercentageScaling=2;
    //final public static int SpacingPercentage=3;
    final public static int IncludeJSFontResizingCode=4;

    final public static int ExcludeMetadata=6;
    final public static int EmbedImageAsBase64Stream =7;
    final public static int AddNavBar =8;
    final public static int SingleFileOutput =9;
    final public static int UseCharSpacing =10;
    final public static int UseWordSpacing =11;
    final public static int UseFontResizing =12;
    final public static int HasJavascript =13;
    final public static int ConvertSpacesTonbsp =14;
    final public static int EncloseContentInDiv =15;
    final public static int IncludeClip =16;
    final public static int UseImagesOnNavBar =17;
    final public static int RasterizeText =18;
    final public static int CustomSinglePageHeader = 19;
    final public static int CustomSingleFileName = 20;

    //hold a default value set via JVM flag for font mapping
    protected static int defaultMode= FontMapper.DEFAULT_ON_UNMAPPED;

    protected int fontMode=FontMapper.DEFAULT_ON_UNMAPPED;

    //only output to 1 page
    protected boolean isSingleFileOutput =false;

    //give user option to embed image inside HTML5
    protected boolean embedImageAsBase64=false;

    private boolean groupGlyphsInTJ=true;

    //allow us to position every glyf on its own and not try to merge into strings*/
    protected boolean writeEveryGlyf=false;

    //control whether CSS inlined in HTML file or in own css file
    public boolean inlineCSS=true;

    /** debug flags */
    static final public boolean debugForms = false;
    final private static boolean DISABLE_IMAGES = false;
    final private static boolean DISABLE_SHAPE = false;
    final private static boolean DISABLE_TEXT = false;
    protected final static boolean DEBUG_TEXT_AREA = false;
    protected final static boolean DEBUG_DRAW_PAGE_BORDER = false;

    public final static int TOFILE = 0;
    public final static int TOP_SECTION = 1;
    public final static int SCRIPT = 2;
    public final static int FORM = 3;
    public final static int CSS = 4;
    public final static int IMAGE = 5;
    public final static int TEXT = 6;
    public final static int KEEP_GLYFS_SEPARATE = 7;
    public final static int SET_ENCODING_USED = 8;
    public final static int JSIMAGESPECIAL = 9;
    public final static int SAVE_EMBEDDED_FONT = 10;
    public final static int PAGEDATA = 11;
    public final static int IMAGE_CONTROLLER = 12;
    public final static int JAVAFXMLMETHODS = 13;
    public final static int FONT_AS_SHAPE = 14;

    protected OutputImageController imageController=null;

    protected BufferedWriter output =null;

    protected StringBuffer script = new StringBuffer(10000);
    protected StringBuffer fonts_as_shapes = new StringBuffer(10000);
    protected StringBuffer form = new StringBuffer(10000);
    protected StringBuffer testDivs = new StringBuffer(10000);
    protected StringBuffer images = new StringBuffer(10000);
    protected StringBuffer css = new StringBuffer(10000);
    protected StringBuffer topSection = new StringBuffer(10000);
    protected StringBuffer javaFXMLMethods = new StringBuffer(10000);

    /**allow user to control scaling of images*/
    protected boolean userControlledImageScaling=false;

    /**current text element number if using Divs. Used as link to CSS*/
    protected int textID=1;
    protected int shadeId = 0;

    /**number of decimal places on numbers*/
    protected int dpCount=0;

    public String rootDir=null, fileName=null;

    protected int dx;
    protected int dy;
    protected int dr;

    protected boolean excludeMetadata=false;

    private boolean convertSpacesTonbsp=false;

    /**include a user nav tollbar in output*/
    protected boolean addNavBar=false,useImagesOnNavBar=false;

    /** Controls blocks of text so they can be joined up*/
    protected TextBlock currentTextBlock;
    protected TextBlock previousTextBlock;

    protected Rectangle cropBox;
    protected Point2D midPoint;

    /** Used to reduce canvas color javascript calls between text and shapes*/
    protected int currentColor = 0;

    /**control for encodings Java/CSS*/
    protected String[] encodingType=new String[]{"UTF-8","utf-8"};
    protected static final int JAVA_TYPE=0;
    protected static final int OUTPUT_TYPE =1;
    public static final int FORM_TAG = 0;

    protected float scaling = 1.0f; //Scale to large and images may be lost due to lack of memory.

    //amount of font change needed
    protected int fontChangeNeeded=-1;

    //protected float spacingNeeded=1.0f; //amount of space needed

    float w;
    protected float h; //override int super.w and super.h for better accuracy.  @Shit

    protected String[] tag={"<form>"};

    //flag to say if JS has been added to allow images to work for checkboxes and radio buttons.
    protected boolean jsImagesAdded = false;

    protected String pageNumberAsString=null;
    protected PdfPageData pageData;
    private int currentTokenNumber=-1,lastTokenNumber=496; //496 is impossible value
    protected boolean includeJSFontResizingCode =true;

    protected static final boolean debugSigg = false;
    protected int fontSize;
    protected float[][] Trm;
    protected float[][] ctm;
    protected String imageName,id;
    protected int iw,ih;
    protected double[] coords;   
   
    //Change header name in single page output
    protected String customSinglePageHeaderName = System.getProperty("org.jpedal.pdf2html.customSinglePageHeader");
 
    //Change file name in single page output
    protected String customSingleFileOutputName = System.getProperty("org.jpedal.pdf2html.customSingleFileName");
   
   
    //used to eliminate duplicate glyfs
    protected float[][] lastTrm;

    public OutputDisplay(int pageNumber, Point2D midPoint, Rectangle cropBox, boolean addBackground, int defaultSize, ObjectStore newObjectRef) {

        super();

        //setup FontMode with any value passed in via JVM or default
        fontMode=defaultMode;

        type = DynamicVectorRenderer.CREATE_HTML;

        this.pageNumber = pageNumber;
        this.objectStoreRef = newObjectRef;
        this.addBackground = addBackground;
        this.cropBox = cropBox;
        this.midPoint = midPoint;

        //setupArrays(defaultSize);
        areas = new Vector_Rectangle(defaultSize);

    }

    //allow user to control various values
    public void setValue(int key,int value){

        switch(key){
            case MaxNumberOfDecimalPlaces:
                this.dpCount=value;
                break;


            case FontMode:
                //<start-std>
                /**
                 //<end-std>
                 if(value==org.jpedal.examples.html.HTMLFontMapper.EMBED_ALL || value==org.jpedal.examples.html.HTMLFontMapper.EMBED_ALL_EXCEPT_BASE_FAMILIES){
                 break;
                 }
                 /**/

                this.fontMode=value;
                break;

            case PercentageScaling:
                this.scaling=value/100f;

                //adjust for single page
                newTotalHeight = (int)(((cropBox.height*scaling) + pageGap) * (pageNumber-1)) ; //How far down the page the content of next page starts in single file output
     
                break;

            case RasterizeText:
                this.rasterizeText=value;
                break;

            case UseFontResizing:
                this.fontChangeNeeded=value;
                break;

           //case SpacingPercentage:
            //    this.spacingNeeded=value/100f;
            //    break;

        }
    }

    //allow user to control various values
    public void setBooleanValue(int key,boolean value){

        switch(key){


            case AddNavBar:
                this.addNavBar=value;
                break;

            case ConvertSpacesTonbsp:
                convertSpacesTonbsp=value;
                break;

            case ExcludeMetadata:
                excludeMetadata=value;
                break;

            case IncludeClip:
                includeClip=value;
                break;

            case UseImagesOnNavBar:
                this.useImagesOnNavBar=value;
                break;
        }
    }

    //allow user to control various values
    public int getValue(int key){

        int value=-1;

        switch(key){

            case FontMode:
                value=fontMode;
                break;

            case RasterizeText:
                value=rasterizeText;
                break;

        }

        return value;
    }

    /**
     * allow user to set own value for certain tags
     * Throws RuntimeException
     * @param type
     * @param value
     */
    public void setTag(int type,String value){
     

//    switch (type) {
//
//    case FORM_TAG:
//      tag[FORM_TAG]=value;
//      break;
//
//      default:
        throw new RuntimeException("Unknown tag value "+type);
//    }
    }

    /*setup renderer*/
    public void init(int width, int height, int rawRotation, Color backgroundColor) {

        if(rawRotation==90 || rawRotation==270){
            h = width * scaling;
            w = height * scaling;
        }else{
            w = width * scaling;
            h = height * scaling;
        }

        this.pageRotation = rawRotation;
        this.backgroundColor = backgroundColor;
        shadeId = 0;

        currentTextBlock = new TextBlock();
        previousTextBlock = new TextBlock();

    }

    /**
     * Add output to correct area so we can assemble later.
     * Can also be used for any specific code features (ie setting a value)
     */
    public synchronized void writeCustom(int section, Object str) {

        switch(section){

        case PAGEDATA:

            this.pageData=(PdfPageData)str;

            //any offset
            dx=pageData.getCropBoxX(pageNumber);
            dy=pageData.getCropBoxY(pageNumber);
            dr=pageData.getRotation(pageNumber);


           
            if(isSingleFileOutput && pageData.getPageCount() != 1){
                int pageCount=pageData.getPageCount()+1;

                //work out cumulative page heights so far for offset (ie not this page but all previous)
                pageoffsets=new int[pageCount];
                for(int ii=2;ii<pageCount;ii++){ //so if page 4 we get page 1,2,3
                  pageoffsets[ii]=(int) (pageoffsets[ii-1]+(pageData.getCropBoxHeight(pageNumber)*scaling)+pageGap);
                }
            }

            break;

        case IMAGE_CONTROLLER:

            this.imageController=(OutputImageController)str;
            this.userControlledImageScaling=imageController!=null;
            break;


        default:
            throw new RuntimeException("Option "+section+" not recognised");
        }

    }

    public synchronized void flagDecodingFinished(){

        if(output !=null){
            completeOutput();
        }
    }

    // save image in array to draw
    public int drawImage(int pageNumber, BufferedImage image, GraphicsState gs, boolean alreadyCached, String name, int optionsApplied, int previousUse) {

        //flush any cached text before we write out
        flushText();


        //show if image is upside down
        boolean needsflipping=false;

        //figure out co-ords
        float x = (gs.CTM[2][0] * scaling);
        float y = (gs.CTM[2][1] * scaling);
        iw = (int) (gs.CTM[0][0] * scaling);
       

        //value can also be set this way but we need to adjust the x coordinate
        if(iw==0){
            iw= (int) (gs.CTM[1][0] * scaling);

            if(iw<0)
                iw=-iw;
        }
       
        ih = (int) (gs.CTM[1][1] * scaling);
       
        //again value can be set this way
        if(ih==0){
            ih= (int) (gs.CTM[0][1] * scaling);
        }
       
      //Reset with to positive if negative
        if(iw<0)
          iw = iw*-1;

        if(iw < 1)
            iw = 1;

        if(ih == 0) {
            ih = 1;
        }

        //Account for negative widths (ie ficha_acceso_a_ofimatica_e-portafirma.pdf)
        if(ih < 1) {
            y += ih;
            ih = Math.abs(ih);
        }

        //add negative width value to of the x coord problem_document2.pdf page 3
        if(gs.CTM[0][0] < 0){
          x=x-iw;
        }
       
        //adjust in this case so in correct place
        if(gs.CTM[0][0]==0 && gs.CTM[1][1]==0 && gs.CTM[0][1]>0 && gs.CTM[1][0]<0){
            x=x-iw;
            needsflipping=true;
        }

        //factor in non-zero offset for cropBox
        //for the moment lets limit it to just this case of page cropping/4.pdf
        if(gs.CTM[0][0]==0 && gs.CTM[1][1]==0 && gs.CTM[0][1]>0 && gs.CTM[1][0]>0){
            x=x-(pageData.getCropBoxX(pageNumber)/2);
            y=y-pageData.getCropBoxY(pageNumber);
        }

        Graphics2D g2savedImage=null;
        //scale image
        BufferedImage savedImage=image;
        Image img=null;

        if(!userControlledImageScaling){ //default scaling option

            //we use image type if no clip but use transparent if clipped
            int imageMode=image.getType();

            //see if clip and apply if needed
            Rectangle clip=null;
            Area clipShape = gs.getClippingShape();
            if(clipShape!=null) {

                //scale if needed
                if(scaling!=1)
                    clipShape.transform(AffineTransform.getScaleInstance(scaling, scaling));

                clip=clipShape.getBounds();

            }
            boolean applyClip=false;

            if(clip!=null && (clip.x>x || clip.y>y || clip.getMaxX()<(x+iw) || clip.getMaxY()<(y+ih))){
                applyClip=true;
                imageMode=BufferedImage.TYPE_INT_ARGB;
            }

            img = image.getScaledInstance(iw, ih, BufferedImage.SCALE_SMOOTH);
            savedImage = new BufferedImage(iw, ih, imageMode);
            g2savedImage = (Graphics2D) savedImage.getGraphics();

            if(applyClip){ //add the clip onto image

                AffineTransform before=g2savedImage.getTransform();

                //apply transforms to invert
                //we built the clip in Java2d (which has origin top left, and image in PDF coords)
                //so we need to flip it.
                AffineTransform invert=new AffineTransform();
                invert.scale(1, -1);
                invert.translate(0, -ih);

                g2savedImage.transform(invert);
                g2savedImage.transform(AffineTransform.getTranslateInstance(-x, -y));

                //set the actual clip
                g2savedImage.setClip(gs.getClippingShape());

                //restore
                g2savedImage.setTransform(before);

            }

            //add transform to flip if needed
            if(needsflipping){
                AffineTransform aff=new AffineTransform();
                aff.scale(scaling, -scaling);
                aff.translate(0, -ih);
                g2savedImage.setTransform(aff);
            }
        }

        // Covert PDF coords (factor out scaling in calc)
        coords =new double[]{x/scaling,y/scaling};

        correctCoords(coords);

        //add back in scaling
        coords[0]=coords[0]*scaling;
        coords[1]=coords[1]*scaling;

        //subtract image height as y co-ordinate inverted
        coords[1] -= ih;

        //we need to factor in scaling as cropBox not scaled and co-ords are!
        Rectangle rect = new Rectangle((int) (coords[0]/scaling),(int)(coords[1]/scaling),(int)(iw/scaling),(int)(ih/scaling));

        if(cropBox.intersects(rect)) {

            if(!userControlledImageScaling){ //default values

                g2savedImage.drawImage(img,0, 0, null);

                /**
                 * rotate if needed
                 * (done separately at moment to keep it simple and separate)
                 */
                if(pageRotation==90 || pageRotation==270){
                    savedImage = rotateImage(savedImage);
                }
            }

            /**
             * store image
             */
            image=savedImage; //assign so we will use if passed out

            id=name+'_'+pageNumber;

            if(!embedImageAsBase64){ //usual operation

                //make sure image Dir exists
              String imageDir;
              if(type == DynamicVectorRenderer.CREATE_JAVAFX)
                imageDir= "/img/pix/" + pageNumberAsString+ '/';
              else
                imageDir= fileName+"/img/" + pageNumberAsString+ '/';
               
                File imgDir=new File(rootDir +imageDir);
                if(!imgDir.exists())
                    imgDir.mkdirs();

                imageName= imageDir+name+".png";

                try{
                    ImageIO.write(savedImage, "PNG", new File(rootDir + imageName));
                }catch(Exception e){
                    e.printStackTrace();
                }
            }

            return -2;
        }else
            return -1;

    }

    protected BufferedImage rotateImage(BufferedImage savedImage) {

        int w = savedImage.getWidth();
        int h = savedImage.getHeight();
        BufferedImage bi = new BufferedImage(h,w,savedImage.getType());
        Graphics2D g2 = bi.createGraphics();

        g2.rotate(Math.toRadians(pageRotation), w/2, h/2);

        int diff=(w-h)/2;
        g2.drawImage(savedImage,diff,diff,null);
        savedImage=bi;

        return savedImage;
    }

    /**
     * trim if needed
     * @param i
     * @return
     * * (note duplicate in OutputShape)
     */
    protected String setPrecision(double i) {


        String value=String.valueOf(i);

        int ptr=value.indexOf(".");
        int len=value.length();
        int decimals=len-ptr-1;

        if(ptr>0 && decimals> this.dpCount){
            if(dpCount==0)
                value=value.substring(0,ptr+dpCount);
            else
                value=value.substring(0,ptr+dpCount+1);

        }

        return value;
    }

    /**
     * trim if needed
     * @param i
     * @return
     * * (note duplicate in OutputShape)
     */
    protected static String setPrecision(double i, int dpCount) {


        String value=String.valueOf(i);

        int ptr=value.indexOf(".");
        int len=value.length();
        int decimals=len-ptr-1;

        if(ptr>0 && decimals> dpCount){
            if(dpCount==0)
                value=value.substring(0,ptr+dpCount);
            else
                value=value.substring(0,ptr+dpCount+1);

        }

        return value;
    }

    /*save clip in array to draw*/
    public void drawClip(GraphicsState currentGraphicsState, Shape defaultClip, boolean canBeCached) {
        //RenderUtils.renderClip(currentGraphicsState.getClippingShape(), null, defaultClip, g2);
    }

    //For debugging text
//  static int textShown = 0;
//  static int amount = 7;

    public void drawEmbeddedText(float[][] Trm, int fontSize, PdfGlyph embeddedGlyph,
                                 Object javaGlyph, int type, GraphicsState gs,
                                 AffineTransform at, String glyf, PdfFont currentFontData, float glyfWidth){

        //90 broken general/siggietest broken and cropping/02-07.pdf
        /**
         * factor in page rotation to Trm (not on canvas)
         */
        if(rasterizeText!= TEXT_ON_CANVAS){
            switch(this.pageRotation){

                case 90:

                {

                    float x=Trm[2][0],y=Trm[2][1];
                    Trm=Matrix.multiply(Trm,new float[][]{{0,-1,0},{1,0,0},{0,0,1}});
                    Trm[2][0]=x;
                    Trm[2][1]=y;

                    //alter trm to include
                    //System.out.println("after="+(int)Trm[0][0]+" "+(int)Trm[1][0]+" "+(int)Trm[0][1]+" "+(int)Trm[1][1]+" "+Trm[2][0]+" "+Trm[2][1]);
                }

                break;

                case 180:

                {

                    float x=Trm[2][0],y=Trm[2][1];
                    Trm=Matrix.multiply(Trm,new float[][]{{1,0,0},{0,-1,0},{0,0,1}});
                    Trm[2][0]=x;
                    Trm[2][1]=y;

                    //alter trm to include
                    //System.out.println("after="+(int)Trm[0][0]+" "+(int)Trm[1][0]+" "+(int)Trm[0][1]+" "+(int)Trm[1][1]+" "+Trm[2][0]+" "+Trm[2][1]);
                }

                break;

                case 270:

                {

                    float x=Trm[2][0],y=Trm[2][1];
                    Trm=Matrix.multiply(Trm,new float[][]{{0,1,0},{-1,0,0},{0,0,1}});
                    Trm[2][0]=(pageData.getMediaBoxHeight(pageNumber)+(pageData.getCropBoxHeight(pageNumber)-pageData.getCropBoxY(pageNumber)))-y;
                    Trm[2][1]=x+pageData.getCropBoxX(pageNumber);

                    //alter trm to include
                    //System.out.println("after="+(int)Trm[0][0]+" "+(int)Trm[1][0]+" "+(int)Trm[0][1]+" "+(int)Trm[1][1]+" "+Trm[2][0]+" "+Trm[2][1]);
                    break;
                }
            }
        }

        //if -100 we get value - allows user to override
        if(glyfWidth==-100){
            glyfWidth=currentFontData.getWidth(-1);
        }

        /**
        * optimisations to choose best fontsize for different cases of FontSize, Tc and Tw
        */
        int altFontSize=-1,fontCondition=-1;
        TextState currentTextState=gs.getTextState();
        if(currentTextState!=null){
            float rawFontSize=Trm[0][0];
            //if(Math.abs(rawFontSize)<1)
              //  rawFontSize=(int)Trm[0][1];

            float tc=currentTextState.getCharacterSpacing();
            float tw=currentTextState.getWordSpacing();

            //do not use rounded up fontSize - we generate value again
            float diff=(rawFontSize-(int)rawFontSize);

           // System.out.println(fontSize+" "+rawFontSize+" "+Trm[0][0]+" "+(rawFontSize-(int)rawFontSize)+" "+tc+" "+tw+" "+glyf);

            if(rawFontSize>1){
                //case 1 font sixe 8.5 or above and negative Tc
                if(fontSize==&& diff==0.5f && tc>-0.2 && tw==0 && fontSize!=(int)rawFontSize){
                    altFontSize=(int)rawFontSize;

                    //identify type
                    fontCondition=1;

            //       System.out.println(fontSize+" "+rawFontSize+" "+Trm[0][0]+" "+(rawFontSize-(int)rawFontSize)+" "+tc+" "+tw);


                }else if(fontSize==&& rawFontSize>8 && tc>0 && diff<0.3f && tw==0){
                    altFontSize=fontSize+1;

                    //System.out.println("XX "+fontSize+" "+rawFontSize+" "+Trm[0][0]+" "+(rawFontSize-(int)rawFontSize)+" "+tc+" "+tw);

                    fontCondition=2;
                }
            }

        }
        //ignore blocks of multiple spaces
        if(currentTokenNumber==lastTokenNumber && glyf.equals(" ") && lastGlyf.equals(" ")){
            flushText();
            return;

        }
        //trap any non-standard glyfs
        if(OutputDisplay.Helper!=null && glyf.length()>3 && !StandardFonts.isValidGlyphName(glyf)){
            glyf= OutputDisplay.Helper.mapNonstandardGlyfName(glyf,currentFontData);
        }
        //if(!isObjectVisible(new Rectangle((int)Trm[2][0], (int)Trm[2][1],fontSize,fontSize),gs.getClippingShape()))
        //    return;

//    if(textShown == amount)
//      return;
//
//    textShown++;


        //save raw value as we need this for test later
        this.Trm = Trm;
        this.ctm=gs.CTM;

        //code assumes Trm is square - if not alter font size
        if(Trm[0][0]!=Trm[1][1] && Trm[1][0]==0 && Trm[0][1]==0){
            fontSize= (int) Trm[0][0];
        }

        this.fontSize = fontSize;

        //if its not visible, ignore it
        Area clip=gs.getClippingShape();
        if(clip!=null && !clip.getBounds().contains(new Point((int)Trm[2][0]+1,(int) Trm[2][1]+1)) && !clip.getBounds().contains(new Point((int)Trm[2][0]+fontSize/2,(int) Trm[2][1]+fontSize/2)))
        return;

        //<start-std>
        if(rasterizeText!= TEXT_AS_TEXT){ //option to convert to shapes
            if(glyf.length()>0)
                rasterizeTextAsShape(Trm, fontSize, embeddedGlyph, gs, currentFontData, glyf, at);
            return;
        }
        //<end-std>


        //Ignore empty or crappy characters
        if(glyf.length()==0 || TextBlock.ignoreGlyf(glyf)) {
            return;
        }

        //code assumes Trm is square - if not alter font size
        if(Trm[0][0]!=Trm[1][1] && Trm[1][0]==0 && Trm[0][1]==0){
            fontSize= (int) Trm[0][0];
        }

        //text size sometimes negative as a flag so ensure always positive
        if(fontSize<0)
            fontSize=-fontSize;

        float charWidth = fontSize * glyfWidth;
       
        //get font name  and convert if needed for output and changed
        if(!currentFontData.getBaseFontName().equals(lastFontUsed)){
            fontMapper= getFontMapper(currentFontData);
            lastFontUsed=currentFontData.getBaseFontName();

            //save font id so we can see how many fonts mapped onto this name
            String value= (String) embeddedFontsByFontID.get(lastFontUsed);
            if(value==null)
                embeddedFontsByFontID.put(lastFontUsed,"browser");
            else if(!value.contains("browser"))
                embeddedFontsByFontID.put(lastFontUsed,value+ ',' +"browser");
        }

        int textFillType = gs.getTextRenderType();
        int color = textFillType == GraphicsState.STROKE ? gs.getStrokeColor().getRGB(): gs.getNonstrokeColor().getRGB();

        double[] coords = {Trm[2][0], Trm[2][1]};
        correctCoords(coords);

        //reject duplicate text used to create text bold by creating slighlty offset second character
        if(lastTrm!=null  &&
                Trm[0][0]==lastTrm[0][0] && Trm[0][1]==lastTrm[0][1] && Trm[1][0]==lastTrm[1][0] && Trm[1][1]==lastTrm[1][1]
                && glyf.equals(lastGlyf)){

            //work out absolute diffs
            float xDiff= Math.abs(Trm[2][0]-lastTrm[2][0]);
            float yDiff= Math.abs(Trm[2][1]-lastTrm[2][1]);

            //if does not look like new char, ignore
            float fontDiffAllowed=1;
            if(xDiff<fontDiffAllowed && yDiff<fontDiffAllowed){
                return;
            }

        }

        float x=(float) coords[0];
        float y=(float) coords[1];
        float rotX=x;
        float rotY=y;

        //needed for this case to ensure text runs across page
        if(this.pageRotation==90){
            rotX=Trm[2][1];
            rotY=Trm[2][0];
        }


        //Append new glyf to text block if we can otherwise flush it
        if(writeEveryGlyf || !currentTextBlock.isSameFont(fontSize, fontMapper, Trm, color)
            || !currentTextBlock.appendText(glyf, charWidth, rotX, rotY , groupGlyphsInTJ, (!groupGlyphsInTJ || currentTokenNumber!=lastTokenNumber),x,y)) {

          flushText();

          //Set up new block, if it just a space disregard it.
          if(!glyf.equals(" ")) {
            float spaceWidth = fontSize * currentFontData.getCurrentFontSpaceWidth();

            currentTextBlock = new TextBlock(glyf, fontSize, fontMapper, Trm, x, y,
                charWidth, color, spaceWidth, cropBox, Trm, ctm, altFontSize,fontCondition,rotX,rotY);

            if(convertSpacesTonbsp)
              currentTextBlock.convertSpacesTonbsp(true);

            if(currentTextBlock.getRotationAngle() == 0) {
              currentTextBlock.adjustY(-fontSize);
            }
          }else {
            currentTextBlock = new TextBlock();
          }
        }

        //update incase changed
        lastTokenNumber=currentTokenNumber;
        lastGlyf=glyf;
        lastTrm=Trm;

    }

    //<start-std>
    private void rasterizeTextAsShape(float[][] Trm, int fontSize, PdfGlyph embeddedGlyph, GraphicsState gs, PdfFont currentFontData, String glyf, AffineTransform at) {

        /**
         * convert text to shape and draw shape instead
         * Generally text at 1000x1000 matrix so we scale down by 1000 and then up by fontsize
         */
        if(embeddedGlyph!=null && embeddedGlyph.getShape()!=null && !glyf.equals(" ") ){

            GraphicsState TextGs=gs;//new GraphicsState();

            //name we will store draw code under as routine
            String JSRoutineName;

            //check all chars letters or numbers and use int value if invalid
            boolean isInvalid=false;
            for(int aa=0;aa<glyf.length();aa++){
                if(!Character.isLetterOrDigit(glyf.charAt(aa))){
                    isInvalid=true;
                }

                //exit if wrong
                if(isInvalid)
                    break;
            }

            if(isInvalid)
                JSRoutineName=currentFontData.getFontID()+Integer.toHexString((int)glyf.charAt(0));
            else
                JSRoutineName=currentFontData.getFontID()+glyf;

            //ensure starts with letter
            if(!Character.isLetter(JSRoutineName.charAt(0)))
                JSRoutineName= 's' +JSRoutineName;

            //flag to show if generated
            String cacheKey= currentFontData.getBaseFontName() + '.' + JSRoutineName;

            //see if we have already decoded glyph and use that data to reduce filesize
            boolean isAlreadyDecoded=glyfsRasterized.containsKey(cacheKey);

            //get the glyph as textGlyf shape (which we already have to render)
            Area textGlyf = (Area) embeddedGlyph.getShape().clone();

            //adjust GS to work correctly
            TextGs.setClippingShape(null);
            TextGs.setFillType(gs.getTextRenderType());

            /**
             *  adjust scaling to factor in font size
             */

            float[] aff={Trm[0][0],Trm[0][1],Trm[1][0],Trm[1][1]};
            float d=1000f; //default if not scaled

            float[] BBox=currentFontData.getFontBounds();
            //System.out.println(glyf+" "+textGlyf.getBounds()+" "+BBox[0]+" "+BBox[1]+" "+BBox[2]+" "+BBox[3]+" "+fontSize);

            //allow for rescaled TT
            if(textGlyf.getBounds().height>2000){

                //fails!!
                //textGlyf.transform(AffineTransform.getScaleInstance(0.01f,0.01f));

                //in theory does the same thing and works but larger files
                d=d*100;

            }

            //apply font scaling scaling
            for(int aa=0;aa<4;aa++)
                aff[aa]=aff[aa]/d;

            //do the actual rendering
            writeCustom(SCRIPT, "pdf.save();");
            double[] coords = {Trm[2][0], Trm[2][1]};
            correctCoords(coords);

            /**
             * adjustments for Type1 fonts
             */
            double dx=0,dy=0;
            if(currentFontData.getFontType()==StandardFonts.TYPE1){
                //fixes bg_holiday
                double totY=BBox[3]-BBox[1];
                if(totY>1000)
                    dy=fontSize+(totY-1000)/100;

                //good example is general/1.pdf
                double totX=BBox[2]-BBox[0];
                if(totX>1000)
                    dx=fontSize-(fontSize*((totX-1000)/1000));

            }

            //adjust co-ords to allow for scaling
            int tx,ty;

            switch(pageRotation){

                case 90:
                    ty= (int) ((int) (coords[0]*1.33f)+dx);
                    tx=(int)(((cropBox.height-coords[1])*1.33f)-dy);
                    break;
                /**
                 180 and 270 also need an upside down
                case 180:
                    ty= (int) ((int) (coords[0]*1.33f)+dx);
                    tx=(int)(((coords[1])*1.33f)-dy);
                    break;

                case 270:
                    ty= (int) ((int) (coords[0]*1.33f)+dx);
                    tx=(int)(((coords[1])*1.33f)-dy);
                break;
                  /**/

                default:
                    tx= (int) ((int) (coords[0]*1.33f)+dx);
                    ty=(int)(((coords[1])*1.33f)-dy);

            }
            //place scaled shape of text on page
            if(Trm[0][1]==0 && Trm[1][0]==0){ //simple version, no scaling
                writeCustom(SCRIPT, "pdf.translate(" +tx + ',' +ty+");");


                writeCustom(SCRIPT, "pdf.scale("+aff[0]+" , "+aff[3]+");");
            }else{ //full monty! Takes more space so not used unless needed
                writeCustom(SCRIPT, "pdf.transform("+(aff[0])+" , "+(-aff[1])+", "+(-aff[2])+" , "+(aff[3])+", "+tx+" , "+ty+");");
            }

            //call to draw text
            writeCustom(SCRIPT, JSRoutineName+"(pdf);");
           //writeCustom(SCRIPT, JSRoutineName+"a(pdf);");
           //writeCustom(SCRIPT, JSRoutineName+"b(pdf);");

            //write out colour
            int fillType = gs.getFillType();

            if(fillType==GraphicsState.FILL || fillType==GraphicsState.FILLSTROKE) {
                writeCustom(SCRIPT, "pdf.fillStyle = '" + OutputShape.rgbToCSSColor(gs.nonstrokeColorSpace.getColor().getRGB()) + "';");
                writeCustom(SCRIPT, "pdf.fill();");
            }

            if(fillType==GraphicsState.STROKE || fillType==GraphicsState.STROKE) {
                writeCustom(SCRIPT, "pdf.strokeStyle = '" + OutputShape.rgbToCSSColor(gs.strokeColorSpace.getColor().getRGB()) + "';");
                writeCustom(SCRIPT, "pdf.stroke();");
            }

            //generate the JS ONCE for each glyf
            if(!isAlreadyDecoded){
                //marks debug code to put Trm box and bounds on screen
                //drawNonPatternedShape(new Rectangle((int)0,(int)0,(int)(aff[0]),(int)aff[3]), TextGs, Cmd.Tj,JSRoutineName+"a");
                //drawNonPatternedShape(textGlyf.getBounds(), TextGs, Cmd.Tj,JSRoutineName+"b");

                drawNonPatternedShape(textGlyf, TextGs, Cmd.Tj,JSRoutineName);

                glyfsRasterized.put(cacheKey,"x"); //flag it as now in file
            }

            writeCustom(SCRIPT, "pdf.restore();");
        }
    }
    //<end-std>

    protected FontMapper getFontMapper(PdfFont currentFontData) {
        return null;
    }

    /**
     * Write out text buffer in correct format
     */
    protected void flushText() {}

    /*save shape in array to draw*/

    public void drawShape(Shape currentShape, GraphicsState gs, int cmd) {

        //fix for missing lines on /sample_pdfs_html/general/mmv6.pdf
        if(currentShape.getBounds().height<1 && (gs.getLineWidth()*gs.CTM[0][0]>0.5f))
            currentShape=new Rectangle(currentShape.getBounds().x,currentShape.getBounds().y,currentShape.getBounds().width,1);

        this.ctm = gs.CTM;

        if(!isObjectVisible(currentShape.getBounds(),gs.getClippingShape()))
            return;

        //flush any cached text before we write out
        flushText();


        //turn pattern into an image
        if(gs.getNonstrokeColor().isPattern() || gs.nonstrokeColorSpace.getID()== ColorSpaces.Pattern) {  //complex stuff

            drawPatternedShape(currentShape, gs);

        }
        else //standard shapes

            drawNonPatternedShape(currentShape, gs, cmd,null);

        }
    }

    protected void drawNonPatternedShape(Shape currentShape, GraphicsState gs, int cmd, String name) {}

    protected void drawPatternedShape(Shape currentShape, GraphicsState gs) {}

    private boolean isObjectVisible(Rectangle bounds, Area clip) {

        boolean fixShape=true;

        if(fixShape && dx==0 && dy==0 && dr==0){
        //get any Clip (only rectangle outline)
        Rectangle clipBox=null;

            //if(dx!=0 || dy!=0) //factor in offset on crop
            //  bounds.translate(-dx, -dy);

        if(clip!=null)
            clipBox=clip.getBounds();
        else
            clipBox=null;


        //additional tests to allow for being on edge
        if(cropBox!=null && !cropBox.intersects(bounds) && Math.abs(cropBox.getMaxX()-bounds.getMaxX())>1 ||
                ( clipBox!=null && !clipBox.intersects(bounds)&& Math.abs(clipBox.getMaxX()-bounds.getMaxX())>1)){
            return false;
            }
        }

        return true;
    }

    /*add XForm object*/

    final public void drawXForm(DynamicVectorRenderer dvr, GraphicsState gs) {

        //flush any cached text before we write out
        flushText();

        //renderXForm(dvr, gs.getStrokeAlpha());
    }


    /**
     * add footer and other material to complete
     */
    protected void completeOutput() {}


    public void setOutputDir(String outputDir,String outputFilename, String pageNumberAsString) {
        rootDir=outputDir;
        fileName=outputFilename;

        this.pageNumberAsString=pageNumberAsString;
    }

    /**
     * Coverts an array of numbers to a String for JavaScript parameters.
     *
     * @param coords Numbers to change
     * @param count Use up to count doubles from coords array
     * @return String Bracketed stringified version of coords
     */
    protected String coordsToStringParam(double[] coords, int count)
    {
        String result = "";

        for(int i = 0; i<count; i++) {
            if(i!=0) {
                result += ",";
            }

            result += setPrecision(coords[i]);
        }

        return result;
    }

    /**
     * Converts coords from Pdf system to java.
     */
    protected void correctCoords(double[] coords)
    {
        coords[0] = coords[0] - midPoint.getX();
        coords[0] += cropBox.width / 2;

        coords[1] = coords[1] - midPoint.getY();
        coords[1] = 0 - coords[1];
        coords[1] += cropBox.height / 2;
    }


    /**
     * Formats an int to CSS rgb(r,g,b) string
     *
     */
    protected static String rgbToColor(int raw)
    {
        int r = (raw>>16) & 255;
        int g = (raw>>8) & 255;
        int b = raw & 255;

        return "rgb(" + r + ',' + g + ',' + b + ')';
    }


    /**
     * Draws boxes around where the text should be.
     */
    protected void drawTextArea() {}

    /**
     * Draw a debug area around border of page.
     */
    protected void drawPageBorder() {}

    /**
     * allow tracking of specific commands
     **/
    public void flagCommand(int commandID, int tokenNumber){

        switch(commandID){

            case Cmd.BT:

                //reset to will be rest for text
//      lastR=-1;
//      lastG=-1;
//      lastB=-1;
                break;

            case Cmd.Tj:
                this.currentTokenNumber=tokenNumber;
                break;
        }
    }


  protected String replaceTokenValues(String customSingleFileName, int type) {
   
    //Strip the file name from the root dir
    String nameOfPDF = rootDir.substring(0, rootDir.length()-1); //strip off last / or \
   
    //find end (which could be after \ or /)
    int pt = nameOfPDF.lastIndexOf("\\");
    int fowardSlash = nameOfPDF.lastIndexOf("/");
    if(fowardSlash>pt)
      pt=fowardSlash;
   
    //and remove path from filename
    nameOfPDF = nameOfPDF.substring(pt+1);
   
    //all tokens start $ so ignore if not present
    if(customSingleFileName!=null && customSingleFileName.contains("$")){
     
      //possible replacement values
      String fileName = nameOfPDF;
      String pageNumber = pageNumberAsString;
      String pageCount = String.valueOf(pageData.getPageCount());


      //do the replacements
      customSingleFileName=customSingleFileName.replace("$fileName$", fileName);
     
      //prevent "$currentPageNumber$" being used in filename as several files gets produced instead of one
      if(type==OutputDisplay.CustomSingleFileName && customSingleFileName.contains("$currentPageNumber$"))
        throw new RuntimeException("$currentPageNumber$ - is an in valid token for file name. Use $fileName$ to get the file name or $totalPageNumber$ to get the total amount of pages ");
     
      customSingleFileName=customSingleFileName.replace("$currentPageNumber$", pageNumber);
      customSingleFileName=customSingleFileName.replace("$totalPageNumber$", pageCount);
     
    }
   
    return customSingleFileName;
   
  }
    public boolean isScalingControlledByUser(){
        return userControlledImageScaling;
    }
}
TOP

Related Classes of org.jpedal.render.output.OutputDisplay

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.