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

com.itextpdf.pdfa.checker.PdfA2Checker Maven / Gradle / Ivy

/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2024 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    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 .
 */
package com.itextpdf.pdfa.checker;

import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.io.colors.IccProfile;
import com.itextpdf.io.font.FontEncoding;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.io.image.Jpeg2000ImageData;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.colors.PatternColor;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfTrueTypeFont;
import com.itextpdf.kernel.font.PdfType3Font;
import com.itextpdf.kernel.font.Type3Glyph;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfBoolean;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfNumber;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfStream;
import com.itextpdf.kernel.pdf.PdfString;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import com.itextpdf.kernel.pdf.canvas.CanvasGraphicsState;
import com.itextpdf.kernel.pdf.colorspace.PdfCieBasedCs;
import com.itextpdf.kernel.pdf.colorspace.PdfColorSpace;
import com.itextpdf.kernel.pdf.colorspace.PdfDeviceCs;
import com.itextpdf.kernel.pdf.colorspace.PdfPattern;
import com.itextpdf.kernel.pdf.colorspace.PdfSpecialCs;
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
import com.itextpdf.pdfa.exceptions.PdfAConformanceException;
import com.itextpdf.pdfa.exceptions.PdfaExceptionMessageConstant;
import com.itextpdf.pdfa.logs.PdfAConformanceLogMessageConstant;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * PdfA2Checker defines the requirements of the PDF/A-2 standard and contains a
 * number of methods that override the implementations of its superclass
 * {@link PdfA1Checker}.
 * 

* The specification implemented by this class is ISO 19005-2 */ public class PdfA2Checker extends PdfA1Checker { protected static final Set forbiddenAnnotations = Collections .unmodifiableSet(new HashSet<>(Arrays.asList( PdfName._3D, PdfName.Sound, PdfName.Screen, PdfName.Movie))); protected static final Set apLessAnnotations = Collections.unmodifiableSet( new HashSet<>(Arrays.asList(PdfName.Popup, PdfName.Link))); protected static final Set forbiddenActions = Collections .unmodifiableSet(new HashSet<>(Arrays.asList( PdfName.Launch, PdfName.Sound, PdfName.Movie, PdfName.ResetForm, PdfName.ImportData, PdfName.JavaScript, PdfName.Hide, PdfName.SetOCGState, PdfName.Rendition, PdfName.Trans, PdfName.GoTo3DView))); protected static final Set allowedBlendModes = Collections .unmodifiableSet(new HashSet<>(Arrays.asList( PdfName.Normal, PdfName.Compatible, PdfName.Multiply, PdfName.Screen, PdfName.Overlay, PdfName.Darken, PdfName.Lighten, PdfName.ColorDodge, PdfName.ColorBurn, PdfName.HardLight, PdfName.SoftLight, PdfName.Difference, PdfName.Exclusion, PdfName.Hue, PdfName.Saturation, PdfName.Color, PdfName.Luminosity))); protected static final Set allowedFilters = Collections .unmodifiableSet(new HashSet<>(Arrays.asList( PdfName.ASCIIHexDecode, PdfName.ASCII85Decode, PdfName.RunLengthDecode, PdfName.FlateDecode, PdfName.CCITTFaxDecode, PdfName.JBIG2Decode, PdfName.DCTDecode, PdfName.JPXDecode, PdfName.Crypt))); protected static final Set allowedInlineImageFilters = Collections .unmodifiableSet(new HashSet<>(Arrays.asList( PdfName.DeviceGray, PdfName.DeviceRGB, PdfName.DeviceCMYK, PdfName.Indexed, PdfName.ASCIIHexDecode, PdfName.ASCII85Decode, PdfName.FlateDecode, PdfName.RunLengthDecode, PdfName.CCITTFaxDecode, PdfName.DCTDecode, PdfName.G, PdfName.RGB, PdfName.CMYK, PdfName.I, PdfName.AHx, PdfName.A85, PdfName.Fl, PdfName.RL, PdfName.CCF, PdfName.DCT))); protected Set transparencyObjects = new HashSet<>(); static final int MAX_PAGE_SIZE = 14400; static final int MIN_PAGE_SIZE = 3; private static final int MAX_NUMBER_OF_DEVICEN_COLOR_COMPONENTS = 32; private static final Logger logger = LoggerFactory.getLogger(PdfAChecker.class); private static final String TRANSPARENCY_ERROR_MESSAGE = PdfAConformanceException.THE_DOCUMENT_DOES_NOT_CONTAIN_A_PDFA_OUTPUTINTENT_BUT_PAGE_CONTAINS_TRANSPARENCY_AND_DOES_NOT_CONTAIN_BLENDING_COLOR_SPACE; private boolean currentFillCsIsIccBasedCMYK = false; private boolean currentStrokeCsIsIccBasedCMYK = false; private Map separationColorSpaces = new HashMap<>(); /** * Creates a PdfA2Checker with the required conformance level * * @param conformanceLevel the required conformance level, a or * u or b */ public PdfA2Checker(PdfAConformanceLevel conformanceLevel) { super(conformanceLevel); } @Override public void checkInlineImage(PdfStream inlineImage, PdfDictionary currentColorSpaces) { PdfObject filter = inlineImage.get(PdfName.Filter); if (filter instanceof PdfName) { if (filter.equals(PdfName.LZWDecode)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.LZWDECODE_FILTER_IS_NOT_PERMITTED); } if (filter.equals(PdfName.Crypt)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.CRYPT_FILTER_IS_NOT_PERMITTED_INLINE_IMAGE); } if (!allowedInlineImageFilters.contains((PdfName) filter)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.INVALID_INLINE_IMAGE_FILTER_USAGE); } } else if (filter instanceof PdfArray) { for (int i = 0; i < ((PdfArray) filter).size(); i++) { PdfName f = ((PdfArray) filter).getAsName(i); if (f.equals(PdfName.LZWDecode)) throw new PdfAConformanceException(PdfaExceptionMessageConstant.LZWDECODE_FILTER_IS_NOT_PERMITTED); if (f.equals(PdfName.Crypt)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.CRYPT_FILTER_IS_NOT_PERMITTED_INLINE_IMAGE); } if (!allowedInlineImageFilters.contains((PdfName) f)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.INVALID_INLINE_IMAGE_FILTER_USAGE); } } } checkImage(inlineImage, currentColorSpaces); } /** * {@inheritDoc} */ @Override @Deprecated public void checkColor(Color color, PdfDictionary currentColorSpaces, Boolean fill, PdfStream contentStream) { checkColor(null, color, currentColorSpaces, fill, contentStream); } /** * {@inheritDoc} */ @Override public void checkColor(CanvasGraphicsState gState, Color color, PdfDictionary currentColorSpaces, Boolean fill, PdfStream contentStream) { if (color instanceof PatternColor) { PdfPattern pattern = ((PatternColor) color).getPattern(); if (pattern instanceof PdfPattern.Shading) { PdfDictionary shadingDictionary = ((PdfPattern.Shading) pattern).getShading(); PdfObject colorSpace = shadingDictionary.get(PdfName.ColorSpace); checkColorSpace(PdfColorSpace.makeColorSpace(colorSpace), contentStream, currentColorSpaces, true, true); if (gState == null) { //Note that this method of getting ExtGState won't work for PatternType = 1, and won't always work //for PatternType = 2, since it's an optional parameter there, so this code is just a fallback for the //user input. final PdfDictionary extGStateDict = ((PdfDictionary) pattern.getPdfObject()).getAsDictionary(PdfName.ExtGState); gState = new UpdateCanvasGraphicsState(extGStateDict); } checkExtGState(gState, contentStream); } else if (pattern instanceof PdfPattern.Tiling) { checkContentStream((PdfStream) pattern.getPdfObject()); } } super.checkColor(gState, color, currentColorSpaces, fill, contentStream); } /** * {@inheritDoc} */ @Override public void checkColorSpace(PdfColorSpace colorSpace, PdfObject pdfObject, PdfDictionary currentColorSpaces, boolean checkAlternate, Boolean fill) { if (fill != null) { if ((boolean) fill) { currentFillCsIsIccBasedCMYK = false; } else { currentStrokeCsIsIccBasedCMYK = false; } } if (colorSpace instanceof PdfSpecialCs.Separation) { PdfSpecialCs.Separation separation = (PdfSpecialCs.Separation) colorSpace; checkSeparationCS((PdfArray) separation.getPdfObject()); if (checkAlternate) { checkColorSpace(separation.getBaseCs(), pdfObject, currentColorSpaces, false, fill); } } else if (colorSpace instanceof PdfSpecialCs.DeviceN) { PdfSpecialCs.DeviceN deviceN = (PdfSpecialCs.DeviceN) colorSpace; checkNumberOfDeviceNComponents(deviceN); //According to spec DeviceN is an array of size 4 or 5 depending on whether it contains attributes or not (see ISO 32000-2:2020 8.6.6.5) //for the pdf/a-2 it should look as follows: [/DeviceN names alternateSpace tintTransform attributes], since colourants dictionary is // located in attributes and according to pdf/a-2 spec it should always be present. if (((PdfArray) deviceN.getPdfObject()).size() != 5) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.COLORANTS_DICTIONARY_SHALL_NOT_BE_EMPTY_IN_DEVICE_N_COLORSPACE); } PdfDictionary attributes = ((PdfArray) deviceN.getPdfObject()).getAsDictionary(4); PdfDictionary colorants = attributes.getAsDictionary(PdfName.Colorants); if (colorants != null && !colorants.isEmpty()) { for (Map.Entry entry : colorants.entrySet()) { PdfArray separation = (PdfArray) entry.getValue(); checkSeparationInsideDeviceN(separation, ((PdfArray) deviceN.getPdfObject()).get(2), ((PdfArray) deviceN.getPdfObject()).get(3)); } } else { throw new PdfAConformanceException(PdfaExceptionMessageConstant.COLORANTS_DICTIONARY_SHALL_NOT_BE_EMPTY_IN_DEVICE_N_COLORSPACE); } if (checkAlternate) { checkColorSpace(deviceN.getBaseCs(), pdfObject, currentColorSpaces, false, fill); } } else if (colorSpace instanceof PdfSpecialCs.Indexed) { if (checkAlternate) { checkColorSpace(((PdfSpecialCs.Indexed) colorSpace).getBaseCs(), pdfObject, currentColorSpaces, true, fill); } } else if (colorSpace instanceof PdfSpecialCs.UncoloredTilingPattern) { if (checkAlternate) { checkColorSpace(((PdfSpecialCs.UncoloredTilingPattern) colorSpace).getUnderlyingColorSpace(), pdfObject, currentColorSpaces, true, fill); } } else { if (colorSpace instanceof PdfDeviceCs.Rgb) { if (!checkDefaultCS(pdfObject, currentColorSpaces, fill, PdfName.DefaultRGB, 3)) { rgbUsedObjects.add(pdfObject); } } else if (colorSpace instanceof PdfDeviceCs.Cmyk) { if (!checkDefaultCS(pdfObject, currentColorSpaces, fill, PdfName.DefaultCMYK, 4)) { cmykUsedObjects.add(pdfObject); } } else if (colorSpace instanceof PdfDeviceCs.Gray) { if (!checkDefaultCS(pdfObject, currentColorSpaces, fill, PdfName.DefaultGray, 1)) { grayUsedObjects.add(pdfObject); } } } if (fill != null && colorSpace instanceof PdfCieBasedCs.IccBased) { byte[] iccBytes = ((PdfArray) colorSpace.getPdfObject()).getAsStream(1).getBytes(); if (ICC_COLOR_SPACE_CMYK.equals(IccProfile.getIccColorSpaceName(iccBytes))) { if ((boolean) fill) { currentFillCsIsIccBasedCMYK = true; } else { currentStrokeCsIsIccBasedCMYK = true; } } } } @Override public void checkExtGState(CanvasGraphicsState extGState, PdfStream contentStream) { if (Integer.valueOf(1).equals(extGState.getOverprintMode())) { if (extGState.getFillOverprint() && currentFillCsIsIccBasedCMYK) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.OVERPRINT_MODE_SHALL_NOT_BE_ONE_WHEN_AN_ICCBASED_CMYK_COLOUR_SPACE_IS_USED_AND_WHEN_OVERPRINTING_IS_SET_TO_TRUE); } if (extGState.getStrokeOverprint() && currentStrokeCsIsIccBasedCMYK) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.OVERPRINT_MODE_SHALL_NOT_BE_ONE_WHEN_AN_ICCBASED_CMYK_COLOUR_SPACE_IS_USED_AND_WHEN_OVERPRINTING_IS_SET_TO_TRUE); } } if (extGState.getTransferFunction() != null) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.AN_EXTGSTATE_DICTIONARY_SHALL_NOT_CONTAIN_THE_TR_KEY); } if (extGState.getHTP() != null) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.AN_EXTGSTATE_DICTIONARY_SHALL_NOT_CONTAIN_THE_HTP_KEY); } PdfObject transferFunction2 = extGState.getTransferFunction2(); if (transferFunction2 != null && !PdfName.Default.equals(transferFunction2)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.AN_EXTGSTATE_DICTIONARY_SHALL_NOT_CONTAIN_THE_TR_2_KEY_WITH_A_VALUE_OTHER_THAN_DEFAULT); } if (extGState.getHalftone() instanceof PdfDictionary) { PdfDictionary halftoneDict = (PdfDictionary) extGState.getHalftone(); Integer halftoneType = halftoneDict.getAsInt(PdfName.HalftoneType); if (halftoneType != 1 && halftoneType != 5) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.ALL_HALFTONES_SHALL_HAVE_HALFTONETYPE_1_OR_5); } if (halftoneDict.containsKey(PdfName.HalftoneName)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.HALFTONES_SHALL_NOT_CONTAIN_HALFTONENAME); } } checkRenderingIntent(extGState.getRenderingIntent()); if (extGState.getSoftMask() != null && extGState.getSoftMask() instanceof PdfDictionary) { transparencyObjects.add(contentStream); } if (extGState.getStrokeOpacity() < 1) { transparencyObjects.add(contentStream); } if (extGState.getFillOpacity() < 1) { transparencyObjects.add(contentStream); } PdfObject bm = extGState.getBlendMode(); if (bm != null) { if (!PdfName.Normal.equals(bm)) { transparencyObjects.add(contentStream); } if (bm instanceof PdfArray) { for (PdfObject b : (PdfArray) bm) { checkBlendMode((PdfName) b); } } else if (bm instanceof PdfName) { checkBlendMode((PdfName) bm); } } } @Override public void checkSignature(PdfDictionary signatureDict) { if (isAlreadyChecked(signatureDict)) { return; } PdfArray references = signatureDict.getAsArray(PdfName.Reference); if (references != null) { for (int i = 0; i < references.size(); i++) { PdfDictionary referenceDict = references.getAsDictionary(i); if (referenceDict.containsKey(PdfName.DigestLocation) || referenceDict.containsKey(PdfName.DigestMethod) || referenceDict.containsKey(PdfName.DigestValue)) { throw new PdfAConformanceException( PdfaExceptionMessageConstant.SIGNATURE_REFERENCES_DICTIONARY_SHALL_NOT_CONTAIN_DIGESTLOCATION_DIGESTMETHOD_DIGESTVALUE); } } } } protected void checkNumberOfDeviceNComponents(PdfSpecialCs.DeviceN deviceN) { if (deviceN.getNumberOfComponents() > MAX_NUMBER_OF_DEVICEN_COLOR_COMPONENTS) { throw new PdfAConformanceException(PdfaExceptionMessageConstant. THE_NUMBER_OF_COLOR_COMPONENTS_IN_DEVICE_N_COLORSPACE_SHOULD_NOT_EXCEED, MAX_NUMBER_OF_DEVICEN_COLOR_COMPONENTS); } } @Override protected void checkNonSymbolicTrueTypeFont(PdfTrueTypeFont trueTypeFont) { String encoding = trueTypeFont.getFontEncoding().getBaseEncoding(); // non-symbolic true type font will always has an encoding entry in font dictionary in itext if (!PdfEncodings.WINANSI.equals(encoding) && !PdfEncodings.MACROMAN.equals(encoding)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.ALL_NON_SYMBOLIC_TRUE_TYPE_FONT_SHALL_SPECIFY_MAC_ROMAN_ENCODING_OR_WIN_ANSI_ENCODING, trueTypeFont); } // if font has differences array, itext ensures that all names in it are listed in AdobeGlyphList } @Override protected double getMaxRealValue() { return Float.MAX_VALUE; } @Override protected int getMaxStringLength() { return 32767; } @Override protected void checkPdfArray(PdfArray array) { // currently no validation for arrays is implemented for PDF/A 2 } @Override protected void checkPdfDictionary(PdfDictionary dictionary) { // currently no validation for dictionaries is implemented for PDF/A 2 } /** * {@inheritDoc} */ @Override protected void checkAnnotation(PdfDictionary annotDic) { PdfName subtype = annotDic.getAsName(PdfName.Subtype); if (subtype == null) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.ANNOTATION_TYPE_0_IS_NOT_PERMITTED).setMessageParams("null"); } if (getForbiddenAnnotations().contains(subtype)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.ANNOTATION_TYPE_0_IS_NOT_PERMITTED).setMessageParams(subtype.getValue()); } if (!subtype.equals(PdfName.Popup)) { PdfNumber f = annotDic.getAsNumber(PdfName.F); if (f == null) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.AN_ANNOTATION_DICTIONARY_SHALL_CONTAIN_THE_F_KEY); } int flags = f.intValue(); if (!checkFlag(flags, PdfAnnotation.PRINT) || checkFlag(flags, PdfAnnotation.HIDDEN) || checkFlag(flags, PdfAnnotation.INVISIBLE) || checkFlag(flags, PdfAnnotation.NO_VIEW) || checkFlag(flags, PdfAnnotation.TOGGLE_NO_VIEW)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.THE_F_KEYS_PRINT_FLAG_BIT_SHALL_BE_SET_TO_1_AND_ITS_HIDDEN_INVISIBLE_NOVIEW_AND_TOGGLENOVIEW_FLAG_BITS_SHALL_BE_SET_TO_0); } if (subtype.equals(PdfName.Text)) { if (!checkFlag(flags, PdfAnnotation.NO_ZOOM) || !checkFlag(flags, PdfAnnotation.NO_ROTATE)) { throw new PdfAConformanceException(PdfAConformanceLogMessageConstant.TEXT_ANNOTATIONS_SHOULD_SET_THE_NOZOOM_AND_NOROTATE_FLAG_BITS_OF_THE_F_KEY_TO_1); } } } checkAnnotationAgainstActions(annotDic); if (checkStructure(conformanceLevel)) { if (contentAnnotations.contains(subtype) && !annotDic.containsKey(PdfName.Contents)) { logger.warn(MessageFormatUtil.format( PdfAConformanceLogMessageConstant.ANNOTATION_OF_TYPE_0_SHOULD_HAVE_CONTENTS_KEY, subtype.getValue())); } } PdfDictionary ap = annotDic.getAsDictionary(PdfName.AP); if (ap != null) { if (ap.containsKey(PdfName.R) || ap.containsKey(PdfName.D)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.APPEARANCE_DICTIONARY_SHALL_CONTAIN_ONLY_THE_N_KEY_WITH_STREAM_VALUE); } PdfObject n = ap.get(PdfName.N); if (PdfName.Widget.equals(subtype) && PdfName.Btn.equals(PdfFormField.getFormType(annotDic))) { if (n == null || !n.isDictionary()) throw new PdfAConformanceException(PdfaExceptionMessageConstant.APPEARANCE_DICTIONARY_OF_WIDGET_SUBTYPE_AND_BTN_FIELD_TYPE_SHALL_CONTAIN_ONLY_THE_N_KEY_WITH_DICTIONARY_VALUE); } else { if (n == null || !n.isStream()) throw new PdfAConformanceException(PdfaExceptionMessageConstant.APPEARANCE_DICTIONARY_SHALL_CONTAIN_ONLY_THE_N_KEY_WITH_STREAM_VALUE); } checkResourcesOfAppearanceStreams(ap); } else { boolean isCorrectRect = false; PdfArray rect = annotDic.getAsArray(PdfName.Rect); if (rect != null && rect.size() == 4) { PdfNumber index0 = rect.getAsNumber(0); PdfNumber index1 = rect.getAsNumber(1); PdfNumber index2 = rect.getAsNumber(2); PdfNumber index3 = rect.getAsNumber(3); if (index0 != null && index1 != null && index2 != null && index3 != null && index0.floatValue() == index2.floatValue() && index1.floatValue() == index3.floatValue()) isCorrectRect = true; } if (!getAppearanceLessAnnotations().contains(subtype) && !isCorrectRect) throw new PdfAConformanceException(PdfaExceptionMessageConstant.EVERY_ANNOTATION_SHALL_HAVE_AT_LEAST_ONE_APPEARANCE_DICTIONARY); } } /** * Gets annotation types which are allowed not to have appearance stream. * * @return set of annotation names. */ protected Set getAppearanceLessAnnotations() { return apLessAnnotations; } /** * Checked annotation against actions, exception will be thrown if either {@code A} * or {@code AA} actions aren't allowed for specific type of annotation. * * @param annotDic an annotation PDF dictionary */ protected void checkAnnotationAgainstActions(PdfDictionary annotDic) { if (PdfName.Widget.equals(annotDic.getAsName(PdfName.Subtype)) && (annotDic.containsKey(PdfName.AA) || annotDic.containsKey(PdfName.A))) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.WIDGET_ANNOTATION_DICTIONARY_OR_FIELD_DICTIONARY_SHALL_NOT_INCLUDE_A_OR_AA_ENTRY); } if (annotDic.containsKey(PdfName.AA)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.AN_ANNOTATION_DICTIONARY_SHALL_NOT_CONTAIN_AA_KEY); } } /** * {@inheritDoc} */ @Override protected Set getForbiddenAnnotations() { return forbiddenAnnotations; } @Override protected void checkAppearanceStream(PdfStream appearanceStream) { if (isAlreadyChecked(appearanceStream)) { return; } if (isContainsTransparencyGroup(appearanceStream)) { this.transparencyObjects.add(appearanceStream); } checkResources(appearanceStream.getAsDictionary(PdfName.Resources), appearanceStream); } @Override protected void checkForm(PdfDictionary form) { if (form != null) { PdfBoolean needAppearances = form.getAsBoolean(PdfName.NeedAppearances); if (needAppearances != null && needAppearances.getValue()) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.NEEDAPPEARANCES_FLAG_OF_THE_INTERACTIVE_FORM_DICTIONARY_SHALL_EITHER_NOT_BE_PRESENTED_OR_SHALL_BE_FALSE); } if (form.containsKey(PdfName.XFA)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.THE_INTERACTIVE_FORM_DICTIONARY_SHALL_NOT_CONTAIN_THE_XFA_KEY); } checkResources(form.getAsDictionary(PdfName.DR), form); PdfArray fields = form.getAsArray(PdfName.Fields); if (fields != null) { fields = getFormFields(fields); for (PdfObject field : fields) { PdfDictionary fieldDic = (PdfDictionary) field; checkResources(fieldDic.getAsDictionary(PdfName.DR), fieldDic); } } } } /** * Checks if the catalog is compliant with the PDF/A-2 standard. * * @param dict the catalog dictionary */ protected void checkCatalogAAConformance(PdfDictionary dict) { if (dict.containsKey(PdfName.AA)) { throw new PdfAConformanceException( PdfAConformanceException.A_CATALOG_DICTIONARY_SHALL_NOT_CONTAIN_AA_ENTRY); } } @Override protected void checkCatalogValidEntries(PdfDictionary catalogDict) { if (catalogDict.containsKey(PdfName.NeedsRendering)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.THE_CATALOG_DICTIONARY_SHALL_NOT_CONTAIN_THE_NEEDSRENDERING_KEY); } checkCatalogAAConformance(catalogDict); if (catalogDict.containsKey(PdfName.Requirements)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.A_CATALOG_DICTIONARY_SHALL_NOT_CONTAIN_REQUIREMENTS_ENTRY); } PdfDictionary permissions = catalogDict.getAsDictionary(PdfName.Perms); if (permissions != null) { for (PdfName dictKey : permissions.keySet()) { if (PdfName.DocMDP.equals(dictKey)) { PdfDictionary signatureDict = permissions.getAsDictionary(PdfName.DocMDP); if (signatureDict != null) { checkSignature(signatureDict); } } else if (PdfName.UR3.equals(dictKey)) { } else { throw new PdfAConformanceException(PdfaExceptionMessageConstant.NO_KEYS_OTHER_THAN_UR3_AND_DOC_MDP_SHALL_BE_PRESENT_IN_A_PERMISSIONS_DICTIONARY); } } } PdfDictionary namesDictionary = catalogDict.getAsDictionary(PdfName.Names); if (namesDictionary != null && namesDictionary.containsKey(PdfName.AlternatePresentations)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.A_CATALOG_DICTIONARY_SHALL_NOT_CONTAIN_ALTERNATEPRESENTATIONS_NAMES_ENTRY); } checkOCProperties(catalogDict.getAsDictionary(PdfName.OCProperties)); } @Override protected void checkPageSize(PdfDictionary page) { PdfName[] boxNames = new PdfName[]{PdfName.MediaBox, PdfName.CropBox, PdfName.TrimBox, PdfName.ArtBox, PdfName.BleedBox}; for (PdfName boxName : boxNames) { Rectangle box = page.getAsRectangle(boxName); if (box != null) { float width = box.getWidth(); float height = box.getHeight(); if (width < MIN_PAGE_SIZE || width > MAX_PAGE_SIZE || height < MIN_PAGE_SIZE || height > MAX_PAGE_SIZE) throw new PdfAConformanceException(PdfaExceptionMessageConstant.THE_PAGE_LESS_3_UNITS_NO_GREATER_14400_IN_EITHER_DIRECTION); } } } @Override protected void checkFileSpec(PdfDictionary fileSpec) { if (fileSpec.containsKey(PdfName.EF)) { if (!fileSpec.containsKey(PdfName.F) || !fileSpec.containsKey(PdfName.UF)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.FILE_SPECIFICATION_DICTIONARY_SHALL_CONTAIN_F_KEY_AND_UF_KEY); } if (!fileSpec.containsKey(PdfName.Desc)) { logger.warn(PdfAConformanceLogMessageConstant.FILE_SPECIFICATION_DICTIONARY_SHOULD_CONTAIN_DESC_KEY); } PdfDictionary ef = fileSpec.getAsDictionary(PdfName.EF); PdfStream embeddedFile = ef.getAsStream(PdfName.F); if (embeddedFile == null) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.EF_KEY_OF_FILE_SPECIFICATION_DICTIONARY_SHALL_CONTAIN_DICTIONARY_WITH_VALID_F_KEY); } // iText doesn't check whether provided file is compliant to PDF-A specs. logger.warn(PdfAConformanceLogMessageConstant.EMBEDDED_FILE_SHALL_BE_COMPLIANT_WITH_SPEC); } } @Override protected void checkPdfStream(PdfStream stream) { checkPdfDictionary(stream); if (stream.containsKey(PdfName.F) || stream.containsKey(PdfName.FFilter) || stream.containsKey(PdfName.FDecodeParams)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.STREAM_OBJECT_DICTIONARY_SHALL_NOT_CONTAIN_THE_F_FFILTER_OR_FDECODEPARAMS_KEYS); } PdfObject filter = stream.get(PdfName.Filter); if (filter instanceof PdfName) { if (filter.equals(PdfName.LZWDecode)) throw new PdfAConformanceException(PdfaExceptionMessageConstant.LZWDECODE_FILTER_IS_NOT_PERMITTED); if (filter.equals(PdfName.Crypt)) { PdfDictionary decodeParams = stream.getAsDictionary(PdfName.DecodeParms); if (decodeParams != null) { PdfName cryptFilterName = decodeParams.getAsName(PdfName.Name); if (cryptFilterName != null && !cryptFilterName.equals(PdfName.Identity)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.NOT_IDENTITY_CRYPT_FILTER_IS_NOT_PERMITTED); } } } if (!allowedFilters.contains((PdfName) filter)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.INVALID_STREAM_FILTER_USAGE); } } else if (filter instanceof PdfArray) { for (int i = 0; i < ((PdfArray) filter).size(); i++) { PdfName f = ((PdfArray) filter).getAsName(i); if (f.equals(PdfName.LZWDecode)) throw new PdfAConformanceException(PdfaExceptionMessageConstant.LZWDECODE_FILTER_IS_NOT_PERMITTED); if (f.equals(PdfName.Crypt)) { PdfArray decodeParams = stream.getAsArray(PdfName.DecodeParms); if (decodeParams != null && i < decodeParams.size()) { PdfDictionary decodeParam = decodeParams.getAsDictionary(i); PdfName cryptFilterName = decodeParam.getAsName(PdfName.Name); if (cryptFilterName != null && !cryptFilterName.equals(PdfName.Identity)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.NOT_IDENTITY_CRYPT_FILTER_IS_NOT_PERMITTED); } } } if (!allowedFilters.contains((PdfName) f)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.INVALID_STREAM_FILTER_USAGE); } } } } /** * Checks if the page is compliant with the PDF/A-2 standard. * * @param dict the page dictionary */ protected void checkPageAAConformance(PdfDictionary dict) { if (dict.containsKey(PdfName.AA)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.THE_PAGE_DICTIONARY_SHALL_NOT_CONTAIN_AA_ENTRY); } } @Override protected void checkPageObject(PdfDictionary pageDict, PdfDictionary pageResources) { checkPageAAConformance(pageDict); if (pageDict.containsKey(PdfName.PresSteps)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.THE_PAGE_DICTIONARY_SHALL_NOT_CONTAIN_PRESSTEPS_ENTRY); } if (isContainsTransparencyGroup(pageDict)) { PdfObject cs = pageDict.getAsDictionary(PdfName.Group).get(PdfName.CS); if (cs != null) { PdfDictionary currentColorSpaces = pageResources.getAsDictionary(PdfName.ColorSpace); checkColorSpace(PdfColorSpace.makeColorSpace(cs), pageDict, currentColorSpaces, true, null); } } } @Override protected void checkPageTransparency(PdfDictionary pageDict, PdfDictionary pageResources) { if (pdfAOutputIntentColorSpace == null && transparencyObjects.size() > 0 && (pageDict.getAsDictionary(PdfName.Group) == null || pageDict.getAsDictionary(PdfName.Group).get(PdfName.CS) == null)) { checkContentsForTransparency(pageDict); checkAnnotationsForTransparency(pageDict.getAsArray(PdfName.Annots)); checkResourcesForTransparency(pageResources, new HashSet()); } } @Override protected void checkOutputIntents(PdfDictionary catalog) { PdfArray outputIntents = catalog.getAsArray(PdfName.OutputIntents); if (outputIntents == null) return; int i; PdfObject destOutputProfile = null; for (i = 0; i < outputIntents.size() && destOutputProfile == null; ++i) { destOutputProfile = outputIntents.getAsDictionary(i).get(PdfName.DestOutputProfile); } for (; i < outputIntents.size(); ++i) { PdfObject otherDestOutputProfile = outputIntents.getAsDictionary(i).get(PdfName.DestOutputProfile); if (otherDestOutputProfile != null && destOutputProfile != otherDestOutputProfile) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.IF_OUTPUTINTENTS_ARRAY_HAS_MORE_THAN_ONE_ENTRY_WITH_DESTOUTPUTPROFILE_KEY_THE_SAME_INDIRECT_OBJECT_SHALL_BE_USED_AS_THE_VALUE_OF_THAT_OBJECT); } } if (destOutputProfile != null) { String deviceClass = IccProfile.getIccDeviceClass(((PdfStream) destOutputProfile).getBytes()); if (!ICC_DEVICE_CLASS_OUTPUT_PROFILE.equals(deviceClass) && !ICC_DEVICE_CLASS_MONITOR_PROFILE.equals(deviceClass)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.PROFILE_STREAM_OF_OUTPUTINTENT_SHALL_BE_OUTPUT_PROFILE_PRTR_OR_MONITOR_PROFILE_MNTR); } String cs = IccProfile.getIccColorSpaceName(((PdfStream) destOutputProfile).getBytes()); if (!ICC_COLOR_SPACE_RGB.equals(cs) && !ICC_COLOR_SPACE_CMYK.equals(cs) && !ICC_COLOR_SPACE_GRAY.equals(cs)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.OUTPUT_INTENT_COLOR_SPACE_SHALL_BE_EITHER_GRAY_RGB_OR_CMYK); } } } @Override protected Set getForbiddenActions() { return forbiddenActions; } @Override protected Set getAllowedNamedActions() { return allowedNamedActions; } /** * {@inheritDoc} */ @Override protected void checkPageColorsUsages(PdfDictionary pageDict, PdfDictionary pageResources) { if ((rgbIsUsed || cmykIsUsed || grayIsUsed || !rgbUsedObjects.isEmpty() || !cmykUsedObjects.isEmpty() || !grayUsedObjects.isEmpty()) && pdfAOutputIntentColorSpace == null) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.IF_DEVICE_RGB_CMYK_GRAY_USED_IN_FILE_THAT_FILE_SHALL_CONTAIN_PDFA_OUTPUTINTENT_OR_DEFAULT_RGB_CMYK_GRAY_IN_USAGE_CONTEXT); } if (rgbIsUsed || !rgbUsedObjects.isEmpty()) { if (!ICC_COLOR_SPACE_RGB.equals(pdfAOutputIntentColorSpace)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.DEVICERGB_MAY_BE_USED_ONLY_IF_THE_FILE_HAS_A_RGB_PDFA_OUTPUT_INTENT_OR_DEFAULTRGB_IN_USAGE_CONTEXT); } } if (cmykIsUsed || !cmykUsedObjects.isEmpty()) { if (!ICC_COLOR_SPACE_CMYK.equals(pdfAOutputIntentColorSpace)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.DEVICECMYK_MAY_BE_USED_ONLY_IF_THE_FILE_HAS_A_CMYK_PDFA_OUTPUT_INTENT_OR_DEFAULTCMYK_IN_USAGE_CONTEXT); } } } private void checkOCProperties(PdfDictionary oCProperties) { if (oCProperties != null) { List configList = new ArrayList<>(); PdfDictionary d = oCProperties.getAsDictionary(PdfName.D); if (d != null) { configList.add(d); } PdfArray configs = oCProperties.getAsArray(PdfName.Configs); if (configs != null) { for (PdfObject config : configs) { configList.add((PdfDictionary) config); } } HashSet ocgs = new HashSet<>(); PdfArray ocgsArray = oCProperties.getAsArray(PdfName.OCGs); if (ocgsArray != null) { for (PdfObject ocg : ocgsArray) { ocgs.add(ocg); } } HashSet names = new HashSet<>(); for (PdfDictionary config : configList) { checkCatalogConfig(config, ocgs, names); } } } @Override protected void checkImage(PdfStream image, PdfDictionary currentColorSpaces) { PdfColorSpace colorSpace = null; if (isAlreadyChecked(image)) { colorSpace = checkedObjectsColorspace.get(image); checkColorSpace(colorSpace, image, currentColorSpaces, true, null); return; } PdfObject colorSpaceObj = image.get(PdfName.ColorSpace); if (colorSpaceObj != null) { colorSpace = PdfColorSpace.makeColorSpace(colorSpaceObj); checkColorSpace(colorSpace, image, currentColorSpaces, true, null); checkedObjectsColorspace.put(image, colorSpace); } if (image.containsKey(PdfName.Alternates)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.AN_IMAGE_DICTIONARY_SHALL_NOT_CONTAIN_ALTERNATES_KEY); } if (image.containsKey(PdfName.OPI)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.AN_IMAGE_DICTIONARY_SHALL_NOT_CONTAIN_OPI_KEY); } if (image.containsKey(PdfName.Interpolate) && (boolean) image.getAsBool(PdfName.Interpolate)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.THE_VALUE_OF_INTERPOLATE_KEY_SHALL_BE_FALSE); } checkRenderingIntent(image.getAsName(PdfName.Intent)); if (image.getAsStream(PdfName.SMask) != null) { transparencyObjects.add(image); } if (image.containsKey(PdfName.SMaskInData) && image.getAsInt(PdfName.SMaskInData) > 0) { transparencyObjects.add(image); } if (PdfName.JPXDecode.equals(image.get(PdfName.Filter))) { Jpeg2000ImageData jpgImage = (Jpeg2000ImageData) ImageDataFactory.createJpeg2000(image.getBytes(false)); Jpeg2000ImageData.Parameters params = jpgImage.getParameters(); /* Concerning !params.isJpxBaseline check * * In pdf/a-2 ISO (ISO 19005-2:2011 6.2.8.3 JPEG2000) is stated that: * "Only the JPX baseline set of features, ... , shall be used." * * Also in jpeg2000 ISO (ISO/IEC 15444-2:2004 Annex M: M.9.2 Support for JPX feature set) is stated that: * "In general, a JPX reader is not required to support the entire set of features defined within this Recommendation |International Standard. * However, to promote interoperability, the following baseline set of features is defined. Files that * are written in such a way as to allow a reader that supports only this JPX baseline set of features to properly open the * file shall contain a CLi field in the File Type box with the value 'jpxb' (0x6a70 7862); all JPX baseline readers are * required to properly support all files with this code in the compatibility list in the File Type box." * * Therefore, I assumed that a file, which doesn't has the jpxb flag (which can be checked with the isJpxBaseline flag) * uses not only JPX baseline set of features. * * But, all the test files used in iText5 failed on this check, so may be my assumption is wrong. */ if (!params.isJp2 /*|| !params.isJpxBaseline*/) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.ONLY_JPX_BASELINE_SET_OF_FEATURES_SHALL_BE_USED); } if (params.numOfComps != 1 && params.numOfComps != 3 && params.numOfComps != 4) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.THE_NUMBER_OF_COLOUR_CHANNELS_IN_THE_JPEG2000_DATA_SHALL_BE_1_3_OR_4); } if (params.colorSpecBoxes != null && params.colorSpecBoxes.size() > 1) { int numOfApprox0x01 = 0; for (Jpeg2000ImageData.ColorSpecBox colorSpecBox : params.colorSpecBoxes) { if (colorSpecBox.getApprox() == 1) { ++numOfApprox0x01; if (numOfApprox0x01 == 1 && colorSpecBox.getMeth() != 1 && colorSpecBox.getMeth() != 2 && colorSpecBox.getMeth() != 3) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.THE_VALUE_OF_THE_METH_ENTRY_IN_COLR_BOX_SHALL_BE_1_2_OR_3); } if (image.get(PdfName.ColorSpace) == null) { switch (colorSpecBox.getEnumCs()) { case 1: PdfDeviceCs.Gray deviceGrayCs = new PdfDeviceCs.Gray(); checkColorSpace(deviceGrayCs, image, currentColorSpaces, true, null); checkedObjectsColorspace.put(image, deviceGrayCs); break; case 3: PdfDeviceCs.Rgb deviceRgbCs = new PdfDeviceCs.Rgb(); checkColorSpace(deviceRgbCs, image, currentColorSpaces, true, null); checkedObjectsColorspace.put(image, deviceRgbCs); break; case 12: PdfDeviceCs.Cmyk deviceCmykCs = new PdfDeviceCs.Cmyk(); checkColorSpace(deviceCmykCs, image, currentColorSpaces, true, null); checkedObjectsColorspace.put(image, deviceCmykCs); break; } } } if (colorSpecBox.getEnumCs() == 19) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.JPEG2000_ENUMERATED_COLOUR_SPACE_19_CIEJAB_SHALL_NOT_BE_USED); } } if (numOfApprox0x01 != 1) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.EXACTLY_ONE_COLOUR_SPACE_SPECIFICATION_SHALL_HAVE_THE_VALUE_0X01_IN_THE_APPROX_FIELD); } } if (jpgImage.getBpc() < 1 || jpgImage.getBpc() > 38) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.THE_BIT_DEPTH_OF_THE_JPEG2000_DATA_SHALL_HAVE_A_VALUE_IN_THE_RANGE_1_TO_38); } // The Bits Per Component box specifies the bit depth of each component. // If the bit depth of all components in the codestream is the same (in both sign and precision), // then this box shall not be found. Otherwise, this box specifies the bit depth of each individual component. if (params.bpcBoxData != null) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.ALL_COLOUR_CHANNELS_IN_THE_JPEG2000_DATA_SHALL_HAVE_THE_SAME_BIT_DEPTH); } } } @Override public void checkFontGlyphs(PdfFont font, PdfStream contentStream) { if (font instanceof PdfType3Font) { checkType3FontGlyphs((PdfType3Font) font, contentStream); } } /** * For pdf/a-2+ checkers use the {@code checkFormXObject(PdfStream form, PdfStream contentStream)} method * * @param form the {@link PdfStream} to check */ @Override @Deprecated protected void checkFormXObject(PdfStream form) { checkFormXObject(form, null); } /** * Verify the conformity of the Form XObject with appropriate * specification. Throws PdfAConformanceException if any discrepancy was found * * @param form the {@link PdfStream} to be checked * @param contentStream the {@link PdfStream} current content stream */ protected void checkFormXObject(PdfStream form, PdfStream contentStream) { if (isAlreadyChecked(form)) return; if (form.containsKey(PdfName.OPI)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.A_FORM_XOBJECT_DICTIONARY_SHALL_NOT_CONTAIN_OPI_KEY); } if (form.containsKey(PdfName.PS)) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.A_FORM_XOBJECT_DICTIONARY_SHALL_NOT_CONTAIN_PS_KEY); } if (PdfName.PS.equals(form.getAsName(PdfName.Subtype2))) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.A_FORM_XOBJECT_DICTIONARY_SHALL_NOT_CONTAIN_SUBTYPE2_KEY_WITH_A_VALUE_OF_PS); } checkTransparencyGroup(form, contentStream); checkResources(form.getAsDictionary(PdfName.Resources), contentStream != null ? contentStream : form); checkContentStream(form); } /** * Verify the conformity of the transparency group XObject with appropriate * specification. Throws PdfAConformanceException if any discrepancy was found * * @param form the {@link PdfStream} transparency group XObject. * @param contentStream the {@link PdfStream} current content stream */ protected void checkTransparencyGroup(PdfStream form, PdfStream contentStream) { if (isContainsTransparencyGroup(form)) { if (contentStream != null) { transparencyObjects.add(contentStream); } else { transparencyObjects.add(form); } PdfObject cs = form.getAsDictionary(PdfName.Group).get(PdfName.CS); PdfDictionary resources = form.getAsDictionary(PdfName.Resources); if (cs != null && resources != null) { PdfDictionary currentColorSpaces = resources.getAsDictionary(PdfName.ColorSpace); checkColorSpace(PdfColorSpace.makeColorSpace(cs), contentStream != null ? contentStream : form, currentColorSpaces, true, null); } } } /** * Check optional content configuration dictionary against AS key. * * @param config a content configuration dictionary */ protected void checkContentConfigurationDictAgainstAsKey(PdfDictionary config) { if (config.containsKey(PdfName.AS)) { throw new PdfAConformanceException(PdfAConformanceException.THE_AS_KEY_SHALL_NOT_APPEAR_IN_ANY_OPTIONAL_CONTENT_CONFIGURATION_DICTIONARY); } } /** * Retrieve transparency error message valid for the pdf/a standard being used. * * @return error message. */ protected String getTransparencyErrorMessage() { return TRANSPARENCY_ERROR_MESSAGE; } /** * Check if blendMode is compatible with pdf/a standard being used. * * @param blendMode blend mode name to check. */ protected void checkBlendMode(PdfName blendMode) { if (!allowedBlendModes.contains(blendMode)) { throw new PdfAConformanceException(PdfAConformanceException.ONLY_STANDARD_BLEND_MODES_SHALL_BE_USED_FOR_THE_VALUE_OF_THE_BM_KEY_IN_AN_EXTENDED_GRAPHIC_STATE_DICTIONARY); } } void checkContentsForTransparency(PdfDictionary pageDict) { PdfStream contentStream = pageDict.getAsStream(PdfName.Contents); if (contentStream != null && transparencyObjects.contains(contentStream)) { throw new PdfAConformanceException(getTransparencyErrorMessage()); } else { PdfArray contentSteamArray = pageDict.getAsArray(PdfName.Contents); if (contentSteamArray != null) { for (int i = 0; i < contentSteamArray.size(); i++) { if (transparencyObjects.contains(contentSteamArray.get(i))) { throw new PdfAConformanceException(getTransparencyErrorMessage()); } } } } } void checkAnnotationsForTransparency(PdfArray annotations) { if (annotations == null) { return; } for (int i = 0; i < annotations.size(); ++i) { PdfDictionary annot = annotations.getAsDictionary(i); if (this.transparencyObjects.contains(annot)) { throw new PdfAConformanceException(getTransparencyErrorMessage()); } PdfDictionary ap = annot.getAsDictionary(PdfName.AP); if (ap != null) { checkAppearanceStreamForTransparency(ap, new HashSet()); } } } private void checkAppearanceStreamForTransparency(PdfDictionary ap, Set checkedObjects) { if (checkedObjects.contains(ap)) { return; } else { checkedObjects.add(ap); } for (final PdfObject val : ap.values()) { if (this.transparencyObjects.contains(val)) { throw new PdfAConformanceException(getTransparencyErrorMessage()); } else if (val.isDictionary()) { checkAppearanceStreamForTransparency((PdfDictionary) val, checkedObjects); } else if (val.isStream()) { checkObjectWithResourcesForTransparency(val, checkedObjects); } } } private void checkObjectWithResourcesForTransparency(PdfObject objectWithResources, Set checkedObjects) { if (checkedObjects.contains(objectWithResources)) { return; } else { checkedObjects.add(objectWithResources); } if (this.transparencyObjects.contains(objectWithResources)) { throw new PdfAConformanceException(getTransparencyErrorMessage()); } if (objectWithResources instanceof PdfDictionary) { checkResourcesForTransparency(((PdfDictionary) objectWithResources).getAsDictionary(PdfName.Resources), checkedObjects); } } void checkResourcesForTransparency(PdfDictionary resources, Set checkedObjects) { if (resources != null) { checkSingleResourceTypeForTransparency(resources.getAsDictionary(PdfName.XObject), checkedObjects); checkSingleResourceTypeForTransparency(resources.getAsDictionary(PdfName.Pattern), checkedObjects); } } private void checkSingleResourceTypeForTransparency(PdfDictionary singleResourceDict, Set checkedObjects) { if (singleResourceDict != null) { for (PdfObject resource : singleResourceDict.values()) { checkObjectWithResourcesForTransparency(resource, checkedObjects); } } } private void checkSeparationInsideDeviceN(PdfArray separation, PdfObject deviceNColorSpace, PdfObject deviceNTintTransform) { if (!isAltCSIsTheSame(separation.get(2), deviceNColorSpace) || !deviceNTintTransform.equals(separation.get(3))) { logger.warn(PdfAConformanceLogMessageConstant.TINT_TRANSFORM_AND_ALTERNATE_SPACE_OF_SEPARATION_ARRAYS_IN_THE_COLORANTS_OF_DEVICE_N_SHOULD_BE_CONSISTENT_WITH_SAME_ATTRIBUTES_OF_DEVICE_N); } checkSeparationCS(separation); } private void checkSeparationCS(PdfArray separation) { if (separationColorSpaces.containsKey(separation.getAsName(0))) { boolean altCSIsTheSame; boolean tintTransformIsTheSame; PdfArray sameNameSeparation = separationColorSpaces.get(separation.getAsName(0)); PdfObject cs1 = separation.get(2); PdfObject cs2 = sameNameSeparation.get(2); altCSIsTheSame = isAltCSIsTheSame(cs1, cs2); // TODO(DEVSIX-1672) in fact need to check if objects content is equal. ISO 19005-2, 6.2.4.4 "Separation and DeviceN colour spaces": // In evaluating equivalence, the PDF objects shall be compared, rather than the computational // result of the use of those PDF objects. Compression and whether or not an object is direct or indirect shall be ignored. PdfObject f1Obj = separation.get(3); PdfObject f2Obj = sameNameSeparation.get(3); //Can be a stream or dict boolean bothAllowedType = (f1Obj.getType() == f2Obj.getType()) && (f1Obj.isDictionary() || f1Obj.isStream()); //Check if the indirect references are equal tintTransformIsTheSame = bothAllowedType && f1Obj.equals(f2Obj); if (!altCSIsTheSame || !tintTransformIsTheSame) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.TINT_TRANSFORM_AND_ALTERNATE_SPACE_SHALL_BE_THE_SAME_FOR_THE_ALL_SEPARATION_CS_WITH_THE_SAME_NAME); } } else { separationColorSpaces.put(separation.getAsName(0), separation); } } private boolean isAltCSIsTheSame(PdfObject cs1, PdfObject cs2) { boolean altCSIsTheSame = false; if (cs1 instanceof PdfName) { altCSIsTheSame = cs1.equals(cs2); } else if (cs1 instanceof PdfArray && cs2 instanceof PdfArray) { // TODO(DEVSIX-1672) in fact need to check if objects content is equal. ISO 19005-2, 6.2.4.4 "Separation and DeviceN colour spaces": // In evaluating equivalence, the PDF objects shall be compared, rather than the computational // result of the use of those PDF objects. Compression and whether or not an object is direct or indirect shall be ignored. altCSIsTheSame = ((PdfArray) cs1).get(0).equals(((PdfArray) cs1).get(0)); } return altCSIsTheSame; } private void checkCatalogConfig(PdfDictionary config, HashSet ocgs, HashSet names) { PdfString name = config.getAsString(PdfName.Name); if (name == null) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.OPTIONAL_CONTENT_CONFIGURATION_DICTIONARY_SHALL_CONTAIN_NAME_ENTRY); } if (!names.add(name.toUnicodeString())) { throw new PdfAConformanceException(PdfaExceptionMessageConstant.VALUE_OF_NAME_ENTRY_SHALL_BE_UNIQUE_AMONG_ALL_OPTIONAL_CONTENT_CONFIGURATION_DICTIONARIES); } checkContentConfigurationDictAgainstAsKey(config); PdfArray orderArray = config.getAsArray(PdfName.Order); if (orderArray != null) { HashSet order = new HashSet<>(); fillOrderRecursively(orderArray, order); if (!order.equals(ocgs)) { throw new PdfAConformanceException( PdfaExceptionMessageConstant.ORDER_ARRAY_SHALL_CONTAIN_REFERENCES_TO_ALL_OCGS); } } } private void fillOrderRecursively(PdfArray orderArray, Set order) { for (PdfObject orderItem : orderArray) { if (!orderItem.isArray()) { order.add(orderItem); } else { fillOrderRecursively((PdfArray) orderItem, order); } } } private boolean checkDefaultCS(PdfObject pdfObject, PdfDictionary currentColorSpaces, Boolean fill, PdfName defaultCsName, int numOfComponents) { if (currentColorSpaces == null) return false; if (!currentColorSpaces.containsKey(defaultCsName)) return false; PdfObject defaultCsObj = currentColorSpaces.get(defaultCsName); PdfColorSpace defaultCs = PdfColorSpace.makeColorSpace(defaultCsObj); if (defaultCs instanceof PdfDeviceCs) throw new PdfAConformanceException(PdfaExceptionMessageConstant.COLOR_SPACE_0_SHALL_BE_DEVICE_INDEPENDENT).setMessageParams(defaultCsName.toString()); if (defaultCs.getNumberOfComponents() != numOfComponents) throw new PdfAConformanceException(PdfaExceptionMessageConstant.COLOR_SPACE_0_SHALL_HAVE_1_COMPONENTS).setMessageParams(defaultCsName.getValue(), numOfComponents); checkColorSpace(defaultCs, pdfObject, currentColorSpaces, false, fill); return true; } private void checkType3FontGlyphs(PdfType3Font font, PdfStream contentStream) { for (int i = 0; i <= PdfFont.SIMPLE_FONT_MAX_CHAR_CODE_VALUE; ++i) { FontEncoding fontEncoding = font.getFontEncoding(); if (fontEncoding.canDecode(i)) { Type3Glyph type3Glyph = font.getType3Glyph(fontEncoding.getUnicode(i)); if (type3Glyph != null) { checkFormXObject(type3Glyph.getContentStream(), contentStream); } } } } private static final class UpdateCanvasGraphicsState extends CanvasGraphicsState { public UpdateCanvasGraphicsState(PdfDictionary extGStateDict) { updateFromExtGState(new PdfExtGState(extGStateDict)); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy