Skip to content

Commit 5caaeaf

Browse files
committed
Add page transparency checks for pdf/a-4
DEVSIX-7776
1 parent 478bd7b commit 5caaeaf

File tree

10 files changed

+466
-34
lines changed

10 files changed

+466
-34
lines changed

pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfA1Checker.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,9 @@ protected void checkFileSpec(PdfDictionary fileSpec) {
578578
}
579579
}
580580

581+
/**
582+
* {@inheritDoc}
583+
*/
581584
@Override
582585
protected void checkAnnotation(PdfDictionary annotDic) {
583586
PdfName subtype = annotDic.getAsName(PdfName.Subtype);

pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfA2Checker.java

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -155,19 +155,22 @@ public class PdfA2Checker extends PdfA1Checker {
155155
PdfName.CCF,
156156
PdfName.DCT)));
157157

158+
protected Set<PdfObject> transparencyObjects = new HashSet<>();
159+
158160
static final int MAX_PAGE_SIZE = 14400;
159161
static final int MIN_PAGE_SIZE = 3;
160162
private static final int MAX_NUMBER_OF_DEVICEN_COLOR_COMPONENTS = 32;
161163

162164
private static final Logger logger = LoggerFactory.getLogger(PdfAChecker.class);
163165

166+
private static final String TRANSPARENCY_ERROR_MESSAGE =
167+
PdfAConformanceException.THE_DOCUMENT_DOES_NOT_CONTAIN_A_PDFA_OUTPUTINTENT_BUT_PAGE_CONTAINS_TRANSPARENCY_AND_DOES_NOT_CONTAIN_BLENDING_COLOR_SPACE;
168+
164169
private boolean currentFillCsIsIccBasedCMYK = false;
165170
private boolean currentStrokeCsIsIccBasedCMYK = false;
166171

167172
private Map<PdfName, PdfArray> separationColorSpaces = new HashMap<>();
168173

169-
private Set<PdfObject> transparencyObjects = new HashSet<>();
170-
171174
/**
172175
* Creates a PdfA2Checker with the required conformance level
173176
*
@@ -423,6 +426,9 @@ protected void checkPdfDictionary(PdfDictionary dictionary) {
423426
// currently no validation for dictionaries is implemented for PDF/A 2
424427
}
425428

429+
/**
430+
* {@inheritDoc}
431+
*/
426432
@Override
427433
protected void checkAnnotation(PdfDictionary annotDic) {
428434
PdfName subtype = annotDic.getAsName(PdfName.Subtype);
@@ -732,9 +738,6 @@ protected void checkPageTransparency(PdfDictionary pageDict, PdfDictionary pageR
732738
if (pdfAOutputIntentColorSpace == null
733739
&& transparencyObjects.size() > 0
734740
&& (pageDict.getAsDictionary(PdfName.Group) == null || pageDict.getAsDictionary(PdfName.Group).get(PdfName.CS) == null)) {
735-
if (transparencyObjects.contains(pageDict)) {
736-
throw new PdfAConformanceException(PdfAConformanceException.THE_DOCUMENT_DOES_NOT_CONTAIN_A_PDFA_OUTPUTINTENT_BUT_PAGE_CONTAINS_TRANSPARENCY_AND_DOES_NOT_CONTAIN_BLENDING_COLOR_SPACE);
737-
}
738741
checkContentsForTransparency(pageDict);
739742
checkAnnotationsForTransparency(pageDict.getAsArray(PdfName.Annots));
740743
checkResourcesForTransparency(pageResources, new HashSet<PdfObject>());
@@ -967,28 +970,52 @@ protected void checkFormXObject(PdfStream form, PdfStream contentStream) {
967970
checkContentStream(form);
968971
}
969972

970-
private void checkContentsForTransparency(PdfDictionary pageDict) {
973+
/**
974+
* Retrieve transparency error message valid for the pdf/a standard being used.
975+
*
976+
* @return error message.
977+
*/
978+
protected String getTransparencyErrorMessage() {
979+
return TRANSPARENCY_ERROR_MESSAGE;
980+
}
981+
982+
/**
983+
* Check if blendMode is compatible with pdf/a standard being used.
984+
*
985+
* @param blendMode blend mode name to check.
986+
*/
987+
protected void checkBlendMode(PdfName blendMode) {
988+
if (!allowedBlendModes.contains(blendMode)) {
989+
throw new PdfAConformanceException(PdfAConformanceException.ONLY_STANDARD_BLEND_MODES_SHALL_BE_USED_FOR_THE_VALUE_OF_THE_BM_KEY_IN_AN_EXTENDED_GRAPHIC_STATE_DICTIONARY);
990+
}
991+
}
992+
993+
void checkContentsForTransparency(PdfDictionary pageDict) {
971994
PdfStream contentStream = pageDict.getAsStream(PdfName.Contents);
972995
if (contentStream != null && transparencyObjects.contains(contentStream)) {
973-
throw new PdfAConformanceException(PdfAConformanceException.THE_DOCUMENT_DOES_NOT_CONTAIN_A_PDFA_OUTPUTINTENT_BUT_PAGE_CONTAINS_TRANSPARENCY_AND_DOES_NOT_CONTAIN_BLENDING_COLOR_SPACE);
996+
throw new PdfAConformanceException(getTransparencyErrorMessage());
974997
} else {
975998
PdfArray contentSteamArray = pageDict.getAsArray(PdfName.Contents);
976999
if (contentSteamArray != null) {
9771000
for (int i = 0; i < contentSteamArray.size(); i++) {
9781001
if (transparencyObjects.contains(contentSteamArray.get(i))) {
979-
throw new PdfAConformanceException(PdfAConformanceException.THE_DOCUMENT_DOES_NOT_CONTAIN_A_PDFA_OUTPUTINTENT_BUT_PAGE_CONTAINS_TRANSPARENCY_AND_DOES_NOT_CONTAIN_BLENDING_COLOR_SPACE);
1002+
throw new PdfAConformanceException(getTransparencyErrorMessage());
9801003
}
9811004
}
9821005
}
9831006
}
9841007
}
9851008

986-
private void checkAnnotationsForTransparency(PdfArray annotations) {
1009+
void checkAnnotationsForTransparency(PdfArray annotations) {
9871010
if (annotations == null) {
9881011
return;
9891012
}
9901013
for (int i = 0; i < annotations.size(); ++i) {
9911014
PdfDictionary annot = annotations.getAsDictionary(i);
1015+
if (this.transparencyObjects.contains(annot)) {
1016+
throw new PdfAConformanceException(getTransparencyErrorMessage());
1017+
}
1018+
9921019
PdfDictionary ap = annot.getAsDictionary(PdfName.AP);
9931020
if (ap != null) {
9941021
checkAppearanceStreamForTransparency(ap, new HashSet<PdfObject>());
@@ -1002,9 +1029,10 @@ private void checkAppearanceStreamForTransparency(PdfDictionary ap, Set<PdfObjec
10021029
} else {
10031030
checkedObjects.add(ap);
10041031
}
1032+
10051033
for (final PdfObject val : ap.values()) {
10061034
if (this.transparencyObjects.contains(val)) {
1007-
throw new PdfAConformanceException(PdfAConformanceException.THE_DOCUMENT_DOES_NOT_CONTAIN_A_PDFA_OUTPUTINTENT_BUT_PAGE_CONTAINS_TRANSPARENCY_AND_DOES_NOT_CONTAIN_BLENDING_COLOR_SPACE);
1035+
throw new PdfAConformanceException(getTransparencyErrorMessage());
10081036
} else if (val.isDictionary()) {
10091037
checkAppearanceStreamForTransparency((PdfDictionary) val, checkedObjects);
10101038
} else if (val.isStream()) {
@@ -1021,14 +1049,14 @@ private void checkObjectWithResourcesForTransparency(PdfObject objectWithResourc
10211049
}
10221050

10231051
if (this.transparencyObjects.contains(objectWithResources)) {
1024-
throw new PdfAConformanceException(PdfAConformanceException.THE_DOCUMENT_DOES_NOT_CONTAIN_A_PDFA_OUTPUTINTENT_BUT_PAGE_CONTAINS_TRANSPARENCY_AND_DOES_NOT_CONTAIN_BLENDING_COLOR_SPACE);
1052+
throw new PdfAConformanceException(getTransparencyErrorMessage());
10251053
}
10261054
if (objectWithResources instanceof PdfDictionary) {
10271055
checkResourcesForTransparency(((PdfDictionary) objectWithResources).getAsDictionary(PdfName.Resources), checkedObjects);
10281056
}
10291057
}
10301058

1031-
private void checkResourcesForTransparency(PdfDictionary resources, Set<PdfObject> checkedObjects) {
1059+
void checkResourcesForTransparency(PdfDictionary resources, Set<PdfObject> checkedObjects) {
10321060
if (resources != null) {
10331061
checkSingleResourceTypeForTransparency(resources.getAsDictionary(PdfName.XObject), checkedObjects);
10341062
checkSingleResourceTypeForTransparency(resources.getAsDictionary(PdfName.Pattern), checkedObjects);
@@ -1043,12 +1071,6 @@ private void checkSingleResourceTypeForTransparency(PdfDictionary singleResource
10431071
}
10441072
}
10451073

1046-
private void checkBlendMode(PdfName blendMode) {
1047-
if (!allowedBlendModes.contains(blendMode)) {
1048-
throw new PdfAConformanceException(PdfAConformanceException.ONLY_STANDARD_BLEND_MODES_SHALL_BE_USED_FOR_THE_VALUE_OF_THE_BM_KEY_IN_AN_EXTENDED_GRAPHIC_STATE_DICTIONARY);
1049-
}
1050-
}
1051-
10521074
private void checkSeparationInsideDeviceN(PdfArray separation, PdfObject deviceNColorSpace, PdfObject deviceNTintTransform) {
10531075
if (!isAltCSIsTheSame(separation.get(2), deviceNColorSpace) ||
10541076
!deviceNTintTransform.equals(separation.get(3))) {
@@ -1083,7 +1105,6 @@ private void checkSeparationCS(PdfArray separation) {
10831105
} else {
10841106
separationColorSpaces.put(separation.getAsName(0), separation);
10851107
}
1086-
10871108
}
10881109

10891110
private boolean isAltCSIsTheSame(PdfObject cs1, PdfObject cs2) {

pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfA4Checker.java

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.commons.utils.MessageFormatUtil;
2626
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
27+
import com.itextpdf.kernel.pdf.PdfArray;
2728
import com.itextpdf.kernel.pdf.PdfCatalog;
2829
import com.itextpdf.kernel.pdf.PdfDictionary;
2930
import com.itextpdf.kernel.pdf.PdfName;
3031
import com.itextpdf.kernel.pdf.PdfNumber;
32+
import com.itextpdf.kernel.pdf.PdfObject;
3133
import com.itextpdf.kernel.pdf.PdfString;
3234
import com.itextpdf.kernel.pdf.colorspace.PdfSpecialCs;
3335
import com.itextpdf.pdfa.exceptions.PdfAConformanceException;
@@ -47,7 +49,7 @@ This file is part of the iText (R) project.
4749
* The specification implemented by this class is ISO 19005-4
4850
*/
4951
public class PdfA4Checker extends PdfA3Checker {
50-
protected static final Set<PdfName> forbiddenAnnotations4 = Collections
52+
private static final Set<PdfName> forbiddenAnnotations4 = Collections
5153
.unmodifiableSet(new HashSet<>(Arrays.asList(
5254
PdfName._3D,
5355
PdfName.RichMedia,
@@ -56,24 +58,46 @@ public class PdfA4Checker extends PdfA3Checker {
5658
PdfName.Screen,
5759
PdfName.Movie)));
5860

59-
protected static final Set<PdfName> forbiddenAnnotations4E = Collections
61+
private static final Set<PdfName> forbiddenAnnotations4E = Collections
6062
.unmodifiableSet(new HashSet<>(Arrays.asList(
6163
PdfName.FileAttachment,
6264
PdfName.Sound,
6365
PdfName.Screen,
6466
PdfName.Movie)));
6567

66-
protected static final Set<PdfName> forbiddenAnnotations4F = Collections
68+
private static final Set<PdfName> forbiddenAnnotations4F = Collections
6769
.unmodifiableSet(new HashSet<>(Arrays.asList(
6870
PdfName._3D,
6971
PdfName.RichMedia,
7072
PdfName.Sound,
7173
PdfName.Screen,
7274
PdfName.Movie)));
7375

74-
protected static final Set<PdfName> apLessAnnotations = Collections.unmodifiableSet(
76+
private static final Set<PdfName> apLessAnnotations = Collections.unmodifiableSet(
7577
new HashSet<>(Arrays.asList(PdfName.Popup, PdfName.Link, PdfName.Projection)));
7678

79+
private static final Set<PdfName> allowedBlendModes4 = Collections
80+
.unmodifiableSet(new HashSet<>(Arrays.asList(
81+
PdfName.Normal,
82+
PdfName.Multiply,
83+
PdfName.Screen,
84+
PdfName.Overlay,
85+
PdfName.Darken,
86+
PdfName.Lighten,
87+
PdfName.ColorDodge,
88+
PdfName.ColorBurn,
89+
PdfName.HardLight,
90+
PdfName.SoftLight,
91+
PdfName.Difference,
92+
PdfName.Exclusion,
93+
PdfName.Hue,
94+
PdfName.Saturation,
95+
PdfName.Color,
96+
PdfName.Luminosity)));
97+
98+
private static final String TRANSPARENCY_ERROR_MESSAGE =
99+
PdfaExceptionMessageConstant.THE_DOCUMENT_AND_THE_PAGE_DO_NOT_CONTAIN_A_PDFA_OUTPUTINTENT_BUT_PAGE_CONTAINS_TRANSPARENCY_AND_DOES_NOT_CONTAIN_BLENDING_COLOR_SPACE;
100+
77101
/**
78102
* Creates a PdfA4Checker with the required conformance level
79103
*
@@ -129,6 +153,26 @@ protected void checkCatalogValidEntries(PdfDictionary catalogDict) {
129153
}
130154
}
131155

156+
/**
157+
* {@inheritDoc}
158+
*/
159+
@Override
160+
protected void checkPageTransparency(PdfDictionary pageDict, PdfDictionary pageResources) {
161+
// Get page pdf/a output intent
162+
PdfDictionary pdfAPageOutputIntent = null;
163+
PdfArray outputIntents = pageDict.getAsArray(PdfName.OutputIntents);
164+
if (outputIntents != null) {
165+
pdfAPageOutputIntent = getPdfAOutputIntent(outputIntents);
166+
}
167+
if (pdfAOutputIntentColorSpace == null && pdfAPageOutputIntent == null
168+
&& transparencyObjects.size() > 0
169+
&& (pageDict.getAsDictionary(PdfName.Group) == null || pageDict.getAsDictionary(PdfName.Group).get(PdfName.CS) == null)) {
170+
checkContentsForTransparency(pageDict);
171+
checkAnnotationsForTransparency(pageDict.getAsArray(PdfName.Annots));
172+
checkResourcesForTransparency(pageResources, new HashSet<PdfObject>());
173+
}
174+
}
175+
132176
//There are no limits for numbers in pdf-a/4
133177
/**
134178
* {@inheritDoc}
@@ -165,6 +209,25 @@ protected void checkNumberOfDeviceNComponents(PdfSpecialCs.DeviceN deviceN) {
165209

166210
}
167211

212+
/**
213+
* {@inheritDoc}
214+
*/
215+
@Override
216+
protected void checkAnnotation(PdfDictionary annotDic) {
217+
super.checkAnnotation(annotDic);
218+
219+
// Extra check for blending mode
220+
PdfName blendMode = annotDic.getAsName(PdfName.BM);
221+
if (blendMode != null && !allowedBlendModes4.contains(blendMode)) {
222+
throw new PdfAConformanceException(PdfaExceptionMessageConstant.ONLY_STANDARD_BLEND_MODES_SHALL_BE_USED_FOR_THE_VALUE_OF_THE_BM_KEY_IN_A_GRAPHIC_STATE_AND_ANNOTATION_DICTIONARY);
223+
}
224+
225+
// And then treat the annotation as an object with transparency
226+
if (blendMode != null && !PdfName.Normal.equals(blendMode)) {
227+
transparencyObjects.add(annotDic);
228+
}
229+
}
230+
168231
/**
169232
* {@inheritDoc}
170233
*/
@@ -199,4 +262,22 @@ protected void checkAnnotationAgainstActions(PdfDictionary annotDic) {
199262
throw new PdfAConformanceException(PdfAConformanceException.AN_ANNOTATION_DICTIONARY_SHALL_NOT_CONTAIN_AA_KEY);
200263
}
201264
}
265+
266+
/**
267+
* {@inheritDoc}
268+
*/
269+
@Override
270+
protected String getTransparencyErrorMessage() {
271+
return TRANSPARENCY_ERROR_MESSAGE;
272+
}
273+
274+
/**
275+
* {@inheritDoc}
276+
*/
277+
@Override
278+
protected void checkBlendMode(PdfName blendMode) {
279+
if (!allowedBlendModes4.contains(blendMode)) {
280+
throw new PdfAConformanceException(PdfAConformanceException.ONLY_STANDARD_BLEND_MODES_SHALL_BE_USED_FOR_THE_VALUE_OF_THE_BM_KEY_IN_AN_EXTENDED_GRAPHIC_STATE_DICTIONARY);
281+
}
282+
}
202283
}

pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfAChecker.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,17 @@ protected void checkAppearanceStream(PdfStream appearanceStream) {
735735
checkResources(appearanceStream.getAsDictionary(PdfName.Resources));
736736
}
737737

738+
PdfDictionary getPdfAOutputIntent(PdfArray outputIntents) {
739+
for (int i = 0; i < outputIntents.size(); ++i) {
740+
PdfName outputIntentSubtype = outputIntents.getAsDictionary(i).getAsName(PdfName.S);
741+
if (PdfName.GTS_PDFA1.equals(outputIntentSubtype)) {
742+
return outputIntents.getAsDictionary(i);
743+
}
744+
}
745+
746+
return null;
747+
}
748+
738749
private void checkResourcesOfAppearanceStreams(PdfDictionary appearanceStreamsDict, Set<PdfObject> checkedObjects) {
739750
if (checkedObjects.contains(appearanceStreamsDict)) {
740751
return;
@@ -855,17 +866,6 @@ private void setPdfAOutputIntentColorSpace(PdfDictionary catalog) {
855866
setCheckerOutputIntent(pdfAOutputIntent);
856867
}
857868

858-
private PdfDictionary getPdfAOutputIntent(PdfArray outputIntents) {
859-
for (int i = 0; i < outputIntents.size(); ++i) {
860-
PdfName outputIntentSubtype = outputIntents.getAsDictionary(i).getAsName(PdfName.S);
861-
if (PdfName.GTS_PDFA1.equals(outputIntentSubtype)) {
862-
return outputIntents.getAsDictionary(i);
863-
}
864-
}
865-
866-
return null;
867-
}
868-
869869
private void setCheckerOutputIntent(PdfDictionary outputIntent) {
870870
if (outputIntent != null) {
871871
PdfStream destOutputProfile = outputIntent.getAsStream(PdfName.DestOutputProfile);

pdfa/src/main/java/com/itextpdf/pdfa/exceptions/PdfaExceptionMessageConstant.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,15 @@ public final class PdfaExceptionMessageConstant {
3737
= "Can't find an appropriate checker for a specified name.";
3838
public static final String WIDGET_ANNOTATION_DICTIONARY_OR_FIELD_DICTIONARY_SHALL_NOT_INCLUDE_A_ENTRY = "Widget annotation dictionary or field dictionary shall not include a entry";
3939

40+
public static final String THE_DOCUMENT_AND_THE_PAGE_DO_NOT_CONTAIN_A_PDFA_OUTPUTINTENT_BUT_PAGE_CONTAINS_TRANSPARENCY_AND_DOES_NOT_CONTAIN_BLENDING_COLOR_SPACE =
41+
"If the document does not contain a PDF/A output intent, then all pages that contain transparency shall"
42+
+ " either have a page-level PDF/A output intent or the page dictionary shall include the"
43+
+ " Group key, and the attribute dictionary that forms the value of that Group key shall include"
44+
+ " a CS entry whose value shall be used as the default blending colour space.";
45+
46+
public static final String ONLY_STANDARD_BLEND_MODES_SHALL_BE_USED_FOR_THE_VALUE_OF_THE_BM_KEY_IN_A_GRAPHIC_STATE_AND_ANNOTATION_DICTIONARY =
47+
"Only blend modes that are specified in ISO 32000-2:2020 shall be used for the value of the BM key in a"
48+
+ " graphic state dictionary or an annotation dictionary.";
49+
4050
private PdfaExceptionMessageConstant(){}
4151
}

0 commit comments

Comments
 (0)