From: Jeremias Maerki Date: Thu, 10 Jan 2008 07:38:47 +0000 (+0000) Subject: Added basic support for PDF page labels. X-Git-Tag: fop-0_95beta~177 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e9693885fd98f2f0cb77cb9527708edfdfbe7a7b;p=xmlgraphics-fop.git Added basic support for PDF page labels. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@610704 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/fop/pdf/PDFDictionary.java b/src/java/org/apache/fop/pdf/PDFDictionary.java index 71393cace..c183871b5 100644 --- a/src/java/org/apache/fop/pdf/PDFDictionary.java +++ b/src/java/org/apache/fop/pdf/PDFDictionary.java @@ -101,16 +101,26 @@ public class PDFDictionary extends PDFObject { */ protected void writeDictionary(StringBuffer sb) { sb.append("<<"); + boolean compact = (this.order.size() <= 2); Iterator iter = this.order.iterator(); while (iter.hasNext()) { String key = (String)iter.next(); - sb.append("\n /"); - sb.append(key); - sb.append(" "); + if (compact) { + sb.append(' '); + } else { + sb.append("\n "); + } + sb.append('/').append(key); + sb.append(' '); Object obj = this.entries.get(key); formatObject(obj, sb); } - sb.append("\n>>\n"); + if (compact) { + sb.append(' '); + } else { + sb.append('\n'); + } + sb.append(">>\n"); } } diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index e1546baed..f851abae7 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -848,6 +848,17 @@ public class PDFFactory { return names; } + /** + * Make a names dictionary (the /PageLabels object). + * @return the new PDFPageLabels object + */ + public PDFPageLabels makePageLabels() { + PDFPageLabels pageLabels = new PDFPageLabels(); + getDocument().assignObjectNumber(pageLabels); + getDocument().addTrailerObject(pageLabels); + return pageLabels; + } + /** * Make a the head object of the name dictionary (the /Dests object). * diff --git a/src/java/org/apache/fop/pdf/PDFNumberTreeNode.java b/src/java/org/apache/fop/pdf/PDFNumberTreeNode.java new file mode 100644 index 000000000..4e69c01d4 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFNumberTreeNode.java @@ -0,0 +1,121 @@ +/* + * 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; + +/** + * Class representing a PDF number tree node. + */ +public class PDFNumberTreeNode extends PDFDictionary { + + private static final String KIDS = "Kids"; + private static final String NUMS = "Nums"; + private static final String LIMITS = "Limits"; + + /** + * create a named destination + */ + public PDFNumberTreeNode() { + /* generic creation of PDF object */ + super(); + } + + /** + * Sets the Kids array. + * @param kids the Kids array + */ + public void setKids(PDFArray kids) { + put(KIDS, kids); + } + + /** + * Returns the Kids array. + * @return the Kids array + */ + public PDFArray getKids() { + return (PDFArray)get(KIDS); + } + + /** + * Sets the Nums array. + * @param nums the Nums array + */ + public void setNums(PDFNumsArray nums) { + put(NUMS, nums); + } + + /** + * Returns the Nums array. + * @return the Nums array + */ + public PDFNumsArray getNums() { + return (PDFNumsArray)get(NUMS); + } + + /** + * Sets the lower limit value of the Limits array. + * @param key the lower limit value + */ + public void setLowerLimit(Integer key) { + PDFArray limits = prepareLimitsArray(); + limits.set(0, key); + } + + /** + * Returns the lower limit value of the Limits array. + * @return the lower limit value + */ + public Integer getLowerLimit() { + PDFArray limits = prepareLimitsArray(); + return (Integer)limits.get(0); + } + + /** + * Sets the upper limit value of the Limits array. + * @param key the upper limit value + */ + public void setUpperLimit(Integer key) { + PDFArray limits = prepareLimitsArray(); + limits.set(1, key); + } + + /** + * Returns the upper limit value of the Limits array. + * @return the upper limit value + */ + public Integer getUpperLimit() { + PDFArray limits = prepareLimitsArray(); + return (Integer)limits.get(1); + } + + + private PDFArray prepareLimitsArray() { + PDFArray limits = (PDFArray)get(LIMITS); + if (limits == null) { + limits = new PDFArray(new Object[2]); + put(LIMITS, limits); + } + if (limits.length() != 2) { + throw new IllegalStateException("Limits array must have 2 entries"); + } + return limits; + } + +} + diff --git a/src/java/org/apache/fop/pdf/PDFNumsArray.java b/src/java/org/apache/fop/pdf/PDFNumsArray.java new file mode 100644 index 000000000..55f973ccd --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFNumsArray.java @@ -0,0 +1,94 @@ +/* + * 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: PDFArray.java 588547 2007-10-26 07:48:14Z jeremias $ */ + +package org.apache.fop.pdf; + +import java.util.Iterator; +import java.util.Map; +import java.util.SortedMap; + +/** + * Class representing an "Nums" array object (for Number Trees). + */ +public class PDFNumsArray extends PDFObject { + + /** Sorted Map holding the values of this array. */ + protected SortedMap map = new java.util.TreeMap(); + + /** + * Create a new, empty array object + */ + public PDFNumsArray() { + /* generic creation of PDF object */ + super(); + } + + /** + * Returns the length of the array + * @return the length of the array + */ + public int length() { + return this.map.size(); + } + + /** + * Sets an entry. + * @param key the key of the value to set + * @param obj the new value + */ + public void put(int key, Object obj) { + this.map.put(new Integer(key), obj); + } + + /** + * Gets an entry. + * @param key the key of requested value + * @return the requested value + */ + public Object get(int key) { + return this.map.get(new Integer(key)); + } + + /** {@inheritDoc} */ + public String toPDFString() { + StringBuffer p = new StringBuffer(64); + if (hasObjectNumber()) { + p.append(getObjectID()); + } + p.append("["); + boolean first = true; + Iterator iter = this.map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry)iter.next(); + if (!first) { + p.append(" "); + } + first = false; + formatObject(entry.getKey(), p); + p.append(" "); + formatObject(entry.getValue(), p); + } + p.append("]"); + if (hasObjectNumber()) { + p.append("\nendobj\n"); + } + return p.toString(); + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFPageLabels.java b/src/java/org/apache/fop/pdf/PDFPageLabels.java new file mode 100644 index 000000000..bb02a3c7e --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFPageLabels.java @@ -0,0 +1,48 @@ +/* + * 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; + +/** + * Class representing a PDF /PageLabels dictionary. + */ +public class PDFPageLabels extends PDFNumberTreeNode { + + /** + * Create the /PageLabels dictionary + */ + public PDFPageLabels() { + 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(); + setNums(nums); + } + return nums; + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFRoot.java b/src/java/org/apache/fop/pdf/PDFRoot.java index 64103df22..1a54a209f 100644 --- a/src/java/org/apache/fop/pdf/PDFRoot.java +++ b/src/java/org/apache/fop/pdf/PDFRoot.java @@ -19,12 +19,10 @@ package org.apache.fop.pdf; -import java.util.List; - /** - * class representing a Root (/Catalog) object + * Class representing a Root (/Catalog) object. */ -public class PDFRoot extends PDFObject { +public class PDFRoot extends PDFDictionary { /** * Use no page mode setting, default @@ -46,27 +44,13 @@ public class PDFRoot extends PDFObject { */ public static final int PAGEMODE_FULLSCREEN = 3; - /** - * the /Pages object that is root of the Pages hierarchy - */ - protected PDFPages rootPages; - - /** - * Root outline object - */ - private PDFOutline outline; - - /** Optional Metadata object */ - private PDFMetadata metadata; + private static final PDFName[] PAGEMODE_NAMES = new PDFName[] { + new PDFName("UseNone"), + new PDFName("UseOutlines"), + new PDFName("UseThumbs"), + new PDFName("FullScreen"), + }; - /** The array of OutputIntents */ - private List outputIntents; - - /** the /Dests object, if this PDF has a Names Dictionary */ - private PDFNames names; - - private int pageMode = PAGEMODE_USENONE; - /** * create a Root (/Catalog) object. NOTE: The PDFRoot * object must be created before the PDF document is @@ -80,25 +64,45 @@ public class PDFRoot extends PDFObject { public PDFRoot(int objnum, PDFPages pages) { super(); setObjectNumber(objnum); + put("Type", new PDFName("Catalog")); setRootPages(pages); } /** * Set the page mode for the PDF document. * - * @param mode the page mode + * @param mode the page mode (one of PAGEMODE_*) */ public void setPageMode(int mode) { - pageMode = mode; + put("PageMode", PAGEMODE_NAMES[mode]); } + /** + * Returns the currently active /PageMode. + * @return the /PageMode (one of PAGEMODE_*) + */ + public int getPageMode() { + PDFName mode = (PDFName)get("PageMode"); + if (mode != null) { + for (int i = 0; i < PAGEMODE_NAMES.length; i++) { + if (PAGEMODE_NAMES[i].equals(mode)) { + return i; + } + } + throw new IllegalStateException("Unknown /PageMode encountered: " + mode); + } else { + return PAGEMODE_USENONE; + } + } + /** * add a /Page object to the root /Pages object * * @param page the /Page object to add */ public void addPage(PDFPage page) { - this.rootPages.addPage(page); + PDFPages pages = getRootPages(); + pages.addPage(page); } /** @@ -107,16 +111,50 @@ public class PDFRoot extends PDFObject { * @param pages the /Pages object to set as root */ public void setRootPages(PDFPages pages) { - this.rootPages = pages; + put("Pages", pages.makeReference()); } + /** + * Returns the /PageLabels object. + * @return the /PageLabels object if set, null otherwise. + * @since PDF 1.3 + */ + public PDFPages getRootPages() { + PDFReference ref = (PDFReference)get("Pages"); + return (ref != null ? (PDFPages)ref.getObject() : null); + } + + /** + * Sets the /PageLabels object. + * @param pageLabels the /PageLabels object + */ + public void setPageLabels(PDFPageLabels pageLabels) { + put("PageLabels", pageLabels.makeReference()); + } + + /** + * Returns the /PageLabels object. + * @return the /PageLabels object if set, null otherwise. + * @since PDF 1.3 + */ + public PDFPageLabels getPageLabels() { + PDFReference ref = (PDFReference)get("PageLabels"); + return (ref != null ? (PDFPageLabels)ref.getObject() : null); + } + /** * Set the root outline for the PDF document. * * @param out the root PDF Outline */ public void setRootOutline(PDFOutline out) { - outline = out; + put("Outlines", out.makeReference()); + + //Set /PageMode to /UseOutlines by default if no other mode has been set + PDFName mode = (PDFName)get("PageMode"); + if (mode == null) { + setPageMode(PAGEMODE_USEOUTLINES); + } } /** @@ -125,24 +163,27 @@ public class PDFRoot extends PDFObject { * @return the root PDF Outline */ public PDFOutline getRootOutline() { - return outline; + PDFReference ref = (PDFReference)get("Outlines"); + return (ref != null ? (PDFOutline)ref.getObject() : null); } /** - * Set the Names object. + * Set the /Names object. * @param names the Names object * @since PDF 1.2 */ public void setNames(PDFNames names) { - this.names = names; + put("Names", names.makeReference()); } /** + * Returns the /Names object. * @return the Names object if set, null otherwise. * @since PDF 1.2 */ public PDFNames getNames() { - return this.names; + PDFReference ref = (PDFReference)get("Names"); + return (ref != null ? (PDFNames)ref.getObject() : null); } /** @@ -151,78 +192,44 @@ public class PDFRoot extends PDFObject { * @since PDF 1.4 */ public void setMetadata(PDFMetadata meta) { - this.metadata = meta; + if (getDocumentSafely().getPDFVersion() >= PDFDocument.PDF_VERSION_1_4) { + put("Metadata", meta.makeReference()); + } } /** - * @return the Metadata object if set, null otherwise. + * Returns the /Metadata object + * @return the /Metadata object if set, null otherwise. * @since PDF 1.4 */ public PDFMetadata getMetadata() { - return this.metadata; + PDFReference ref = (PDFReference)get("Metadata"); + return (ref != null ? (PDFMetadata)ref.getObject() : null); } /** - * Adds an OutputIntent to the PDF - * @param outputIntent the OutputIntent dictionary + * Returns the /OutputIntents array. + * @return the /OutputIntents array or null if it doesn't exist + * @since PDF 1.4 */ - public void addOutputIntent(PDFOutputIntent outputIntent) { - if (this.outputIntents == null) { - this.outputIntents = new java.util.ArrayList(); - } - this.outputIntents.add(outputIntent); + public PDFArray getOutputIntents() { + return (PDFArray)get("OutputIntents"); } /** - * {@inheritDoc} - */ - public String toPDFString() { - StringBuffer p = new StringBuffer(128); - p.append(getObjectID()); - p.append("<< /Type /Catalog\n /Pages " - + this.rootPages.referencePDF() - + "\n"); - if (outline != null) { - p.append(" /Outlines " + outline.referencePDF() + "\n"); - p.append(" /PageMode /UseOutlines\n"); - } else { - switch (pageMode) { - case PAGEMODE_USEOUTLINES: - p.append(" /PageMode /UseOutlines\n"); - break; - case PAGEMODE_USETHUMBS: - p.append(" /PageMode /UseThumbs\n"); - break; - case PAGEMODE_FULLSCREEN: - p.append(" /PageMode /FullScreen\n"); - break; - case PAGEMODE_USENONE: - default: - break; - } - } - if (getDocumentSafely().hasDestinations() && getNames() != null) { - p.append(" /Names " + getNames().referencePDF() + "\n"); - } - if (getMetadata() != null - && getDocumentSafely().getPDFVersion() >= PDFDocument.PDF_VERSION_1_4) { - p.append(" /Metadata " + getMetadata().referencePDF() + "\n"); - } - if (this.outputIntents != null - && this.outputIntents.size() > 0 - && getDocumentSafely().getPDFVersion() >= PDFDocument.PDF_VERSION_1_4) { - p.append(" /OutputIntents ["); - for (int i = 0, c = this.outputIntents.size(); i < c; i++) { - PDFOutputIntent outputIntent = (PDFOutputIntent)this.outputIntents.get(i); - if (i > 0) { - p.append(" "); - } - p.append(outputIntent.referencePDF()); + * Adds an OutputIntent to the PDF + * @param outputIntent the OutputIntent dictionary + * @since PDF 1.4 + */ + public void addOutputIntent(PDFOutputIntent outputIntent) { + if (getDocumentSafely().getPDFVersion() >= PDFDocument.PDF_VERSION_1_4) { + PDFArray outputIntents = getOutputIntents(); + if (outputIntents == null) { + outputIntents = new PDFArray(); + put("OutputIntents", outputIntents); } - p.append("]\n"); + outputIntents.add(outputIntent); } - p.append(">>\nendobj\n"); - return p.toString(); } - + } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index a03179692..2d8e7d5ba 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -73,6 +73,7 @@ import org.apache.fop.pdf.PDFAction; import org.apache.fop.pdf.PDFAnnotList; import org.apache.fop.pdf.PDFColor; import org.apache.fop.pdf.PDFConformanceException; +import org.apache.fop.pdf.PDFDictionary; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFEncryptionManager; import org.apache.fop.pdf.PDFEncryptionParams; @@ -88,6 +89,7 @@ import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFOutline; import org.apache.fop.pdf.PDFOutputIntent; import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFPageLabels; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; import org.apache.fop.pdf.PDFState; @@ -715,6 +717,19 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { page.getPageIndex()); pageReferences.put(page.getKey(), currentPage.referencePDF()); pvReferences.put(page.getKey(), page); + + //Produce page labels + PDFPageLabels pageLabels = this.pdfDoc.getRoot().getPageLabels(); + if (pageLabels == null) { + //Set up PageLabels + pageLabels = this.pdfDoc.getFactory().makePageLabels(); + this.pdfDoc.getRoot().setPageLabels(pageLabels); + } + PDFDictionary dict = new PDFDictionary(); + dict.put("P", page.getPageNumberString()); + //TODO If the sequence of generated page numbers were inspected, this could be + //expressed in a more space-efficient way + pageLabels.getNums().put(page.getPageIndex(), dict); } /** diff --git a/status.xml b/status.xml index 1c2f18b4b..3b8881211 100644 --- a/status.xml +++ b/status.xml @@ -28,6 +28,9 @@ + + Added support for PDF page labels. + Added support for custom fonts in Java2DRenderer and derived renderers.