Skip to content

Commit 29c6e11

Browse files
author
Dmitry Radchuk
committed
Add check for colourants dictionary for pdf/a-2 in when checking color spaces
DEVSIX-4203
1 parent 9d1f446 commit 29c6e11

File tree

7 files changed

+162
-8
lines changed

7 files changed

+162
-8
lines changed

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,17 +256,21 @@ public void checkColorSpace(PdfColorSpace colorSpace, PdfObject pdfObject, PdfDi
256256

257257
PdfSpecialCs.DeviceN deviceN = (PdfSpecialCs.DeviceN) colorSpace;
258258
checkNumberOfDeviceNComponents(deviceN);
259-
//TODO DEVSIX-4203 Fix IndexOutOfBounds exception being thrown for DeviceN (not NChannel) colorspace without
260-
// attributes. According to the spec PdfAConformanceException should be thrown.
259+
//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)
260+
//for the pdf/a-2 it should look as follows: [/DeviceN names alternateSpace tintTransform attributes], since colourants dictionary is
261+
// located in attributes and according to pdf/a-2 spec it should always be present.
262+
if (((PdfArray) deviceN.getPdfObject()).size() != 5) {
263+
throw new PdfAConformanceException(PdfaExceptionMessageConstant.COLORANTS_DICTIONARY_SHALL_NOT_BE_EMPTY_IN_DEVICE_N_COLORSPACE);
264+
}
261265
PdfDictionary attributes = ((PdfArray) deviceN.getPdfObject()).getAsDictionary(4);
262266
PdfDictionary colorants = attributes.getAsDictionary(PdfName.Colorants);
263-
//TODO DEVSIX-4203 Colorants dictionary is mandatory in PDF/A-2 spec. Need to throw an appropriate exception
264-
// if it is not present.
265-
if (colorants != null) {
267+
if (colorants != null && !colorants.isEmpty()) {
266268
for (Map.Entry<PdfName, PdfObject> entry : colorants.entrySet()) {
267269
PdfArray separation = (PdfArray) entry.getValue();
268270
checkSeparationInsideDeviceN(separation, ((PdfArray) deviceN.getPdfObject()).get(2), ((PdfArray) deviceN.getPdfObject()).get(3));
269271
}
272+
} else {
273+
throw new PdfAConformanceException(PdfaExceptionMessageConstant.COLORANTS_DICTIONARY_SHALL_NOT_BE_EMPTY_IN_DEVICE_N_COLORSPACE);
270274
}
271275

272276
if (checkAlternate) {
@@ -956,7 +960,13 @@ public void checkFontGlyphs(PdfFont font, PdfStream contentStream) {
956960
}
957961
}
958962

963+
/**
964+
* For pdf/a-2+ checkers use the {@code checkFormXObject(PdfStream form, PdfStream contentStream)} method
965+
*
966+
* @param form the {@link PdfStream} to check
967+
*/
959968
@Override
969+
@Deprecated
960970
protected void checkFormXObject(PdfStream form) {
961971
checkFormXObject(form, null);
962972
}
@@ -987,6 +997,13 @@ protected void checkFormXObject(PdfStream form, PdfStream contentStream) {
987997
checkContentStream(form);
988998
}
989999

1000+
/**
1001+
* Verify the conformity of the transparency group XObject with appropriate
1002+
* specification. Throws PdfAConformanceException if any discrepancy was found
1003+
*
1004+
* @param form the {@link PdfStream} transparency group XObject.
1005+
* @param contentStream the {@link PdfStream} current content stream
1006+
*/
9901007
protected void checkTransparencyGroup(PdfStream form, PdfStream contentStream) {
9911008
if (isContainsTransparencyGroup(form)) {
9921009
if (contentStream != null) {

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,12 @@ public void checkExtGState(CanvasGraphicsState extGState, PdfStream contentStrea
407407
}
408408
}
409409
}
410+
411+
/**
412+
* {@inheritDoc}
413+
*/
410414
@Override
411-
protected void checkFormXObject(PdfStream form) {
415+
protected void checkFormXObject(PdfStream form, PdfStream contentStream) {
412416
if (isAlreadyChecked(form)) {
413417
return;
414418
}
@@ -418,7 +422,7 @@ protected void checkFormXObject(PdfStream form) {
418422
if (form.containsKey(PdfName.Ref)) {
419423
throw new PdfAConformanceException(PdfaExceptionMessageConstant.A_FORM_XOBJECT_DICTIONARY_SHALL_NOT_CONTAIN_REF_KEY);
420424
}
421-
checkTransparencyGroup(form, null);
425+
checkTransparencyGroup(form, contentStream);
422426
checkResources(form.getAsDictionary(PdfName.Resources), form);
423427
checkContentStream(form);
424428
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,5 +205,8 @@ public final class PdfaExceptionMessageConstant {
205205
"An ICCBased colour space shall not be used where the profile is a CMYK destination profile and is "
206206
+ "identical to that in the current PDF/A OutputIntent or the current transparency blending colorspace.";
207207

208+
public static final String COLORANTS_DICTIONARY_SHALL_NOT_BE_EMPTY_IN_DEVICE_N_COLORSPACE = "For any spot color " +
209+
"used in a DeviceN or NChannel colorspace, an entry in the Colorants dictionary shall be present.";
210+
208211
private PdfaExceptionMessageConstant(){}
209212
}

pdfa/src/test/java/com/itextpdf/pdfa/PdfA2GraphicsCheckTest.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,67 @@ public void colourSpaceTest03() throws FileNotFoundException {
625625
doc.close();
626626
}
627627

628+
@Test
629+
public void colourSpaceWithoutColourantsTest() throws FileNotFoundException {
630+
PdfWriter writer = new PdfWriter(new com.itextpdf.io.source.ByteArrayOutputStream());
631+
InputStream is = new FileInputStream(sourceFolder + "sRGB Color Space Profile.icm");
632+
PdfADocument doc = new PdfADocument(writer, PdfAConformanceLevel.PDF_A_2B, new PdfOutputIntent("Custom", "", "http://www.color.org", "sRGB IEC61966-2.1", is));
633+
PdfPage page = doc.addNewPage();
634+
635+
PdfColorSpace alternateSpace= new PdfDeviceCs.Rgb();
636+
//Tint transformation function is a dictionary
637+
float[] domain = new float[]{0,1};
638+
float[] range = new float[]{0,1,0,1,0,1};
639+
float[] C0 = new float[]{0,0,0};
640+
float[] C1 = new float[]{1,1,1};
641+
int n = 1;
642+
643+
PdfType2Function type2 = new PdfType2Function(domain, range, C0, C1, n);
644+
645+
PdfCanvas canvas = new PdfCanvas(page);
646+
String separationName = "separationTest";
647+
canvas.setColor(new Separation(separationName, alternateSpace, type2, 0.5f), true);
648+
649+
PdfDictionary attributes = new PdfDictionary();
650+
PdfDictionary colorantsDict = new PdfDictionary();
651+
colorantsDict.put(new PdfName(separationName), new PdfSpecialCs.Separation(separationName, alternateSpace,type2).getPdfObject());
652+
DeviceN deviceN = new DeviceN(new PdfSpecialCs.NChannel(Collections.singletonList(separationName), alternateSpace, type2, attributes), new float[]{0.5f});
653+
Exception e = Assert.assertThrows(PdfAConformanceException.class,
654+
() -> canvas.setColor(deviceN, true));
655+
Assert.assertEquals(PdfaExceptionMessageConstant.COLORANTS_DICTIONARY_SHALL_NOT_BE_EMPTY_IN_DEVICE_N_COLORSPACE, e.getMessage());
656+
doc.close();
657+
}
658+
659+
@Test
660+
public void colourSpaceWithoutAttributesTest() throws FileNotFoundException {
661+
PdfWriter writer = new PdfWriter(new com.itextpdf.io.source.ByteArrayOutputStream());
662+
InputStream is = new FileInputStream(sourceFolder + "sRGB Color Space Profile.icm");
663+
PdfADocument doc = new PdfADocument(writer, PdfAConformanceLevel.PDF_A_2B, new PdfOutputIntent("Custom", "", "http://www.color.org", "sRGB IEC61966-2.1", is));
664+
PdfPage page = doc.addNewPage();
665+
666+
PdfColorSpace alternateSpace= new PdfDeviceCs.Rgb();
667+
//Tint transformation function is a dictionary
668+
float[] domain = new float[]{0,1};
669+
float[] range = new float[]{0,1,0,1,0,1};
670+
float[] C0 = new float[]{0,0,0};
671+
float[] C1 = new float[]{1,1,1};
672+
int n = 1;
673+
674+
PdfType2Function type2 = new PdfType2Function(domain, range, C0, C1, n);
675+
676+
PdfCanvas canvas = new PdfCanvas(page);
677+
String separationName = "separationTest";
678+
canvas.setColor(new Separation(separationName, alternateSpace, type2, 0.5f), true);
679+
680+
PdfDictionary colorantsDict = new PdfDictionary();
681+
colorantsDict.put(new PdfName(separationName), new PdfSpecialCs.Separation(separationName, alternateSpace,type2).getPdfObject());
682+
DeviceN deviceN = new DeviceN(new PdfSpecialCs.DeviceN(Collections.singletonList(separationName), alternateSpace, type2), new float[]{0.5f});
683+
Exception e = Assert.assertThrows(PdfAConformanceException.class,
684+
() -> canvas.setColor(deviceN, true));
685+
Assert.assertEquals(PdfaExceptionMessageConstant.COLORANTS_DICTIONARY_SHALL_NOT_BE_EMPTY_IN_DEVICE_N_COLORSPACE, e.getMessage());
686+
doc.close();
687+
}
688+
628689
private void compareResult(String outPdf, String cmpPdf) throws IOException, InterruptedException {
629690
String result = new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder, "diff_");
630691
if (result != null) {

pdfa/src/test/java/com/itextpdf/pdfa/checker/PdfA2CheckerTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,59 @@ public void checkPdfStreamContainsCryptArrayKeyTest() {
718718
Assert.assertEquals(PdfaExceptionMessageConstant.NOT_IDENTITY_CRYPT_FILTER_IS_NOT_PERMITTED, e.getMessage());
719719
}
720720

721+
@Test
722+
public void checkColorSpaceWithDeviceNWithoutAttributes() {
723+
List<String> tmpArray = new ArrayList<String>(3);
724+
float[] transformArray = new float[6];
725+
tmpArray.add("Black");
726+
tmpArray.add("Magenta");
727+
tmpArray.add("White");
728+
729+
for (int i = 0; i < 3; i++) {
730+
transformArray[i * 2] = 0;
731+
transformArray[i * 2 + 1] = 1;
732+
}
733+
PdfType4Function function = new PdfType4Function(transformArray, new float[]{0, 1, 0, 1, 0, 1},
734+
"{0}".getBytes(StandardCharsets.ISO_8859_1));
735+
736+
PdfArray deviceNAsArray = ((PdfArray)(new PdfSpecialCs.DeviceN(tmpArray, new PdfDeviceCs.Rgb(), function)).getPdfObject());
737+
PdfDictionary currentColorSpaces = new PdfDictionary();
738+
739+
740+
Exception e = Assert.assertThrows(PdfAConformanceException.class,
741+
() -> pdfA2Checker.checkColorSpace(new PdfSpecialCs.DeviceN(deviceNAsArray), currentColorSpaces, true, false)
742+
);
743+
Assert.assertEquals(PdfaExceptionMessageConstant.COLORANTS_DICTIONARY_SHALL_NOT_BE_EMPTY_IN_DEVICE_N_COLORSPACE, e.getMessage());
744+
}
745+
746+
747+
@Test
748+
public void checkColorSpaceWithDeviceNWithoutColorants() {
749+
List<String> tmpArray = new ArrayList<String>(3);
750+
float[] transformArray = new float[6];
751+
tmpArray.add("Black");
752+
tmpArray.add("Magenta");
753+
tmpArray.add("White");
754+
755+
for (int i = 0; i < 3; i++) {
756+
transformArray[i * 2] = 0;
757+
transformArray[i * 2 + 1] = 1;
758+
}
759+
PdfType4Function function = new PdfType4Function(transformArray, new float[]{0, 1, 0, 1, 0, 1},
760+
"{0}".getBytes(StandardCharsets.ISO_8859_1));
761+
762+
PdfArray deviceNAsArray = ((PdfArray)(new PdfSpecialCs.DeviceN(tmpArray, new PdfDeviceCs.Rgb(), function)).getPdfObject());
763+
PdfDictionary currentColorSpaces = new PdfDictionary();
764+
PdfDictionary attributes = new PdfDictionary();
765+
deviceNAsArray.add(attributes);
766+
767+
768+
Exception e = Assert.assertThrows(PdfAConformanceException.class,
769+
() -> pdfA2Checker.checkColorSpace(new PdfSpecialCs.DeviceN(deviceNAsArray), currentColorSpaces, true, false)
770+
);
771+
Assert.assertEquals(PdfaExceptionMessageConstant.COLORANTS_DICTIONARY_SHALL_NOT_BE_EMPTY_IN_DEVICE_N_COLORSPACE, e.getMessage());
772+
}
773+
721774
private static PdfDictionary createSignatureDict() {
722775
PdfDictionary signatureDict = new PdfDictionary();
723776

pdfa/src/test/java/com/itextpdf/pdfa/checker/PdfA2ImplementationLimitsCheckerTest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This file is part of the iText (R) project.
2525
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
2626
import com.itextpdf.kernel.pdf.PdfArray;
2727
import com.itextpdf.kernel.pdf.PdfDictionary;
28+
import com.itextpdf.kernel.pdf.PdfName;
2829
import com.itextpdf.kernel.pdf.PdfNumber;
2930
import com.itextpdf.kernel.pdf.PdfStream;
3031
import com.itextpdf.kernel.pdf.PdfString;
@@ -47,10 +48,11 @@ This file is part of the iText (R) project.
4748

4849
@Category(UnitTest.class)
4950
public class PdfA2ImplementationLimitsCheckerTest extends ExtendedITextTest {
50-
private PdfA2Checker pdfA2Checker = new PdfA2Checker(PdfAConformanceLevel.PDF_A_2B);
51+
private PdfA2Checker pdfA2Checker;
5152

5253
@Before
5354
public void before() {
55+
pdfA2Checker = new PdfA2Checker(PdfAConformanceLevel.PDF_A_2B);
5456
pdfA2Checker.setFullCheckMode(true);
5557
}
5658

@@ -170,6 +172,10 @@ private PdfColorSpace buildDeviceNColorspace(int numberOfComponents) {
170172
//TODO DEVSIX-4205 Replace with a constructor with 4 parameters or use a setter for attributes dictionary
171173
PdfArray deviceNAsArray = ((PdfArray)(new PdfSpecialCs.DeviceN(tmpArray, new PdfDeviceCs.Rgb(), function)).getPdfObject());
172174
PdfDictionary attributes = new PdfDictionary();
175+
PdfDictionary colourants = new PdfDictionary();
176+
String colourantName = "colourantTest";
177+
colourants.put(new PdfName(colourantName), new PdfSpecialCs.DeviceN(((PdfArray)(new PdfSpecialCs.DeviceN(tmpArray, new PdfDeviceCs.Rgb(), function)).getPdfObject())).getPdfObject());
178+
attributes.put(PdfName.Colorants, colourants);
173179
deviceNAsArray.add(attributes);
174180
return new PdfSpecialCs.DeviceN(deviceNAsArray);
175181
}

pdfa/src/test/java/com/itextpdf/pdfa/checker/PdfA4ImplementationLimitsTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,20 @@ This file is part of the iText (R) project.
2525
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
2626
import com.itextpdf.kernel.pdf.PdfArray;
2727
import com.itextpdf.kernel.pdf.PdfDictionary;
28+
import com.itextpdf.kernel.pdf.PdfName;
2829
import com.itextpdf.kernel.pdf.PdfNumber;
2930
import com.itextpdf.kernel.pdf.PdfStream;
3031
import com.itextpdf.kernel.pdf.PdfString;
3132
import com.itextpdf.kernel.pdf.colorspace.PdfColorSpace;
3233
import com.itextpdf.kernel.pdf.colorspace.PdfDeviceCs;
3334
import com.itextpdf.kernel.pdf.colorspace.PdfSpecialCs;
35+
import com.itextpdf.kernel.pdf.function.PdfType2Function;
3436
import com.itextpdf.kernel.pdf.function.PdfType4Function;
37+
import com.itextpdf.pdfa.exceptions.PdfaExceptionMessageConstant;
38+
import com.itextpdf.pdfa.logs.PdfAConformanceLogMessageConstant;
3539
import com.itextpdf.test.ExtendedITextTest;
40+
import com.itextpdf.test.annotations.LogMessage;
41+
import com.itextpdf.test.annotations.LogMessages;
3642
import com.itextpdf.test.annotations.type.UnitTest;
3743
import org.junit.Assert;
3844
import org.junit.Before;
@@ -107,6 +113,10 @@ private PdfColorSpace buildDeviceNColorspace(int numberOfComponents) {
107113

108114
PdfArray deviceNAsArray = ((PdfArray)(new PdfSpecialCs.DeviceN(tmpArray, new PdfDeviceCs.Rgb(), function)).getPdfObject());
109115
PdfDictionary attributes = new PdfDictionary();
116+
PdfDictionary colourants = new PdfDictionary();
117+
String colourantName = "colourantTest";
118+
colourants.put(new PdfName(colourantName), new PdfSpecialCs.DeviceN(((PdfArray)(new PdfSpecialCs.DeviceN(tmpArray, new PdfDeviceCs.Rgb(), function)).getPdfObject())).getPdfObject());
119+
attributes.put(PdfName.Colorants, colourants);
110120
deviceNAsArray.add(attributes);
111121
return new PdfSpecialCs.DeviceN(deviceNAsArray);
112122
}

0 commit comments

Comments
 (0)