* @return the Kids array
*/
public PDFArray getKids() {
- return (PDFArray)get(KIDS);
+ return (PDFArray) get(KIDS);
}
/**
* @return the Nums array
*/
public PDFNumsArray getNums() {
- return (PDFNumsArray)get(NUMS);
+ PDFNumsArray nums = (PDFNumsArray) get(NUMS);
+ if (nums == null) {
+ nums = new PDFNumsArray(this);
+ setNums(nums);
+ }
+ return nums;
}
/**
*/
public Integer getLowerLimit() {
PDFArray limits = prepareLimitsArray();
- return (Integer)limits.get(0);
+ return (Integer) limits.get(0);
}
/**
*/
public Integer getUpperLimit() {
PDFArray limits = prepareLimitsArray();
- return (Integer)limits.get(1);
+ return (Integer) limits.get(1);
+ }
+
+ /**
+ * Adds a number and object to the nums array and increases the
+ * upper limit should it be required.
+ * @param num The unique number identifying the object in the array
+ * @param object The object being added
+ */
+ protected void addToNums(int num, Object object) {
+ getNums().put(num, object);
+ if (getUpperLimit() < num) {
+ setUpperLimit(num);
+ }
}
private PDFArray prepareLimitsArray() {
- PDFArray limits = (PDFArray)get(LIMITS);
+ PDFArray limits = (PDFArray) get(LIMITS);
if (limits == null) {
limits = new PDFArray(this, new Object[2]);
put(LIMITS, limits);
super();
}
- /**
- * Returns the Nums object
- * @return the Nums object (an empty PDFNumsArray for the "/Nums" entry is created
- * if it doesn't exist)
- */
- public PDFNumsArray getNums() {
- PDFNumsArray nums = super.getNums();
- if (nums == null) {
- nums = new PDFNumsArray(this);
- setNums(nums);
- }
- return nums;
- }
-
/**
* Adds a new entry, if necessary, to the /PageLabels dictionary.
* @param index the page index (0 for page 1)
*/
public class PDFParentTree extends PDFNumberTreeNode {
+ private static final int MAX_NUMS_ARRAY_SIZE = 50;
+
+ public PDFParentTree() {
+ put("Kids", new PDFArray());
+ }
+
+ @Override
+ public void addToNums(int num, Object object) {
+ int arrayIndex = num / MAX_NUMS_ARRAY_SIZE;
+ setNumOfKidsArrays(arrayIndex + 1);
+ insertItemToNumsArray(arrayIndex, num, object);
+ }
+
+ private void setNumOfKidsArrays(int numKids) {
+ for (int i = getKids().length(); i < numKids; i++) {
+ PDFNumberTreeNode newArray = new PDFNumberTreeNode();
+ newArray.setNums(new PDFNumsArray(newArray));
+ newArray.setLowerLimit(i * MAX_NUMS_ARRAY_SIZE);
+ newArray.setUpperLimit(i * MAX_NUMS_ARRAY_SIZE);
+ addKid(newArray);
+ }
+ }
+
/**
- * Returns the number tree corresponding to this parent tree.
- *
- * @return the number tree
+ * Registers a child object and adds it to the Kids array.
+ * @param kid The child PDF object to be added
*/
- public PDFNumsArray getNums() {
- PDFNumsArray nums = super.getNums();
- if (nums == null) {
- nums = new PDFNumsArray(this);
- setNums(nums);
- }
- return nums;
+ private void addKid(PDFObject kid) {
+ assert getDocument() != null;
+ getDocument().assignObjectNumber(kid);
+ getDocument().addTrailerObject(kid);
+ ((PDFArray) get("Kids")).add(kid);
+ }
+
+ private void insertItemToNumsArray(int array, int num, Object object) {
+ assert getKids().get(array) instanceof PDFNumberTreeNode;
+ PDFNumberTreeNode numsArray = (PDFNumberTreeNode) getKids().get(array);
+ numsArray.addToNums(num, object);
}
}
// being output to the PDF.
// This should really be handled by PDFNumsArray
pdfDoc.registerObject(pageParentTreeArray);
- parentTree.getNums().put(currentPage.getStructParents(), pageParentTreeArray);
+ parentTree.addToNums(currentPage.getStructParents(), pageParentTreeArray);
}
private MarkedContentInfo addToParentTree(PDFStructElem structureTreeElement) {
contentItem.put("Type", OBJR);
contentItem.put("Pg", this.currentPage);
contentItem.put("Obj", link);
- parentTree.getNums().put(structParent, structureTreeElement);
+ parentTree.addToNums(structParent, structureTreeElement);
structureTreeElement.addKid(contentItem);
}
documents. Example: the fix of marks layering will be such a case when it's done.
-->
<release version="FOP Trunk" date="TBD">
+ <action context="Renderers" dev="VH" type="fix" fixes-bug="54169" due-to="Robert Meyer">
+ Split the parent tree (the number tree corresponding to the ParentTree entry in the
+ structure tree root) to avoid reaching the internal limits of Acrobat Pro, that would
+ otherwise split it at the wrong place when saving the document.
+ </action>
<action context="Fonts" dev="MH" type="add" fixes-bug="54120">
Created a simple mechanism to avoid NPEs when AWT fonts are requested from Batik
for AFP output.
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests that the nums array in the ParentTree dictionary is correctly being split into
+ * separate arrays if the elements number exceeds the set limit.
+ */
+public class PDFParentTreeTestCase {
+
+ private PDFParentTree parentTree;
+
+ @Before
+ public void initializeStructureTree() {
+ parentTree = new PDFParentTree();
+ PDFDocument pdfDocument = new PDFDocument("test");
+ pdfDocument.makeStructTreeRoot(parentTree);
+ }
+
+ /**
+ * Adds less structured items than the imposed limit which should result
+ * in only one nums array being created.
+ * @throws Exception
+ */
+ @Test
+ public void testNoSplit() throws Exception {
+ assertEquals(getArrayNumber(45), 1);
+ }
+
+ /**
+ * Adds more than the imposed array limit to test that it splits the
+ * nums array into two objects.
+ * @throws Exception
+ */
+ @Test
+ public void testSingleSplit() throws Exception {
+ assertEquals(getArrayNumber(70), 2);
+ }
+
+ /**
+ * Adds items to the nums array to cause and test that multiple splits occur
+ * @throws Exception
+ */
+ @Test
+ public void testMultipleSplit() throws Exception {
+ assertEquals(getArrayNumber(165), 4);
+ }
+
+ /**
+ * Ensures that items added out of order get added to the correct nums array
+ * @throws Exception
+ */
+ @Test
+ public void testOutOfOrderSplit() throws Exception {
+ PDFStructElem structElem = mock(PDFStructElem.class);
+ for (int num = 50; num < 53; num++) {
+ parentTree.addToNums(num, structElem);
+ }
+ assertEquals(getArrayNumber(50), 2);
+ PDFNumberTreeNode treeNode = (PDFNumberTreeNode) parentTree.getKids().get(0);
+ for (int num = 0; num < 50; num++) {
+ assertTrue(treeNode.getNums().map.containsKey(num));
+ }
+ treeNode = (PDFNumberTreeNode) parentTree.getKids().get(1);
+ for (int num = 50; num < 53; num++) {
+ assertTrue(treeNode.getNums().map.containsKey(num));
+ }
+ }
+
+ /**
+ * Gets the number of arrays created for a given number of elements
+ * @param elementNumber The number of elements to be added to the nums array
+ * @return Returns the number of array objects
+ * @throws Exception
+ */
+ private int getArrayNumber(int elementNumber) throws Exception {
+ PDFStructElem structElem = mock(PDFStructElem.class);
+ for (int structParent = 0; structParent < elementNumber; structParent++) {
+ parentTree.addToNums(structParent, structElem);
+ }
+ return parentTree.getKids().length();
+ }
+}