Skip to content

Commit bba9f55

Browse files
committed
Fix SO error while iterating struct tree root
DEVSIX-8373
1 parent 1f62986 commit bba9f55

File tree

16 files changed

+513
-52
lines changed

16 files changed

+513
-52
lines changed

kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/PdfStructTreeRoot.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ This file is part of the iText (R) project.
4545
import java.util.List;
4646
import java.util.Map;
4747
import java.util.concurrent.ConcurrentHashMap;
48+
49+
import com.itextpdf.kernel.pdf.tagutils.TagTreeIterator;
50+
import com.itextpdf.kernel.pdf.tagutils.TagTreeIteratorAvoidDuplicatesApprover;
51+
import com.itextpdf.kernel.pdf.tagutils.TagTreeIteratorFlusher;
4852
import org.slf4j.Logger;
4953
import org.slf4j.LoggerFactory;
5054

@@ -351,7 +355,7 @@ public void flush() {
351355
getPdfObject().put(PdfName.IDTree, this.idTree.buildTree().makeIndirect(getDocument()));
352356
}
353357
if (!getDocument().isAppendMode()) {
354-
flushAllKids(this);
358+
PdfStructTreeRoot.flushAllKids(this);
355359
}
356360
super.flush();
357361
}
@@ -521,13 +525,11 @@ protected boolean isWrappedObjectMustBeIndirect() {
521525
return true;
522526
}
523527

524-
private void flushAllKids(IStructureNode elem) {
525-
for (IStructureNode kid : elem.getKids()) {
526-
if (kid instanceof PdfStructElem && !((PdfStructElem) kid).isFlushed()) {
527-
flushAllKids(kid);
528-
((PdfStructElem) kid).flush();
529-
}
530-
}
528+
private static void flushAllKids(PdfStructTreeRoot elem) {
529+
TagTreeIterator iterator = new TagTreeIterator(
530+
elem, new TagTreeIteratorAvoidDuplicatesApprover(), TagTreeIterator.TreeTraversalOrder.POST_ORDER);
531+
iterator.addHandler(new TagTreeIteratorFlusher());
532+
iterator.traverse();
531533
}
532534

533535
private void ifKidIsStructElementAddToList(PdfObject kid, List<IStructureNode> kids) {

kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/ITagTreeIteratorHandler.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,4 @@ public interface ITagTreeIteratorHandler {
3636
* @param elem the next element
3737
*/
3838
void nextElement(IStructureNode elem);
39-
4039
}

kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/TagTreeIterator.java

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,44 +28,74 @@ This file is part of the iText (R) project.
2828

2929
import java.util.HashSet;
3030
import java.util.List;
31-
import java.util.Objects;
3231
import java.util.Set;
3332

3433
/**
3534
* This class is used to traverse the tag tree.
3635
* <p>
3736
*
38-
* There is a possibility to add a handler that will be called for specific events during the traversal.
37+
* There is a possibility to add a handler that will be called for the elements during the traversal.
3938
*/
4039
public class TagTreeIterator {
4140

42-
4341
private final IStructureNode pointer;
4442

4543
private final Set<ITagTreeIteratorHandler> handlerList;
4644

45+
private final TagTreeIteratorElementApprover approver;
46+
47+
private final TreeTraversalOrder traversalOrder;
48+
4749
/**
48-
* Creates a new instance of {@link TagTreeIterator}.
50+
* Creates a new instance of {@link TagTreeIterator}. It will use {@link TagTreeIteratorElementApprover} to filter
51+
* elements and TreeTraversalOrder.PRE_ORDER for tree traversal.
4952
*
5053
* @param tagTreePointer the tag tree pointer.
5154
*/
5255
public TagTreeIterator(IStructureNode tagTreePointer) {
56+
this(tagTreePointer, new TagTreeIteratorElementApprover(), TreeTraversalOrder.PRE_ORDER);
57+
}
58+
59+
/**
60+
* Creates a new instance of {@link TagTreeIterator}.
61+
*
62+
* @param tagTreePointer the tag tree pointer.
63+
* @param approver a filter that will be called to let iterator know whether some particular element
64+
* should be traversed or not.
65+
* @param traversalOrder an order in which the tree will be traversed.
66+
*/
67+
public TagTreeIterator(IStructureNode tagTreePointer, TagTreeIteratorElementApprover approver,
68+
TreeTraversalOrder traversalOrder) {
5369
if (tagTreePointer == null) {
5470
throw new IllegalArgumentException(
5571
MessageFormatUtil.format(KernelExceptionMessageConstant.ARG_SHOULD_NOT_BE_NULL, "tagTreepointer"));
5672
}
73+
if (approver == null) {
74+
throw new IllegalArgumentException(
75+
MessageFormatUtil.format(KernelExceptionMessageConstant.ARG_SHOULD_NOT_BE_NULL, "approver"));
76+
}
77+
if (traversalOrder == null) {
78+
throw new IllegalArgumentException(
79+
MessageFormatUtil.format(KernelExceptionMessageConstant.ARG_SHOULD_NOT_BE_NULL, "traversalOrder"));
80+
}
5781
this.pointer = tagTreePointer;
82+
this.traversalOrder = traversalOrder;
5883
handlerList = new HashSet<>();
84+
this.approver = approver;
5985
}
6086

6187
/**
62-
* Adds a handler that will be called for specific events during the traversal.
88+
* Adds a handler that will be called for the elements during the traversal.
6389
*
6490
* @param handler the handler.
6591
*
6692
* @return this {@link TagTreeIterator} instance.
6793
*/
6894
public TagTreeIterator addHandler(ITagTreeIteratorHandler handler) {
95+
if (handler == null) {
96+
throw new IllegalArgumentException(
97+
MessageFormatUtil.format(KernelExceptionMessageConstant.ARG_SHOULD_NOT_BE_NULL, "handler"));
98+
}
6999
this.handlerList.add(handler);
70100
return this;
71101
}
@@ -77,22 +107,45 @@ public TagTreeIterator addHandler(ITagTreeIteratorHandler handler) {
77107
* Make sure the correct handlers are added before calling this method.
78108
*/
79109
public void traverse() {
80-
traverse(this.pointer, this.handlerList);
110+
traverse(this.pointer);
81111
}
82112

83-
private static void traverse(IStructureNode elem, Set<ITagTreeIteratorHandler> handlerList) {
84-
if (elem == null) {
113+
private void traverse(IStructureNode elem) {
114+
if (!approver.approve(elem)) {
85115
return;
86116
}
87-
for (ITagTreeIteratorHandler handler : handlerList) {
88-
handler.nextElement(elem);
117+
118+
if (traversalOrder == TreeTraversalOrder.PRE_ORDER) {
119+
for (ITagTreeIteratorHandler handler : handlerList) {
120+
handler.nextElement(elem);
121+
}
89122
}
123+
90124
List<IStructureNode> kids = elem.getKids();
91125
if (kids != null) {
92126
for (IStructureNode kid : kids) {
93-
traverse(kid, handlerList);
127+
traverse(kid);
128+
}
129+
}
130+
131+
if (traversalOrder == TreeTraversalOrder.POST_ORDER) {
132+
for (ITagTreeIteratorHandler handler : handlerList) {
133+
handler.nextElement(elem);
94134
}
95135
}
96136
}
97137

138+
/**
139+
* Tree traversal order enum.
140+
*/
141+
public enum TreeTraversalOrder {
142+
/**
143+
* Preorder traversal.
144+
*/
145+
PRE_ORDER,
146+
/**
147+
* Postorder traversal.
148+
*/
149+
POST_ORDER
150+
}
98151
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2024 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.kernel.pdf.tagutils;
24+
25+
import com.itextpdf.kernel.pdf.PdfObject;
26+
import com.itextpdf.kernel.pdf.tagging.IStructureNode;
27+
import com.itextpdf.kernel.pdf.tagging.PdfStructElem;
28+
import com.itextpdf.kernel.pdf.tagging.PdfStructTreeRoot;
29+
30+
import java.util.HashSet;
31+
import java.util.Set;
32+
33+
/**
34+
* Element checker for {@link TagTreeIterator}.
35+
* It is used to check whether specific element should be traversed.
36+
* It doesn't approve elements which have been traversed before.
37+
*/
38+
public class TagTreeIteratorAvoidDuplicatesApprover extends TagTreeIteratorElementApprover {
39+
private final Set<PdfObject> processedObjects = new HashSet<>();
40+
41+
/**
42+
* Creates a new instance of {@link TagTreeIteratorAvoidDuplicatesApprover}
43+
*/
44+
public TagTreeIteratorAvoidDuplicatesApprover() {
45+
super();
46+
}
47+
48+
/**
49+
* {@inheritDoc}
50+
*/
51+
@Override
52+
public boolean approve(IStructureNode elem) {
53+
if (elem instanceof PdfStructTreeRoot) {
54+
return true;
55+
}
56+
57+
if (!super.approve(elem) || !(elem instanceof PdfStructElem)) {
58+
return false;
59+
}
60+
61+
PdfObject obj = ((PdfStructElem) elem).getPdfObject();
62+
final boolean isProcessed = processedObjects.contains(obj);
63+
if (isProcessed) {
64+
return false;
65+
} else {
66+
processedObjects.add(obj);
67+
return true;
68+
}
69+
}
70+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2024 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.kernel.pdf.tagutils;
24+
25+
import com.itextpdf.kernel.pdf.tagging.IStructureNode;
26+
27+
/**
28+
* Element checker for {@link TagTreeIterator}.
29+
* It is used to check whether specific element should be traversed.
30+
*/
31+
public class TagTreeIteratorElementApprover {
32+
33+
/**
34+
* Creates a new instance of {@link TagTreeIteratorElementApprover}
35+
*/
36+
public TagTreeIteratorElementApprover() {
37+
// Empty constructor
38+
}
39+
40+
/**
41+
* Checks whether the element should be traversed.
42+
*
43+
* @param elem the element to check
44+
* @return {@code true} if the element should be traversed, {@code false otherwise}
45+
*/
46+
public boolean approve(IStructureNode elem) {
47+
return elem != null;
48+
}
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2024 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.kernel.pdf.tagutils;
24+
25+
import com.itextpdf.kernel.pdf.tagging.IStructureNode;
26+
import com.itextpdf.kernel.pdf.tagging.PdfStructElem;
27+
28+
/**
29+
* Class that flushes struct elements while iterating over struct tree root with {@link TagTreeIterator}.
30+
*/
31+
public class TagTreeIteratorFlusher implements ITagTreeIteratorHandler {
32+
33+
/**
34+
* Creates a new instance of {@link TagTreeIteratorFlusher}
35+
*/
36+
public TagTreeIteratorFlusher() {
37+
// Empty constructor
38+
}
39+
40+
/**
41+
* {@inheritDoc}
42+
*/
43+
@Override
44+
public void nextElement(IStructureNode elem) {
45+
if (elem instanceof PdfStructElem && !((PdfStructElem) elem).isFlushed()) {
46+
((PdfStructElem) elem).flush();
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)