diff options
Diffstat (limited to 'src/java/org/apache')
32 files changed, 1311 insertions, 377 deletions
diff --git a/src/java/org/apache/fop/area/AreaTreeHandler.java b/src/java/org/apache/fop/area/AreaTreeHandler.java index 91a5d603d..e7f6effe0 100644 --- a/src/java/org/apache/fop/area/AreaTreeHandler.java +++ b/src/java/org/apache/fop/area/AreaTreeHandler.java @@ -48,6 +48,9 @@ import org.apache.fop.layoutmgr.PageSequenceLayoutManager; import org.apache.fop.layoutmgr.LayoutManagerMaker; import org.apache.fop.layoutmgr.LayoutManagerMapping; +import org.apache.fop.area.DestinationData; +import org.apache.fop.fo.extensions.destination.Destination; + /** * Area tree handler for formatting objects. * @@ -412,6 +415,15 @@ public class AreaTreeHandler extends FOEventHandler { public void endDocument() throws SAXException { finishPrevPageSequence(null); + // process fox:destination elements + ArrayList destinationList = rootFObj.getDestinationList(); + if (destinationList != null) { + while(destinationList.size() > 0) { + Destination destination = (Destination)destinationList.remove(0); + DestinationData destinationData = new DestinationData(destination); + addOffDocumentItem(destinationData); + } + } // process fo:bookmark-tree BookmarkTree bookmarkTree = rootFObj.getBookmarkTree(); if (bookmarkTree != null) { diff --git a/src/java/org/apache/fop/area/DestinationData.java b/src/java/org/apache/fop/area/DestinationData.java new file mode 100644 index 000000000..7e59210e0 --- /dev/null +++ b/src/java/org/apache/fop/area/DestinationData.java @@ -0,0 +1,131 @@ +/* + * 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.area; + +import java.util.List; + +import org.apache.fop.fo.extensions.destination.Destination; +import org.apache.fop.area.PageViewport; +/** + * An instance of this class is named destination from fox:destination + */ +public class DestinationData extends AbstractOffDocumentItem implements Resolvable { + + // PDFReference (object reference) for this destination + private String goToReference; + + // ID Reference for this bookmark + private String idRef; + + // String Array to satisfy getIDRefs method + private String[] idRefs; + + // PageViewport that the idRef item refers to + private PageViewport pageRef = null; + + /** + * Create a new pdf destination data object. + * This is used by the destination to create a data object + * with a idref. During processing, this idref will be + * subsequently resolved to a particular PageViewport. + * + * @param destination the fo:bookmark object + */ + public DestinationData(Destination destination) { + idRef = destination.getInternalDestination(); + idRefs = new String[] {idRef}; + } + + /** + * Get the idref for this destination + * + * @return the idref for the destination + */ + public String getIDRef() { + return idRef; + } + + /** + * @see org.apache.fop.area.Resolvable#getIDRefs() + */ + public String[] getIDRefs() { + return idRefs; + } + + /** + * Get the PageViewport object that this destination refers to + * + * @return the PageViewport that this destination points to + */ + public PageViewport getPageViewport() { + return pageRef; + } + + /** + * 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. + * + * @return true if this object has been resolved + */ + public boolean isResolved() { + return true; + } + + /** + * Resolves the idref of this object by getting the PageViewport + * object that corresponds to the IDRef + * + * @see org.apache.fop.area.Resolvable#resolveIDRef(String, List) + * @todo check to make sure it works if multiple bookmark-items + * have the same idref + */ + public void resolveIDRef(String id, List pages) { + pageRef = (PageViewport) pages.get(0); + // TODO get rect area of id on page + } + + /** + * @see org.apache.fop.area.OffDocumentItem#getName() + */ + public String getName() { + return "Destination"; + } + +} + diff --git a/src/java/org/apache/fop/fo/ElementMappingRegistry.java b/src/java/org/apache/fop/fo/ElementMappingRegistry.java index 105a956a2..887fe8b55 100644 --- a/src/java/org/apache/fop/fo/ElementMappingRegistry.java +++ b/src/java/org/apache/fop/fo/ElementMappingRegistry.java @@ -90,7 +90,6 @@ public class ElementMappingRegistry { */ public void addElementMapping(String mappingClassName) throws IllegalArgumentException { - try { ElementMapping mapping = (ElementMapping)Class.forName(mappingClassName).newInstance(); diff --git a/src/java/org/apache/fop/fo/FONode.java b/src/java/org/apache/fop/fo/FONode.java index 91817b87f..baf964935 100644 --- a/src/java/org/apache/fop/fo/FONode.java +++ b/src/java/org/apache/fop/fo/FONode.java @@ -45,6 +45,7 @@ public abstract class FONode implements Cloneable { /** the XSL-FO namespace URI */ protected static final String FO_URI = FOElementMapping.URI; + protected static final String FOX_URI = ExtensionElementMapping.URI; /** Parent FO node */ protected FONode parent; diff --git a/src/java/org/apache/fop/fo/FOTreeBuilder.java b/src/java/org/apache/fop/fo/FOTreeBuilder.java index 4c754520d..2f1b53f1d 100644 --- a/src/java/org/apache/fop/fo/FOTreeBuilder.java +++ b/src/java/org/apache/fop/fo/FOTreeBuilder.java @@ -23,6 +23,7 @@ import java.io.OutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FormattingResults; @@ -296,9 +297,8 @@ public class FOTreeBuilder extends DefaultHandler { + " Please make sure you're producing a valid XSL-FO document."); } } else { // check that incoming node is valid for currentFObj - if (namespaceURI.equals(FOElementMapping.URI)) { - // currently no fox: elements to validate - // || namespaceURI.equals(ExtensionElementMapping.URI) */) { + if (namespaceURI.equals(FOElementMapping.URI) + || namespaceURI.equals(ExtensionElementMapping.URI)) { try { currentFObj.validateChildNode(locator, namespaceURI, localName); } catch (ValidationException e) { @@ -368,7 +368,8 @@ public class FOTreeBuilder extends DefaultHandler { throw new IllegalStateException( "endElement() called for " + rawName + " where there is no current element."); - } else if (!currentFObj.getLocalName().equals(localName) + } else + if (!currentFObj.getLocalName().equals(localName) || !currentFObj.getNamespaceURI().equals(uri)) { log.warn("Mismatch: " + currentFObj.getLocalName() + " (" + currentFObj.getNamespaceURI() diff --git a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java index 0b5ae827b..b5ed3e61c 100644 --- a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java +++ b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java @@ -20,7 +20,9 @@ package org.apache.fop.fo.extensions; import org.apache.fop.fo.ElementMapping; +import org.apache.fop.fo.FONode; import org.apache.fop.fo.UnknownXMLObj; +import org.apache.fop.fo.extensions.destination.Destination; import org.apache.fop.util.QName; import java.util.HashMap; @@ -41,6 +43,7 @@ public class ExtensionElementMapping extends ElementMapping { propertyAttributes.add("block-progression-unit"); propertyAttributes.add("widow-content-limit"); propertyAttributes.add("orphan-content-limit"); + propertyAttributes.add("internal-destination"); } /** @@ -58,9 +61,16 @@ public class ExtensionElementMapping extends ElementMapping { foObjs = new HashMap(); foObjs.put("outline", new UnknownXMLObj.Maker(URI)); foObjs.put("label", new UnknownXMLObj.Maker(URI)); + foObjs.put("destination", new DestinationMaker()); } } + static class DestinationMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new Destination(parent); + } + } + /** @see org.apache.fop.fo.ElementMapping#getStandardPrefix() */ public String getStandardPrefix() { return "fox"; diff --git a/src/java/org/apache/fop/fo/extensions/destination/Destination.java b/src/java/org/apache/fop/fo/extensions/destination/Destination.java new file mode 100644 index 000000000..b4611661e --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/destination/Destination.java @@ -0,0 +1,102 @@ +/* + * 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.fo.extensions.destination; + +import org.apache.fop.fo.ValidationException; +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.pagination.Root; +import org.apache.fop.fo.extensions.ExtensionElementMapping; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +/** + * Class for named destinations in PDF. + */ +public class Destination extends FONode { + + private String internalDestination; + private Root root; + + /** + * Constructs a Destination object (called by Maker). + * + * @param parent the parent formatting object + */ + public Destination(FONode parent) { + super(parent); + root = parent.getRoot(); + } + + /** + * @see org.apache.fop.fo.FONode#processNode(java.lang.String, org.xml.sax.Locator, + * org.xml.sax.Attributes, org.apache.fop.fo.PropertyList) + */ + public void processNode(String elementName, Locator locator, + Attributes attlist, PropertyList pList) throws FOPException { + internalDestination = attlist.getValue("internal-destination"); + if (internalDestination == null || internalDestination.length() == 0) { + attributeError("Missing attribute: internal-destination must be specified."); + } + } + + /** + * @see org.apache.fop.fo.FONode#endOfNode + */ + protected void endOfNode() throws FOPException { + root.addDestination(this); + } + + /** + * @see org.apache.fop.fo.FONode#validateChildNode(Locator, String, String) + XSL/FOP: empty + */ + protected void validateChildNode(Locator loc, String nsURI, String localName) + throws ValidationException { + invalidChildError(loc, nsURI, localName); + } + + /** + * Returns the internal destination (an reference of the id property of any FO). + * @return the internal destination + */ + public String getInternalDestination() { + return internalDestination; + } + + /** @see org.apache.fop.fo.FONode#getNamespaceURI() */ + public String getNamespaceURI() { + return ExtensionElementMapping.URI; + } + + /** @see org.apache.fop.fo.FONode#getNormalNamespacePrefix() */ + public String getNormalNamespacePrefix() { + return "fox"; + } + + /** @see org.apache.fop.fo.FONode#getLocalName() */ + public String getLocalName() { + return "destination"; + } + +} + diff --git a/src/java/org/apache/fop/fo/extensions/destination/package.html b/src/java/org/apache/fop/fo/extensions/destination/package.html new file mode 100644 index 000000000..59cbb493d --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/destination/package.html @@ -0,0 +1,23 @@ +<!-- + 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$ --> +<HTML> +<TITLE>org.apache.fop.destination Package</TITLE> +<BODY> +<P>Classes to support named destinations (only relevant for PDF output)</P> +</BODY> +</HTML> diff --git a/src/java/org/apache/fop/fo/flow/Block.java b/src/java/org/apache/fop/fo/flow/Block.java index 48a8d3bdf..6beffdfe5 100644 --- a/src/java/org/apache/fop/fo/flow/Block.java +++ b/src/java/org/apache/fop/fo/flow/Block.java @@ -296,25 +296,27 @@ public class Block extends FObjMixed { * fo:inline-container." */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (blockOrInlineItemFound || initialPropertySetFound) { - nodesOutOfOrderError(loc, "fo:marker", - "initial-property-set? (#PCDATA|%inline;|%block;)"); - } - } else if (FO_URI.equals(nsURI) && localName.equals("initial-property-set")) { - if (initialPropertySetFound) { - tooManyNodesError(loc, "fo:initial-property-set"); - } else if (blockOrInlineItemFound) { - nodesOutOfOrderError(loc, "fo:initial-property-set", - "(#PCDATA|%inline;|%block;)"); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (blockOrInlineItemFound || initialPropertySetFound) { + nodesOutOfOrderError(loc, "fo:marker", + "initial-property-set? (#PCDATA|%inline;|%block;)"); + } + } else if (localName.equals("initial-property-set")) { + if (initialPropertySetFound) { + tooManyNodesError(loc, "fo:initial-property-set"); + } else if (blockOrInlineItemFound) { + nodesOutOfOrderError(loc, "fo:initial-property-set", + "(#PCDATA|%inline;|%block;)"); + } else { + initialPropertySetFound = true; + } + } else if (isBlockOrInlineItem(nsURI, localName)) { + blockOrInlineItemFound = true; } else { - initialPropertySetFound = true; + invalidChildError(loc, nsURI, localName); } - } else if (isBlockOrInlineItem(nsURI, localName)) { - blockOrInlineItemFound = true; - } else { - invalidChildError(loc, nsURI, localName); } } diff --git a/src/java/org/apache/fop/fo/pagination/Root.java b/src/java/org/apache/fop/fo/pagination/Root.java index 47ee3096a..23f9cd391 100644 --- a/src/java/org/apache/fop/fo/pagination/Root.java +++ b/src/java/org/apache/fop/fo/pagination/Root.java @@ -21,6 +21,7 @@ package org.apache.fop.fo.pagination; // java import java.util.List; +import java.util.ArrayList; import org.xml.sax.Locator; @@ -31,6 +32,7 @@ import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.pagination.bookmarks.BookmarkTree; +import org.apache.fop.fo.extensions.destination.Destination; /** * The fo:root formatting object. Contains page masters, page-sequences. @@ -43,6 +45,7 @@ public class Root extends FObj { private LayoutMasterSet layoutMasterSet; private Declarations declarations; private BookmarkTree bookmarkTree = null; + private ArrayList destinationList; private List pageSequences; // temporary until above list populated @@ -64,7 +67,7 @@ public class Root extends FObj { */ public Root(FONode parent) { super(parent); - pageSequences = new java.util.ArrayList(); + pageSequences = new ArrayList(); if (parent != null) { //throw new FOPException("root must be root element"); } @@ -252,6 +255,25 @@ public class Root extends FObj { } /** + * Add a Destination object to this FO + * @param destination the Destination object to add + */ + public void addDestination(Destination destination) { + if (destinationList == null) { + destinationList = new ArrayList(); + } + destinationList.add(destination); + } + + /** + * Public accessor for the list of Destination objects for this FO + * @return the Destination object + */ + public ArrayList getDestinationList() { + return destinationList; + } + + /** * Public accessor for the BookmarkTree object for this FO * @return the BookmarkTree object */ diff --git a/src/java/org/apache/fop/image/JpegImage.java b/src/java/org/apache/fop/image/JpegImage.java index 820a728f9..3ad745281 100644 --- a/src/java/org/apache/fop/image/JpegImage.java +++ b/src/java/org/apache/fop/image/JpegImage.java @@ -189,15 +189,20 @@ public class JpegImage extends AbstractFopImage { } try { iccProfile = ICC_Profile.getInstance(iccStream.toByteArray()); - } catch (Exception e) { - log.error("Invalid ICC profile: " + e, e); - return false; + } catch (IllegalArgumentException iae) { + log.warn("An ICC profile is present but it is invalid (" + + iae.getMessage() + "). The color profile will be ignored. (" + + this.getOriginalURI() + ")"); } } else if (this.colorSpace == null) { log.error("ColorSpace not specified for JPEG image"); return false; } if (hasAPPEMarker && this.colorSpace.getType() == ColorSpace.TYPE_CMYK) { + if (log.isDebugEnabled()) { + log.debug("JPEG has an Adobe APPE marker. Note: CMYK Image will be inverted. (" + + this.getOriginalURI() + ")"); + } this.invertImage = true; } return true; diff --git a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java index e63345f77..117893304 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java @@ -34,6 +34,7 @@ import org.apache.fop.area.Trait; import org.apache.fop.fo.FONode; import org.apache.fop.fo.flow.BlockContainer; import org.apache.fop.fo.properties.CommonAbsolutePosition; +import org.apache.fop.layoutmgr.inline.InlineLayoutManager; import org.apache.fop.area.CTM; import org.apache.fop.datatypes.FODimension; import org.apache.fop.datatypes.Length; @@ -989,9 +990,12 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager */ public boolean mustKeepTogether() { //TODO Keeps will have to be more sophisticated sooner or later - return ((BlockLevelLayoutManager)getParent()).mustKeepTogether() - || !getBlockContainerFO().getKeepTogether().getWithinPage().isAuto() - || !getBlockContainerFO().getKeepTogether().getWithinColumn().isAuto(); + return (!getBlockContainerFO().getKeepTogether().getWithinPage().isAuto() + || !getBlockContainerFO().getKeepTogether().getWithinColumn().isAuto() + || (getParent() instanceof BlockLevelLayoutManager + && ((BlockLevelLayoutManager) getParent()).mustKeepTogether()) + || (getParent() instanceof InlineLayoutManager + && ((InlineLayoutManager) getParent()).mustKeepTogether())); } /** diff --git a/src/java/org/apache/fop/layoutmgr/KnuthPossPosIter.java b/src/java/org/apache/fop/layoutmgr/KnuthPossPosIter.java index a3b48c26a..9553959b0 100644 --- a/src/java/org/apache/fop/layoutmgr/KnuthPossPosIter.java +++ b/src/java/org/apache/fop/layoutmgr/KnuthPossPosIter.java @@ -28,8 +28,8 @@ public class KnuthPossPosIter extends PositionIterator { /** * Main constructor * @param elementList List of Knuth elements - * @param startPos starting position - * @param endPos ending position + * @param startPos starting position, inclusive + * @param endPos ending position, exclusive */ public KnuthPossPosIter(List elementList, int startPos, int endPos) { super(elementList.listIterator(startPos)); diff --git a/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java b/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java index db4663df6..de779b011 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java @@ -28,6 +28,7 @@ import java.util.Iterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.fo.FOElementMapping; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FOText; import org.apache.fop.fo.FObjMixed; @@ -85,6 +86,8 @@ import org.apache.fop.layoutmgr.list.ListItemLayoutManager; import org.apache.fop.layoutmgr.table.TableLayoutManager; import org.apache.fop.util.CharUtilities; +import org.apache.fop.fo.extensions.destination.Destination; + /** * The default LayoutManager maker class */ @@ -146,7 +149,13 @@ public class LayoutManagerMapping implements LayoutManagerMaker { public void makeLayoutManagers(FONode node, List lms) { Maker maker = (Maker) makers.get(node.getClass()); if (maker == null) { - log.error("No LayoutManager maker for class " + node.getClass()); + if (FOElementMapping.URI.equals(node.getNamespaceURI())) { + log.error("No LayoutManager maker for class " + node.getClass()); + } else { + if (log.isDebugEnabled()) { + log.debug("Skipping the creation of a layout manager for " + node.getClass()); + } + } } else { maker.make(node, lms); } diff --git a/src/java/org/apache/fop/layoutmgr/SpaceResolver.java b/src/java/org/apache/fop/layoutmgr/SpaceResolver.java index 9fd7c9f21..c59911bf1 100644 --- a/src/java/org/apache/fop/layoutmgr/SpaceResolver.java +++ b/src/java/org/apache/fop/layoutmgr/SpaceResolver.java @@ -57,7 +57,7 @@ public class SpaceResolver { * @param isFirst Resolution at the beginning of a (full) element list * @param isLast Resolution at the end of a (full) element list */ - public SpaceResolver(List first, BreakElement breakPoss, List second, + private SpaceResolver(List first, BreakElement breakPoss, List second, boolean isFirst, boolean isLast) { this.isFirst = isFirst; this.isLast = isLast; @@ -494,7 +494,7 @@ public class SpaceResolver { * Position class for break possibilities. It is used to notify layout manager about the * effective spaces and conditional lengths. */ - public class SpaceHandlingBreakPosition extends Position { + public static class SpaceHandlingBreakPosition extends Position { private SpaceResolver resolver; private Position originalPosition; @@ -566,7 +566,7 @@ public class SpaceResolver { * Position class for no-break situations. It is used to notify layout manager about the * effective spaces and conditional lengths. */ - public class SpaceHandlingPosition extends Position { + public static class SpaceHandlingPosition extends Position { private SpaceResolver resolver; diff --git a/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java b/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java index b7f29a744..eb91f7175 100644 --- a/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java +++ b/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java @@ -221,8 +221,14 @@ public class ColumnSetup { public int getXOffset(int col, PercentBaseContext context) { int xoffset = 0; for (int i = col; --i >= 0;) { - if (colWidths.get(i) != null) { - xoffset += ((Length) colWidths.get(i)).getValue(context); + int effCol; + if (i < colWidths.size()) { + effCol = i; + } else { + effCol = colWidths.size() - 1; + } + if (colWidths.get(effCol) != null) { + xoffset += ((Length) colWidths.get(effCol)).getValue(context); } } return xoffset; diff --git a/src/java/org/apache/fop/layoutmgr/table/GridUnit.java b/src/java/org/apache/fop/layoutmgr/table/GridUnit.java index b472acf49..beb41f35b 100644 --- a/src/java/org/apache/fop/layoutmgr/table/GridUnit.java +++ b/src/java/org/apache/fop/layoutmgr/table/GridUnit.java @@ -345,11 +345,24 @@ public class GridUnit { buffer.append("GridUnit:"); if (colSpanIndex > 0) { buffer.append(" colSpan=").append(colSpanIndex); + if (isLastGridUnitColSpan()) { + buffer.append("(last)"); + } } if (rowSpanIndex > 0) { buffer.append(" rowSpan=").append(rowSpanIndex); + if (isLastGridUnitRowSpan()) { + buffer.append("(last)"); + } } buffer.append(" startCol=").append(startCol); + if (!isPrimary() && getPrimary() != null) { + buffer.append(" primary=").append(getPrimary().getStartRow()); + buffer.append("/").append(getPrimary().getStartCol()); + if (getPrimary().getCell() != null) { + buffer.append(" id=" + getPrimary().getCell().getId()); + } + } buffer.append(" flags=").append(Integer.toBinaryString(flags)); return buffer.toString(); } diff --git a/src/java/org/apache/fop/layoutmgr/table/RowPainter.java b/src/java/org/apache/fop/layoutmgr/table/RowPainter.java index d8c294a07..4eac98953 100644 --- a/src/java/org/apache/fop/layoutmgr/table/RowPainter.java +++ b/src/java/org/apache/fop/layoutmgr/table/RowPainter.java @@ -42,12 +42,16 @@ class RowPainter { /** Currently handled row (= last encountered row). */ private EffRow lastRow = null; private LayoutContext layoutContext; - private int lastRowHeight = 0; /** * For each part of the table (header, footer, body), index of the first row of that * part present on the current page. */ private int[] firstRow = new int[3]; + /** + * Keeps track of the y-offsets of each row on a page (for body, header and footer separately). + * This is particularly needed for spanned cells where you need to know the y-offset + * of the starting row when the area is generated at the time the cell is closed. + */ private Map[] rowOffsets = new Map[] {new java.util.HashMap(), new java.util.HashMap(), new java.util.HashMap()}; @@ -66,9 +70,9 @@ class RowPainter { private int[] end; /** * Length, for each column, of the elements from the current cell put on the - * current page. + * current page. This is the corresponding area's bpd. */ - private int[] partLength; + private int[] partBPD; private TableContentLayoutManager tclm; public RowPainter(TableContentLayoutManager tclm, LayoutContext layoutContext) { @@ -78,7 +82,7 @@ class RowPainter { this.primaryGridUnits = new PrimaryGridUnit[colCount]; this.start = new int[colCount]; this.end = new int[colCount]; - this.partLength = new int[colCount]; + this.partBPD = new int[colCount]; Arrays.fill(firstRow, -1); Arrays.fill(end, -1); } @@ -87,12 +91,9 @@ class RowPainter { return this.accumulatedBPD; } - public void notifyEndOfSequence() { - this.accumulatedBPD += lastRowHeight; //for last row - } - public void notifyNestedPenaltyArea(int length) { - this.lastRowHeight += length; + yoffset += length; + accumulatedBPD += length; } /** @@ -104,8 +105,6 @@ class RowPainter { public void handleTableContentPosition(TableContentPosition tcpos) { if (lastRow != tcpos.row && lastRow != null) { addAreasAndFlushRow(false); - yoffset += lastRowHeight; - this.accumulatedBPD += lastRowHeight; } if (log.isDebugEnabled()) { log.debug("===handleTableContentPosition(" + tcpos); @@ -144,12 +143,12 @@ class RowPainter { * lying on the current page must be drawn. * * @param forcedFlush true if the elements must be drawn even if the row isn't - * finished yet (last row on the page) + * finished yet (last row on the page), or if the row is the last of the current table + * part * @return the height of the (grid) row */ public int addAreasAndFlushRow(boolean forcedFlush) { int actualRowHeight = 0; - int readyCount = 0; int bt = lastRow.getBodyType(); if (log.isDebugEnabled()) { @@ -160,117 +159,142 @@ class RowPainter { for (int i = 0; i < primaryGridUnits.length; i++) { if ((primaryGridUnits[i] != null) && (forcedFlush || (end[i] == primaryGridUnits[i].getElements().size() - 1))) { - if (log.isTraceEnabled()) { - log.trace("getting len for " + i + " " - + start[i] + "-" + end[i]); - } - readyCount++; - int len = ElementListUtils.calcContentLength( - primaryGridUnits[i].getElements(), start[i], end[i]); - partLength[i] = len; - if (log.isTraceEnabled()) { - log.trace("len of part: " + len); - } - - if (start[i] == 0) { - LengthRangeProperty bpd = primaryGridUnits[i].getCell() - .getBlockProgressionDimension(); - if (!bpd.getMinimum(tclm.getTableLM()).isAuto()) { - int min = bpd.getMinimum(tclm.getTableLM()) - .getLength().getValue(tclm.getTableLM()); - if (min > 0) { - len = Math.max(len, min); - } - } - if (!bpd.getOptimum(tclm.getTableLM()).isAuto()) { - int opt = bpd.getOptimum(tclm.getTableLM()) - .getLength().getValue(tclm.getTableLM()); - if (opt > 0) { - len = Math.max(len, opt); - } - } - if (primaryGridUnits[i].getRow() != null) { - bpd = primaryGridUnits[i].getRow().getBlockProgressionDimension(); - if (!bpd.getMinimum(tclm.getTableLM()).isAuto()) { - int min = bpd.getMinimum(tclm.getTableLM()).getLength() - .getValue(tclm.getTableLM()); - if (min > 0) { - len = Math.max(len, min); - } - } - } - } - - // Add the padding if any - len += primaryGridUnits[i].getBorders() - .getPaddingBefore(false, primaryGridUnits[i].getCellLM()); - len += primaryGridUnits[i].getBorders() - .getPaddingAfter(false, primaryGridUnits[i].getCellLM()); - - //Now add the borders to the contentLength - if (tclm.isSeparateBorderModel()) { - len += primaryGridUnits[i].getBorders().getBorderBeforeWidth(false); - len += primaryGridUnits[i].getBorders().getBorderAfterWidth(false); - } else { - len += primaryGridUnits[i].getHalfMaxBeforeBorderWidth(); - len += primaryGridUnits[i].getHalfMaxAfterBorderWidth(); - } - int startRow = Math.max(primaryGridUnits[i].getStartRow(), firstRow[bt]); - Integer storedOffset = (Integer)rowOffsets[bt].get(new Integer(startRow)); - int effYOffset; - if (storedOffset != null) { - effYOffset = storedOffset.intValue(); - } else { - effYOffset = yoffset; - } - len -= yoffset - effYOffset; - actualRowHeight = Math.max(actualRowHeight, len); + actualRowHeight = Math.max(actualRowHeight, computeSpanHeight( + primaryGridUnits[i], start[i], end[i], i, bt)); } } - if (readyCount == 0) { - return 0; - } actualRowHeight += 2 * tclm.getTableLM().getHalfBorderSeparationBPD(); - lastRowHeight = actualRowHeight; //Add areas for row tclm.addRowBackgroundArea(rowFO, actualRowHeight, layoutContext.getRefIPD(), yoffset); for (int i = 0; i < primaryGridUnits.length; i++) { GridUnit currentGU = lastRow.safelyGetGridUnit(i); - if ((primaryGridUnits[i] != null) - && (forcedFlush || (end[i] == primaryGridUnits[i].getElements().size() - 1) - && (currentGU == null || currentGU.isLastGridUnitRowSpan())) - || (primaryGridUnits[i] == null && currentGU != null)) { - //the last line in the "if" above is to avoid a premature end of an - //row-spanned cell because no GridUnitParts are generated after a cell is - //finished with its content. currentGU can be null if there's no grid unit - //at this place in the current row (empty cell and no borders to process) - if (log.isDebugEnabled()) { - log.debug((forcedFlush ? "FORCED " : "") + "flushing..." + i + " " - + start[i] + "-" + end[i]); - } - PrimaryGridUnit gu = primaryGridUnits[i]; - if (gu == null - && !currentGU.isEmpty() - && currentGU.getColSpanIndex() == 0 - && currentGU.isLastGridUnitColSpan() - && (forcedFlush || currentGU.isLastGridUnitRowSpan())) { - gu = currentGU.getPrimary(); - } - if (gu != null) { - addAreasForCell(gu, start[i], end[i], - lastRow, - partLength[i], actualRowHeight); + //currentGU can be null if there's no grid unit + //at this place in the current row (empty cell and no borders to process) + + if (primaryGridUnits[i] != null) { + if (forcedFlush || ((end[i] == primaryGridUnits[i].getElements().size() - 1) + && (currentGU == null || currentGU.isLastGridUnitRowSpan()))) { + //the last line in the "if" above is to avoid a premature end of a + //row-spanned cell because no GridUnitParts are generated after a cell is + //finished with its content. + //See table-cell_number-rows-spanned_bug38397.xml + addAreasForCell(primaryGridUnits[i], start[i], end[i], lastRow, partBPD[i], + actualRowHeight); primaryGridUnits[i] = null; start[i] = 0; end[i] = -1; - partLength[i] = 0; + partBPD[i] = 0; } + } else if (currentGU != null && !currentGU.isEmpty() + && currentGU.getColSpanIndex() == 0 + && (forcedFlush || currentGU.isLastGridUnitRowSpan())) { + //A row-spanned cell has finished contributing content on the previous page + //and now still has to cause grid units to be painted. + //See table-cell_page-break_span.xml + addAreasForCell(currentGU.getPrimary(), start[i], end[i], lastRow, partBPD[i], + actualRowHeight); + start[i] = 0; + end[i] = -1; + partBPD[i] = 0; } } + yoffset += actualRowHeight; + accumulatedBPD += actualRowHeight; + if (forcedFlush) { + // Either the end of the page is reached, then this was the last call of this + // method and we no longer care about lastRow; or the end of a table-part + // (header, footer, body) has been reached, and the next row will anyway be + // different from the current one, and this is unnecessary to recall this + // method in the first lines of handleTableContentPosition, so we may reset + // lastRow + lastRow = null; + } return actualRowHeight; } + /** + * Computes the total height of the part of the given cell spanning on the current + * active row, including borders and paddings. The bpd is also stored in partBPD, and + * it is ensured that the cell's or row's explicit height is respected. yoffset is + * updated accordingly. + * + * @param pgu primary grid unit corresponding to the cell + * @param start index of the first element of the cell occuring on the current page + * @param end index of the last element of the cell occuring on the current page + * @param columnIndex column index of the cell + * @param bodyType {@link TableRowIterator#HEADER}, {@link TableRowIterator#FOOTER}, or + * {@link TableRowIterator#BODY} + * @return the cell's height + */ + private int computeSpanHeight(PrimaryGridUnit pgu, int start, int end, int columnIndex, + int bodyType) { + if (log.isTraceEnabled()) { + log.trace("getting len for " + columnIndex + " " + + start + "-" + end); + } + int len = ElementListUtils.calcContentLength( + pgu.getElements(), start, end); + partBPD[columnIndex] = len; + if (log.isTraceEnabled()) { + log.trace("len of part: " + len); + } + + if (start == 0) { + LengthRangeProperty bpd = pgu.getCell() + .getBlockProgressionDimension(); + if (!bpd.getMinimum(tclm.getTableLM()).isAuto()) { + int min = bpd.getMinimum(tclm.getTableLM()) + .getLength().getValue(tclm.getTableLM()); + if (min > 0) { + len = Math.max(len, min); + } + } + if (!bpd.getOptimum(tclm.getTableLM()).isAuto()) { + int opt = bpd.getOptimum(tclm.getTableLM()) + .getLength().getValue(tclm.getTableLM()); + if (opt > 0) { + len = Math.max(len, opt); + } + } + if (pgu.getRow() != null) { + bpd = pgu.getRow().getBlockProgressionDimension(); + if (!bpd.getMinimum(tclm.getTableLM()).isAuto()) { + int min = bpd.getMinimum(tclm.getTableLM()).getLength() + .getValue(tclm.getTableLM()); + if (min > 0) { + len = Math.max(len, min); + } + } + } + } + + // Add the padding if any + len += pgu.getBorders() + .getPaddingBefore(false, pgu.getCellLM()); + len += pgu.getBorders() + .getPaddingAfter(false, pgu.getCellLM()); + + //Now add the borders to the contentLength + if (tclm.isSeparateBorderModel()) { + len += pgu.getBorders().getBorderBeforeWidth(false); + len += pgu.getBorders().getBorderAfterWidth(false); + } else { + len += pgu.getHalfMaxBeforeBorderWidth(); + len += pgu.getHalfMaxAfterBorderWidth(); + } + int startRow = Math.max(pgu.getStartRow(), firstRow[bodyType]); + Integer storedOffset = (Integer)rowOffsets[bodyType].get(new Integer(startRow)); + int effYOffset; + if (storedOffset != null) { + effYOffset = storedOffset.intValue(); + } else { + effYOffset = yoffset; + } + len -= yoffset - effYOffset; + return len; + } + private void addAreasForCell(PrimaryGridUnit pgu, int startPos, int endPos, EffRow row, int contentHeight, int rowHeight) { int bt = row.getBodyType(); @@ -278,12 +302,33 @@ class RowPainter { firstRow[bt] = row.getIndex(); } //Determine the first row in this sequence - int startRow = Math.max(pgu.getStartRow(), firstRow[bt]); + int startRowIndex = Math.max(pgu.getStartRow(), firstRow[bt]); + int lastRowIndex = lastRow.getIndex(); + + // In collapsing-border model, if the cell spans over several columns/rows then + // dedicated areas will be created for each grid unit to hold the corresponding + // borders. For that we need to know the height of each grid unit, that is of each + // grid row spanned over by the cell + int[] spannedGridRowHeights = null; + if (!tclm.getTableLM().getTable().isSeparateBorderModel() && pgu.hasSpanning()) { + spannedGridRowHeights = new int[lastRowIndex - startRowIndex + 1]; + int prevOffset = ((Integer)rowOffsets[bt].get(new Integer(startRowIndex))).intValue(); + for (int i = 0; i < lastRowIndex - startRowIndex; i++) { + int newOffset = ((Integer) rowOffsets[bt].get(new Integer(startRowIndex + i + 1))) + .intValue(); + spannedGridRowHeights[i] = newOffset - prevOffset; + prevOffset = newOffset; + } + spannedGridRowHeights[lastRowIndex - startRowIndex] = rowHeight; + } + //Determine y offset for the cell - Integer offset = (Integer)rowOffsets[bt].get(new Integer(startRow)); + Integer offset = (Integer)rowOffsets[bt].get(new Integer(startRowIndex)); while (offset == null) { - startRow--; - offset = (Integer)rowOffsets[bt].get(new Integer(startRow)); + //TODO Figure out what this does and when it's triggered + //This block is probably never used, at least it's not triggered by any of our tests + startRowIndex--; + offset = (Integer)rowOffsets[bt].get(new Integer(startRowIndex)); } int effYOffset = offset.intValue(); int effCellHeight = rowHeight; @@ -306,7 +351,8 @@ class RowPainter { SpaceResolver.performConditionalsNotification(pgu.getElements(), startPos, endPos, prevBreak); } - cellLM.addAreas(new KnuthPossPosIter(pgu.getElements(), - startPos, endPos + 1), layoutContext); + cellLM.addAreas(new KnuthPossPosIter(pgu.getElements(), startPos, endPos + 1), + layoutContext, spannedGridRowHeights, startRowIndex - pgu.getStartRow(), + lastRowIndex - pgu.getStartRow() + 1); } } diff --git a/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java index 223d80a7f..2f17a6a25 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java @@ -16,13 +16,14 @@ */ /* $Id$ */ - + package org.apache.fop.layoutmgr.table; import java.util.LinkedList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.fo.FONode; import org.apache.fop.fo.flow.Table; import org.apache.fop.fo.flow.TableCell; @@ -48,20 +49,20 @@ import org.apache.fop.traits.MinOptMax; * LayoutManager for a table-cell FO. * A cell contains blocks. These blocks fill the cell. */ -public class TableCellLayoutManager extends BlockStackingLayoutManager +public class TableCellLayoutManager extends BlockStackingLayoutManager implements BlockLevelLayoutManager { /** * logging instance */ private static Log log = LogFactory.getLog(TableCellLayoutManager.class); - - private PrimaryGridUnit gridUnit; - + + private PrimaryGridUnit primaryGridUnit; + private Block curBlockArea; private int inRowIPDOffset; - + private int xoffset; private int yoffset; private int cellIPD; @@ -75,23 +76,23 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager /** * Create a new Cell layout manager. * @param node table-cell FO for which to create the LM - * @param pgu primary grid unit for the cell + * @param pgu primary grid unit for the cell */ public TableCellLayoutManager(TableCell node, PrimaryGridUnit pgu) { super(node); fobj = node; - this.gridUnit = pgu; + this.primaryGridUnit = pgu; } /** @return the table-cell FO */ public TableCell getTableCell() { return (TableCell)this.fobj; } - + private boolean isSeparateBorderModel() { return getTable().isSeparateBorderModel(); } - + /** @see org.apache.fop.layoutmgr.LayoutManager#initialize() */ public void initialize() { borderAndPaddingBPD = 0; @@ -107,7 +108,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager borderAndPaddingBPD += getTableCell().getCommonBorderPaddingBackground() .getPaddingAfter(false, this); } - + /** * @return the table owning this cell */ @@ -118,12 +119,12 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager } return (Table)node; } - + /** @see org.apache.fop.layoutmgr.BlockStackingLayoutManager#getIPIndents() */ protected int getIPIndents() { int iIndents = 0; - int[] startEndBorderWidths = gridUnit.getStartEndBorderWidths(); + int[] startEndBorderWidths = primaryGridUnit.getStartEndBorderWidths(); startBorderWidth += startEndBorderWidths[0]; endBorderWidth += startEndBorderWidths[1]; iIndents += startBorderWidth; @@ -135,14 +136,14 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager iIndents += getTableCell().getCommonBorderPaddingBackground().getPaddingEnd(false, this); return iIndents; } - + /** * @see org.apache.fop.layoutmgr.LayoutManager */ public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { MinOptMax stackLimit = new MinOptMax(context.getStackLimit()); - referenceIPD = context.getRefIPD(); + referenceIPD = context.getRefIPD(); cellIPD = referenceIPD; cellIPD -= getIPIndents(); if (isSeparateBorderModel()) { @@ -173,7 +174,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING); childLC.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING, false); } - + if (returnedList.size() == 1 && ((ListElement)returnedList.getFirst()).isForcedBreak()) { // a descendant of this block has break-before @@ -193,13 +194,13 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager //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() + if (mustKeepTogether() || context.isKeepWithNextPending() || childLC.isKeepWithPreviousPending()) { //Clear keep pending flag @@ -242,7 +243,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager //Space resolution SpaceResolver.resolveElementList(returnList); - + return returnList; } } @@ -256,16 +257,16 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager returnedList = new LinkedList(); wrapPositionElements(contentList, returnList); - + //Space resolution SpaceResolver.resolveElementList(returnList); - + getPSLM().notifyEndOfLayout(((TableCell)getFObj()).getId()); - + setFinished(true); return returnList; } - + /** * Set the y offset of this cell. * This offset is used to set the absolute position of the cell. @@ -294,7 +295,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager public void setInRowIPDOffset(int off) { this.inRowIPDOffset = off; } - + /** * Set the content height for this cell. This method is used during * addAreas() stage. @@ -304,7 +305,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager public void setContentHeight(int h) { usedBPD = h; } - + /** * Set the row height that contains this cell. This method is used during * addAreas() stage. @@ -328,96 +329,107 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager } else { bpd -= gu.getPrimary().getHalfMaxBorderWidth(); } - CommonBorderPaddingBackground cbpb - = gu.getCell().getCommonBorderPaddingBackground(); + CommonBorderPaddingBackground cbpb + = gu.getCell().getCommonBorderPaddingBackground(); bpd -= cbpb.getPaddingBefore(false, this); bpd -= cbpb.getPaddingAfter(false, this); bpd -= 2 * ((TableLayoutManager)getParent()).getHalfBorderSeparationBPD(); return bpd; } - + /** - * Add the areas for the break points. - * The cell contains block stacking layout managers - * that add block areas. - * + * Add the areas for the break points. The cell contains block stacking layout + * managers that add block areas. + * + * <p>In the collapsing-border model, the borders of a cell that spans over several + * rows or columns are drawn separately for each grid unit. Therefore we must know the + * height of each grid row spanned over by the cell. Also, if the cell is broken over + * two pages we must know which spanned grid rows are present on the current page.</p> + * * @param parentIter the iterator of the break positions * @param layoutContext the layout context for adding the areas + * @param spannedGridRowHeights in collapsing-border model for a spanning cell, height + * of each spanned grid row + * @param startRow first grid row on the current page spanned over by the cell, + * inclusive + * @param endRow last grid row on the current page spanned over by the cell, exclusive */ public void addAreas(PositionIterator parentIter, - LayoutContext layoutContext) { + LayoutContext layoutContext, + int[] spannedGridRowHeights, + int startRow, + int endRow) { getParentArea(null); getPSLM().addIDToPage(getTableCell().getId()); if (isSeparateBorderModel()) { if (!emptyCell || getTableCell().showEmptyCells()) { - TraitSetter.addBorders(curBlockArea, - getTableCell().getCommonBorderPaddingBackground(), this); + TraitSetter.addBorders(curBlockArea, getTableCell().getCommonBorderPaddingBackground(), + false, false, false, false, this); + TraitSetter.addPadding(curBlockArea, getTableCell().getCommonBorderPaddingBackground(), + false, false, false, false, this); } } else { - boolean[] outer = new boolean[] { - gridUnit.getFlag(GridUnit.FIRST_IN_TABLE), - gridUnit.getFlag(GridUnit.LAST_IN_TABLE), - gridUnit.getFlag(GridUnit.IN_FIRST_COLUMN), - gridUnit.getFlag(GridUnit.IN_LAST_COLUMN)}; - if (!gridUnit.hasSpanning()) { + if (!primaryGridUnit.hasSpanning()) { //Can set the borders directly if there's no span - TraitSetter.addCollapsingBorders(curBlockArea, - gridUnit.getBorders(), outer, this); + boolean[] outer = new boolean[] { + primaryGridUnit.getFlag(GridUnit.FIRST_IN_TABLE), + primaryGridUnit.getFlag(GridUnit.LAST_IN_TABLE), + primaryGridUnit.getFlag(GridUnit.IN_FIRST_COLUMN), + primaryGridUnit.getFlag(GridUnit.IN_LAST_COLUMN)}; + TraitSetter.addCollapsingBorders(curBlockArea, + primaryGridUnit.getBorders(), outer, this); } else { + boolean[] outer = new boolean[4]; int dy = yoffset; - for (int y = 0; y < gridUnit.getRows().size(); y++) { - GridUnit[] gridUnits = (GridUnit[])gridUnit.getRows().get(y); + for (int y = startRow; y < endRow; y++) { + GridUnit[] gridUnits = (GridUnit[])primaryGridUnit.getRows().get(y); int dx = xoffset; - int lastRowHeight = 0; for (int x = 0; x < gridUnits.length; x++) { GridUnit gu = gridUnits[x]; if (!gu.hasBorders()) { continue; } - + //Blocks for painting grid unit borders Block block = new Block(); block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); block.setPositioning(Block.ABSOLUTE); - int bpd = getContentHeight(gu); - if (isSeparateBorderModel()) { - bpd += (gu.getBorders().getBorderBeforeWidth(false)); - bpd += (gu.getBorders().getBorderAfterWidth(false)); - } else { - bpd += gridUnit.getHalfMaxBeforeBorderWidth() - - (gu.getBorders().getBorderBeforeWidth(false) / 2); - bpd += gridUnit.getHalfMaxAfterBorderWidth() - - (gu.getBorders().getBorderAfterWidth(false) / 2); - } + int bpd = spannedGridRowHeights[y - startRow]; + bpd -= gu.getBorders().getBorderBeforeWidth(false) / 2; + bpd -= gu.getBorders().getBorderAfterWidth(false) / 2; block.setBPD(bpd); - lastRowHeight = rowHeight; - int ipd = gu.getColumn().getColumnWidth().getValue(this); - int borderStartWidth = gu.getBorders().getBorderStartWidth(false) / 2; + 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); - int halfCollapsingBorderHeight = 0; - if (!isSeparateBorderModel()) { - halfCollapsingBorderHeight - += gu.getBorders().getBorderBeforeWidth(false) / 2; - } - block.setYOffset(dy - halfCollapsingBorderHeight); + 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(this); + dx += gu.getColumn().getColumnWidth().getValue( + (PercentBaseContext) getParent()); } - dy += lastRowHeight; + dy += spannedGridRowHeights[y - startRow]; } - log.warn("TODO Add collapsed border painting for spanned cells"); } } //Handle display-align - int contentBPD = getContentHeight(gridUnit); + int contentBPD = getContentHeight(primaryGridUnit); if (usedBPD < contentBPD) { if (getTableCell().getDisplayAlign() == EN_CENTER) { Block space = new Block(); @@ -431,7 +443,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager } AreaAdditionUtil.addAreas(this, parentIter, layoutContext); - + curBlockArea.setBPD(contentBPD); // Add background after we know the BPD @@ -446,7 +458,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager getTableCell().getCommonBorderPaddingBackground(), this); } - + flush(); curBlockArea = null; @@ -471,8 +483,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager curBlockArea.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); TraitSetter.setProducerID(curBlockArea, getTableCell().getId()); curBlockArea.setPositioning(Block.ABSOLUTE); - int indent = 0; - indent += startBorderWidth; + int indent = startBorderWidth; if (!isSeparateBorderModel()) { indent /= 2; } @@ -481,18 +492,18 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager // set position int borderAdjust = 0; if (!isSeparateBorderModel()) { - if (gridUnit.hasSpanning()) { - borderAdjust -= gridUnit.getHalfMaxBeforeBorderWidth(); + if (primaryGridUnit.hasSpanning()) { + borderAdjust -= primaryGridUnit.getHalfMaxBeforeBorderWidth(); } else { - borderAdjust += gridUnit.getHalfMaxBeforeBorderWidth(); + borderAdjust += primaryGridUnit.getHalfMaxBeforeBorderWidth(); } } else { - //borderAdjust += gridUnit.getBorders().getBorderBeforeWidth(false); + //borderAdjust += primaryGridUnit.getBorders().getBorderBeforeWidth(false); } TableLayoutManager tableLM = (TableLayoutManager)getParent(); - curBlockArea.setXOffset(xoffset + inRowIPDOffset + curBlockArea.setXOffset(xoffset + inRowIPDOffset + tableLM.getHalfBorderSeparationIPD() + indent); - curBlockArea.setYOffset(yoffset - borderAdjust + curBlockArea.setYOffset(yoffset - borderAdjust + tableLM.getHalfBorderSeparationBPD()); curBlockArea.setIPD(cellIPD); //curBlockArea.setHeight(); @@ -548,8 +559,8 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager public boolean mustKeepTogether() { //TODO Keeps will have to be more sophisticated sooner or later boolean keep = ((BlockLevelLayoutManager)getParent()).mustKeepTogether(); - if (gridUnit.getRow() != null) { - keep |= gridUnit.getRow().mustKeepTogether(); + if (primaryGridUnit.getRow() != null) { + keep |= primaryGridUnit.getRow().mustKeepTogether(); } return keep; } @@ -575,9 +586,9 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager || !fobj.getKeepWithNext().getWithinColumn().isAuto(); */ } - + // --------- Property Resolution related functions --------- // - + /** * Returns the IPD of the content area * @return the IPD of the content area @@ -585,7 +596,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager public int getContentAreaIPD() { return cellIPD; } - + /** * Returns the BPD of the content area * @return the BPD of the content area @@ -598,14 +609,14 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager return -1; } } - + /** * @see org.apache.fop.layoutmgr.LayoutManager#getGeneratesReferenceArea */ public boolean getGeneratesReferenceArea() { return true; } - + /** * @see org.apache.fop.layoutmgr.LayoutManager#getGeneratesBlockArea */ diff --git a/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java index f9d7c6397..c3e1a41da 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java @@ -235,6 +235,12 @@ public class TableContentLayoutManager implements PercentBaseContext { if (!isSeparateBorderModel()) { resolveNormalBeforeAfterBordersForRowGroup(rowGroup, iter); } + + //Reset keep-with-next when remaining inside the table. + //The context flag is only used to propagate keep-with-next to the outside. + //The clearing is ok here because createElementsForRowGroup already handles + //the keep when inside a table. + context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING, false); //Element list creation createElementsForRowGroup(context, alignment, bodyType, @@ -691,7 +697,6 @@ public class TableContentLayoutManager implements PercentBaseContext { iterateAndPaintPositions(nestedIter, painter); } - painter.notifyEndOfSequence(); this.usedBPD += painter.getAccumulatedBPD(); if (markers != null) { @@ -701,15 +706,16 @@ public class TableContentLayoutManager implements PercentBaseContext { } /** - * Iterates over a part of the table and paints the related elements. + * Iterates over a part of the table (header, footer, body) and paints the related + * elements. * - * @param iterator iterator over the table's header, body or footer elements + * @param iterator iterator over Position elements. Those positions correspond to the + * elements of the table present on the current page * @param painter */ private void iterateAndPaintPositions(Iterator iterator, RowPainter painter) { List lst = new java.util.ArrayList(); boolean firstPos = false; - boolean lastPos = false; TableBody body = null; while (iterator.hasNext()) { Position pos = (Position)iterator.next(); @@ -728,18 +734,9 @@ public class TableContentLayoutManager implements PercentBaseContext { if (tcpos.getFlag(TableContentPosition.LAST_IN_ROWGROUP) && tcpos.row.getFlag(EffRow.LAST_IN_PART)) { log.trace("LAST_IN_ROWGROUP + LAST_IN_PART"); - lastPos = true; - getTableLM().getCurrentPV().addMarkers(body.getMarkers(), - true, firstPos, lastPos); - int size = lst.size(); - for (int i = 0; i < size; i++) { - painter.handleTableContentPosition((TableContentPosition)lst.get(i)); - } - getTableLM().getCurrentPV().addMarkers(body.getMarkers(), - false, firstPos, lastPos); + handleMarkersAndPositions(lst, body, firstPos, true, painter); //reset firstPos = false; - lastPos = false; body = null; lst.clear(); } @@ -750,18 +747,26 @@ public class TableContentLayoutManager implements PercentBaseContext { } } if (body != null) { - getTableLM().getCurrentPV().addMarkers(body.getMarkers(), - true, firstPos, lastPos); - int size = lst.size(); - for (int i = 0; i < size; i++) { - painter.handleTableContentPosition((TableContentPosition)lst.get(i)); - } - getTableLM().getCurrentPV().addMarkers(body.getMarkers(), - false, firstPos, lastPos); + // Entering this block means that the end of the current table-part hasn't + // been reached (otherwise it would have been caught by the test above). So + // lastPos is necessarily false + handleMarkersAndPositions(lst, body, firstPos, false, painter); } painter.addAreasAndFlushRow(true); } - + + private void handleMarkersAndPositions(List positions, TableBody body, boolean firstPos, + boolean lastPos, RowPainter painter) { + getTableLM().getCurrentPV().addMarkers(body.getMarkers(), + true, firstPos, lastPos); + int size = positions.size(); + for (int i = 0; i < size; i++) { + painter.handleTableContentPosition((TableContentPosition)positions.get(i)); + } + getTableLM().getCurrentPV().addMarkers(body.getMarkers(), + false, firstPos, lastPos); + } + /** * Get the area for a row for background. * @param row the table-row object or null diff --git a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java index 1707ad422..00ecb01d2 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java @@ -215,10 +215,12 @@ public class TableLayoutManager extends BlockStackingLayoutManager if (getTable().isSeparateBorderModel()) { addKnuthElementsForBorderPaddingBefore(returnList, !firstVisibleMarkServed); firstVisibleMarkServed = true; + // Border and padding to be repeated at each break + // This must be done only in the separate-border model, as in collapsing + // tables have no padding and borders are determined at the cell level + addPendingMarks(context); } - //Spaces, border and padding to be repeated at each break - addPendingMarks(context); // Elements for the table-header/footer/body LinkedList contentKnuthElements = null; diff --git a/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java b/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java index 63d153584..f8cedd7de 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java @@ -477,7 +477,7 @@ public class TableRowIterator { safelySetListItem(gridUnits, colnum - 1, guSpan); if (hasRowSpanningLeft) { pendingRowSpans++; - safelySetListItem(previousRowsSpanningCells, colnum - 1, gu); + safelySetListItem(previousRowsSpanningCells, colnum - 1, guSpan); } horzSpan[j] = guSpan; } diff --git a/src/java/org/apache/fop/layoutmgr/table/TableStepper.java b/src/java/org/apache/fop/layoutmgr/table/TableStepper.java index 4459f9a3b..20b80c1bd 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableStepper.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableStepper.java @@ -46,6 +46,8 @@ public class TableStepper { private TableContentLayoutManager tclm; private EffRow[] rowGroup; + /** Number of columns in the row group. */ + private int columnCount; private int totalHeight; private int activeRowIndex; /** @@ -83,7 +85,7 @@ public class TableStepper { private boolean rowBacktrackForLastStep; private boolean skippedStep; private boolean[] keepWithNextSignals; - private boolean[] forcedBreaks; + private boolean forcedBreak; private int lastMaxPenaltyLength; /** @@ -100,6 +102,7 @@ public class TableStepper { * @param columnCount number of columns the row group has */ private void setup(int columnCount) { + this.columnCount = columnCount; this.activeRowIndex = 0; elementLists = new List[columnCount]; startRow = new int[columnCount]; @@ -112,21 +115,15 @@ public class TableStepper { borderAfter = new int[columnCount]; paddingAfter = new int[columnCount]; keepWithNextSignals = new boolean[columnCount]; - forcedBreaks = new boolean[columnCount]; Arrays.fill(end, -1); } private void clearBreakCondition() { - Arrays.fill(forcedBreaks, false); + forcedBreak = false; } private boolean isBreakCondition() { - for (int i = 0; i < forcedBreaks.length; i++) { - if (forcedBreaks[i]) { - return true; - } - } - return false; + return forcedBreak; } /** @@ -171,7 +168,7 @@ public class TableStepper { private int getMaxRemainingHeight() { int maxW = 0; if (!rowBacktrackForLastStep) { - for (int i = 0; i < widths.length; i++) { + for (int i = 0; i < columnCount; i++) { if (elementLists[i] == null) { continue; } @@ -217,7 +214,6 @@ public class TableStepper { widths[column] = 0; startRow[column] = activeRowIndex; keepWithNextSignals[column] = false; - forcedBreaks[column] = false; } else if (gu.isPrimary()) { PrimaryGridUnit pgu = (PrimaryGridUnit)gu; boolean makeBoxForWholeRow = false; @@ -263,9 +259,6 @@ public class TableStepper { widths[column] = 0; startRow[column] = activeRowIndex; keepWithNextSignals[column] = false; - forcedBreaks[column] = false; - } else { - log.trace("TableStepper.setupElementList: not empty nor primary grid unit"); } } @@ -302,7 +295,7 @@ public class TableStepper { int addedBoxLen = 0; TableContentPosition lastTCPos = null; LinkedList returnList = new LinkedList(); - while ((step = getNextStep(laststep)) >= 0) { + while ((step = getNextStep()) >= 0) { int normalRow = activeRowIndex; if (rowBacktrackForLastStep) { //Even though we've already switched to the next row, we have to @@ -316,7 +309,7 @@ public class TableStepper { //Put all involved grid units into a list List gridUnitParts = new java.util.ArrayList(maxColumnCount); - for (int i = 0; i < start.length; i++) { + for (int i = 0; i < columnCount; i++) { if (end[i] >= start[i]) { PrimaryGridUnit pgu = rowGroup[startRow[i]].getGridUnit(i).getPrimary(); if (start[i] == 0 && end[i] == 0 @@ -395,7 +388,7 @@ public class TableStepper { int p = 0; boolean allCellsHaveContributed = true; signalKeepWithNext = false; - for (int i = 0; i < start.length; i++) { + for (int i = 0; i < columnCount; i++) { if (start[i] == 0 && end[i] < 0 && elementLists[i] != null) { allCellsHaveContributed = false; } @@ -455,10 +448,9 @@ public class TableStepper { /** * Finds the smallest increment leading to the next legal break inside the row-group. * - * @param lastStep used for log only * @return the size of the increment, -1 if no next step is available (end of row-group reached) */ - private int getNextStep(int lastStep) { + private int getNextStep() { log.trace("Entering getNextStep"); this.lastMaxPenaltyLength = 0; //Check for forced break conditions @@ -467,65 +459,15 @@ public class TableStepper { return -1; }*/ - int[] backupWidths = new int[start.length]; - System.arraycopy(widths, 0, backupWidths, 0, backupWidths.length); + int[] backupWidths = new int[columnCount]; + System.arraycopy(widths, 0, backupWidths, 0, columnCount); //set starting points - // We assume that the current grid row is finished. If this is not the case this - // boolean will be reset (see below) - boolean currentGridRowFinished = true; - for (int i = 0; i < start.length; i++) { - // null element lists probably correspond to empty cells - if (elementLists[i] == null) { - continue; - } - if (end[i] < elementLists[i].size()) { - start[i] = end[i] + 1; - if (end[i] + 1 < elementLists[i].size() - && getActiveGridUnit(i).isLastGridUnitRowSpan()) { - // Ok, so this grid unit is the last in the row-spanning direction and - // there are still unhandled Knuth elements. They /will/ have to be - // put on the current grid row, which means that this row isn't - // finished yet - currentGridRowFinished = false; - } - } else { - throw new IllegalStateException("end[i] overflows elementList[i].size()"); -// start[i] = -1; //end of list reached -// end[i] = -1; - } - } - - if (currentGridRowFinished) { - if (activeRowIndex < rowGroup.length - 1) { - TableRow rowFO = getActiveRow().getTableRow(); - if (rowFO != null && rowFO.getBreakAfter() != Constants.EN_AUTO) { - log.warn(FONode.decorateWithContextInfo( - "break-after ignored on table-row because of row spanning " - + "in progress (See XSL 1.0, 7.19.1)", rowFO)); - } - activeRowIndex++; - if (log.isDebugEnabled()) { - log.debug("===> new row: " + activeRowIndex); - } - initializeElementLists(); - for (int i = 0; i < backupWidths.length; i++) { - if (end[i] < 0) { - backupWidths[i] = 0; - } - } - rowFO = getActiveRow().getTableRow(); - if (rowFO != null && rowFO.getBreakBefore() != Constants.EN_AUTO) { - log.warn(FONode.decorateWithContextInfo( - "break-before ignored on table-row because of row spanning " - + "in progress (See XSL 1.0, 7.19.2)", rowFO)); - } - } - } + goToNextRowIfCurrentFinished(backupWidths); //Get next possible sequence for each cell - int seqCount = 0; - for (int i = 0; i < start.length; i++) { + boolean stepFound = false; + for (int i = 0; i < columnCount; i++) { if (elementLists[i] == null) { continue; } @@ -536,7 +478,7 @@ public class TableStepper { this.lastMaxPenaltyLength = Math.max(this.lastMaxPenaltyLength, el.getW()); if (el.getP() <= -KnuthElement.INFINITE) { log.debug("FORCED break encountered!"); - forcedBreaks[i] = true; + forcedBreak = true; break; } else if (el.getP() < KnuthElement.INFINITE) { //First legal break point @@ -562,7 +504,7 @@ public class TableStepper { } widths[i] = backupWidths[i]; } else { - seqCount++; + stepFound = true; } //log.debug("part " + start[i] + "-" + end[i] + " " + widths[i]); if (end[i] + 1 >= elementLists[i].size()) { @@ -588,14 +530,14 @@ public class TableStepper { log.trace("column " + (i+1) + ": padding before=" + paddingBefore[i] + " after=" + paddingAfter[i]); } } - if (seqCount == 0) { + if (!stepFound) { return -1; } //Determine smallest possible step int minStep = Integer.MAX_VALUE; StringBuffer sb = new StringBuffer(); - for (int i = 0; i < widths.length; i++) { + for (int i = 0; i < columnCount; i++) { baseWidth[i] = 0; for (int prevRow = 0; prevRow < startRow[i]; prevRow++) { baseWidth[i] += rowGroup[prevRow].getHeight().opt; @@ -610,12 +552,12 @@ public class TableStepper { } } if (log.isDebugEnabled()) { - log.debug("candidate steps: " + sb + " lastStep=" + lastStep); + log.debug("candidate steps: " + sb); } //Check for constellations that would result in overlapping borders /* - for (int i = 0; i < widths.length; i++) { + for (int i = 0; i < columnCount; i++) { }*/ @@ -623,7 +565,7 @@ public class TableStepper { //See http://people.apache.org/~jeremias/fop/NextStepAlgoNotes.pdf rowBacktrackForLastStep = false; skippedStep = false; - for (int i = 0; i < widths.length; i++) { + for (int i = 0; i < columnCount; i++) { int len = baseWidth[i] + widths[i]; if (len > minStep) { widths[i] = backupWidths[i]; @@ -651,7 +593,7 @@ public class TableStepper { } if (log.isDebugEnabled()) { /*StringBuffer*/ sb = new StringBuffer("[col nb: start-end(width)] "); - for (int i = 0; i < widths.length; i++) { + for (int i = 0; i < columnCount; i++) { if (end[i] >= start[i]) { sb.append(i + ": " + start[i] + "-" + end[i] + "(" + widths[i] + "), "); } else { @@ -663,8 +605,61 @@ public class TableStepper { return minStep; } - - + + private void goToNextRowIfCurrentFinished(int[] backupWidths) { + // We assume that the current grid row is finished. If this is not the case this + // boolean will be reset (see below) + boolean currentGridRowFinished = true; + for (int i = 0; i < columnCount; i++) { + // null element lists probably correspond to empty cells + if (elementLists[i] == null) { + continue; + } + if (end[i] < elementLists[i].size()) { + start[i] = end[i] + 1; + if (end[i] + 1 < elementLists[i].size() + && getActiveGridUnit(i).isLastGridUnitRowSpan()) { + // Ok, so this grid unit is the last in the row-spanning direction and + // there are still unhandled Knuth elements. They /will/ have to be + // put on the current grid row, which means that this row isn't + // finished yet + currentGridRowFinished = false; + } + } else { + throw new IllegalStateException("end[i] overflows elementList[i].size()"); +// start[i] = -1; //end of list reached +// end[i] = -1; + } + } + + if (currentGridRowFinished) { + if (activeRowIndex < rowGroup.length - 1) { + TableRow rowFO = getActiveRow().getTableRow(); + if (rowFO != null && rowFO.getBreakAfter() != Constants.EN_AUTO) { + log.warn(FONode.decorateWithContextInfo( + "break-after ignored on table-row because of row spanning " + + "in progress (See XSL 1.0, 7.19.1)", rowFO)); + } + activeRowIndex++; + if (log.isDebugEnabled()) { + log.debug("===> new row: " + activeRowIndex); + } + initializeElementLists(); + for (int i = 0; i < columnCount; i++) { + if (end[i] < 0) { + backupWidths[i] = 0; + } + } + rowFO = getActiveRow().getTableRow(); + if (rowFO != null && rowFO.getBreakBefore() != Constants.EN_AUTO) { + log.warn(FONode.decorateWithContextInfo( + "break-before ignored on table-row because of row spanning " + + "in progress (See XSL 1.0, 7.19.2)", rowFO)); + } + } + } + } + /** @return true if the table uses the separate border model. */ private boolean isSeparateBorderModel() { return getTableLM().getTable().isSeparateBorderModel(); diff --git a/src/java/org/apache/fop/pdf/DestinationComparator.java b/src/java/org/apache/fop/pdf/DestinationComparator.java new file mode 100644 index 000000000..87d6a00ff --- /dev/null +++ b/src/java/org/apache/fop/pdf/DestinationComparator.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +import org.apache.fop.pdf.PDFDestination; + +/** + * Comparator class to enable comparing (and + * hence sorting) of PDFDestination objects. + */ +public class DestinationComparator implements java.util.Comparator { +/* public int compare (PDFDestination dest1, PDFDestination dest2) { + return dest1.getIDRef().compareTo(dest2.getIDRef()); + }*/ + + public int compare (Object obj1, Object obj2) { + if (obj1 instanceof PDFDestination && obj2 instanceof PDFDestination) { + PDFDestination dest1 = (PDFDestination)obj1; + PDFDestination dest2 = (PDFDestination)obj2; + return dest1.getIDRef().compareTo(dest2.getIDRef()); + } + return 0; + } +} diff --git a/src/java/org/apache/fop/pdf/PDFDestination.java b/src/java/org/apache/fop/pdf/PDFDestination.java new file mode 100644 index 000000000..97923e935 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFDestination.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +import org.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 + */ + private PageViewport pageViewport = null; + + /** + * create a named destination + */ + public PDFDestination(DestinationData destinationData) { + /* generic creation of PDF object */ + super(); + this.goToReference = destinationData.getGoToReference(); + this.idRef = destinationData.getIDRef(); + this.pageViewport = destinationData.getPageViewport(); + } + + /** + * @see org.apache.fop.pdf.PDFObject#toPDFString() + */ + public String toPDFString() { + String s = getObjectID() + + "<<" + + "/Limits [(" + idRef + ") (" + idRef + ")]\n" + + "/Names [(" + idRef + ") " + goToReference + "]" + + "\n>>\nendobj\n"; + return s; + } + + /* + * 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. + */ + public void setGoToReference(String goToReference) { + this.goToReference = goToReference; + } + + /** + * Returns the GoToReference from the associated DestinationData object. + * + * @return the GoToReference from the associated DestinationData object. + */ + public String getGoToReference() { + return this.goToReference; + } + + /** + * Get the PageViewport object that this destination refers to + * + * @return the PageViewport that this destination points to + */ + public PageViewport getPageViewport() { + return this.pageViewport; + } + + /** + * Returns the RefID from the associated DestinationData object. + * + * @return the RefID from the associated DestinationData object. + */ + public String getIDRef() { + return this.idRef; + } + + /** + * 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 PDFDestination)) { + return false; + } + + PDFDestination dest = (PDFDestination)obj; + if (dest.getIDRef() == this.getIDRef()) { + return true; + } + + return true; + } +} + diff --git a/src/java/org/apache/fop/pdf/PDFDests.java b/src/java/org/apache/fop/pdf/PDFDests.java new file mode 100644 index 000000000..96172c8e2 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFDests.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +import org.apache.fop.area.DestinationData; + +/** + * class representing an /Dests object (part of a name dictionary) + */ +public class PDFDests extends PDFObject { + + private String limitsRef; + + /** + * create a named destination + */ + public PDFDests(String limitsRef) { + /* generic creation of PDF object */ + super(); + this.limitsRef = limitsRef; + } + + /** + * @see org.apache.fop.pdf.PDFObject#toPDFString() + */ + public String toPDFString() { + String s = getObjectID() + + "<<\n" + + "/Dests " + limitsRef + + "\n>>\nendobj\n"; + return s; + } + + /* + * 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/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index 4ce82fab9..29cb88731 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; @@ -32,10 +32,14 @@ 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 */ @@ -63,13 +67,13 @@ import org.apache.commons.logging.LogFactory; 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. */ @@ -85,7 +89,7 @@ public class PDFDocument { /** * the character position of each object */ - protected List location = new java.util.ArrayList(); + protected List location = new ArrayList(); /** List of objects to write in the trailer */ private List trailerObjects = new java.util.ArrayList(); @@ -107,12 +111,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 */ @@ -202,6 +206,11 @@ public class PDFDocument { protected List links = new java.util.ArrayList(); /** + * List of Destinations. + */ + protected List destinations = new java.util.ArrayList(); + + /** * List of FileSpecs. */ protected List filespecs = new java.util.ArrayList(); @@ -216,6 +225,25 @@ public class PDFDocument { */ protected List gotos = new java.util.ArrayList(); + /** + * The PDFDests object for the name dictionary. + * Note: This object is not a list. + */ + 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; @@ -255,7 +283,7 @@ public class PDFDocument { public int getPDFVersion() { return this.pdfVersion; } - + /** @return the String representing the active PDF version */ public String getPDFVersionString() { switch (getPDFVersion()) { @@ -272,7 +300,7 @@ public class PDFDocument { public PDFProfile getProfile() { return this.pdfProfile; } - + /** * Returns the factory for PDF objects. * @return PDFFactory the factory @@ -315,7 +343,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) { @@ -460,6 +488,9 @@ 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); } @@ -578,6 +609,15 @@ public class PDFDocument { } /** + * Finds a named destination. + * @param compare reference object to use as search template + * @return the link if found, null otherwise + */ + protected PDFDestination findDestination(PDFDestination compare) { + return (PDFDestination)findPDFObject(destinations, compare); + } + + /** * Finds a link. * @param compare reference object to use as search template * @return the link if found, null otherwise @@ -702,6 +742,51 @@ public class PDFDocument { } /** + * Gets the PDFDests object (which represents the /Dests entry). + * + * @return the PDFDests object (which represents the /Dests entry). + */ + public PDFDests getDests() { + return dests; + } + + /** + * Gets the list of named destinations. + * + * @return the list of named destinations. + */ + public ArrayList getDestinationList() { + return (ArrayList)destinations; + } + + /** + * Sets whether the document has named destinations. + * + * @param hasDestinations whether the document has named destinations. + */ + public void setHasDestinations(boolean hasDestinations) { + this.hasDestinations = hasDestinations; + } + + /** + * Gets whether the document has named destinations. + * + * @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; + } + + /** * Add an image to the PDF document. * This adds an image to the PDF objects. * If an image with the same key already exists it will return the @@ -848,7 +933,7 @@ public class PDFDocument { this.position = 0; getProfile().verifyPDFVersion(); - + byte[] pdf = ("%PDF-" + getPDFVersionString() + "\n").getBytes(); stream.write(pdf); this.position += pdf.length; @@ -885,7 +970,7 @@ public class PDFDocument { } } } - + /** * write the trailer * @@ -893,6 +978,12 @@ 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()); + } output(stream); for (int count = 0; count < trailerObjects.size(); count++) { PDFObject o = (PDFObject)trailerObjects.get(count); @@ -970,4 +1061,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 4ab6ebd7b..3005f2018 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.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. @@ -31,6 +31,7 @@ import java.util.List; import java.util.Map; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; +import java.util.ArrayList; // Apache libs import org.apache.commons.io.IOUtils; @@ -52,6 +53,8 @@ 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. @@ -731,7 +734,7 @@ public class PDFFactory { PDFPattern myPattern; //PDFColorSpace theColorSpace; double interpolation = (double)1.000; - List theFunctions = new java.util.ArrayList(); + List theFunctions = new ArrayList(); int currentPosition; int lastPosition = theColors.size() - 1; @@ -782,7 +785,7 @@ public class PDFFactory { } else { // if the center x, center y, and radius specifiy // the gradient, then assume the same center x, center y, // and radius of zero for the other necessary component - List newCoords = new java.util.ArrayList(); + List newCoords = new ArrayList(); newCoords.add(theCoords.get(0)); newCoords.add(theCoords.get(1)); newCoords.add(theCoords.get(2)); @@ -809,6 +812,56 @@ public class PDFFactory { return (myPattern); } + /* ============= named destinations and the name dictionary ============ */ + + /** + * Make a named destination. + * + * @param destinationData the DestinationData object that holds the info about this named destination + * @return the new PDF named destination object + */ + 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; + } else { + getDocument().registerObject(destination); + getDocument().setHasDestinations(true); + } + + return destination; + } + + /** + * Make a the head object of the name dictionary (the /Dests object). + * + * @return the new PDFDests object + */ + public PDFDests makeDests(String limitsRef) { + PDFDests dests = new PDFDests(limitsRef); + getDocument().registerObject(dests); + + return dests; + } + + /** + * Make a the limits object of the name dictionary (the /Limits object). + * + * @return the new PDFLimits object + */ + public PDFLimits makeLimits(ArrayList destinationList) { + PDFLimits limits = new PDFLimits(destinationList); + getDocument().registerObject(limits); + + return limits; + } + /* ========================= links ===================================== */ /** @@ -888,7 +941,7 @@ public class PDFFactory { return link; } - private String getGoToReference(String destination, float yoffset) { + public String getGoToReference(String destination, float yoffset) { getDocument().getProfile().verifyActionAllowed(); String goToReference = null; PDFGoTo gt = new PDFGoTo(destination); @@ -1153,9 +1206,9 @@ public class PDFFactory { int value = 0; for (int i = 0, c = cidSubset.length(); i < c; i++) { int shift = i % 8; - boolean b = cidSubset.get(i); + boolean b = cidSubset.get(i); if (b) { - value |= 1 << 7 - shift; + value |= 1 << 7 - shift; } if (shift == 7) { baout.write(value); @@ -1335,7 +1388,7 @@ public class PDFFactory { public PDFICCBasedColorSpace makeICCBasedColorSpace(PDFResourceContext res, String explicitName, PDFICCStream iccStream) { PDFICCBasedColorSpace cs = new PDFICCBasedColorSpace(explicitName, iccStream); - + getDocument().registerObject(cs); if (res != null) { @@ -1343,7 +1396,7 @@ public class PDFFactory { } else { getDocument().getResources().addColorSpace(cs); } - + return cs; } diff --git a/src/java/org/apache/fop/pdf/PDFLimits.java b/src/java/org/apache/fop/pdf/PDFLimits.java new file mode 100644 index 000000000..97c097463 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFLimits.java @@ -0,0 +1,93 @@ +/* + * 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/PDFRoot.java b/src/java/org/apache/fop/pdf/PDFRoot.java index ffe9611ba..60fe6b390 100644 --- a/src/java/org/apache/fop/pdf/PDFRoot.java +++ b/src/java/org/apache/fop/pdf/PDFRoot.java @@ -20,6 +20,7 @@ package org.apache.fop.pdf; import java.util.List; +import java.util.ArrayList; /** * class representing a Root (/Catalog) object @@ -62,6 +63,12 @@ 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; + private int pageMode = PAGEMODE_USENONE; /** @@ -130,6 +137,15 @@ public class PDFRoot extends PDFObject { * @param meta the Metadata object * @since PDF 1.4 */ + public void setNames(String referencePDF) { + this.namesReferencePDF = referencePDF; + } + + /** + * Set the optional Metadata object. + * @param meta the Metadata object + * @since PDF 1.4 + */ public void setMetadata(PDFMetadata meta) { this.metadata = meta; } @@ -181,6 +197,9 @@ public class PDFRoot extends PDFObject { break; } } + if (getDocumentSafely().getHasDestinations() && namesReferencePDF != null) { + p.append(" /Names " + namesReferencePDF + "\n"); + } if (getMetadata() != null && getDocumentSafely().getPDFVersion() >= PDFDocument.PDF_VERSION_1_4) { p.append("/Metadata " + getMetadata().referencePDF() + "\n"); diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index 481ce844c..f64fa75aa 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -73,6 +73,7 @@ import org.apache.fop.pdf.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; @@ -105,6 +106,8 @@ import org.apache.xmlgraphics.xmp.Metadata; import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter; import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema; +import org.apache.fop.area.DestinationData; + /** * Renderer that renders areas to PDF. */ @@ -509,8 +512,19 @@ 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 - if (odi instanceof BookmarkData) { + else if (odi instanceof BookmarkData) { renderBookmarkTree((BookmarkData) odi); } else if (odi instanceof OffDocumentExtensionAttachment) { ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment(); diff --git a/src/java/org/apache/fop/render/ps/PSRenderer.java b/src/java/org/apache/fop/render/ps/PSRenderer.java index 24595368f..68c6cc86c 100644 --- a/src/java/org/apache/fop/render/ps/PSRenderer.java +++ b/src/java/org/apache/fop/render/ps/PSRenderer.java @@ -953,15 +953,22 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } if (rotate) { + gen.writeln("<<"); + gen.writeln("/PageSize [" + + Math.round(pspageheight) + " " + + Math.round(pspagewidth) + "]"); + gen.writeln("/ImagingBBox null"); + gen.writeln(">> setpagedevice"); gen.writeln(Math.round(pspageheight) + " 0 translate"); gen.writeln("90 rotate"); + } else { + gen.writeln("<<"); + gen.writeln("/PageSize [" + + Math.round(pspagewidth) + " " + + Math.round(pspageheight) + "]"); + gen.writeln("/ImagingBBox null"); + gen.writeln(">> setpagedevice"); } - gen.writeln("<<"); - gen.writeln("/PageSize [" - + Math.round(pspagewidth) + " " - + Math.round(pspageheight) + "]"); - gen.writeln("/ImagingBBox null"); - gen.writeln(">> setpagedevice"); concatMatrix(1, 0, 0, -1, 0, pageheight / 1000f); gen.writeDSCComment(DSCConstants.END_PAGE_SETUP); |