org.apache.pdfbox.contentstream.PDFStreamEngine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pdfbox Show documentation
Show all versions of pdfbox Show documentation
The Apache PDFBox library is an open source Java tool for working with PDF documents.
/*
* 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 org.apache.pdfbox.contentstream;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.contentstream.operator.MissingOperandException;
import org.apache.pdfbox.contentstream.operator.state.EmptyGraphicsStackException;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSNumber;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.filter.MissingImageReaderException;
import org.apache.pdfbox.pdfparser.PDFStreamParser;
import org.apache.pdfbox.pdmodel.MissingResourceException;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDFontFactory;
import org.apache.pdfbox.pdmodel.font.PDType3CharProc;
import org.apache.pdfbox.pdmodel.font.PDType3Font;
import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDTransparencyGroup;
import org.apache.pdfbox.pdmodel.graphics.pattern.PDTilingPattern;
import org.apache.pdfbox.pdmodel.graphics.state.PDGraphicsState;
import org.apache.pdfbox.pdmodel.graphics.state.PDTextState;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.util.Matrix;
import org.apache.pdfbox.util.Vector;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.contentstream.operator.OperatorProcessor;
/**
* Processes a PDF content stream and executes certain operations.
* Provides a callback interface for clients that want to do things with the stream.
*
* @author Ben Litchfield
*/
public abstract class PDFStreamEngine
{
private static final Log LOG = LogFactory.getLog(PDFStreamEngine.class);
private final Map operators = new HashMap(80);
private Matrix textMatrix;
private Matrix textLineMatrix;
private Stack graphicsStack = new Stack();
private PDResources resources;
private PDPage currentPage;
private boolean isProcessingPage;
private Matrix initialMatrix;
/**
* Creates a new PDFStreamEngine.
*/
protected PDFStreamEngine()
{
}
/**
* Register a custom operator processor with the engine.
*
* @param operator The operator as a string.
* @param op Processor instance.
* @deprecated Use {@link #addOperator(OperatorProcessor)} instead
*/
@Deprecated
public void registerOperatorProcessor(String operator, OperatorProcessor op)
{
op.setContext(this);
operators.put(operator, op);
}
/**
* Adds an operator processor to the engine.
*
* @param op operator processor
*/
public final void addOperator(OperatorProcessor op)
{
op.setContext(this);
operators.put(op.getName(), op);
}
/**
* Initialises the stream engine for the given page.
*/
private void initPage(PDPage page)
{
if (page == null)
{
throw new IllegalArgumentException("Page cannot be null");
}
currentPage = page;
graphicsStack.clear();
graphicsStack.push(new PDGraphicsState(page.getCropBox()));
textMatrix = null;
textLineMatrix = null;
resources = null;
initialMatrix = page.getMatrix();
}
/**
* This will initialise and process the contents of the stream.
*
* @param page the page to process
* @throws IOException if there is an error accessing the stream
*/
public void processPage(PDPage page) throws IOException
{
initPage(page);
if (page.hasContents())
{
isProcessingPage = true;
processStream(page);
isProcessingPage = false;
}
}
/**
* Shows a transparency group from the content stream.
*
* @param form transparency group (form) XObject
* @throws IOException if the transparency group cannot be processed
*/
public void showTransparencyGroup(PDTransparencyGroup form) throws IOException
{
processTransparencyGroup(form);
}
/**
* Shows a form from the content stream.
*
* @param form form XObject
* @throws IOException if the form cannot be processed
*/
public void showForm(PDFormXObject form) throws IOException
{
if (currentPage == null)
{
throw new IllegalStateException("No current page, call " +
"#processChildStream(PDContentStream, PDPage) instead");
}
processStream(form);
}
/**
* Processes a soft mask transparency group stream.
*/
protected void processSoftMask(PDTransparencyGroup group) throws IOException
{
// clear the current soft mask (this mask) to avoid recursion
saveGraphicsState();
getGraphicsState().setSoftMask(null);
processTransparencyGroup(group);
restoreGraphicsState();
}
/**
* Processes a transparency group stream.
*/
protected void processTransparencyGroup(PDTransparencyGroup group) throws IOException
{
if (currentPage == null)
{
throw new IllegalStateException("No current page, call " +
"#processChildStream(PDContentStream, PDPage) instead");
}
PDResources parent = pushResources(group);
Stack savedStack = saveGraphicsStack();
// transform the CTM using the stream's matrix
getGraphicsState().getCurrentTransformationMatrix().concatenate(group.getMatrix());
// clip to bounding box
clipToRect(group.getBBox());
processStreamOperators(group);
restoreGraphicsStack(savedStack);
popResources(parent);
}
/**
* Processes a Type 3 character stream.
*
* @param charProc Type 3 character procedure
* @param textRenderingMatrix the Text Rendering Matrix
* @throws IOException if there is an error reading or parsing the character content stream.
*/
protected void processType3Stream(PDType3CharProc charProc, Matrix textRenderingMatrix)
throws IOException
{
if (currentPage == null)
{
throw new IllegalStateException("No current page, call " +
"#processChildStream(PDContentStream, PDPage) instead");
}
PDResources parent = pushResources(charProc);
Stack savedStack = saveGraphicsStack();
// replace the CTM with the TRM
getGraphicsState().setCurrentTransformationMatrix(textRenderingMatrix);
// transform the CTM using the stream's matrix (this is the FontMatrix)
getGraphicsState().getCurrentTransformationMatrix().concatenate(charProc.getMatrix());
// note: we don't clip to the BBox as it is often wrong, see PDFBOX-1917
// save text matrices (Type 3 stream may contain BT/ET, see PDFBOX-2137)
Matrix textMatrixOld = textMatrix;
textMatrix = new Matrix();
Matrix textLineMatrixOld = textLineMatrix;
textLineMatrix = new Matrix();
processStreamOperators(charProc);
// restore text matrices
textMatrix = textMatrixOld;
textLineMatrix = textLineMatrixOld;
restoreGraphicsStack(savedStack);
popResources(parent);
}
/**
* Process the given annotation with the specified appearance stream.
*
* @param annotation The annotation containing the appearance stream to process.
* @param appearance The appearance stream to process.
* @throws IOException If there is an error reading or parsing the appearance content stream.
*/
protected void processAnnotation(PDAnnotation annotation, PDAppearanceStream appearance)
throws IOException
{
PDResources parent = pushResources(appearance);
Stack savedStack = saveGraphicsStack();
PDRectangle bbox = appearance.getBBox();
PDRectangle rect = annotation.getRectangle();
Matrix matrix = appearance.getMatrix();
// zero-sized rectangles are not valid
if (rect != null && rect.getWidth() > 0 && rect.getHeight() > 0 && bbox != null)
{
// transformed appearance box fixme: may be an arbitrary shape
Rectangle2D transformedBox = bbox.transform(matrix).getBounds2D();
// compute a matrix which scales and translates the transformed appearance box to align
// with the edges of the annotation's rectangle
Matrix a = Matrix.getTranslateInstance(rect.getLowerLeftX(), rect.getLowerLeftY());
a.concatenate(Matrix.getScaleInstance((float)(rect.getWidth() / transformedBox.getWidth()),
(float)(rect.getHeight() / transformedBox.getHeight())));
a.concatenate(Matrix.getTranslateInstance((float) -transformedBox.getX(),
(float) -transformedBox.getY()));
// Matrix shall be concatenated with A to form a matrix AA that maps from the appearance's
// coordinate system to the annotation's rectangle in default user space
//
// HOWEVER only the opposite order works for rotated pages with
// filled fields / annotations that have a matrix in the appearance stream, see PDFBOX-3083
Matrix aa = Matrix.concatenate(a, matrix);
// make matrix AA the CTM
getGraphicsState().setCurrentTransformationMatrix(aa);
// clip to bounding box
clipToRect(bbox);
processStreamOperators(appearance);
}
restoreGraphicsStack(savedStack);
popResources(parent);
}
/**
* Process the given tiling pattern.
*
* @param tilingPattern the tiling pattern
* @param color color to use, if this is an uncoloured pattern, otherwise null.
* @param colorSpace color space to use, if this is an uncoloured pattern, otherwise null.
* @throws IOException if there is an error reading or parsing the tiling pattern content stream.
*/
protected final void processTilingPattern(PDTilingPattern tilingPattern, PDColor color,
PDColorSpace colorSpace) throws IOException
{
processTilingPattern(tilingPattern, color, colorSpace, tilingPattern.getMatrix());
}
/**
* Process the given tiling pattern. Allows the pattern matrix to be overridden for custom
* rendering.
*
* @param tilingPattern the tiling pattern
* @param color color to use, if this is an uncoloured pattern, otherwise null.
* @param colorSpace color space to use, if this is an uncoloured pattern, otherwise null.
* @param patternMatrix the pattern matrix, may be overridden for custom rendering.
* @throws IOException if there is an error reading or parsing the tiling pattern content stream.
*/
protected final void processTilingPattern(PDTilingPattern tilingPattern, PDColor color,
PDColorSpace colorSpace, Matrix patternMatrix)
throws IOException
{
PDResources parent = pushResources(tilingPattern);
Matrix parentMatrix = initialMatrix;
initialMatrix = Matrix.concatenate(initialMatrix, patternMatrix);
// save the original graphics state
Stack savedStack = saveGraphicsStack();
// save a clean state (new clipping path, line path, etc.)
Rectangle2D bbox = tilingPattern.getBBox().transform(patternMatrix).getBounds2D();
PDRectangle rect = new PDRectangle((float)bbox.getX(), (float)bbox.getY(),
(float)bbox.getWidth(), (float)bbox.getHeight());
graphicsStack.push(new PDGraphicsState(rect));
// non-colored patterns have to be given a color
if (colorSpace != null)
{
color = new PDColor(color.getComponents(), colorSpace);
getGraphicsState().setNonStrokingColorSpace(colorSpace);
getGraphicsState().setNonStrokingColor(color);
getGraphicsState().setStrokingColorSpace(colorSpace);
getGraphicsState().setStrokingColor(color);
}
// transform the CTM using the stream's matrix
getGraphicsState().getCurrentTransformationMatrix().concatenate(patternMatrix);
// clip to bounding box
clipToRect(tilingPattern.getBBox());
processStreamOperators(tilingPattern);
initialMatrix = parentMatrix;
restoreGraphicsStack(savedStack);
popResources(parent);
}
/**
* Shows the given annotation.
*
* @param annotation An annotation on the current page.
* @throws IOException If an error occurred reading the annotation
*/
public void showAnnotation(PDAnnotation annotation) throws IOException
{
PDAppearanceStream appearanceStream = getAppearance(annotation);
if (appearanceStream != null)
{
processAnnotation(annotation, appearanceStream);
}
}
/**
* Returns the appearance stream to process for the given annotation. May be used to render
* a specific appearance such as "hover".
*
* @param annotation The current annotation.
* @return The stream to process.
*/
public PDAppearanceStream getAppearance(PDAnnotation annotation)
{
return annotation.getNormalAppearanceStream();
}
/**
* Process a child stream of the given page. Cannot be used with {@link #processPage(PDPage)}.
*
* @param contentStream the child content stream
* @param page
* @throws IOException if there is an exception while processing the stream
*/
protected void processChildStream(PDContentStream contentStream, PDPage page) throws IOException
{
if (isProcessingPage)
{
throw new IllegalStateException("Current page has already been set via " +
" #processPage(PDPage) call #processChildStream(PDContentStream) instead");
}
initPage(page);
processStream(contentStream);
currentPage = null;
}
/**
* Process a content stream.
*
* @param contentStream the content stream
* @throws IOException if there is an exception while processing the stream
*/
private void processStream(PDContentStream contentStream) throws IOException
{
PDResources parent = pushResources(contentStream);
Stack savedStack = saveGraphicsStack();
Matrix parentMatrix = initialMatrix;
// transform the CTM using the stream's matrix
getGraphicsState().getCurrentTransformationMatrix().concatenate(contentStream.getMatrix());
// the stream's initial matrix includes the parent CTM, e.g. this allows a scaled form
initialMatrix = getGraphicsState().getCurrentTransformationMatrix().clone();
// clip to bounding box
PDRectangle bbox = contentStream.getBBox();
clipToRect(bbox);
processStreamOperators(contentStream);
initialMatrix = parentMatrix;
restoreGraphicsStack(savedStack);
popResources(parent);
}
/**
* Processes the operators of the given content stream.
*
* @param contentStream to content stream to parse.
* @throws IOException if there is an error reading or parsing the content stream.
*/
private void processStreamOperators(PDContentStream contentStream) throws IOException
{
List arguments = new ArrayList();
PDFStreamParser parser = new PDFStreamParser(contentStream);
Object token = parser.parseNextToken();
while (token != null)
{
if (token instanceof COSObject)
{
arguments.add(((COSObject) token).getObject());
}
else if (token instanceof Operator)
{
processOperator((Operator) token, arguments);
arguments = new ArrayList();
}
else
{
arguments.add((COSBase) token);
}
token = parser.parseNextToken();
}
}
/**
* Pushes the given stream's resources, returning the previous resources.
*/
private PDResources pushResources(PDContentStream contentStream)
{
// resource lookup: first look for stream resources, then fallback to the current page
PDResources parentResources = resources;
PDResources streamResources = contentStream.getResources();
if (streamResources != null)
{
resources = streamResources;
}
else if (resources != null)
{
// inherit directly from parent stream, this is not in the PDF spec, but the file from
// PDFBOX-1359 does this and works in Acrobat
}
else
{
resources = currentPage.getResources();
}
// resources are required in PDF
if (resources == null)
{
resources = new PDResources();
}
return parentResources;
}
/**
* Pops the current resources, replacing them with the given resources.
*/
private void popResources(PDResources parentResources)
{
resources = parentResources;
}
/**
* Transforms the given rectangle using the CTM and then intersects it with the current
* clipping area.
*/
private void clipToRect(PDRectangle rectangle)
{
if (rectangle != null)
{
GeneralPath clip = rectangle.transform(getGraphicsState().getCurrentTransformationMatrix());
getGraphicsState().intersectClippingPath(clip);
}
}
/**
* Called when the BT operator is encountered. This method is for overriding in subclasses, the
* default implementation does nothing.
*
* @throws IOException if there was an error processing the text
*/
public void beginText() throws IOException
{
// overridden in subclasses
}
/**
* Called when the ET operator is encountered. This method is for overriding in subclasses, the
* default implementation does nothing.
*
* @throws IOException if there was an error processing the text
*/
public void endText() throws IOException
{
// overridden in subclasses
}
/**
* Called when a string of text is to be shown.
*
* @param string the encoded text
* @throws IOException if there was an error showing the text
*/
public void showTextString(byte[] string) throws IOException
{
showText(string);
}
/**
* Called when a string of text with spacing adjustments is to be shown.
*
* @param array array of encoded text strings and adjustments
* @throws IOException if there was an error showing the text
*/
public void showTextStrings(COSArray array) throws IOException
{
PDTextState textState = getGraphicsState().getTextState();
float fontSize = textState.getFontSize();
float horizontalScaling = textState.getHorizontalScaling() / 100f;
PDFont font = textState.getFont();
boolean isVertical = false;
if (font != null)
{
isVertical = font.isVertical();
}
for (COSBase obj : array)
{
if (obj instanceof COSNumber)
{
float tj = ((COSNumber)obj).floatValue();
// calculate the combined displacements
float tx, ty;
if (isVertical)
{
tx = 0;
ty = -tj / 1000 * fontSize;
}
else
{
tx = -tj / 1000 * fontSize * horizontalScaling;
ty = 0;
}
applyTextAdjustment(tx, ty);
}
else if(obj instanceof COSString)
{
byte[] string = ((COSString)obj).getBytes();
showText(string);
}
else
{
throw new IOException("Unknown type in array for TJ operation:" + obj);
}
}
}
/**
* Applies a text position adjustment from the TJ operator. May be overridden in subclasses.
*
* @param tx x-translation
* @param ty y-translation
*/
protected void applyTextAdjustment(float tx, float ty) throws IOException
{
// update the text matrix
textMatrix.concatenate(Matrix.getTranslateInstance(tx, ty));
}
/**
* Process text from the PDF Stream. You should override this method if you want to
* perform an action when encoded text is being processed.
*
* @param string the encoded text
* @throws IOException if there is an error processing the string
*/
protected void showText(byte[] string) throws IOException
{
PDGraphicsState state = getGraphicsState();
PDTextState textState = state.getTextState();
// get the current font
PDFont font = textState.getFont();
if (font == null)
{
LOG.warn("No current font, will use default");
font = PDFontFactory.createDefaultFont();
}
float fontSize = textState.getFontSize();
float horizontalScaling = textState.getHorizontalScaling() / 100f;
float charSpacing = textState.getCharacterSpacing();
// put the text state parameters into matrix form
Matrix parameters = new Matrix(
fontSize * horizontalScaling, 0, // 0
0, fontSize, // 0
0, textState.getRise()); // 1
// read the stream until it is empty
InputStream in = new ByteArrayInputStream(string);
while (in.available() > 0)
{
// decode a character
int before = in.available();
int code = font.readCode(in);
int codeLength = before - in.available();
String unicode = font.toUnicode(code);
// Word spacing shall be applied to every occurrence of the single-byte character code
// 32 in a string when using a simple font or a composite font that defines code 32 as
// a single-byte code.
float wordSpacing = 0;
if (codeLength == 1 && code == 32)
{
wordSpacing += textState.getWordSpacing();
}
// text rendering matrix (text space -> device space)
Matrix ctm = state.getCurrentTransformationMatrix();
Matrix textRenderingMatrix = parameters.multiply(textMatrix).multiply(ctm);
// get glyph's position vector if this is vertical text
// changes to vertical text should be tested with PDFBOX-2294 and PDFBOX-1422
if (font.isVertical())
{
// position vector, in text space
Vector v = font.getPositionVector(code);
// apply the position vector to the horizontal origin to get the vertical origin
textRenderingMatrix.translate(v);
}
// get glyph's horizontal and vertical displacements, in text space
Vector w = font.getDisplacement(code);
// process the decoded glyph
saveGraphicsState();
Matrix textMatrixOld = textMatrix;
Matrix textLineMatrixOld = textLineMatrix;
showGlyph(textRenderingMatrix, font, code, unicode, w);
textMatrix = textMatrixOld;
textLineMatrix = textLineMatrixOld;
restoreGraphicsState();
// calculate the combined displacements
float tx, ty;
if (font.isVertical())
{
tx = 0;
ty = w.getY() * fontSize + charSpacing + wordSpacing;
}
else
{
tx = (w.getX() * fontSize + charSpacing + wordSpacing) * horizontalScaling;
ty = 0;
}
// update the text matrix
textMatrix.concatenate(Matrix.getTranslateInstance(tx, ty));
}
}
/**
* Called when a glyph is to be processed.This method is intended for overriding in subclasses,
* the default implementation does nothing.
*
* @param textRenderingMatrix the current text rendering matrix, Trm
* @param font the current font
* @param code internal PDF character code for the glyph
* @param unicode the Unicode text for this glyph, or null if the PDF does provide it
* @param displacement the displacement (i.e. advance) of the glyph in text space
* @throws IOException if the glyph cannot be processed
*/
protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, String unicode,
Vector displacement) throws IOException
{
if (font instanceof PDType3Font)
{
showType3Glyph(textRenderingMatrix, (PDType3Font)font, code, unicode, displacement);
}
else
{
showFontGlyph(textRenderingMatrix, font, code, unicode, displacement);
}
}
/**
* Called when a glyph is to be processed.This method is intended for overriding in subclasses,
* the default implementation does nothing.
*
* @param textRenderingMatrix the current text rendering matrix, Trm
* @param font the current font
* @param code internal PDF character code for the glyph
* @param unicode the Unicode text for this glyph, or null if the PDF does provide it
* @param displacement the displacement (i.e. advance) of the glyph in text space
* @throws IOException if the glyph cannot be processed
*/
protected void showFontGlyph(Matrix textRenderingMatrix, PDFont font, int code, String unicode,
Vector displacement) throws IOException
{
// overridden in subclasses
}
/**
* Called when a glyph is to be processed.This method is intended for overriding in subclasses,
* the default implementation does nothing.
*
* @param textRenderingMatrix the current text rendering matrix, Trm
* @param font the current font
* @param code internal PDF character code for the glyph
* @param unicode the Unicode text for this glyph, or null if the PDF does provide it
* @param displacement the displacement (i.e. advance) of the glyph in text space
* @throws IOException if the glyph cannot be processed
*/
protected void showType3Glyph(Matrix textRenderingMatrix, PDType3Font font, int code,
String unicode, Vector displacement) throws IOException
{
PDType3CharProc charProc = font.getCharProc(code);
if (charProc != null)
{
processType3Stream(charProc, textRenderingMatrix);
}
}
/**
* This is used to handle an operation.
*
* @param operation The operation to perform.
* @param arguments The list of arguments.
* @throws IOException If there is an error processing the operation.
*/
public void processOperator(String operation, List arguments) throws IOException
{
Operator operator = Operator.getOperator(operation);
processOperator(operator, arguments);
}
/**
* This is used to handle an operation.
*
* @param operator The operation to perform.
* @param operands The list of arguments.
* @throws IOException If there is an error processing the operation.
*/
protected void processOperator(Operator operator, List operands) throws IOException
{
String name = operator.getName();
OperatorProcessor processor = operators.get(name);
if (processor != null)
{
processor.setContext(this);
try
{
processor.process(operator, operands);
}
catch (IOException e)
{
operatorException(operator, operands, e);
}
}
else
{
unsupportedOperator(operator, operands);
}
}
/**
* Called when an unsupported operator is encountered.
*
* @param operator The unknown operator.
* @param operands The list of operands.
*/
protected void unsupportedOperator(Operator operator, List operands) throws IOException
{
// overridden in subclasses
}
/**
* Called when an exception is thrown by an operator.
*
* @param operator The unknown operator.
* @param operands The list of operands.
*/
protected void operatorException(Operator operator, List operands, IOException e)
throws IOException
{
if (e instanceof MissingOperandException ||
e instanceof MissingResourceException ||
e instanceof MissingImageReaderException)
{
LOG.error(e.getMessage());
}
else if (e instanceof EmptyGraphicsStackException)
{
LOG.warn(e.getMessage());
}
else if (operator.getName().equals("Do"))
{
// todo: this too forgiving, but PDFBox has always worked this way for DrawObject
// some careful refactoring is needed
LOG.warn(e.getMessage());
}
else
{
throw e;
}
}
/**
* Pushes the current graphics state to the stack.
*/
public void saveGraphicsState()
{
graphicsStack.push(graphicsStack.peek().clone());
}
/**
* Pops the current graphics state from the stack.
*/
public void restoreGraphicsState()
{
graphicsStack.pop();
}
/**
* Saves the entire graphics stack.
*/
protected final Stack saveGraphicsStack()
{
Stack savedStack = graphicsStack;
graphicsStack = new Stack();
graphicsStack.add(savedStack.peek().clone());
return savedStack;
}
/**
* Restores the entire graphics stack.
*/
protected final void restoreGraphicsStack(Stack snapshot)
{
graphicsStack = snapshot;
}
/**
* @return Returns the size of the graphicsStack.
*/
public int getGraphicsStackSize()
{
return graphicsStack.size();
}
/**
* @return Returns the graphicsState.
*/
public PDGraphicsState getGraphicsState()
{
return graphicsStack.peek();
}
/**
* @return Returns the textLineMatrix.
*/
public Matrix getTextLineMatrix()
{
return textLineMatrix;
}
/**
* @param value The textLineMatrix to set.
*/
public void setTextLineMatrix(Matrix value)
{
textLineMatrix = value;
}
/**
* @return Returns the textMatrix.
*/
public Matrix getTextMatrix()
{
return textMatrix;
}
/**
* @param value The textMatrix to set.
*/
public void setTextMatrix(Matrix value)
{
textMatrix = value;
}
/**
* @param array dash array
* @param phase dash phase
*/
public void setLineDashPattern(COSArray array, int phase)
{
if (phase < 0)
{
LOG.warn("Dash phase has negative value " + phase + ", set to 0");
phase = 0;
}
PDLineDashPattern lineDash = new PDLineDashPattern(array, phase);
getGraphicsState().setLineDashPattern(lineDash);
}
/**
* Returns the stream' resources.
*/
public PDResources getResources()
{
return resources;
}
/**
* Returns the current page.
*/
public PDPage getCurrentPage()
{
return currentPage;
}
/**
* Gets the stream's initial matrix.
*/
public Matrix getInitialMatrix()
{
return initialMatrix;
}
/**
* Transforms a point using the CTM.
*/
public Point2D.Float transformedPoint(float x, float y)
{
float[] position = { x, y };
getGraphicsState().getCurrentTransformationMatrix().createAffineTransform()
.transform(position, 0, position, 0, 1);
return new Point2D.Float(position[0], position[1]);
}
/**
* Transforms a width using the CTM.
*/
protected float transformWidth(float width)
{
Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
float x = ctm.getScaleX() + ctm.getShearX();
float y = ctm.getScaleY() + ctm.getShearY();
return width * (float)Math.sqrt((x * x + y * y) * 0.5);
}
}