diff --git a/itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfDocumentTest.cs b/itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfDocumentTest.cs index 272f412bc7..32c9b14ac2 100644 --- a/itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfDocumentTest.cs +++ b/itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfDocumentTest.cs @@ -529,6 +529,62 @@ public virtual void GetDefaultConformanceLevelTest() { NUnit.Framework.Assert.IsNull(document.GetConformanceLevel()); } + //TODO DEVSIX-8490 remove this test when implemented + [NUnit.Framework.Test] + [LogMessage(KernelLogMessageConstant.DUPLICATE_ENTRIES_IN_ORDER_ARRAY_REMOVED)] + public virtual void RemoveDuplicatesInOrderArrayTest() { + String inputPdf = "removeDuplicatesInOrderArray.pdf"; + String outputPdf = "removedDuplicateInOrderArray.pdf"; + PdfDocument doc = new PdfDocument(new PdfReader(SOURCE_FOLDER + inputPdf), CompareTool.CreateTestPdfWriter + (DESTINATION_FOLDER + outputPdf)); + //Need to update OCProperties + doc.GetCatalog().GetOCProperties(false); + doc.Close(); + NUnit.Framework.Assert.IsNull(new CompareTool().CompareByContent(DESTINATION_FOLDER + outputPdf, SOURCE_FOLDER + + "cmp_" + outputPdf, DESTINATION_FOLDER)); + } + + //TODO DEVSIX-8490 remove this test when implemented + [NUnit.Framework.Test] + [LogMessage(KernelLogMessageConstant.DUPLICATE_ENTRIES_IN_ORDER_ARRAY_REMOVED)] + public virtual void RemoveNestedDuplicatesInOrderArrayTest() { + String inputPdf = "removeNestedDuplicatesInOrderArray.pdf"; + String outputPdf = "removedNestedDuplicatesInOrderArray.pdf"; + PdfDocument doc = new PdfDocument(new PdfReader(SOURCE_FOLDER + inputPdf), new PdfWriter(DESTINATION_FOLDER + + outputPdf)); + //Need to update OCProperties + doc.GetCatalog().GetOCProperties(false); + doc.Close(); + NUnit.Framework.Assert.IsNull(new CompareTool().CompareByContent(DESTINATION_FOLDER + outputPdf, SOURCE_FOLDER + + "cmp_" + outputPdf, DESTINATION_FOLDER)); + } + + //TODO DEVSIX-8490 remove this test when implemented + [NUnit.Framework.Test] + public virtual void RemoveDuplicatesHasChildInOrderArrayTest() { + String inputPdf = "removeDuplicatesHasChildInOrderArray.pdf"; + String outputPdf = "removedDuplicatesHasChildInOrderArray.pdf"; + PdfDocument doc = new PdfDocument(new PdfReader(SOURCE_FOLDER + inputPdf), CompareTool.CreateTestPdfWriter + (DESTINATION_FOLDER + outputPdf)); + PdfCatalog catalog = doc.GetCatalog(); + Exception e = NUnit.Framework.Assert.Catch(typeof(PdfException), () => catalog.GetOCProperties(false)); + NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(KernelExceptionMessageConstant.UNABLE_TO_REMOVE_DUPLICATE_LAYER + , "4 0 R"), e.Message); + } + + //TODO DEVSIX-8490 remove this test when implemented + [NUnit.Framework.Test] + public virtual void RemoveNestedDuplicatesHasChildInOrderArrayTest() { + String inputPdf = "removeNestedDuplicatesHasChildInOrderArray.pdf"; + String outputPdf = "removedNestedDuplicatesHasChildInOrderArray.pdf"; + PdfDocument doc = new PdfDocument(new PdfReader(SOURCE_FOLDER + inputPdf), CompareTool.CreateTestPdfWriter + (DESTINATION_FOLDER + outputPdf)); + PdfCatalog catalog = doc.GetCatalog(); + Exception e = NUnit.Framework.Assert.Catch(typeof(PdfException), () => catalog.GetOCProperties(false)); + NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(KernelExceptionMessageConstant.UNABLE_TO_REMOVE_DUPLICATE_LAYER + , "27 0 R"), e.Message); + } + private class IgnoreTagStructurePdfDocument : PdfDocument { //\cond DO_NOT_DOCUMENT internal IgnoreTagStructurePdfDocument(PdfReader reader) diff --git a/itext.tests/itext.kernel.tests/itext/kernel/pdf/layer/PdfLayerTest.cs b/itext.tests/itext.kernel.tests/itext/kernel/pdf/layer/PdfLayerTest.cs index b529d1b5dd..5a1073eb2d 100644 --- a/itext.tests/itext.kernel.tests/itext/kernel/pdf/layer/PdfLayerTest.cs +++ b/itext.tests/itext.kernel.tests/itext/kernel/pdf/layer/PdfLayerTest.cs @@ -24,6 +24,8 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using iText.Commons.Utils; using iText.IO.Font.Constants; +using iText.IO.Source; +using iText.Kernel.Exceptions; using iText.Kernel.Font; using iText.Kernel.Pdf; using iText.Kernel.Pdf.Canvas; @@ -354,5 +356,22 @@ public virtual void TestInStamperMode2() { NUnit.Framework.Assert.IsNull(new CompareTool().CompareByContent(destinationFolder + "output_layered.pdf", sourceFolder + "cmp_output_layered.pdf", destinationFolder, "diff")); } + + //TODO DEVSIX-8490 remove this test when implemented + [NUnit.Framework.Test] + public virtual void AddSecondParentlayerTest() { + using (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + using (PdfDocument doc = new PdfDocument(new PdfWriter(outputStream))) { + PdfLayer childLayer = new PdfLayer("childLayer", doc); + PdfLayer parentLayer1 = new PdfLayer("firstParentLayer", doc); + PdfLayer parentLayer2 = new PdfLayer("secondParentLayer", doc); + parentLayer1.AddChild(childLayer); + PdfIndirectReference @ref = childLayer.GetIndirectReference(); + Exception e = NUnit.Framework.Assert.Catch(typeof(PdfException), () => parentLayer2.AddChild(childLayer)); + NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(KernelExceptionMessageConstant.UNABLE_TO_ADD_SECOND_PARENT_LAYER + , @ref.ToString()), e.Message); + } + } + } } } diff --git a/itext.tests/itext.kernel.tests/itext/kernel/pdf/layer/PdfOCPropertiesUnitTest.cs b/itext.tests/itext.kernel.tests/itext/kernel/pdf/layer/PdfOCPropertiesUnitTest.cs new file mode 100644 index 0000000000..f2a257799f --- /dev/null +++ b/itext.tests/itext.kernel.tests/itext/kernel/pdf/layer/PdfOCPropertiesUnitTest.cs @@ -0,0 +1,110 @@ +/* +This file is part of the iText (R) project. +Copyright (c) 1998-2024 Apryse Group NV +Authors: Apryse Software. + +This program is offered under a commercial and under the AGPL license. +For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. + +AGPL licensing: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +using System; +using System.IO; +using iText.Commons.Utils; +using iText.IO.Source; +using iText.Kernel.Exceptions; +using iText.Kernel.Pdf; + +namespace iText.Kernel.Pdf.Layer { + [NUnit.Framework.Category("UnitTest")] + public class PdfOCPropertiesUnitTest { + //TODO DEVSIX-8490 remove this test when implemented + [NUnit.Framework.Test] + public virtual void RemoveOrderDuplicatesTest() { + byte[] docBytes; + using (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + using (PdfDocument document = new PdfDocument(new PdfWriter(outputStream))) { + PdfDictionary ocgDic = new PdfDictionary(); + ocgDic.MakeIndirect(document); + PdfArray orderArray = new PdfArray(); + for (int i = 0; i < 3; i++) { + orderArray.Add(ocgDic); + } + PdfDictionary ocgDic2 = new PdfDictionary(); + ocgDic.MakeIndirect(document); + for (int i = 0; i < 3; i++) { + PdfArray layerArray = new PdfArray(); + layerArray.Add(new PdfString("layerName" + i)); + layerArray.Add(ocgDic2); + orderArray.Add(layerArray); + } + PdfDictionary DDictionary = new PdfDictionary(); + DDictionary.Put(PdfName.Order, orderArray); + PdfArray OCGsArray = new PdfArray(); + OCGsArray.Add(ocgDic); + OCGsArray.Add(ocgDic2); + PdfDictionary OCPropertiesDic = new PdfDictionary(); + OCPropertiesDic.Put(PdfName.D, DDictionary); + OCPropertiesDic.Put(PdfName.OCGs, OCGsArray); + document.GetCatalog().GetPdfObject().Put(PdfName.OCProperties, OCPropertiesDic); + document.GetCatalog().GetOCProperties(false); + } + docBytes = outputStream.ToArray(); + } + using (PdfDocument docReopen = new PdfDocument(new PdfReader(new MemoryStream(docBytes)))) { + PdfArray resultArray = docReopen.GetCatalog().GetPdfObject().GetAsDictionary(PdfName.OCProperties).GetAsDictionary + (PdfName.D).GetAsArray(PdfName.Order); + NUnit.Framework.Assert.AreEqual(2, resultArray.Size()); + } + } + + //TODO DEVSIX-8490 remove this test when implemented + [NUnit.Framework.Test] + public virtual void RemoveOrderDuplicateHasChildTest() { + using (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + using (PdfDocument document = new PdfDocument(new PdfWriter(outputStream))) { + PdfDictionary ocgDic = new PdfDictionary(); + PdfDictionary ocgDicChild1 = new PdfDictionary(); + PdfDictionary ocgDicChild2 = new PdfDictionary(); + ocgDic.MakeIndirect(document); + PdfArray orderArray = new PdfArray(); + PdfArray childArray1 = new PdfArray(); + childArray1.Add(ocgDicChild1); + PdfArray childArray2 = new PdfArray(); + childArray2.Add(ocgDicChild2); + orderArray.Add(ocgDic); + orderArray.Add(childArray1); + orderArray.Add(ocgDic); + orderArray.Add(childArray2); + PdfDictionary DDictionary = new PdfDictionary(); + DDictionary.Put(PdfName.Order, orderArray); + PdfArray OCGsArray = new PdfArray(); + OCGsArray.Add(ocgDic); + OCGsArray.Add(ocgDicChild1); + OCGsArray.Add(ocgDicChild2); + PdfDictionary OCPropertiesDic = new PdfDictionary(); + OCPropertiesDic.Put(PdfName.D, DDictionary); + OCPropertiesDic.Put(PdfName.OCGs, OCGsArray); + document.GetCatalog().GetPdfObject().Put(PdfName.OCProperties, OCPropertiesDic); + PdfIndirectReference @ref = ocgDic.GetIndirectReference(); + PdfCatalog catalog = document.GetCatalog(); + Exception e = NUnit.Framework.Assert.Catch(typeof(PdfException), () => catalog.GetOCProperties(false)); + NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(KernelExceptionMessageConstant.UNABLE_TO_REMOVE_DUPLICATE_LAYER + , @ref.ToString()), e.Message); + } + } + } + } +} diff --git a/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/cmp_removedDuplicateInOrderArray.pdf b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/cmp_removedDuplicateInOrderArray.pdf new file mode 100644 index 0000000000..602e3a4c81 Binary files /dev/null and b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/cmp_removedDuplicateInOrderArray.pdf differ diff --git a/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/cmp_removedNestedDuplicatesInOrderArray.pdf b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/cmp_removedNestedDuplicatesInOrderArray.pdf new file mode 100644 index 0000000000..9da2248dfa Binary files /dev/null and b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/cmp_removedNestedDuplicatesInOrderArray.pdf differ diff --git a/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeDuplicatesHasChildInOrderArray.pdf b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeDuplicatesHasChildInOrderArray.pdf new file mode 100644 index 0000000000..2fa10d0386 Binary files /dev/null and b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeDuplicatesHasChildInOrderArray.pdf differ diff --git a/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeDuplicatesInOrderArray.pdf b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeDuplicatesInOrderArray.pdf new file mode 100644 index 0000000000..5afded545d Binary files /dev/null and b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeDuplicatesInOrderArray.pdf differ diff --git a/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeNestedDuplicatesHasChildInOrderArray.pdf b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeNestedDuplicatesHasChildInOrderArray.pdf new file mode 100644 index 0000000000..754950dda9 Binary files /dev/null and b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeNestedDuplicatesHasChildInOrderArray.pdf differ diff --git a/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeNestedDuplicatesInOrderArray.pdf b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeNestedDuplicatesInOrderArray.pdf new file mode 100644 index 0000000000..1f43c432c2 Binary files /dev/null and b/itext.tests/itext.kernel.tests/resources/itext/kernel/pdf/PdfDocumentTest/removeNestedDuplicatesInOrderArray.pdf differ diff --git a/itext/itext.kernel/itext/kernel/exceptions/KernelExceptionMessageConstant.cs b/itext/itext.kernel/itext/kernel/exceptions/KernelExceptionMessageConstant.cs index f8e582445e..b66c96c2cd 100644 --- a/itext/itext.kernel/itext/kernel/exceptions/KernelExceptionMessageConstant.cs +++ b/itext/itext.kernel/itext/kernel/exceptions/KernelExceptionMessageConstant.cs @@ -619,6 +619,12 @@ public const String WHEN_ADDING_OBJECT_REFERENCE_TO_THE_TAG_TREE_IT_MUST_BE_CONN public const String INVALID_OBJECT_STREAM_NUMBER = "Unable to read object {0} with object stream " + "number {1} and index {2} from object stream."; + //TODO DEVSIX-8490 remove this exception message when implemented + public const String UNABLE_TO_ADD_SECOND_PARENT_LAYER = "Unable to add second parent layer to " + "{0} ocg layer"; + + //TODO DEVSIX-8490 remove this exception message when implemented + public const String UNABLE_TO_REMOVE_DUPLICATE_LAYER = "Unable to remove duplicated layer {0} " + "because it has child layers."; + private KernelExceptionMessageConstant() { } } diff --git a/itext/itext.kernel/itext/kernel/logs/KernelLogMessageConstant.cs b/itext/itext.kernel/itext/kernel/logs/KernelLogMessageConstant.cs index 3ed81a33f3..b7f484f675 100644 --- a/itext/itext.kernel/itext/kernel/logs/KernelLogMessageConstant.cs +++ b/itext/itext.kernel/itext/kernel/logs/KernelLogMessageConstant.cs @@ -90,6 +90,10 @@ public sealed class KernelLogMessageConstant { public const String XOBJECT_STRUCT_PARENT_INDEX_MISSED_AND_RECREATED = "XObject has no StructParents index in its stream, so index is recreated"; + //TODO DEVSIX-8490 remove this log message when implemented + public const String DUPLICATE_ENTRIES_IN_ORDER_ARRAY_REMOVED = "Duplicated entries in order array are " + + "removed"; + private KernelLogMessageConstant() { } //Private constructor will prevent the instantiation of this class directly diff --git a/itext/itext.kernel/itext/kernel/pdf/layer/PdfLayer.cs b/itext/itext.kernel/itext/kernel/pdf/layer/PdfLayer.cs index dc677a1ed5..de5a67994a 100644 --- a/itext/itext.kernel/itext/kernel/pdf/layer/PdfLayer.cs +++ b/itext/itext.kernel/itext/kernel/pdf/layer/PdfLayer.cs @@ -24,6 +24,7 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using iText.Commons.Utils; using iText.IO.Font; +using iText.Kernel.Exceptions; using iText.Kernel.Pdf; namespace iText.Kernel.Pdf.Layer { @@ -114,8 +115,11 @@ public static void AddOCGRadioGroup(PdfDocument document, IListAdds a child layer. Nested layers can only have one parent. /// the child layer public virtual void AddChild(iText.Kernel.Pdf.Layer.PdfLayer childLayer) { + //TODO DEVSIX-8490 implement multiple parent support if (childLayer.parent != null) { - throw new ArgumentException("Illegal argument: childLayer"); + PdfIndirectReference @ref = childLayer.GetIndirectReference(); + throw new PdfException(MessageFormatUtil.Format(KernelExceptionMessageConstant.UNABLE_TO_ADD_SECOND_PARENT_LAYER + , @ref.ToString())); } childLayer.parent = this; if (children == null) { diff --git a/itext/itext.kernel/itext/kernel/pdf/layer/PdfOCProperties.cs b/itext/itext.kernel/itext/kernel/pdf/layer/PdfOCProperties.cs index 3bfbb72e00..f61c976b11 100644 --- a/itext/itext.kernel/itext/kernel/pdf/layer/PdfOCProperties.cs +++ b/itext/itext.kernel/itext/kernel/pdf/layer/PdfOCProperties.cs @@ -26,6 +26,7 @@ You should have received a copy of the GNU Affero General Public License using iText.Commons; using iText.Commons.Utils; using iText.IO.Font; +using iText.Kernel.Exceptions; using iText.Kernel.Logs; using iText.Kernel.Pdf; @@ -51,6 +52,12 @@ public class PdfOCProperties : PdfObjectWrapper { private IList layers = new List(); + //TODO DEVSIX-8490 remove this field when implemented + private ICollection references; + + //TODO DEVSIX-8490 remove this field when implemented + private bool isDuplicateRemoved; + /// Creates a new PdfOCProperties instance. /// the document the optional content belongs to public PdfOCProperties(PdfDocument document) @@ -406,7 +413,14 @@ private void ReadLayersFromDictionary() { } PdfArray orderArray = d.GetAsArray(PdfName.Order); if (orderArray != null && !orderArray.IsEmpty()) { + references = new HashSet(); + isDuplicateRemoved = false; ReadOrderFromDictionary(null, orderArray, layerMap); + //TODO DEVSIX-8490 remove this check when implemented + if (isDuplicateRemoved) { + ILogger logger = ITextLogManager.GetLogger(typeof(iText.Kernel.Pdf.Layer.PdfOCProperties)); + logger.LogWarning(KernelLogMessageConstant.DUPLICATE_ENTRIES_IN_ORDER_ARRAY_REMOVED); + } } } // Add the layers which should not be displayed on the panel to the order list @@ -424,7 +438,25 @@ private void ReadOrderFromDictionary(PdfLayer parent, PdfArray orderArray, IDict PdfObject item = orderArray.Get(i); if (item.GetObjectType() == PdfObject.DICTIONARY) { PdfLayer layer = layerMap.Get(item.GetIndirectReference()); - if (layer != null) { + if (layer == null) { + continue; + } + //TODO DEVSIX-8490 remove this check and it statement when implemented + if (references.Contains(layer.GetIndirectReference())) { + //We want to check if this duplicate layer has childLayers, if it has - throw an exception, + // else just don't add this layer. + if (i + 1 < orderArray.Size() && orderArray.Get(i + 1).GetObjectType() == PdfObject.ARRAY) { + PdfArray nextArray = orderArray.GetAsArray(i + 1); + if (nextArray.Size() > 0 && nextArray.Get(0).GetObjectType() != PdfObject.STRING) { + PdfIndirectReference @ref = layer.GetIndirectReference(); + throw new PdfException(MessageFormatUtil.Format(KernelExceptionMessageConstant.UNABLE_TO_REMOVE_DUPLICATE_LAYER + , @ref.ToString())); + } + } + isDuplicateRemoved = true; + } + else { + references.Add(layer.GetIndirectReference()); layers.Add(layer); layer.onPanel = true; if (parent != null) { diff --git a/port-hash b/port-hash index 6174884e88..2f5e19c137 100644 --- a/port-hash +++ b/port-hash @@ -1 +1 @@ -cd91477a6b5dcb768f827dfa4d1083fecb2c8f52 +c8f9dfd8aab3ecfc3815c096cd36fa8109b9b0ab