net.padaf.preflight.utils.ContentStreamEngine.java Source code

Java tutorial

Introduction

Here is the source code for net.padaf.preflight.utils.ContentStreamEngine.java

Source

/*******************************************************************************
 * Copyright 2010 Atos Worldline SAS
 * 
 * Licensed by Atos Worldline SAS under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Atos Worldline SAS 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 net.padaf.preflight.utils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.padaf.preflight.DocumentHandler;
import net.padaf.preflight.ValidationException;
import net.padaf.preflight.ValidationResult.ValidationError;
import net.padaf.preflight.contentstream.ContentStreamException;
import net.padaf.preflight.contentstream.StubOperator;
import net.padaf.preflight.graphics.ICCProfileWrapper;
import net.padaf.preflight.graphics.color.ColorSpaceHelper;
import net.padaf.preflight.graphics.color.ColorSpaceHelperFactory;
import net.padaf.preflight.graphics.color.ColorSpaces;
import net.padaf.preflight.graphics.color.ColorSpaceHelperFactory.ColorSpaceRestriction;

import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSDocument;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.util.PDFOperator;
import org.apache.pdfbox.util.PDFStreamEngine;
import org.apache.pdfbox.util.operator.BeginText;
import org.apache.pdfbox.util.operator.Concatenate;
import org.apache.pdfbox.util.operator.EndText;
import org.apache.pdfbox.util.operator.GRestore;
import org.apache.pdfbox.util.operator.GSave;
import org.apache.pdfbox.util.operator.Invoke;
import org.apache.pdfbox.util.operator.MoveText;
import org.apache.pdfbox.util.operator.MoveTextSetLeading;
import org.apache.pdfbox.util.operator.NextLine;
import org.apache.pdfbox.util.operator.OperatorProcessor;
import org.apache.pdfbox.util.operator.SetCharSpacing;
import org.apache.pdfbox.util.operator.SetHorizontalTextScaling;
import org.apache.pdfbox.util.operator.SetLineCapStyle;
import org.apache.pdfbox.util.operator.SetLineDashPattern;
import org.apache.pdfbox.util.operator.SetLineJoinStyle;
import org.apache.pdfbox.util.operator.SetLineWidth;
import org.apache.pdfbox.util.operator.SetMatrix;
import org.apache.pdfbox.util.operator.SetNonStrokingCMYKColor;
import org.apache.pdfbox.util.operator.SetNonStrokingColor;
import org.apache.pdfbox.util.operator.SetNonStrokingColorSpace;
import org.apache.pdfbox.util.operator.SetNonStrokingRGBColor;
import org.apache.pdfbox.util.operator.SetStrokingCMYKColor;
import org.apache.pdfbox.util.operator.SetStrokingColor;
import org.apache.pdfbox.util.operator.SetStrokingColorSpace;
import org.apache.pdfbox.util.operator.SetStrokingRGBColor;
import org.apache.pdfbox.util.operator.SetTextFont;
import org.apache.pdfbox.util.operator.SetTextLeading;
import org.apache.pdfbox.util.operator.SetTextRenderingMode;
import org.apache.pdfbox.util.operator.SetTextRise;
import org.apache.pdfbox.util.operator.SetWordSpacing;
import static net.padaf.preflight.ValidationConstants.*;

/**
 * This class inherits from org.apache.pdfbox.util.PDFStreamEngine to allow the
 * validation of specific rules in ContentStream.
 */
public abstract class ContentStreamEngine extends PDFStreamEngine {

    protected DocumentHandler documentHandler = null;

    protected Map<String, OperatorProcessor> contentStreamEngineOperators = new HashMap<String, OperatorProcessor>();

    public ContentStreamEngine(DocumentHandler _handler) {
        this.documentHandler = _handler;

        // ---- Graphics operators
        registerOperatorProcessor("w", new SetLineWidth());
        registerOperatorProcessor("cm", new Concatenate());

        registerOperatorProcessor("CS", new SetStrokingColorSpace());
        registerOperatorProcessor("cs", new SetNonStrokingColorSpace());
        registerOperatorProcessor("d", new SetLineDashPattern());
        registerOperatorProcessor("Do", new Invoke());

        registerOperatorProcessor("j", new SetLineJoinStyle());
        registerOperatorProcessor("J", new SetLineCapStyle());
        registerOperatorProcessor("K", new SetStrokingCMYKColor());
        registerOperatorProcessor("k", new SetNonStrokingCMYKColor());

        registerOperatorProcessor("rg", new SetNonStrokingRGBColor());
        registerOperatorProcessor("RG", new SetStrokingRGBColor());

        registerOperatorProcessor("SC", new SetStrokingColor());
        registerOperatorProcessor("SCN", new SetStrokingColor());
        registerOperatorProcessor("sc", new SetNonStrokingColor());
        registerOperatorProcessor("scn", new SetNonStrokingColor());

        // ---- Graphics state
        registerOperatorProcessor("Q", new GRestore());
        registerOperatorProcessor("q", new GSave());

        // ---- Text operators
        registerOperatorProcessor("BT", new BeginText());
        registerOperatorProcessor("ET", new EndText());
        registerOperatorProcessor("Tf", new SetTextFont());
        registerOperatorProcessor("Tr", new SetTextRenderingMode());
        registerOperatorProcessor("Tm", new SetMatrix());
        registerOperatorProcessor("Td", new MoveText());
        registerOperatorProcessor("T*", new NextLine());
        registerOperatorProcessor("TD", new MoveTextSetLeading());
        registerOperatorProcessor("Tc", new SetCharSpacing());
        registerOperatorProcessor("TL", new SetTextLeading());
        registerOperatorProcessor("Ts", new SetTextRise());
        registerOperatorProcessor("Tw", new SetWordSpacing());
        registerOperatorProcessor("Tz", new SetHorizontalTextScaling());

        // ---- Do not use the PDFBox Operator, because of the PageDrawer class cast
        // Or because the Operator doesn't exist
        StubOperator so = new StubOperator();
        registerOperatorProcessor("l", so);
        registerOperatorProcessor("re", so);
        registerOperatorProcessor("c", so);
        registerOperatorProcessor("y", so);
        registerOperatorProcessor("v", so);
        registerOperatorProcessor("n", so);
        registerOperatorProcessor("BI", so);
        registerOperatorProcessor("EI", so);
        registerOperatorProcessor("m", so);
        registerOperatorProcessor("W*", so);
        registerOperatorProcessor("W", so);
        registerOperatorProcessor("h", so);

        registerOperatorProcessor("Tj", so);
        registerOperatorProcessor("TJ", so);
        registerOperatorProcessor("'", so);
        registerOperatorProcessor("\"", so);

        registerOperatorProcessor("b", so);
        registerOperatorProcessor("B", so);
        registerOperatorProcessor("b*", so);
        registerOperatorProcessor("B*", so);

        registerOperatorProcessor("BDC", so);
        registerOperatorProcessor("BMC", so);
        registerOperatorProcessor("DP", so);
        registerOperatorProcessor("EMC", so);

        registerOperatorProcessor("d0", so);
        registerOperatorProcessor("d1", so);

        registerOperatorProcessor("f", so);
        registerOperatorProcessor("F", so);
        registerOperatorProcessor("f*", so);

        registerOperatorProcessor("g", so);
        registerOperatorProcessor("G", so);

        registerOperatorProcessor("M", so);
        registerOperatorProcessor("MP", so);

        registerOperatorProcessor("gs", so);
        registerOperatorProcessor("h", so);
        registerOperatorProcessor("i", so);

        registerOperatorProcessor("ri", so);
        registerOperatorProcessor("s", so);
        registerOperatorProcessor("S", so);
        registerOperatorProcessor("sh", so);
    }

    public final void registerOperatorProcessor(String operator, OperatorProcessor op) {
        super.registerOperatorProcessor(operator, op);
        contentStreamEngineOperators.put(operator, op);
    }

    /**
     * Check operands of the "ri" operator. Operands must exist in the
     * RenderingIntent list. (net.awl.edoc.pdfa.validation.utils.RenderingIntents)
     * 
     * @param operator
     *          the "ri" operator
     * @param arguments
     *          the "ri" operands
     * @throws ContentStreamException
     *           ERROR_GRAPHIC_UNEXPECTED_VALUE_FOR_KEY if the operand is invalid
     */
    protected void validRenderingIntent(PDFOperator operator, List arguments) throws ContentStreamException {
        if ("ri".equals(operator.getOperation())) {
            if (!RenderingIntents.contains(arguments.get(0))) {
                throwContentStreamException("Unexpected value '" + arguments.get(0) + "' for ri operand. ",
                        ERROR_GRAPHIC_UNEXPECTED_VALUE_FOR_KEY);
            }
        }
    }

    /**
     * Valid the number of graphic states if the operator is the Save Graphic state operator ("q")
     * @param operator
     * @throws ContentStreamException
     */
    protected void validNumberOfGraphicStates(PDFOperator operator) throws ContentStreamException {
        if ("q".equals(operator.getOperation())) {
            int numberOfGraphicStates = this.getGraphicsStack().size();
            if (numberOfGraphicStates > MAX_GRAPHIC_STATES) {
                throwContentStreamException("Too many graphic states", ERROR_GRAPHIC_TOO_MANY_GRAPHIC_STATES);
            }
        }
    }

    /**
     * Throw a ContentStreamException if the LZW filter is used in a InlinedImage.
     * 
     * @param operator
     *          the InlinedImage object (BI to EI)
     * @throws ContentStreamException
     */
    protected void validImageFilter(PDFOperator operator) throws ContentStreamException {
        COSDictionary dict = operator.getImageParameters().getDictionary();
        // ---- Search a Filter declaration in the InlinedImage dictionary.
        // ---- The LZWDecode Filter is forbidden.
        String filter = dict.getNameAsString(STREAM_DICTIONARY_KEY_F);
        if (filter == null) {
            filter = dict.getNameAsString(STREAM_DICTIONARY_KEY_FILTER);
        }

        String errorCode = FilterHelper.isAuthorizedFilter(filter);
        if (errorCode != null) {
            // --- LZW is forbidden.
            if (ERROR_SYNTAX_STREAM_INVALID_FILTER.equals(errorCode)) {
                throwContentStreamException("LZW filter can't be used in a PDF/A File",
                        ERROR_SYNTAX_STREAM_INVALID_FILTER);
            } else {
                throwContentStreamException("This filter isn't defined in the PDF Reference Third Edition.",
                        ERROR_SYNTAX_STREAM_UNDEFINED_FILTER);
            }
        }
    }

    /**
     * This method validates if the ColorSpace used by the InlinedImage is
     * consistent with the color space defined in OutputIntent dictionaries.
     * 
     * @param operator
     *          the InlinedImage object (BI to EI)
     * @throws ContentStreamException
     */
    protected void validImageColorSpace(PDFOperator operator) throws ContentStreamException, IOException {
        COSDictionary dict = operator.getImageParameters().getDictionary();

        COSDocument doc = this.documentHandler.getDocument().getDocument();
        COSBase csInlinedBase = dict.getItem(COSName.getPDFName(STREAM_DICTIONARY_KEY_COLOR_SPACE));

        ColorSpaceHelper csHelper = null;
        if (csInlinedBase != null) {

            if (COSUtils.isString(csInlinedBase, doc)) {
                // ---- In InlinedImage only DeviceGray/RGB/CMYK and restricted Indexed
                // color spaces
                // are allowed.
                String colorSpace = COSUtils.getAsString(csInlinedBase, doc);
                ColorSpaces cs = null;

                try {
                    cs = ColorSpaces.valueOf(colorSpace);
                } catch (IllegalArgumentException e) {
                    // ---- The color space is unknown.
                    // ---- Try to access the resources dictionary, the color space can be
                    // a reference.
                    PDColorSpace pdCS = (PDColorSpace) this.getColorSpaces().get(colorSpace);
                    if (pdCS != null) {
                        cs = ColorSpaces.valueOf(pdCS.getName());
                        csHelper = ColorSpaceHelperFactory.getColorSpaceHelper(pdCS, documentHandler,
                                ColorSpaceRestriction.ONLY_DEVICE);
                    }
                }

                if (cs == null) {
                    throwContentStreamException("The ColorSpace is unknown",
                            ERROR_GRAPHIC_UNEXPECTED_VALUE_FOR_KEY);
                }
            }

            if (csHelper == null) {
                csHelper = ColorSpaceHelperFactory.getColorSpaceHelper(csInlinedBase, documentHandler,
                        ColorSpaceRestriction.ONLY_DEVICE);
            }
            List<ValidationError> errors = new ArrayList<ValidationError>();
            try {
                if (!csHelper.validate(errors)) {
                    ValidationError ve = errors.get(0);
                    throwContentStreamException(ve.getDetails(), ve.getErrorCode());
                }
            } catch (ValidationException e) {
                throw new IOException(e.getMessage());
            }
        }
    }

    /**
     * This method validates if the ColorOperator can be used with the color space
     * defined in OutputIntent dictionaries.
     * 
     * @param operator
     *          the color operator
     * @throws ContentStreamException
     */
    protected void checkColorOperators(String operation) throws ContentStreamException {
        if ("rg".equals(operation) || "RG".equals(operation)) {
            ICCProfileWrapper iccpw = documentHandler.getIccProfileWrapper();
            if (iccpw == null || !iccpw.isRGBColorSpace()) {
                throwContentStreamException("The operator \"" + operation + "\" can't be used with CMYK Profile",
                        ERROR_GRAPHIC_INVALID_COLOR_SPACE_RGB);
            }
        }

        if ("k".equals(operation) || "K".equals(operation)) {
            ICCProfileWrapper iccpw = documentHandler.getIccProfileWrapper();
            if (iccpw == null || !iccpw.isCMYKColorSpace()) {
                throwContentStreamException("The operator \"" + operation + "\" can't be used with RGB Profile",
                        ERROR_GRAPHIC_INVALID_COLOR_SPACE_CMYK);
            }
        }

        if ("g".equals(operation) || "G".equals(operation)) {
            ICCProfileWrapper iccpw = documentHandler.getIccProfileWrapper();
            if (iccpw == null) {
                // ---- Gray is possible with RGB and CMYK color space
                throwContentStreamException(
                        "The operator \"" + operation + "\" can't be used without Color Profile",
                        ERROR_GRAPHIC_INVALID_COLOR_SPACE_MISSING);
            }
        }

        if ("f".equals(operation) || "F".equals(operation) || "f*".equals(operation) || "B".equals(operation)
                || "B*".equals(operation) || "b".equals(operation) || "b*".equals(operation)) {
            ICCProfileWrapper iccpw = documentHandler.getIccProfileWrapper();
            if (iccpw == null) {
                // ---- The default fill color needs an OutputIntent
                throwContentStreamException(
                        "The operator \"" + operation + "\" can't be used without Color Profile",
                        ERROR_GRAPHIC_INVALID_COLOR_SPACE_MISSING);
            }
        }
    }

    /**
     * This method validates if the ColorSpace used as operand is consistent with
     * the color space defined in OutputIntent dictionaries.
     * 
     * @param operator
     * @param arguments
     * @throws IOException
     */
    protected void checkSetColorSpaceOperators(PDFOperator operator, List<?> arguments) throws IOException {
        if (!("CS".equals(operator.getOperation()) || "cs".equals(operator.getOperation()))) {
            return;
        }

        String colorSpaceName = null;
        if (arguments.get(0) instanceof String) {
            colorSpaceName = (String) arguments.get(0);
        } else if (arguments.get(0) instanceof COSString) {
            colorSpaceName = ((COSString) arguments.get(0)).toString();
        } else if (arguments.get(0) instanceof COSName) {
            colorSpaceName = ((COSName) arguments.get(0)).getName();
        } else {
            throwContentStreamException("The operand doesn't have the expected type",
                    ERROR_GRAPHIC_UNEXPECTED_VALUE_FOR_KEY);
        }

        ColorSpaceHelper csHelper = null;
        ColorSpaces cs = null;
        try {
            cs = ColorSpaces.valueOf(colorSpaceName);
        } catch (IllegalArgumentException e) {
            // ---- The color space is unknown.
            // ---- Try to access the resources dictionary, the color space can be a
            // reference.
            PDColorSpace pdCS = (PDColorSpace) this.getColorSpaces().get(colorSpaceName);
            if (pdCS != null) {
                cs = ColorSpaces.valueOf(pdCS.getName());
                csHelper = ColorSpaceHelperFactory.getColorSpaceHelper(pdCS, documentHandler,
                        ColorSpaceRestriction.NO_RESTRICTION);
            }
        }

        if (cs == null) {
            throwContentStreamException("The ColorSpace is unknown", ERROR_GRAPHIC_UNEXPECTED_VALUE_FOR_KEY);
        }

        if (csHelper == null) {
            csHelper = ColorSpaceHelperFactory.getColorSpaceHelper(COSName.getPDFName(colorSpaceName),
                    documentHandler, ColorSpaceRestriction.NO_RESTRICTION);
        }

        List<ValidationError> errors = new ArrayList<ValidationError>();
        try {
            if (!csHelper.validate(errors)) {
                ValidationError ve = errors.get(0);
                throwContentStreamException(ve.getDetails(), ve.getErrorCode());
            }
        } catch (ValidationException e) {
            //      throw new IOException(e.getMessage(), e); java 6
            throw new IOException(e.getMessage());
        }
    }

    /**
     * Build a ContentStreamException using the given parameters
     * 
     * @param msg
     *          exception details
     * @param errorCode
     *          the error code.
     * @throws ContentStreamException
     */
    protected void throwContentStreamException(String msg, String errorCode) throws ContentStreamException {
        ContentStreamException cex = new ContentStreamException(msg);
        cex.setValidationError(errorCode);
        throw cex;
    }
}