/*****************************************************************************
*
* 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.padaf.preflight.graphics.color;
import static org.apache.padaf.preflight.ValidationConstants.ERROR_GRAPHIC_INVALID_COLOR_SPACE;
import static org.apache.padaf.preflight.ValidationConstants.ERROR_GRAPHIC_INVALID_COLOR_SPACE_ALTERNATE;
import static org.apache.padaf.preflight.ValidationConstants.ERROR_GRAPHIC_INVALID_COLOR_SPACE_CMYK;
import static org.apache.padaf.preflight.ValidationConstants.ERROR_GRAPHIC_INVALID_COLOR_SPACE_INDEXED;
import static org.apache.padaf.preflight.ValidationConstants.ERROR_GRAPHIC_INVALID_COLOR_SPACE_MISSING;
import static org.apache.padaf.preflight.ValidationConstants.ERROR_GRAPHIC_INVALID_COLOR_SPACE_RGB;
import static org.apache.padaf.preflight.ValidationConstants.ERROR_GRAPHIC_INVALID_COLOR_SPACE_TOO_MANY_COMPONENTS_DEVICEN;
import static org.apache.padaf.preflight.ValidationConstants.ERROR_GRAPHIC_INVALID_PATTERN_COLOR_SPACE_FORBIDDEN;
import static org.apache.padaf.preflight.ValidationConstants.ERROR_GRAPHIC_INVALID_UNKNOWN_COLOR_SPACE;
import static org.apache.padaf.preflight.ValidationConstants.MAX_DEVICE_N_LIMIT;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.padaf.preflight.DocumentHandler;
import org.apache.padaf.preflight.ValidationException;
import org.apache.padaf.preflight.ValidationResult.ValidationError;
import org.apache.padaf.preflight.graphics.ICCProfileWrapper;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
import org.apache.pdfbox.pdmodel.graphics.color.PDIndexed;
import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
/**
* This class doesn't define restrictions on ColorSpace. It checks only the
* consistency of the Color space with the DestOutputIntent.
*/
public class StandardColorSpaceHelper implements ColorSpaceHelper {
/**
* The color space object to check, this object is used to instantiate the
* pdcs object.
*/
protected COSBase csObject = null;
/**
* The document handler which contains useful information to process the
* validation.
*/
protected DocumentHandler handler = null;
/**
* The ICCProfile contained in the DestOutputIntent
*/
protected ICCProfileWrapper iccpw = null;
/**
* High level object which represents the colors space to check.
*/
protected PDColorSpace pdcs = null;
StandardColorSpaceHelper(COSBase _csObject, DocumentHandler _handler) {
this.csObject = _csObject;
this.handler = _handler;
this.iccpw = this.handler.getIccProfileWrapper();
}
StandardColorSpaceHelper(PDColorSpace _cs, DocumentHandler _handler) {
this.handler = _handler;
this.pdcs = _cs;
this.iccpw = this.handler.getIccProfileWrapper();
}
/*
* (non-Javadoc)
*
* @see
* net.awl.edoc.pdfa.validation.graphics.color.ColorSpaceHelper#validate(java
* .util.List)
*/
public final boolean validate(List<ValidationError> result) throws ValidationException {
// ---- Create a PDFBox ColorSpace object
if (pdcs == null && csObject != null) {
try {
if (csObject instanceof COSObject) {
pdcs = PDColorSpaceFactory.createColorSpace(((COSObject)csObject).getObject());
} else {
pdcs = PDColorSpaceFactory.createColorSpace(csObject);
}
} catch (IOException e) {
throw new ValidationException("Unable to create a PDColorSpace : "
+ e.getMessage(), e);
}
}
if ( pdcs == null ) {
throw new ValidationException(
"Unable to create a PDColorSpace with the value null");
}
return processAllColorSpace(pdcs, result);
}
/**
* Method called by the validate method. According to the ColorSpace, a
* specific ColorSpace method is called.
*
* @param pdcs
* the color space object to check.
* @param result
* the list of error to update if the validation fails.
* @return true if the validation succeed, false otherwise.
*/
protected final boolean processAllColorSpace(PDColorSpace pdcs,
List<ValidationError> result) {
ColorSpaces cs = ColorSpaces.valueOf(pdcs.getName());
switch (cs) {
case DeviceRGB:
case DeviceRGB_SHORT:
return processRGBColorSpace(result);
case DeviceCMYK:
case DeviceCMYK_SHORT:
return processCYMKColorSpace(result);
case CalRGB:
case CalGray:
case Lab:
return processCalibratedColorSpace(result);
case DeviceGray:
case DeviceGray_SHORT:
return processGrayColorSpace(result);
case ICCBased:
return processICCBasedColorSpace(pdcs, result);
case DeviceN:
return processDeviceNColorSpace(pdcs, result);
case Indexed:
case Indexed_SHORT:
return processIndexedColorSpace(pdcs, result);
case Separation:
return processSeparationColorSpace(pdcs, result);
case Pattern:
return processPatternColorSpace(result);
default:
result
.add(new ValidationError(ERROR_GRAPHIC_INVALID_UNKNOWN_COLOR_SPACE, cs.getLabel() + " is unknown as ColorSpace"));
return false;
}
}
/**
* Method called by the processAllColorSpace if the ColorSpace to check is
* DeviceRGB.
*
* @param result
* the list of error to update if the validation fails.
* @return true if the color space is valid, false otherwise.
*/
protected boolean processRGBColorSpace(List<ValidationError> result) {
// ---- ICCProfile must contain a RGB Color Space
if (iccpw == null || !iccpw.isRGBColorSpace()) {
result.add(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE_RGB, "DestOutputProfile is missing"));
return false;
}
return true;
}
/**
* Method called by the processAllColorSpace if the ColorSpace to check is
* DeviceCYMK.
*
* @param result
* the list of error to update if the validation fails.
* @return true if the color space is valid, false otherwise.
*/
protected boolean processCYMKColorSpace(List<ValidationError> result) {
// ---- ICCProfile must contain a CYMK Color Space
if (iccpw == null || !iccpw.isCMYKColorSpace()) {
result.add(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE_CMYK, "DestOutputProfile is missing"));
return false;
}
return true;
}
/**
* Method called by the processAllColorSpace if the ColorSpace to check is a
* Pattern.
*
* @param result
* the list of error to update if the validation fails.
* @return true if the color space is valid, false otherwise.
*/
protected boolean processPatternColorSpace(List<ValidationError> result) {
if (iccpw == null) {
result
.add(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE_MISSING, "DestOutputProfile is missing"));
return false;
}
return true;
}
/**
* Method called by the processAllColorSpace if the ColorSpace to check is
* DeviceGray.
*
* @param result
* the list of error to update if the validation fails.
* @return true if the color space is valid, false otherwise.
*/
protected boolean processGrayColorSpace(List<ValidationError> result) {
// ---- OutputIntent is mandatory
if (iccpw == null) {
result
.add(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE_MISSING, "DestOutputProfile is missing"));
return false;
}
return true;
}
/**
* Method called by the processAllColorSpace if the ColorSpace to check is a
* Clibrated Color (CalGary, CalRGB, Lab).
*
* @param result
* the list of error to update if the validation fails.
* @return true if the color space is valid, false otherwise.
*/
protected boolean processCalibratedColorSpace(List<ValidationError> result) {
// ---- OutputIntent isn't mandatory
return true;
}
/**
* Method called by the processAllColorSpace if the ColorSpace to check is a
* ICCBased color space. Because this kind of ColorSpace can have alternate
* color space, the processAllColorSpace is called to check this alternate
* color space. (Pattern is forbidden as Alternate Color Space)
*
* @param pdcs
* the color space object to check.
* @param result
* the list of error to update if the validation fails.
* @return true if the color space is valid, false otherwise.
*/
protected boolean processICCBasedColorSpace(PDColorSpace pdcs,
List<ValidationError> result) {
PDICCBased iccBased = (PDICCBased) pdcs;
try {
if (iccpw == null) {
result.add(new ValidationError(
ERROR_GRAPHIC_INVALID_COLOR_SPACE_MISSING, "DestOutputProfile is missing"));
return false;
}
List<PDColorSpace> altCs = iccBased.getAlternateColorSpaces();
for (PDColorSpace altpdcs : altCs) {
if (altpdcs != null) {
ColorSpaces altCsId = ColorSpaces.valueOf(altpdcs.getName());
if (altCsId == ColorSpaces.Pattern) {
result.add(new ValidationError(
ERROR_GRAPHIC_INVALID_PATTERN_COLOR_SPACE_FORBIDDEN, "Pattern is forbidden as AlternateColorSpace of a ICCBased"));
return false;
}
if (!processAllColorSpace(altpdcs, result)) {
return false;
}
}
}
} catch (IOException e) {
result.add(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE, "Unable to read ICCBase color space : " + e.getMessage()));
return false;
}
return true;
}
/**
* Method called by the processAllColorSpace if the ColorSpace to check is
* DeviceN. Because this kind of ColorSpace can have alternate color space,
* the processAllColorSpace is called to check this alternate color space.
* (There are no restrictions on the Alternate Color space)
*
* @param pdcs
* the color space object to check.
* @param result
* the list of error to update if the validation fails.
* @return true if the color space is valid, false otherwise.
*/
protected boolean processDeviceNColorSpace(PDColorSpace pdcs,
List<ValidationError> result) {
PDDeviceN deviceN = (PDDeviceN) pdcs;
try {
if (iccpw == null) {
result.add(new ValidationError(
ERROR_GRAPHIC_INVALID_COLOR_SPACE_MISSING, "DestOutputProfile is missing"));
return false;
}
PDColorSpace altColor = deviceN.getAlternateColorSpace();
boolean res = true;
if (altColor != null) {
res = processAllColorSpace(altColor, result);
}
Map colorants = deviceN.getAttributes().getColorants();
int numberOfColorants = 0;
if (colorants != null) {
numberOfColorants = colorants.size();
for (Object col : colorants.values()) {
if (col != null) {
res = res && processAllColorSpace((PDColorSpace) col, result);
}
}
}
int numberOfComponents = deviceN.getNumberOfComponents();
if (numberOfColorants > MAX_DEVICE_N_LIMIT || numberOfComponents > MAX_DEVICE_N_LIMIT ) {
result.add(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE_TOO_MANY_COMPONENTS_DEVICEN, "DeviceN has too many tint components or colorants"));
res = false;
}
return res;
} catch (IOException e) {
result.add(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE, "Unable to read DeviceN color space : " + e.getMessage()));
return false;
}
}
/**
* Method called by the processAllColorSpace if the ColorSpace to check is
* Indexed. Because this kind of ColorSpace can have a Base color space, the
* processAllColorSpace is called to check this base color space. (Indexed and
* Pattern can't be a Base color space)
*
* @param pdcs
* the color space object to check.
* @param result
* the list of error to update if the validation fails.
* @return true if the color space is valid, false otherwise.
*/
protected boolean processIndexedColorSpace(PDColorSpace pdcs,
List<ValidationError> result) {
PDIndexed indexed = (PDIndexed) pdcs;
try {
if (iccpw == null) {
result.add(new ValidationError(
ERROR_GRAPHIC_INVALID_COLOR_SPACE_MISSING, "DestOutputProfile is missing"));
return false;
}
PDColorSpace based = indexed.getBaseColorSpace();
ColorSpaces cs = ColorSpaces.valueOf(based.getName());
if (cs == ColorSpaces.Indexed || cs == ColorSpaces.Indexed_SHORT) {
result.add(new ValidationError(
ERROR_GRAPHIC_INVALID_COLOR_SPACE_INDEXED,"Indexed color space can't be used as Base color space"));
return false;
}
if (cs == ColorSpaces.Pattern) {
result.add(new ValidationError(
ERROR_GRAPHIC_INVALID_COLOR_SPACE_INDEXED,"Pattern color space can't be used as Base color space"));
return false;
}
return processAllColorSpace(based, result);
} catch (IOException e) {
result.add(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE, "Unable to read Indexed color space : " + e.getMessage()));
return false;
}
}
/**
* Method called by the processAllColorSpace if the ColorSpace to check is
* Separation. Because this kind of ColorSpace can have an alternate color
* space, the processAllColorSpace is called to check this alternate color
* space. (Indexed, Separation, DeviceN and Pattern can't be a Base color
* space)
*
* @param pdcs
* the color space object to check.
* @param result
* the list of error to update if the validation fails.
* @return true if the color space is valid, false otherwise.
*/
protected boolean processSeparationColorSpace(PDColorSpace pdcs,
List<ValidationError> result) {
PDSeparation separation = (PDSeparation) pdcs;
try {
if (iccpw == null) {
result.add(new ValidationError(
ERROR_GRAPHIC_INVALID_COLOR_SPACE_MISSING,"DestOutputProfile is missing"));
return false;
}
PDColorSpace altCol = separation.getAlternateColorSpace();
if (altCol != null) {
ColorSpaces acs = ColorSpaces.valueOf(altCol.getName());
switch (acs) {
case Separation:
case DeviceN:
case Pattern:
case Indexed:
case Indexed_SHORT:
result.add(new ValidationError(
ERROR_GRAPHIC_INVALID_COLOR_SPACE_ALTERNATE, acs.getLabel() + " color space can't be used as alternate color space"));
return false;
default:
return processAllColorSpace(altCol, result);
}
}
return true;
} catch (IOException e) {
result.add(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE, "Unable to read Separation color space : " + e.getMessage()));
return false;
}
}
}