/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pdfimport.pdfbox;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.pdmodel.text.PDTextState;
import org.apache.pdfbox.util.Matrix;
import org.apache.pdfbox.util.PDFStreamEngine;
import org.apache.pdfbox.util.ResourceLoader;
import org.apache.pdfbox.util.TextPosition;
/**
* This will paint a page in a PDF document to a graphics context.
*
* @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
* @version $Revision: 1.22 $
*/
public class PageDrawer extends PDFStreamEngine {
private GraphicsProcessor graphics;
private BasicStroke stroke;
protected PDPage page;
private final GeneralPath linePath = new GeneralPath();
/**
* Default constructor, loads properties from file.
*
* @throws IOException If there is an error loading properties from the file.
*/
public PageDrawer() throws IOException {
super(ResourceLoader.loadProperties(
"resources/pdfimport/pdfbox/PageDrawer.properties", true));
}
/**
* This will draw the page to the requested context.
*
* @param g The graphics context to draw onto.
* @param p The page to draw.
* @param pageDimension The size of the page to draw.
*
* @throws IOException If there is an IO error while drawing the page.
*/
public void drawPage(GraphicsProcessor g, PDPage p) throws IOException {
graphics = g;
page = p;
// Only if there is some content, we have to process it.
// Otherwise we are done here and we will produce an empty page
if (page.getContents() != null) {
PDResources resources = page.findResources();
processStream(page, resources, page.getContents().getStream());
}
List<?> annotations = page.getAnnotations();
for (int i = 0; i < annotations.size(); i++) {
PDAnnotation annot = (PDAnnotation) annotations.get(i);
String appearanceName = annot.getAppearanceStream();
PDAppearanceDictionary appearDictionary = annot.getAppearance();
if (appearDictionary != null) {
if (appearanceName == null) {
appearanceName = "default";
}
Map<?, ?> appearanceMap = appearDictionary.getNormalAppearance();
if (appearanceMap != null) {
PDAppearanceStream appearance =
(PDAppearanceStream) appearanceMap.get(appearanceName);
if (appearance != null) {
processSubStream(page, appearance.getResources(), appearance.getStream());
}
}
}
}
}
/**
* You should override this method if you want to perform an action when a
* text is being processed.
*
* @param text The text to process
*/
@Override
protected void processTextPosition(TextPosition text) {
Color color = null;
try {
switch(this.getGraphicsState().getTextState().getRenderingMode()) {
case PDTextState.RENDERING_MODE_FILL_TEXT:
color = this.getGraphicsState().getNonStrokingColor().getJavaColor();
break;
case PDTextState.RENDERING_MODE_STROKE_TEXT:
color = this.getGraphicsState().getStrokingColor().getJavaColor();
break;
case PDTextState.RENDERING_MODE_NEITHER_FILL_NOR_STROKE_TEXT:
//basic support for text rendering mode "invisible"
Color nsc = this.getGraphicsState().getStrokingColor().getJavaColor();
float[] components = {Color.black.getRed(), Color.black.getGreen(), Color.black.getBlue()};
color = new Color(nsc.getColorSpace(), components, 0f);
break;
default:
color = this.getGraphicsState().getNonStrokingColor().getJavaColor();
}
Matrix textPos = text.getTextPos().copy();
float x = textPos.getXPosition();
float y = textPos.getYPosition();
graphics.setClip(getGraphicsState().getCurrentClippingPath());
graphics.drawString(x, y, text.getCharacter(), color);
} catch (IOException io) {
io.printStackTrace();
}
}
/**
* Get the page that is currently being drawn.
*
* @return The page that is being drawn.
*/
public PDPage getPage() {
return page;
}
/**
* Get the current line path to be drawn.
*
* @return The current line path to be drawn.
*/
public GeneralPath getLinePath() {
return linePath;
}
/**
* This will set the current stroke.
*
* @param newStroke The current stroke.
*
*/
public void setStroke(BasicStroke newStroke) {
this.stroke = newStroke;
}
public BasicStroke getStroke() {
return this.stroke;
}
public void drawPath(boolean stroke, boolean fill, int windingRule) throws IOException {
graphics.setClip(getGraphicsState().getCurrentClippingPath());
GeneralPath path = getLinePath();
Color strokeColor = getGraphicsState().getStrokingColor().getJavaColor();
Color fillColor = getGraphicsState().getNonStrokingColor().getJavaColor();
graphics.drawPath(path, stroke ? strokeColor : null, fill ? fillColor : null, windingRule);
path.reset();
}
/**
* Draw the AWT image. Called by Invoke.
* Moved into PageDrawer so that Invoke doesn't have to reach in here for Graphics as that breaks extensibility.
*
* @param awtImage The image to draw.
* @param at The transformation to use when drawing.
*
*/
public void drawImage() {
graphics.setClip(getGraphicsState().getCurrentClippingPath());
graphics.drawImage();
}
/**
* Fill with Shading. Called by SHFill operator.
*
* @param ShadingName The name of the Shading Dictionary to use for this fill instruction.
*
* @throws IOException If there is an IO error while shade-filling the path/clipping area.
*/
public void SHFill(COSName ShadingName) throws IOException {
this.drawPath(false, true, Path2D.WIND_NON_ZERO);
}
//This code generalizes the code Jim Lynch wrote for AppendRectangleToPath
/**
* use the current transformation matrix to transform a single point.
* @param x x-coordinate of the point to be transform
* @param y y-coordinate of the point to be transform
* @return the transformed coordinates as Point2D.Double
*/
public java.awt.geom.Point2D.Double transformedPoint(double x, double y) {
double[] position = {x, y};
getGraphicsState().getCurrentTransformationMatrix().createAffineTransform().transform(
position, 0, position, 0, 1);
return new Point2D.Double(position[0], position[1]);
}
/**
* Set the clipping Path.
*
* @param windingRule The winding rule this path will use.
*
*/
public void setClippingPath(int windingRule) {
PDGraphicsState graphicsState = getGraphicsState();
GeneralPath clippingPath = (GeneralPath) getLinePath().clone();
clippingPath.setWindingRule(windingRule);
// If there is already set a clipping path, we have to intersect the new with the existing one
if (graphicsState.getCurrentClippingPath() != null) {
Area currentArea = new Area(getGraphicsState().getCurrentClippingPath());
Area newArea = new Area(clippingPath);
currentArea.intersect(newArea);
graphicsState.setCurrentClippingPath(currentArea);
} else {
graphicsState.setCurrentClippingPath(clippingPath);
}
getLinePath().reset();
}
}