aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/org')
-rw-r--r--src/java/org/apache/fop/apps/FOURIResolver.java5
-rw-r--r--src/java/org/apache/fop/area/AreaTreeHandler.java280
-rw-r--r--src/java/org/apache/fop/area/AreaTreeParser.java201
-rw-r--r--src/java/org/apache/fop/area/BookmarkData.java46
-rw-r--r--src/java/org/apache/fop/area/DestinationData.java21
-rw-r--r--src/java/org/apache/fop/area/LinkResolver.java18
-rw-r--r--src/java/org/apache/fop/area/PageViewport.java27
-rw-r--r--src/java/org/apache/fop/area/Trait.java141
-rw-r--r--src/java/org/apache/fop/fo/flow/Table.java7
-rw-r--r--src/java/org/apache/fop/fo/pagination/Root.java9
-rw-r--r--src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java2
-rw-r--r--src/java/org/apache/fop/layoutmgr/ElementListUtils.java13
-rw-r--r--src/java/org/apache/fop/layoutmgr/TraitSetter.java2
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java51
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/FootnoteLayoutManager.java125
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/WrapperLayoutManager.java37
-rw-r--r--src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java181
-rw-r--r--src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java29
-rw-r--r--src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java98
-rw-r--r--src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java7
-rw-r--r--src/java/org/apache/fop/layoutmgr/table/TableStepper.java26
-rw-r--r--src/java/org/apache/fop/pdf/PDFArray.java104
-rw-r--r--src/java/org/apache/fop/pdf/PDFDestination.java89
-rw-r--r--src/java/org/apache/fop/pdf/PDFDests.java52
-rw-r--r--src/java/org/apache/fop/pdf/PDFDictionary.java96
-rw-r--r--src/java/org/apache/fop/pdf/PDFDocument.java115
-rw-r--r--src/java/org/apache/fop/pdf/PDFFactory.java277
-rw-r--r--src/java/org/apache/fop/pdf/PDFGoTo.java44
-rw-r--r--src/java/org/apache/fop/pdf/PDFLimits.java93
-rw-r--r--src/java/org/apache/fop/pdf/PDFNameTreeNode.java121
-rw-r--r--src/java/org/apache/fop/pdf/PDFNames.java51
-rw-r--r--src/java/org/apache/fop/pdf/PDFObject.java42
-rw-r--r--src/java/org/apache/fop/pdf/PDFRectangle.java2
-rw-r--r--src/java/org/apache/fop/pdf/PDFReference.java67
-rw-r--r--src/java/org/apache/fop/pdf/PDFRoot.java36
-rw-r--r--src/java/org/apache/fop/pdf/PDFState.java17
-rw-r--r--src/java/org/apache/fop/pdf/PDFStream.java2
-rw-r--r--src/java/org/apache/fop/pdf/PDFWritable.java36
-rw-r--r--src/java/org/apache/fop/render/AbstractRenderer.java2
-rw-r--r--src/java/org/apache/fop/render/Renderer.java4
-rw-r--r--src/java/org/apache/fop/render/bitmap/MultiFileRenderingUtil.java75
-rw-r--r--src/java/org/apache/fop/render/bitmap/PNGRenderer.java119
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRenderer.java443
-rw-r--r--src/java/org/apache/fop/render/xml/XMLRenderer.java79
44 files changed, 2226 insertions, 1066 deletions
diff --git a/src/java/org/apache/fop/apps/FOURIResolver.java b/src/java/org/apache/fop/apps/FOURIResolver.java
index 5b66ffa57..e3ffd22cc 100644
--- a/src/java/org/apache/fop/apps/FOURIResolver.java
+++ b/src/java/org/apache/fop/apps/FOURIResolver.java
@@ -189,10 +189,11 @@ public class FOURIResolver
try {
ByteArrayOutputStream baout = new ByteArrayOutputStream(combined.length() * 2);
Base64EncodeStream base64 = new Base64EncodeStream(baout);
- base64.write(combined.getBytes());
+ //TODO Not sure what charset/encoding can be used with basic authentication
+ base64.write(combined.getBytes("UTF-8"));
base64.close();
connection.setRequestProperty("Authorization",
- "Basic " + new String(baout.toByteArray()));
+ "Basic " + new String(baout.toByteArray(), "UTF-8"));
} catch (IOException e) {
//won't happen. We're operating in-memory.
throw new RuntimeException("Error during base64 encodation of username/password");
diff --git a/src/java/org/apache/fop/area/AreaTreeHandler.java b/src/java/org/apache/fop/area/AreaTreeHandler.java
index e7f6effe0..0104d0688 100644
--- a/src/java/org/apache/fop/area/AreaTreeHandler.java
+++ b/src/java/org/apache/fop/area/AreaTreeHandler.java
@@ -53,36 +53,23 @@ import org.apache.fop.fo.extensions.destination.Destination;
/**
* Area tree handler for formatting objects.
- *
- * Concepts:
- * The area tree is to be as small as possible. With minimal classes
- * and data to fully represent an area tree for formatting objects.
- * The area tree needs to be simple to render and follow the spec
- * closely.
- * This area tree has the concept of page sequences.
- * Wherever possible information is discarded or optimized to
- * keep memory use low. The data is also organized to make it
- * possible for renderers to minimize their output.
- * A page can be saved if not fully resolved and once rendered
- * a page contains only size and id reference information.
- * The area tree pages are organized in a model that depends on the
+ *
+ * Concepts: The area tree is to be as small as possible. With minimal classes
+ * and data to fully represent an area tree for formatting objects. The area
+ * tree needs to be simple to render and follow the spec closely. This area tree
+ * has the concept of page sequences. Wherever possible information is discarded
+ * or optimized to keep memory use low. The data is also organized to make it
+ * possible for renderers to minimize their output. A page can be saved if not
+ * fully resolved and once rendered a page contains only size and id reference
+ * information. The area tree pages are organized in a model that depends on the
* type of renderer.
*/
public class AreaTreeHandler extends FOEventHandler {
- private static Log log = LogFactory.getLog(AreaTreeHandler.class);
-
- // show statistics after document complete?
- private boolean outputStatistics;
-
- // for statistics gathering
- private Runtime runtime;
-
- // heap memory allocated (for statistics)
- private long initialMemory;
+ /** debug statistics */
+ private Statistics statistics = null;
- // time used in rendering (for statistics)
- private long startTime;
+ private static Log log = LogFactory.getLog(AreaTreeHandler.class);
// the LayoutManager maker
private LayoutManagerMaker lmMaker;
@@ -93,66 +80,68 @@ public class AreaTreeHandler extends FOEventHandler {
// The fo:root node of the document
private Root rootFObj;
- // HashMap of ID's whose area is located on one or more consecutive
- // PageViewports. Each ID has an arraylist of PageViewports that
+ // HashMap of ID's whose area is located on one or more consecutive
+ // PageViewports. Each ID has an arraylist of PageViewports that
// form the defined area of this ID
private Map idLocations = new HashMap();
// idref's whose target PageViewports have yet to be identified
// Each idref has a HashSet of Resolvable objects containing that idref
private Map unresolvedIDRefs = new HashMap();
-
+
private Set unfinishedIDs = new HashSet();
+
private Set alreadyResolvedIDs = new HashSet();
- // The formatting results to be handed back to the caller.
+ // The formatting results to be handed back to the caller.
private FormattingResults results = new FormattingResults();
private PageSequenceLayoutManager prevPageSeqLM;
private int idGen = 0;
-
+
/**
* Constructor.
+ *
* @param userAgent FOUserAgent object for process
- * @param outputFormat the MIME type of the output format to use (ex. "application/pdf").
+ * @param outputFormat the MIME type of the output format to use (ex.
+ * "application/pdf").
* @param stream OutputStream
* @throws FOPException if the RenderPagesModel cannot be created
*/
- public AreaTreeHandler (FOUserAgent userAgent, String outputFormat,
- OutputStream stream) throws FOPException {
+ public AreaTreeHandler(FOUserAgent userAgent, String outputFormat,
+ OutputStream stream) throws FOPException {
super(userAgent);
setupModel(userAgent, outputFormat, stream);
-
+
lmMaker = userAgent.getFactory().getLayoutManagerMakerOverride();
if (lmMaker == null) {
lmMaker = new LayoutManagerMapping();
}
- outputStatistics = log.isDebugEnabled();
-
- if (outputStatistics) {
- runtime = Runtime.getRuntime();
+ if (log.isDebugEnabled()) {
+ statistics = new Statistics();
}
}
/**
* Sets up the AreaTreeModel instance for use by the AreaTreeHandler.
+ *
* @param userAgent FOUserAgent object for process
- * @param outputFormat the MIME type of the output format to use (ex. "application/pdf").
+ * @param outputFormat the MIME type of the output format to use (ex.
+ * "application/pdf").
* @param stream OutputStream
* @throws FOPException if the RenderPagesModel cannot be created
*/
- protected void setupModel(FOUserAgent userAgent, String outputFormat,
+ protected void setupModel(FOUserAgent userAgent, String outputFormat,
OutputStream stream) throws FOPException {
- model = new RenderPagesModel(userAgent, outputFormat, fontInfo,
- stream);
+ model = new RenderPagesModel(userAgent, outputFormat, fontInfo, stream);
}
-
+
/**
* Get the area tree model for this area tree.
- *
+ *
* @return AreaTreeModel the model being used for this area tree
*/
public AreaTreeModel getAreaTreeModel() {
@@ -161,17 +150,19 @@ public class AreaTreeHandler extends FOEventHandler {
/**
* Get the LayoutManager maker for this area tree.
- *
- * @return LayoutManagerMaker the LayoutManager maker being used for this area tree
+ *
+ * @return LayoutManagerMaker the LayoutManager maker being used for this
+ * area tree
*/
public LayoutManagerMaker getLayoutManagerMaker() {
return lmMaker;
}
/**
- * Tie a PageViewport with an ID found on a child area of the PV.
- * Note that an area with a given ID may be on more than one PV, hence
- * an ID may have more than one PV associated with it.
+ * Tie a PageViewport with an ID found on a child area of the PV. Note that
+ * an area with a given ID may be on more than one PV, hence an ID may have
+ * more than one PV associated with it.
+ *
* @param id the property ID of the area
* @param pv a page viewport that contains the area with this ID
*/
@@ -184,23 +175,25 @@ public class AreaTreeHandler extends FOEventHandler {
pvList = new ArrayList();
idLocations.put(id, pvList);
pvList.add(pv);
-
- /*
- * See if this ID is in the unresolved idref list, if so
- * resolve Resolvable objects tied to it.
+ // signal the PageViewport that it is the first PV to contain this id:
+ pv.setFirstWithID(id);
+ /*
+ * See if this ID is in the unresolved idref list, if so resolve
+ * Resolvable objects tied to it.
*/
if (!unfinishedIDs.contains(id)) {
- tryIDResolution(id, pv, pvList);
+ tryIDResolution(id, pv, pvList);
}
} else {
pvList.add(pv);
}
}
-
+
/**
- * This method tie an ID to the areaTreeHandler until this one is
- * ready to be processed. This is used in page-number-citation-last processing so we know
- * when an id can be resolved.
+ * This method tie an ID to the areaTreeHandler until this one is ready to
+ * be processed. This is used in page-number-citation-last processing so we
+ * know when an id can be resolved.
+ *
* @param id the id of the object being processed
*/
public void signalPendingID(String id) {
@@ -209,23 +202,25 @@ public class AreaTreeHandler extends FOEventHandler {
}
unfinishedIDs.add(id);
}
-
+
/**
- * Signals that all areas for the formatting object with the given ID have been generated.
- * This is used to determine when page-number-citation-last ref-ids can be resolved.
+ * Signals that all areas for the formatting object with the given ID have
+ * been generated. This is used to determine when page-number-citation-last
+ * ref-ids can be resolved.
+ *
* @param id the id of the formatting object which was just finished
*/
public void signalIDProcessed(String id) {
if (log.isDebugEnabled()) {
log.debug("signalIDProcessed(" + id + ")");
}
-
+
alreadyResolvedIDs.add(id);
if (!unfinishedIDs.contains(id)) {
return;
}
unfinishedIDs.remove(id);
-
+
List pvList = (List) idLocations.get(id);
Set todo = (Set) unresolvedIDRefs.get(id);
if (todo != null) {
@@ -236,9 +231,10 @@ public class AreaTreeHandler extends FOEventHandler {
unresolvedIDRefs.remove(id);
}
}
-
+
/**
* Check if an ID has already been resolved
+ *
* @param id the id to check
* @return true if the ID has been resolved
*/
@@ -248,6 +244,7 @@ public class AreaTreeHandler extends FOEventHandler {
/**
* Tries to resolve all unresolved ID references on the given page.
+ *
* @param id ID to resolve
* @param pv page viewport whose ID refs to resolve
* @param List of PageViewports
@@ -270,6 +267,7 @@ public class AreaTreeHandler extends FOEventHandler {
/**
* Tries to resolve all unresolved ID references on the given page.
+ *
* @param pv page viewport whose ID refs to resolve
*/
public void tryIDResolution(PageViewport pv) {
@@ -283,9 +281,10 @@ public class AreaTreeHandler extends FOEventHandler {
}
}
}
-
+
/**
* Get the list of page viewports that have an area with a given id.
+ *
* @param id the id to lookup
* @return the list of PageViewports
*/
@@ -294,8 +293,8 @@ public class AreaTreeHandler extends FOEventHandler {
}
/**
- * Get information about the rendered output, like
- * number of pages created.
+ * Get information about the rendered output, like number of pages created.
+ *
* @return the results structure
*/
public FormattingResults getResults() {
@@ -304,6 +303,7 @@ public class AreaTreeHandler extends FOEventHandler {
/**
* Add an Resolvable object with an unresolved idref
+ *
* @param idref the idref whose target id has not yet been located
* @param res the Resolvable object needing the idref to be resolved
*/
@@ -318,16 +318,16 @@ public class AreaTreeHandler extends FOEventHandler {
}
/**
- * Prepare AreaTreeHandler for document processing
- * This is called from FOTreeBuilder.startDocument()
- *
- * @throws SAXException if there is an error
+ * Prepare AreaTreeHandler for document processing This is called from
+ * FOTreeBuilder.startDocument()
+ *
+ * @throws SAXException
+ * if there is an error
*/
public void startDocument() throws SAXException {
- //Initialize statistics
- if (outputStatistics) {
- initialMemory = runtime.totalMemory() - runtime.freeMemory();
- startTime = System.currentTimeMillis();
+ // Initialize statistics
+ if (statistics != null) {
+ statistics.start();
}
}
@@ -344,40 +344,39 @@ public class AreaTreeHandler extends FOEventHandler {
/**
* @see org.apache.fop.fo.FOEventHandler
- * @param pageSequence is the pageSequence being started
- * */
+ * @param pageSequence
+ * is the pageSequence being started
+ */
public void startPageSequence(PageSequence pageSequence) {
rootFObj = pageSequence.getRoot();
finishPrevPageSequence(pageSequence.getInitialPageNumber());
pageSequence.initPageNumber();
- //extension attachments from fo:root
+ // extension attachments from fo:root
wrapAndAddExtensionAttachments(rootFObj.getExtensionAttachments());
- //extension attachments from fo:declarations
+ // extension attachments from fo:declarations
if (rootFObj.getDeclarations() != null) {
wrapAndAddExtensionAttachments(rootFObj.getDeclarations().getExtensionAttachments());
}
}
-
+
private void wrapAndAddExtensionAttachments(List list) {
Iterator i = list.iterator();
while (i.hasNext()) {
- ExtensionAttachment attachment = (ExtensionAttachment)i.next();
+ ExtensionAttachment attachment = (ExtensionAttachment) i.next();
addOffDocumentItem(new OffDocumentExtensionAttachment(attachment));
}
}
-
+
/**
- * End the PageSequence.
- * The PageSequence formats Pages and adds them to the AreaTree.
- * The area tree then handles what happens with the pages.
- *
+ * End the PageSequence. The PageSequence formats Pages and adds them to the
+ * AreaTree. The area tree then handles what happens with the pages.
+ *
* @param pageSequence the page sequence ending
*/
public void endPageSequence(PageSequence pageSequence) {
- if (outputStatistics) {
- long memoryNow = runtime.totalMemory() - runtime.freeMemory();
- log.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
+ if (statistics != null) {
+ statistics.end();
}
// If no main flow, nothing to layout!
@@ -388,38 +387,38 @@ public class AreaTreeHandler extends FOEventHandler {
pageSLM.activateLayout();
// preserve the current PageSequenceLayoutManger for the
// force-page-count check at the beginning of the next PageSequence
- prevPageSeqLM = pageSLM;
+ prevPageSeqLM = pageSLM;
}
}
/**
- * Called by the PageSequenceLayoutManager when it is finished with a page-sequence.
+ * Called by the PageSequenceLayoutManager when it is finished with a
+ * page-sequence.
+ *
* @param pageSequence the page-sequence just finished
* @param pageCount The number of pages generated for the page-sequence
*/
public void notifyPageSequenceFinished(PageSequence pageSequence,
- int pageCount) {
- this.results.haveFormattedPageSequence(pageSequence,
- pageCount);
+ int pageCount) {
+ this.results.haveFormattedPageSequence(pageSequence, pageCount);
if (log.isDebugEnabled()) {
- log.debug("Last page-sequence produced "
- + pageCount + " pages.");
+ log.debug("Last page-sequence produced " + pageCount + " pages.");
}
}
/**
* End the document.
- *
+ *
* @throws SAXException if there is some error
*/
public void endDocument() throws SAXException {
finishPrevPageSequence(null);
// process fox:destination elements
- ArrayList destinationList = rootFObj.getDestinationList();
+ List destinationList = rootFObj.getDestinationList();
if (destinationList != null) {
- while(destinationList.size() > 0) {
- Destination destination = (Destination)destinationList.remove(0);
+ while (destinationList.size() > 0) {
+ Destination destination = (Destination) destinationList.remove(0);
DestinationData destinationData = new DestinationData(destination);
addOffDocumentItem(destinationData);
}
@@ -430,35 +429,23 @@ public class AreaTreeHandler extends FOEventHandler {
BookmarkData data = new BookmarkData(bookmarkTree);
addOffDocumentItem(data);
if (!data.isResolved()) {
- //bookmarks did not fully resolve, add anyway. (hacky? yeah)
+ // bookmarks did not fully resolve, add anyway. (hacky? yeah)
model.handleOffDocumentItem(data);
}
}
model.endDocument();
- if (outputStatistics) {
- long memoryNow = runtime.totalMemory() - runtime.freeMemory();
- long memoryUsed = (memoryNow - initialMemory) / 1024L;
- long timeUsed = System.currentTimeMillis() - startTime;
- int pageCount = rootFObj.getTotalPagesGenerated();
- log.debug("Initial heap size: " + (initialMemory / 1024L) + "Kb");
- log.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
- log.debug("Total memory used: " + memoryUsed + "Kb");
- log.debug("Total time used: " + timeUsed + "ms");
- log.debug("Pages rendered: " + pageCount);
- if (pageCount > 0) {
- long perPage = (timeUsed / pageCount);
- long ppm = (timeUsed != 0 ? Math.round(60000 * pageCount / (double)timeUsed) : -1);
- log.debug("Avg render time: " + perPage + "ms/page (" + ppm + "pages/min)");
- }
+ if (statistics != null) {
+ statistics.logResults();
}
}
/**
- * Add a OffDocumentItem to the area tree model
- * This checks if the OffDocumentItem is resolvable and attempts
- * to resolve or add the resolvable ids for later resolution.
+ * Add a OffDocumentItem to the area tree model. This checks if the
+ * OffDocumentItem is resolvable and attempts to resolve or add the
+ * resolvable ids for later resolution.
+ *
* @param odi the OffDocumentItem to add.
*/
private void addOffDocumentItem(OffDocumentItem odi) {
@@ -469,8 +456,8 @@ public class AreaTreeHandler extends FOEventHandler {
if (idLocations.containsKey(ids[count])) {
res.resolveIDRef(ids[count], (List) idLocations.get(ids[count]));
} else {
- log.warn(odi.getName() + ": Unresolved id reference \""
- + ids[count] + "\" found.");
+ log.warn(odi.getName() + ": Unresolved id reference \""
+ + ids[count] + "\" found.");
addUnresolvedIDRef(ids[count], res);
}
}
@@ -482,15 +469,60 @@ public class AreaTreeHandler extends FOEventHandler {
model.handleOffDocumentItem(odi);
}
}
-
+
/**
* Generates and returns a unique key for a page viewport.
+ *
* @return the generated key.
*/
public String generatePageViewportKey() {
this.idGen++;
return "P" + this.idGen;
}
-
-}
+ /**
+ * Gather statistics when log is debug
+ */
+ private final class Statistics {
+ // for statistics gathering
+ private Runtime runtime;
+
+ // heap memory allocated (for statistics)
+ private long initialMemory;
+
+ // time used in rendering (for statistics)
+ private long startTime;
+
+ private Statistics() {
+ runtime = Runtime.getRuntime();
+ }
+
+ public void start() {
+ initialMemory = runtime.totalMemory() - runtime.freeMemory();
+ startTime = System.currentTimeMillis();
+ }
+
+ public void end() {
+ long memoryNow = runtime.totalMemory() - runtime.freeMemory();
+ log.debug("Current heap size: " + (memoryNow / 1024L) + "KB");
+ }
+
+ public void logResults() {
+ long memoryNow = runtime.totalMemory() - runtime.freeMemory();
+ long memoryUsed = (memoryNow - initialMemory) / 1024L;
+ long timeUsed = System.currentTimeMillis() - startTime;
+ int pageCount = rootFObj.getTotalPagesGenerated();
+ log.debug("Initial heap size: " + (initialMemory / 1024L) + "KB");
+ log.debug("Current heap size: " + (memoryNow / 1024L) + "KB");
+ log.debug("Total memory used: " + memoryUsed + "KB");
+ log.debug("Total time used: " + timeUsed + "ms");
+ log.debug("Pages rendered: " + pageCount);
+ if (pageCount > 0) {
+ long perPage = (timeUsed / pageCount);
+ long ppm = (timeUsed != 0 ? Math.round(60000 * pageCount
+ / (double) timeUsed) : -1);
+ log.debug("Avg render time: " + perPage + "ms/page (" + ppm + "pages/min)");
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java
index fce776e22..9e293afac 100644
--- a/src/java/org/apache/fop/area/AreaTreeParser.java
+++ b/src/java/org/apache/fop/area/AreaTreeParser.java
@@ -23,6 +23,7 @@ import java.awt.Color;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
@@ -38,7 +39,9 @@ import javax.xml.transform.sax.TransformerHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.area.Trait.InternalLink;
import org.apache.fop.area.Trait.Background;
+import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.area.inline.AbstractTextArea;
import org.apache.fop.area.inline.Character;
import org.apache.fop.area.inline.ForeignObject;
@@ -98,10 +101,10 @@ public class AreaTreeParser {
transformer.setErrorListener(new DefaultErrorListener(log));
SAXResult res = new SAXResult(getContentHandler(treeModel, userAgent));
-
+
transformer.transform(src, res);
}
-
+
/**
* Creates a new ContentHandler instance that you can send the area tree XML to. The parsed
* pages are added to the AreaTreeModel instance you pass in as a parameter.
@@ -110,33 +113,37 @@ public class AreaTreeParser {
* @return the ContentHandler instance to receive the SAX stream from the area tree XML
*/
public ContentHandler getContentHandler(AreaTreeModel treeModel, FOUserAgent userAgent) {
- ElementMappingRegistry elementMappingRegistry
+ ElementMappingRegistry elementMappingRegistry
= userAgent.getFactory().getElementMappingRegistry();
return new Handler(treeModel, userAgent, elementMappingRegistry);
}
-
+
private static class Handler extends DefaultHandler {
-
+
private Map makers = new java.util.HashMap();
-
+
private AreaTreeModel treeModel;
private FOUserAgent userAgent;
private ElementMappingRegistry elementMappingRegistry;
-
+
private Attributes lastAttributes;
private StringBuffer content = new StringBuffer();
private PageViewport currentPageViewport;
+ private Map pageViewportsByKey = new java.util.HashMap();
+ // set of "ID firsts" that have already been assigned to a PV:
+ private Set idFirstsAssigned = new java.util.HashSet();
+
private Stack areaStack = new Stack();
private boolean firstFlow;
private boolean pendingStartPageSequence;
-
+
private Stack delegateStack = new Stack();
private ContentHandler delegate;
private DOMImplementation domImplementation;
-
-
- public Handler(AreaTreeModel treeModel, FOUserAgent userAgent,
+
+
+ public Handler(AreaTreeModel treeModel, FOUserAgent userAgent,
ElementMappingRegistry elementMappingRegistry) {
this.treeModel = treeModel;
this.userAgent = userAgent;
@@ -159,6 +166,7 @@ public class AreaTreeParser {
makers.put("beforeFloat", new BeforeFloatMaker());
makers.put("block", new BlockMaker());
makers.put("lineArea", new LineAreaMaker());
+ makers.put("inline", new InlineMaker());
makers.put("inlineparent", new InlineParentMaker());
makers.put("inlineblockparent", new InlineBlockParentMaker());
makers.put("text", new TextMaker());
@@ -169,6 +177,8 @@ public class AreaTreeParser {
makers.put("viewport", new ViewportMaker());
makers.put("image", new ImageMaker());
makers.put("foreignObject", new ForeignObjectMaker());
+ makers.put("bookmarkTree", new BookmarkTreeMaker());
+ makers.put("bookmark", new BookmarkMaker());
}
private static Rectangle2D parseRect(String rect) {
@@ -179,7 +189,7 @@ public class AreaTreeParser {
Double.parseDouble(tokenizer.nextToken()),
Double.parseDouble(tokenizer.nextToken()));
}
-
+
private Area findAreaType(Class clazz) {
if (areaStack.size() > 0) {
int pos = areaStack.size() - 1;
@@ -193,7 +203,7 @@ public class AreaTreeParser {
}
return null;
}
-
+
private RegionViewport getCurrentRegionViewport() {
return (RegionViewport)findAreaType(RegionViewport.class);
}
@@ -201,21 +211,21 @@ public class AreaTreeParser {
private BodyRegion getCurrentBodyRegion() {
return (BodyRegion)findAreaType(BodyRegion.class);
}
-
+
private BlockParent getCurrentBlockParent() {
return (BlockParent)findAreaType(BlockParent.class);
}
-
+
private AbstractTextArea getCurrentText() {
return (AbstractTextArea)findAreaType(AbstractTextArea.class);
}
-
+
private Viewport getCurrentViewport() {
return (Viewport)findAreaType(Viewport.class);
}
-
+
/** @see org.xml.sax.helpers.DefaultHandler */
- public void startElement(String uri, String localName, String qName, Attributes attributes)
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (delegate != null) {
delegateStack.push(qName);
@@ -234,7 +244,7 @@ public class AreaTreeParser {
handler.setResult(new DOMResult(doc));
Area parent = (Area)areaStack.peek();
((ForeignObject)parent).setDocument(doc);
-
+
//activate delegate for nested foreign document
domImplementation = null; //Not needed anymore now
this.delegate = handler;
@@ -268,16 +278,16 @@ public class AreaTreeParser {
}
if (!handled) {
if (uri == null || uri.length() == 0) {
- throw new SAXException("Unhandled element " + localName
+ throw new SAXException("Unhandled element " + localName
+ " in namespace: " + uri);
} else {
- log.warn("Unhandled element " + localName
+ log.warn("Unhandled element " + localName
+ " in namespace: " + uri);
}
}
}
}
-
+
/** @see org.xml.sax.helpers.DefaultHandler */
public void endElement(String uri, String localName, String qName) throws SAXException {
if (delegate != null) {
@@ -303,14 +313,14 @@ public class AreaTreeParser {
content.setLength(0); //Reset text buffer (see characters())
}
}
-
+
// ============== Maker classes for the area tree objects =============
-
+
private static interface Maker {
void startElement(Attributes attributes) throws SAXException;
void endElement();
}
-
+
private abstract class AbstractMaker implements Maker {
public void startElement(Attributes attributes) throws SAXException {
@@ -321,11 +331,15 @@ public class AreaTreeParser {
//nop
}
}
-
+
private class AreaTreeMaker extends AbstractMaker {
- //no overrides
+
+ public void startElement(Attributes attributes) {
+ // In case the Handler is reused:
+ idFirstsAssigned.clear();
+ }
}
-
+
private class PageSequenceMaker extends AbstractMaker {
public void startElement(Attributes attributes) {
@@ -333,7 +347,7 @@ public class AreaTreeParser {
//treeModel.startPageSequence(null); Done after title or on the first viewport
}
}
-
+
private class TitleMaker extends AbstractMaker {
public void startElement(Attributes attributes) {
@@ -347,10 +361,10 @@ public class AreaTreeParser {
treeModel.startPageSequence(line);
pendingStartPageSequence = false;
}
-
-
+
+
}
-
+
private class PageViewportMaker extends AbstractMaker {
public void startElement(Attributes attributes) {
@@ -367,15 +381,16 @@ public class AreaTreeParser {
String pageNumberString = attributes.getValue("formatted-nr");
String pageMaster = attributes.getValue("simple-page-master-name");
boolean blank = getAttributeAsBoolean(attributes, "blank", false);
- currentPageViewport = new PageViewport(viewArea,
+ currentPageViewport = new PageViewport(viewArea,
pageNumber, pageNumberString,
pageMaster, blank);
transferForeignObjects(attributes, currentPageViewport);
currentPageViewport.setKey(key);
+ pageViewportsByKey.put(key, currentPageViewport);
}
}
-
+
private class PageMaker extends AbstractMaker {
public void startElement(Attributes attributes) {
@@ -411,7 +426,7 @@ public class AreaTreeParser {
assertObjectOfClass(areaStack.pop(), RegionViewport.class);
}
}
-
+
private class RegionBeforeMaker extends AbstractMaker {
public void startElement(Attributes attributes) {
@@ -428,7 +443,7 @@ public class AreaTreeParser {
public void startElement(Attributes attributes) {
pushNewRegionReference(attributes, Constants.FO_REGION_AFTER);
}
-
+
public void endElement() {
assertObjectOfClass(areaStack.pop(), RegionReference.class);
}
@@ -439,7 +454,7 @@ public class AreaTreeParser {
public void startElement(Attributes attributes) {
pushNewRegionReference(attributes, Constants.FO_REGION_START);
}
-
+
public void endElement() {
assertObjectOfClass(areaStack.pop(), RegionReference.class);
}
@@ -450,7 +465,7 @@ public class AreaTreeParser {
public void startElement(Attributes attributes) {
pushNewRegionReference(attributes, Constants.FO_REGION_END);
}
-
+
public void endElement() {
assertObjectOfClass(areaStack.pop(), RegionReference.class);
}
@@ -557,7 +572,7 @@ public class AreaTreeParser {
private class BlockMaker extends AbstractMaker {
public void startElement(Attributes attributes) {
- boolean isViewport = getAttributeAsBoolean(attributes,
+ boolean isViewport = getAttributeAsBoolean(attributes,
"is-viewport-area", false);
Block block;
if (isViewport) {
@@ -618,10 +633,31 @@ public class AreaTreeParser {
parent.addChildArea(line);
areaStack.push(line);
}
-
+
public void endElement() {
assertObjectOfClass(areaStack.pop(), LineArea.class);
- }
+ }
+ }
+
+ // Maker for "generic" inline areas
+ private class InlineMaker extends AbstractMaker {
+
+ public void startElement(Attributes attributes) {
+ InlineArea inl = new InlineArea();
+ transferForeignObjects(attributes, inl);
+ inl.setOffset(getAttributeAsInteger(attributes, "offset", 0));
+ setAreaAttributes(attributes, inl);
+ setTraits(attributes, inl, SUBSET_COMMON);
+ setTraits(attributes, inl, SUBSET_BOX);
+ setTraits(attributes, inl, SUBSET_COLOR);
+ Area parent = (Area)areaStack.peek();
+ parent.addChildArea(inl);
+ areaStack.push(inl);
+ }
+
+ public void endElement() {
+ assertObjectOfClass(areaStack.pop(), InlineArea.class);
+ }
}
private class InlineParentMaker extends AbstractMaker {
@@ -639,10 +675,10 @@ public class AreaTreeParser {
parent.addChildArea(ip);
areaStack.push(ip);
}
-
+
public void endElement() {
assertObjectOfClass(areaStack.pop(), InlineParent.class);
- }
+ }
}
private class InlineBlockParentMaker extends AbstractMaker {
@@ -662,7 +698,7 @@ public class AreaTreeParser {
public void endElement() {
assertObjectOfClass(areaStack.pop(), InlineBlockParent.class);
- }
+ }
}
private class TextMaker extends AbstractMaker {
@@ -679,7 +715,7 @@ public class AreaTreeParser {
setTraits(attributes, text, SUBSET_FONT);
text.setBaselineOffset(getAttributeAsInteger(attributes, "baseline", 0));
text.setOffset(getAttributeAsInteger(attributes, "offset", 0));
- text.setTextLetterSpaceAdjust(getAttributeAsInteger(attributes,
+ text.setTextLetterSpaceAdjust(getAttributeAsInteger(attributes,
"tlsadjust", 0));
text.setTextWordSpaceAdjust(getAttributeAsInteger(attributes,
"twsadjust", 0));
@@ -841,19 +877,58 @@ public class AreaTreeParser {
getCurrentViewport().setContent(foreign);
areaStack.push(foreign);
}
-
+
public void endElement() {
assertObjectOfClass(areaStack.pop(), ForeignObject.class);
- }
+ }
+ }
+
+ private class BookmarkTreeMaker extends AbstractMaker {
+
+ public void startElement(Attributes attributes) {
+ BookmarkData bm = new BookmarkData();
+ areaStack.push(bm);
+ }
+
+ public void endElement() {
+ Object tos = areaStack.pop();
+ assertObjectOfClass(tos, BookmarkData.class);
+ treeModel.handleOffDocumentItem((BookmarkData) tos);
+ // as long as the bookmark tree comes after the last PageViewport in the
+ // area tree XML, we don't have to worry about resolved/unresolved. The
+ // only resolution needed is the mapping of the pvKey to the PV instance.
+ }
+ }
+
+ private class BookmarkMaker extends AbstractMaker {
+
+ public void startElement(Attributes attributes) {
+ String title = attributes.getValue("title");
+ boolean showChildren = getAttributeAsBoolean(attributes, "show-children", false);
+ String[] linkdata
+ = InternalLink.parseXMLAttribute(attributes.getValue("internal-link"));
+ PageViewport pv = (PageViewport) pageViewportsByKey.get(linkdata[0]);
+ BookmarkData bm = new BookmarkData(title, showChildren, pv, linkdata[1]);
+ Object tos = areaStack.peek();
+ if (tos instanceof BookmarkData) {
+ BookmarkData parent = (BookmarkData) tos;
+ parent.addSubData(bm);
+ }
+ areaStack.push(bm);
+ }
+
+ public void endElement() {
+ assertObjectOfClass(areaStack.pop(), BookmarkData.class);
+ }
}
// ====================================================================
-
+
private void pushNewRegionReference(Attributes attributes, int side) {
String regionName = attributes.getValue("name");
RegionViewport rv = getCurrentRegionViewport();
- RegionReference reg = new RegionReference(side,
+ RegionReference reg = new RegionReference(side,
regionName, rv);
transferForeignObjects(attributes, reg);
reg.setCTM(getAttributeAsCTM(attributes, "ctm"));
@@ -903,7 +978,7 @@ public class AreaTreeParser {
Trait.BACKGROUND, Trait.COLOR};
private static final Object[] SUBSET_FONT = new Object[] {
Trait.FONT, Trait.FONT_SIZE, Trait.BLINK,
- Trait.OVERLINE, Trait.OVERLINE_COLOR,
+ Trait.OVERLINE, Trait.OVERLINE_COLOR,
Trait.LINETHROUGH, Trait.LINETHROUGH_COLOR,
Trait.UNDERLINE, Trait.UNDERLINE_COLOR};
private static final Object[] SUBSET_BOX = new Object[] {
@@ -926,12 +1001,20 @@ public class AreaTreeParser {
area.addTrait(trait, Boolean.valueOf(value));
} else if (cl == String.class) {
area.addTrait(trait, value);
+ if (trait == Trait.PROD_ID
+ && !idFirstsAssigned.contains(value)
+ && currentPageViewport != null) {
+ currentPageViewport.setFirstWithID(value);
+ idFirstsAssigned.add(value);
+ }
} else if (cl == Color.class) {
try {
area.addTrait(trait, ColorUtil.parseColorString(this.userAgent, value));
} catch (PropertyException e) {
throw new IllegalArgumentException(e.getMessage());
}
+ } else if (cl == InternalLink.class) {
+ area.addTrait(trait, new InternalLink(value));
} else if (cl == Background.class) {
Background bkg = new Background();
try {
@@ -940,11 +1023,11 @@ public class AreaTreeParser {
bkg.setColor(col);
} catch (PropertyException e) {
throw new IllegalArgumentException(e.getMessage());
- }
+ }
String url = attributes.getValue("bkg-img");
if (url != null) {
bkg.setURL(url);
-
+
ImageFactory fact = userAgent.getFactory().getImageFactory();
FopImage img = fact.getImage(url, userAgent);
if (img == null) {
@@ -952,7 +1035,7 @@ public class AreaTreeParser {
} else {
// load dimensions
if (!img.load(FopImage.DIMENSIONS)) {
- log.error("Cannot read background image dimensions: "
+ log.error("Cannot read background image dimensions: "
+ url);
}
}
@@ -962,9 +1045,9 @@ public class AreaTreeParser {
if (repeat != null) {
bkg.setRepeat(repeat);
}
- bkg.setHoriz(getAttributeAsInteger(attributes,
+ bkg.setHoriz(getAttributeAsInteger(attributes,
"bkg-horz-offset", 0));
- bkg.setVertical(getAttributeAsInteger(attributes,
+ bkg.setVertical(getAttributeAsInteger(attributes,
"bkg-vert-offset", 0));
}
area.addTrait(trait, bkg);
@@ -978,15 +1061,15 @@ public class AreaTreeParser {
String fontStyle = attributes.getValue("font-style");
int fontWeight = getAttributeAsInteger(
attributes, "font-weight", Font.NORMAL);
- area.addTrait(trait,
+ area.addTrait(trait,
FontInfo.createFontKey(fontName, fontStyle, fontWeight));
}
}
}
}
}
-
- private boolean getAttributeAsBoolean(Attributes attributes, String name,
+
+ private boolean getAttributeAsBoolean(Attributes attributes, String name,
boolean defaultValue) {
String s = attributes.getValue(name);
if (s == null) {
@@ -1058,5 +1141,5 @@ public class AreaTreeParser {
}
}
-
+
}
diff --git a/src/java/org/apache/fop/area/BookmarkData.java b/src/java/org/apache/fop/area/BookmarkData.java
index 8ef1e6086..2ad8273a9 100644
--- a/src/java/org/apache/fop/area/BookmarkData.java
+++ b/src/java/org/apache/fop/area/BookmarkData.java
@@ -19,7 +19,6 @@
package org.apache.fop.area;
-import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
@@ -32,7 +31,7 @@ import org.apache.fop.fo.pagination.bookmarks.Bookmark;
* child bookmark-items under it.
*/
public class BookmarkData extends AbstractOffDocumentItem implements Resolvable {
- private ArrayList subData = new ArrayList();
+ private List subData = new java.util.ArrayList();
// bookmark-title for this fo:bookmark
private String bookmarkTitle = null;
@@ -61,7 +60,7 @@ public class BookmarkData extends AbstractOffDocumentItem implements Resolvable
whenToProcess = END_OF_DOC;
// top level defined in Rec to show all child bookmarks
bShow = true;
-
+
for (int count = 0; count < bookmarkTree.getBookmarks().size(); count++) {
Bookmark bkmk = (Bookmark)(bookmarkTree.getBookmarks()).get(count);
addSubData(createBookmarkData(bkmk));
@@ -84,6 +83,34 @@ public class BookmarkData extends AbstractOffDocumentItem implements Resolvable
}
/**
+ * Create a new bookmark data root object.
+ * This constructor is called by the AreaTreeParser when the
+ * <bookmarkTree> element is read from the XML file
+ */
+ public BookmarkData() {
+ idRef = null;
+ whenToProcess = END_OF_DOC;
+ bShow = true;
+ }
+
+ /**
+ * Create a new bookmark data object.
+ * This constructor is called by the AreaTreeParser when a
+ * <bookmark> element is read from the XML file.
+ *
+ * @param title the bookmark's title
+ * @param showChildren whether to initially display the bookmark's children
+ * @param pv the target PageViewport
+ * @param idRef the target ID
+ */
+ public BookmarkData(String title, boolean showChildren, PageViewport pv, String idRef) {
+ bookmarkTitle = title;
+ bShow = showChildren;
+ pageRef = pv;
+ this.idRef = idRef;
+ }
+
+ /**
* Get the idref for this bookmark-item
*
* @return the idref for the bookmark-item
@@ -93,17 +120,19 @@ public class BookmarkData extends AbstractOffDocumentItem implements Resolvable
}
/**
- * Add the child bookmark data object.
+ * Add a child bookmark data object.
* This adds a child bookmark in the bookmark hierarchy.
*
* @param sub the child bookmark data
*/
public void addSubData(BookmarkData sub) {
subData.add(sub);
- unresolvedIDRefs.put(sub.getIDRef(), sub);
- String[] ids = sub.getIDRefs();
- for (int count = 0; count < ids.length; count++) {
- unresolvedIDRefs.put(ids[count], sub);
+ if (sub.pageRef == null || sub.pageRef.equals("")) {
+ unresolvedIDRefs.put(sub.getIDRef(), sub);
+ String[] ids = sub.getIDRefs();
+ for (int count = 0; count < ids.length; count++) {
+ unresolvedIDRefs.put(ids[count], sub);
+ }
}
}
@@ -221,4 +250,3 @@ public class BookmarkData extends AbstractOffDocumentItem implements Resolvable
}
}
-
diff --git a/src/java/org/apache/fop/area/DestinationData.java b/src/java/org/apache/fop/area/DestinationData.java
index 7e59210e0..6b873ef84 100644
--- a/src/java/org/apache/fop/area/DestinationData.java
+++ b/src/java/org/apache/fop/area/DestinationData.java
@@ -28,9 +28,6 @@ import org.apache.fop.area.PageViewport;
*/
public class DestinationData extends AbstractOffDocumentItem implements Resolvable {
- // PDFReference (object reference) for this destination
- private String goToReference;
-
// ID Reference for this bookmark
private String idRef;
@@ -79,24 +76,6 @@ public class DestinationData extends AbstractOffDocumentItem implements Resolvab
}
/**
- * Set the GoToReference for this destination
- *
- * @param goToReference the GoToReference to associate with this destination
- */
- public void setGoToReference(String goToReference) {
- this.goToReference = goToReference;
- }
-
- /**
- * Get the GoToReference for this destination
- *
- * @return the GoToReference associated with this destination
- */
- public String getGoToReference() {
- return goToReference;
- }
-
- /**
* Check if this resolvable object has been resolved.
* For now, just return true.
* To do: Find a way to determine whether the destination has been resolved.
diff --git a/src/java/org/apache/fop/area/LinkResolver.java b/src/java/org/apache/fop/area/LinkResolver.java
index 603aede87..0b0441bd2 100644
--- a/src/java/org/apache/fop/area/LinkResolver.java
+++ b/src/java/org/apache/fop/area/LinkResolver.java
@@ -65,15 +65,25 @@ public class LinkResolver implements Resolvable, Serializable {
}
/**
- * Resolve by adding an internal link.
+ * Resolve by adding an internal link to the first PageViewport in the list.
*
* @see org.apache.fop.area.Resolvable#resolveIDRef(String, List)
*/
public void resolveIDRef(String id, List pages) {
- if (idRef.equals(id) && pages != null) {
+ resolveIDRef(id, (PageViewport)pages.get(0));
+ }
+
+ /**
+ * Resolve by adding an InternalLink trait to the area
+ *
+ * @param id the target id (should be equal to the object's idRef)
+ * @param pv the PageViewport containing the first area with the given id
+ */
+ public void resolveIDRef(String id, PageViewport pv) {
+ if (idRef.equals(id) && pv != null) {
resolved = true;
- PageViewport page = (PageViewport)pages.get(0);
- area.addTrait(Trait.INTERNAL_LINK, page.getKey());
+ Trait.InternalLink iLink = new Trait.InternalLink(pv.getKey(), idRef);
+ area.addTrait(Trait.INTERNAL_LINK, iLink);
}
}
}
diff --git a/src/java/org/apache/fop/area/PageViewport.java b/src/java/org/apache/fop/area/PageViewport.java
index 282bce367..953cb3840 100644
--- a/src/java/org/apache/fop/area/PageViewport.java
+++ b/src/java/org/apache/fop/area/PageViewport.java
@@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -67,6 +68,9 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl
// list of id references and the rectangle on the page
//private Map idReferences = null;
+ // set of IDs that appear first (or exclusively) on this page:
+ private Set idFirsts = new java.util.HashSet();
+
// this keeps a list of currently unresolved areas or extensions
// once an idref is resolved it is removed
// when this is empty the page can be rendered
@@ -237,6 +241,29 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl
}
/**
+ * Add an "ID-first" to this page.
+ * This is typically called by the AreaTreeHandler when associating
+ * an ID with a PageViewport.
+ *
+ * @param id the id to be registered as first appearing on this page
+ */
+ public void setFirstWithID(String id) {
+ if (id != null) {
+ idFirsts.add(id);
+ }
+ }
+
+ /**
+ * Check whether a certain id first appears on this page
+ *
+ * @param id the id to be checked
+ * @return true if this page is the first where the id appears
+ */
+ public boolean isFirstWithID(String id) {
+ return idFirsts.contains(id);
+ }
+
+ /**
* Add an idref to this page.
* All idrefs found for child areas of this PageViewport are added
* to unresolvedIDRefs, for subsequent resolution by AreaTreeHandler
diff --git a/src/java/org/apache/fop/area/Trait.java b/src/java/org/apache/fop/area/Trait.java
index 3cddc8006..32582dec1 100644
--- a/src/java/org/apache/fop/area/Trait.java
+++ b/src/java/org/apache/fop/area/Trait.java
@@ -43,9 +43,9 @@ public class Trait implements Serializable {
/**
* Internal link trait.
- * This is resolved and provides a link to an internal area.
+ * Contains the PageViewport key and the PROD_ID of the target area
*/
- public static final Integer INTERNAL_LINK = new Integer(1); //resolved
+ public static final Integer INTERNAL_LINK = new Integer(1);
/**
* External link. A URL link to an external resource.
@@ -224,7 +224,7 @@ public class Trait implements Serializable {
static {
// Create a hashmap mapping trait code to name for external representation
//put(ID_LINK, new TraitInfo("id-link", String.class));
- put(INTERNAL_LINK, new TraitInfo("internal-link", String.class));
+ put(INTERNAL_LINK, new TraitInfo("internal-link", InternalLink.class));
put(EXTERNAL_LINK, new TraitInfo("external-link", String.class));
put(FONT, new TraitInfo("font", FontTriplet.class));
put(FONT_SIZE, new TraitInfo("font-size", Integer.class));
@@ -411,7 +411,140 @@ public class Trait implements Serializable {
return null;
}*/
-
+ /**
+ * Class for internal link traits.
+ * Stores PageViewport key and producer ID
+ */
+ public static class InternalLink implements Serializable {
+
+ /** The unique key of the PageViewport. */
+ private String pvKey;
+
+ /** The PROD_ID of the link target */
+ private String idRef;
+
+ /**
+ * Create an InternalLink to the given PageViewport and target ID
+ *
+ * @param pvKey the PageViewport key
+ * @param idRef the target ID
+ */
+ public InternalLink(String pvKey, String idRef) {
+ setPVKey(pvKey);
+ setIDRef(idRef);
+ }
+
+ /**
+ * Create an InternalLink based on the given XML attribute value.
+ * This is typically called when data are read from an XML area tree.
+ *
+ * @param attrValue attribute value to be parsed by InternalLink.parseXMLAttribute
+ */
+ public InternalLink(String attrValue) {
+ String[] values = parseXMLAttribute(attrValue);
+ setPVKey(values[0]);
+ setIDRef(values[1]);
+ }
+
+ /**
+ * Sets the key of the targeted PageViewport.
+ *
+ * @param pvKey the PageViewport key
+ */
+ public void setPVKey(String pvKey) {
+ this.pvKey = pvKey;
+ }
+
+ /**
+ * Returns the key of the targeted PageViewport.
+ *
+ * @return the PageViewport key
+ */
+ public String getPVKey() {
+ return pvKey;
+ }
+
+ /**
+ * Sets the target ID.
+ *
+ * @param idRef the target ID
+ */
+ public void setIDRef(String idRef) {
+ this.idRef = idRef;
+ }
+
+ /**
+ * Returns the target ID.
+ *
+ * @return the target ID
+ */
+ public String getIDRef() {
+ return idRef;
+ }
+
+ /**
+ * Returns the attribute value for this object as
+ * used in the area tree XML.
+ *
+ * @return a string of the type "(thisPVKey,thisIDRef)"
+ */
+ public String xmlAttribute() {
+ return makeXMLAttribute(pvKey, idRef);
+ }
+
+ /**
+ * Returns the XML attribute value for the given PV key and ID ref.
+ * This value is used in the area tree XML.
+ *
+ * @param pvKey the PageViewport key of the link target
+ * @param idRef the ID of the link target
+ * @return a string of the type "(thisPVKey,thisIDRef)"
+ */
+ public static String makeXMLAttribute(String pvKey, String idRef) {
+ return "(" + (pvKey == null ? "" : pvKey) + ","
+ + (idRef == null ? "" : idRef) + ")";
+ }
+
+ /**
+ * Parses XML attribute value from the area tree into
+ * PageViewport key + IDRef strings. If the attribute value is
+ * formatted like "(s1,s2)", then s1 and s2 are considered to be
+ * the PV key and the IDRef, respectively.
+ * Otherwise, the entire string is the PV key and the IDRef is null.
+ *
+ * @param attrValue the atribute value (PV key and possibly IDRef)
+ * @return a 2-String array containing the PV key and the IDRef.
+ * Both may be null.
+ */
+ public static String[] parseXMLAttribute(String attrValue) {
+ String[] result = {null, null};
+ if (attrValue != null) {
+ int len = attrValue.length();
+ if (len >= 2 && attrValue.charAt(0) == '(' && attrValue.charAt(len - 1) == ')') {
+ String value = attrValue.substring(1, len - 1); // remove brackets
+ int delimIndex = value.indexOf(',');
+ result[0] = value.substring(0, delimIndex).trim(); // PV key
+ result[1] = value.substring(delimIndex + 1, value.length()).trim(); // IDRef
+ } else {
+ // PV key only, e.g. from old area tree XML:
+ result[0] = attrValue;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Return the human-friendly string for debugging.
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("pvKey=").append(pvKey);
+ sb.append(",idRef=").append(idRef);
+ return sb.toString();
+ }
+ }
+
/**
* Background trait structure.
* Used for storing back trait information which are related.
diff --git a/src/java/org/apache/fop/fo/flow/Table.java b/src/java/org/apache/fop/fo/flow/Table.java
index ed69e3100..2fa46a75e 100644
--- a/src/java/org/apache/fop/fo/flow/Table.java
+++ b/src/java/org/apache/fop/fo/flow/Table.java
@@ -136,13 +136,6 @@ public class Table extends TableFObj {
+ " behavior on fo:table. Falling back to \"auto\"");
// Anyway, the bpd of a table is not used by the layout code
}
- if (borderCollapse != EN_SEPARATE) {
- //TODO Remove once the collapsing border is at least marginally working.
- borderCollapse = EN_SEPARATE;
- log.debug("A table has been forced to use the separate border model"
- + " (border-collapse=\"separate\") as the collapsing border model"
- + " is not implemented, yet.");
- }
if (tableLayout == EN_AUTO) {
attributeWarning("table-layout=\"auto\" is currently not supported by FOP");
}
diff --git a/src/java/org/apache/fop/fo/pagination/Root.java b/src/java/org/apache/fop/fo/pagination/Root.java
index 23f9cd391..929c721ec 100644
--- a/src/java/org/apache/fop/fo/pagination/Root.java
+++ b/src/java/org/apache/fop/fo/pagination/Root.java
@@ -21,7 +21,6 @@ package org.apache.fop.fo.pagination;
// java
import java.util.List;
-import java.util.ArrayList;
import org.xml.sax.Locator;
@@ -45,7 +44,7 @@ public class Root extends FObj {
private LayoutMasterSet layoutMasterSet;
private Declarations declarations;
private BookmarkTree bookmarkTree = null;
- private ArrayList destinationList;
+ private List destinationList;
private List pageSequences;
// temporary until above list populated
@@ -67,7 +66,7 @@ public class Root extends FObj {
*/
public Root(FONode parent) {
super(parent);
- pageSequences = new ArrayList();
+ pageSequences = new java.util.ArrayList();
if (parent != null) {
//throw new FOPException("root must be root element");
}
@@ -260,7 +259,7 @@ public class Root extends FObj {
*/
public void addDestination(Destination destination) {
if (destinationList == null) {
- destinationList = new ArrayList();
+ destinationList = new java.util.ArrayList();
}
destinationList.add(destination);
}
@@ -269,7 +268,7 @@ public class Root extends FObj {
* Public accessor for the list of Destination objects for this FO
* @return the Destination object
*/
- public ArrayList getDestinationList() {
+ public List getDestinationList() {
return destinationList;
}
diff --git a/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java b/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java
index 8892eae16..aed12a687 100644
--- a/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java
+++ b/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java
@@ -101,6 +101,8 @@ public class AreaAdditionUtil {
StackingIter childPosIter = new StackingIter(positionList.listIterator());
while ((childLM = childPosIter.getNextChildLM()) != null) {
+ // TODO vh: the test above might be problematic in some cases. See comment in
+ // the TableCellLM.getNextKnuthElements method
// Add the block areas to Area
lc.setFlags(LayoutContext.FIRST_AREA, childLM == firstLM);
lc.setFlags(LayoutContext.LAST_AREA, childLM == lastLM);
diff --git a/src/java/org/apache/fop/layoutmgr/ElementListUtils.java b/src/java/org/apache/fop/layoutmgr/ElementListUtils.java
index 1166482a9..08450db98 100644
--- a/src/java/org/apache/fop/layoutmgr/ElementListUtils.java
+++ b/src/java/org/apache/fop/layoutmgr/ElementListUtils.java
@@ -143,10 +143,15 @@ public class ElementListUtils {
i.add(new KnuthPenalty(0, KnuthPenalty.INFINITE, false,
null, false));
}
- } else if (el instanceof BreakElement) {
- BreakElement breakEl = (BreakElement)el;
- if (breakEl.getPenaltyValue() < KnuthPenalty.INFINITE) {
- breakEl.setPenaltyValue(KnuthPenalty.INFINITE);
+ } else if (el.isUnresolvedElement()) {
+ if (el instanceof BreakElement) {
+ BreakElement breakEl = (BreakElement)el;
+ if (breakEl.getPenaltyValue() < KnuthPenalty.INFINITE) {
+ breakEl.setPenaltyValue(KnuthPenalty.INFINITE);
+ }
+ } else if (el instanceof UnresolvedListElementWithLength) {
+ UnresolvedListElementWithLength uel = (UnresolvedListElementWithLength)el;
+ len += uel.getLength().opt;
}
} else {
KnuthElement kel = (KnuthElement)el;
diff --git a/src/java/org/apache/fop/layoutmgr/TraitSetter.java b/src/java/org/apache/fop/layoutmgr/TraitSetter.java
index 274204122..b50167b16 100644
--- a/src/java/org/apache/fop/layoutmgr/TraitSetter.java
+++ b/src/java/org/apache/fop/layoutmgr/TraitSetter.java
@@ -208,8 +208,6 @@ public class TraitSetter {
if (bps != null) {
area.addTrait(Trait.BORDER_END, bps);
}
-
- addPadding(area, bordProps, context);
}
private static void addPadding(Area area, CommonBorderPaddingBackground bordProps,
diff --git a/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java
index 3c9dd4ed9..7e261d074 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java
@@ -22,17 +22,17 @@ package org.apache.fop.layoutmgr.inline;
import org.apache.fop.datatypes.URISpecification;
import org.apache.fop.fo.flow.BasicLink;
import org.apache.fop.layoutmgr.LayoutManager;
+import org.apache.fop.layoutmgr.PageSequenceLayoutManager;
import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.area.Trait;
import org.apache.fop.area.LinkResolver;
-import org.apache.fop.area.PageViewport;
/**
* LayoutManager for the fo:basic-link formatting object
*/
public class BasicLinkLayoutManager extends InlineLayoutManager {
private BasicLink fobj;
-
+
/**
* Create an fo:basic-link layout manager.
*
@@ -49,22 +49,33 @@ public class BasicLinkLayoutManager extends InlineLayoutManager {
setupBasicLinkArea(parentLM, area);
return area;
}
-
- private void setupBasicLinkArea(LayoutManager parentLM,
- InlineArea area) {
- if (fobj.getExternalDestination() != null) {
- area.addTrait(Trait.EXTERNAL_LINK,
- URISpecification.getURL(fobj.getExternalDestination()));
- } else {
- String idref = fobj.getInternalDestination();
- PageViewport page = getPSLM().getFirstPVWithID(idref);
- if (page != null) {
- area.addTrait(Trait.INTERNAL_LINK, page.getKey());
- } else {
- LinkResolver res = new LinkResolver(idref, area);
- getPSLM().addUnresolvedArea(idref, res);
- }
- }
- }
-}
+ /*
+ * Detect internal or external link and add it as an area trait
+ *
+ * @param parentLM the parent LayoutManager
+ * @param area the basic-link's area
+ */
+ private void setupBasicLinkArea(LayoutManager parentLM, InlineArea area) {
+ // internal destinations take precedence:
+ String idref = fobj.getInternalDestination();
+ if (idref != null && idref.length() > 0) {
+ PageSequenceLayoutManager pslm = getPSLM();
+ // the INTERNAL_LINK trait is added by the LinkResolver
+ // if and when the link is resolved:
+ LinkResolver res = new LinkResolver(idref, area);
+ res.resolveIDRef(idref, pslm.getFirstPVWithID(idref));
+ if (!res.isResolved()) {
+ pslm.addUnresolvedArea(idref, res);
+ }
+ } else {
+ String extdest = fobj.getExternalDestination();
+ if (extdest != null) {
+ String url = URISpecification.getURL(extdest);
+ if (url.length() > 0) {
+ area.addTrait(Trait.EXTERNAL_LINK, url);
+ }
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/fop/layoutmgr/inline/FootnoteLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/FootnoteLayoutManager.java
index 91b7270eb..849ae08d8 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/FootnoteLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/FootnoteLayoutManager.java
@@ -26,19 +26,20 @@ import java.util.ListIterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.fo.flow.Footnote;
-import org.apache.fop.layoutmgr.AbstractLayoutManager;
import org.apache.fop.layoutmgr.FootnoteBodyLayoutManager;
import org.apache.fop.layoutmgr.InlineKnuthSequence;
import org.apache.fop.layoutmgr.KnuthElement;
import org.apache.fop.layoutmgr.KnuthSequence;
import org.apache.fop.layoutmgr.LayoutContext;
-import org.apache.fop.layoutmgr.Position;
+import org.apache.fop.layoutmgr.LayoutManager;
+import org.apache.fop.layoutmgr.ListElement;
+import org.apache.fop.layoutmgr.NonLeafPosition;
+import org.apache.fop.layoutmgr.PositionIterator;
/**
* Layout manager for fo:footnote.
*/
-public class FootnoteLayoutManager extends AbstractLayoutManager
- implements InlineLevelLayoutManager {
+public class FootnoteLayoutManager extends InlineStackingLayoutManager {
/**
* logging instance
@@ -72,13 +73,10 @@ public class FootnoteLayoutManager extends AbstractLayoutManager
/** @see org.apache.fop.layoutmgr.LayoutManager */
public LinkedList getNextKnuthElements(LayoutContext context,
int alignment) {
- // this is the only method that must be implemented:
- // all other methods will never be called, as the returned elements
- // contain Positions created by the citationLM, so its methods will
- // be called instead
-
- // set the citationLM parent to be this LM's parent
- citationLM.setParent(getParent());
+ // for the moment, this LM is set as the citationLM's parent
+ // later on, when this LM will have nothing more to do, the citationLM's parent
+ // will be set to the fotnoteLM's parent
+ citationLM.setParent(this);
citationLM.initialize();
bodyLM.setParent(this);
bodyLM.initialize();
@@ -104,22 +102,82 @@ public class FootnoteLayoutManager extends AbstractLayoutManager
addAnchor(returnedList);
+ // "wrap" the Position stored in each list inside returnedList
+ ListIterator listIterator = returnedList.listIterator();
+ ListIterator elementIterator = null;
+ KnuthSequence list = null;
+ ListElement element = null;
+ while (listIterator.hasNext()) {
+ list = (KnuthSequence) listIterator.next();
+ elementIterator = list.listIterator();
+ while (elementIterator.hasNext()) {
+ element = (KnuthElement) elementIterator.next();
+ element.setPosition(notifyPos(new NonLeafPosition(this, element.getPosition())));
+ }
+ }
+
+ return returnedList;
+ }
+
+ /**
+ * @see org.apache.fop.layoutmgr.LayoutManager#getChangedKnuthElements(java.util.List, int)
+ */
+ public LinkedList getChangedKnuthElements(List oldList,
+ int alignment) {
+ LinkedList returnedList = super.getChangedKnuthElements(oldList, alignment);
+ addAnchor(returnedList);
return returnedList;
}
+
+ /**
+ * @see org.apache.fop.layoutmgr.LayoutManager#addAreas(PositionIterator posIter, LayoutContext context);
+ */
+ public void addAreas(PositionIterator posIter, LayoutContext context) {
+ // "Unwrap" the NonLeafPositions stored in posIter and put
+ // them in a new list, that will be given to the citationLM
+ LinkedList positionList = new LinkedList();
+ NonLeafPosition pos = null;
+ while (posIter.hasNext()) {
+ pos = (NonLeafPosition) posIter.next();
+ if (pos != null && pos.getPosition() != null) {
+ positionList.add(pos.getPosition());
+ }
+ }
+
+ // FootnoteLM does not create any area,
+ // so the citationLM child will add directly to the FootnoteLM parent area
+ citationLM.setParent(getParent());
+
+ // make the citationLM add its areas
+ LayoutContext childContext = new LayoutContext(context);
+ StackingIter childPosIter = new StackingIter(positionList.listIterator());
+ LayoutManager childLM;
+ while ((childLM = childPosIter.getNextChildLM()) != null) {
+ childLM.addAreas(childPosIter, childContext);
+ childContext.setLeadingSpace(childContext.getTrailingSpace());
+ childContext.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
+ }
+ }
+
+ /**
+ * Find the last box in the sequence, and add a reference to the FootnoteBodyLM
+ * @param citationList the list of elements representing the footnote citation
+ */
private void addAnchor(LinkedList citationList) {
- // find the last box in the sequence, and add a reference
- // to the FootnoteBodyLM
KnuthInlineBox lastBox = null;
+ // the list of elements is searched backwards, until we find a box
ListIterator citationIterator = citationList.listIterator(citationList.size());
while (citationIterator.hasPrevious() && lastBox == null) {
Object obj = citationIterator.previous();
if (obj instanceof KnuthElement) {
+ // obj is an element
KnuthElement element = (KnuthElement)obj;
if (element instanceof KnuthInlineBox) {
lastBox = (KnuthInlineBox) element;
}
} else {
+ // obj is a sequence of elements
KnuthSequence seq = (KnuthSequence)obj;
ListIterator nestedIterator = seq.listIterator(seq.size());
while (nestedIterator.hasPrevious() && lastBox == null) {
@@ -137,45 +195,4 @@ public class FootnoteLayoutManager extends AbstractLayoutManager
//throw new IllegalStateException("No anchor box was found for a footnote.");
}
}
-
- /** @see org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager */
- public List addALetterSpaceTo(List oldList) {
- log.warn("null implementation of addALetterSpaceTo() called!");
- return oldList;
- }
-
- /**
- * Remove the word space represented by the given elements
- *
- * @param oldList the elements representing the word space
- */
- public void removeWordSpace(List oldList) {
- // do nothing
- log.warn(this.getClass().getName() + " should not receive a call to removeWordSpace(list)");
- }
-
- /** @see org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager */
- public void getWordChars(StringBuffer sbChars, Position pos) {
- log.warn("null implementation of getWordChars() called!");
- }
-
- /** @see org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager */
- public void hyphenate(Position pos, HyphContext hc) {
- log.warn("null implementation of hyphenate called!");
- }
-
- /** @see org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager */
- public boolean applyChanges(List oldList) {
- log.warn("null implementation of applyChanges() called!");
- return false;
- }
-
- /**
- * @see org.apache.fop.layoutmgr.LayoutManager#getChangedKnuthElements(java.util.List, int)
- */
- public LinkedList getChangedKnuthElements(List oldList,
- int alignment) {
- log.warn("null implementation of getChangeKnuthElement() called!");
- return null;
- }
}
diff --git a/src/java/org/apache/fop/layoutmgr/inline/WrapperLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/WrapperLayoutManager.java
index 9c946afbc..a9807573d 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/WrapperLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/WrapperLayoutManager.java
@@ -22,6 +22,8 @@ package org.apache.fop.layoutmgr.inline;
import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.fo.flow.Wrapper;
import org.apache.fop.layoutmgr.LayoutContext;
+import org.apache.fop.layoutmgr.PositionIterator;
+import org.apache.fop.layoutmgr.TraitSetter;
/**
* This is the layout manager for the fo:wrapper formatting object.
@@ -41,16 +43,41 @@ public class WrapperLayoutManager extends LeafNodeLayoutManager {
/** @see org.apache.fop.layoutmgr.inline.LeafNodeLayoutManager */
public InlineArea get(LayoutContext context) {
- //Create a zero-width, zero-height dummy area so this node can
- //participate in the ID handling. Otherwise, addId() wouldn't
- //be called.
+ // Create a zero-width, zero-height dummy area so this node can
+ // participate in the ID handling. Otherwise, addId() wouldn't
+ // be called. The area must also be added to the tree, because
+ // determination of the X,Y position is done in the renderer.
InlineArea area = new InlineArea();
+ String id = fobj.getId();
+ if (id != null && id.length() > 0) {
+ TraitSetter.setProducerID(area, fobj.getId());
+ }
return area;
}
-
+
+ /**
+ * Add the area for this layout manager.
+ * This adds the dummy area to the parent, *if* it has an id
+ * - otherwise it serves no purpose.
+ *
+ * @param posIter the position iterator
+ * @param context the layout context for adding the area
+ */
+ public void addAreas(PositionIterator posIter, LayoutContext context) {
+ String id = fobj.getId();
+ if (id != null && id.length() > 0) {
+ addId();
+ InlineArea area = getEffectiveArea();
+ parentLM.addChildArea(area);
+ }
+ while (posIter.hasNext()) {
+ posIter.next();
+ }
+ }
+
/** @see org.apache.fop.layoutmgr.inline.LeafNodeLayoutManager#addId() */
protected void addId() {
getPSLM().addIDToPage(fobj.getId());
}
-
+
}
diff --git a/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java
index 2f17a6a25..1ea836d38 100644
--- a/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java
@@ -34,6 +34,7 @@ import org.apache.fop.layoutmgr.BlockStackingLayoutManager;
import org.apache.fop.layoutmgr.BreakElement;
import org.apache.fop.layoutmgr.KnuthElement;
import org.apache.fop.layoutmgr.KnuthGlue;
+import org.apache.fop.layoutmgr.KnuthPenalty;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.ListElement;
import org.apache.fop.layoutmgr.PositionIterator;
@@ -175,78 +176,55 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager
childLC.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING, false);
}
- if (returnedList.size() == 1
- && ((ListElement)returnedList.getFirst()).isForcedBreak()) {
- // a descendant of this block has break-before
- if (returnList.size() == 0) {
- // the first child (or its first child ...) has
- // break-before;
- // all this block, including space before, will be put in
- // the
- // following page
- }
- contentList.addAll(returnedList);
-
- // "wrap" the Position inside each element
- // moving the elements from contentList to returnList
- returnedList = new LinkedList();
- wrapPositionElements(contentList, returnList);
-
- //Space resolution
- SpaceResolver.resolveElementList(returnList);
-
- return returnList;
- } else {
- if (prevLM != null) {
- // there is a block handled by prevLM
- // before the one handled by curLM
- if (mustKeepTogether()
- || context.isKeepWithNextPending()
- || childLC.isKeepWithPreviousPending()) {
- //Clear keep pending flag
- context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING, false);
- childLC.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING, false);
- // add an infinite penalty to forbid a break between
- // blocks
- contentList.add(new BreakElement(
- new Position(this), KnuthElement.INFINITE, context));
- //contentList.add(new KnuthPenalty(0,
- // KnuthElement.INFINITE, false,
- // new Position(this), false));
- } else if (!((ListElement) contentList.getLast()).isGlue()) {
- // add a null penalty to allow a break between blocks
- contentList.add(new BreakElement(
- new Position(this), 0, context));
- //contentList.add(new KnuthPenalty(0, 0, false,
- // new Position(this), false));
- } else {
- // the last element in contentList is a glue;
- // it is a feasible breakpoint, there is no need to add
- // a penalty
- }
- }
- contentList.addAll(returnedList);
- if (returnedList.size() == 0) {
- //Avoid NoSuchElementException below (happens with empty blocks)
- continue;
- }
- if (((ListElement)returnedList.getLast()).isForcedBreak()) {
- // a descendant of this block has break-after
- if (curLM.isFinished()) {
- // there is no other content in this block;
- // it's useless to add space after before a page break
- setFinished(true);
- }
-
- returnedList = new LinkedList();
- wrapPositionElements(contentList, returnList);
-
- //Space resolution
- SpaceResolver.resolveElementList(returnList);
-
- return returnList;
+ if (prevLM != null) {
+ // there is a block handled by prevLM
+ // before the one handled by curLM
+ if (mustKeepTogether()
+ || context.isKeepWithNextPending()
+ || childLC.isKeepWithPreviousPending()) {
+ //Clear keep pending flag
+ context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING, false);
+ childLC.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING, false);
+ // add an infinite penalty to forbid a break between
+ // blocks
+ contentList.add(new BreakElement(
+ new Position(this), KnuthElement.INFINITE, context));
+ //contentList.add(new KnuthPenalty(0,
+ // KnuthElement.INFINITE, false,
+ // new Position(this), false));
+ } else if (!(((ListElement) contentList.getLast()).isGlue()
+ || (((ListElement)contentList.getLast()).isPenalty()
+ && ((KnuthPenalty)contentList.getLast()).getP() < KnuthElement.INFINITE)
+ || (contentList.getLast() instanceof BreakElement
+ && ((BreakElement)contentList.getLast()).getPenaltyValue() < KnuthElement.INFINITE))) {
+ // TODO vh: this is hacky
+ // The getNextKnuthElements method of TableCellLM must not be called
+ // twice, otherwise some settings like indents or borders will be
+ // counted several times and lead to a wrong output. Anyway the
+ // getNextKnuthElements methods should be called only once eventually
+ // (i.e., when multi-threading the code), even when there are forced
+ // breaks.
+ // If we add a break possibility after a forced break the
+ // AreaAdditionUtil.addAreas method will act on a sequence starting
+ // with a SpaceResolver.SpaceHandlingBreakPosition element, having no
+ // LM associated to it. Thus it will stop early instead of adding
+ // areas for following Positions. The above test aims at preventing
+ // such a situation from occuring. add a null penalty to allow a break
+ // between blocks
+ contentList.add(new BreakElement(
+ new Position(this), 0, context));
+ //contentList.add(new KnuthPenalty(0, 0, false,
+ // new Position(this), false));
+ } else {
+ // the last element in contentList is a feasible breakpoint, there is
+ // no need to add a penalty
}
}
+ contentList.addAll(returnedList);
+ if (returnedList.size() == 0) {
+ //Avoid NoSuchElementException below (happens with empty blocks)
+ continue;
+ }
if (childLC.isKeepWithNextPending()) {
//Clear and propagate
childLC.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING, false);
@@ -388,38 +366,36 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager
int dx = xoffset;
for (int x = 0; x < gridUnits.length; x++) {
GridUnit gu = gridUnits[x];
- if (!gu.hasBorders()) {
- continue;
+ if (gu.hasBorders()) {
+ //Blocks for painting grid unit borders
+ Block block = new Block();
+ block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
+ block.setPositioning(Block.ABSOLUTE);
+
+ int bpd = spannedGridRowHeights[y - startRow];
+ bpd -= gu.getBorders().getBorderBeforeWidth(false) / 2;
+ bpd -= gu.getBorders().getBorderAfterWidth(false) / 2;
+ block.setBPD(bpd);
+ if (log.isTraceEnabled()) {
+ log.trace("pgu: " + primaryGridUnit + "; gu: " + gu + "; yoffset: "
+ + (dy - gu.getBorders().getBorderBeforeWidth(false) / 2)
+ + "; bpd: " + bpd);
+ }
+ int ipd = gu.getColumn().getColumnWidth().getValue(
+ (PercentBaseContext) getParent());
+ int borderStartWidth = gu.getBorders().getBorderStartWidth(false) / 2;
+ ipd -= borderStartWidth;
+ ipd -= gu.getBorders().getBorderEndWidth(false) / 2;
+ block.setIPD(ipd);
+ block.setXOffset(dx + borderStartWidth);
+ block.setYOffset(dy - gu.getBorders().getBorderBeforeWidth(false) / 2);
+ outer[0] = gu.getFlag(GridUnit.FIRST_IN_TABLE);
+ outer[1] = gu.getFlag(GridUnit.LAST_IN_TABLE);
+ outer[2] = gu.getFlag(GridUnit.IN_FIRST_COLUMN);
+ outer[3] = gu.getFlag(GridUnit.IN_LAST_COLUMN);
+ TraitSetter.addCollapsingBorders(block, gu.getBorders(), outer, this);
+ parentLM.addChildArea(block);
}
-
- //Blocks for painting grid unit borders
- Block block = new Block();
- block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
- block.setPositioning(Block.ABSOLUTE);
-
- int bpd = spannedGridRowHeights[y - startRow];
- bpd -= gu.getBorders().getBorderBeforeWidth(false) / 2;
- bpd -= gu.getBorders().getBorderAfterWidth(false) / 2;
- block.setBPD(bpd);
- if (log.isTraceEnabled()) {
- log.trace("pgu: " + primaryGridUnit + "; gu: " + gu + "; yoffset: "
- + (dy - gu.getBorders().getBorderBeforeWidth(false) / 2)
- + "; bpd: " + bpd);
- }
- int ipd = gu.getColumn().getColumnWidth().getValue(
- (PercentBaseContext) getParent());
- int borderStartWidth = gu.getBorders().getBorderStartWidth(false) / 2;
- ipd -= borderStartWidth;
- ipd -= gu.getBorders().getBorderEndWidth(false) / 2;
- block.setIPD(ipd);
- block.setXOffset(dx + borderStartWidth);
- block.setYOffset(dy - gu.getBorders().getBorderBeforeWidth(false) / 2);
- outer[0] = gu.getFlag(GridUnit.FIRST_IN_TABLE);
- outer[1] = gu.getFlag(GridUnit.LAST_IN_TABLE);
- outer[2] = gu.getFlag(GridUnit.IN_FIRST_COLUMN);
- outer[3] = gu.getFlag(GridUnit.IN_LAST_COLUMN);
- TraitSetter.addCollapsingBorders(block, gu.getBorders(), outer, this);
- parentLM.addChildArea(block);
dx += gu.getColumn().getColumnWidth().getValue(
(PercentBaseContext) getParent());
}
@@ -427,6 +403,8 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager
}
}
}
+ TraitSetter.addPadding(curBlockArea, primaryGridUnit.getBorders(),
+ false, false, false, false, this);
//Handle display-align
int contentBPD = getContentHeight(primaryGridUnit);
@@ -625,4 +603,3 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager
}
}
-
diff --git a/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java
index c3e1a41da..8abe1a3e8 100644
--- a/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java
@@ -69,7 +69,6 @@ public class TableContentLayoutManager implements PercentBaseContext {
private LinkedList footerList;
private int headerNetHeight = 0;
private int footerNetHeight = 0;
- private boolean firstBreakBeforeServed = false;
private int startXOffset;
private int usedBPD;
@@ -180,12 +179,24 @@ public class TableContentLayoutManager implements PercentBaseContext {
LinkedList returnList = getKnuthElementsForRowIterator(
bodyIter, context, alignment, TableRowIterator.BODY);
if (headerAsFirst != null) {
- returnList.add(0, headerAsFirst);
+ int insertionPoint = 0;
+ if (returnList.size() > 0 && ((ListElement)returnList.getFirst()).isForcedBreak()) {
+ insertionPoint++;
+ }
+ returnList.add(insertionPoint, headerAsFirst);
} else if (headerAsSecondToLast != null) {
- returnList.add(headerAsSecondToLast);
+ int insertionPoint = returnList.size();
+ if (returnList.size() > 0 && ((ListElement)returnList.getLast()).isForcedBreak()) {
+ insertionPoint--;
+ }
+ returnList.add(insertionPoint, headerAsSecondToLast);
}
if (footerAsLast != null) {
- returnList.add(footerAsLast);
+ int insertionPoint = returnList.size();
+ if (returnList.size() > 0 && ((ListElement)returnList.getLast()).isForcedBreak()) {
+ insertionPoint--;
+ }
+ returnList.add(insertionPoint, footerAsLast);
}
return returnList;
}
@@ -220,16 +231,10 @@ public class TableContentLayoutManager implements PercentBaseContext {
breakPoss.setBreakClass(rowFO.getBreakBefore());
}
} else {
- if (!firstBreakBeforeServed) {
- returnList.add(new BreakElement(new Position(getTableLM()),
- 0, -KnuthPenalty.INFINITE, rowFO.getBreakBefore(), context));
- iter.backToPreviousRow();
- firstBreakBeforeServed = true;
- break;
- }
+ returnList.add(new BreakElement(new Position(getTableLM()),
+ 0, -KnuthPenalty.INFINITE, rowFO.getBreakBefore(), context));
}
}
- firstBreakBeforeServed = true;
//Border resolution
if (!isSeparateBorderModel()) {
diff --git a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java
index 00ecb01d2..155111ac4 100644
--- a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java
@@ -29,6 +29,7 @@ import org.apache.fop.layoutmgr.KnuthElement;
import org.apache.fop.layoutmgr.KnuthGlue;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.ListElement;
+import org.apache.fop.layoutmgr.NonLeafPosition;
import org.apache.fop.layoutmgr.PositionIterator;
import org.apache.fop.layoutmgr.Position;
import org.apache.fop.layoutmgr.RelSide;
@@ -248,7 +249,34 @@ public class TableLayoutManager extends BlockStackingLayoutManager
log.debug("TableContentLM signals pending keep-with-previous");
context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING);
}
-
+
+ // Check if the table's content starts/ends with a forced break
+ // TODO this is hacky and will need to be handled better eventually
+ if (contentKnuthElements.size() > 0) {
+ ListElement element = (ListElement)contentKnuthElements.getFirst();
+ if (element.isForcedBreak()) {
+ // The first row of the table(-body), or (the content of) one of its cells
+ // has a forced break-before
+ int breakBeforeTable = ((Table) fobj).getBreakBefore();
+ if (breakBeforeTable == EN_PAGE
+ || breakBeforeTable == EN_COLUMN
+ || breakBeforeTable == EN_EVEN_PAGE
+ || breakBeforeTable == EN_ODD_PAGE) {
+ // There is already a forced break before the table; remove this one
+ // to prevent a double break
+ contentKnuthElements.removeFirst();
+ } else {
+ element.setPosition(new NonLeafPosition(this, null));
+ }
+ }
+ element = (ListElement)contentKnuthElements.getLast();
+ if (element.isForcedBreak()) {
+ // The last row of the table(-body), or (the content of) one of its cells
+ // has a forced break-after
+ element.setPosition(new NonLeafPosition(this, null));
+ }
+ }
+
//Set index values on elements coming from the content LM
Iterator iter = contentKnuthElements.iterator();
while (iter.hasNext()) {
@@ -256,73 +284,19 @@ public class TableLayoutManager extends BlockStackingLayoutManager
notifyPos(el.getPosition());
}
log.debug(contentKnuthElements);
-
- if (contentKnuthElements.size() == 1
- && ((ListElement)contentKnuthElements.getFirst()).isForcedBreak()) {
- // a descendant of this block has break-before
- if (returnList.size() == 0) {
- // the first child (or its first child ...) has
- // break-before;
- // all this block, including space before, will be put in
- // the
- // following page
- //FIX ME
- //bSpaceBeforeServed = false;
- }
- contentList.addAll(contentKnuthElements);
-
- // "wrap" the Position inside each element
- // moving the elements from contentList to returnList
- contentKnuthElements = new LinkedList();
- wrapPositionElements(contentList, returnList);
-
- return returnList;
- } else {
- /*
- if (prevLM != null) {
- // there is a block handled by prevLM
- // before the one handled by curLM
- if (mustKeepTogether()
- || prevLM.mustKeepWithNext()
- || curLM.mustKeepWithPrevious()) {
- // add an infinite penalty to forbid a break between
- // blocks
- contentList.add(new KnuthPenalty(0,
- KnuthElement.INFINITE, false,
- new Position(this), false));
- } else if (!((KnuthElement) contentList.getLast()).isGlue()) {
- // add a null penalty to allow a break between blocks
- contentList.add(new KnuthPenalty(0, 0, false,
- new Position(this), false));
- } else {
- // the last element in contentList is a glue;
- // it is a feasible breakpoint, there is no need to add
- // a penalty
- }
- }*/
- contentList.addAll(contentKnuthElements);
- if (contentKnuthElements.size() > 0) {
- if (((ListElement)contentKnuthElements.getLast()).isForcedBreak()) {
- // a descendant of this block has break-after
- if (false /*curLM.isFinished()*/) {
- // there is no other content in this block;
- // it's useless to add space after before a page break
- setFinished(true);
- }
-
- contentKnuthElements = new LinkedList();
- wrapPositionElements(contentList, returnList);
-
- return returnList;
- }
- }
- }
+ contentList.addAll(contentKnuthElements);
wrapPositionElements(contentList, returnList);
if (getTable().isSeparateBorderModel()) {
addKnuthElementsForBorderPaddingAfter(returnList, true);
}
addKnuthElementsForSpaceAfter(returnList, alignment);
addKnuthElementsForBreakAfter(returnList, context);
+ if (mustKeepWithNext()) {
+ context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING);
+ }
+ if (mustKeepWithPrevious()) {
+ context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING);
+ }
setFinished(true);
resetSpaces();
return returnList;
diff --git a/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java b/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java
index f8cedd7de..0f7604f20 100644
--- a/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java
+++ b/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java
@@ -212,13 +212,6 @@ public class TableRowIterator {
}
/**
- * Sets the iterator to the previous row.
- */
- public void backToPreviousRow() {
- iteratorIndex--;
- }
-
- /**
* Returns the first effective row.
* @return the requested effective row.
*/
diff --git a/src/java/org/apache/fop/layoutmgr/table/TableStepper.java b/src/java/org/apache/fop/layoutmgr/table/TableStepper.java
index 20b80c1bd..60ccf2b0a 100644
--- a/src/java/org/apache/fop/layoutmgr/table/TableStepper.java
+++ b/src/java/org/apache/fop/layoutmgr/table/TableStepper.java
@@ -85,7 +85,6 @@ public class TableStepper {
private boolean rowBacktrackForLastStep;
private boolean skippedStep;
private boolean[] keepWithNextSignals;
- private boolean forcedBreak;
private int lastMaxPenaltyLength;
/**
@@ -117,15 +116,7 @@ public class TableStepper {
keepWithNextSignals = new boolean[columnCount];
Arrays.fill(end, -1);
}
-
- private void clearBreakCondition() {
- forcedBreak = false;
- }
-
- private boolean isBreakCondition() {
- return forcedBreak;
- }
-
+
/**
* Returns the row currently being processed.
*
@@ -307,6 +298,8 @@ public class TableStepper {
int boxLen = step - addedBoxLen - penaltyLen;
addedBoxLen += boxLen;
+ boolean forcedBreak = false;
+ int breakClass = -1;
//Put all involved grid units into a list
List gridUnitParts = new java.util.ArrayList(maxColumnCount);
for (int i = 0; i < columnCount; i++) {
@@ -320,6 +313,10 @@ public class TableStepper {
0, pgu.getElements().size() - 1));
} else {
gridUnitParts.add(new GridUnitPart(pgu, start[i], end[i]));
+ if (((KnuthElement)elementLists[i].get(end[i])).isForcedBreak()) {
+ forcedBreak = true;
+ breakClass = ((KnuthPenalty)elementLists[i].get(end[i])).getBreakClass();
+ }
}
if (end[i] + 1 == elementLists[i].size()) {
if (pgu.getFlag(GridUnit.KEEP_WITH_NEXT_PENDING)) {
@@ -408,15 +405,14 @@ public class TableStepper {
//Need to avoid breaking because borders and/or paddding from other columns would
//not fit in the available space (see getNextStep())
}
- if (isBreakCondition()) {
+ if (forcedBreak) {
if (skippedStep) {
log.error("This is a conflict situation. The output may be wrong."
+ " Please send your FO file to fop-dev@xmlgraphics.apache.org!");
}
p = -KnuthPenalty.INFINITE; //Overrides any keeps (see 4.8 in XSL 1.0)
- clearBreakCondition();
}
- returnList.add(new BreakElement(penaltyPos, effPenaltyLen, p, -1, context));
+ returnList.add(new BreakElement(penaltyPos, effPenaltyLen, p, breakClass, context));
if (log.isDebugEnabled()) {
log.debug("step=" + step + " (+" + increase + ")"
@@ -436,9 +432,6 @@ public class TableStepper {
//we have to signal the still pending last keep-with-next using the LayoutContext.
context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING);
}
- if (isBreakCondition()) {
- ((BreakElement)returnList.getLast()).setPenaltyValue(-KnuthPenalty.INFINITE);
- }
if (lastTCPos != null) {
lastTCPos.setFlag(TableContentPosition.LAST_IN_ROWGROUP, true);
}
@@ -478,7 +471,6 @@ public class TableStepper {
this.lastMaxPenaltyLength = Math.max(this.lastMaxPenaltyLength, el.getW());
if (el.getP() <= -KnuthElement.INFINITE) {
log.debug("FORCED break encountered!");
- forcedBreak = true;
break;
} else if (el.getP() < KnuthElement.INFINITE) {
//First legal break point
diff --git a/src/java/org/apache/fop/pdf/PDFArray.java b/src/java/org/apache/fop/pdf/PDFArray.java
index 2dcbd5224..abeed1d86 100644
--- a/src/java/org/apache/fop/pdf/PDFArray.java
+++ b/src/java/org/apache/fop/pdf/PDFArray.java
@@ -19,40 +19,120 @@
package org.apache.fop.pdf;
+import java.util.Collection;
+import java.util.List;
+
/**
- * class representing an array object
+ * Class representing an array object.
*/
public class PDFArray extends PDFObject {
/**
- * Array of calues for this pdf object.
+ * List holding the values of this array
*/
- protected int[] values;
+ protected List values = new java.util.ArrayList();
+
+ /**
+ * Create a new, empty array object
+ */
+ public PDFArray() {
+ /* generic creation of PDF object */
+ super();
+ }
/**
- * create the array object
+ * Create the array object
*
* @param values the actual array wrapped by this object
*/
public PDFArray(int[] values) {
-
/* generic creation of PDF object */
super();
- /* set fields using paramaters */
- this.values = values;
+ for (int i = 0, c = values.length; i < c; i++) {
+ this.values.add(new Integer(values[i]));
+ }
}
/**
+ * Create the array object
+ *
+ * @param values the actual values wrapped by this object
+ */
+ public PDFArray(Collection values) {
+ /* generic creation of PDF object */
+ super();
+
+ this.values.addAll(values);
+ }
+
+ /**
+ * Create the array object
+ *
+ * @param values the actual array wrapped by this object
+ */
+ public PDFArray(Object[] values) {
+ /* generic creation of PDF object */
+ super();
+
+ for (int i = 0, c = values.length; i < c; i++) {
+ this.values.add(values[i]);
+ }
+ }
+
+ /**
+ * Returns the length of the array
+ * @return the length of the array
+ */
+ public int length() {
+ return this.values.size();
+ }
+
+ /**
+ * Sets an entry at a given location.
+ * @param index the index of the value to set
+ * @param obj the new value
+ */
+ public void set(int index, Object obj) {
+ this.values.set(index, obj);
+ }
+
+ /**
+ * Gets an entry at a given location.
+ * @param index the index of the value to set
+ * @return the requested value
+ */
+ public Object get(int index) {
+ return this.values.get(index);
+ }
+
+ /**
+ * Adds a new value to the array.
+ * @param obj the value
+ */
+ public void add(Object obj) {
+ this.values.add(obj);
+ }
+
+ /**
* @see org.apache.fop.pdf.PDFObject#toPDFString()
*/
public String toPDFString() {
StringBuffer p = new StringBuffer(64);
- p.append(getObjectID() + "[");
- for (int i = 0; i < values.length; i++) {
- p.append(" ");
- p.append(values[i]);
+ if (hasObjectNumber()) {
+ p.append(getObjectID());
+ }
+ p.append("[");
+ for (int i = 0; i < values.size(); i++) {
+ if (i > 0) {
+ p.append(" ");
+ }
+ Object obj = this.values.get(i);
+ formatObject(obj, p);
+ }
+ p.append("]");
+ if (hasObjectNumber()) {
+ p.append("\nendobj\n");
}
- p.append("]\nendobj\n");
return p.toString();
}
diff --git a/src/java/org/apache/fop/pdf/PDFDestination.java b/src/java/org/apache/fop/pdf/PDFDestination.java
index 97923e935..2b7ec0a1f 100644
--- a/src/java/org/apache/fop/pdf/PDFDestination.java
+++ b/src/java/org/apache/fop/pdf/PDFDestination.java
@@ -19,88 +19,77 @@
package org.apache.fop.pdf;
-import org.apache.fop.area.DestinationData;
-import org.apache.fop.area.PageViewport;
-
/**
* class representing a named destination
*/
public class PDFDestination extends PDFObject {
/**
- * PDFReference (object reference) for this destination
- */
- private String goToReference;
-
- /**
* ID Reference for this destination
*/
private String idRef;
/**
- * PageViewport to which the idRef item refers
+ * PDFReference (object reference) for this destination
*/
- private PageViewport pageViewport = null;
+ private Object goToReference;
/**
- * create a named destination
+ * Create a named destination
+ * @param idRef ID Reference for this destination (the name of the destination)
+ * @param goToRef Object reference to the GoTo Action
*/
- public PDFDestination(DestinationData destinationData) {
- /* generic creation of PDF object */
- super();
- this.goToReference = destinationData.getGoToReference();
- this.idRef = destinationData.getIDRef();
- this.pageViewport = destinationData.getPageViewport();
+ public PDFDestination(String idRef, Object goToRef) {
+ this.goToReference = goToRef;
+ this.idRef = idRef;
}
/**
- * @see org.apache.fop.pdf.PDFObject#toPDFString()
+ * Creates the key/value pair for this destination entry for the name tree.
+ * @return the formatted key/value pair
*/
- public String toPDFString() {
- String s = getObjectID()
- + "<<"
- + "/Limits [(" + idRef + ") (" + idRef + ")]\n"
- + "/Names [(" + idRef + ") " + goToReference + "]"
- + "\n>>\nendobj\n";
- return s;
+ public String toKeyValuePair() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("(").append(getIDRef()).append(") ");
+ if (goToReference instanceof PDFWritable) {
+ sb.append(((PDFWritable)goToReference).toInlinePDFString());
+ } else {
+ sb.append(goToReference);
+ }
+ return sb.toString();
+ }
+
+ /** @see org.apache.fop.pdf.PDFObject#toPDFString() */
+ protected String toPDFString() {
+ return toKeyValuePair();
}
-
- /*
- * example:
- *
- * 249 0 obj
- * <<
- * /Limits [(drivervariables) (drivervariables)]
- * /Names [(drivervariables) 73 0 R]
- * >>
- * endobj
- */
/**
* Sets the GoToReference in the associated DestinationData object.
*
* @param goToReference the reference to set in the associated DestinationData object.
+ * @deprecated use setGoToReference(Object) instead
*/
public void setGoToReference(String goToReference) {
this.goToReference = goToReference;
}
/**
- * Returns the GoToReference from the associated DestinationData object.
+ * Sets the GoToReference in the associated DestinationData object.
*
- * @return the GoToReference from the associated DestinationData object.
+ * @param goToReference the reference to set in the associated DestinationData object.
*/
- public String getGoToReference() {
- return this.goToReference;
+ public void setGoToReference(Object goToReference) {
+ this.goToReference = goToReference;
}
/**
- * Get the PageViewport object that this destination refers to
+ * Returns the GoToReference from the associated DestinationData object.
*
- * @return the PageViewport that this destination points to
+ * @return the GoToReference from the associated DestinationData object.
*/
- public PageViewport getPageViewport() {
- return this.pageViewport;
+ public Object getGoToReference() {
+ return this.goToReference;
}
/**
@@ -128,11 +117,19 @@ public class PDFDestination extends PDFObject {
}
PDFDestination dest = (PDFDestination)obj;
- if (dest.getIDRef() == this.getIDRef()) {
+ if (dest.getIDRef().equals(this.getIDRef())) {
return true;
}
- return true;
+ return false;
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return getIDRef().hashCode();
}
+
}
diff --git a/src/java/org/apache/fop/pdf/PDFDests.java b/src/java/org/apache/fop/pdf/PDFDests.java
index 96172c8e2..463ef8d7e 100644
--- a/src/java/org/apache/fop/pdf/PDFDests.java
+++ b/src/java/org/apache/fop/pdf/PDFDests.java
@@ -19,61 +19,29 @@
package org.apache.fop.pdf;
-import org.apache.fop.area.DestinationData;
+import java.util.List;
/**
- * class representing an /Dests object (part of a name dictionary)
+ * class representing an /Dests dictionary object
*/
-public class PDFDests extends PDFObject {
-
- private String limitsRef;
+public class PDFDests extends PDFNameTreeNode {
/**
- * create a named destination
+ * Create a named destination
*/
- public PDFDests(String limitsRef) {
+ public PDFDests() {
/* generic creation of PDF object */
super();
- this.limitsRef = limitsRef;
}
/**
- * @see org.apache.fop.pdf.PDFObject#toPDFString()
+ * Create a named destination
+ * @param destinationList a list of destinations
*/
- public String toPDFString() {
- String s = getObjectID()
- + "<<\n"
- + "/Dests " + limitsRef
- + "\n>>\nendobj\n";
- return s;
+ public PDFDests(List destinationList) {
+ this();
+ setNames(new PDFArray(destinationList));
}
- /*
- * example:
- *
- * 262 0 obj
- * <<
- * /Dests 260 0 R
- * >>
- * endobj
- */
-
- /**
- * Check if this equals another object.
- *
- * @param obj the object to compare
- * @return true if this equals other object
- */
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
-
- if (obj == null || !(obj instanceof PDFDests)) {
- return false;
- }
-
- return true;
- }
}
diff --git a/src/java/org/apache/fop/pdf/PDFDictionary.java b/src/java/org/apache/fop/pdf/PDFDictionary.java
new file mode 100644
index 000000000..2a9c3ffe4
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/PDFDictionary.java
@@ -0,0 +1,96 @@
+/*
+ * 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 java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class representing a PDF dictionary object
+ */
+public class PDFDictionary extends PDFObject {
+
+ /**
+ * the entry map
+ */
+ protected Map entries = new java.util.HashMap();
+
+ /**
+ * maintains the order of the entries added to the entry map. Whenever you modify
+ * "entries", always make sure you adjust this list accordingly.
+ */
+ protected List order = new java.util.ArrayList();
+
+ /**
+ * Create the dictionary object
+ */
+ public PDFDictionary() {
+ /* generic creation of PDF object */
+ super();
+ }
+
+ /**
+ * Puts a new name/value pair.
+ * @param name the name
+ * @param value the value
+ */
+ public void put(String name, Object value) {
+ if (!entries.containsKey(name)) {
+ this.order.add(name);
+ }
+ this.entries.put(name, value);
+ }
+
+ /**
+ * Returns the value given a name.
+ * @param name the name of the value
+ * @return the value or null, if there's no value with the given name.
+ */
+ public Object get(String name) {
+ return this.entries.get(name);
+ }
+
+ /**
+ * @see org.apache.fop.pdf.PDFObject#toPDFString()
+ */
+ public String toPDFString() {
+ StringBuffer p = new StringBuffer(64);
+ if (hasObjectNumber()) {
+ p.append(getObjectID());
+ }
+ p.append("<<");
+ Iterator iter = this.order.iterator();
+ while (iter.hasNext()) {
+ String key = (String)iter.next();
+ p.append("\n /");
+ p.append(key);
+ p.append(" ");
+ Object obj = this.entries.get(key);
+ formatObject(obj, p);
+ }
+ p.append("\n>>\n");
+ if (hasObjectNumber()) {
+ p.append("endobj\n");
+ }
+ return p.toString();
+ }
+
+}
diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java
index 29cb88731..748c6b3a8 100644
--- a/src/java/org/apache/fop/pdf/PDFDocument.java
+++ b/src/java/org/apache/fop/pdf/PDFDocument.java
@@ -5,9 +5,9 @@
* 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.
@@ -19,7 +19,7 @@
package org.apache.fop.pdf;
-/// Java
+// Java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -28,18 +28,15 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Iterator;
-import java.util.ArrayList;
-import java.util.Collections;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.fop.pdf.DestinationComparator;
-
/* image support modified from work of BoBoGi */
/* font support based on work by Takayuki Takeuchi */
@@ -67,13 +64,13 @@ import org.apache.fop.pdf.DestinationComparator;
public class PDFDocument {
private static final Integer LOCATION_PLACEHOLDER = new Integer(0);
-
+
/** Integer constant to represent PDF 1.3 */
public static final int PDF_VERSION_1_3 = 3;
/** Integer constant to represent PDF 1.4 */
public static final int PDF_VERSION_1_4 = 4;
-
+
/**
* the encoding to use when converting strings to PDF commandos.
*/
@@ -89,7 +86,7 @@ public class PDFDocument {
/**
* the character position of each object
*/
- protected List location = new ArrayList();
+ protected List location = new java.util.ArrayList();
/** List of objects to write in the trailer */
private List trailerObjects = new java.util.ArrayList();
@@ -111,12 +108,12 @@ public class PDFDocument {
/** Indicates what PDF version is active */
protected int pdfVersion = PDF_VERSION_1_4;
-
+
/**
* Indicates which PDF profiles are active (PDF/A, PDF/X etc.)
*/
protected PDFProfile pdfProfile = new PDFProfile(this);
-
+
/**
* the /Root object
*/
@@ -208,7 +205,7 @@ public class PDFDocument {
/**
* List of Destinations.
*/
- protected List destinations = new java.util.ArrayList();
+ protected List destinations;
/**
* List of FileSpecs.
@@ -231,19 +228,6 @@ public class PDFDocument {
*/
private PDFDests dests;
- /**
- * The PDFLimits object for the name dictionary.
- * Note: This object is not a list.
- */
- private PDFLimits limits;
-
- /**
- * Whether this PDFDocument has named destinations
- * (and thus needs PDFDestinations, PDFLimits, and
- * PDFDests)
- */
- private boolean hasDestinations = false;
-
private PDFFactory factory;
private boolean encodingOnTheFly = true;
@@ -283,7 +267,7 @@ public class PDFDocument {
public int getPDFVersion() {
return this.pdfVersion;
}
-
+
/** @return the String representing the active PDF version */
public String getPDFVersionString() {
switch (getPDFVersion()) {
@@ -300,7 +284,7 @@ public class PDFDocument {
public PDFProfile getProfile() {
return this.pdfProfile;
}
-
+
/**
* Returns the factory for PDF objects.
* @return PDFFactory the factory
@@ -343,7 +327,7 @@ public class PDFDocument {
/**
* Set the creation date of the document.
- *
+ *
* @param date Date to be stored as creation date in the PDF.
*/
public void setCreationDate(Date date) {
@@ -488,9 +472,6 @@ public class PDFDocument {
if (obj instanceof PDFLink) {
this.links.add(obj);
}
- if (obj instanceof PDFDestination) {
- this.destinations.add(obj);
- }
if (obj instanceof PDFFileSpec) {
this.filespecs.add(obj);
}
@@ -614,7 +595,12 @@ public class PDFDocument {
* @return the link if found, null otherwise
*/
protected PDFDestination findDestination(PDFDestination compare) {
- return (PDFDestination)findPDFObject(destinations, compare);
+ int index = getDestinationList().indexOf(compare);
+ if (index >= 0) {
+ return (PDFDestination)getDestinationList().get(index);
+ } else {
+ return null;
+ }
}
/**
@@ -751,21 +737,27 @@ public class PDFDocument {
}
/**
- * Gets the list of named destinations.
- *
- * @return the list of named destinations.
+ * Adds a destination to the document.
+ * @param destination the destination object
*/
- public ArrayList getDestinationList() {
- return (ArrayList)destinations;
+ public void addDestination(PDFDestination destination) {
+ if (this.destinations == null) {
+ this.destinations = new java.util.ArrayList();
+ }
+ this.destinations.add(destination);
}
-
+
/**
- * Sets whether the document has named destinations.
+ * Gets the list of named destinations.
*
- * @param hasDestinations whether the document has named destinations.
+ * @return the list of named destinations.
*/
- public void setHasDestinations(boolean hasDestinations) {
- this.hasDestinations = hasDestinations;
+ public List getDestinationList() {
+ if (hasDestinations()) {
+ return destinations;
+ } else {
+ return Collections.EMPTY_LIST;
+ }
}
/**
@@ -773,17 +765,8 @@ public class PDFDocument {
*
* @return whether the document has named destinations.
*/
- public boolean getHasDestinations() {
- return this.hasDestinations;
- }
-
- /**
- * Gets the PDFLimits object (part of the name dictionary).
- *
- * @return the PDFLimits object (part of the name dictionary).
- */
- public PDFLimits getLimits() {
- return limits;
+ public boolean hasDestinations() {
+ return this.destinations != null && !this.destinations.isEmpty();
}
/**
@@ -933,8 +916,8 @@ public class PDFDocument {
this.position = 0;
getProfile().verifyPDFVersion();
-
- byte[] pdf = ("%PDF-" + getPDFVersionString() + "\n").getBytes();
+
+ byte[] pdf = encode("%PDF-" + getPDFVersionString() + "\n");
stream.write(pdf);
this.position += pdf.length;
@@ -955,9 +938,9 @@ public class PDFDocument {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
DateFormat df = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS");
- digest.update(df.format(new Date()).getBytes());
+ digest.update(encode(df.format(new Date())));
//Ignoring the filename here for simplicity even though it's recommended by the PDF spec
- digest.update(String.valueOf(this.position).getBytes());
+ digest.update(encode(String.valueOf(this.position)));
digest.update(getInfo().toPDF());
byte[] res = digest.digest();
String s = PDFText.toHex(res);
@@ -970,7 +953,7 @@ public class PDFDocument {
}
}
}
-
+
/**
* write the trailer
*
@@ -978,11 +961,13 @@ public class PDFDocument {
* @throws IOException if there is an exception writing to the output stream
*/
public void outputTrailer(OutputStream stream) throws IOException {
- if (hasDestinations) {
- Collections.sort((ArrayList)destinations, new DestinationComparator());
- limits = getFactory().makeLimits((ArrayList)destinations);
- dests = getFactory().makeDests(limits.referencePDF());
- this.root.setNames(dests.referencePDF());
+ if (hasDestinations()) {
+ Collections.sort(destinations, new DestinationComparator());
+ dests = getFactory().makeDests(destinations);
+ if (this.root.getNames() == null) {
+ this.root.setNames(getFactory().makeNames());
+ }
+ this.root.getNames().setDests(dests);
}
output(stream);
for (int count = 0; count < trailerObjects.size(); count++) {
@@ -1061,4 +1046,4 @@ public class PDFDocument {
return pdfBytes.length;
}
-} \ No newline at end of file
+}
diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java
index 3005f2018..8011ffc92 100644
--- a/src/java/org/apache/fop/pdf/PDFFactory.java
+++ b/src/java/org/apache/fop/pdf/PDFFactory.java
@@ -20,6 +20,7 @@
package org.apache.fop.pdf;
// Java
+import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -53,8 +54,6 @@ import org.apache.fop.fonts.truetype.TTFSubSetFile;
import org.apache.fop.fonts.type1.PFBData;
import org.apache.fop.fonts.type1.PFBParser;
import org.apache.xmlgraphics.xmp.Metadata;
-import org.apache.fop.area.PageViewport;
-import org.apache.fop.area.DestinationData;
/**
* This class provides method to create and register PDF objects.
@@ -144,7 +143,7 @@ public class PDFFactory {
/**
* Make a Metadata object.
- * @param doc the DOM Document containing the XMP metadata.
+ * @param meta the DOM Document containing the XMP metadata.
* @param readOnly true if the metadata packet should be marked read-only
* @return the newly created Metadata object
*/
@@ -746,13 +745,11 @@ public class PDFFactory {
for (currentPosition = 0; currentPosition < lastPosition;
currentPosition++) { // for every consecutive color pair
- PDFColor currentColor =
- (PDFColor)theColors.get(currentPosition);
+ PDFColor currentColor = (PDFColor)theColors.get(currentPosition);
PDFColor nextColor = (PDFColor)theColors.get(currentPosition
+ 1);
// colorspace must be consistant
- if (getDocument().getColorSpace()
- != currentColor.getColorSpace()) {
+ if (getDocument().getColorSpace() != currentColor.getColorSpace()) {
currentColor.setColorSpace(
getDocument().getColorSpace());
}
@@ -815,55 +812,116 @@ public class PDFFactory {
/* ============= named destinations and the name dictionary ============ */
/**
- * Make a named destination.
+ * Registers and returns newdest if it is unique. Otherwise, returns
+ * the equal destination already present in the document.
*
- * @param destinationData the DestinationData object that holds the info about this named destination
- * @return the new PDF named destination object
+ * @param newdest a new, as yet unregistered destination
+ * @return newdest if unique, else the already registered instance
*/
- public PDFDestination makeDestination(DestinationData destinationData) {
- PageViewport pv = destinationData.getPageViewport();
- if (pv == null) {
- log.warn("Unresolved destination item received: " + destinationData.getIDRef());
- }
- PDFDestination destination = new PDFDestination(destinationData);
-
- PDFDestination oldDestination = getDocument().findDestination(destination);
- if (destination == oldDestination) {
- destination = oldDestination;
+ protected PDFDestination getUniqueDestination(PDFDestination newdest) {
+ PDFDestination existing = getDocument().findDestination(newdest);
+ if (existing != null) {
+ return existing;
} else {
- getDocument().registerObject(destination);
- getDocument().setHasDestinations(true);
+ getDocument().addDestination(newdest);
+ return newdest;
}
+ }
- return destination;
+ /**
+ * Make a named destination.
+ *
+ * @param idRef ID Reference for this destination (the name of the destination)
+ * @param goToRef Object reference to the GoTo Action
+ * @return the newly created destrination
+ */
+ public PDFDestination makeDestination(String idRef, Object goToRef) {
+ PDFDestination destination = new PDFDestination(idRef, goToRef);
+ return getUniqueDestination(destination);
+ }
+
+ /**
+ * Make a names dictionary (the /Names object).
+ * @return the new PDFNames object
+ */
+ public PDFNames makeNames() {
+ PDFNames names = new PDFNames();
+ getDocument().registerObject(names);
+ return names;
}
/**
* Make a the head object of the name dictionary (the /Dests object).
*
+ * @param destinationList a list of PDFDestination instances
* @return the new PDFDests object
*/
- public PDFDests makeDests(String limitsRef) {
- PDFDests dests = new PDFDests(limitsRef);
+ public PDFDests makeDests(List destinationList) {
+ PDFDests dests;
+
+ final boolean deep = true;
+ //true for a "deep" structure (one node per entry), true for a "flat" structure
+ if (deep) {
+ dests = new PDFDests();
+ PDFArray kids = new PDFArray();
+ Iterator iter = destinationList.iterator();
+ while (iter.hasNext()) {
+ PDFDestination dest = (PDFDestination)iter.next();
+ PDFNameTreeNode node = new PDFNameTreeNode();
+ getDocument().registerObject(node);
+ node.setLowerLimit(dest.getIDRef());
+ node.setUpperLimit(dest.getIDRef());
+ node.setNames(new PDFArray());
+ node.getNames().add(dest);
+ kids.add(node);
+ }
+ dests.setLowerLimit(((PDFNameTreeNode)kids.get(0)).getLowerLimit());
+ dests.setUpperLimit(((PDFNameTreeNode)kids.get(kids.length() - 1)).getUpperLimit());
+ dests.setKids(kids);
+ } else {
+ dests = new PDFDests(destinationList);
+ }
getDocument().registerObject(dests);
-
return dests;
}
/**
- * Make a the limits object of the name dictionary (the /Limits object).
+ * Make a name tree node.
*
- * @return the new PDFLimits object
+ * @return the new name tree node
*/
- public PDFLimits makeLimits(ArrayList destinationList) {
- PDFLimits limits = new PDFLimits(destinationList);
- getDocument().registerObject(limits);
+ public PDFNameTreeNode makeNameTreeNode() {
+ PDFNameTreeNode node = new PDFNameTreeNode();
+ getDocument().registerObject(node);
+ return node;
+ }
+
+ /* ========================= links ===================================== */
+ // Some of the "yoffset-only" functions in this part are obsolete and can
+ // possibly be removed or deprecated. Some are still called by PDFGraphics2D
+ // (although that could be changed, they don't need the yOffset param anyway).
- return limits;
+ /**
+ * Create a PDF link to an existing PDFAction object
+ *
+ * @param rect the hotspot position in absolute coordinates
+ * @param pdfAction the PDFAction that this link refers to
+ * @return the new PDFLink object, or null if either rect or pdfAction is null
+ */
+ public PDFLink makeLink(Rectangle2D rect, PDFAction pdfAction) {
+ if (rect == null || pdfAction == null) {
+ return null;
+ } else {
+ PDFLink link = new PDFLink(rect);
+ link.setAction(pdfAction);
+ getDocument().registerObject(link);
+ return link;
+ // does findLink make sense? I mean, how often will it happen that several
+ // links have the same target *and* the same hot rect? And findLink has to
+ // walk and compare the entire link list everytime you call it...
+ }
}
- /* ========================= links ===================================== */
-
/**
* Make an internal link.
*
@@ -898,32 +956,10 @@ public class PDFFactory {
int linkType, float yoffset) {
//PDFLink linkObject;
- int index;
-
PDFLink link = new PDFLink(rect);
if (linkType == PDFLink.EXTERNAL) {
- // check destination
- if (destination.startsWith("http://")) {
- PDFUri uri = new PDFUri(destination);
- link.setAction(uri);
- } else if (destination.endsWith(".pdf")) { // FileSpec
- PDFGoToRemote remote = getGoToPDFAction(destination, null, -1);
- link.setAction(remote);
- } else if ((index = destination.indexOf(".pdf#page=")) > 0) {
- //String file = destination.substring(0, index + 4);
- int page = Integer.parseInt(destination.substring(index + 10));
- PDFGoToRemote remote = getGoToPDFAction(destination, null, page);
- link.setAction(remote);
- } else if ((index = destination.indexOf(".pdf#dest=")) > 0) {
- //String file = destination.substring(0, index + 4);
- String dest = destination.substring(index + 10);
- PDFGoToRemote remote = getGoToPDFAction(destination, dest, -1);
- link.setAction(remote);
- } else { // URI
- PDFUri uri = new PDFUri(destination);
- link.setAction(uri);
- }
+ link.setAction(getExternalAction(destination));
} else {
// linkType is internal
String goToReference = getGoToReference(destination, yoffset);
@@ -941,11 +977,62 @@ public class PDFFactory {
return link;
}
- public String getGoToReference(String destination, float yoffset) {
+ /**
+ * Create/find and return the appropriate external PDFAction according to the target
+ *
+ * @param target The external target. This may be a PDF file name
+ * (optionally with internal page number or destination) or any type of URI.
+ * @return the PDFAction thus created or found
+ */
+ public PDFAction getExternalAction(String target) {
+ int index;
+ String targetLo = target.toLowerCase();
+ // HTTP URL?
+ if (targetLo.startsWith("http://")) {
+ return new PDFUri(target);
+ // Bare PDF file name?
+ } else if (targetLo.endsWith(".pdf")) {
+ return getGoToPDFAction(target, null, -1);
+ // PDF file + page?
+ } else if ((index = targetLo.indexOf(".pdf#page=")) > 0) {
+ String filename = target.substring(0, index + 4);
+ int page = Integer.parseInt(target.substring(index + 10));
+ return getGoToPDFAction(filename, null, page);
+ // PDF file + destination?
+ } else if ((index = targetLo.indexOf(".pdf#dest=")) > 0) {
+ String filename = target.substring(0, index + 4);
+ String dest = target.substring(index + 10);
+ return getGoToPDFAction(filename, dest, -1);
+ // None of the above? Default to URI:
+ } else {
+ return new PDFUri(target);
+ }
+ }
+
+ /**
+ * Create or find a PDF GoTo with the given page reference string and Y offset,
+ * and return its PDF object reference
+ *
+ * @param pdfPageRef the PDF page reference, e.g. "23 0 R"
+ * @param yoffset the distance from the bottom of the page in points
+ * @return the GoTo's object reference
+ */
+ public String getGoToReference(String pdfPageRef, float yoffset) {
+ return getPDFGoTo(pdfPageRef, new Point2D.Float(0.0f, yoffset)).referencePDF();
+ }
+
+ /**
+ * Finds and returns a PDFGoTo to the given page and position.
+ * Creates the PDFGoTo if not found.
+ *
+ * @param pdfPageRef the PDF page reference
+ * @param position the (X,Y) position in points
+ *
+ * @return the new or existing PDFGoTo object
+ */
+ public PDFGoTo getPDFGoTo(String pdfPageRef, Point2D position) {
getDocument().getProfile().verifyActionAllowed();
- String goToReference = null;
- PDFGoTo gt = new PDFGoTo(destination);
- gt.setYPosition(yoffset);
+ PDFGoTo gt = new PDFGoTo(pdfPageRef, position);
PDFGoTo oldgt = getDocument().findGoTo(gt);
if (oldgt == null) {
getDocument().assignObjectNumber(gt);
@@ -953,9 +1040,7 @@ public class PDFFactory {
} else {
gt = oldgt;
}
-
- goToReference = gt.referencePDF();
- return goToReference;
+ return gt;
}
/**
@@ -997,6 +1082,42 @@ public class PDFFactory {
}
/**
+ * Make an outline object and add it to the given parent
+ *
+ * @param parent the parent PDFOutline object (may be null)
+ * @param label the title for the new outline object
+ * @param actionRef the action reference string to be placed after the /A
+ * @param showSubItems whether to initially display child outline items
+ * @return the new PDF outline object
+ */
+ public PDFOutline makeOutline(PDFOutline parent, String label,
+ String actionRef, boolean showSubItems) {
+ PDFOutline pdfOutline = new PDFOutline(label, actionRef, showSubItems);
+ if (parent != null) {
+ parent.addOutline(pdfOutline);
+ }
+ getDocument().registerObject(pdfOutline);
+ return pdfOutline;
+ }
+
+ /**
+ * Make an outline object and add it to the given parent
+ *
+ * @param parent the parent PDFOutline object (may be null)
+ * @param label the title for the new outline object
+ * @param pdfAction the action that this outline item points to - must not be null!
+ * @param showSubItems whether to initially display child outline items
+ * @return the new PDFOutline object, or null if pdfAction is null
+ */
+ public PDFOutline makeOutline(PDFOutline parent, String label,
+ PDFAction pdfAction, boolean showSubItems) {
+ return pdfAction == null
+ ? null
+ : makeOutline(parent, label, pdfAction.getAction(), showSubItems);
+ }
+
+ // This one is obsolete now, at least it isn't called from anywhere inside FOP
+ /**
* Make an outline object and add it to the given outline
*
* @param parent parent PDFOutline object which may be null
@@ -1011,17 +1132,10 @@ public class PDFFactory {
boolean showSubItems) {
String goToRef = getGoToReference(destination, yoffset);
- PDFOutline obj = new PDFOutline(label, goToRef, showSubItems);
-
- if (parent != null) {
- parent.addOutline(obj);
- }
- getDocument().registerObject(obj);
- return obj;
+ return makeOutline(parent, label, goToRef, showSubItems);
}
-
/* ========================= fonts ===================================== */
/**
@@ -1077,15 +1191,12 @@ public class PDFFactory {
* cmap.addContents();
* this.objects.add(cmap);
*/
- font =
- (PDFFontNonBase14)PDFFont.createFont(fontname, fonttype,
- basefont,
- "Identity-H");
+ font = (PDFFontNonBase14)PDFFont.createFont(fontname, fonttype,
+ basefont, "Identity-H");
} else {
- font =
- (PDFFontNonBase14)PDFFont.createFont(fontname, fonttype,
- basefont, encoding);
+ font = (PDFFontNonBase14)PDFFont.createFont(fontname, fonttype,
+ basefont, encoding);
}
getDocument().registerObject(font);
@@ -1098,12 +1209,12 @@ public class PDFFactory {
} else {
cidMetrics = (CIDFont)metrics;
}
- PDFCIDSystemInfo sysInfo =
- new PDFCIDSystemInfo(cidMetrics.getRegistry(),
+ PDFCIDSystemInfo sysInfo
+ = new PDFCIDSystemInfo(cidMetrics.getRegistry(),
cidMetrics.getOrdering(),
cidMetrics.getSupplement());
- PDFCIDFont cidFont =
- new PDFCIDFont(basefont,
+ PDFCIDFont cidFont
+ = new PDFCIDFont(basefont,
cidMetrics.getCIDType(),
cidMetrics.getDefaultWidth(),
getSubsetWidths(cidMetrics), sysInfo,
diff --git a/src/java/org/apache/fop/pdf/PDFGoTo.java b/src/java/org/apache/fop/pdf/PDFGoTo.java
index a493a9f32..16eaf73ce 100644
--- a/src/java/org/apache/fop/pdf/PDFGoTo.java
+++ b/src/java/org/apache/fop/pdf/PDFGoTo.java
@@ -19,6 +19,8 @@
package org.apache.fop.pdf;
+import java.awt.geom.Point2D;
+
/**
* class representing a /GoTo object.
* This can either have a Goto to a page reference and location
@@ -47,6 +49,20 @@ public class PDFGoTo extends PDFAction {
}
/**
+ * create a /GoTo object.
+ *
+ * @param pageReference the PDF reference to the target page
+ * @param position the target area's on-page coordinates in points
+ */
+ public PDFGoTo(String pageReference, Point2D position) {
+ /* generic creation of object */
+ super();
+
+ this.pageReference = pageReference;
+ setPosition(position);
+ }
+
+ /**
* Sets page reference after object has been created
*
* @param pageReference the new page reference to use
@@ -56,6 +72,25 @@ public class PDFGoTo extends PDFAction {
}
/**
+ * Sets the target (X,Y) position
+ *
+ * @param position the target's on-page coordinates in points
+ */
+ public void setPosition(Point2D position) {
+ this.xPosition = (float) position.getX();
+ this.yPosition = (float) position.getY();
+ }
+
+ /**
+ * Sets the x Position to jump to
+ *
+ * @param xPosition x position
+ */
+ public void setXPosition(float xPosition) {
+ this.xPosition = xPosition;
+ }
+
+ /**
* Sets the Y position to jump to
*
* @param yPosition y position
@@ -74,15 +109,6 @@ public class PDFGoTo extends PDFAction {
}
/**
- * Sets the x Position to jump to
- *
- * @param xPosition x position
- */
- public void setXPosition(int xPosition) {
- this.xPosition = (xPosition / 1000f);
- }
-
- /**
* Get the PDF reference for the GoTo action.
*
* @return the PDF reference for the action
diff --git a/src/java/org/apache/fop/pdf/PDFLimits.java b/src/java/org/apache/fop/pdf/PDFLimits.java
deleted file mode 100644
index 97c097463..000000000
--- a/src/java/org/apache/fop/pdf/PDFLimits.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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 java.util.ArrayList;
-
-import org.apache.fop.pdf.PDFDestination;
-
-/**
- * class representing a Limits object (part of the names dictionary for named destinations)
- */
-public class PDFLimits extends PDFObject {
-
- private ArrayList destinationList;
-
- /**
- * create a named destination
- */
- public PDFLimits(ArrayList destinationList) {
- /* generic creation of PDF object */
- super();
- this.destinationList = destinationList;
- }
-
- /**
- * @see org.apache.fop.pdf.PDFObject#toPDFString()
- */
- public String toPDFString() {
- String[] idRefs = new String[destinationList.size()];
- String kidsString = "";
- for (int i = 0; i < destinationList.size(); i++) {
- PDFDestination dest = (PDFDestination)destinationList.get(i);
- idRefs[i] = dest.getIDRef();
- kidsString += dest.referencePDF();
- if (!(i == destinationList.size() - 1)) {
- kidsString += " ";
- }
- }
- String s = getObjectID()
- + "<<\n"
- + "/Limits [(" + idRefs[0] + ") (" + idRefs[destinationList.size() - 1] + ")]\n"
- + "/Kids [" + kidsString + "]"
- + "\n>>\nendobj\n";
- return s;
- }
-
- /*
- * example:
- *
- * 260 0 obj
- * <<
- * /Limits [(Annotate) (thumbnails)]
- * /Kids [248 0 R 253 0 R 254 0 R 259 0 R]
- * >>
- * endobj
- */
-
- /**
- * Check if this equals another object.
- *
- * @param obj the object to compare
- * @return true if this equals other object
- */
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
-
- if (obj == null || !(obj instanceof PDFLimits)) {
- return false;
- }
-
- return true;
- }
-}
-
diff --git a/src/java/org/apache/fop/pdf/PDFNameTreeNode.java b/src/java/org/apache/fop/pdf/PDFNameTreeNode.java
new file mode 100644
index 000000000..476cfc7a2
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/PDFNameTreeNode.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 name tree node.
+ */
+public class PDFNameTreeNode extends PDFDictionary {
+
+ private static final String KIDS = "Kids";
+ private static final String NAMES = "Names";
+ private static final String LIMITS = "Limits";
+
+ /**
+ * create a named destination
+ */
+ public PDFNameTreeNode() {
+ /* 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 Names array.
+ * @param names the Names array
+ */
+ public void setNames(PDFArray names) {
+ put(NAMES, names);
+ }
+
+ /**
+ * Returns the Names array.
+ * @return the Names array
+ */
+ public PDFArray getNames() {
+ return (PDFArray)get(NAMES);
+ }
+
+ /**
+ * Sets the lower limit value of the Limits array.
+ * @param key the lower limit value
+ */
+ public void setLowerLimit(String key) {
+ PDFArray limits = prepareLimitsArray();
+ limits.set(0, key);
+ }
+
+ /**
+ * Returns the lower limit value of the Limits array.
+ * @return the lower limit value
+ */
+ public String getLowerLimit() {
+ PDFArray limits = prepareLimitsArray();
+ return (String)limits.get(0);
+ }
+
+ /**
+ * Sets the upper limit value of the Limits array.
+ * @param key the upper limit value
+ */
+ public void setUpperLimit(String key) {
+ PDFArray limits = prepareLimitsArray();
+ limits.set(1, key);
+ }
+
+ /**
+ * Returns the upper limit value of the Limits array.
+ * @return the upper limit value
+ */
+ public String getUpperLimit() {
+ PDFArray limits = prepareLimitsArray();
+ return (String)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/PDFNames.java b/src/java/org/apache/fop/pdf/PDFNames.java
new file mode 100644
index 000000000..d2d895771
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/PDFNames.java
@@ -0,0 +1,51 @@
+/*
+ * 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 Names object
+ */
+public class PDFNames extends PDFDictionary {
+
+ /**
+ * Create the Names object
+ */
+ public PDFNames() {
+ /* generic creation of PDF object */
+ super();
+ }
+
+ /**
+ * Returns the Dests object
+ * @return the Dests object, or null if it's not used
+ */
+ public PDFDests getDests() {
+ return (PDFDests)get("Dests");
+ }
+
+ /**
+ * Set the Dests object
+ * @param dests the Dests object
+ */
+ public void setDests(PDFDests dests) {
+ put("Dests", dests);
+ }
+
+}
diff --git a/src/java/org/apache/fop/pdf/PDFObject.java b/src/java/org/apache/fop/pdf/PDFObject.java
index b1e8e1245..544a30cc4 100644
--- a/src/java/org/apache/fop/pdf/PDFObject.java
+++ b/src/java/org/apache/fop/pdf/PDFObject.java
@@ -37,7 +37,7 @@ import org.apache.commons.logging.LogFactory;
* Object has a number and a generation (although the generation will always
* be 0 in new documents).
*/
-public abstract class PDFObject {
+public abstract class PDFObject implements PDFWritable {
/** logger for all PDFObjects (and descendants) */
protected static Log log = LogFactory.getLog(PDFObject.class.getName());
@@ -138,11 +138,23 @@ public abstract class PDFObject {
* @return the reference string
*/
public String referencePDF() {
+ if (!hasObjectNumber()) {
+ throw new IllegalArgumentException(
+ "Cannot reference this object. It doesn't have an object number");
+ }
String ref = getObjectNumber() + " " + getGeneration() + " R";
return ref;
}
/**
+ * Creates and returns a reference to this object.
+ * @return the object reference
+ */
+ public PDFReference makeReference() {
+ return new PDFReference(this);
+ }
+
+ /**
* Write the PDF represention of this object
*
* @param stream the stream to write the PDF to
@@ -178,6 +190,19 @@ public abstract class PDFObject {
+ "Use output(OutputStream) instead.");
}
+ /**
+ * Returns a representation of this object for in-object placement, i.e. if the object
+ * has an object number its reference is returned. Otherwise, its PDF representation is
+ * returned.
+ * @return the String representation
+ */
+ public String toInlinePDFString() {
+ if (hasObjectNumber()) {
+ return referencePDF();
+ } else {
+ return toPDFString();
+ }
+ }
/**
* Converts text to a byte array for writing to a PDF file.
@@ -220,6 +245,21 @@ public abstract class PDFObject {
}*/
}
+ /**
+ * Formats an object for serialization to PDF.
+ * @param obj the object
+ * @param sb the StringBuffer to write to
+ */
+ protected void formatObject(Object obj, StringBuffer sb) {
+ if (obj instanceof PDFWritable) {
+ sb.append(((PDFWritable)obj).toInlinePDFString());
+ } else if (obj instanceof Number) {
+ sb.append(obj);
+ } else {
+ sb.append("(").append(obj).append(")");
+ }
+ }
+
/** Formatting pattern for PDF date */
protected static final SimpleDateFormat DATE_FORMAT
= new SimpleDateFormat("'D:'yyyyMMddHHmmss");
diff --git a/src/java/org/apache/fop/pdf/PDFRectangle.java b/src/java/org/apache/fop/pdf/PDFRectangle.java
index def6d9a2b..e84227adf 100644
--- a/src/java/org/apache/fop/pdf/PDFRectangle.java
+++ b/src/java/org/apache/fop/pdf/PDFRectangle.java
@@ -79,7 +79,7 @@ public class PDFRectangle {
* @return the PDF
*/
public byte[] toPDF() {
- return toPDFString().getBytes();
+ return PDFDocument.encode(toPDFString());
}
/**
diff --git a/src/java/org/apache/fop/pdf/PDFReference.java b/src/java/org/apache/fop/pdf/PDFReference.java
new file mode 100644
index 000000000..4ca50a79f
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/PDFReference.java
@@ -0,0 +1,67 @@
+/*
+ * 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 java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+
+/**
+ * Class representing a PDF object reference. The object holds a soft reference to the actual
+ * PDF object so the garbage collector can free the object if it's not referenced elsewhere. The
+ * important thing about the class is the reference information to the actual PDF object in the
+ * PDF file.
+ */
+public class PDFReference implements PDFWritable {
+
+ private String indirectReference;
+
+ private Reference objReference;
+
+ /**
+ * Creates a new PDF reference.
+ * @param obj the object to be referenced
+ */
+ public PDFReference(PDFObject obj) {
+ this.indirectReference = obj.referencePDF();
+ this.objReference = new SoftReference(obj);
+ }
+
+ /**
+ * Returns the PDF object
+ * @return the PDF object, or null if it has been released
+ */
+ public PDFObject getObject() {
+ if (this.objReference != null) {
+ PDFObject obj = (PDFObject)this.objReference.get();
+ if (obj == null) {
+ this.objReference = null;
+ }
+ return obj;
+ } else {
+ return null;
+ }
+ }
+
+ /** @see org.apache.fop.pdf.PDFWritable#toInlinePDFString() */
+ public String toInlinePDFString() {
+ return this.indirectReference;
+ }
+
+}
diff --git a/src/java/org/apache/fop/pdf/PDFRoot.java b/src/java/org/apache/fop/pdf/PDFRoot.java
index 60fe6b390..4a80de4c9 100644
--- a/src/java/org/apache/fop/pdf/PDFRoot.java
+++ b/src/java/org/apache/fop/pdf/PDFRoot.java
@@ -20,7 +20,6 @@
package org.apache.fop.pdf;
import java.util.List;
-import java.util.ArrayList;
/**
* class representing a Root (/Catalog) object
@@ -63,11 +62,8 @@ public class PDFRoot extends PDFObject {
/** The array of OutputIntents */
private List outputIntents;
- /**
- * The referencePDF value of the /Dests object,
- * if this PDF has a Name Dictionary
- */
- private String namesReferencePDF = null;
+ /** the /Dests object, if this PDF has a Names Dictionary */
+ private PDFNames names;
private int pageMode = PAGEMODE_USENONE;
@@ -133,12 +129,20 @@ public class PDFRoot extends PDFObject {
}
/**
- * Set the optional Metadata object.
- * @param meta the Metadata object
- * @since PDF 1.4
+ * Set the Names object.
+ * @param names the Names object
+ * @since PDF 1.2
+ */
+ public void setNames(PDFNames names) {
+ this.names = names;
+ }
+
+ /**
+ * @return the Names object if set, null otherwise.
+ * @since PDF 1.2
*/
- public void setNames(String referencePDF) {
- this.namesReferencePDF = referencePDF;
+ public PDFNames getNames() {
+ return this.names;
}
/**
@@ -175,7 +179,7 @@ public class PDFRoot extends PDFObject {
public String toPDFString() {
StringBuffer p = new StringBuffer(128);
p.append(getObjectID());
- p.append("<< /Type /Catalog\n/Pages "
+ p.append("<< /Type /Catalog\n /Pages "
+ this.rootPages.referencePDF()
+ "\n");
if (outline != null) {
@@ -197,17 +201,17 @@ public class PDFRoot extends PDFObject {
break;
}
}
- if (getDocumentSafely().getHasDestinations() && namesReferencePDF != null) {
- p.append(" /Names " + namesReferencePDF + "\n");
+ 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");
+ p.append(" /Metadata " + getMetadata().referencePDF() + "\n");
}
if (this.outputIntents != null
&& this.outputIntents.size() > 0
&& getDocumentSafely().getPDFVersion() >= PDFDocument.PDF_VERSION_1_4) {
- p.append("/OutputIntents [");
+ p.append(" /OutputIntents [");
for (int i = 0, c = this.outputIntents.size(); i < c; i++) {
PDFOutputIntent outputIntent = (PDFOutputIntent)this.outputIntents.get(i);
if (i > 0) {
diff --git a/src/java/org/apache/fop/pdf/PDFState.java b/src/java/org/apache/fop/pdf/PDFState.java
index e0a2eae1a..c01aab52e 100644
--- a/src/java/org/apache/fop/pdf/PDFState.java
+++ b/src/java/org/apache/fop/pdf/PDFState.java
@@ -301,7 +301,22 @@ public class PDFState {
}
/**
- * Get the grapics state.
+ * Get a copy of the base transform for the page. Used to translate
+ * IPP/BPP values into X,Y positions when positioning is "fixed".
+ *
+ * @return the base transform, or null if the state stack is empty
+ */
+ public AffineTransform getBaseTransform() {
+ if (stateStack.size() == 0) {
+ return null;
+ } else {
+ Data baseData = (Data) stateStack.get(0);
+ return (AffineTransform) baseData.transform.clone();
+ }
+ }
+
+ /**
+ * Get the graphics state.
* This gets the combination of all graphic states for
* the current context.
* This is the graphic state set with the gs operator not
diff --git a/src/java/org/apache/fop/pdf/PDFStream.java b/src/java/org/apache/fop/pdf/PDFStream.java
index ea7748576..2cc9a1b0a 100644
--- a/src/java/org/apache/fop/pdf/PDFStream.java
+++ b/src/java/org/apache/fop/pdf/PDFStream.java
@@ -57,7 +57,7 @@ public class PDFStream extends AbstractPDFStream {
*/
public void add(String s) {
try {
- data.getOutputStream().write(s.getBytes());
+ data.getOutputStream().write(PDFDocument.encode(s));
} catch (IOException ex) {
//TODO throw the exception and catch it elsewhere
ex.printStackTrace();
diff --git a/src/java/org/apache/fop/pdf/PDFWritable.java b/src/java/org/apache/fop/pdf/PDFWritable.java
new file mode 100644
index 000000000..23bcf9ad8
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/PDFWritable.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * This interface is implemented by classes that can be serialized to a PDF file either by
+ * serializing the object or by writing a indirect reference to the actual object.
+ */
+public interface PDFWritable {
+
+ /**
+ * Returns a representation of this object for in-object placement, i.e. if the object
+ * has an object number its reference is returned. Otherwise, its PDF representation is
+ * returned.
+ * @return the String representation
+ */
+ String toInlinePDFString();
+
+}
diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java
index 835445603..50c314f15 100644
--- a/src/java/org/apache/fop/render/AbstractRenderer.java
+++ b/src/java/org/apache/fop/render/AbstractRenderer.java
@@ -165,7 +165,7 @@ public abstract class AbstractRenderer
/**
* @see org.apache.fop.render.Renderer#processOffDocumentItem(OffDocumentItem)
*/
- public void processOffDocumentItem(OffDocumentItem oDI) { }
+ public void processOffDocumentItem(OffDocumentItem odi) { }
/** @see org.apache.fop.render.Renderer#getGraphics2DAdapter() */
public Graphics2DAdapter getGraphics2DAdapter() {
diff --git a/src/java/org/apache/fop/render/Renderer.java b/src/java/org/apache/fop/render/Renderer.java
index 0b763b996..d54a61025 100644
--- a/src/java/org/apache/fop/render/Renderer.java
+++ b/src/java/org/apache/fop/render/Renderer.java
@@ -113,9 +113,9 @@ public interface Renderer {
* document (e.g., PDF bookmarks). Note - not all renderers will process
* all off-document items.
*
- * @param ext The extension element to be rendered
+ * @param odi The off-document item to be rendered
*/
- void processOffDocumentItem(OffDocumentItem ext);
+ void processOffDocumentItem(OffDocumentItem odi);
/**
* @return the adapter for painting Java2D images (or null if not supported)
diff --git a/src/java/org/apache/fop/render/bitmap/MultiFileRenderingUtil.java b/src/java/org/apache/fop/render/bitmap/MultiFileRenderingUtil.java
new file mode 100644
index 000000000..ca757fccf
--- /dev/null
+++ b/src/java/org/apache/fop/render/bitmap/MultiFileRenderingUtil.java
@@ -0,0 +1,75 @@
+package org.apache.fop.render.bitmap;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This utility class helps renderers who generate one file per page,
+ * like the PNG renderer.
+ */
+public class MultiFileRenderingUtil {
+
+ /** The file syntax prefix, eg. "page" will output "page1.png" etc */
+ private String filePrefix;
+
+ private String fileExtension;
+
+ /** The output directory where images are to be written */
+ private File outputDir;
+
+ /**
+ * Creates a new instance.
+ * <p>
+ * The file name must not have an extension, or must have extension "png",
+ * and its last period must not be at the start (empty file prefix).
+ * @param ext the extension to be used
+ * @param outputFile the output file or null if there's no such information
+ */
+ public MultiFileRenderingUtil(String ext, File outputFile) {
+ this.fileExtension = ext;
+ // the file provided on the command line
+ if (outputFile == null) {
+ //No filename information available. Only the first page will be rendered.
+ outputDir = null;
+ filePrefix = null;
+ } else {
+ outputDir = outputFile.getParentFile();
+
+ // extracting file name syntax
+ String s = outputFile.getName();
+ int i = s.lastIndexOf(".");
+ if (i > 0) {
+ // Make sure that the file extension was "png"
+ String extension = s.substring(i + 1).toLowerCase();
+ if (!ext.equals(extension)) {
+ throw new IllegalArgumentException("Invalid file extension ('"
+ + extension + "') specified");
+ }
+ } else if (i == -1) {
+ i = s.length();
+ } else { // i == 0
+ throw new IllegalArgumentException("Invalid file name ('"
+ + s + "') specified");
+ }
+ if (s.charAt(i - 1) == '1') {
+ i--; // getting rid of the "1"
+ }
+ filePrefix = s.substring(0, i);
+ }
+ }
+
+ public OutputStream createOutputStream(int pageNumber) throws IOException {
+ if (filePrefix == null) {
+ return null;
+ } else {
+ File f = new File(outputDir,
+ filePrefix + (pageNumber + 1) + "." + fileExtension);
+ OutputStream os = new BufferedOutputStream(new FileOutputStream(f));
+ return os;
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/bitmap/PNGRenderer.java b/src/java/org/apache/fop/render/bitmap/PNGRenderer.java
index 19573e4d9..67bee3a8b 100644
--- a/src/java/org/apache/fop/render/bitmap/PNGRenderer.java
+++ b/src/java/org/apache/fop/render/bitmap/PNGRenderer.java
@@ -20,10 +20,6 @@
package org.apache.fop.render.bitmap;
import java.awt.image.RenderedImage;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -33,7 +29,6 @@ import org.apache.xmlgraphics.image.writer.ImageWriterRegistry;
import org.apache.commons.io.IOUtils;
-import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.area.PageViewport;
import org.apache.fop.render.java2d.Java2DRenderer;
@@ -51,14 +46,11 @@ public class PNGRenderer extends Java2DRenderer {
/** The file extension expected for PNG files */
private static final String PNG_FILE_EXTENSION = "png";
- /** The file syntax prefix, eg. "page" will output "page1.png" etc */
- private String filePrefix;
-
- /** The output directory where images are to be written */
- private File outputDir;
-
/** The OutputStream for the first Image */
private OutputStream firstOutputStream;
+
+ /** Helper class for generating multiple files */
+ private MultiFileRenderingUtil multiFileUtil;
/** @see org.apache.fop.render.AbstractRenderer */
public String getMimeType() {
@@ -68,54 +60,11 @@ public class PNGRenderer extends Java2DRenderer {
/** @see org.apache.fop.render.Renderer#startRenderer(java.io.OutputStream) */
public void startRenderer(OutputStream outputStream) throws IOException {
log.info("rendering areas to PNG");
- setOutputDirectory();
+ multiFileUtil = new MultiFileRenderingUtil(PNG_FILE_EXTENSION,
+ getUserAgent().getOutputFile());
this.firstOutputStream = outputStream;
}
- /**
- * Sets the output directory, either from the outfile specified on the
- * command line, or from the directory specified in configuration file.
- * Also sets the file name syntax, eg. "page".
- * The file name must not have an extension, or must have extension "png",
- * and its last period must not be at the start (empty file prefix).
- *
- * @throws IOException if an invalid output file name was specified
- * (e.g., with an extension other than '.png')
- */
- private void setOutputDirectory() throws IOException {
-
- // the file provided on the command line
- File f = getUserAgent().getOutputFile();
- if (f == null) {
- //No filename information available. Only the first page will be rendered.
- outputDir = null;
- filePrefix = null;
- } else {
- outputDir = f.getParentFile();
-
- // extracting file name syntax
- String s = f.getName();
- int i = s.lastIndexOf(".");
- if (i > 0) {
- // Make sure that the file extension was "png"
- String extension = s.substring(i + 1).toLowerCase();
- if (!PNG_FILE_EXTENSION.equals(extension)) {
- throw new IOException("Invalid file extension ('"
- + extension + "') specified");
- }
- } else if (i == -1) {
- i = s.length();
- } else { // i == 0
- throw new IOException("Invalid file name ('"
- + s + "') specified");
- }
- if (s.charAt(i - 1) == '1') {
- i--; // getting rid of the "1"
- }
- filePrefix = s.substring(0, i);
- }
- }
-
/** @see org.apache.fop.render.Renderer#stopRenderer() */
public void stopRenderer() throws IOException {
@@ -131,23 +80,14 @@ public class PNGRenderer extends Java2DRenderer {
}
try {
// Do the rendering: get the image for this page
- RenderedImage image = (RenderedImage) getPageImage((PageViewport) pageViewportList
- .get(i));
+ PageViewport pv = (PageViewport)pageViewportList.get(i);
+ RenderedImage image = (RenderedImage)getPageImage(pv);
// Encode this image
- log.debug("Encoding page " + (i + 1));
- ImageWriterParams params = new ImageWriterParams();
- params.setResolution(Math.round(userAgent.getTargetResolution()));
-
- // Encode PNG image
- ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor(getMimeType());
- if (writer == null) {
- throw new IOException("Could not get an ImageWriter to produce "
- + getMimeType() + ". The most likely explanation for this is a class"
- + " loading problem.");
+ if (log.isDebugEnabled()) {
+ log.debug("Encoding page " + (i + 1));
}
- log.debug("Writing image using " + writer.getClass().getName());
- writer.writeImage(image, os, params);
+ writeImage(os, image);
} finally {
//Only close self-created OutputStreams
if (os != firstOutputStream) {
@@ -157,29 +97,36 @@ public class PNGRenderer extends Java2DRenderer {
}
}
+ private void writeImage(OutputStream os, RenderedImage image) throws IOException {
+ ImageWriterParams params = new ImageWriterParams();
+ params.setResolution(Math.round(userAgent.getTargetResolution()));
+
+ // Encode PNG image
+ ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor(getMimeType());
+ if (writer == null) {
+ throw new IOException("Could not get an ImageWriter to produce "
+ + getMimeType() + ". The most likely explanation for this is a class"
+ + " loading problem.");
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Writing image using " + writer.getClass().getName());
+ }
+ writer.writeImage(image, os, params);
+ }
+
/**
- * Builds the OutputStream corresponding to this page
- * @param 0-based pageNumber
+ * Returns the OutputStream corresponding to this page
+ * @param pageNumber 0-based page number
* @return the corresponding OutputStream
+ * @throws IOException In case of an I/O error
*/
- private OutputStream getCurrentOutputStream(int pageNumber) {
+ protected OutputStream getCurrentOutputStream(int pageNumber) throws IOException {
if (pageNumber == 0) {
return firstOutputStream;
- }
-
- if (filePrefix == null) {
- return null;
} else {
- File f = new File(outputDir,
- filePrefix + (pageNumber + 1) + "." + PNG_FILE_EXTENSION);
- try {
- OutputStream os = new BufferedOutputStream(new FileOutputStream(f));
- return os;
- } catch (FileNotFoundException e) {
- new FOPException("Can't build the OutputStream\n" + e);
- return null;
- }
+ return multiFileUtil.createOutputStream(pageNumber);
}
+
}
}
diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java
index f64fa75aa..7d584c036 100644
--- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java
+++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java
@@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
+import java.awt.geom.Point2D;
import java.awt.Color;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
@@ -48,6 +49,8 @@ import org.apache.commons.io.IOUtils;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.area.Area;
+import org.apache.fop.area.Block;
import org.apache.fop.area.CTM;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.OffDocumentExtensionAttachment;
@@ -60,6 +63,7 @@ import org.apache.fop.area.inline.AbstractTextArea;
import org.apache.fop.area.inline.TextArea;
import org.apache.fop.area.inline.Image;
import org.apache.fop.area.inline.Leader;
+import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.area.inline.InlineParent;
import org.apache.fop.area.inline.WordArea;
import org.apache.fop.area.inline.SpaceArea;
@@ -69,17 +73,20 @@ import org.apache.fop.fonts.FontSetup;
import org.apache.fop.image.FopImage;
import org.apache.fop.image.ImageFactory;
import org.apache.fop.image.XMLImage;
+import org.apache.fop.pdf.PDFAction;
import org.apache.fop.pdf.PDFAMode;
import org.apache.fop.pdf.PDFAnnotList;
import org.apache.fop.pdf.PDFColor;
import org.apache.fop.pdf.PDFConformanceException;
-import org.apache.fop.pdf.PDFDestination;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFEncryptionManager;
import org.apache.fop.pdf.PDFEncryptionParams;
+import org.apache.fop.pdf.PDFFactory;
import org.apache.fop.pdf.PDFFilterList;
+import org.apache.fop.pdf.PDFGoTo;
import org.apache.fop.pdf.PDFICCBasedColorSpace;
import org.apache.fop.pdf.PDFICCStream;
+import org.apache.fop.pdf.PDFGoTo;
import org.apache.fop.pdf.PDFInfo;
import org.apache.fop.pdf.PDFLink;
import org.apache.fop.pdf.PDFMetadata;
@@ -164,16 +171,35 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
protected Map pages = null;
/**
- * Page references are stored using the PageViewport as the key
- * when a reference is made the PageViewport is used
- * for pdf this means we need the pdf page reference
+ * Maps unique PageViewport key to PDF page reference
*/
protected Map pageReferences = new java.util.HashMap();
- /** Page viewport references */
+ /**
+ * Maps unique PageViewport key back to PageViewport itself
+ */
protected Map pvReferences = new java.util.HashMap();
/**
+ * Maps XSL-FO element IDs to their on-page XY-positions
+ * Must be used in conjunction with the page reference to fully specify the PDFGoTo details
+ */
+ protected Map idPositions = new java.util.HashMap();
+
+ /**
+ * Maps XSL-FO element IDs to PDFGoTo objects targeting the corresponding areas
+ * These objects may not all be fully filled in yet
+ */
+ protected Map idGoTos = new java.util.HashMap();
+
+ /**
+ * The PDFGoTos in idGoTos that are not complete yet
+ */
+ protected List unfinishedGoTos = new java.util.ArrayList();
+ // can't use a Set because PDFGoTo.equals returns true if the target is the same,
+ // even if the object number differs
+
+ /**
* The output stream to write the document to
*/
protected OutputStream ostream;
@@ -197,7 +223,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
* the current page to add annotations to
*/
protected PDFPage currentPage;
-
+
+ /**
+ * the current page's PDF reference string (to avoid numerous function calls)
+ */
+ protected String currentPageRef;
+
/** the (optional) encryption parameters */
protected PDFEncryptionParams encryptionParams;
@@ -479,9 +510,34 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
/**
+ * Checks if there are any unfinished PDFGoTos left in the list and resolves them
+ * to a default position on the page. Logs a warning, as this should not happen.
+ */
+ protected void finishOpenGoTos() {
+ int count = unfinishedGoTos.size();
+ if (count > 0) {
+ // TODO : page height may not be the same for all targeted pages
+ Point2D.Float defaultPos = new Point2D.Float(0f, pageHeight / 1000f); // top-o-page
+ while (!unfinishedGoTos.isEmpty()) {
+ PDFGoTo gt = (PDFGoTo) unfinishedGoTos.get(0);
+ finishIDGoTo(gt, defaultPos);
+ }
+ boolean one = count == 1;
+ String pl = one ? "" : "s";
+ String ww = one ? "was" : "were";
+ String ia = one ? "is" : "are";
+ log.warn("" + count + " link target" + pl + " could not be fully resolved and "
+ + ww + " now point to the top of the page or "
+ + ia + " dysfunctional."); // dysfunctional if pageref is null
+ }
+ }
+
+ /**
* @see org.apache.fop.render.Renderer#stopRenderer()
*/
public void stopRenderer() throws IOException {
+ finishOpenGoTos();
+
pdfDoc.getResources().addFonts(pdfDoc, fontInfo);
pdfDoc.outputTrailer(ostream);
@@ -498,6 +554,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
currentPage = null;
currentState = null;
currentFontName = "";
+
+ idPositions.clear();
+ idGoTos.clear();
}
/**
@@ -512,19 +571,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
* @see org.apache.fop.render.Renderer#processOffDocumentItem(OffDocumentItem)
*/
public void processOffDocumentItem(OffDocumentItem odi) {
- // render Destinations
if (odi instanceof DestinationData) {
- PDFDestination destination = pdfDoc.getFactory().makeDestination((DestinationData) odi);
- PageViewport pv = destination.getPageViewport();
- String dest = (String)pageReferences.get(pv.getKey());
- Rectangle2D bounds = pv.getViewArea();
- double h = bounds.getHeight();
- float yoffset = (float)h / 1000f;
- String gtRef = pdfDoc.getFactory().getGoToReference(dest, yoffset);
- destination.setGoToReference(gtRef);
- }
- // render Bookmark-Tree
- else if (odi instanceof BookmarkData) {
+ // render Destinations
+ renderDestination((DestinationData) odi);
+ } else if (odi instanceof BookmarkData) {
+ // render Bookmark-Tree
renderBookmarkTree((BookmarkData) odi);
} else if (odi instanceof OffDocumentExtensionAttachment) {
ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment();
@@ -534,6 +585,21 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
}
+ private void renderDestination(DestinationData dd) {
+ String targetID = dd.getIDRef();
+ if (targetID != null && targetID.length() > 0) {
+ PageViewport pv = dd.getPageViewport();
+ if (pv == null) {
+ log.warn("Unresolved destination item received: " + dd.getIDRef());
+ }
+ PDFGoTo gt = getPDFGoToForID(targetID, pv.getKey());
+ pdfDoc.getFactory().makeDestination(
+ dd.getIDRef(), gt.makeReference());
+ } else {
+ log.warn("DestinationData item with null or empty IDRef received.");
+ }
+ }
+
/**
* Renders a Bookmark-Tree object
* @param bookmarks the BookmarkData object containing all the Bookmark-Items
@@ -545,36 +611,34 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
}
- private void renderBookmarkItem(BookmarkData bookmarkItem,
- PDFOutline parentBookmarkItem) {
+ private void renderBookmarkItem(BookmarkData bookmarkItem,
+ PDFOutline parentBookmarkItem) {
PDFOutline pdfOutline = null;
- PageViewport pv = bookmarkItem.getPageViewport();
- if (pv != null) {
- Rectangle2D bounds = pv.getViewArea();
- double h = bounds.getHeight();
- float yoffset = (float)h / 1000f;
- String intDest = (String)pageReferences.get(pv.getKey());
- if (parentBookmarkItem == null) {
- PDFOutline outlineRoot = pdfDoc.getOutlineRoot();
- pdfOutline = pdfDoc.getFactory().makeOutline(outlineRoot,
- bookmarkItem.getBookmarkTitle(),
- intDest, yoffset,
- bookmarkItem.showChildItems());
+
+ String targetID = bookmarkItem.getIDRef();
+ if (targetID != null && targetID.length() > 0) {
+ PageViewport pv = bookmarkItem.getPageViewport();
+ if (pv != null) {
+ String pvKey = pv.getKey();
+ PDFGoTo gt = getPDFGoToForID(targetID, pvKey);
+ // create outline object:
+ PDFOutline parent = parentBookmarkItem != null
+ ? parentBookmarkItem
+ : pdfDoc.getOutlineRoot();
+ pdfOutline = pdfDoc.getFactory().makeOutline(parent,
+ bookmarkItem.getBookmarkTitle(), gt, bookmarkItem.showChildItems());
} else {
- pdfOutline = pdfDoc.getFactory().makeOutline(parentBookmarkItem,
- bookmarkItem.getBookmarkTitle(),
- intDest, yoffset,
- bookmarkItem.showChildItems());
+ log.warn("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport.");
}
} else {
- log.warn("Unresolved bookmark item received: " + bookmarkItem.getIDRef());
+ log.warn("Bookmark item with null or empty IDRef received.");
}
for (int i = 0; i < bookmarkItem.getCount(); i++) {
renderBookmarkItem(bookmarkItem.getSubData(i), pdfOutline);
}
}
-
+
private void renderXMPMetadata(XMPMetadata metadata) {
Metadata docXMP = metadata.getMetadata();
Metadata fopXMP = PDFMetadata.createXMPFromUserAgent(pdfDoc);
@@ -707,6 +771,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
} else {
setupPage(page);
}
+ currentPageRef = currentPage.referencePDF();
+
Rectangle2D bounds = page.getViewArea();
double h = bounds.getHeight();
pageHeight = (int) h;
@@ -1089,6 +1155,188 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
/**
+ * Returns area's id if it is the first area in the document with that id
+ * (i.e. if the area qualifies as a link target).
+ * Otherwise, or if the area has no id, null is returned.
+ *
+ * NOTE : area must be on currentPageViewport, otherwise result may be wrong!
+ *
+ * @param area the area for which to return the id
+ */
+ protected String getTargetableID(Area area) {
+ String id = (String) area.getTrait(Trait.PROD_ID);
+ if (id == null || id.length() == 0
+ || !currentPageViewport.isFirstWithID(id)
+ || idPositions.containsKey(id)) {
+ return null;
+ } else {
+ return id;
+ }
+ }
+
+ /**
+ * Set XY position in the PDFGoTo and add it to the PDF trailer.
+ *
+ * @param gt the PDFGoTo object
+ * @param position the X,Y position to set
+ */
+ protected void finishIDGoTo(PDFGoTo gt, Point2D.Float position) {
+ gt.setPosition(position);
+ pdfDoc.addTrailerObject(gt);
+ unfinishedGoTos.remove(gt);
+ }
+
+ /**
+ * Set page reference and XY position in the PDFGoTo and add it to the PDF trailer.
+ *
+ * @param gt the PDFGoTo object
+ * @param pdfPageRef the PDF reference string of the target page object
+ * @param position the X,Y position to set
+ */
+ protected void finishIDGoTo(PDFGoTo gt, String pdfPageRef, Point2D.Float position) {
+ gt.setPageReference(pdfPageRef);
+ finishIDGoTo(gt, position);
+ }
+
+ /**
+ * Get a PDFGoTo pointing to the given id. Create one if necessary.
+ * It is possible that the PDFGoTo is not fully resolved yet. In that case
+ * it must be completed (and added to the PDF trailer) later.
+ *
+ * @param targetID the target id of the PDFGoTo
+ * @param pvKey the unique key of the target PageViewport
+ *
+ * @return the PDFGoTo that was found or created
+ */
+ protected PDFGoTo getPDFGoToForID(String targetID, String pvKey) {
+ // Already a PDFGoTo present for this target? If not, create.
+ PDFGoTo gt = (PDFGoTo) idGoTos.get(targetID);
+ if (gt == null) {
+ String pdfPageRef = (String) pageReferences.get(pvKey);
+ Point2D.Float position = (Point2D.Float) idPositions.get(targetID);
+ // can the GoTo already be fully filled in?
+ if (pdfPageRef != null && position != null) {
+ // getPDFGoTo shares PDFGoTo objects as much as possible.
+ // It also takes care of assignObjectNumber and addTrailerObject.
+ gt = pdfDoc.getFactory().getPDFGoTo(pdfPageRef, position);
+ } else {
+ // Not complete yet, can't use getPDFGoTo:
+ gt = new PDFGoTo(pdfPageRef);
+ pdfDoc.assignObjectNumber(gt);
+ // pdfDoc.addTrailerObject() will be called later, from finishIDGoTo()
+ unfinishedGoTos.add(gt);
+ }
+ idGoTos.put(targetID, gt);
+ }
+ return gt;
+ }
+
+ /**
+ * Saves id's absolute position on page for later retrieval by PDFGoTos
+ *
+ * @param id the id of the area whose position must be saved
+ * @param pdfPageRef the PDF page reference string
+ * @param relativeIPP the *relative* IP position in millipoints
+ * @param relativeBPP the *relative* BP position in millipoints
+ * @param tf the transformation to apply once the relative positions have been
+ * converted to points
+ */
+ protected void saveAbsolutePosition(String id, String pdfPageRef,
+ int relativeIPP, int relativeBPP, AffineTransform tf) {
+ Point2D.Float position = new Point2D.Float(relativeIPP / 1000f, relativeBPP / 1000f);
+ tf.transform(position, position);
+ idPositions.put(id, position);
+ // is there already a PDFGoTo waiting to be completed?
+ PDFGoTo gt = (PDFGoTo) idGoTos.get(id);
+ if (gt != null) {
+ finishIDGoTo(gt, pdfPageRef, position);
+ }
+/*
+ // The code below auto-creates a named destination for every id in the document.
+ // This should probably be controlled by a user-configurable setting, as it may
+ // make the PDF file grow noticeably.
+ // *** NOT YET WELL-TESTED ! ***
+ if (true) {
+ PDFFactory factory = pdfDoc.getFactory();
+ if (gt == null) {
+ gt = factory.getPDFGoTo(pdfPageRef, position);
+ idGoTos.put(id, gt); // so others can pick it up too
+ }
+ factory.makeDestination(id, gt.referencePDF(), currentPageViewport);
+ // Note: using currentPageViewport is only correct if the id is indeed on
+ // the current PageViewport. But even if incorrect, it won't interfere with
+ // what gets created in the PDF.
+ // For speedup, we should also create a lookup map id -> PDFDestination
+ }
+*/
+ }
+
+ /**
+ * Saves id's absolute position on page for later retrieval by PDFGoTos,
+ * using the currently valid transformation and the currently valid PDF page reference
+ *
+ * @param id the id of the area whose position must be saved
+ * @param relativeIPP the *relative* IP position in millipoints
+ * @param relativeBPP the *relative* BP position in millipoints
+ */
+ protected void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) {
+ saveAbsolutePosition(id, currentPageRef,
+ relativeIPP, relativeBPP, currentState.getTransform());
+ }
+
+ /**
+ * If the given block area is a possible link target, its id + absolute position will
+ * be saved. The saved position is only correct if this function is called at the very
+ * start of renderBlock!
+ *
+ * @param block the block area in question
+ */
+ protected void saveBlockPosIfTargetable(Block block) {
+ String id = getTargetableID(block);
+ if (id != null) {
+ // FIXME: Like elsewhere in the renderer code, absolute and relative
+ // directions are happily mixed here. This makes sure that the
+ // links point to the right location, but it is not correct.
+ int ipp = block.getXOffset();
+ int bpp = block.getYOffset() + block.getSpaceBefore();
+ int positioning = block.getPositioning();
+ if (!(positioning == Block.FIXED || positioning == Block.ABSOLUTE)) {
+ ipp += currentIPPosition;
+ bpp += currentBPPosition;
+ }
+ AffineTransform tf = positioning == Block.FIXED
+ ? currentState.getBaseTransform()
+ : currentState.getTransform();
+ saveAbsolutePosition(id, currentPageRef, ipp, bpp, tf);
+ }
+ }
+
+ /**
+ * If the given inline area is a possible link target, its id + absolute position will
+ * be saved. The saved position is only correct if this function is called at the very
+ * start of renderInlineArea!
+ *
+ * @param inlineArea the inline area in question
+ */
+ protected void saveInlinePosIfTargetable(InlineArea inlineArea) {
+ String id = getTargetableID(inlineArea);
+ if (id != null) {
+ int extraMarginBefore = 5000; // millipoints
+ int ipp = currentIPPosition;
+ int bpp = currentBPPosition + inlineArea.getOffset() - extraMarginBefore;
+ saveAbsolutePosition(id, ipp, bpp);
+ }
+ }
+
+ /**
+ * @see org.apache.fop.render.AbstractRenderer#renderBlock(Block)
+ */
+ protected void renderBlock(Block block) {
+ saveBlockPosIfTargetable(block);
+ super.renderBlock(block);
+ }
+
+ /**
* @see org.apache.fop.render.AbstractRenderer#renderLineArea(LineArea)
*/
protected void renderLineArea(LineArea line) {
@@ -1097,53 +1345,88 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
/**
+ * @see org.apache.fop.render.AbstractRenderer#renderInlineArea(InlineArea)
+ */
+ protected void renderInlineArea(InlineArea inlineArea) {
+ saveInlinePosIfTargetable(inlineArea);
+ super.renderInlineArea(inlineArea);
+ }
+
+ /**
* Render inline parent area.
* For pdf this handles the inline parent area traits such as
* links, border, background.
* @param ip the inline parent area
*/
public void renderInlineParent(InlineParent ip) {
- float start = currentIPPosition / 1000f;
- float top = (ip.getOffset() + currentBPPosition) / 1000f;
- float width = ip.getIPD() / 1000f;
- float height = ip.getBPD() / 1000f;
+ boolean annotsAllowed = pdfDoc.getProfile().isAnnotationAllowed();
+
+ // stuff we only need if a link must be created:
+ Rectangle2D ipRect = null;
+ PDFFactory factory = null;
+ PDFAction action = null;
+ if (annotsAllowed) {
+ // make sure the rect is determined *before* calling super!
+ int ipp = currentIPPosition;
+ int bpp = currentBPPosition + ip.getOffset();
+ ipRect = new Rectangle2D.Float(ipp / 1000f, bpp / 1000f,
+ ip.getIPD() / 1000f, ip.getBPD() / 1000f);
+ AffineTransform transform = currentState.getTransform();
+ ipRect = transform.createTransformedShape(ipRect).getBounds2D();
+
+ factory = pdfDoc.getFactory();
+ }
+
// render contents
super.renderInlineParent(ip);
- if (pdfDoc.getProfile().isAnnotationAllowed()) {
- // place the link over the top
- Object tr = ip.getTrait(Trait.INTERNAL_LINK);
- boolean internal = false;
- String dest = null;
- float yoffset = 0;
- if (tr == null) {
- dest = (String)ip.getTrait(Trait.EXTERNAL_LINK);
+ boolean linkTraitFound = false;
+
+ // try INTERNAL_LINK first
+ Trait.InternalLink intLink = (Trait.InternalLink) ip.getTrait(Trait.INTERNAL_LINK);
+ if (intLink != null) {
+ linkTraitFound = true;
+ String pvKey = intLink.getPVKey();
+ String idRef = intLink.getIDRef();
+ boolean pvKeyOK = pvKey != null && pvKey.length() > 0;
+ boolean idRefOK = idRef != null && idRef.length() > 0;
+ if (pvKeyOK && idRefOK) {
+ if (annotsAllowed) {
+ action = getPDFGoToForID(idRef, pvKey);
+ }
+ } else if (pvKeyOK) {
+ log.warn("Internal link trait with PageViewport key " + pvKey
+ + " contains no ID reference.");
+ } else if (idRefOK) {
+ log.warn("Internal link trait with ID reference " + idRef
+ + " contains no PageViewport key.");
} else {
- String pvKey = (String)tr;
- dest = (String)pageReferences.get(pvKey);
- if (dest != null) {
- PageViewport pv = (PageViewport)pvReferences.get(pvKey);
- Rectangle2D bounds = pv.getViewArea();
- double h = bounds.getHeight();
- yoffset = (float)h / 1000f;
- internal = true;
+ log.warn("Internal link trait received with neither PageViewport key"
+ + " nor ID reference.");
+ }
+ }
+
+ // no INTERNAL_LINK, look for EXTERNAL_LINK
+ if (!linkTraitFound) {
+ String extDest = (String) ip.getTrait(Trait.EXTERNAL_LINK);
+ if (extDest != null && extDest.length() > 0) {
+ linkTraitFound = true;
+ if (annotsAllowed) {
+ action = factory.getExternalAction(extDest);
}
}
- if (dest != null) {
- // add link to pdf document
- Rectangle2D rect = new Rectangle2D.Float(start, top, width, height);
- // transform rect to absolute coords
- AffineTransform transform = currentState.getTransform();
- rect = transform.createTransformedShape(rect).getBounds2D();
-
- int type = internal ? PDFLink.INTERNAL : PDFLink.EXTERNAL;
- PDFLink pdflink = pdfDoc.getFactory().makeLink(
- rect, dest, type, yoffset);
- currentPage.addAnnotation(pdflink);
+ }
+
+ // warn if link trait found but not allowed, else create link
+ if (linkTraitFound) {
+ if (!annotsAllowed) {
+ log.warn("Skipping annotation for a link due to PDF profile: "
+ + pdfDoc.getProfile());
+ } else if (action != null) {
+ PDFLink pdfLink = factory.makeLink(ipRect, action);
+ currentPage.addAnnotation(pdfLink);
}
- } else {
- log.warn("Skipping annotation for a link due to PDF profile: " + pdfDoc.getProfile());
}
}
@@ -1199,12 +1482,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
String s = word.getWord();
escapeText(s, word.getLetterAdjustArray(),
font, (AbstractTextArea)word.getParentArea(), useMultiByte, pdf);
-
+
currentStream.add(pdf.toString());
super.renderWord(word);
}
-
+
/**
* @see org.apache.fop.render.AbstractRenderer#renderSpace(SpaceArea)
*/
@@ -1223,18 +1506,18 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
if (space.isAdjustable()) {
int tws = -((TextArea) space.getParentArea()).getTextWordSpaceAdjust()
- 2 * textArea.getTextLetterSpaceAdjust();
-
+
if (tws != 0) {
pdf.append(format(tws / (font.getFontSize() / 1000f)));
pdf.append(" ");
}
}
-
+
currentStream.add(pdf.toString());
super.renderSpace(space);
}
-
+
/**
* Escapes text according to PDF rules.
* @param s Text to escape
@@ -1282,9 +1565,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
}
if (letterAdjust != null && i < l - 1) {
- glyphAdjust -= letterAdjust[i + 1];
+ glyphAdjust -= letterAdjust[i + 1];
}
-
+
if (startPending) {
pdf.append(startText);
startPending = false;
@@ -1309,12 +1592,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
float adjust = glyphAdjust / fontSize;
-
+
if (adjust != 0) {
pdf.append(endText).append(format(adjust)).append(' ');
startPending = true;
}
-
+
}
if (!startPending) {
pdf.append(endText);
diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java
index 857847e10..cccdb6d71 100644
--- a/src/java/org/apache/fop/render/xml/XMLRenderer.java
+++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java
@@ -60,11 +60,13 @@ import org.apache.fop.area.MainReference;
import org.apache.fop.area.NormalFlow;
import org.apache.fop.area.OffDocumentExtensionAttachment;
import org.apache.fop.area.OffDocumentItem;
+import org.apache.fop.area.BookmarkData;
import org.apache.fop.area.PageViewport;
import org.apache.fop.area.RegionReference;
import org.apache.fop.area.RegionViewport;
import org.apache.fop.area.Span;
import org.apache.fop.area.Trait;
+import org.apache.fop.area.Trait.InternalLink;
import org.apache.fop.area.Trait.Background;
import org.apache.fop.area.inline.Container;
import org.apache.fop.area.inline.ForeignObject;
@@ -350,6 +352,9 @@ public class XMLRenderer extends PrintRenderer {
addAttribute("font-name", triplet.getName());
addAttribute("font-style", triplet.getStyle());
addAttribute("font-weight", triplet.getWeight());
+ } else if (clazz.equals(InternalLink.class)) {
+ InternalLink iLink = (InternalLink)value;
+ addAttribute(name, iLink.xmlAttribute());
} else if (clazz.equals(Background.class)) {
Background bkg = (Background)value;
//TODO Remove the following line (makes changes in the test checks necessary)
@@ -431,7 +436,9 @@ public class XMLRenderer extends PrintRenderer {
/** @see org.apache.fop.render.AbstractRenderer#processOffDocumentItem(OffDocumentItem) */
public void processOffDocumentItem(OffDocumentItem oDI) {
- if (oDI instanceof OffDocumentExtensionAttachment) {
+ if (oDI instanceof BookmarkData) {
+ renderBookmarkTree((BookmarkData) oDI);
+ } else if (oDI instanceof OffDocumentExtensionAttachment) {
ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)oDI).getAttachment();
if (extensionAttachments == null) {
extensionAttachments = new java.util.ArrayList();
@@ -444,6 +451,40 @@ public class XMLRenderer extends PrintRenderer {
}
/**
+ * Renders a BookmarkTree object
+ * @param bookmarkRoot the BookmarkData object representing the top of the tree
+ */
+ protected void renderBookmarkTree(BookmarkData bookmarkRoot) {
+ if (bookmarkRoot.getWhenToProcess() == OffDocumentItem.END_OF_DOC) {
+ endPageSequence();
+ }
+ /* If this kind of handling is also necessary for other renderers, then
+ better add endPageSequence to the Renderer interface and call it
+ explicitly from model.endDocument() */
+
+ startElement("bookmarkTree");
+ for (int i = 0; i < bookmarkRoot.getCount(); i++) {
+ renderBookmarkItem(bookmarkRoot.getSubData(i));
+ }
+ endElement("bookmarkTree");
+ }
+
+ private void renderBookmarkItem(BookmarkData bm) {
+ atts.clear();
+ addAttribute("title", bm.getBookmarkTitle());
+ addAttribute("show-children", String.valueOf(bm.showChildItems()));
+ PageViewport pv = bm.getPageViewport();
+ String pvKey = pv == null ? null : pv.getKey();
+ addAttribute("internal-link",
+ InternalLink.makeXMLAttribute(pvKey, bm.getIDRef()));
+ startElement("bookmark", atts);
+ for (int i = 0; i < bm.getCount(); i++) {
+ renderBookmarkItem(bm.getSubData(i));
+ }
+ endElement("bookmark");
+ }
+
+ /**
* @see org.apache.fop.render.Renderer#startRenderer(OutputStream)
*/
public void startRenderer(OutputStream outputStream)
@@ -480,9 +521,7 @@ public class XMLRenderer extends PrintRenderer {
* @see org.apache.fop.render.Renderer#stopRenderer()
*/
public void stopRenderer() throws IOException {
- if (startedSequence) {
- endElement("pageSequence");
- }
+ endPageSequence();
endElement("areaTree");
try {
handler.endDocument();
@@ -549,9 +588,7 @@ public class XMLRenderer extends PrintRenderer {
*/
public void startPageSequence(LineArea seqTitle) {
handleDocumentExtensionAttachments();
- if (startedSequence) {
- endElement("pageSequence");
- }
+ endPageSequence(); // move this before handleDocumentExtensionAttachments() ?
startedSequence = true;
startElement("pageSequence");
if (seqTitle != null) {
@@ -568,6 +605,16 @@ public class XMLRenderer extends PrintRenderer {
}
/**
+ * Tells the renderer to finish the current PageSequence
+ */
+ public void endPageSequence() {
+ if (startedSequence) {
+ endElement("pageSequence");
+ }
+ startedSequence = false;
+ }
+
+ /**
* @see org.apache.fop.render.AbstractRenderer#renderRegionViewport(RegionViewport)
*/
protected void renderRegionViewport(RegionViewport port) {
@@ -759,6 +806,24 @@ public class XMLRenderer extends PrintRenderer {
}
/**
+ * @see org.apache.fop.render.AbstractRenderer#renderInlineArea(InlineArea)
+ */
+ protected void renderInlineArea(InlineArea inlineArea) {
+ atts.clear();
+ if (inlineArea.getClass() == InlineArea.class) {
+ // Generic inline area. This is implemented to allow the 0x0 "dummy"
+ // area generated by fo:wrapper to pass its id.
+ addAreaAttributes(inlineArea);
+ addTraitAttributes(inlineArea);
+ startElement("inline", atts);
+ endElement("inline");
+ } else {
+ super.renderInlineArea(inlineArea);
+ // calls specific renderers for Text, Space, Viewport, etc. etc.
+ }
+ }
+
+ /**
* @see org.apache.fop.render.AbstractRenderer#renderViewport(Viewport)
*/
protected void renderViewport(Viewport viewport) {