aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/area
diff options
context:
space:
mode:
authorJeremias Maerki <jeremias@apache.org>2007-05-07 12:22:22 +0000
committerJeremias Maerki <jeremias@apache.org>2007-05-07 12:22:22 +0000
commit719a8a50772a780263366f9286f7f132f4561e0c (patch)
tree4c0ebcbb84750d5f42901b3315afb1f654e11655 /src/java/org/apache/fop/area
parent283e22abdafff51ac4b5aefcdc7fe06ca9cfb61e (diff)
downloadxmlgraphics-fop-719a8a50772a780263366f9286f7f132f4561e0c.tar.gz
xmlgraphics-fop-719a8a50772a780263366f9286f7f132f4561e0c.zip
Bugzilla #42067:
Add support for exact positioning of internal PDF links. Submitted by: Paul Vinkenoog <paul.at.vinkenoog.nl> git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@535866 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop/area')
-rw-r--r--src/java/org/apache/fop/area/AreaTreeHandler.java3
-rw-r--r--src/java/org/apache/fop/area/AreaTreeParser.java201
-rw-r--r--src/java/org/apache/fop/area/BookmarkData.java43
-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.java144
6 files changed, 361 insertions, 75 deletions
diff --git a/src/java/org/apache/fop/area/AreaTreeHandler.java b/src/java/org/apache/fop/area/AreaTreeHandler.java
index d818a0d83..cc70cf1d9 100644
--- a/src/java/org/apache/fop/area/AreaTreeHandler.java
+++ b/src/java/org/apache/fop/area/AreaTreeHandler.java
@@ -175,7 +175,8 @@ public class AreaTreeHandler extends FOEventHandler {
pvList = new ArrayList();
idLocations.put(id, pvList);
pvList.add(pv);
-
+ // 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.
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..d87b38592 100644
--- a/src/java/org/apache/fop/area/BookmarkData.java
+++ b/src/java/org/apache/fop/area/BookmarkData.java
@@ -61,7 +61,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 +84,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 +121,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 +251,3 @@ public class BookmarkData extends AbstractOffDocumentItem implements Resolvable
}
}
-
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..1ded69663 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,143 @@ 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[] values = attrValue.substring(1, len - 1).split(",", 2);
+ if (values.length > 0) {
+ result[0] = values[0].trim();
+ if (values.length > 1) {
+ result[1] = values[1].trim();
+ }
+ }
+ } 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.