Skip to content

Commit 9a1bbbd

Browse files
author
vitali.prudnikovich
committed
PDF/A: Add check for Default Color Space
DEVSIX-2801
1 parent ffb56a7 commit 9a1bbbd

File tree

6 files changed

+149
-5
lines changed

6 files changed

+149
-5
lines changed

kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/PdfCanvas.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ This file is part of the iText (R) project.
5151
import com.itextpdf.io.image.ImageType;
5252
import com.itextpdf.io.source.ByteUtils;
5353
import com.itextpdf.io.util.StreamUtil;
54+
import com.itextpdf.kernel.colors.DeviceGray;
5455
import com.itextpdf.kernel.exceptions.PdfException;
5556
import com.itextpdf.kernel.colors.Color;
5657
import com.itextpdf.kernel.colors.PatternColor;
@@ -174,6 +175,10 @@ public class PdfCanvas {
174175

175176
private static final float IDENTITY_MATRIX_EPS = 1e-4f;
176177

178+
// Flag showing whether to check the color on drawing or not
179+
// Normally the color is checked on setColor but not the default one which is DeviceGray.BLACK
180+
private boolean defaultDeviceGrayBlackColorCheckRequired = true;
181+
177182
/**
178183
* a LIFO stack of graphics state saved states.
179184
*/
@@ -539,6 +544,8 @@ public PdfCanvas newlineText() {
539544
* @return current canvas.
540545
*/
541546
public PdfCanvas newlineShowText(String text) {
547+
checkDefaultDeviceGrayBlackColor(getColorKeyForText());
548+
542549
showTextInt(text);
543550
contentStream.getOutputStream()
544551
.writeByte('\'')
@@ -555,6 +562,8 @@ public PdfCanvas newlineShowText(String text) {
555562
* @return current canvas.
556563
*/
557564
public PdfCanvas newlineShowText(float wordSpacing, float charSpacing, String text) {
565+
checkDefaultDeviceGrayBlackColor(getColorKeyForText());
566+
558567
contentStream.getOutputStream()
559568
.writeFloat(wordSpacing)
560569
.writeSpace()
@@ -702,6 +711,8 @@ public PdfCanvas setTextMatrix(float x, float y) {
702711
* @return current canvas.
703712
*/
704713
public PdfCanvas showText(String text) {
714+
checkDefaultDeviceGrayBlackColor(getColorKeyForText());
715+
705716
showTextInt(text);
706717
contentStream.getOutputStream().writeBytes(Tj);
707718
return this;
@@ -726,7 +737,9 @@ public PdfCanvas showText(GlyphLine text) {
726737
* @return current canvas.
727738
*/
728739
public PdfCanvas showText(GlyphLine text, Iterator<GlyphLine.GlyphLinePart> iterator) {
740+
checkDefaultDeviceGrayBlackColor(getColorKeyForText());
729741
document.checkIsoConformance(currentGs, IsoKey.FONT_GLYPHS, null, contentStream);
742+
730743
PdfFont font;
731744
if ((font = currentGs.getFont()) == null) {
732745
throw new PdfException(
@@ -898,7 +911,9 @@ private float getWordSpacingAddition(Glyph glyph) {
898911
* @return current canvas.
899912
*/
900913
public PdfCanvas showText(PdfArray textArray) {
914+
checkDefaultDeviceGrayBlackColor(getColorKeyForText());
901915
document.checkIsoConformance(currentGs, IsoKey.FONT_GLYPHS, null, contentStream);
916+
902917
if (currentGs.getFont() == null)
903918
throw new PdfException(
904919
KernelExceptionMessageConstant.FONT_AND_SIZE_MUST_BE_SET_BEFORE_WRITING_ANY_TEXT, currentGs);
@@ -1269,6 +1284,8 @@ public PdfCanvas closePath() {
12691284
* @return current canvas.
12701285
*/
12711286
public PdfCanvas closePathEoFillStroke() {
1287+
checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL_AND_STROKE);
1288+
12721289
contentStream.getOutputStream().writeBytes(bStar);
12731290
return this;
12741291
}
@@ -1279,6 +1296,8 @@ public PdfCanvas closePathEoFillStroke() {
12791296
* @return current canvas.
12801297
*/
12811298
public PdfCanvas closePathFillStroke() {
1299+
checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL_AND_STROKE);
1300+
12821301
contentStream.getOutputStream().writeBytes(b);
12831302
return this;
12841303
}
@@ -1299,6 +1318,8 @@ public PdfCanvas endPath() {
12991318
* @return current canvas.
13001319
*/
13011320
public PdfCanvas stroke() {
1321+
checkDefaultDeviceGrayBlackColor(CheckColorMode.STROKE);
1322+
13021323
contentStream.getOutputStream().writeBytes(S);
13031324
return this;
13041325
}
@@ -1341,6 +1362,8 @@ public PdfCanvas closePathStroke() {
13411362
* @return current canvas.
13421363
*/
13431364
public PdfCanvas fill() {
1365+
checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL);
1366+
13441367
contentStream.getOutputStream().writeBytes(f);
13451368
return this;
13461369
}
@@ -1351,6 +1374,8 @@ public PdfCanvas fill() {
13511374
* @return current canvas.
13521375
*/
13531376
public PdfCanvas fillStroke() {
1377+
checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL_AND_STROKE);
1378+
13541379
contentStream.getOutputStream().writeBytes(B);
13551380
return this;
13561381
}
@@ -1361,6 +1386,8 @@ public PdfCanvas fillStroke() {
13611386
* @return current canvas.
13621387
*/
13631388
public PdfCanvas eoFill() {
1389+
checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL);
1390+
13641391
contentStream.getOutputStream().writeBytes(fStar);
13651392
return this;
13661393
}
@@ -1371,6 +1398,8 @@ public PdfCanvas eoFill() {
13711398
* @return current canvas.
13721399
*/
13731400
public PdfCanvas eoFillStroke() {
1401+
checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL_AND_STROKE);
1402+
13741403
contentStream.getOutputStream().writeBytes(BStar);
13751404
return this;
13761405
}
@@ -2479,6 +2508,40 @@ private PdfCanvas drawArc(double x1, double y1, double x2, double y2,
24792508
return this;
24802509
}
24812510

2511+
private void checkDefaultDeviceGrayBlackColor(CheckColorMode checkColorMode) {
2512+
if (defaultDeviceGrayBlackColorCheckRequired) {
2513+
// It's enough to check DeviceGray.BLACK once for fill color or stroke color
2514+
// But it's still important to do not check fill color if it's not used and vice versa
2515+
if (currentGs.getFillColor() == DeviceGray.BLACK &&
2516+
(checkColorMode == CheckColorMode.FILL || checkColorMode == CheckColorMode.FILL_AND_STROKE)) {
2517+
document.checkIsoConformance(currentGs, IsoKey.FILL_COLOR, resources, contentStream);
2518+
defaultDeviceGrayBlackColorCheckRequired = false;
2519+
} else if (currentGs.getStrokeColor() == DeviceGray.BLACK &&
2520+
(checkColorMode == CheckColorMode.STROKE || checkColorMode == CheckColorMode.FILL_AND_STROKE)) {
2521+
document.checkIsoConformance(currentGs, IsoKey.STROKE_COLOR, resources, contentStream);
2522+
defaultDeviceGrayBlackColorCheckRequired = false;
2523+
} else {
2524+
// Nothing
2525+
}
2526+
}
2527+
}
2528+
2529+
private CheckColorMode getColorKeyForText() {
2530+
switch (currentGs.getTextRenderingMode()) {
2531+
case PdfCanvasConstants.TextRenderingMode.FILL:
2532+
case PdfCanvasConstants.TextRenderingMode.FILL_CLIP:
2533+
return CheckColorMode.FILL;
2534+
case PdfCanvasConstants.TextRenderingMode.STROKE:
2535+
case PdfCanvasConstants.TextRenderingMode.STROKE_CLIP:
2536+
return CheckColorMode.STROKE;
2537+
case PdfCanvasConstants.TextRenderingMode.FILL_STROKE:
2538+
case PdfCanvasConstants.TextRenderingMode.FILL_STROKE_CLIP:
2539+
return CheckColorMode.FILL_AND_STROKE;
2540+
default:
2541+
return CheckColorMode.NONE;
2542+
}
2543+
}
2544+
24822545
private static PdfStream getPageStream(PdfPage page) {
24832546
PdfStream stream = page.getLastContentStream();
24842547
return stream == null || stream.getOutputStream() == null || stream.containsKey(PdfName.Filter) ? page.newContentStreamAfter() : stream;
@@ -2508,4 +2571,11 @@ private static boolean isIdentityMatrix(float a, float b, float c, float d, floa
25082571
return Math.abs(1 - a) < IDENTITY_MATRIX_EPS && Math.abs(b) < IDENTITY_MATRIX_EPS && Math.abs(c) < IDENTITY_MATRIX_EPS &&
25092572
Math.abs(1 - d) < IDENTITY_MATRIX_EPS && Math.abs(e) < IDENTITY_MATRIX_EPS && Math.abs(f) < IDENTITY_MATRIX_EPS;
25102573
}
2574+
2575+
private enum CheckColorMode {
2576+
NONE,
2577+
FILL,
2578+
STROKE,
2579+
FILL_AND_STROKE
2580+
}
25112581
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public class PdfAConformanceException extends PdfException {
105105
public static final String GRAPHICS_STATE_STACK_DEPTH_IS_GREATER_THAN_28 = "Graphics state stack depth is greater than 28";
106106
public static final String HALFTONES_SHALL_NOT_CONTAIN_HALFTONENAME = "Halftones shall not contain halftonename";
107107
public static final String IF_DEVICE_RGB_CMYK_GRAY_USED_IN_FILE_THAT_FILE_SHALL_CONTAIN_PDFA_OUTPUTINTENT = "If device rgb cmyk gray used in file, that file shall contain pdfa outputintent";
108-
public static final String IF_DEVICE_RGB_CMYK_GRAY_USED_IN_FILE_THAT_FILE_SHALL_CONTAIN_PDFA_OUTPUTINTENT_OR_DEFAULT_RGB_CMYK_GRAY_IN_USAGE_CONTEXT = "If device rgb cmyk gray used in file that file shall contain pdfa outputintent orDefaultRgb Cmyk Gray in usage context";
108+
public static final String IF_DEVICE_RGB_CMYK_GRAY_USED_IN_FILE_THAT_FILE_SHALL_CONTAIN_PDFA_OUTPUTINTENT_OR_DEFAULT_RGB_CMYK_GRAY_IN_USAGE_CONTEXT = "If device rgb cmyk gray used in file that file shall contain pdfa outputintent or DefaultRgb Cmyk Gray in usage context";
109109
public static final String 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 outputintents array has more than one entry with destoutputprofile key the same indirect object shall be used as the value of that object";
110110
public static final String IF_SPECIFIED_RENDERING_SHALL_BE_ONE_OF_THE_FOLLOWING_RELATIVECOLORIMETRIC_ABSOLUTECOLORIMETRIC_PERCEPTUAL_OR_SATURATION = "If specified rendering shall be one of the following relativecolorimetric absolutecolorimetric perceptual or saturation";
111111
public static final String INTEGER_NUMBER_IS_OUT_OF_RANGE = "Integer number is out of range";

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,15 @@ This file is part of the iText (R) project.
5555
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
5656
import com.itextpdf.kernel.pdf.PdfArray;
5757
import com.itextpdf.kernel.pdf.PdfDictionary;
58+
import com.itextpdf.kernel.pdf.PdfDocument;
5859
import com.itextpdf.kernel.pdf.PdfName;
5960
import com.itextpdf.kernel.pdf.PdfNumber;
6061
import com.itextpdf.kernel.pdf.PdfOutputIntent;
6162
import com.itextpdf.kernel.pdf.PdfPage;
6263
import com.itextpdf.kernel.pdf.PdfWriter;
6364
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
6465
import com.itextpdf.kernel.pdf.canvas.PdfCanvasConstants;
66+
import com.itextpdf.kernel.pdf.canvas.PdfCanvasConstants.TextRenderingMode;
6567
import com.itextpdf.kernel.pdf.colorspace.PdfCieBasedCs;
6668
import com.itextpdf.kernel.pdf.colorspace.PdfColorSpace;
6769
import com.itextpdf.kernel.pdf.colorspace.PdfDeviceCs;
@@ -292,6 +294,78 @@ public void colorCheckTest7() throws IOException, InterruptedException {
292294
e.getMessage());
293295
}
294296

297+
@Test
298+
public void defaultTextColorCheckTest() throws IOException {
299+
String outPdf = destinationFolder + "defaultColorCheck.pdf";
300+
301+
PdfDocument pdfDocument = new PdfADocument(new PdfWriter(outPdf), PdfAConformanceLevel.PDF_A_2B, null);
302+
PdfFont font = PdfFontFactory.createFont(sourceFolder + "FreeSans.ttf",
303+
"Identity-H", EmbeddingStrategy.FORCE_EMBEDDED);
304+
305+
PdfPage page = pdfDocument.addNewPage();
306+
PdfCanvas canvas = new PdfCanvas(page);
307+
canvas.saveState();
308+
canvas.beginText()
309+
.moveText(36, 750)
310+
.setFontAndSize(font, 16)
311+
.showText("some text")
312+
.endText()
313+
.restoreState();
314+
315+
Exception e = Assert.assertThrows(PdfAConformanceException.class, () -> pdfDocument.close());
316+
Assert.assertEquals(MessageFormatUtil.format(PdfAConformanceException.IF_DEVICE_RGB_CMYK_GRAY_USED_IN_FILE_THAT_FILE_SHALL_CONTAIN_PDFA_OUTPUTINTENT_OR_DEFAULT_RGB_CMYK_GRAY_IN_USAGE_CONTEXT),
317+
e.getMessage());
318+
}
319+
320+
@Test
321+
public void defaultTextColorCheckForInvisibleTextTest() throws IOException, InterruptedException {
322+
String outPdf = destinationFolder + "defaultColorCheckInvisibleText.pdf";
323+
String cmpPdf = cmpFolder + "cmp_pdfA2b_defaultColorCheckInvisibleText.pdf";
324+
325+
PdfDocument pdfDocument = new PdfADocument(new PdfWriter(outPdf), PdfAConformanceLevel.PDF_A_2B, null);
326+
PdfFont font = PdfFontFactory.createFont(sourceFolder + "FreeSans.ttf",
327+
"Identity-H", EmbeddingStrategy.FORCE_EMBEDDED);
328+
329+
PdfPage page = pdfDocument.addNewPage();
330+
PdfCanvas canvas = new PdfCanvas(page);
331+
canvas.saveState();
332+
canvas.beginText()
333+
.setTextRenderingMode(TextRenderingMode.INVISIBLE)
334+
.moveText(36, 750)
335+
.setFontAndSize(font, 16)
336+
.showText("some text")
337+
.endText()
338+
.restoreState();
339+
340+
pdfDocument.close();
341+
compareResult(outPdf, cmpPdf);
342+
}
343+
344+
@Test
345+
public void defaultStrokeColorCheckTest() throws IOException {
346+
String outPdf = destinationFolder + "defaultColorCheck.pdf";
347+
348+
PdfDocument pdfDocument = new PdfADocument(new PdfWriter(outPdf), PdfAConformanceLevel.PDF_A_2B, null);
349+
PdfPage page = pdfDocument.addNewPage();
350+
PdfCanvas canvas = new PdfCanvas(page);
351+
canvas.saveState();
352+
float[] whitePoint = {0.9505f, 1f, 1.089f};
353+
float[] gamma = {2.2f, 2.2f, 2.2f};
354+
float[] matrix = {0.4124f, 0.2126f, 0.0193f, 0.3576f, 0.7152f, 0.1192f, 0.1805f, 0.0722f, 0.9505f};
355+
PdfCieBasedCs.CalRgb calRgb = new PdfCieBasedCs.CalRgb(whitePoint, null, gamma, matrix);
356+
canvas.getResources().setDefaultRgb(calRgb);
357+
canvas.setFillColor(ColorConstants.BLUE);
358+
canvas.moveTo(pdfDocument.getDefaultPageSize().getLeft(), pdfDocument.getDefaultPageSize().getBottom());
359+
canvas.lineTo(pdfDocument.getDefaultPageSize().getRight(), pdfDocument.getDefaultPageSize().getBottom());
360+
canvas.lineTo(pdfDocument.getDefaultPageSize().getRight(), pdfDocument.getDefaultPageSize().getTop());
361+
canvas.stroke();
362+
363+
// We set fill color but stroked so the exception should be thrown
364+
Exception e = Assert.assertThrows(PdfAConformanceException.class, () -> pdfDocument.close());
365+
Assert.assertEquals(MessageFormatUtil.format(PdfAConformanceException.IF_DEVICE_RGB_CMYK_GRAY_USED_IN_FILE_THAT_FILE_SHALL_CONTAIN_PDFA_OUTPUTINTENT_OR_DEFAULT_RGB_CMYK_GRAY_IN_USAGE_CONTEXT),
366+
e.getMessage());
367+
}
368+
295369
@Test
296370
public void egsCheckTest1() throws IOException {
297371
PdfWriter writer = new PdfWriter(new ByteArrayOutputStream());

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,11 @@ public void transparentTextWithGroupColorSpaceTest() throws IOException, Interru
127127
String cmpPdf = cmpFolder + "cmp_transparencyAndCS.pdf";
128128

129129
PdfDocument pdfDocument = new PdfADocument(new PdfWriter(outPdf), PdfAConformanceLevel.PDF_A_3B, null);
130-
PdfPage page = pdfDocument.addNewPage();
131130
PdfFont font = PdfFontFactory.createFont(sourceFolder + "FreeSans.ttf",
132131
"Identity-H", EmbeddingStrategy.FORCE_EMBEDDED);
133132

133+
PdfPage page = pdfDocument.addNewPage();
134+
page.getResources().setDefaultGray(new PdfCieBasedCs.CalGray(getCalGrayArray()));
134135
PdfCanvas canvas = new PdfCanvas(page);
135136
canvas.saveState();
136137
PdfExtGState state = new PdfExtGState();
@@ -149,8 +150,8 @@ public void transparentTextWithGroupColorSpaceTest() throws IOException, Interru
149150
groupObj.put(PdfName.S, PdfName.Transparency);
150151
page.getPdfObject().put(PdfName.Group, groupObj);
151152

152-
153153
PdfPage page2 = pdfDocument.addNewPage();
154+
page2.getResources().setDefaultGray(new PdfCieBasedCs.CalGray(getCalGrayArray()));
154155
canvas = new PdfCanvas(page2);
155156
canvas.saveState();
156157
canvas.beginText()
@@ -230,6 +231,7 @@ public void testTransparencyObjectsAbsence() throws IOException, InterruptedExce
230231
"Identity-H", EmbeddingStrategy.FORCE_EMBEDDED);
231232

232233
PdfCanvas canvas = new PdfCanvas(page);
234+
page.getResources().setDefaultGray(new PdfCieBasedCs.CalGray(getCalGrayArray()));
233235
canvas.beginText()
234236
.moveText(36, 750)
235237
.setFontAndSize(font, 16)
@@ -241,8 +243,6 @@ public void testTransparencyObjectsAbsence() throws IOException, InterruptedExce
241243
groupObj.put(PdfName.S, PdfName.Transparency);
242244
page.getPdfObject().put(PdfName.Group, groupObj);
243245

244-
page.getResources().setDefaultGray(new PdfCieBasedCs.CalGray(getCalGrayArray()));
245-
246246
pdfDocument.close();
247247
compareResult(outPdf, cmpPdf);
248248
}

0 commit comments

Comments
 (0)