Skip to content

Commit 5a443fc

Browse files
author
Alexander Pliushchou
committed
Fix empty TR tag copying
DEVSIX-5974
1 parent 393980e commit 5a443fc

31 files changed

+175
-13
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.itextpdf.kernel.pdf.tagging;
2+
3+
import com.itextpdf.kernel.pdf.PdfDictionary;
4+
import com.itextpdf.kernel.pdf.tagutils.ITagTreeIteratorHandler;
5+
import com.itextpdf.kernel.pdf.tagutils.TagTreeIterator;
6+
7+
/**
8+
* Class that provides methods for searching mcr in tag tree.
9+
*/
10+
public final class McrCheckUtil {
11+
12+
/**
13+
* Creates a new {@link McrCheckUtil} instance.
14+
*/
15+
private McrCheckUtil() {
16+
// Empty constructor
17+
}
18+
19+
/**
20+
* Checks if tag structure of TR element contains any mcr.
21+
*
22+
* @param elementTR PdfDictionary of TR element.
23+
*
24+
* @return true if mcr found.
25+
*/
26+
public static boolean isTrContainsMcr(PdfDictionary elementTR) {
27+
TagTreeIterator tagTreeIterator = new TagTreeIterator(new PdfStructElem(elementTR));
28+
McrCheckUtil.McrTagHandler handler = new McrCheckUtil.McrTagHandler();
29+
tagTreeIterator.addHandler(handler);
30+
tagTreeIterator.traverse();
31+
return handler.tagTreeHaveMcr();
32+
}
33+
34+
/**
35+
* Search for mcr elements in the TagTree.
36+
*/
37+
private static class McrTagHandler implements ITagTreeIteratorHandler {
38+
39+
private boolean haveMcr = false;
40+
41+
/**
42+
* Method returns if tag tree has mcr in it.
43+
*/
44+
public boolean tagTreeHaveMcr() {
45+
return haveMcr;
46+
}
47+
48+
/**
49+
* Creates a new {@link McrTagHandler} instance.
50+
*/
51+
public McrTagHandler() {
52+
//empty constructor
53+
}
54+
55+
/**
56+
* {@inheritDoc}
57+
*/
58+
@Override
59+
public void nextElement(IStructureNode elem) {
60+
if ((elem instanceof PdfMcr)) {
61+
haveMcr = true;
62+
}
63+
}
64+
}
65+
}

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

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,13 @@ private static void copyTo(PdfDocument destDocument, int insertBeforePage, Map<P
244244
* @param page2page association between original page and copied page.
245245
* @param copyFromDestDocument indicates if <code>page2page</code> keys and values represent pages from {@code destDocument}.
246246
*/
247-
private static void copyTo(PdfDocument destDocument, Map<PdfPage, PdfPage> page2page, PdfDocument callingDocument, boolean copyFromDestDocument) {
247+
private static void copyTo(PdfDocument destDocument, Map<PdfPage, PdfPage> page2page, PdfDocument callingDocument
248+
, boolean copyFromDestDocument) {
248249
copyTo(destDocument, page2page, callingDocument, copyFromDestDocument, -1);
249250
}
250251

251-
private static void copyTo(PdfDocument destDocument, Map<PdfPage, PdfPage> page2page, PdfDocument callingDocument, boolean copyFromDestDocument, int insertIndex) {
252+
private static void copyTo(PdfDocument destDocument, Map<PdfPage, PdfPage> page2page, PdfDocument callingDocument
253+
, boolean copyFromDestDocument, int insertIndex) {
252254
CopyStructureResult copiedStructure = copyStructure(destDocument, page2page, callingDocument, copyFromDestDocument);
253255
PdfStructTreeRoot destStructTreeRoot = destDocument.getStructTreeRoot();
254256
destStructTreeRoot.makeIndirect(destDocument);
@@ -283,7 +285,8 @@ private static void copyTo(PdfDocument destDocument, Map<PdfPage, PdfPage> page2
283285
}
284286
}
285287

286-
private static CopyStructureResult copyStructure(PdfDocument destDocument, Map<PdfPage, PdfPage> page2page, PdfDocument callingDocument, boolean copyFromDestDocument) {
288+
private static CopyStructureResult copyStructure(PdfDocument destDocument, Map<PdfPage, PdfPage> page2page
289+
, PdfDocument callingDocument, boolean copyFromDestDocument) {
287290
PdfDocument fromDocument = copyFromDestDocument ? destDocument : callingDocument;
288291
Map<PdfDictionary, PdfDictionary> topsToFirstDestPage = new HashMap<>();
289292
Set<PdfObject> objectsToCopy = new HashSet<>();
@@ -380,16 +383,23 @@ private static PdfDictionary copyObject(PdfDictionary source, PdfDictionary dest
380383
}
381384

382385
PdfObject k = source.get(PdfName.K);
386+
PdfDictionary lastCopiedTrPage = null;
383387
if (k != null) {
384388
if (k.isArray()) {
385389
PdfArray kArr = (PdfArray) k;
386390
PdfArray newArr = new PdfArray();
387391
for (int i = 0; i < kArr.size(); i++) {
388-
PdfObject copiedKid = copyObjectKid(kArr.get(i), copied, destPage, parentChangePg, copyingParams);
392+
PdfObject copiedKid = copyObjectKid(kArr.get(i), copied, destPage, parentChangePg, copyingParams
393+
, lastCopiedTrPage);
389394
if (copiedKid != null) {
390395
newArr.add(copiedKid);
396+
if (copiedKid instanceof PdfDictionary
397+
&& PdfName.TR.equals(((PdfDictionary) copiedKid).getAsName(PdfName.S))) {
398+
lastCopiedTrPage = destPage;
399+
}
391400
}
392401
}
402+
393403
if (!newArr.isEmpty()) {
394404
if (newArr.size() == 1) {
395405
copied.put(PdfName.K, newArr.get(0));
@@ -398,7 +408,8 @@ private static PdfDictionary copyObject(PdfDictionary source, PdfDictionary dest
398408
}
399409
}
400410
} else {
401-
PdfObject copiedKid = copyObjectKid(k, copied, destPage, parentChangePg, copyingParams);
411+
PdfObject copiedKid = copyObjectKid(k, copied, destPage, parentChangePg, copyingParams
412+
, lastCopiedTrPage);
402413
if (copiedKid != null) {
403414
copied.put(PdfName.K, copiedKid);
404415
}
@@ -407,7 +418,9 @@ private static PdfDictionary copyObject(PdfDictionary source, PdfDictionary dest
407418
return copied;
408419
}
409420

410-
private static PdfObject copyObjectKid(PdfObject kid, PdfDictionary copiedParent, PdfDictionary destPage, boolean parentChangePg, StructElemCopyingParams copyingParams) {
421+
private static PdfObject copyObjectKid(PdfObject kid, PdfDictionary copiedParent, PdfDictionary destPage,
422+
boolean parentChangePg, StructElemCopyingParams copyingParams,
423+
PdfDictionary lastCopiedTrPage) {
411424
if (kid.isNumber()) {
412425
if (!parentChangePg) {
413426
copyingParams.getToDocument().getStructTreeRoot().getParentTreeHandler()
@@ -416,11 +429,27 @@ private static PdfObject copyObjectKid(PdfObject kid, PdfDictionary copiedParent
416429
}
417430
} else if (kid.isDictionary()) {
418431
PdfDictionary kidAsDict = (PdfDictionary) kid;
419-
// if element is TD and its parent is TR which was copied, then we copy it in any case
432+
//if element is TD and its parent is TR which was copied, then we copy it in any case
420433
if (copyingParams.getObjectsToCopy().contains(kidAsDict) ||
421434
shouldTableElementBeCopied(kidAsDict, copiedParent)) {
435+
//if TR element is not connected to any page,
436+
//it should be copied to the same page as the last copied TR which connected to page
437+
PdfDictionary destination = destPage;
438+
if (PdfName.TR.equals(kidAsDict.getAsName(PdfName.S))
439+
&& !copyingParams.getObjectsToCopy().contains(kidAsDict)) {
440+
if (McrCheckUtil.isTrContainsMcr(kidAsDict)){
441+
return null;
442+
}
443+
444+
if (lastCopiedTrPage == null) {
445+
return null;
446+
} else {
447+
destination = lastCopiedTrPage;
448+
}
449+
}
422450
boolean hasParent = kidAsDict.containsKey(PdfName.P);
423-
PdfDictionary copiedKid = copyObject(kidAsDict, destPage, parentChangePg, copyingParams);
451+
PdfDictionary copiedKid = copyObject(kidAsDict, destination, parentChangePg, copyingParams);
452+
424453
if (hasParent) {
425454
copiedKid.put(PdfName.P, copiedParent);
426455
} else {
@@ -446,8 +475,9 @@ private static PdfObject copyObjectKid(PdfObject kid, PdfDictionary copiedParent
446475
}
447476

448477
static boolean shouldTableElementBeCopied(PdfDictionary obj, PdfDictionary parent) {
449-
return (PdfName.TD.equals(obj.get(PdfName.S)) || PdfName.TH.equals(obj.get(PdfName.S)))
450-
&& PdfName.TR.equals(parent.get(PdfName.S));
478+
PdfName role = obj.getAsName(PdfName.S);
479+
return ((PdfName.TD.equals(role) || PdfName.TH.equals(role)) && PdfName.TR.equals(parent.get(PdfName.S)))
480+
|| PdfName.TR.equals(role);
451481
}
452482

453483
private static PdfDictionary copyNamespaceDict(PdfDictionary srcNsDict, StructElemCopyingParams copyingParams) {

kernel/src/test/java/com/itextpdf/kernel/pdf/tagging/StructureTreeCopierUnitTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,15 @@ public void shouldTableElementBeCopiedTrTdTest() {
7676
PdfDictionary obj = new PdfDictionary(tr);
7777
PdfDictionary parent = new PdfDictionary(td);
7878

79-
Assert.assertFalse(StructureTreeCopier.shouldTableElementBeCopied(obj, parent));
79+
Assert.assertTrue(StructureTreeCopier.shouldTableElementBeCopied(obj, parent));
8080
}
8181

8282
@Test
8383
public void shouldTableElementBeCopiedTrTrTest() {
8484
PdfDictionary obj = new PdfDictionary(tr);
8585
PdfDictionary parent = new PdfDictionary(tr);
8686

87-
Assert.assertFalse(StructureTreeCopier.shouldTableElementBeCopied(obj, parent));
87+
Assert.assertTrue(StructureTreeCopier.shouldTableElementBeCopied(obj, parent));
8888
}
8989

9090
@Test

kernel/src/test/java/com/itextpdf/kernel/utils/PdfMergerTest.java

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,78 @@ public void tdInsideTdTableTest() throws ParserConfigurationException, SAXExcept
275275
}
276276

277277
@Test
278-
// TODO DEVSIX-5974 Empty tr isn't copied.
279278
public void emptyTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
280279
mergeAndCompareTagStructures("emptyTrTable.pdf", 1, 1);
281280
}
282281

282+
@Test
283+
public void splitEmptyTrTableFirstPageTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
284+
mergeAndCompareTagStructures("splitTableWithEmptyTrFirstPage.pdf", 1, 1);
285+
}
286+
287+
@Test
288+
public void splitEmptyTrTableSecondPageTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
289+
mergeAndCompareTagStructures("splitTableWithEmptyTrSecondPage.pdf", 2, 2);
290+
}
291+
292+
@Test
293+
public void splitEmptyTrTableFullTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
294+
mergeAndCompareTagStructures("splitTableWithEmptyTrFull.pdf", 1, 2);
295+
}
296+
297+
@Test
298+
public void emptyFirstTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
299+
mergeAndCompareTagStructures("emptyFirstTrTable.pdf", 1, 1);
300+
}
301+
302+
@Test
303+
public void emptyLastTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
304+
mergeAndCompareTagStructures("emptyLastTrTable.pdf", 1, 1);
305+
}
306+
307+
@Test
308+
public void emptyTwoAdjacentTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
309+
mergeAndCompareTagStructures("emptyTwoAdjacentTrTable.pdf", 1, 1);
310+
}
311+
312+
@Test
313+
public void emptyAllTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
314+
mergeAndCompareTagStructures("emptyAllTrTable.pdf", 1, 1);
315+
}
316+
317+
@Test
318+
public void emptySingleTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
319+
mergeAndCompareTagStructures("emptySingleTrTable.pdf", 1, 1);
320+
}
321+
322+
@Test
323+
public void splitAndMergeEmptyTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
324+
String sourceFilename = sourceFolder + "splitTableWithEmptyTrFull.pdf";
325+
String firstPageFilename = destinationFolder + "firstPageDoc.pdf";
326+
String secondPageFilename = destinationFolder + "secondPageDoc.pdf";
327+
String resultFilename = destinationFolder + "splitAndMergeEmptyTrTable.pdf";
328+
String cmpFilename = sourceFolder + "cmp_splitAndMergeEmptyTrTable.pdf";
329+
330+
PdfDocument sourceDoc = new PdfDocument(new PdfReader(sourceFilename));
331+
332+
PdfDocument firstPageDoc = new PdfDocument(new PdfWriter(firstPageFilename));
333+
PdfMerger mergerFirstPage = new PdfMerger(firstPageDoc);
334+
mergerFirstPage.merge(sourceDoc, 1, 1);
335+
mergerFirstPage.close();
336+
337+
PdfDocument secondPageDoc = new PdfDocument(new PdfWriter(secondPageFilename));
338+
PdfMerger mergerSecondPage = new PdfMerger(secondPageDoc);
339+
mergerSecondPage.merge(sourceDoc, 2, 2);
340+
mergerSecondPage.close();
341+
342+
List<File> sources = new ArrayList<File>();
343+
sources.add(new File(firstPageFilename));
344+
sources.add(new File(secondPageFilename));
345+
mergePdfs(sources, resultFilename, new PdfMergerProperties(), false);
346+
347+
Assert.assertNull(new CompareTool().compareTagStructures(resultFilename, cmpFilename));
348+
}
349+
283350
@Test
284351
@LogMessages(messages = {@LogMessage(messageTemplate = IoLogMessageConstant.NAME_ALREADY_EXISTS_IN_THE_NAME_TREE, count = 2)})
285352
public void mergeOutlinesNamedDestinations() throws IOException, InterruptedException {

0 commit comments

Comments
 (0)