Skip to content

Commit 56cede9

Browse files
committed
Add PDF/UA1 Graphics checks
DEVSIX-7999
1 parent 303cf59 commit 56cede9

File tree

15 files changed

+674
-10
lines changed

15 files changed

+674
-10
lines changed

kernel/src/main/java/com/itextpdf/kernel/pdf/IsoKey.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,6 @@ public enum IsoKey {
4444
FONT,
4545
// PDF/UA Enums
4646
CANVAS_BEGIN_MARKED_CONTENT,
47-
CANVAS_WRITING_CONTENT
47+
CANVAS_WRITING_CONTENT,
48+
LAYOUT
4849
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.itextpdf.kernel.pdf.tagutils;
2+
3+
import com.itextpdf.kernel.pdf.tagging.IStructureNode;
4+
5+
/**
6+
* Handler for {@link TagTreeIterator}.
7+
* Is used to handle specific events during the traversal.
8+
*/
9+
public interface ITagTreeIteratorHandler {
10+
11+
/**
12+
* Called when the next element is reached during the traversal.
13+
*
14+
* @param elem the next element
15+
*/
16+
void nextElement(IStructureNode elem);
17+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.itextpdf.kernel.pdf.tagutils;
2+
3+
import com.itextpdf.commons.utils.MessageFormatUtil;
4+
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
5+
import com.itextpdf.kernel.pdf.tagging.IStructureNode;
6+
7+
import java.util.HashSet;
8+
import java.util.List;
9+
import java.util.Objects;
10+
import java.util.Set;
11+
12+
/**
13+
* This class is used to traverse the tag tree.
14+
* <p>
15+
*
16+
* There is a possibility to add a handler that will be called for specific events during the traversal.
17+
*/
18+
public class TagTreeIterator {
19+
20+
21+
private final IStructureNode pointer;
22+
23+
private final Set<ITagTreeIteratorHandler> handlerList;
24+
25+
/**
26+
* Creates a new instance of {@link TagTreeIterator}.
27+
*
28+
* @param tagTreePointer the tag tree pointer.
29+
*/
30+
public TagTreeIterator(IStructureNode tagTreePointer) {
31+
if (tagTreePointer == null) {
32+
throw new IllegalArgumentException(
33+
MessageFormatUtil.format(KernelExceptionMessageConstant.ARG_SHOULD_NOT_BE_NULL, "tagTreepointer"));
34+
}
35+
this.pointer = tagTreePointer;
36+
handlerList = new HashSet<>();
37+
}
38+
39+
/**
40+
* Adds a handler that will be called for specific events during the traversal.
41+
*
42+
* @param handler the handler.
43+
*
44+
* @return this {@link TagTreeIterator} instance.
45+
*/
46+
public TagTreeIterator addHandler(ITagTreeIteratorHandler handler) {
47+
this.handlerList.add(handler);
48+
return this;
49+
}
50+
51+
/**
52+
* Traverses the tag tree in the order of the document structure.
53+
* <p>
54+
*
55+
* Make sure the correct handlers are added before calling this method.
56+
*/
57+
public void traverse() {
58+
traverse(this.pointer, this.handlerList);
59+
}
60+
61+
private static void traverse(IStructureNode elem, Set<ITagTreeIteratorHandler> handlerList) {
62+
for (ITagTreeIteratorHandler handler : handlerList) {
63+
handler.nextElement(elem);
64+
}
65+
List<IStructureNode> kids = elem.getKids();
66+
if (kids != null) {
67+
for (IStructureNode kid : kids) {
68+
traverse(kid, handlerList);
69+
}
70+
}
71+
}
72+
73+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.itextpdf.kernel.pdf.tagutils;
2+
3+
import com.itextpdf.commons.utils.MessageFormatUtil;
4+
import com.itextpdf.io.source.ByteArrayOutputStream;
5+
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
6+
import com.itextpdf.kernel.pdf.PdfDocument;
7+
import com.itextpdf.kernel.pdf.PdfName;
8+
import com.itextpdf.kernel.pdf.PdfWriter;
9+
import com.itextpdf.kernel.pdf.WriterProperties;
10+
import com.itextpdf.kernel.pdf.tagging.IStructureNode;
11+
import com.itextpdf.kernel.pdf.tagging.StandardRoles;
12+
import com.itextpdf.test.ExtendedITextTest;
13+
import com.itextpdf.test.annotations.type.UnitTest;
14+
15+
import java.util.ArrayList;
16+
import java.util.List;
17+
import org.junit.Assert;
18+
import org.junit.Test;
19+
import org.junit.experimental.categories.Category;
20+
21+
@Category(UnitTest.class)
22+
public class TagTreeIteratorTest extends ExtendedITextTest {
23+
24+
25+
@Test
26+
public void tagTreeIteratorTagPointerNull() {
27+
String errorMessage =
28+
MessageFormatUtil.format(KernelExceptionMessageConstant.ARG_SHOULD_NOT_BE_NULL, "tagTreepointer");
29+
Exception e = Assert.assertThrows(IllegalArgumentException.class, () -> new TagTreeIterator(null));
30+
Assert.assertEquals(e.getMessage(), errorMessage);
31+
}
32+
33+
@Test
34+
public void traversalWithoutElements() {
35+
PdfDocument doc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream(), new WriterProperties()));
36+
doc.setTagged();
37+
TagTreeIterator iterator = new TagTreeIterator(doc.getStructTreeRoot());
38+
TestHandler handler = new TestHandler();
39+
iterator.addHandler(handler);
40+
iterator.traverse();
41+
Assert.assertEquals(1, handler.nodes.size());
42+
}
43+
44+
45+
@Test
46+
public void traversalWithSomeElements() {
47+
PdfDocument doc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream(), new WriterProperties()));
48+
doc.setTagged();
49+
TagTreePointer tp = new TagTreePointer(doc);
50+
51+
tp.addTag(StandardRoles.DIV);
52+
tp.addTag(StandardRoles.P);
53+
tp.addTag(StandardRoles.FIGURE);
54+
tp.moveToParent();
55+
tp.addTag(StandardRoles.DIV);
56+
tp.addTag(StandardRoles.CODE);
57+
58+
TagTreeIterator iterator = new TagTreeIterator(doc.getStructTreeRoot());
59+
TestHandler handler = new TestHandler();
60+
61+
iterator.addHandler(handler);
62+
iterator.traverse();
63+
Assert.assertEquals(7, handler.nodes.size());
64+
Assert.assertNull(handler.nodes.get(0).getRole());
65+
Assert.assertEquals(PdfName.Document, handler.nodes.get(1).getRole());
66+
Assert.assertEquals(PdfName.Div, handler.nodes.get(2).getRole());
67+
Assert.assertEquals(PdfName.P, handler.nodes.get(3).getRole());
68+
Assert.assertEquals(PdfName.Figure, handler.nodes.get(4).getRole());
69+
Assert.assertEquals(PdfName.Div, handler.nodes.get(5).getRole());
70+
Assert.assertEquals(PdfName.Code, handler.nodes.get(6).getRole());
71+
}
72+
73+
static class TestHandler implements ITagTreeIteratorHandler {
74+
75+
final List<IStructureNode> nodes = new ArrayList<>();
76+
77+
@Override
78+
public void nextElement(IStructureNode elem) {
79+
nodes.add(elem);
80+
}
81+
}
82+
83+
84+
}

layout/src/main/java/com/itextpdf/layout/RootElement.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.kernel.font.PdfFont;
2626
import com.itextpdf.kernel.font.PdfFontFactory;
27+
import com.itextpdf.kernel.pdf.IsoKey;
2728
import com.itextpdf.kernel.pdf.PdfDocument;
2829
import com.itextpdf.kernel.pdf.canvas.PdfCanvasConstants;
2930
import com.itextpdf.kernel.pdf.tagging.StandardRoles;
@@ -344,6 +345,7 @@ protected void createAndAddRendererSubTree(IElement element) {
344345
taggingHelper.addKidsHint(pdfDocument.getTagStructureContext().getAutoTaggingPointer(), Collections.<IRenderer>singletonList(rendererSubTreeRoot));
345346
}
346347
ensureRootRendererNotNull().addChild(rendererSubTreeRoot);
348+
traverseAndCallIso(pdfDocument, rendererSubTreeRoot);
347349
}
348350

349351
private LayoutTaggingHelper initTaggingHelperIfNeeded() {
@@ -358,4 +360,18 @@ private T addElement(IElement element) {
358360
}
359361
return (T) (Object) this;
360362
}
363+
364+
private static void traverseAndCallIso(PdfDocument pdfDocument, IRenderer renderer) {
365+
if (renderer == null) {
366+
return;
367+
}
368+
pdfDocument.checkIsoConformance(renderer.getModelElement(), IsoKey.LAYOUT);
369+
List<IRenderer> renderers = renderer.getChildRenderers();
370+
if (renderers == null) {
371+
return;
372+
}
373+
for (IRenderer childRenderer : renderers) {
374+
traverseAndCallIso(pdfDocument, childRenderer);
375+
}
376+
}
361377
}

pdfua/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
<groupId>com.itextpdf</groupId>
2222
<artifactId>layout</artifactId>
2323
<version>${project.version}</version>
24-
<scope>test</scope>
2524
</dependency>
2625
<dependency>
2726
<groupId>com.itextpdf</groupId>
2827
<artifactId>pdftest</artifactId>
2928
<version>${project.version}</version>
3029
<scope>test</scope>
3130
</dependency>
31+
3232
</dependencies>
3333
</project>

pdfua/src/main/java/com/itextpdf/pdfua/checkers/PdfUA1Checker.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ This file is part of the iText (R) project.
4040
import com.itextpdf.kernel.pdf.tagging.StandardRoles;
4141
import com.itextpdf.kernel.pdf.tagutils.IRoleMappingResolver;
4242
import com.itextpdf.kernel.pdf.tagutils.TagStructureContext;
43+
import com.itextpdf.kernel.pdf.tagutils.TagTreeIterator;
44+
import com.itextpdf.kernel.pdf.tagutils.TagTreePointer;
4345
import com.itextpdf.kernel.utils.IValidationChecker;
4446
import com.itextpdf.kernel.utils.ValidationContext;
47+
import com.itextpdf.pdfua.checkers.utils.GraphicsCheckUtil;
48+
import com.itextpdf.pdfua.checkers.utils.LayoutCheckUtil;
4549
import com.itextpdf.pdfua.exceptions.PdfUAConformanceException;
4650
import com.itextpdf.pdfua.exceptions.PdfUAExceptionMessageConstants;
4751

@@ -54,7 +58,6 @@ This file is part of the iText (R) project.
5458
/**
5559
* The class defines the requirements of the PDF/UA-1 standard.
5660
* <p>
57-
*
5861
* The specification implemented by this class is ISO 14289-1
5962
*/
6063
public class PdfUA1Checker implements IValidationChecker {
@@ -89,6 +92,9 @@ public void validateDocument(ValidationContext validationContext) {
8992
@Override
9093
public void validateObject(Object obj, IsoKey key, PdfResources resources, PdfStream contentStream, Object extra) {
9194
switch (key) {
95+
case LAYOUT:
96+
LayoutCheckUtil.checkLayoutElements(obj);
97+
break;
9298
case CANVAS_WRITING_CONTENT:
9399
checkOnWritingCanvasToContent(obj);
94100
break;
@@ -98,18 +104,22 @@ public void validateObject(Object obj, IsoKey key, PdfResources resources, PdfSt
98104
}
99105
}
100106

107+
101108
private void checkOnWritingCanvasToContent(Object data) {
102109
Stack<Tuple2<PdfName, PdfDictionary>> tagStack = getTagStack(data);
103-
if (tagStack.isEmpty() ) {
104-
throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.TAG_HASNT_BEEN_ADDED_BEFORE_CONTENT_ADDING);
110+
if (tagStack.isEmpty()) {
111+
throw new PdfUAConformanceException(
112+
PdfUAExceptionMessageConstants.TAG_HASNT_BEEN_ADDED_BEFORE_CONTENT_ADDING);
105113
}
106114

107115
final boolean insideRealContent = isInsideRealContent(tagStack);
108116
final boolean insideArtifact = isInsideArtifact(tagStack);
109117
if (insideRealContent && insideArtifact) {
110-
throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.REAL_CONTENT_INSIDE_ARTIFACT_OR_VICE_VERSA);
118+
throw new PdfUAConformanceException(
119+
PdfUAExceptionMessageConstants.REAL_CONTENT_INSIDE_ARTIFACT_OR_VICE_VERSA);
111120
} else if (!insideRealContent && !insideArtifact) {
112-
throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.CONTENT_IS_NOT_REAL_CONTENT_AND_NOT_ARTIFACT);
121+
throw new PdfUAConformanceException(
122+
PdfUAExceptionMessageConstants.CONTENT_IS_NOT_REAL_CONTENT_AND_NOT_ARTIFACT);
113123
}
114124
}
115125

@@ -140,9 +150,11 @@ private void checkOnOpeningBeginMarkedContent(Object obj, Object extra) {
140150
private void checkStandardRoleMapping(Tuple2<PdfName, PdfDictionary> tag) {
141151
final PdfNamespace namespace = tagStructureContext.getDocumentDefaultNamespace();
142152
final String role = tag.getFirst().getValue();
143-
if (!StandardRoles.ARTIFACT.equals(role) && !tagStructureContext.checkIfRoleShallBeMappedToStandardRole(role, namespace)) {
153+
if (!StandardRoles.ARTIFACT.equals(role) && !tagStructureContext.checkIfRoleShallBeMappedToStandardRole(role,
154+
namespace)) {
144155
throw new PdfUAConformanceException(
145-
MessageFormatUtil.format(PdfUAExceptionMessageConstants.TAG_MAPPING_DOESNT_TERMINATE_WITH_STANDARD_TYPE, role));
156+
MessageFormatUtil.format(
157+
PdfUAExceptionMessageConstants.TAG_MAPPING_DOESNT_TERMINATE_WITH_STANDARD_TYPE, role));
146158
}
147159
}
148160

@@ -172,7 +184,8 @@ private boolean isRealContent(Tuple2<PdfName, PdfDictionary> tag) {
172184
if (properties == null || !properties.containsKey(PdfName.MCID)) {
173185
return false;
174186
}
175-
PdfMcr mcr = this.pdfDocument.getStructTreeRoot().findMcrByMcid(pdfDocument, (int) properties.getAsInt(PdfName.MCID));
187+
PdfMcr mcr = this.pdfDocument.getStructTreeRoot()
188+
.findMcrByMcid(pdfDocument, (int) properties.getAsInt(PdfName.MCID));
176189
if (mcr == null) {
177190
throw new PdfUAConformanceException(
178191
PdfUAExceptionMessageConstants.CONTENT_WITH_MCID_BUT_MCID_NOT_FOUND_IN_STRUCT_TREE_ROOT);
@@ -207,6 +220,12 @@ private void checkStructureTreeRoot(PdfStructTreeRoot structTreeRoot) {
207220
throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.ONE_OR_MORE_STANDARD_ROLE_REMAPPED);
208221
}
209222
}
223+
224+
TagTreeIterator tagTreeIterator = new TagTreeIterator(structTreeRoot);
225+
tagTreeIterator.addHandler(GraphicsCheckUtil.createFigureTagHandler());
226+
tagTreeIterator.traverse();
227+
228+
210229
}
211230

212231
private void checkFonts(Collection<PdfFont> fontsInDocument) {

0 commit comments

Comments
 (0)