Home » pdfbox-1.1.0-src » org.apache.pdfbox.pdfviewer » [javadoc | source]

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   package org.apache.pdfbox.pdfviewer;
   18   
   19   import java.awt.BasicStroke;
   20   import java.awt.Dimension;
   21   import java.awt.Graphics;
   22   import java.awt.Graphics2D;
   23   import java.awt.geom.Area;
   24   import java.awt.RenderingHints;
   25   import java.awt.geom.AffineTransform;
   26   import java.awt.geom.GeneralPath;
   27   import java.awt.geom.Point2D;
   28   import java.io.IOException;
   29   import java.util.List;
   30   import java.util.Map;
   31   
   32   import org.apache.commons.logging.Log;
   33   import org.apache.commons.logging.LogFactory;
   34   import org.apache.pdfbox.pdmodel.PDPage;
   35   import org.apache.pdfbox.pdmodel.PDResources;
   36   import org.apache.pdfbox.pdmodel.common.PDMatrix;
   37   import org.apache.pdfbox.pdmodel.common.PDRectangle;
   38   import org.apache.pdfbox.pdmodel.font.PDFont;
   39   import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
   40   import org.apache.pdfbox.pdmodel.graphics.PDShading;
   41   import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
   42   import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
   43   import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
   44   import org.apache.pdfbox.pdmodel.text.PDTextState;
   45   import org.apache.pdfbox.util.Matrix;
   46   import org.apache.pdfbox.util.PDFStreamEngine;
   47   import org.apache.pdfbox.util.ResourceLoader;
   48   import org.apache.pdfbox.util.TextPosition;
   49   import org.apache.pdfbox.cos.COSName;
   50   import org.apache.pdfbox.cos.COSBase;
   51   import org.apache.pdfbox.cos.COSObject;
   52   import org.apache.pdfbox.cos.COSDictionary;
   53   
   54   
   55   /**
   56    * This will paint a page in a PDF document to a graphics context.
   57    *
   58    * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
   59    * @version $Revision: 1.22 $
   60    */
   61   public class PageDrawer extends PDFStreamEngine
   62   {
   63   
   64       /**
   65        * Log instance.
   66        */
   67       private static final Log log = LogFactory.getLog(PageDrawer.class);
   68   
   69       private Graphics2D graphics;
   70       private Dimension pageSize;
   71       private PDPage page;
   72   
   73       private GeneralPath linePath = new GeneralPath();
   74   
   75       /**
   76        * Default constructor, loads properties from file.
   77        *
   78        * @throws IOException If there is an error loading properties from the file.
   79        */
   80       public PageDrawer() throws IOException
   81       {
   82           super( ResourceLoader.loadProperties( "Resources/PageDrawer.properties", true ) );
   83       }
   84   
   85       /**
   86        * This will draw the page to the requested context.
   87        *
   88        * @param g The graphics context to draw onto.
   89        * @param p The page to draw.
   90        * @param pageDimension The size of the page to draw.
   91        *
   92        * @throws IOException If there is an IO error while drawing the page.
   93        */
   94       public void drawPage( Graphics g, PDPage p, Dimension pageDimension ) throws IOException
   95       {
   96           graphics = (Graphics2D)g;
   97           page = p;
   98           pageSize = pageDimension;
   99           graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
  100           graphics.setRenderingHint( RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON );
  101           // Only if there is some content, we have to process it. 
  102           // Otherwise we are done here and we will produce an empty page
  103           if ( page.getContents() != null) 
  104           {
  105               PDResources resources = page.findResources();
  106               processStream( page, resources, page.getContents().getStream() );
  107           }
  108           List annotations = page.getAnnotations();
  109           for( int i=0; i<annotations.size(); i++ )
  110           {
  111               PDAnnotation annot = (PDAnnotation)annotations.get( i );
  112               PDRectangle rect = annot.getRectangle();
  113               String appearanceName = annot.getAppearanceStream();
  114               PDAppearanceDictionary appearDictionary = annot.getAppearance();
  115               if( appearDictionary != null )
  116               {
  117                   if( appearanceName == null )
  118                   {
  119                       appearanceName = "default";
  120                   }
  121                   Map appearanceMap = appearDictionary.getNormalAppearance();
  122                   PDAppearanceStream appearance =
  123                       (PDAppearanceStream)appearanceMap.get( appearanceName );
  124                   if( appearance != null )
  125                   {
  126                       g.translate( (int)rect.getLowerLeftX(), (int)-rect.getLowerLeftY()  );
  127                       processSubStream( page, appearance.getResources(), appearance.getStream() );
  128                       g.translate( (int)-rect.getLowerLeftX(), (int)+rect.getLowerLeftY()  );
  129                   }
  130               }
  131           }
  132   
  133       }
  134   
  135       /**
  136        * You should override this method if you want to perform an action when a
  137        * text is being processed. 
  138        *
  139        * @param text The text to process 
  140        */
  141       protected void processTextPosition( TextPosition text )
  142       {
  143           //should use colorspaces for the font color but for now assume that
  144           //the font color is black
  145           try
  146           {
  147               if( this.getGraphicsState().getTextState().getRenderingMode() == PDTextState.RENDERING_MODE_FILL_TEXT )
  148               {
  149                   graphics.setColor( this.getGraphicsState().getNonStrokingColor().getJavaColor() );
  150               }
  151               else if( this.getGraphicsState().getTextState().getRenderingMode() 
  152                           == PDTextState.RENDERING_MODE_STROKE_TEXT )
  153               {
  154                   graphics.setColor( this.getGraphicsState().getStrokingColor().getJavaColor() );
  155               }
  156               else
  157               {
  158                   // TODO : need to implement....
  159                   log.warn("Unsupported RenderingMode "
  160                           + this.getGraphicsState().getTextState().getRenderingMode()
  161                           + " in PageDrawer.processTextPosition()."
  162                           + " Using RenderingMode "
  163                           + PDTextState.RENDERING_MODE_FILL_TEXT
  164                           + " instead");
  165                   graphics.setColor( this.getGraphicsState().getNonStrokingColor().getJavaColor() );
  166               }
  167               PDFont font = text.getFont();
  168   
  169               Matrix textPos = text.getTextPos().copy();
  170               float x = textPos.getXPosition();
  171               // the 0,0-reference has to be moved from the lower left (PDF) to the upper left (AWT-graphics)
  172               float y = pageSize.height - textPos.getYPosition();
  173               // Set translation to 0,0. We only need the scaling and shearing
  174               textPos.setValue(2, 0, 0);
  175               textPos.setValue(2, 1, 0);
  176               // because of the moved 0,0-reference, we have to shear in the opposite direction
  177               textPos.setValue(0, 1, (-1)*textPos.getValue(0, 1));
  178               textPos.setValue(1, 0, (-1)*textPos.getValue(1, 0));
  179               AffineTransform at = textPos.createAffineTransform();
  180               PDMatrix fontMatrix = font.getFontMatrix();
  181               at.scale(fontMatrix.getValue(0, 0) * 1000f, fontMatrix.getValue(1, 0) * 1000f);
  182               graphics.setClip(getGraphicsState().getCurrentClippingPath());
  183               font.drawString( text.getCharacter(), graphics, text.getFontSize(), at, x, y );
  184           }
  185           catch( IOException io )
  186           {
  187               io.printStackTrace();
  188           }
  189       }
  190   
  191       /**
  192        * Get the graphics that we are currently drawing on.
  193        *
  194        * @return The graphics we are drawing on.
  195        */
  196       public Graphics2D getGraphics()
  197       {
  198           return graphics;
  199       }
  200   
  201       /**
  202        * Get the page that is currently being drawn.
  203        *
  204        * @return The page that is being drawn.
  205        */
  206       public PDPage getPage()
  207       {
  208           return page;
  209       }
  210   
  211       /**
  212        * Get the size of the page that is currently being drawn.
  213        *
  214        * @return The size of the page that is being drawn.
  215        */
  216       public Dimension getPageSize()
  217       {
  218           return pageSize;
  219       }
  220   
  221       /**
  222        * Fix the y coordinate.
  223        *
  224        * @param y The y coordinate.
  225        * @return The updated y coordinate.
  226        */
  227       public double fixY( double y )
  228       {
  229           return pageSize.getHeight() - y;
  230       }
  231   
  232       /**
  233        * Get the current line path to be drawn.
  234        *
  235        * @return The current line path to be drawn.
  236        */
  237       public GeneralPath getLinePath()
  238       {
  239           return linePath;
  240       }
  241   
  242       /**
  243        * Set the line path to draw.
  244        *
  245        * @param newLinePath Set the line path to draw.
  246        */
  247       public void setLinePath(GeneralPath newLinePath)
  248       {
  249           if (linePath == null || linePath.getCurrentPoint() == null)
  250           {
  251               linePath = newLinePath;
  252           }
  253           else
  254           {
  255               linePath.append(newLinePath, false);
  256           }
  257       }
  258   
  259   
  260       /**
  261        * Fill the path.
  262        *
  263        * @param windingRule The winding rule this path will use.
  264        * 
  265        * @throws IOException If there is an IO error while filling the path.
  266        */
  267       public void fillPath(int windingRule) throws IOException
  268       {
  269           graphics.setColor( getGraphicsState().getNonStrokingColor().getJavaColor() );
  270           getLinePath().setWindingRule(windingRule);
  271           graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
  272           graphics.setClip(getGraphicsState().getCurrentClippingPath());
  273           graphics.fill( getLinePath() );
  274           getLinePath().reset();
  275       }
  276   
  277   
  278       /**
  279        * This will set the current stroke.
  280        *
  281        * @param newStroke The current stroke.
  282        * 
  283        */
  284       public void setStroke(BasicStroke newStroke)
  285       {
  286           getGraphics().setStroke( newStroke );
  287       }
  288   
  289       /**
  290        * Stroke the path.
  291        *
  292        * @throws IOException If there is an IO error while stroking the path.
  293        */
  294       public void strokePath() throws IOException
  295       {
  296           graphics.setColor( getGraphicsState().getStrokingColor().getJavaColor() ); 
  297           graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
  298           graphics.setClip(getGraphicsState().getCurrentClippingPath());
  299           GeneralPath path = getLinePath();
  300           graphics.draw( path );
  301           path.reset();
  302       }
  303   
  304       /**
  305        * Called when the color changed.
  306        * @param bStroking true for the stroking color, false for the non-stroking color
  307        * @throws IOException if an I/O error occurs
  308        */
  309       public void colorChanged(boolean bStroking) throws IOException
  310       {
  311           //logger().info("changing " + (bStroking ? "" : "non") + "stroking color");
  312       }
  313   
  314       //This code generalizes the code Jim Lynch wrote for AppendRectangleToPath
  315       /**
  316        * use the current transformation matrix to transform a single point.
  317        * @param x x-coordinate of the point to be transform
  318        * @param y y-coordinate of the point to be transform
  319        * @return the transformed coordinates as Point2D.Double
  320        */
  321       public java.awt.geom.Point2D.Double transformedPoint(double x, double y)
  322       {
  323           double[] position = {x,y}; 
  324           getGraphicsState().getCurrentTransformationMatrix().createAffineTransform().transform(
  325                   position, 0, position, 0, 1);
  326           position[1] = fixY(position[1]);
  327           return new Point2D.Double(position[0],position[1]);
  328       }
  329   
  330       /**
  331        * Set the clipping Path.
  332        *
  333        * @param windingRule The winding rule this path will use.
  334        * 
  335        */
  336       public void setClippingPath(int windingRule)
  337       {
  338           PDGraphicsState graphicsState = getGraphicsState();
  339           GeneralPath clippingPath = (GeneralPath)getLinePath().clone();
  340           clippingPath.setWindingRule(windingRule);
  341           // If there is already set a clipping path, we have to intersect the new with the existing one
  342           if (graphicsState.getCurrentClippingPath() != null) 
  343           {
  344               Area currentArea = new Area(getGraphicsState().getCurrentClippingPath());
  345               Area newArea = new Area(clippingPath);
  346               currentArea.intersect(newArea);
  347               graphicsState.setCurrentClippingPath(currentArea);
  348           }
  349           else 
  350           {
  351               graphicsState.setCurrentClippingPath(clippingPath);
  352           }
  353           getLinePath().reset();
  354       }
  355       
  356       /**
  357        * Fill with Shading.  Called by SHFill operator.
  358        *
  359        * @param ShadingName  The name of the Shading Dictionary to use for this fill instruction.
  360        *
  361        * @throws IOException If there is an IO error while shade-filling the path/clipping area.
  362        */
  363       public void SHFill(COSName ShadingName) throws IOException
  364       {
  365           PDShading Shading =FindShadingDictionary(ShadingName);
  366           log.info("Shading = " + Shading.toString());
  367           
  368           switch (Shading.getShadingType()){
  369               case 1:
  370                   SHFill_Function(Shading);
  371               break;
  372               case 2:
  373                   SHFill_Axial(Shading);
  374                   break;
  375               case 3:
  376                   SHFill_Radial(Shading);
  377                   break;
  378               case 4:
  379                   SHFill_FreeGourad(Shading);
  380                   break;
  381               case 5:
  382                   SHFill_LatticeGourad(Shading);
  383                   break;
  384               case 6:
  385                   SHFill_CoonsPatch(Shading);
  386                   break;
  387               case 7:
  388                   SHFill_TensorPatch(Shading);
  389                   break;
  390               
  391               default:
  392                   throw new IOException("Invalid ShadingType " + Shading.getShadingType() + " for Shading " + ShadingName);
  393           }
  394       }
  395       
  396       /**
  397        * Find the appropriate Shading Dictionary. This is its own private function as it is really not appropriate to override when deriving from PageDrawer.
  398        *
  399        * @param ShadingName  The name of the Shading Dictionary to use for this fill instruction.
  400        *
  401        * @returns The PDShading object
  402        * @throws IOException If there is an IO error while attempting to find the appropriate PDShading object.
  403        */
  404       private PDShading FindShadingDictionary(COSName ShadingName) throws IOException
  405       {
  406           
  407           PDResources resources = (PDResources)page.getResources();
  408                   
  409           COSDictionary AllShadings = (COSDictionary)(resources.getCOSDictionary().getDictionaryObject(COSName.SHADING));
  410           
  411           PDShading Shading = new PDShading(ShadingName, (COSDictionary)(AllShadings.getDictionaryObject(ShadingName)));
  412           
  413           return Shading;
  414           
  415       }
  416       
  417       /**
  418        * Fill with a Function-based gradient / shading.  
  419        * If extending the class, override this and its siblings, not the public SHFill method.
  420        *
  421        * @param Shading  The Shading Dictionary to use for this fill instruction.
  422        *
  423        * @throws IOException If there is an IO error while shade-filling the path/clipping area.
  424        */
  425       protected void SHFill_Function(PDShading Shading) throws IOException
  426       {
  427           throw new IOException("Not Implemented");
  428       }
  429   
  430       /**
  431        * Fill with an Axial Shading.  
  432        * If extending the class, override this and its siblings, not the public SHFill method.
  433        *
  434        * @param Shading  The Shading Dictionary to use for this fill instruction.
  435        *
  436        * @throws IOException If there is an IO error while shade-filling the path/clipping area.
  437        */
  438       protected void SHFill_Axial(PDShading Shading) throws IOException
  439       {
  440           throw new IOException("Not Implemented");
  441           
  442       }
  443   
  444       /**
  445        * Fill with a Radial gradient / shading.  
  446        * If extending the class, override this and its siblings, not the public SHFill method.
  447        *
  448        * @param Shading  The Shading Dictionary to use for this fill instruction.
  449        *
  450        * @throws IOException If there is an IO error while shade-filling the path/clipping area.
  451        */
  452       protected void SHFill_Radial(PDShading Shading) throws IOException
  453       {
  454           throw new IOException("Not Implemented");
  455       }
  456       
  457       /**
  458        * Fill with a Free-form Gourad-shaded triangle mesh.
  459        * If extending the class, override this and its siblings, not the public SHFill method.
  460        *
  461        * @param Shading  The Shading Dictionary to use for this fill instruction.
  462        *
  463        * @throws IOException If there is an IO error while shade-filling the path/clipping area.
  464        */
  465       protected void SHFill_FreeGourad(PDShading Shading) throws IOException
  466       {
  467           throw new IOException("Not Implemented");
  468       }
  469       
  470       /**
  471        * Fill with a Lattice-form Gourad-shaded triangle mesh.
  472        * If extending the class, override this and its siblings, not the public SHFill method.
  473        *
  474        * @param Shading  The Shading Dictionary to use for this fill instruction.
  475        *
  476        * @throws IOException If there is an IO error while shade-filling the path/clipping area.
  477        */
  478       protected void SHFill_LatticeGourad(PDShading Shading) throws IOException
  479       {
  480           throw new IOException("Not Implemented");
  481       }
  482       
  483       /**
  484        * Fill with a Coons patch mesh
  485        * If extending the class, override this and its siblings, not the public SHFill method.
  486        *
  487        * @param Shading  The Shading Dictionary to use for this fill instruction.
  488        *
  489        * @throws IOException If there is an IO error while shade-filling the path/clipping area.
  490        */
  491       protected void SHFill_CoonsPatch(PDShading Shading) throws IOException
  492       {
  493           throw new IOException("Not Implemented");
  494       }
  495       
  496       /**
  497        * Fill with a Tensor-product patch mesh.
  498        * If extending the class, override this and its siblings, not the public SHFill method.
  499        *
  500        * @param Shading  The Shading Dictionary to use for this fill instruction.
  501        *
  502        * @throws IOException If there is an IO error while shade-filling the path/clipping area.
  503        */
  504       protected void SHFill_TensorPatch(PDShading Shading) throws IOException
  505       {
  506           throw new IOException("Not Implemented");
  507       }
  508   }

Home » pdfbox-1.1.0-src » org.apache.pdfbox.pdfviewer » [javadoc | source]