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

com.itextpdf.text.pdf.parser.PdfContentStreamProcessor Maven / Gradle / Ivy

/*
 * $Id: 058eeefd3934590b5e4f2c04d5d81db3c54525a5 $
 *
 * This file is part of the iText (R) project.
 * Copyright (c) 1998-2016 iText Group NV
 * Authors: Kevin Day, Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * This program 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 Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: [email protected]
 */
package com.itextpdf.text.pdf.parser;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.io.RandomAccessSourceFactory;
import com.itextpdf.text.pdf.*;

import java.io.IOException;
import java.util.*;

/**
 * Processor for a PDF content Stream.
 * @since	2.1.4
 */
public class PdfContentStreamProcessor {
	/**
	 * Default operator
	 * @since 5.0.1
	 */
    public static final String DEFAULTOPERATOR = "DefaultOperator";

	/** A map with all supported operators (PDF syntax). */
    final private Map operators;
    /** Resources for the content stream. */
    private ResourceDictionary resources;
    /** Stack keeping track of the graphics state. */
    private final Stack gsStack = new Stack();
    /** Text matrix. */
    private Matrix textMatrix;
    /** Text line matrix. */
    private Matrix textLineMatrix;
    /** Listener that will be notified of render events */
    final private RenderListener renderListener;
    /** A map with all supported XObject handlers */
    final private Map xobjectDoHandlers;
    /**
     * The font cache.
     * @since 5.0.6
     */
    /**  */
    final private Map cachedFonts = new HashMap();
    /**
     * A stack containing marked content info.
     * @since 5.0.2
     */
    private final Stack markedContentStack = new Stack();

    /**
     * Creates a new PDF Content Stream Processor that will send it's output to the
     * designated render listener.
     *
     * @param renderListener the {@link RenderListener} that will receive rendering notifications
     */
    public PdfContentStreamProcessor(RenderListener renderListener) {
        this.renderListener = renderListener;
        operators = new HashMap();
        populateOperators();
        xobjectDoHandlers = new HashMap();
        populateXObjectDoHandlers();
        reset();
    }

    private void populateXObjectDoHandlers(){
        registerXObjectDoHandler(PdfName.DEFAULT, new IgnoreXObjectDoHandler());
        registerXObjectDoHandler(PdfName.FORM, new FormXObjectDoHandler());
        registerXObjectDoHandler(PdfName.IMAGE, new ImageXObjectDoHandler());
    }

    /**
     * Registers a Do handler that will be called when Do for the provided XObject subtype is encountered during content processing.
     * 
* If you register a handler, it is a very good idea to pass the call on to the existing registered handler (returned by this call), otherwise you * may inadvertently change the internal behavior of the processor. * @param xobjectSubType the XObject subtype this handler will process, or PdfName.DEFAULT for a catch-all handler * @param handler the handler that will receive notification when the Do operator for the specified subtype is encountered * @return the existing registered handler, if any * @since 5.0.1 */ public XObjectDoHandler registerXObjectDoHandler(PdfName xobjectSubType, XObjectDoHandler handler){ return xobjectDoHandlers.put(xobjectSubType, handler); } /** * Gets the font pointed to by the indirect reference. The font may have been cached. * @param ind the indirect reference ponting to the font * @return the font * @since 5.0.6 */ private CMapAwareDocumentFont getFont(PRIndirectReference ind) { Integer n = Integer.valueOf(ind.getNumber()); CMapAwareDocumentFont font = cachedFonts.get(n); if (font == null) { font = new CMapAwareDocumentFont(ind); cachedFonts.put(n, font); } return font; } private CMapAwareDocumentFont getFont(PdfDictionary fontResource) { return new CMapAwareDocumentFont(fontResource); } /** * Loads all the supported graphics and text state operators in a map. */ private void populateOperators(){ registerContentOperator(DEFAULTOPERATOR, new IgnoreOperatorContentOperator()); registerContentOperator("q", new PushGraphicsState()); registerContentOperator("Q", new PopGraphicsState()); registerContentOperator("g", new SetGrayFill()); registerContentOperator("G", new SetGrayStroke()); registerContentOperator("rg", new SetRGBFill()); registerContentOperator("RG", new SetRGBStroke()); registerContentOperator("k", new SetCMYKFill()); registerContentOperator("K", new SetCMYKStroke()); registerContentOperator("cs", new SetColorSpaceFill()); registerContentOperator("CS", new SetColorSpaceStroke()); registerContentOperator("sc", new SetColorFill()); registerContentOperator("SC", new SetColorStroke()); registerContentOperator("scn", new SetColorFill()); registerContentOperator("SCN", new SetColorStroke()); registerContentOperator("cm", new ModifyCurrentTransformationMatrix()); registerContentOperator("gs", new ProcessGraphicsStateResource()); SetTextCharacterSpacing tcOperator = new SetTextCharacterSpacing(); registerContentOperator("Tc", tcOperator); SetTextWordSpacing twOperator = new SetTextWordSpacing(); registerContentOperator("Tw", twOperator); registerContentOperator("Tz", new SetTextHorizontalScaling()); SetTextLeading tlOperator = new SetTextLeading(); registerContentOperator("TL", tlOperator); registerContentOperator("Tf", new SetTextFont()); registerContentOperator("Tr", new SetTextRenderMode()); registerContentOperator("Ts", new SetTextRise()); registerContentOperator("BT", new BeginText()); registerContentOperator("ET", new EndText()); registerContentOperator("BMC", new BeginMarkedContent()); registerContentOperator("BDC", new BeginMarkedContentDictionary()); registerContentOperator("EMC", new EndMarkedContent()); TextMoveStartNextLine tdOperator = new TextMoveStartNextLine(); registerContentOperator("Td", tdOperator); registerContentOperator("TD", new TextMoveStartNextLineWithLeading(tdOperator, tlOperator)); registerContentOperator("Tm", new TextSetTextMatrix()); TextMoveNextLine tstarOperator = new TextMoveNextLine(tdOperator); registerContentOperator("T*", tstarOperator); ShowText tjOperator = new ShowText(); registerContentOperator("Tj", tjOperator); MoveNextLineAndShowText tickOperator = new MoveNextLineAndShowText(tstarOperator, tjOperator); registerContentOperator("'", tickOperator); registerContentOperator("\"", new MoveNextLineAndShowTextWithSpacing(twOperator, tcOperator, tickOperator)); registerContentOperator("TJ", new ShowTextArray()); registerContentOperator("Do", new Do()); registerContentOperator("w", new SetLineWidth()); registerContentOperator("J", new SetLineCap()); registerContentOperator("j", new SetLineJoin()); registerContentOperator("M", new SetMiterLimit()); registerContentOperator("d", new SetLineDashPattern()); // Path construction and painting operators if (renderListener instanceof ExtRenderListener) { int fillStroke = PathPaintingRenderInfo.FILL | PathPaintingRenderInfo.STROKE; registerContentOperator("m", new MoveTo()); registerContentOperator("l", new LineTo()); registerContentOperator("c", new Curve()); registerContentOperator("v", new CurveFirstPointDuplicated()); registerContentOperator("y", new CurveFourhPointDuplicated()); registerContentOperator("h", new CloseSubpath()); registerContentOperator("re", new Rectangle()); registerContentOperator("S", new PaintPath(PathPaintingRenderInfo.STROKE, -1, false)); registerContentOperator("s", new PaintPath(PathPaintingRenderInfo.STROKE, -1, true)); registerContentOperator("f", new PaintPath(PathPaintingRenderInfo.FILL, PathPaintingRenderInfo.NONZERO_WINDING_RULE, false)); registerContentOperator("F", new PaintPath(PathPaintingRenderInfo.FILL, PathPaintingRenderInfo.NONZERO_WINDING_RULE, false)); registerContentOperator("f*", new PaintPath(PathPaintingRenderInfo.FILL, PathPaintingRenderInfo.EVEN_ODD_RULE, false)); registerContentOperator("B", new PaintPath(fillStroke, PathPaintingRenderInfo.NONZERO_WINDING_RULE, false)); registerContentOperator("B*", new PaintPath(fillStroke, PathPaintingRenderInfo.EVEN_ODD_RULE, false)); registerContentOperator("b", new PaintPath(fillStroke, PathPaintingRenderInfo.NONZERO_WINDING_RULE, true)); registerContentOperator("b*", new PaintPath(fillStroke, PathPaintingRenderInfo.EVEN_ODD_RULE, true)); registerContentOperator("n", new PaintPath(PathPaintingRenderInfo.NO_OP, -1, false)); registerContentOperator("W", new ClipPath(PathPaintingRenderInfo.NONZERO_WINDING_RULE)); registerContentOperator("W*", new ClipPath(PathPaintingRenderInfo.EVEN_ODD_RULE)); } } /** * Registers a content operator that will be called when the specified operator string is encountered during content processing. *
* If you register an operator, it is a very good idea to pass the call on to the existing registered operator (returned by this call), otherwise you * may inadvertently change the internal behavior of the processor. * @param operatorString the operator id, or DEFAULTOPERATOR for a catch-all operator * @param operator the operator that will receive notification when the operator is encountered * @return the existing registered operator, if any * @since 2.1.7 */ public ContentOperator registerContentOperator(String operatorString, ContentOperator operator){ return operators.put(operatorString, operator); } /** * @return {@link java.util.Collection} containing all the registered operators strings * @since 5.5.6 */ public Collection getRegisteredOperatorStrings() { return new ArrayList(operators.keySet()); } /** * Resets the graphics state stack, matrices and resources. */ public void reset(){ gsStack.removeAllElements(); gsStack.add(new GraphicsState()); textMatrix = null; textLineMatrix = null; resources = new ResourceDictionary(); } /** * Returns the current graphics state. * @return the graphics state */ public GraphicsState gs(){ return gsStack.peek(); } /** * Invokes an operator. * @param operator the PDF Syntax of the operator * @param operands a list with operands */ private void invokeOperator(PdfLiteral operator, ArrayList operands) throws Exception{ ContentOperator op = operators.get(operator.toString()); if (op == null) op = operators.get(DEFAULTOPERATOR); op.invoke(this, operator, operands); } /** * Add to the marked content stack * @param tag the tag of the marked content * @param dict the PdfDictionary associated with the marked content * @since 5.0.2 */ private void beginMarkedContent(PdfName tag, PdfDictionary dict) { markedContentStack.push(new MarkedContentInfo(tag, dict)); } /** * Remove the latest marked content from the stack. Keeps track of the BMC, BDC and EMC operators. * @since 5.0.2 */ private void endMarkedContent() { markedContentStack.pop(); } /** * Used to trigger beginTextBlock on the renderListener */ private void beginText(){ renderListener.beginTextBlock(); } /** * Used to trigger endTextBlock on the renderListener */ private void endText(){ renderListener.endTextBlock(); } /** * Displays text. * @param string the text to display */ private void displayPdfString(PdfString string){ TextRenderInfo renderInfo = new TextRenderInfo(string, gs(), textMatrix, markedContentStack); renderListener.renderText(renderInfo); textMatrix = new Matrix(renderInfo.getUnscaledWidth(), 0).multiply(textMatrix); } /** * Displays an XObject using the registered handler for this XObject's subtype * @param xobjectName the name of the XObject to retrieve from the resource dictionary */ private void displayXObject(PdfName xobjectName) throws IOException { PdfDictionary xobjects = resources.getAsDict(PdfName.XOBJECT); PdfObject xobject = xobjects.getDirectObject(xobjectName); PdfStream xobjectStream = (PdfStream)xobject; PdfName subType = xobjectStream.getAsName(PdfName.SUBTYPE); if (xobject.isStream()){ XObjectDoHandler handler = xobjectDoHandlers.get(subType); if (handler == null) handler = xobjectDoHandlers.get(PdfName.DEFAULT); handler.handleXObject(this, xobjectStream, xobjects.getAsIndirectObject(xobjectName)); } else { throw new IllegalStateException(MessageLocalization.getComposedMessage("XObject.1.is.not.a.stream", xobjectName)); } } /** * Displays the current path. * * @param operation One of the possible combinations of {@link com.itextpdf.text.pdf.parser.PathPaintingRenderInfo#STROKE} * and {@link com.itextpdf.text.pdf.parser.PathPaintingRenderInfo#FILL} values or * {@link com.itextpdf.text.pdf.parser.PathPaintingRenderInfo#NO_OP} * @param rule Either {@link com.itextpdf.text.pdf.parser.PathPaintingRenderInfo#NONZERO_WINDING_RULE} or * {@link com.itextpdf.text.pdf.parser.PathPaintingRenderInfo#EVEN_ODD_RULE} * In case it isn't applicable pass any byte value. * @param close Indicates whether the path should be closed or not. * @since 5.5.6 */ private void paintPath(int operation, int rule, boolean close) { if (close) { modifyPath(PathConstructionRenderInfo.CLOSE, null); } PathPaintingRenderInfo renderInfo = new PathPaintingRenderInfo(operation, rule, gs()); ((ExtRenderListener) renderListener).renderPath(renderInfo); } /** * Modifies the current path. * * @param operation Indicates which path-construction operation should be performed. * @param segmentData Contains x, y components of points of a new segment being added to the current path. * E.g. x1 y1 x2 y2 x3 y3 etc. It's ignored for "close subpath" operarion (h). */ private void modifyPath(int operation, List segmentData) { PathConstructionRenderInfo renderInfo = new PathConstructionRenderInfo(operation, segmentData, gs().getCtm()); ((ExtRenderListener) renderListener).modifyPath(renderInfo); } private void clipPath(int rule) { ((ExtRenderListener) renderListener).clipPath(rule); } /** * Adjusts the text matrix for the specified adjustment value (see TJ operator in the PDF spec for information) * @param tj the text adjustment */ private void applyTextAdjust(float tj){ float adjustBy = -tj/1000f * gs().fontSize * gs().horizontalScaling; textMatrix = new Matrix(adjustBy, 0).multiply(textMatrix); } /** * Processes PDF syntax. * Note: If you re-use a given {@link PdfContentStreamProcessor}, you must call {@link PdfContentStreamProcessor#reset()} * @param contentBytes the bytes of a content stream * @param resources the resources that come with the content stream */ public void processContent(byte[] contentBytes, PdfDictionary resources){ this.resources.push(resources); try { PRTokeniser tokeniser = new PRTokeniser(new RandomAccessFileOrArray(new RandomAccessSourceFactory().createSource(contentBytes))); PdfContentParser ps = new PdfContentParser(tokeniser); ArrayList operands = new ArrayList(); while (ps.parse(operands).size() > 0){ PdfLiteral operator = (PdfLiteral)operands.get(operands.size()-1); if ("BI".equals(operator.toString())){ // we don't call invokeOperator for embedded images - this is one area of the PDF spec that is particularly nasty and inconsistent PdfDictionary colorSpaceDic = resources != null ? resources.getAsDict(PdfName.COLORSPACE) : null; handleInlineImage(InlineImageUtils.parseInlineImage(ps, colorSpaceDic), colorSpaceDic); } else { invokeOperator(operator, operands); } } } catch (Exception e) { throw new ExceptionConverter(e); } this.resources.pop(); } /** * Callback when an inline image is found. This requires special handling because inline images don't follow the standard operator syntax * @param info the inline image * @param colorSpaceDic the color space for the inline immage */ protected void handleInlineImage(InlineImageInfo info, PdfDictionary colorSpaceDic){ ImageRenderInfo renderInfo = ImageRenderInfo.createForEmbeddedImage(gs(), info, colorSpaceDic); renderListener.renderImage(renderInfo); } /** * Accessor method for the RenderListener object maintained in this class. * Necessary for implementing custom ContentOperator implementations. * @return the renderListener */ public RenderListener getRenderListener() { return renderListener; } /** * A resource dictionary that allows stack-like behavior to support resource dictionary inheritance */ private static class ResourceDictionary extends PdfDictionary{ private final List resourcesStack = new ArrayList(); public ResourceDictionary() { } public void push(PdfDictionary resources){ resourcesStack.add(resources); } public void pop(){ resourcesStack.remove(resourcesStack.size()-1); } @Override public PdfObject getDirectObject(PdfName key) { for (int i = resourcesStack.size() - 1; i >= 0; i--){ PdfDictionary subResource = resourcesStack.get(i); if (subResource != null){ PdfObject obj = subResource.getDirectObject(key); if (obj != null) return obj; } } return super.getDirectObject(key); // shouldn't be necessary, but just in case we've done something crazy } } /** * A content operator implementation (unregistered). */ private static class IgnoreOperatorContentOperator implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands){ // ignore the operator } } /** * A content operator implementation (TJ). */ private static class ShowTextArray implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfArray array = (PdfArray)operands.get(0); float tj = 0; for (Iterator i = array.listIterator(); i.hasNext(); ) { PdfObject entryObj = i.next(); if (entryObj instanceof PdfString){ processor.displayPdfString((PdfString)entryObj); tj = 0; } else { tj = ((PdfNumber)entryObj).floatValue(); processor.applyTextAdjust(tj); } } } } /** * A content operator implementation ("). */ private static class MoveNextLineAndShowTextWithSpacing implements ContentOperator{ private final SetTextWordSpacing setTextWordSpacing; private final SetTextCharacterSpacing setTextCharacterSpacing; private final MoveNextLineAndShowText moveNextLineAndShowText; public MoveNextLineAndShowTextWithSpacing(SetTextWordSpacing setTextWordSpacing, SetTextCharacterSpacing setTextCharacterSpacing, MoveNextLineAndShowText moveNextLineAndShowText) { this.setTextWordSpacing = setTextWordSpacing; this.setTextCharacterSpacing = setTextCharacterSpacing; this.moveNextLineAndShowText = moveNextLineAndShowText; } public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfNumber aw = (PdfNumber)operands.get(0); PdfNumber ac = (PdfNumber)operands.get(1); PdfString string = (PdfString)operands.get(2); ArrayList twOperands = new ArrayList(1); twOperands.add(0, aw); setTextWordSpacing.invoke(processor, null, twOperands); ArrayList tcOperands = new ArrayList(1); tcOperands.add(0, ac); setTextCharacterSpacing.invoke(processor, null, tcOperands); ArrayList tickOperands = new ArrayList(1); tickOperands.add(0, string); moveNextLineAndShowText.invoke(processor, null, tickOperands); } } /** * A content operator implementation ('). */ private static class MoveNextLineAndShowText implements ContentOperator{ private final TextMoveNextLine textMoveNextLine; private final ShowText showText; public MoveNextLineAndShowText(TextMoveNextLine textMoveNextLine, ShowText showText) { this.textMoveNextLine = textMoveNextLine; this.showText = showText; } public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { textMoveNextLine.invoke(processor, null, new ArrayList(0)); showText.invoke(processor, null, operands); } } /** * A content operator implementation (Tj). */ private static class ShowText implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfString string = (PdfString)operands.get(0); processor.displayPdfString(string); } } /** * A content operator implementation (T*). */ private static class TextMoveNextLine implements ContentOperator{ private final TextMoveStartNextLine moveStartNextLine; public TextMoveNextLine(TextMoveStartNextLine moveStartNextLine){ this.moveStartNextLine = moveStartNextLine; } public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { ArrayList tdoperands = new ArrayList(2); tdoperands.add(0, new PdfNumber(0)); tdoperands.add(1, new PdfNumber(-processor.gs().leading)); moveStartNextLine.invoke(processor, null, tdoperands); } } /** * A content operator implementation (Tm). */ private static class TextSetTextMatrix implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { float a = ((PdfNumber)operands.get(0)).floatValue(); float b = ((PdfNumber)operands.get(1)).floatValue(); float c = ((PdfNumber)operands.get(2)).floatValue(); float d = ((PdfNumber)operands.get(3)).floatValue(); float e = ((PdfNumber)operands.get(4)).floatValue(); float f = ((PdfNumber)operands.get(5)).floatValue(); processor.textLineMatrix = new Matrix(a, b, c, d, e, f); processor.textMatrix = processor.textLineMatrix; } } /** * A content operator implementation (TD). */ private static class TextMoveStartNextLineWithLeading implements ContentOperator{ private final TextMoveStartNextLine moveStartNextLine; private final SetTextLeading setTextLeading; public TextMoveStartNextLineWithLeading(TextMoveStartNextLine moveStartNextLine, SetTextLeading setTextLeading){ this.moveStartNextLine = moveStartNextLine; this.setTextLeading = setTextLeading; } public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { float ty = ((PdfNumber)operands.get(1)).floatValue(); ArrayList tlOperands = new ArrayList(1); tlOperands.add(0, new PdfNumber(-ty)); setTextLeading.invoke(processor, null, tlOperands); moveStartNextLine.invoke(processor, null, operands); } } /** * A content operator implementation (Td). */ private static class TextMoveStartNextLine implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { float tx = ((PdfNumber)operands.get(0)).floatValue(); float ty = ((PdfNumber)operands.get(1)).floatValue(); Matrix translationMatrix = new Matrix(tx, ty); processor.textMatrix = translationMatrix.multiply(processor.textLineMatrix); processor.textLineMatrix = processor.textMatrix; } } /** * A content operator implementation (Tf). */ private static class SetTextFont implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfName fontResourceName = (PdfName)operands.get(0); float size = ((PdfNumber)operands.get(1)).floatValue(); PdfDictionary fontsDictionary = processor.resources.getAsDict(PdfName.FONT); CMapAwareDocumentFont font; PdfObject fontObject = fontsDictionary.get(fontResourceName); if (fontObject instanceof PdfDictionary) font = processor.getFont((PdfDictionary)fontObject); else font = processor.getFont((PRIndirectReference)fontObject); processor.gs().font = font; processor.gs().fontSize = size; } } /** * A content operator implementation (Tr). */ private static class SetTextRenderMode implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfNumber render = (PdfNumber)operands.get(0); processor.gs().renderMode = render.intValue(); } } /** * A content operator implementation (Ts). */ private static class SetTextRise implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfNumber rise = (PdfNumber)operands.get(0); processor.gs().rise = rise.floatValue(); } } /** * A content operator implementation (TL). */ private static class SetTextLeading implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfNumber leading = (PdfNumber)operands.get(0); processor.gs().leading = leading.floatValue(); } } /** * A content operator implementation (Tz). */ private static class SetTextHorizontalScaling implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfNumber scale = (PdfNumber)operands.get(0); processor.gs().horizontalScaling = scale.floatValue()/100f; } } /** * A content operator implementation (Tc). */ private static class SetTextCharacterSpacing implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfNumber charSpace = (PdfNumber)operands.get(0); processor.gs().characterSpacing = charSpace.floatValue(); } } /** * A content operator implementation (Tw). */ private static class SetTextWordSpacing implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfNumber wordSpace = (PdfNumber)operands.get(0); processor.gs().wordSpacing = wordSpace.floatValue(); } } /** * A content operator implementation (gs). */ private static class ProcessGraphicsStateResource implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { PdfName dictionaryName = (PdfName)operands.get(0); PdfDictionary extGState = processor.resources.getAsDict(PdfName.EXTGSTATE); if (extGState == null) throw new IllegalArgumentException(MessageLocalization.getComposedMessage("resources.do.not.contain.extgstate.entry.unable.to.process.operator.1", operator)); PdfDictionary gsDic = extGState.getAsDict(dictionaryName); if (gsDic == null) throw new IllegalArgumentException(MessageLocalization.getComposedMessage("1.is.an.unknown.graphics.state.dictionary", dictionaryName)); // at this point, all we care about is the FONT entry in the GS dictionary PdfArray fontParameter = gsDic.getAsArray(PdfName.FONT); if (fontParameter != null){ CMapAwareDocumentFont font = processor.getFont((PRIndirectReference)fontParameter.getPdfObject(0)); float size = fontParameter.getAsNumber(1).floatValue(); processor.gs().font = font; processor.gs().fontSize = size; } } } /** * A content operator implementation (q). */ private static class PushGraphicsState implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { GraphicsState gs = processor.gsStack.peek(); GraphicsState copy = new GraphicsState(gs); processor.gsStack.push(copy); } } /** * A content operator implementation (cm). */ private static class ModifyCurrentTransformationMatrix implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { float a = ((PdfNumber)operands.get(0)).floatValue(); float b = ((PdfNumber)operands.get(1)).floatValue(); float c = ((PdfNumber)operands.get(2)).floatValue(); float d = ((PdfNumber)operands.get(3)).floatValue(); float e = ((PdfNumber)operands.get(4)).floatValue(); float f = ((PdfNumber)operands.get(5)).floatValue(); Matrix matrix = new Matrix(a, b, c, d, e, f); GraphicsState gs = processor.gsStack.peek(); gs.ctm = matrix.multiply(gs.ctm); } } /** * Gets a color based on a list of operands. */ private static BaseColor getColor(PdfName colorSpace, List operands) { if (PdfName.DEVICEGRAY.equals(colorSpace)) { return getColor(1, operands); } if (PdfName.DEVICERGB.equals(colorSpace)) { return getColor(3, operands); } if (PdfName.DEVICECMYK.equals(colorSpace)) { return getColor(4, operands); } return null; } /** * Gets a color based on a list of operands. */ private static BaseColor getColor(int nOperands, List operands) { float[] c = new float[nOperands]; for (int i = 0; i < nOperands; i++) { c[i] = ((PdfNumber)operands.get(i)).floatValue(); // fallbacks for illegal values: handled as Acrobat and Foxit do if (c[i] > 1f) { c[i] = 1f; } else if (c[i] < 0f) { c[i] = 0f; } } switch (nOperands) { case 1: return new GrayColor(c[0]); case 3: return new BaseColor(c[0], c[1], c[2]); case 4: return new CMYKColor(c[0], c[1], c[2], c[3]); } return null; } /** * A content operator implementation (g). */ private static class SetGrayFill implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gs().fillColor = getColor(1, operands); } } /** * A content operator implementation (G). */ private static class SetGrayStroke implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gs().strokeColor = getColor(1, operands); } } /** * A content operator implementation (rg). */ private static class SetRGBFill implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gs().fillColor = getColor(3, operands); } } /** * A content operator implementation (RG). */ private static class SetRGBStroke implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gs().strokeColor = getColor(3, operands); } } /** * A content operator implementation (rg). */ private static class SetCMYKFill implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gs().fillColor = getColor(4, operands); } } /** * A content operator implementation (RG). */ private static class SetCMYKStroke implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gs().strokeColor = getColor(4, operands); } } /** * A content operator implementation (cs). */ private static class SetColorSpaceFill implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gs().colorSpaceFill = (PdfName)operands.get(0); } } /** * A content operator implementation (CS). */ private static class SetColorSpaceStroke implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gs().colorSpaceStroke = (PdfName)operands.get(0); } } /** * A content operator implementation (sc / scn). */ private static class SetColorFill implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gs().fillColor = getColor(processor.gs().colorSpaceFill, operands); } } /** * A content operator implementation (SC / SCN). */ private static class SetColorStroke implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gs().strokeColor = getColor(processor.gs().colorSpaceStroke, operands); } } /** * A content operator implementation (Q). */ private static class PopGraphicsState implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.gsStack.pop(); } } /** * A content operator implementation (BT). */ private static class BeginText implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.textMatrix = new Matrix(); processor.textLineMatrix = processor.textMatrix; processor.beginText(); } } /** * A content operator implementation (ET). */ private static class EndText implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) { processor.textMatrix = null; processor.textLineMatrix = null; processor.endText(); } } /** * A content operator implementation (BMC). * @since 5.0.2 */ private static class BeginMarkedContent implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { processor.beginMarkedContent((PdfName)operands.get(0), new PdfDictionary()); } } /** * A content operator implementation (BDC). * @since 5.0.2 */ private static class BeginMarkedContentDictionary implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { PdfObject properties = operands.get(1); processor.beginMarkedContent((PdfName)operands.get(0), getPropertiesDictionary(properties, processor.resources)); } private PdfDictionary getPropertiesDictionary(PdfObject operand1, ResourceDictionary resources){ if (operand1.isDictionary()) return (PdfDictionary)operand1; PdfName dictionaryName = ((PdfName)operand1); return resources.getAsDict(dictionaryName); } } /** * A content operator implementation (EMC). * @since 5.0.2 */ private static class EndMarkedContent implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { processor.endMarkedContent(); } } /** * A content operator implementation (Do). */ private static class Do implements ContentOperator{ public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws IOException { PdfName xobjectName = (PdfName)operands.get(0); processor.displayXObject(xobjectName); } } /** * A content operator implementation (w). */ private static class SetLineWidth implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral oper, ArrayList operands) { float lineWidth = ((PdfNumber) operands.get(0)).floatValue(); processor.gs().setLineWidth(lineWidth); } } /** * A content operator implementation (J). */ private class SetLineCap implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral oper, ArrayList operands) { int lineCap = ((PdfNumber) operands.get(0)).intValue(); processor.gs().setLineCapStyle(lineCap); } } /** * A content operator implementation (j). */ private class SetLineJoin implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral oper, ArrayList operands) { int lineJoin = ((PdfNumber) operands.get(0)).intValue(); processor.gs().setLineJoinStyle(lineJoin); } } /** * A content operator implementation (M). */ private class SetMiterLimit implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral oper, ArrayList operands) { float miterLimit = ((PdfNumber) operands.get(0)).floatValue(); processor.gs().setMiterLimit(miterLimit); } } /** * A content operator implementation (d). */ private class SetLineDashPattern implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral oper, ArrayList operands) { LineDashPattern pattern = new LineDashPattern(((PdfArray) operands.get(0)), ((PdfNumber) operands.get(1)).floatValue()); processor.gs().setLineDashPattern(pattern); } } /** * A content operator implementation (m). * * @since 5.5.6 */ private static class MoveTo implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { float x = ((PdfNumber) operands.get(0)).floatValue(); float y = ((PdfNumber) operands.get(1)).floatValue(); processor.modifyPath(PathConstructionRenderInfo.MOVETO, Arrays.asList(x, y)); } } /** * A content operator implementation (l). * * @since 5.5.6 */ private static class LineTo implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { float x = ((PdfNumber) operands.get(0)).floatValue(); float y = ((PdfNumber) operands.get(1)).floatValue(); processor.modifyPath(PathConstructionRenderInfo.LINETO, Arrays.asList(x, y)); } } /** * A content operator implementation (c). * * @since 5.5.6 */ private static class Curve implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { float x1 = ((PdfNumber) operands.get(0)).floatValue(); float y1 = ((PdfNumber) operands.get(1)).floatValue(); float x2 = ((PdfNumber) operands.get(2)).floatValue(); float y2 = ((PdfNumber) operands.get(3)).floatValue(); float x3 = ((PdfNumber) operands.get(4)).floatValue(); float y3 = ((PdfNumber) operands.get(5)).floatValue(); processor.modifyPath(PathConstructionRenderInfo.CURVE_123, Arrays.asList(x1, y1, x2, y2, x3, y3)); } } /** * A content operator implementation (v). * * @since 5.5.6 */ private static class CurveFirstPointDuplicated implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { float x2 = ((PdfNumber) operands.get(0)).floatValue(); float y2 = ((PdfNumber) operands.get(1)).floatValue(); float x3 = ((PdfNumber) operands.get(2)).floatValue(); float y3 = ((PdfNumber) operands.get(3)).floatValue(); processor.modifyPath(PathConstructionRenderInfo.CURVE_23, Arrays.asList(x2, y2, x3, y3)); } } /** * A content operator implementation (y). * * @since 5.5.6 */ private static class CurveFourhPointDuplicated implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { float x1 = ((PdfNumber) operands.get(0)).floatValue(); float y1 = ((PdfNumber) operands.get(1)).floatValue(); float x3 = ((PdfNumber) operands.get(2)).floatValue(); float y3 = ((PdfNumber) operands.get(3)).floatValue(); processor.modifyPath(PathConstructionRenderInfo.CURVE_13, Arrays.asList(x1, y1, x3, y3)); } } /** * A content operator implementation (h). * * @since 5.5.6 */ private static class CloseSubpath implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { processor.modifyPath(PathConstructionRenderInfo.CLOSE, null); } } /** * A content operator implementation (re). * * @since 5.5.6 */ private static class Rectangle implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { float x = ((PdfNumber) operands.get(0)).floatValue(); float y = ((PdfNumber) operands.get(1)).floatValue(); float w = ((PdfNumber) operands.get(2)).floatValue(); float h = ((PdfNumber) operands.get(3)).floatValue(); processor.modifyPath(PathConstructionRenderInfo.RECT, Arrays.asList(x, y, w, h)); } } /** * A content operator implementation (S, s, f, F, f*, B, B*, b, b*). * * @since 5.5.6 */ private static class PaintPath implements ContentOperator { private int operation; private int rule; private boolean close; /** * Constructs PainPath object. * * @param operation One of the possible combinations of {@link com.itextpdf.text.pdf.parser.PathPaintingRenderInfo#STROKE} * and {@link com.itextpdf.text.pdf.parser.PathPaintingRenderInfo#FILL} values or * {@link com.itextpdf.text.pdf.parser.PathPaintingRenderInfo#NO_OP} * @param rule Either {@link com.itextpdf.text.pdf.parser.PathPaintingRenderInfo#NONZERO_WINDING_RULE} or * {@link com.itextpdf.text.pdf.parser.PathPaintingRenderInfo#EVEN_ODD_RULE} * In case it isn't applicable pass any value. * @param close Indicates whether the path should be closed or not. */ public PaintPath(int operation, int rule, boolean close) { this.operation = operation; this.rule = rule; this.close = close; } public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { processor.paintPath(operation, rule, close); // TODO: add logic for clipping path (before add it to the graphics state) } } /** * A content operator implementation (W, W*) * * @since 5.5.6 */ private static class ClipPath implements ContentOperator { private int rule; public ClipPath(int rule) { this.rule = rule; } public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { processor.clipPath(rule); } } /** * A content operator implementation (n). * * @since 5.5.6 */ private static class EndPath implements ContentOperator { public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList operands) throws Exception { processor.paintPath(PathPaintingRenderInfo.NO_OP, -1, false); } } /** * An XObject subtype handler for FORM */ private static class FormXObjectDoHandler implements XObjectDoHandler{ public void handleXObject(PdfContentStreamProcessor processor, PdfStream stream, PdfIndirectReference ref) { final PdfDictionary resources = stream.getAsDict(PdfName.RESOURCES); // we read the content bytes up here so if it fails we don't leave the graphics state stack corrupted // this is probably not necessary (if we fail on this, probably the entire content stream processing // operation should be rejected byte[] contentBytes; try { contentBytes = ContentByteUtils.getContentBytesFromContentObject(stream); } catch (IOException e1) { throw new ExceptionConverter(e1); } final PdfArray matrix = stream.getAsArray(PdfName.MATRIX); new PushGraphicsState().invoke(processor, null, null); if (matrix != null){ float a = matrix.getAsNumber(0).floatValue(); float b = matrix.getAsNumber(1).floatValue(); float c = matrix.getAsNumber(2).floatValue(); float d = matrix.getAsNumber(3).floatValue(); float e = matrix.getAsNumber(4).floatValue(); float f = matrix.getAsNumber(5).floatValue(); Matrix formMatrix = new Matrix(a, b, c, d, e, f); processor.gs().ctm = formMatrix.multiply(processor.gs().ctm); } processor.processContent(contentBytes, resources); new PopGraphicsState().invoke(processor, null, null); } } /** * An XObject subtype handler for IMAGE */ private static class ImageXObjectDoHandler implements XObjectDoHandler{ public void handleXObject(PdfContentStreamProcessor processor, PdfStream xobjectStream, PdfIndirectReference ref) { PdfDictionary colorSpaceDic = processor.resources.getAsDict(PdfName.COLORSPACE); ImageRenderInfo renderInfo = ImageRenderInfo.createForXObject(processor.gs(), ref, colorSpaceDic); processor.renderListener.renderImage(renderInfo); } } /** * An XObject subtype handler that does nothing */ private static class IgnoreXObjectDoHandler implements XObjectDoHandler{ public void handleXObject(PdfContentStreamProcessor processor, PdfStream xobjectStream, PdfIndirectReference ref) { // ignore XObject subtype } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy