From 6069c859c2ca901e8931564d044264f9bac8cbb4 Mon Sep 17 00:00:00 2001 From: "(no author)" <(no author)@unknown> Date: Fri, 13 May 2005 14:55:14 +0000 Subject: [PATCH] This commit was manufactured by cvs2svn to create tag 'Before_Merge_KnuthStylePageBreaking_Back_To_HEAD'. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/tags/Before_Merge_KnuthStylePageBreaking_Back_To_HEAD@198626 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/fop/apps/CommandLineOptions.java | 3 + src/java/org/apache/fop/apps/FOUserAgent.java | 2 +- .../org/apache/fop/area/AreaTreeHandler.java | 13 +- src/java/org/apache/fop/area/BodyRegion.java | 87 +- .../org/apache/fop/area/MainReference.java | 39 +- src/java/org/apache/fop/area/NormalFlow.java | 4 +- src/java/org/apache/fop/area/Page.java | 110 +- .../org/apache/fop/area/PageViewport.java | 65 +- .../org/apache/fop/area/RegionReference.java | 40 +- .../org/apache/fop/area/RegionViewport.java | 37 +- src/java/org/apache/fop/area/Span.java | 61 +- src/java/org/apache/fop/fo/Constants.java | 7 +- .../org/apache/fop/fo/FOPropertyMapping.java | 8 + src/java/org/apache/fop/fo/FObj.java | 13 +- src/java/org/apache/fop/fo/PropertySets.java | 1 + src/java/org/apache/fop/fo/flow/Inline.java | 1 - src/java/org/apache/fop/fo/flow/Marker.java | 13 +- .../apache/fop/fo/flow/RetrieveMarker.java | 11 +- src/java/org/apache/fop/fo/flow/Table.java | 33 +- .../org/apache/fop/fo/flow/TableBody.java | 5 +- .../org/apache/fop/fo/flow/TableCell.java | 1 + .../org/apache/fop/fo/flow/TableColumn.java | 25 +- src/java/org/apache/fop/fo/flow/Wrapper.java | 35 +- .../apache/fop/fo/pagination/RegionBA.java | 16 +- .../apache/fop/fo/pagination/RegionBody.java | 11 + .../apache/fop/fo/pagination/RegionSE.java | 14 +- .../apache/fop/fo/pagination/SideRegion.java | 49 + .../apache/fop/layoutmgr/AbstractBreaker.java | 661 ++++++++++ .../fop/layoutmgr/AbstractLayoutManager.java | 204 +-- .../fop/layoutmgr/AreaAdditionUtil.java | 80 ++ .../fop/layoutmgr/BasicLinkLayoutManager.java | 8 +- .../fop/layoutmgr/BidiLayoutManager.java | 1 - .../BlockContainerLayoutManager.java | 753 ++++++++++- .../fop/layoutmgr/BlockLayoutManager.java | 269 +++- .../layoutmgr/BlockLevelLayoutManager.java | 51 + .../layoutmgr/BlockStackingLayoutManager.java | 1077 ++++++++++++++- .../fop/layoutmgr/BreakingAlgorithm.java | 783 +++++++++++ .../fop/layoutmgr/CharacterLayoutManager.java | 16 +- .../fop/layoutmgr/ContentLayoutManager.java | 62 +- .../fop/layoutmgr/ElementListUtils.java | 136 ++ .../ExternalGraphicLayoutManager.java | 2 +- .../fop/layoutmgr/FlowLayoutManager.java | 299 ++++- .../apache/fop/layoutmgr/ICLayoutManager.java | 2 +- .../fop/layoutmgr/InlineLayoutManager.java | 3 +- .../layoutmgr/InlineLevelLayoutManager.java | 34 +- .../InlineStackingLayoutManager.java | 219 +++- .../layoutmgr/InstreamForeignObjectLM.java | 3 +- .../apache/fop/layoutmgr/KnuthBlockBox.java | 41 + .../org/apache/fop/layoutmgr/KnuthBox.java | 42 +- .../org/apache/fop/layoutmgr/KnuthGlue.java | 41 +- .../apache/fop/layoutmgr/KnuthInlineBox.java | 64 + .../apache/fop/layoutmgr/KnuthParagraph.java | 7 +- .../apache/fop/layoutmgr/KnuthPenalty.java | 44 + .../fop/layoutmgr/KnuthPossPosIter.java | 16 +- .../apache/fop/layoutmgr/KnuthSequence.java | 83 ++ .../apache/fop/layoutmgr/LayoutContext.java | 40 + .../apache/fop/layoutmgr/LayoutManager.java | 108 +- .../fop/layoutmgr/LayoutManagerMaker.java | 26 +- .../fop/layoutmgr/LayoutManagerMapping.java | 56 +- .../fop/layoutmgr/LeaderLayoutManager.java | 10 +- .../fop/layoutmgr/LeafNodeLayoutManager.java | 30 +- .../fop/layoutmgr/LineLayoutManager.java | 1150 +++++++++++++---- .../layoutmgr/LineLayoutPossibilities.java | 236 ++++ .../apache/fop/layoutmgr/MinOptMaxUtil.java | 28 + .../apache/fop/layoutmgr/NonLeafPosition.java | 15 +- .../fop/layoutmgr/PageBreakingAlgorithm.java | 81 ++ .../PageNumberCitationLayoutManager.java | 6 +- .../layoutmgr/PageNumberLayoutManager.java | 4 +- .../layoutmgr/PageSequenceLayoutManager.java | 723 ++++------- .../org/apache/fop/layoutmgr/Position.java | 15 +- .../fop/layoutmgr/PositionIterator.java | 2 +- .../layoutmgr/StaticContentLayoutManager.java | 218 +++- .../fop/layoutmgr/TextLayoutManager.java | 135 +- .../org/apache/fop/layoutmgr/list/Item.java | 64 +- .../list/ListBlockLayoutManager.java | 65 +- .../layoutmgr/list/ListItemLayoutManager.java | 316 ++++- .../org/apache/fop/layoutmgr/table/Body.java | 270 ---- .../apache/fop/layoutmgr/table/Caption.java | 2 +- .../org/apache/fop/layoutmgr/table/Cell.java | 326 ++++- .../table/CollapsingBorderModel.java | 24 +- .../CollapsingBorderModelEyeCatching.java | 99 +- .../apache/fop/layoutmgr/table/Column.java | 134 -- .../fop/layoutmgr/table/ColumnSetup.java | 140 ++ .../apache/fop/layoutmgr/table/EffRow.java | 121 ++ .../fop/layoutmgr/table/EmptyGridUnit.java | 60 + .../apache/fop/layoutmgr/table/GridUnit.java | 274 +++- .../fop/layoutmgr/table/PrimaryGridUnit.java | 210 +++ .../org/apache/fop/layoutmgr/table/Row.java | 623 --------- .../table/TableAndCaptionLayoutManager.java | 2 +- .../table/TableContentLayoutManager.java | 863 +++++++++++++ .../layoutmgr/table/TableLayoutManager.java | 387 +++--- .../fop/layoutmgr/table/TableRowIterator.java | 439 +++++++ .../fop/layoutmgr/table/TableStepper.java | 418 ++++++ .../apache/fop/render/AbstractRenderer.java | 25 +- .../apache/fop/render/pdf/PDFRenderer.java | 19 +- .../apache/fop/render/xml/XMLRenderer.java | 2 +- .../org/apache/fop/traits/LayoutProps.java | 10 +- test/layoutengine/testcases/breaks1.xml | 38 +- test/layoutengine/testcases/breaks2.xml | 38 +- .../layoutengine/testcases/keep-together1.xml | 143 ++ .../testcases/keep-with-next1.xml | 95 ++ .../testcases/keep-with-previous1.xml | 95 ++ test/layoutengine/testcases/multi-column1.xml | 72 ++ test/layoutengine/testcases/multi-column2.xml | 76 ++ test/layoutengine/testcases/page-master3.xml | 14 +- test/layoutengine/testcases/page-master4.xml | 64 + 106 files changed, 11025 insertions(+), 3091 deletions(-) create mode 100644 src/java/org/apache/fop/fo/pagination/SideRegion.java create mode 100644 src/java/org/apache/fop/layoutmgr/AbstractBreaker.java create mode 100644 src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java create mode 100644 src/java/org/apache/fop/layoutmgr/BlockLevelLayoutManager.java create mode 100644 src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java create mode 100644 src/java/org/apache/fop/layoutmgr/ElementListUtils.java create mode 100644 src/java/org/apache/fop/layoutmgr/KnuthBlockBox.java create mode 100644 src/java/org/apache/fop/layoutmgr/KnuthInlineBox.java create mode 100644 src/java/org/apache/fop/layoutmgr/KnuthSequence.java create mode 100644 src/java/org/apache/fop/layoutmgr/LineLayoutPossibilities.java create mode 100644 src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java delete mode 100644 src/java/org/apache/fop/layoutmgr/table/Body.java delete mode 100644 src/java/org/apache/fop/layoutmgr/table/Column.java create mode 100644 src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java create mode 100644 src/java/org/apache/fop/layoutmgr/table/EffRow.java create mode 100644 src/java/org/apache/fop/layoutmgr/table/EmptyGridUnit.java create mode 100644 src/java/org/apache/fop/layoutmgr/table/PrimaryGridUnit.java delete mode 100644 src/java/org/apache/fop/layoutmgr/table/Row.java create mode 100644 src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java create mode 100644 src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java create mode 100644 src/java/org/apache/fop/layoutmgr/table/TableStepper.java create mode 100644 test/layoutengine/testcases/keep-together1.xml create mode 100644 test/layoutengine/testcases/keep-with-next1.xml create mode 100644 test/layoutengine/testcases/keep-with-previous1.xml create mode 100644 test/layoutengine/testcases/multi-column1.xml create mode 100644 test/layoutengine/testcases/multi-column2.xml create mode 100644 test/layoutengine/testcases/page-master4.xml diff --git a/src/java/org/apache/fop/apps/CommandLineOptions.java b/src/java/org/apache/fop/apps/CommandLineOptions.java index 42609311a..edd0d8e5e 100644 --- a/src/java/org/apache/fop/apps/CommandLineOptions.java +++ b/src/java/org/apache/fop/apps/CommandLineOptions.java @@ -149,6 +149,8 @@ public class CommandLineOptions implements Constants { suppressLowLevelAreas = Boolean.TRUE; } else if (args[i].equals("-d")) { setLogLevel("debug"); + } else if (args[i].equals("-r")) { + foUserAgent.setStrictValidation(false); } else if (args[i].equals("-q") || args[i].equals("--quiet")) { setLogLevel("error"); } else if (args[i].equals("-fo")) { @@ -612,6 +614,7 @@ public class CommandLineOptions implements Constants { + " -q quiet mode \n" + " -c cfg.xml use additional configuration file cfg.xml\n" + " -l lang the language to use for user information \n" + + " -r relaxed/less strict validation (where available)\n" + " -s for area tree XML, down to block areas only\n" + " -v to show FOP version being used\n\n" + " [INPUT] \n" diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java index df263149c..d15b5d735 100644 --- a/src/java/org/apache/fop/apps/FOUserAgent.java +++ b/src/java/org/apache/fop/apps/FOUserAgent.java @@ -85,7 +85,7 @@ public class FOUserAgent { * behavior for FOP. However, this flag, if set, provides the user the * ability for FOP to halt on all content model violations if desired. */ - private boolean strictValidation = false; + private boolean strictValidation = true; /* Additional fo.ElementMapping subclasses set by user */ private ArrayList additionalElementMappings = null; diff --git a/src/java/org/apache/fop/area/AreaTreeHandler.java b/src/java/org/apache/fop/area/AreaTreeHandler.java index 2d6328f13..ca0f87c7d 100644 --- a/src/java/org/apache/fop/area/AreaTreeHandler.java +++ b/src/java/org/apache/fop/area/AreaTreeHandler.java @@ -230,16 +230,9 @@ public class AreaTreeHandler extends FOEventHandler { // If no main flow, nothing to layout! if (pageSequence.getMainFlow() != null) { PageSequenceLayoutManager pageSLM; - try { - pageSLM = (PageSequenceLayoutManager) - getLayoutManagerMaker().makeLayoutManager(pageSequence); - } catch (FOPException e) { - log.error("Failed to create a PageSequenceLayoutManager; " - + "no pages will be laid out:"); - log.error(e.getMessage()); - return; - } - pageSLM.setAreaTreeHandler(this); + pageSLM = + getLayoutManagerMaker().makePageSequenceLayoutManager(this, + pageSequence); pageSLM.activateLayout(); } } diff --git a/src/java/org/apache/fop/area/BodyRegion.java b/src/java/org/apache/fop/area/BodyRegion.java index c4223db8b..628330540 100644 --- a/src/java/org/apache/fop/area/BodyRegion.java +++ b/src/java/org/apache/fop/area/BodyRegion.java @@ -19,10 +19,9 @@ package org.apache.fop.area; import org.apache.fop.fo.Constants; -import org.apache.fop.fo.pagination.RegionBody; /** - * This class is a container for all areas that may be generated by + * This class is a container for the areas that may be generated by * an fo:region-body. It extends the RegionReference that is used * directly by the other region classes. * See fo:region-body definition in the XSL Rec for more information. @@ -34,31 +33,15 @@ public class BodyRegion extends RegionReference { private int columnGap; private int columnCount; - /** - * Create a new body region area. - * This sets the region reference area class to BODY. - */ - public BodyRegion() { - super(Constants.FO_REGION_BODY); - addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); - mainReference = new MainReference(); - } - /** * Constructor which can read traits directly * from an fo:region-body formatting object. */ - public BodyRegion(RegionBody rb) { - this(); - columnCount = rb.getColumnCount(); - columnGap = rb.getColumnGap(); - if ((columnCount > 1) && (rb.getOverflow() == Constants.EN_SCROLL)) { - // recover by setting 'column-count' to 1. This is allowed but - // not required by the spec. - log.error("Setting 'column-count' to 1 because " - + "'overflow' is set to 'scroll'"); - columnCount = 1; - } + public BodyRegion(int columnCount, int columnGap, RegionViewport parent) { + super(Constants.FO_REGION_BODY, parent); + this.columnCount = columnCount; + this.columnGap = columnGap; + mainReference = new MainReference(this); } /** @@ -89,42 +72,11 @@ public class BodyRegion extends RegionReference { this.columnGap = colGap; } - /** - * Set the before float area. - * - * @param bf the before float area - */ - public void setBeforeFloat(BeforeFloat bf) { - beforeFloat = bf; - } - - /** - * Set the main reference area. - * - * @param mr the main reference area - */ - public void setMainReference(MainReference mr) { - mainReference = mr; - } - - /** - * Set the footnote area. - * - * @param foot the footnote area - */ - public void setFootnote(Footnote foot) { - footnote = foot; + /** @return the column-gap value */ + public int getColumnGap() { + return this.columnGap; } - - /** - * Get the before float area. - * - * @return the before float area - */ - public BeforeFloat getBeforeFloat() { - return beforeFloat; - } - + /** * Get the main reference area. * @@ -144,12 +96,27 @@ public class BodyRegion extends RegionReference { } + /** + * Get the before float area. + * + * @return the before float area + */ + public BeforeFloat getBeforeFloat() { + if (beforeFloat == null) { + beforeFloat = new BeforeFloat(); + } + return beforeFloat; + } + /** * Get the footnote area. * * @return the footnote area */ public Footnote getFootnote() { + if (footnote == null) { + footnote = new Footnote(); + } return footnote; } @@ -159,11 +126,9 @@ public class BodyRegion extends RegionReference { * @return a shallow copy of this object */ public Object clone() { - BodyRegion br = new BodyRegion(); + BodyRegion br = new BodyRegion(columnCount, columnGap, regionViewport); br.setCTM(getCTM()); br.setIPD(getIPD()); - br.columnGap = columnGap; - br.columnCount = columnCount; br.beforeFloat = beforeFloat; br.mainReference = mainReference; br.footnote = footnote; diff --git a/src/java/org/apache/fop/area/MainReference.java b/src/java/org/apache/fop/area/MainReference.java index 5274d4955..85a5d7ec1 100644 --- a/src/java/org/apache/fop/area/MainReference.java +++ b/src/java/org/apache/fop/area/MainReference.java @@ -28,15 +28,17 @@ import java.util.Iterator; * See fo:region-body definition in the XSL Rec for more information. */ public class MainReference extends Area { + + private BodyRegion parent; private List spanAreas = new java.util.ArrayList(); - private int columnGap; private int width; private boolean isEmpty = true; /** * Constructor */ - public MainReference() { + public MainReference(BodyRegion parent) { + this.parent = parent; addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); } @@ -45,8 +47,15 @@ public class MainReference extends Area { * * @param span the span area to add */ - public void addSpan(Span span) { - spanAreas.add(span); + public Span createSpan(boolean spanAll) { + RegionViewport rv = parent.getRegionViewport(); + int ipdWidth = (int) parent.getIPD() - + rv.getBorderAndPaddingWidthStart() - rv.getBorderAndPaddingWidthEnd(); + + Span newSpan = new Span(((spanAll) ? 1 : getColumnCount()), + getColumnGap(), ipdWidth); + spanAreas.add(newSpan); + return getCurrentSpan(); } /** @@ -58,6 +67,15 @@ public class MainReference extends Area { return spanAreas; } + /** + * Get the span area currently being filled (i.e., the last span created) + * + * @return the active span + */ + public Span getCurrentSpan() { + return (Span) spanAreas.get(spanAreas.size()-1); + } + /** * indicates whether any child areas have been added to this reference area * this is achieved by looping through each span @@ -86,13 +104,14 @@ public class MainReference extends Area { return isEmpty; } - /** - * Get the column gap in millipoints. - * - * @return the column gap in millipoints - */ + /** @return the number of columns */ + public int getColumnCount() { + return parent.getColumnCount(); + } + + /** @return the column gap in millipoints */ public int getColumnGap() { - return columnGap; + return parent.getColumnGap(); } /** diff --git a/src/java/org/apache/fop/area/NormalFlow.java b/src/java/org/apache/fop/area/NormalFlow.java index 32dfbc7d8..ce39eb1f7 100644 --- a/src/java/org/apache/fop/area/NormalFlow.java +++ b/src/java/org/apache/fop/area/NormalFlow.java @@ -26,9 +26,11 @@ package org.apache.fop.area; public class NormalFlow extends BlockParent { /** * Constructor. + * @param ipd of Normal flow object */ - public NormalFlow() { + public NormalFlow(int ipd) { addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); + setIPD(ipd); } } diff --git a/src/java/org/apache/fop/area/Page.java b/src/java/org/apache/fop/area/Page.java index 9c1a1aafd..858c0722d 100644 --- a/src/java/org/apache/fop/area/Page.java +++ b/src/java/org/apache/fop/area/Page.java @@ -18,10 +18,20 @@ package org.apache.fop.area; +import java.awt.Rectangle; +import java.awt.geom.Rectangle2D; import java.io.Serializable; import java.util.HashMap; +import java.util.Iterator; +import org.apache.fop.datatypes.FODimension; +import org.apache.fop.datatypes.PercentBase; import org.apache.fop.fo.Constants; +import org.apache.fop.fo.pagination.Region; +import org.apache.fop.fo.pagination.RegionBody; +import org.apache.fop.fo.pagination.SimplePageMaster; +import org.apache.fop.fo.properties.CommonMarginBlock; +import org.apache.fop.layoutmgr.TraitSetter; /** * The page. @@ -45,13 +55,109 @@ public class Page implements Serializable, Cloneable { // temporary map of unresolved objects used when serializing the page private HashMap unresolved = null; + /** + * Empty constructor, for cloning + */ + public Page() { + } + + /** + * Constructor + * @param spm SimplePageMaster containing the dimensions for this + * page-reference-area + */ + public Page(SimplePageMaster spm) { + int pageWidth = spm.getPageWidth().getValue(); + int pageHeight = spm.getPageHeight().getValue(); + + // Get absolute margin properties (top, left, bottom, right) + CommonMarginBlock mProps = spm.getCommonMarginBlock(); + + /* + * Create the page reference area rectangle (0,0 is at top left + * of the "page media" and y increases + * when moving towards the bottom of the page. + * The media rectangle itself is (0,0,pageWidth,pageHeight). + */ + Rectangle pageRefRect = + new Rectangle(mProps.marginLeft.getValue(), mProps.marginTop.getValue(), + pageWidth - mProps.marginLeft.getValue() - mProps.marginRight.getValue(), + pageHeight - mProps.marginTop.getValue() - mProps.marginBottom.getValue()); + + // Set up the CTM on the page reference area based on writing-mode + // and reference-orientation + FODimension reldims = new FODimension(0, 0); + CTM pageCTM = CTM.getCTMandRelDims(spm.getReferenceOrientation(), + spm.getWritingMode(), pageRefRect, reldims); + + // Create a RegionViewport/ reference area pair for each page region + RegionReference rr = null; + for (Iterator regenum = spm.getRegions().values().iterator(); + regenum.hasNext();) { + Region r = (Region)regenum.next(); + RegionViewport rvp = makeRegionViewport(r, reldims, pageCTM); + r.setLayoutDimension(PercentBase.BLOCK_IPD, rvp.getIPD()); + r.setLayoutDimension(PercentBase.BLOCK_BPD, rvp.getBPD()); + if (r.getNameId() == Constants.FO_REGION_BODY) { + RegionBody rb = (RegionBody) r; + rr = new BodyRegion(rb.getColumnCount(), rb.getColumnGap(), + rvp); + } else { + rr = new RegionReference(r.getNameId(), rvp); + } + setRegionReferencePosition(rr, r, rvp.getViewArea()); + rvp.setRegionReference(rr); + setRegionViewport(r.getNameId(), rvp); + } + } + + /** + * Creates a RegionViewport Area object for this pagination Region. + * @param reldims relative dimensions + * @param pageCTM page coordinate transformation matrix + * @return the new region viewport + */ + private RegionViewport makeRegionViewport(Region r, FODimension reldims, CTM pageCTM) { + Rectangle2D relRegionRect = r.getViewportRectangle(reldims); + Rectangle2D absRegionRect = pageCTM.transform(relRegionRect); + // Get the region viewport rectangle in absolute coords by + // transforming it using the page CTM + RegionViewport rv = new RegionViewport(absRegionRect); + rv.setBPD((int)relRegionRect.getHeight()); + rv.setIPD((int)relRegionRect.getWidth()); + TraitSetter.addBackground(rv, r.getCommonBorderPaddingBackground()); + rv.setClip(r.getOverflow() == Constants.EN_HIDDEN + || r.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW); + return rv; + } + + /** + * Set the region reference position within the region viewport. + * This sets the transform that is used to place the contents of + * the region reference. + * + * @param rr the region reference area + * @param r the region-xxx formatting object + * @param absRegVPRect The region viewport rectangle in "absolute" coordinates + * where x=distance from left, y=distance from bottom, width=right-left + * height=top-bottom + */ + private void setRegionReferencePosition(RegionReference rr, Region r, + Rectangle2D absRegVPRect) { + FODimension reldims = new FODimension(0, 0); + rr.setCTM(CTM.getCTMandRelDims(r.getReferenceOrientation(), + r.getWritingMode(), absRegVPRect, reldims)); + rr.setIPD(reldims.ipd); + rr.setBPD(reldims.bpd); + } + /** * Set the region on this page. * * @param areaclass the area class of the region to set * @param port the region viewport to set */ - public void setRegionViewport(int areaclass, RegionViewport port) { + private void setRegionViewport(int areaclass, RegionViewport port) { if (areaclass == Constants.FO_REGION_BEFORE) { regionBefore = port; } else if (areaclass == Constants.FO_REGION_START) { @@ -97,7 +203,7 @@ public class Page implements Serializable, Cloneable { return true; } else { - BodyRegion body = (BodyRegion)regionBody.getRegion(); + BodyRegion body = (BodyRegion)regionBody.getRegionReference(); return body.isEmpty(); } } diff --git a/src/java/org/apache/fop/area/PageViewport.java b/src/java/org/apache/fop/area/PageViewport.java index 48c9dd732..d3e9c6bc0 100644 --- a/src/java/org/apache/fop/area/PageViewport.java +++ b/src/java/org/apache/fop/area/PageViewport.java @@ -18,6 +18,7 @@ package org.apache.fop.area; +import java.awt.Rectangle; import java.awt.geom.Rectangle2D; import java.io.ObjectOutputStream; import java.io.ObjectInputStream; @@ -31,6 +32,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.fo.Constants; +import org.apache.fop.fo.pagination.SimplePageMaster; /** * Page viewport that specifies the viewport area and holds the page contents. @@ -46,6 +48,7 @@ public class PageViewport implements Resolvable, Cloneable { private Rectangle2D viewArea; private boolean clip = false; private String pageNumberString = null; + private SimplePageMaster spm = null; // list of id references and the rectangle on the page private Map idReferences = null; @@ -72,12 +75,26 @@ public class PageViewport implements Resolvable, Cloneable { /** * Create a page viewport. - * @param p the page reference area that holds the contents - * @param bounds the bounds of this viewport + * @param spm SimplePageMaster indicating the page and region dimensions */ - public PageViewport(Page p, Rectangle2D bounds) { - page = p; - viewArea = bounds; + public PageViewport(SimplePageMaster spm) { + this.spm = spm; + int pageWidth = spm.getPageWidth().getValue(); + int pageHeight = spm.getPageHeight().getValue(); + viewArea = new Rectangle(0, 0, pageWidth, pageHeight); + page = new Page(spm); + } + + /** + * Create a page viewport + * @param spm SimplePageMaster indicating the page and region dimensions + * @param p Page Reference Area + * @param bounds Page Viewport dimensions + */ + public PageViewport(SimplePageMaster spm, Page p, Rectangle2D bounds) { + this.spm = spm; + this.page = p; + this.viewArea = bounds; } /** @@ -85,8 +102,29 @@ public class PageViewport implements Resolvable, Cloneable { * @return BodyRegion object */ public BodyRegion getBodyRegion() { - return (BodyRegion) - getPage().getRegionViewport(Constants.FO_REGION_BODY).getRegion(); + return (BodyRegion) getPage().getRegionViewport( + Constants.FO_REGION_BODY).getRegionReference(); + } + + /** + * Convenience method to create a new Span for this + * this PageViewport. + * + * @param spanAll whether this is a single-column span + * @return Span object created + */ + public Span createSpan(boolean spanAll) { + return getBodyRegion().getMainReference().createSpan(spanAll); + } + + /** + * Convenience method to get the span-reference-area currently + * being processed + * + * @return span currently being processed. + */ + public Span getCurrentSpan() { + return getBodyRegion().getMainReference().getCurrentSpan(); } /** @@ -229,6 +267,11 @@ public class PageViewport implements Resolvable, Cloneable { */ public void addMarkers(Map marks, boolean starting, boolean isfirst, boolean islast) { + + if (marks == null) { + return; + } + // at the start of the area, register is-first and any areas if (starting) { if (isfirst) { @@ -385,7 +428,7 @@ public class PageViewport implements Resolvable, Cloneable { */ public Object clone() { Page p = (Page)page.clone(); - PageViewport ret = new PageViewport(p, (Rectangle2D)viewArea.clone()); + PageViewport ret = new PageViewport(spm, p, (Rectangle2D)viewArea.clone()); return ret; } @@ -407,4 +450,10 @@ public class PageViewport implements Resolvable, Cloneable { sb.append(getPageNumberString()); return sb.toString(); } + /** + * @return Returns the spm. + */ + public SimplePageMaster getSPM() { + return spm; + } } \ No newline at end of file diff --git a/src/java/org/apache/fop/area/RegionReference.java b/src/java/org/apache/fop/area/RegionReference.java index e1ebf97f3..6f671e96f 100644 --- a/src/java/org/apache/fop/area/RegionReference.java +++ b/src/java/org/apache/fop/area/RegionReference.java @@ -24,26 +24,29 @@ import java.util.List; import org.apache.fop.fo.Constants; /** - * This is a region reference area for the page regions. - * This area represents a region on the page. It is cloneable + * This is a region reference area for a page regions. + * This area is the direct child of a region-viewport-area. It is cloneable * so the page master can make copies from the original page and regions. */ public class RegionReference extends Area implements Cloneable { private int regionClass = Constants.FO_REGION_BEFORE; private CTM ctm; + // the list of block areas from the static flow private List blocks = new ArrayList(); - - private int bpd; + + // the parent RegionViewport for this object + protected RegionViewport regionViewport; /** * Create a new region reference area. * * @param type the region class type */ - public RegionReference(int type) { + public RegionReference(int type, RegionViewport parent) { regionClass = type; addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); + regionViewport = parent; } /** @@ -58,6 +61,13 @@ public class RegionReference extends Area implements Cloneable { public void setCTM(CTM ctm) { this.ctm = ctm; } + + /** + * @return Returns the parent RegionViewport. + */ + public RegionViewport getRegionViewport() { + return regionViewport; + } /** * Get the current transform of this region. @@ -95,24 +105,6 @@ public class RegionReference extends Area implements Cloneable { blocks.add(block); } - /** - * Set the block-progression-dimension. - * - * @return the footnote area - */ - public void setBPD(int bpd) { - this.bpd = bpd; - } - - /** - * Set the block-progression-dimension. - * - * @return the footnote area - */ - public int getBPD() { - return bpd; - } - /** * Clone this region. * This is used when cloning the page by the page master. @@ -121,7 +113,7 @@ public class RegionReference extends Area implements Cloneable { * @return a copy of this region reference area */ public Object clone() { - RegionReference rr = new RegionReference(regionClass); + RegionReference rr = new RegionReference(regionClass, regionViewport); rr.ctm = ctm; rr.setIPD(getIPD()); return rr; diff --git a/src/java/org/apache/fop/area/RegionViewport.java b/src/java/org/apache/fop/area/RegionViewport.java index 3e1381310..ff3146e17 100644 --- a/src/java/org/apache/fop/area/RegionViewport.java +++ b/src/java/org/apache/fop/area/RegionViewport.java @@ -23,17 +23,19 @@ import java.io.IOException; import java.util.HashMap; /** - * Region Viewport reference area. - * This area is the viewport for a region and contains a region area. + * Region Viewport area. + * This object represents the region-viewport-area. It has a + * region-reference-area as its child. These areas are described + * in the fo:region-body description in the XSL Recommendation. */ public class RegionViewport extends Area implements Cloneable { // this rectangle is relative to the page - private RegionReference region; + private RegionReference regionReference; private Rectangle2D viewArea; private boolean clip = false; /** - * Create a new region viewport. + * Create a new region-viewport-area * * @param viewArea the view area of this viewport */ @@ -43,21 +45,21 @@ public class RegionViewport extends Area implements Cloneable { } /** - * Set the region for this region viewport. + * Set the region-reference-area for this region viewport. * - * @param reg the child region inside this viewport + * @param reg the child region-reference-area inside this viewport */ - public void setRegion(RegionReference reg) { - region = reg; + public void setRegionReference(RegionReference reg) { + regionReference = reg; } /** - * Get the region for this region viewport. + * Get the region-reference-area for this region viewport. * - * @return the child region inside this viewport + * @return the child region-reference-area inside this viewport */ - public RegionReference getRegion() { - return region; + public RegionReference getRegionReference() { + return regionReference; } /** @@ -69,6 +71,11 @@ public class RegionViewport extends Area implements Cloneable { clip = c; } + /** @return true if the viewport should be clipped. */ + public boolean isClip() { + return this.clip; + } + /** * Get the view area of this viewport. * @@ -86,7 +93,7 @@ public class RegionViewport extends Area implements Cloneable { out.writeFloat((float) viewArea.getHeight()); out.writeBoolean(clip); out.writeObject(props); - out.writeObject(region); + out.writeObject(regionReference); } private void readObject(java.io.ObjectInputStream in) @@ -95,7 +102,7 @@ public class RegionViewport extends Area implements Cloneable { in.readFloat(), in.readFloat()); clip = in.readBoolean(); props = (HashMap)in.readObject(); - setRegion((RegionReference) in.readObject()); + setRegionReference((RegionReference) in.readObject()); } /** @@ -106,7 +113,7 @@ public class RegionViewport extends Area implements Cloneable { */ public Object clone() { RegionViewport rv = new RegionViewport((Rectangle2D)viewArea.clone()); - rv.region = (RegionReference)region.clone(); + rv.regionReference = (RegionReference)regionReference.clone(); if (props != null) { rv.props = (HashMap)props.clone(); } diff --git a/src/java/org/apache/fop/area/Span.java b/src/java/org/apache/fop/area/Span.java index ff289d2ca..e080ea599 100644 --- a/src/java/org/apache/fop/area/Span.java +++ b/src/java/org/apache/fop/area/Span.java @@ -32,36 +32,37 @@ public class Span extends Area { // the list of flow reference areas in this span area private List flowAreas; private int height; - private int columnCount; + private int colCount; + private int colGap; + private int colWidth; // width for each normal flow, calculated value /** * Create a span area with the number of columns for this span area. * - * @param cols the number of columns in the span - * @param ipd the ipd of the span + * @param colCount the number of columns in the span + * @param colGap the column gap between each column + * @param ipd the total ipd of the span */ - public Span(int cols, int ipd) { + public Span(int colCount, int colGap, int ipd) { addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); - columnCount = cols; + this.colCount = colCount; + this.colGap = colGap; this.ipd = ipd; - flowAreas = new java.util.ArrayList(cols); - addAdditionalNormalFlow(); // one normal flow is required + createNormalFlows(); } /** - * Create a new normal flow and add it to this span area - * - * @return the newly made NormalFlow object + * Create the normal flows for this Span */ - public NormalFlow addAdditionalNormalFlow() { - if (flowAreas.size() >= columnCount) { // internal error - throw new IllegalStateException("Maximum number of flow areas (" + - columnCount + ") for this span reached."); + private void createNormalFlows() { + flowAreas = new java.util.ArrayList(colCount); + colWidth = (ipd - ((colCount - 1) * colGap)) / colCount; + + for (int i=0; i< colCount; i++) { + NormalFlow newFlow = new NormalFlow(colWidth); + newFlow.setIPD(getIPD()); + flowAreas.add(newFlow); } - NormalFlow newFlow = new NormalFlow(); - newFlow.setIPD(getIPD()); - flowAreas.add(newFlow); - return newFlow; } /** @@ -70,16 +71,16 @@ public class Span extends Area { * @return the number of columns defined for this span area */ public int getColumnCount() { - return columnCount; + return colCount; } /** - * Get the count of normal flows for this span area. + * Get the width of a single column within this Span * - * @return the number of normal flows attached to this span + * @return the width of a single column */ - public int getNormalFlowCount() { - return flowAreas.size(); + public int getColumnWidth() { + return colWidth; } /** @@ -91,15 +92,21 @@ public class Span extends Area { return height; } + /** * Get the normal flow area for a particular column. * - * @param count the column number for the flow + * @param colRequested the zero-based column number of the flow * @return the flow area for the requested column */ - public NormalFlow getNormalFlow(int columnNumber) { - return (NormalFlow) flowAreas.get(columnNumber); + public NormalFlow getNormalFlow(int colRequested) { + if (colRequested >= 0 && colRequested < colCount) { + return (NormalFlow) flowAreas.get(colRequested); + } else { // internal error + throw new IllegalArgumentException("Invalid column number " + + colRequested + " requested; only 0-" + (colCount-1) + + " available."); + } } - } diff --git a/src/java/org/apache/fop/fo/Constants.java b/src/java/org/apache/fop/fo/Constants.java index fb2dc7fe9..9a0423bc4 100644 --- a/src/java/org/apache/fop/fo/Constants.java +++ b/src/java/org/apache/fop/fo/Constants.java @@ -375,7 +375,8 @@ public interface Constants { int PR_INTRUSION_DISPLACE = 247; int PR_INDEX_CLASS = 248; // XSL 1.1 int PR_INDEX_KEY = 249; // XSL 1.1 - int PROPERTY_COUNT = 249; + int PR_X_BLOCK_PROGRESSION_UNIT = 250; //Custom extension + int PROPERTY_COUNT = 250; // compound property constants @@ -553,5 +554,7 @@ public interface Constants { int EN_VISIBLE = 159; int EN_WIDER = 160; int EN_WRAP = 161; - int ENUM_COUNT = 161; + int EN_X_FILL = 162; //non-standard for display-align + int EN_X_DISTRIBUTE = 163; //non-standard for display-align + int ENUM_COUNT = 163; } diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java index a1328b9bf..11b69080d 100644 --- a/src/java/org/apache/fop/fo/FOPropertyMapping.java +++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java @@ -1354,6 +1354,8 @@ public class FOPropertyMapping implements Constants { m.addEnum("after", getEnumProperty(EN_AFTER, "AFTER")); m.addEnum("center", getEnumProperty(EN_CENTER, "CENTER")); m.addEnum("auto", getEnumProperty(EN_AUTO, "AUTO")); +/*LF*/ m.addEnum("distribute", getEnumProperty(EN_X_DISTRIBUTE, "DISTRIBUTE")); +/*LF*/ m.addEnum("fill", getEnumProperty(EN_X_FILL, "FILL")); m.setDefault("auto"); addPropertyMaker("display-align", m); @@ -1536,6 +1538,12 @@ public class FOPropertyMapping implements Constants { l.setPercentBase(LengthBase.BLOCK_WIDTH); l.setDefault("auto"); addPropertyMaker("width", l); + +/*LF*/ // block-progression-unit (**CUSTOM EXTENSION**) +/*LF*/ l = new LengthProperty.Maker(PR_X_BLOCK_PROGRESSION_UNIT); +/*LF*/ l.setInherited(false); +/*LF*/ l.setDefault("0pt"); +/*LF*/ addPropertyMaker("block-progression-unit", l); } private void createBlockAndLineProperties() { diff --git a/src/java/org/apache/fop/fo/FObj.java b/src/java/org/apache/fop/fo/FObj.java index b3982bfa5..b2b79eceb 100644 --- a/src/java/org/apache/fop/fo/FObj.java +++ b/src/java/org/apache/fop/fo/FObj.java @@ -18,9 +18,8 @@ package org.apache.fop.fo; -import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; @@ -39,7 +38,7 @@ public abstract class FObj extends FONode implements Constants { public static PropertyMaker[] propertyListTable = null; /** The immediate child nodes of this node. */ - public ArrayList childNodes = null; + public List childNodes = null; /** Used to indicate if this FO is either an Out Of Line FO (see rec) or a descendant of one. Used during validateChildNode() FO @@ -164,7 +163,7 @@ public abstract class FObj extends FONode implements Constants { addMarker((Marker) child); } else { if (childNodes == null) { - childNodes = new ArrayList(); + childNodes = new java.util.ArrayList(); } childNodes.add(child); } @@ -198,7 +197,7 @@ public abstract class FObj extends FONode implements Constants { */ public void setLayoutDimension(PercentBase.LayoutDimension key, int dimension) { if (layoutDimension == null) { - layoutDimension = new HashMap(); + layoutDimension = new java.util.HashMap(); } layoutDimension.put(key, new Integer(dimension)); } @@ -210,7 +209,7 @@ public abstract class FObj extends FONode implements Constants { */ public void setLayoutDimension(PercentBase.LayoutDimension key, float dimension) { if (layoutDimension == null) { - layoutDimension = new HashMap(); + layoutDimension = new java.util.HashMap(); } layoutDimension.put(key, new Float(dimension)); } @@ -307,7 +306,7 @@ public abstract class FObj extends FONode implements Constants { } } if (markers == null) { - markers = new HashMap(); + markers = new java.util.HashMap(); } if (!markers.containsKey(mcname)) { markers.put(mcname, marker); diff --git a/src/java/org/apache/fop/fo/PropertySets.java b/src/java/org/apache/fop/fo/PropertySets.java index c00b15782..3209cb58b 100644 --- a/src/java/org/apache/fop/fo/PropertySets.java +++ b/src/java/org/apache/fop/fo/PropertySets.java @@ -395,6 +395,7 @@ public class PropertySets { elem.addProperties(CommonBorderPaddingBackgroundProperties); elem.addProperties(CommonMarginPropertiesBlock); elem.addProperty(Constants.PR_BLOCK_PROGRESSION_DIMENSION); + elem.addProperty(Constants.PR_X_BLOCK_PROGRESSION_UNIT); elem.addProperty(Constants.PR_PAGE_BREAK_AFTER); elem.addProperty(Constants.PR_PAGE_BREAK_BEFORE); elem.addProperty(Constants.PR_BREAK_AFTER); diff --git a/src/java/org/apache/fop/fo/flow/Inline.java b/src/java/org/apache/fop/fo/flow/Inline.java index 82d1db9fe..f396c7dc2 100644 --- a/src/java/org/apache/fop/fo/flow/Inline.java +++ b/src/java/org/apache/fop/fo/flow/Inline.java @@ -28,7 +28,6 @@ import org.apache.fop.fo.InlineCharIterator; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.properties.CommonRelativePosition; -import org.apache.fop.fo.properties.CommonTextDecoration; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.LengthRangeProperty; diff --git a/src/java/org/apache/fop/fo/flow/Marker.java b/src/java/org/apache/fop/fo/flow/Marker.java index 96c99b3e4..15fe81aed 100644 --- a/src/java/org/apache/fop/fo/flow/Marker.java +++ b/src/java/org/apache/fop/fo/flow/Marker.java @@ -20,8 +20,6 @@ package org.apache.fop.fo.flow; import java.util.HashMap; import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; import org.xml.sax.Locator; @@ -29,7 +27,6 @@ import org.xml.sax.Locator; import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FOEventHandler; import org.apache.fop.fo.FONode; -import org.apache.fop.fo.FOText; import org.apache.fop.fo.FObj; import org.apache.fop.fo.FObjMixed; import org.apache.fop.fo.PropertyList; @@ -60,7 +57,17 @@ public class Marker extends FObjMixed { * @see org.apache.fop.fo.FObj#bind(PropertyList) */ public void bind(PropertyList pList) throws FOPException { + if (findAncestor(FO_FLOW) < 0) { + invalidChildError(locator, FO_URI, "marker", + "An fo:marker is permitted only as the descendant " + + "of an fo:flow"); + } + markerClassName = pList.get(PR_MARKER_CLASS_NAME).getString(); + + if (markerClassName == null || markerClassName.equals("")) { + missingPropertyError("marker-class-name"); + } } /** diff --git a/src/java/org/apache/fop/fo/flow/RetrieveMarker.java b/src/java/org/apache/fop/fo/flow/RetrieveMarker.java index 744728ae2..9992d52d6 100644 --- a/src/java/org/apache/fop/fo/flow/RetrieveMarker.java +++ b/src/java/org/apache/fop/fo/flow/RetrieveMarker.java @@ -22,7 +22,6 @@ import java.util.Map; import java.util.HashMap; import java.util.Iterator; import java.util.ArrayList; -import java.util.List; import org.xml.sax.Locator; @@ -66,9 +65,19 @@ public class RetrieveMarker extends FObjMixed { * @see org.apache.fop.fo.FObj#bind(PropertyList) */ public void bind(PropertyList pList) throws FOPException { + if (findAncestor(FO_STATIC_CONTENT) < 0) { + invalidChildError(locator, FO_URI, "retrieve-marker", + "An fo:retrieve-marker is permitted only as the " + + " descendant of an fo:static-content."); + } + retrieveClassName = pList.get(PR_RETRIEVE_CLASS_NAME).getString(); retrievePosition = pList.get(PR_RETRIEVE_POSITION).getEnum(); retrieveBoundary = pList.get(PR_RETRIEVE_BOUNDARY).getEnum(); + + if (retrieveClassName == null || retrieveClassName.equals("")) { + missingPropertyError("retrieve-class-name"); + } } /** diff --git a/src/java/org/apache/fop/fo/flow/Table.java b/src/java/org/apache/fop/fo/flow/Table.java index 44ee43339..a6534e3d1 100644 --- a/src/java/org/apache/fop/fo/flow/Table.java +++ b/src/java/org/apache/fop/fo/flow/Table.java @@ -238,6 +238,14 @@ public class Table extends FObj { public List getColumns() { return columns; } + + /** + * @param index index of the table-body element. + * @return the requested table-body element + */ + public TableBody getBody(int index) { + return (TableBody)childNodes.get(index); + } public TableBody getTableHeader() { return tableHeader; @@ -285,20 +293,31 @@ public class Table extends FObj { return commonBorderPaddingBackground; } - /** - * @return the "break-after" property. - */ + /** @return the "break-after" property. */ public int getBreakAfter() { return breakAfter; } - /** - * @return the "break-before" property. - */ + /** @return the "break-before" property. */ public int getBreakBefore() { return breakBefore; } - + + /** @return the "keep-with-next" property. */ + public KeepProperty getKeepWithNext() { + return keepWithNext; + } + + /** @return the "keep-with-previous" property. */ + public KeepProperty getKeepWithPrevious() { + return keepWithPrevious; + } + + /** @return the "keep-together" property. */ + public KeepProperty getKeepTogether() { + return keepTogether; + } + /** @return the "border-collapse" property. */ public int getBorderCollapse() { return borderCollapse; diff --git a/src/java/org/apache/fop/fo/flow/TableBody.java b/src/java/org/apache/fop/fo/flow/TableBody.java index da1dc92f9..cae2fd2ce 100644 --- a/src/java/org/apache/fop/fo/flow/TableBody.java +++ b/src/java/org/apache/fop/fo/flow/TableBody.java @@ -102,9 +102,10 @@ public class TableBody extends FObj { getParent().removeChild(this); } } + /* if (tableCellsFound) { convertCellsToRows(); - } + }*/ savedPropertyList = null; //Release reference } @@ -148,7 +149,7 @@ public class TableBody extends FObj { */ private void convertCellsToRows() throws FOPException { //getLogger().debug("Converting cells to rows..."); - List cells = (List)childNodes.clone(); + List cells = new java.util.ArrayList(childNodes); childNodes.clear(); Iterator i = cells.iterator(); TableRow row = null; diff --git a/src/java/org/apache/fop/fo/flow/TableCell.java b/src/java/org/apache/fop/fo/flow/TableCell.java index 6466da453..dcc7a0a4c 100644 --- a/src/java/org/apache/fop/fo/flow/TableCell.java +++ b/src/java/org/apache/fop/fo/flow/TableCell.java @@ -156,6 +156,7 @@ public class TableCell extends FObj { if (!blockItemFound) { missingChildElementError("marker* (%block;)+"); } + //TODO Complain about startsRow|endsRow=true if parent is a table-row getFOEventHandler().endCell(this); } diff --git a/src/java/org/apache/fop/fo/flow/TableColumn.java b/src/java/org/apache/fop/fo/flow/TableColumn.java index 4a0b7d037..2f8bf5ada 100644 --- a/src/java/org/apache/fop/fo/flow/TableColumn.java +++ b/src/java/org/apache/fop/fo/flow/TableColumn.java @@ -137,13 +137,16 @@ public class TableColumn extends FObj { return columnNumber.getValue(); } - /** - * @return value for number of columns repeated - */ + /** @return value for number-columns-repeated. */ public int getNumberColumnsRepeated() { return numberColumnsRepeated.getValue(); } + /** @return value for number-columns-spanned. */ + public int getNumberColumnsSpanned() { + return numberColumnsSpanned.getValue(); + } + /** @see org.apache.fop.fo.FONode#getName() */ public String getName() { return "fo:table-column"; @@ -153,5 +156,21 @@ public class TableColumn extends FObj { public int getNameId() { return FO_TABLE_COLUMN; } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer("fo:table-column"); + if (hasColumnNumber()) { + sb.append(" column-number=").append(getColumnNumber()); + } + if (getNumberColumnsRepeated() > 1) { + sb.append(" number-columns-repeated=").append(getNumberColumnsRepeated()); + } + if (getNumberColumnsSpanned() > 1) { + sb.append(" number-columns-spanned=").append(getNumberColumnsSpanned()); + } + sb.append(" column-width=").append(getColumnWidth()); + return sb.toString(); + } } diff --git a/src/java/org/apache/fop/fo/flow/Wrapper.java b/src/java/org/apache/fop/fo/flow/Wrapper.java index 1de4327e2..c8bb26f73 100644 --- a/src/java/org/apache/fop/fo/flow/Wrapper.java +++ b/src/java/org/apache/fop/fo/flow/Wrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 1999-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,28 +19,26 @@ package org.apache.fop.fo.flow; // Java -import java.util.List; -import java.util.ListIterator; - import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObjMixed; import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.ValidationException; +import org.xml.sax.Locator; /** * Implementation for fo:wrapper formatting object. * The wrapper object serves as * a property holder for its child node objects. - * - * Content: (#PCDATA|%inline;|%block;)* - * Properties: id - * @todo implement validateChildNode() */ public class Wrapper extends FObjMixed { // The value of properties relevant for fo:wrapper. private String id; // End of property values + // used for FO validation + private boolean blockOrInlineItemFound = false; + /** * @param parent FONode that is the parent of this object */ @@ -62,6 +60,27 @@ public class Wrapper extends FObjMixed { checkId(id); } + /** + * @see org.apache.fop.fo.FONode#validateChildNode(Locator, String, String) + * XSL Content Model: marker* (#PCDATA|%inline;|%block;)* + * Additionally (unimplemented): "An fo:wrapper that is a child of an + * fo:multi-properties is only permitted to have children that would + * be permitted in place of the fo:multi-properties." + */ + protected void validateChildNode(Locator loc, String nsURI, String localName) + throws ValidationException { + if (nsURI == FO_URI && localName.equals("marker")) { + if (blockOrInlineItemFound) { + nodesOutOfOrderError(loc, "fo:marker", + "(#PCDATA|%inline;|%block;)"); + } + } else if (isBlockOrInlineItem(nsURI, localName)) { + blockOrInlineItemFound = true; + } else { + invalidChildError(loc, nsURI, localName); + } + } + /** * Return the "id" property. */ diff --git a/src/java/org/apache/fop/fo/pagination/RegionBA.java b/src/java/org/apache/fop/fo/pagination/RegionBA.java index b06e53138..6d35dae20 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionBA.java +++ b/src/java/org/apache/fop/fo/pagination/RegionBA.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 1999-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,16 +22,14 @@ package org.apache.fop.fo.pagination; import java.awt.Rectangle; import org.apache.fop.apps.FOPException; -import org.apache.fop.datatypes.Length; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; /** * Abstract base class for fo:region-before and fo:region-after. */ -public abstract class RegionBA extends Region { +public abstract class RegionBA extends SideRegion { // The value of properties relevant for fo:region-[before|after]. - private Length extent; private int precedence; // End of property values @@ -47,19 +45,11 @@ public abstract class RegionBA extends Region { */ public void bind(PropertyList pList) throws FOPException { super.bind(pList); - extent = pList.get(PR_EXTENT).getLength(); precedence = pList.get(PR_PRECEDENCE).getEnum(); } /** - * Return the "extent" property. - */ - public Length getExtent() { - return extent; - } - - /** - * Return the "precedence" property. + * @return the "precedence" property. */ public int getPrecedence() { return precedence; diff --git a/src/java/org/apache/fop/fo/pagination/RegionBody.java b/src/java/org/apache/fop/fo/pagination/RegionBody.java index 47fb7e8e8..a4bd82700 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionBody.java +++ b/src/java/org/apache/fop/fo/pagination/RegionBody.java @@ -25,6 +25,7 @@ import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.FODimension; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.Numeric; +import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.properties.CommonMarginBlock; @@ -54,6 +55,16 @@ public class RegionBody extends Region { commonMarginBlock = pList.getMarginBlockProps(); columnCount = pList.get(PR_COLUMN_COUNT).getNumeric(); columnGap = pList.get(PR_COLUMN_GAP).getLength(); + + if ((getColumnCount() > 1) && (getOverflow() == EN_SCROLL)) { + /* This is an error (See XSL Rec, fo:region-body description). + * The Rec allows for acting as if "1" is chosen in + * these cases, but we will need to be able to change Numeric + * values in order to do this. + */ + attributeError("If overflow property is set to \"scroll\"," + + " a column-count other than \"1\" may not be specified."); + } } /** diff --git a/src/java/org/apache/fop/fo/pagination/RegionSE.java b/src/java/org/apache/fop/fo/pagination/RegionSE.java index a47326778..9100c0981 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionSE.java +++ b/src/java/org/apache/fop/fo/pagination/RegionSE.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 1999-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,16 +22,14 @@ package org.apache.fop.fo.pagination; import java.awt.Rectangle; import org.apache.fop.apps.FOPException; -import org.apache.fop.datatypes.Length; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; /** * Abstract base class for fo:region-start and fo:region-end. */ -public abstract class RegionSE extends Region { +public abstract class RegionSE extends SideRegion { // The value of properties relevant for fo:region-[start|end]. - private Length extent; // End of property values /** @@ -46,16 +44,8 @@ public abstract class RegionSE extends Region { */ public void bind(PropertyList pList) throws FOPException { super.bind(pList); - extent = pList.get(PR_EXTENT).getLength(); } - /** - * Return the "extent" property. - */ - public Length getExtent() { - return extent; - } - /** * Adjust the viewport reference rectangle for a region as a function * of precedence. diff --git a/src/java/org/apache/fop/fo/pagination/SideRegion.java b/src/java/org/apache/fop/fo/pagination/SideRegion.java new file mode 100644 index 000000000..1475bc342 --- /dev/null +++ b/src/java/org/apache/fop/fo/pagination/SideRegion.java @@ -0,0 +1,49 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.pagination; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.datatypes.Length; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; + +/** + * Common base class for side regions (before, after, start, end). + */ +public abstract class SideRegion extends Region { + + private Length extent; + + /** @see org.apache.fop.fo.FONode#FONode(FONode) */ + protected SideRegion(FONode parent) { + super(parent); + } + + /** @see org.apache.fop.fo.FObj#bind(PropertyList) */ + public void bind(PropertyList pList) throws FOPException { + super.bind(pList); + extent = pList.get(PR_EXTENT).getLength(); + } + + /** @return the "extent" property. */ + public Length getExtent() { + return extent; + } + +} diff --git a/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java b/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java new file mode 100644 index 000000000..36adad77b --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java @@ -0,0 +1,661 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr; + +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.fop.fo.Constants; +import org.apache.fop.traits.MinOptMax; + +/** + * Abstract base class for breakers (page breakers, static region handlers etc.). + */ +public abstract class AbstractBreaker { + + /** logging instance */ + protected static Log log = LogFactory.getLog(AbstractBreaker.class); + + /*LF*/ + public static class PageBreakPosition extends LeafPosition { + double bpdAdjust; // Percentage to adjust (stretch or shrink) + int difference; + + PageBreakPosition(LayoutManager lm, int iBreakIndex, + double bpdA, int diff) { + super(lm, iBreakIndex); + bpdAdjust = bpdA; + difference = diff; + } + } + + public class BlockSequence extends KnuthSequence { + + /** + * startOn represents where on the page/which page layout + * should start for this BlockSequence. Acceptable values: + * Constants.EN_ANY (can continue from finished location + * of previous BlockSequence?), EN_COLUMN, EN_ODD_PAGE, + * EN_EVEN_PAGE. + */ + private int startOn; + + public BlockSequence(int iStartOn) { + super(); + startOn = iStartOn; + } + + public int getStartOn() { + return this.startOn; + } + + public BlockSequence endBlockSequence() { + KnuthSequence temp = super.endSequence(); + if (temp != null) { + BlockSequence returnSequence = new BlockSequence(startOn); + returnSequence.addAll(temp); + returnSequence.ignoreAtEnd = this.ignoreAtEnd; + return returnSequence; + } else { + return null; + } + } + } + + /** blockListIndex of the current BlockSequence in blockLists */ + private int blockListIndex = 0; +/*LF*/ + /*LF*/ + private List blockLists = null; + + private int alignment; + private int alignmentLast; + /*LF*/ + + protected abstract int getCurrentDisplayAlign(); + protected abstract boolean hasMoreContent(); + protected abstract void addAreas(PositionIterator posIter, LayoutContext context); + protected abstract LayoutManager getTopLevelLM(); + protected abstract LayoutManager getCurrentChildLM(); + protected abstract LinkedList getNextKnuthElements(LayoutContext context, int alignment); + + /** @return true if there's no content that could be handled. */ + public boolean isEmpty() { + return (blockLists.size() == 0); + } + + protected void startPart(BlockSequence list, boolean bIsFirstPage) { + //nop + } + + protected abstract void finishPart(); + + protected LayoutContext createLayoutContext() { + return new LayoutContext(0); + } + + public void doLayout(int flowBPD) { + LayoutContext childLC = createLayoutContext(); + childLC.setStackLimit(new MinOptMax(flowBPD)); + + //System.err.println("Vertical alignment: " + + // currentSimplePageMaster.getRegion(FO_REGION_BODY).getDisplayAlign()); + if (getCurrentDisplayAlign() == Constants.EN_X_FILL) { + //EN_FILL is non-standard (by LF) + alignment = Constants.EN_JUSTIFY; + } else { + alignment = Constants.EN_START; + } + alignmentLast = Constants.EN_START; + + BlockSequence blockList; + blockLists = new java.util.ArrayList(); + + System.out.println("PLM> flow BPD =" + flowBPD); + + //*** Phase 1: Get Knuth elements *** + int nextSequenceStartsOn = Constants.EN_ANY; + while (hasMoreContent()) { + nextSequenceStartsOn = getNextBlockList(childLC, nextSequenceStartsOn, blockLists); + } + + //*** Phase 2: Alignment and breaking *** + System.out.println("PLM> blockLists.size() = " + blockLists.size()); + for (blockListIndex = 0; blockListIndex < blockLists.size(); blockListIndex++) { + blockList = (BlockSequence) blockLists.get(blockListIndex); + + //debug code start + System.err.println(" blockListIndex = " + blockListIndex); + String pagina = (blockList.startOn == Constants.EN_ANY) ? "any page" + : (blockList.startOn == Constants.EN_ODD_PAGE) ? "odd page" + : "even page"; + System.err.println(" sequence starts on " + pagina); + logBlocklist(blockList); + //debug code end + + System.out.println("PLM> start of algorithm (" + this.getClass().getName() + + "), flow BPD =" + flowBPD); + PageBreakingAlgorithm alg = new PageBreakingAlgorithm(getTopLevelLM(), + alignment, alignmentLast); + int iOptPageNumber; + + BlockSequence effectiveList; + if (alignment == Constants.EN_JUSTIFY) { + /* justification */ + effectiveList = justifyBoxes(blockList, alg, flowBPD); + } else { + /* no justification */ + effectiveList = blockList; + } + + //iOptPageNumber = alg.firstFit(effectiveList, flowBPD, 1, true); + iOptPageNumber = alg.findBreakingPoints(effectiveList, flowBPD, 1, + true, true); + System.out.println("PLM> iOptPageNumber= " + iOptPageNumber + + " pageBreaks.size()= " + alg.getPageBreaks().size()); + + + //*** Phase 3: Add areas *** + doPhase3(alg, iOptPageNumber, blockList, effectiveList); + } + } + + /** + * Phase 3 of Knuth algorithm: Adds the areas + * @param alg PageBreakingAlgorithm instance which determined the breaks + * @param partCount number of parts (pages) to be rendered + * @param originalList original Knuth element list + * @param effectiveList effective Knuth element list (after adjustments) + */ + protected abstract void doPhase3(PageBreakingAlgorithm alg, int partCount, + BlockSequence originalList, BlockSequence effectiveList); + + /** + * Phase 3 of Knuth algorithm: Adds the areas + * @param alg PageBreakingAlgorithm instance which determined the breaks + * @param partCount number of parts (pages) to be rendered + * @param originalList original Knuth element list + * @param effectiveList effective Knuth element list (after adjustments) + */ + protected void addAreas(PageBreakingAlgorithm alg, int partCount, + BlockSequence originalList, BlockSequence effectiveList) { + LayoutContext childLC; + // add areas + ListIterator effectiveListIterator = effectiveList.listIterator(); + int startElementIndex = 0; + int endElementIndex = 0; + for (int p = 0; p < partCount; p++) { + PageBreakPosition pbp = (PageBreakPosition) alg.getPageBreaks().get(p); + endElementIndex = pbp.getLeafPos(); + System.out.println("PLM> part: " + (p + 1) + + ", break at position " + endElementIndex); + + startPart(effectiveList, (p == 0)); + + int displayAlign = getCurrentDisplayAlign(); + + // ignore the first elements added by the + // PageSequenceLayoutManager + startElementIndex += (startElementIndex == 0) + ? effectiveList.ignoreAtStart + : 0; + + // ignore the last elements added by the + // PageSequenceLayoutManager + endElementIndex -= (endElementIndex == (originalList.size() - 1)) + ? effectiveList.ignoreAtEnd + : 0; + + // ignore the last element in the page if it is a KnuthGlue + // object + if (((KnuthElement) effectiveList.get(endElementIndex)) + .isGlue()) { + endElementIndex--; + } + + // ignore KnuthGlue and KnuthPenalty objects + // at the beginning of the line + effectiveListIterator = effectiveList + .listIterator(startElementIndex); + while (effectiveListIterator.hasNext() + && !((KnuthElement) effectiveListIterator.next()) + .isBox()) { + startElementIndex++; + } + + if (startElementIndex <= endElementIndex) { + System.out.println(" addAreas da " + startElementIndex + + " a " + endElementIndex); + childLC = new LayoutContext(0); + // add space before if display-align is center or bottom + // add space after if display-align is distribute and + // this is not the last page + if (pbp.difference != 0 && displayAlign == Constants.EN_CENTER) { + childLC.setSpaceBefore(pbp.difference / 2); + } else if (pbp.difference != 0 && displayAlign == Constants.EN_AFTER) { + childLC.setSpaceBefore(pbp.difference); + } else if (pbp.difference != 0 && displayAlign == Constants.EN_X_DISTRIBUTE + && p < (partCount - 1)) { + // count the boxes whose width is not 0 + int boxCount = 0; + effectiveListIterator = effectiveList + .listIterator(startElementIndex); + while (effectiveListIterator.nextIndex() <= endElementIndex) { + KnuthElement tempEl = (KnuthElement)effectiveListIterator.next(); + if (tempEl.isBox() && tempEl.getW() > 0) { + boxCount++; + } + } + // split the difference + if (boxCount >= 2) { + childLC.setSpaceAfter(pbp.difference / (boxCount - 1)); + } + } + + /* *** *** non-standard extension *** *** */ + if (displayAlign == Constants.EN_X_FILL) { + int averageLineLength = optimizeLineLength(effectiveList, startElementIndex, endElementIndex); + if (averageLineLength != 0) { + childLC.setStackLimit(new MinOptMax(averageLineLength)); + } + } + /* *** *** non-standard extension *** *** */ + + addAreas(new KnuthPossPosIter(effectiveList, + startElementIndex, endElementIndex + 1), childLC); + } + + finishPart(); + + startElementIndex = pbp.getLeafPos() + 1; + } + } + + /** + * Gets the next block list (sequence) and adds it to a list of block lists if it's not empty. + * @param childLC LayoutContext to use + * @param nextSequenceStartsOn indicates on what page the next sequence should start + * @param blockLists list of block lists (sequences) + * @return the page on which the next content should appear after a hard break + */ + private int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn, List blockLists) { + LinkedList returnedList; + BlockSequence blockList; + if ((returnedList = getNextKnuthElements(childLC, alignment)) != null) { + if (returnedList.size() == 0) { + return nextSequenceStartsOn; + } + blockList = new BlockSequence(nextSequenceStartsOn); + if (((KnuthElement) returnedList.getLast()).isPenalty() + && ((KnuthPenalty) returnedList.getLast()).getP() == -KnuthElement.INFINITE) { + KnuthPenalty breakPenalty = (KnuthPenalty) returnedList + .removeLast(); + switch (breakPenalty.getBreakClass()) { + case Constants.EN_PAGE: + System.err.println("PLM> break - PAGE"); + nextSequenceStartsOn = Constants.EN_ANY; + break; + case Constants.EN_COLUMN: + System.err.println("PLM> break - COLUMN"); + //TODO Fix this when implementing multi-column layout + nextSequenceStartsOn = Constants.EN_COLUMN; + break; + case Constants.EN_ODD_PAGE: + System.err.println("PLM> break - ODD PAGE"); + nextSequenceStartsOn = Constants.EN_ODD_PAGE; + break; + case Constants.EN_EVEN_PAGE: + System.err.println("PLM> break - EVEN PAGE"); + nextSequenceStartsOn = Constants.EN_EVEN_PAGE; + break; + default: + throw new IllegalStateException("Invalid break class: " + + breakPenalty.getBreakClass()); + } + } + blockList.addAll(returnedList); + BlockSequence seq = null; + seq = blockList.endBlockSequence(); + if (seq != null) { + blockLists.add(seq); + } + } + return nextSequenceStartsOn; + } + + /** + * @param effectiveList effective block list to work on + * @param startElementIndex + * @param endElementIndex + * @return the average line length, 0 if there's no content + */ + private int optimizeLineLength(KnuthSequence effectiveList, int startElementIndex, int endElementIndex) { + ListIterator effectiveListIterator; + // optimize line length + //System.out.println(" "); + int boxCount = 0; + int accumulatedLineLength = 0; + int greatestMinimumLength = 0; + effectiveListIterator = effectiveList + .listIterator(startElementIndex); + while (effectiveListIterator.nextIndex() <= endElementIndex) { + KnuthElement tempEl = (KnuthElement) effectiveListIterator + .next(); + if (tempEl instanceof KnuthBlockBox) { + KnuthBlockBox blockBox = (KnuthBlockBox) tempEl; + if (blockBox.getBPD() > 0) { + log.debug("PSLM> nominal length of line = " + blockBox.getBPD()); + log.debug(" range = " + + blockBox.getIPDRange()); + boxCount++; + accumulatedLineLength += ((KnuthBlockBox) tempEl) + .getBPD(); + } + if (blockBox.getIPDRange().min > greatestMinimumLength) { + greatestMinimumLength = blockBox + .getIPDRange().min; + } + } + } + int averageLineLength = 0; + if (accumulatedLineLength > 0 && boxCount > 0) { + averageLineLength = (int) (accumulatedLineLength / boxCount); + //System.out.println("PSLM> lunghezza media = " + averageLineLength); + if (averageLineLength < greatestMinimumLength) { + averageLineLength = greatestMinimumLength; + //System.out.println(" correzione, ora e' = " + averageLineLength); + } + } + return averageLineLength; + } + + /** + * Justifies the boxes and returns them as a new KnuthSequence. + * @param blockList block list to justify + * @param alg reference to the algorithm instance + * @param availableBPD the available BPD + * @return the effective list + */ + private BlockSequence justifyBoxes(BlockSequence blockList, PageBreakingAlgorithm alg, int availableBPD) { + int iOptPageNumber; + iOptPageNumber = alg.findBreakingPoints(blockList, availableBPD, 1, + true, true); + System.out.println("PLM> iOptPageNumber= " + iOptPageNumber); + + // + ListIterator sequenceIterator = blockList.listIterator(); + ListIterator breakIterator = alg.getPageBreaks().listIterator(); + KnuthElement thisElement = null; + PageBreakPosition thisBreak; + int accumulatedS; // accumulated stretch or shrink + int adjustedDiff; // difference already adjusted + int firstElementIndex; + + while (breakIterator.hasNext()) { + thisBreak = (PageBreakPosition) breakIterator.next(); + System.out.println("| first page: break= " + + thisBreak.getLeafPos() + " difference= " + + thisBreak.difference + " ratio= " + + thisBreak.bpdAdjust); + accumulatedS = 0; + adjustedDiff = 0; + + // glue and penalty items at the beginning of the page must + // be ignored: + // the first element returned by sequenceIterator.next() + // inside the + // while loop must be a box + KnuthElement firstElement; + while (!(firstElement = (KnuthElement) sequenceIterator + .next()).isBox()) { + // + System.out.println("PLM> ignoring glue or penalty element " + + "at the beginning of the sequence"); + if (firstElement.isGlue()) { + ((BlockLevelLayoutManager) firstElement + .getLayoutManager()) + .discardSpace((KnuthGlue) firstElement); + } + } + firstElementIndex = sequenceIterator.previousIndex(); + sequenceIterator.previous(); + + // scan the sub-sequence representing a page, + // collecting information about potential adjustments + MinOptMax lineNumberMaxAdjustment = new MinOptMax(0); + MinOptMax spaceMaxAdjustment = new MinOptMax(0); + double spaceAdjustmentRatio = 0.0; + LinkedList blockSpacesList = new LinkedList(); + LinkedList unconfirmedList = new LinkedList(); + LinkedList adjustableLinesList = new LinkedList(); + boolean bBoxSeen = false; + while (sequenceIterator.hasNext() + && sequenceIterator.nextIndex() <= thisBreak + .getLeafPos()) { + thisElement = (KnuthElement) sequenceIterator.next(); + if (thisElement.isGlue()) { + // glue elements are used to represent adjustable + // lines + // and adjustable spaces between blocks + switch (((KnuthGlue) thisElement) + .getAdjustmentClass()) { + case BlockLevelLayoutManager.SPACE_BEFORE_ADJUSTMENT: + // fall through + case BlockLevelLayoutManager.SPACE_AFTER_ADJUSTMENT: + // potential space adjustment + // glue items before the first box or after the + // last one + // must be ignored + unconfirmedList.add(thisElement); + break; + case BlockLevelLayoutManager.LINE_NUMBER_ADJUSTMENT: + // potential line number adjustment + lineNumberMaxAdjustment.max += ((KnuthGlue) thisElement) + .getY(); + lineNumberMaxAdjustment.min -= ((KnuthGlue) thisElement) + .getZ(); + adjustableLinesList.add(thisElement); + break; + case BlockLevelLayoutManager.LINE_HEIGHT_ADJUSTMENT: + // potential line height adjustment + break; + default: + // nothing + } + } else if (thisElement.isBox()) { + if (!bBoxSeen) { + // this is the first box met in this page + bBoxSeen = true; + } else if (unconfirmedList.size() > 0) { + // glue items in unconfirmedList were not after + // the last box + // in this page; they must be added to + // blockSpaceList + while (unconfirmedList.size() > 0) { + KnuthGlue blockSpace = (KnuthGlue) unconfirmedList + .removeFirst(); + spaceMaxAdjustment.max += ((KnuthGlue) blockSpace) + .getY(); + spaceMaxAdjustment.min -= ((KnuthGlue) blockSpace) + .getZ(); + blockSpacesList.add(blockSpace); + } + } + } + } + System.out.println("| line number adj= " + + lineNumberMaxAdjustment); + System.out.println("| space adj = " + + spaceMaxAdjustment); + + if (thisElement.isPenalty() && thisElement.getW() > 0) { + System.out + .println(" mandatory variation to the number of lines!"); + ((BlockLevelLayoutManager) thisElement + .getLayoutManager()).negotiateBPDAdjustment( + thisElement.getW(), thisElement); + } + + if (thisBreak.bpdAdjust != 0 + && (thisBreak.difference > 0 && thisBreak.difference <= spaceMaxAdjustment.max) + || (thisBreak.difference < 0 && thisBreak.difference >= spaceMaxAdjustment.min)) { + // modify only the spaces between blocks + spaceAdjustmentRatio = ((double) thisBreak.difference / (thisBreak.difference > 0 ? spaceMaxAdjustment.max + : spaceMaxAdjustment.min)); + adjustedDiff += adjustBlockSpaces( + blockSpacesList, + thisBreak.difference, + (thisBreak.difference > 0 ? spaceMaxAdjustment.max + : -spaceMaxAdjustment.min)); + System.out.println("single space: " + + (adjustedDiff == thisBreak.difference + || thisBreak.bpdAdjust == 0 ? "ok" + : "ERROR")); + } else if (thisBreak.bpdAdjust != 0) { + adjustedDiff += adjustLineNumbers( + adjustableLinesList, + thisBreak.difference, + (thisBreak.difference > 0 ? lineNumberMaxAdjustment.max + : -lineNumberMaxAdjustment.min)); + adjustedDiff += adjustBlockSpaces( + blockSpacesList, + thisBreak.difference - adjustedDiff, + ((thisBreak.difference - adjustedDiff) > 0 ? spaceMaxAdjustment.max + : -spaceMaxAdjustment.min)); + System.out.println("lines and space: " + + (adjustedDiff == thisBreak.difference + || thisBreak.bpdAdjust == 0 ? "ok" + : "ERROR")); + + } + } + + // create a new sequence: the new elements will contain the + // Positions + // which will be used in the addAreas() phase + BlockSequence effectiveList = new BlockSequence(blockList.getStartOn()); + effectiveList.addAll(getCurrentChildLM().getChangedKnuthElements( + blockList.subList(0, blockList.size() - blockList.ignoreAtEnd), + /* 0, */0)); + //effectiveList.add(new KnuthPenalty(0, -KnuthElement.INFINITE, + // false, new Position(this), false)); + effectiveList.endSequence(); + + logEffectiveList(effectiveList); + + alg.getPageBreaks().clear(); //Why this? + return effectiveList; + } + + /** + * Logs the contents of a block list for debugging purposes + * @param blockList block list to log + */ + private void logBlocklist(KnuthSequence blockList) { + ListIterator tempIter = blockList.listIterator(); + + KnuthElement temp; + System.out.println(" "); + while (tempIter.hasNext()) { + temp = (KnuthElement) tempIter.next(); + if (temp.isBox()) { + System.out.println(tempIter.previousIndex() + + ") " + temp); + } else if (temp.isGlue()) { + System.out.println(tempIter.previousIndex() + + ") " + temp); + } else { + System.out.println(tempIter.previousIndex() + + ") " + temp); + } + if (temp.getPosition() != null) { + System.out.println(" " + temp.getPosition()); + } + } + System.out.println(" "); + } + + /** + * Logs the contents of an effective block list for debugging purposes + * @param effectiveList block list to log + */ + private void logEffectiveList(KnuthSequence effectiveList) { + System.out.println("Effective list"); + logBlocklist(effectiveList); + } + + private int adjustBlockSpaces(LinkedList spaceList, int difference, int total) { + /*LF*/ System.out.println("AdjustBlockSpaces: difference " + difference + " / " + total + " on " + spaceList.size() + " spaces in block"); + ListIterator spaceListIterator = spaceList.listIterator(); + int adjustedDiff = 0; + int partial = 0; + while (spaceListIterator.hasNext()) { + KnuthGlue blockSpace = (KnuthGlue)spaceListIterator.next(); + partial += (difference > 0 ? blockSpace.getY() : blockSpace.getZ()); + System.out.println("available = " + partial + " / " + total); + System.out.println("competenza = " + (((int) ((float) partial * difference / total)) - adjustedDiff) + " / " + difference); + int newAdjust = ((BlockLevelLayoutManager) blockSpace.getLayoutManager()).negotiateBPDAdjustment(((int) ((float) partial * difference / total)) - adjustedDiff, blockSpace); + adjustedDiff += newAdjust; + } + return adjustedDiff; + } + + private int adjustLineNumbers(LinkedList lineList, int difference, int total) { + /*LF*/ System.out.println("AdjustLineNumbers: difference " + difference + " / " + total + " on " + lineList.size() + " elements"); + +// int adjustedDiff = 0; +// int partial = 0; +// KnuthGlue prevLine = null; +// KnuthGlue currLine = null; +// ListIterator lineListIterator = lineList.listIterator(); +// while (lineListIterator.hasNext()) { +// currLine = (KnuthGlue)lineListIterator.next(); +// if (prevLine != null +// && prevLine.getLayoutManager() != currLine.getLayoutManager()) { +// int newAdjust = ((BlockLevelLayoutManager) prevLine.getLayoutManager()) +// .negotiateBPDAdjustment(((int) ((float) partial * difference / total)) - adjustedDiff, prevLine); +// adjustedDiff += newAdjust; +// } +// partial += (difference > 0 ? currLine.getY() : currLine.getZ()); +// prevLine = currLine; +// } +// if (currLine != null) { +// int newAdjust = ((BlockLevelLayoutManager) currLine.getLayoutManager()) +// .negotiateBPDAdjustment(((int) ((float) partial * difference / total)) - adjustedDiff, currLine); +// adjustedDiff += newAdjust; +// } +// return adjustedDiff; + + ListIterator lineListIterator = lineList.listIterator(); + int adjustedDiff = 0; + int partial = 0; + while (lineListIterator.hasNext()) { + KnuthGlue line = (KnuthGlue)lineListIterator.next(); + partial += (difference > 0 ? line.getY() : line.getZ()); + int newAdjust = ((BlockLevelLayoutManager) line.getLayoutManager()).negotiateBPDAdjustment(((int) ((float) partial * difference / total)) - adjustedDiff, line); + adjustedDiff += newAdjust; + } + return adjustedDiff; + } + + +} diff --git a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java index 9456b58e3..74b5bbfaf 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java @@ -21,23 +21,21 @@ package org.apache.fop.layoutmgr; import org.apache.fop.fo.FObj; import org.apache.fop.fo.FONode; import org.apache.fop.area.Area; -import org.apache.fop.area.Resolvable; import org.apache.fop.area.PageViewport; -import org.apache.fop.area.AreaTreeHandler; import org.apache.fop.fo.Constants; import org.apache.fop.fo.flow.RetrieveMarker; -import org.apache.fop.fo.flow.Marker; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import java.util.LinkedList; import java.util.List; import java.util.ArrayList; import java.util.ListIterator; import java.util.Map; /** - * The base class for all LayoutManagers. + * The base class for most LayoutManagers. */ public abstract class AbstractLayoutManager implements LayoutManager, Constants { protected LayoutManager parentLM = null; @@ -51,7 +49,7 @@ public abstract class AbstractLayoutManager implements LayoutManager, Constants /** * Used during addAreas(): signals that a BreakPoss is not generating areas - * and therefore doesn't add IDs and markers to the current page. + * and therefore shouldn't add IDs and markers to the current page. * @see org.apache.fop.layoutmgr.AbstractLayoutManager#isBogus */ protected boolean bBogus = false; @@ -80,15 +78,6 @@ public abstract class AbstractLayoutManager implements LayoutManager, Constants if (fo == null) { throw new IllegalStateException("Null formatting object found."); } - setFObj(fo); - } - - /** - * Set the FO object for this layout manager - * - * @param fo the formatting object for this layout manager - */ - public void setFObj(FObj fo) { markers = fo.getMarkers(); fobjIter = fo.getChildNodes(); childLMiter = new LMiter(this); @@ -120,38 +109,6 @@ public abstract class AbstractLayoutManager implements LayoutManager, Constants return this.parentLM; } - // /** - // * Ask the parent LayoutManager to add the current (full) area to the - // * appropriate parent area. - // * @param bFinished If true, this area is finished, either because it's - // * completely full or because there is no more content to put in it. - // * If false, we are in the middle of this area. This can happen, - // * for example, if we find floats in a line. We stop the current area, - // * and add it (temporarily) to its parent so that we can see if there - // * is enough space to place the float(s) anchored in the line. - // */ - // protected void flush(Area area, boolean bFinished) { - // if (area != null) { - // // area.setFinished(true); - // parentLM.addChildArea(area, bFinished); // ???? - // if (bFinished) { - // setCurrentArea(null); - // } - // } - // } - - /** - * Return an Area which can contain the passed childArea. The childArea - * may not yet have any content, but it has essential traits set. - * In general, if the LayoutManager already has an Area it simply returns - * it. Otherwise, it makes a new Area of the appropriate class. - * It gets a parent area for its area by calling its parent LM. - * Finally, based on the dimensions of the parent area, it initializes - * its own area. This includes setting the content IPD and the maximum - * BPD. - */ - - /** @see org.apache.fop.layoutmgr.LayoutManager#generatesInlineAreas() */ public boolean generatesInlineAreas() { return false; @@ -296,126 +253,60 @@ public abstract class AbstractLayoutManager implements LayoutManager, Constants * interface which are declared abstract in AbstractLayoutManager. * ---------------------------------------------------------*/ - /** - * @see org.apache.fop.layoutmgr.LayoutManager#getParentArea(org.apache.fop.area.Area) - */ - public Area getParentArea(Area childArea) { + public LinkedList getNextKnuthElements(LayoutContext context, + int alignment) { + log.warn("null implementation of getNextKnuthElements() called!"); + setFinished(true); return null; } - protected void flush() { + public KnuthElement addALetterSpaceTo(KnuthElement element) { + log.warn("null implementation of addALetterSpaceTo() called!"); + return element; } - public void addChildArea(Area childArea) { + public void getWordChars(StringBuffer sbChars, Position pos) { + log.warn("null implementation of getWordChars() called!"); } - /** - * Delegate getting the current page number to the parent layout manager. - * - * @see org.apache.fop.layoutmgr.LayoutManager - */ - public String getCurrentPageNumberString() { - return parentLM.getCurrentPageNumberString(); + public void hyphenate(Position pos, HyphContext hc) { + log.warn("null implementation of hyphenate called!"); } - /** - * Delegate resolving the id reference to the parent layout manager. - * - * @see org.apache.fop.layoutmgr.LayoutManager - */ - public PageViewport resolveRefID(String ref) { - return parentLM.resolveRefID(ref); - } - - /** - * Add the id to the page. - * If the id string is not null then add the id to the current page. - */ - protected void addID(String foID) { - if (foID != null && foID.length() > 0) { - addIDToPage(foID); - } - } - - /** - * Delegate adding id reference to the parent layout manager. - * - * @see org.apache.fop.layoutmgr.LayoutManager - */ - public void addIDToPage(String id) { - parentLM.addIDToPage(id); - } - - /** - * Delegate adding unresolved area to the parent layout manager. - * - * @see org.apache.fop.layoutmgr.LayoutManager - */ - public void addUnresolvedArea(String id, Resolvable res) { - parentLM.addUnresolvedArea(id, res); + public boolean applyChanges(List oldList) { + log.warn("null implementation of applyChanges() called!"); + return false; } - /** - * Add the markers when adding an area. - */ - protected void addMarkers(boolean starting, boolean isfirst, boolean islast) { - // add markers - if (markers != null) { - addMarkerMap(markers, starting, isfirst, islast); - } + public LinkedList getChangedKnuthElements(List oldList, + /*int flaggedPenalty,*/ + int alignment) { + log.warn("null implementation of getChangeKnuthElement() called!"); + return null; } - /** - * Delegate adding marker to the parent layout manager. - * - * @see org.apache.fop.layoutmgr.LayoutManager - */ - public void addMarkerMap(Map marks, boolean starting, boolean isfirst, boolean islast) { - parentLM.addMarkerMap(marks, starting, isfirst, islast); + public int getWordSpaceIPD() { + log.warn("null implementation of getWordSpaceIPD() called!"); + return 0; } /** - * Delegate retrieve marker to the parent layout manager. - * - * @see org.apache.fop.layoutmgr.LayoutManager + * Return an Area which can contain the passed childArea. The childArea + * may not yet have any content, but it has essential traits set. + * In general, if the LayoutManager already has an Area it simply returns + * it. Otherwise, it makes a new Area of the appropriate class. + * It gets a parent area for its area by calling its parent LM. + * Finally, based on the dimensions of the parent area, it initializes + * its own area. This includes setting the content IPD and the maximum + * BPD. */ - public Marker retrieveMarker(String name, int pos, int boundary) { - return parentLM.retrieveMarker(name, pos, boundary); + public Area getParentArea(Area childArea) { + return null; } - /** - * Delegate getAreaTreeHandler to the parent layout manager. - * - * @see org.apache.fop.layoutmgr.LayoutManager - * @return the AreaTreeHandler object. - */ - public AreaTreeHandler getAreaTreeHandler() { - return parentLM.getAreaTreeHandler(); + public void addChildArea(Area childArea) { } - /** - * Handles retrieve-marker nodes as they occur. - * @param foNode FO node to check - * @return the original foNode or in case of a retrieve-marker the replaced - * FO node. null if the the replacement results in no nodes to be - * processed. - */ - private FONode handleRetrieveMarker(FONode foNode) { - if (foNode instanceof RetrieveMarker) { - RetrieveMarker rm = (RetrieveMarker) foNode; - Marker marker = retrieveMarker(rm.getRetrieveClassName(), - rm.getRetrievePosition(), - rm.getRetrieveBoundary()); - if (marker == null) { - return null; - } - rm.bindMarker(marker); - return rm; - } else { - return foNode; - } - } - /** * Convenience method: preload a number of child LMs * @param size the requested number of child LMs @@ -430,9 +321,12 @@ public abstract class AbstractLayoutManager implements LayoutManager, Constants Object theobj = fobjIter.next(); if (theobj instanceof FONode) { FONode foNode = (FONode) theobj; - foNode = handleRetrieveMarker(foNode); + if (foNode instanceof RetrieveMarker) { + foNode = getPSLM().resolveRetrieveMarker( + (RetrieveMarker) foNode); + } if (foNode != null) { - getAreaTreeHandler().getLayoutManagerMaker(). + getPSLM().getLayoutManagerMaker(). makeLayoutManagers(foNode, newLMs); } } @@ -440,6 +334,20 @@ public abstract class AbstractLayoutManager implements LayoutManager, Constants return newLMs; } + /** + * @see org.apache.fop.layoutmgr.PageSequenceLayoutManager#getPSLM + */ + public PageSequenceLayoutManager getPSLM() { + return parentLM.getPSLM(); + } + + /** + * @see org.apache.fop.layoutmgr.PageSequenceLayoutManager#getCurrentPV + */ + public PageViewport getCurrentPV() { + return getPSLM().getCurrentPV(); + } + /** * @see org.apache.fop.layoutmgr.LayoutManager#preLoadNext */ @@ -489,6 +397,4 @@ public abstract class AbstractLayoutManager implements LayoutManager, Constants addChildLM(lm); } } - } - diff --git a/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java b/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java new file mode 100644 index 000000000..57c1dba2e --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java @@ -0,0 +1,80 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr; + +import java.util.Iterator; +import java.util.LinkedList; + +public class AreaAdditionUtil { + + private static class StackingIter extends PositionIterator { + StackingIter(Iterator parentIter) { + super(parentIter); + } + + protected LayoutManager getLM(Object nextObj) { + return ((Position) nextObj).getLM(); + } + + protected Position getPos(Object nextObj) { + return ((Position) nextObj); + } + } + + public static void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { + LayoutManager childLM = null; + LayoutContext lc = new LayoutContext(0); + LayoutManager firstLM = null; + LayoutManager lastLM = null; + + // "unwrap" the NonLeafPositions stored in parentIter + // and put them in a new list; + LinkedList positionList = new LinkedList(); + Position pos; + while (parentIter.hasNext()) { + pos = (Position)parentIter.next(); + if (pos instanceof NonLeafPosition) { + // pos was created by a child of this FlowLM + positionList.add(((NonLeafPosition) pos).getPosition()); + lastLM = ((NonLeafPosition) pos).getPosition().getLM(); + if (firstLM == null) { + firstLM = lastLM; + } + } else { + // pos was created by this LM, so it must be ignored + } + } + + StackingIter childPosIter = new StackingIter(positionList.listIterator()); + while ((childLM = childPosIter.getNextChildLM()) != null) { + // Add the block areas to Area + lc.setFlags(LayoutContext.FIRST_AREA, childLM == firstLM); + lc.setFlags(LayoutContext.LAST_AREA, childLM == lastLM); + // set space before for the first LM, in order to implement + // display-align = center or after + lc.setSpaceBefore((childLM == firstLM ? layoutContext.getSpaceBefore() : 0)); + // set space after for each LM, in order to implement + // display-align = distribute + lc.setSpaceAfter(layoutContext.getSpaceAfter()); + lc.setStackLimit(layoutContext.getStackLimit()); + childLM.addAreas(childPosIter, lc); + } + } + +} diff --git a/src/java/org/apache/fop/layoutmgr/BasicLinkLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BasicLinkLayoutManager.java index 528ce4e6f..38a4b2957 100644 --- a/src/java/org/apache/fop/layoutmgr/BasicLinkLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BasicLinkLayoutManager.java @@ -51,13 +51,13 @@ public class BasicLinkLayoutManager extends InlineLayoutManager { if (fobj.getExternalDestination() != null) { area.addTrait(Trait.EXTERNAL_LINK, fobj.getExternalDestination()); } else { - String link = fobj.getInternalDestination(); - PageViewport page = parentLM.resolveRefID(link); + String idref = fobj.getInternalDestination(); + PageViewport page = getPSLM().getFirstPVWithID(idref); if (page != null) { area.addTrait(Trait.INTERNAL_LINK, page.getKey()); } else { - LinkResolver res = new LinkResolver(link, area); - parentLM.addUnresolvedArea(link, res); + LinkResolver res = new LinkResolver(idref, area); + getPSLM().addUnresolvedArea(idref, res); } } } diff --git a/src/java/org/apache/fop/layoutmgr/BidiLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BidiLayoutManager.java index c20237ac2..bffc4c758 100644 --- a/src/java/org/apache/fop/layoutmgr/BidiLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BidiLayoutManager.java @@ -37,7 +37,6 @@ public class BidiLayoutManager extends LeafNodeLayoutManager { public BidiLayoutManager(BidiOverride node, InlineLayoutManager cLM) { super(node); children = new ArrayList(); - setFObj(node); /* for (int count = cLM.size() - 1; count >= 0; count--) { InlineArea ia = cLM.get(count); diff --git a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java index 36d338943..1fda2cfc4 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java @@ -18,14 +18,15 @@ package org.apache.fop.layoutmgr; +import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.awt.Point; import java.awt.geom.Rectangle2D; import org.apache.fop.area.Area; import org.apache.fop.area.BlockViewport; import org.apache.fop.area.Block; -import org.apache.fop.area.PageViewport; import org.apache.fop.area.Trait; import org.apache.fop.fo.flow.BlockContainer; import org.apache.fop.fo.properties.CommonAbsolutePosition; @@ -40,7 +41,6 @@ import org.apache.fop.traits.SpaceVal; * LayoutManager for a block-container FO. */ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { - private BlockContainer fobj; private BlockViewport viewportBlockArea; private Block referenceArea; @@ -56,7 +56,7 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { private int vpContentIPD; private int vpContentBPD; private int usedBPD; - + // When viewport should grow with the content. private boolean autoHeight = true; @@ -73,6 +73,15 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { //TODO space-before|after: handle space-resolution rules private MinOptMax foBlockSpaceBefore; private MinOptMax foBlockSpaceAfter; + + private boolean bBreakBeforeServed = false; + private boolean bSpaceBeforeServed = false; + + /*LF*/ + /** Only used to store the original list when createUnitElements is called */ + //TODO Maybe pull up as protected member if also used in this class (JM) + private LinkedList storedList = null; + /** * Create a new block container layout manager. @@ -80,45 +89,40 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { */ public BlockContainerLayoutManager(BlockContainer node) { super(node); - fobj = node; } - /** - * @return the currently applicable page viewport - */ - protected PageViewport getPageViewport() { - LayoutManager lm = this; - while (lm != null && !(lm instanceof PageSequenceLayoutManager)) { - lm = lm.getParent(); - } - if (lm == null) { - return null; - } else { - return ((PageSequenceLayoutManager)lm).getCurrentPageViewport(); - } - } - /** * @see org.apache.fop.layoutmgr.AbstractLayoutManager#initProperties() */ protected void initProperties() { - abProps = fobj.getCommonAbsolutePosition(); - foBlockSpaceBefore = new SpaceVal(fobj.getCommonMarginBlock().spaceBefore).getSpace(); - foBlockSpaceAfter = new SpaceVal(fobj.getCommonMarginBlock().spaceAfter).getSpace(); + abProps = getBlockContainerFO().getCommonAbsolutePosition(); + foBlockSpaceBefore = new SpaceVal(getBlockContainerFO().getCommonMarginBlock().spaceBefore).getSpace(); + foBlockSpaceAfter = new SpaceVal(getBlockContainerFO().getCommonMarginBlock().spaceAfter).getSpace(); - boolean rotated = (fobj.getReferenceOrientation() % 180 != 0); + boolean rotated = (getBlockContainerFO().getReferenceOrientation() % 180 != 0); if (rotated) { - height = fobj.getInlineProgressionDimension().getOptimum().getLength(); - width = fobj.getBlockProgressionDimension().getOptimum().getLength(); + height = getBlockContainerFO().getInlineProgressionDimension().getOptimum().getLength(); + width = getBlockContainerFO().getBlockProgressionDimension().getOptimum().getLength(); } else { - height = fobj.getBlockProgressionDimension().getOptimum().getLength(); - width = fobj.getInlineProgressionDimension().getOptimum().getLength(); + height = getBlockContainerFO().getBlockProgressionDimension().getOptimum().getLength(); + width = getBlockContainerFO().getInlineProgressionDimension().getOptimum().getLength(); } + +/*LF*/ bpUnit = 0; //layoutProps.blockProgressionUnit; +/*LF*/ if (bpUnit == 0) { +/*LF*/ // use optimum space values +/*LF*/ adjustedSpaceBefore = getBlockContainerFO().getCommonMarginBlock().spaceBefore.getSpace().getOptimum().getLength().getValue(); +/*LF*/ adjustedSpaceAfter = getBlockContainerFO().getCommonMarginBlock().spaceAfter.getSpace().getOptimum().getLength().getValue(); +/*LF*/ } else { +/*LF*/ // use minimum space values +/*LF*/ adjustedSpaceBefore = getBlockContainerFO().getCommonMarginBlock().spaceBefore.getSpace().getMinimum().getLength().getValue(); +/*LF*/ adjustedSpaceAfter = getBlockContainerFO().getCommonMarginBlock().spaceAfter.getSpace().getMinimum().getLength().getValue(); +/*LF*/ } } /** @return the content IPD */ protected int getRotatedIPD() { - return fobj.getInlineProgressionDimension().getOptimum().getLength().getValue(); + return getBlockContainerFO().getInlineProgressionDimension().getOptimum().getLength().getValue(); } private int getSpaceBefore() { @@ -127,16 +131,16 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { private int getBPIndents() { int indents = 0; - indents += fobj.getCommonMarginBlock().spaceBefore.getOptimum().getLength().getValue(); - indents += fobj.getCommonMarginBlock().spaceAfter.getOptimum().getLength().getValue(); - indents += fobj.getCommonBorderPaddingBackground().getBPPaddingAndBorder(false); + indents += getBlockContainerFO().getCommonMarginBlock().spaceBefore.getOptimum().getLength().getValue(); + indents += getBlockContainerFO().getCommonMarginBlock().spaceAfter.getOptimum().getLength().getValue(); + indents += getBlockContainerFO().getCommonBorderPaddingBackground().getBPPaddingAndBorder(false); return indents; } private int getIPIndents() { int iIndents = 0; - iIndents += fobj.getCommonMarginBlock().startIndent.getValue(); - iIndents += fobj.getCommonMarginBlock().endIndent.getValue(); + iIndents += getBlockContainerFO().getCommonMarginBlock().startIndent.getValue(); + iIndents += getBlockContainerFO().getCommonMarginBlock().endIndent.getValue(); return iIndents; } @@ -149,6 +153,396 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { return (abProps.absolutePosition == EN_FIXED); } + /** + * @see org.apache.fop.layoutmgr.LayoutManager#getNextKnuthElements(org.apache.fop.layoutmgr.LayoutContext, int) + */ + public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + if (isAbsoluteOrFixed()) { + return getNextKnuthElementsAbsolute(context, alignment); + } + + autoHeight = false; + boolean rotated = (getBlockContainerFO().getReferenceOrientation() % 180 != 0); //vals[0] == 0.0; + referenceIPD = context.getRefIPD(); + int maxbpd = context.getStackLimit().opt; + int allocBPD, allocIPD; + if (height.getEnum() != EN_AUTO) { + allocBPD = height.getValue(); //this is the content-height + allocBPD += getBPIndents(); + } else { + allocBPD = maxbpd; + autoHeight = true; + } + if (width.getEnum() != EN_AUTO) { + allocIPD = width.getValue(); //this is the content-width + allocIPD += getIPIndents(); + } else { + allocIPD = referenceIPD; + } + + vpContentBPD = allocBPD - getBPIndents(); + vpContentIPD = allocIPD - getIPIndents(); + + double contentRectOffsetX = 0; + contentRectOffsetX += getBlockContainerFO().getCommonMarginBlock().startIndent.getValue(); + double contentRectOffsetY = 0; + //contentRectOffsetY += getBlockContainerFO().getCommonMarginBlock().startIndent.getValue(); + //contentRectOffsetY += getSpaceBefore(); + contentRectOffsetY += getBlockContainerFO().getCommonBorderPaddingBackground().getBorderBeforeWidth(false); + contentRectOffsetY += getBlockContainerFO().getCommonBorderPaddingBackground().getPaddingBefore(false); + + Rectangle2D rect = new Rectangle2D.Double( + contentRectOffsetX, contentRectOffsetY, + vpContentIPD, vpContentBPD); + relDims = new FODimension(0, 0); + absoluteCTM = CTM.getCTMandRelDims(getBlockContainerFO().getReferenceOrientation(), + getBlockContainerFO().getWritingMode(), rect, relDims); + + MinOptMax stackLimit = new MinOptMax(relDims.bpd); + + LinkedList returnedList = null; + LinkedList contentList = new LinkedList(); + LinkedList returnList = new LinkedList(); + Position returnPosition = new NonLeafPosition(this, null); + + if (!bBreakBeforeServed) { + try { + if (addKnuthElementsForBreakBefore(returnList, returnPosition)) { + return returnList; + } + } finally { + bBreakBeforeServed = true; + } + } + + if (!bSpaceBeforeServed) { + addKnuthElementsForSpaceBefore(returnList, returnPosition, alignment); + bSpaceBeforeServed = true; + } + + addKnuthElementsForBorderPaddingBefore(returnList, returnPosition); + + if (autoHeight) { + BlockLevelLayoutManager curLM; // currently active LM + BlockLevelLayoutManager prevLM = null; // previously active LM + while ((curLM = (BlockLevelLayoutManager) getChildLM()) != null) { + LayoutContext childLC = new LayoutContext(0); + // curLM is a ? + childLC.setStackLimit(MinOptMax.subtract(context + .getStackLimit(), stackLimit)); + childLC.setRefIPD(relDims.ipd); + + // get elements from curLM + returnedList = curLM.getNextKnuthElements(childLC, alignment); + if (returnedList.size() == 1 + && ((KnuthElement) returnedList.getFirst()).isPenalty() + && ((KnuthPenalty) returnedList.getFirst()).getP() == -KnuthElement.INFINITE) { + // a descendant of this block has break-before + if (returnList.size() == 0) { + // the first child (or its first child ...) has + // break-before; + // all this block, including space before, will be put in + // the + // following page + bSpaceBeforeServed = false; + } + contentList.addAll(returnedList); + + // "wrap" the Position inside each element + // moving the elements from contentList to returnList + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } else { + if (prevLM != null) { + // there is a block handled by prevLM + // before the one handled by curLM + if (mustKeepTogether() + || prevLM.mustKeepWithNext() + || curLM.mustKeepWithPrevious()) { + // add an infinite penalty to forbid a break between + // blocks + contentList.add(new KnuthPenalty(0, + KnuthElement.INFINITE, false, + new Position(this), false)); + } else if (!((KnuthElement) contentList.getLast()).isGlue()) { + // add a null penalty to allow a break between blocks + contentList.add(new KnuthPenalty(0, 0, false, + new Position(this), false)); + } else { + // the last element in contentList is a glue; + // it is a feasible breakpoint, there is no need to add + // a penalty + } + } + contentList.addAll(returnedList); + if (returnedList.size() == 0) { + //Avoid NoSuchElementException below (happens with empty blocks) + continue; + } + if (((KnuthElement) returnedList.getLast()).isPenalty() + && ((KnuthPenalty) returnedList.getLast()).getP() == -KnuthElement.INFINITE) { + // a descendant of this block has break-after + if (curLM.isFinished()) { + // there is no other content in this block; + // it's useless to add space after before a page break + setFinished(true); + } + + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } + } + prevLM = curLM; + } + + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + } else { + MinOptMax range = new MinOptMax(relDims.ipd); + BlockContainerBreaker breaker = new BlockContainerBreaker(this, range); + breaker.doLayout(relDims.bpd); + boolean contentOverflows = false; + if (!breaker.isEmpty()) { + contentOverflows = (breaker.deferredAlg.getPageBreaks().size() > 1); + } + + Position bcPosition = new BlockContainerPosition(this, breaker); + returnList.add(new KnuthBox(vpContentBPD, bcPosition, false)); + //TODO Handle min/opt/max for block-progression-dimension + /* These two elements will be used to add stretchability to the above box + returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, returnPosition, false)); + returnList.add(new KnuthGlue(0, 1 * constantLineHeight, 0, + LINE_NUMBER_ADJUSTMENT, returnPosition, false)); + */ + + if (contentOverflows) { + log.warn("Contents overflow block-container viewport: clipping"); + if (getBlockContainerFO().getOverflow() == EN_HIDDEN) { + clip = true; + } else if (getBlockContainerFO().getOverflow() == EN_ERROR_IF_OVERFLOW) { + //TODO Throw layout exception + clip = true; + } + } + } + addKnuthElementsForBorderPaddingAfter(returnList, returnPosition); + addKnuthElementsForSpaceAfter(returnList, returnPosition, alignment); + addKnuthElementsForBreakAfter(returnList, returnPosition); + + setFinished(true); + return returnList; + } + + private LinkedList getNextKnuthElementsAbsolute(LayoutContext context, int alignment) { + MinOptMax stackSize = new MinOptMax(); + autoHeight = false; + + Point offset = getAbsOffset(); + int allocBPD, allocIPD; + if (height.getEnum() != EN_AUTO) { + allocBPD = height.getValue(); //this is the content-height + allocBPD += getBPIndents(); + } else { + allocBPD = 0; + if (abProps.bottom.getEnum() != EN_AUTO) { + if (isFixed()) { + allocBPD = (int)getCurrentPV().getViewArea().getHeight(); + } else { + allocBPD = context.getStackLimit().opt; + } + allocBPD -= offset.y; + if (abProps.bottom.getEnum() != EN_AUTO) { + allocBPD -= abProps.bottom.getValue(); + } + } else { + autoHeight = true; + } + } + if (width.getEnum() != EN_AUTO) { + allocIPD = width.getValue(); //this is the content-width + allocIPD += getIPIndents(); + } else { + if (isFixed()) { + allocIPD = (int)getCurrentPV().getViewArea().getWidth(); + } else { + allocIPD = context.getRefIPD(); + } + if (abProps.left.getEnum() != EN_AUTO) { + allocIPD -= abProps.left.getValue(); + } + if (abProps.right.getEnum() != EN_AUTO) { + allocIPD -= abProps.right.getValue(); + } + } + + vpContentBPD = allocBPD - getBPIndents(); + vpContentIPD = allocIPD - getIPIndents(); + + double contentRectOffsetX = offset.getX(); + contentRectOffsetX += getBlockContainerFO().getCommonMarginBlock().startIndent.getValue(); + double contentRectOffsetY = offset.getY(); + contentRectOffsetY += getSpaceBefore(); + contentRectOffsetY += getBlockContainerFO().getCommonBorderPaddingBackground().getBorderBeforeWidth(false); + contentRectOffsetY += getBlockContainerFO().getCommonBorderPaddingBackground().getPaddingBefore(false); + + Rectangle2D rect = new Rectangle2D.Double( + contentRectOffsetX, contentRectOffsetY, + vpContentIPD, vpContentBPD); + relDims = new FODimension(0, 0); + absoluteCTM = CTM.getCTMandRelDims( + getBlockContainerFO().getReferenceOrientation(), + getBlockContainerFO().getWritingMode(), + rect, relDims); + + MinOptMax range = new MinOptMax(relDims.ipd); + BlockContainerBreaker breaker = new BlockContainerBreaker(this, range); + breaker.doLayout(relDims.bpd); + boolean contentOverflows = breaker.isOverflow(); + LinkedList returnList = new LinkedList(); + if (!breaker.isEmpty()) { + usedBPD = relDims.bpd - breaker.getDifferenceOfFirstPart(); + + Position bcPosition = new BlockContainerPosition(this, breaker); + returnList.add(new KnuthBox(0, bcPosition, false)); + + //TODO Maybe check for page overflow when autoHeight=true + if (!autoHeight & (contentOverflows/*usedBPD > relDims.bpd*/)) { + log.warn("Contents overflow block-container viewport: clipping"); + if (getBlockContainerFO().getOverflow() == EN_HIDDEN) { + clip = true; + } else if (getBlockContainerFO().getOverflow() == EN_ERROR_IF_OVERFLOW) { + //TODO Throw layout exception + clip = true; + } + } + } + + setFinished(true); + return returnList; + } + + private class BlockContainerPosition extends NonLeafPosition { + + private BlockContainerBreaker breaker; + + public BlockContainerPosition(LayoutManager lm, BlockContainerBreaker breaker) { + super(lm, null); + this.breaker = breaker; + } + + public BlockContainerBreaker getBreaker() { + return this.breaker; + } + + } + + private class BlockContainerBreaker extends AbstractBreaker { + + private BlockContainerLayoutManager bclm; + private MinOptMax ipd; + + //Info for deferred adding of areas + private PageBreakingAlgorithm deferredAlg; + private BlockSequence deferredOriginalList; + private BlockSequence deferredEffectiveList; + + public BlockContainerBreaker(BlockContainerLayoutManager bclm, MinOptMax ipd) { + this.bclm = bclm; + this.ipd = ipd; + } + + public int getDifferenceOfFirstPart() { + PageBreakPosition pbp = (PageBreakPosition)this.deferredAlg.getPageBreaks().getFirst(); + return pbp.difference; + } + + public boolean isOverflow() { + if (isEmpty()) { + return false; + } else { + return (deferredAlg.getPageBreaks().size() > 1); + } + } + + protected LayoutManager getTopLevelLM() { + return bclm; + } + + protected LayoutContext createLayoutContext() { + LayoutContext lc = super.createLayoutContext(); + lc.setRefIPD(ipd.opt); + return lc; + } + + protected LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + LayoutManager curLM; // currently active LM + LinkedList returnList = new LinkedList(); + + while ((curLM = getChildLM()) != null) { + LayoutContext childLC = new LayoutContext(0); + childLC.setStackLimit(context.getStackLimit()); + childLC.setRefIPD(context.getRefIPD()); + + LinkedList returnedList = null; + if (!curLM.isFinished()) { + returnedList = curLM.getNextKnuthElements(childLC, alignment); + } + if (returnedList != null) { + bclm.wrapPositionElements(returnedList, returnList); + //returnList.addAll(returnedList); + } + } + setFinished(true); + return returnList; + } + + protected int getCurrentDisplayAlign() { + return getBlockContainerFO().getDisplayAlign(); + } + + protected boolean hasMoreContent() { + return !isFinished(); + } + + protected void addAreas(PositionIterator posIter, LayoutContext context) { + AreaAdditionUtil.addAreas(posIter, context); + } + + protected void doPhase3(PageBreakingAlgorithm alg, int partCount, + BlockSequence originalList, BlockSequence effectiveList) { + //Defer adding of areas until addAreas is called by the parent LM + this.deferredAlg = alg; + this.deferredOriginalList = originalList; + this.deferredEffectiveList = effectiveList; + } + + protected void finishPart() { + //nop for bclm + } + + protected LayoutManager getCurrentChildLM() { + return curChildLM; + } + + public void addContainedAreas() { + if (isEmpty()) { + return; + } + //Rendering all parts (not just the first) at once for the case where the parts that + //overflow should be visible. + //TODO Check if this has any unwanted side-effects. Feels a bit like a hack. + addAreas(this.deferredAlg, + /*1*/ this.deferredAlg.getPageBreaks().size(), + this.deferredOriginalList, this.deferredEffectiveList); + } + + } + /** * @see org.apache.fop.layoutmgr.LayoutManager#getNextBreakPoss(org.apache.fop.layoutmgr.LayoutContext) */ @@ -159,7 +553,7 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { } autoHeight = false; - boolean rotated = (fobj.getReferenceOrientation() % 180 != 0); //vals[0] == 0.0; + boolean rotated = (getBlockContainerFO().getReferenceOrientation() % 180 != 0); //vals[0] == 0.0; referenceIPD = context.getRefIPD(); int maxbpd = context.getStackLimit().opt; int allocBPD, allocIPD; @@ -181,19 +575,19 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { vpContentIPD = allocIPD - getIPIndents(); double contentRectOffsetX = 0; - contentRectOffsetX += fobj.getCommonMarginBlock().startIndent.getValue(); + contentRectOffsetX += getBlockContainerFO().getCommonMarginBlock().startIndent.getValue(); double contentRectOffsetY = 0; - //contentRectOffsetY += fobj.getCommonMarginBlock().startIndent.getValue(); + //contentRectOffsetY += getBlockContainerFO().getCommonMarginBlock().startIndent.getValue(); //contentRectOffsetY += getSpaceBefore(); - contentRectOffsetY += fobj.getCommonBorderPaddingBackground().getBorderBeforeWidth(false); - contentRectOffsetY += fobj.getCommonBorderPaddingBackground().getPaddingBefore(false); + contentRectOffsetY += getBlockContainerFO().getCommonBorderPaddingBackground().getBorderBeforeWidth(false); + contentRectOffsetY += getBlockContainerFO().getCommonBorderPaddingBackground().getPaddingBefore(false); Rectangle2D rect = new Rectangle2D.Double( contentRectOffsetX, contentRectOffsetY, vpContentIPD, vpContentBPD); relDims = new FODimension(0, 0); - absoluteCTM = CTM.getCTMandRelDims(fobj.getReferenceOrientation(), - fobj.getWritingMode(), rect, relDims); + absoluteCTM = CTM.getCTMandRelDims(getBlockContainerFO().getReferenceOrientation(), + getBlockContainerFO().getWritingMode(), rect, relDims); //double[] vals = absoluteCTM.toArray(); MinOptMax stackLimit; @@ -228,10 +622,10 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { BreakPoss lastPos = null; //TODO fix layout dimensions! - fobj.setLayoutDimension(PercentBase.BLOCK_IPD, allocIPD); - fobj.setLayoutDimension(PercentBase.BLOCK_BPD, allocBPD); - fobj.setLayoutDimension(PercentBase.REFERENCE_AREA_IPD, relDims.ipd); - fobj.setLayoutDimension(PercentBase.REFERENCE_AREA_BPD, relDims.bpd); + getBlockContainerFO().setLayoutDimension(PercentBase.BLOCK_IPD, allocIPD); + getBlockContainerFO().setLayoutDimension(PercentBase.BLOCK_BPD, allocBPD); + getBlockContainerFO().setLayoutDimension(PercentBase.REFERENCE_AREA_IPD, relDims.ipd); + getBlockContainerFO().setLayoutDimension(PercentBase.REFERENCE_AREA_BPD, relDims.bpd); while ((curLM = getChildLM()) != null) { //Treat bc with fixed BPD as non-breakable @@ -348,7 +742,7 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { allocBPD = 0; if (abProps.bottom.getEnum() != EN_AUTO) { if (isFixed()) { - allocBPD = (int)getPageViewport().getViewArea().getHeight(); + allocBPD = (int)getCurrentPV().getViewArea().getHeight(); } else { allocBPD = context.getStackLimit().opt; } @@ -365,7 +759,7 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { allocIPD += getIPIndents(); } else { if (isFixed()) { - allocIPD = (int)getPageViewport().getViewArea().getWidth(); + allocIPD = (int)getCurrentPV().getViewArea().getWidth(); } else { allocIPD = context.getRefIPD(); } @@ -381,19 +775,19 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { vpContentIPD = allocIPD - getIPIndents(); double contentRectOffsetX = offset.getX(); - contentRectOffsetX += fobj.getCommonMarginBlock().startIndent.getValue(); + contentRectOffsetX += getBlockContainerFO().getCommonMarginBlock().startIndent.getValue(); double contentRectOffsetY = offset.getY(); contentRectOffsetY += getSpaceBefore(); - contentRectOffsetY += fobj.getCommonBorderPaddingBackground().getBorderBeforeWidth(false); - contentRectOffsetY += fobj.getCommonBorderPaddingBackground().getPaddingBefore(false); + contentRectOffsetY += getBlockContainerFO().getCommonBorderPaddingBackground().getBorderBeforeWidth(false); + contentRectOffsetY += getBlockContainerFO().getCommonBorderPaddingBackground().getPaddingBefore(false); Rectangle2D rect = new Rectangle2D.Double( contentRectOffsetX, contentRectOffsetY, vpContentIPD, vpContentBPD); relDims = new FODimension(0, 0); absoluteCTM = CTM.getCTMandRelDims( - fobj.getReferenceOrientation(), - fobj.getWritingMode(), + getBlockContainerFO().getReferenceOrientation(), + getBlockContainerFO().getWritingMode(), rect, relDims); while ((curLM = getChildLM()) != null) { @@ -423,9 +817,9 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { //TODO Maybe check for page overflow when autoHeight=true if (!autoHeight & (usedBPD > relDims.bpd)) { log.warn("Contents overflow block-container viewport: clipping"); - if (fobj.getOverflow() == EN_HIDDEN) { + if (getBlockContainerFO().getOverflow() == EN_HIDDEN) { clip = true; - } else if (fobj.getOverflow() == EN_ERROR_IF_OVERFLOW) { + } else if (getBlockContainerFO().getOverflow() == EN_ERROR_IF_OVERFLOW) { //TODO Throw layout exception clip = true; } @@ -438,6 +832,186 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { * @see org.apache.fop.layoutmgr.LayoutManager#addAreas(org.apache.fop.layoutmgr.PositionIterator, org.apache.fop.layoutmgr.LayoutContext) */ public void addAreas(PositionIterator parentIter, + LayoutContext layoutContext) { + getParentArea(null); + + // if this will create the first block area in a page + // and display-align is bottom or center, add space before + if (layoutContext.getSpaceBefore() > 0) { + addBlockSpacing(0.0, new MinOptMax(layoutContext.getSpaceBefore())); + } + + getPSLM().addIDToPage(getBlockContainerFO().getId()); + //addMarkersToPV(true, bp1.isFirstArea(), bp1.isLastArea()); + getCurrentPV().addMarkers(markers, true, true, false); + + LayoutManager childLM = null; + LayoutManager lastLM = null; + LayoutContext lc = new LayoutContext(0); + // set space after in the LayoutContext for children + if (layoutContext.getSpaceAfter() > 0) { + lc.setSpaceAfter(layoutContext.getSpaceAfter()); + } + BlockContainerPosition bcpos = null; + PositionIterator childPosIter; + + // "unwrap" the NonLeafPositions stored in parentIter + // and put them in a new list; + LinkedList positionList = new LinkedList(); + Position pos; + boolean bSpaceBefore = false; + boolean bSpaceAfter = false; + while (parentIter.hasNext()) { + pos = (Position) parentIter.next(); + /* LF *///System.out.println("pos = " + pos.getClass().getName()); + Position innerPosition = ((NonLeafPosition) pos).getPosition(); + if (pos instanceof BlockContainerPosition) { + if (bcpos != null) { + throw new IllegalStateException("Only one BlockContainerPosition allowed"); + } + bcpos = (BlockContainerPosition)pos; + //Add child areas inside the reference area + //bcpos.getBreaker().addContainedAreas(); + } else if (innerPosition == null) { + // pos was created by this BCLM and was inside an element + // representing space before or after + // this means the space was not discarded + if (positionList.size() == 0) { + // pos was in the element representing space-before + bSpaceBefore = true; + /* LF *///System.out.println(" space-before"); + } else { + // pos was in the element representing space-after + bSpaceAfter = true; + /* LF *///System.out.println(" space-after"); + } + } else if (innerPosition.getLM() == this + && !(innerPosition instanceof MappingPosition)) { + // pos was created by this BlockLM and was inside a penalty + // allowing or forbidding a page break + // nothing to do + /* LF *///System.out.println(" penalty"); + } else { + // innerPosition was created by another LM + positionList.add(innerPosition); + lastLM = innerPosition.getLM(); + /* LF *///System.out.println(" " + + // innerPosition.getClass().getName()); + } + } + + if (bcpos == null) { + + if (bpUnit == 0) { + // the Positions in positionList were inside the elements + // created by the LineLM + childPosIter = new StackingIter(positionList.listIterator()); + } else { + // the Positions in positionList were inside the elements + // created by the BCLM in the createUnitElements() method + //if (((Position) positionList.getLast()) instanceof + // LeafPosition) { + // // the last item inside positionList is a LeafPosition + // // (a LineBreakPosition, more precisely); this means that + // // the whole paragraph is on the same page + // System.out.println("paragrafo intero"); + // childPosIter = new KnuthPossPosIter(storedList, 0, + // storedList.size()); + //} else { + // // the last item inside positionList is a Position; + // // this means that the paragraph has been split + // // between consecutive pages + LinkedList splitList = new LinkedList(); + int splitLength = 0; + int iFirst = ((MappingPosition) positionList.getFirst()).getFirstIndex(); + int iLast = ((MappingPosition) positionList.getLast()).getLastIndex(); + // copy from storedList to splitList all the elements from + // iFirst to iLast + ListIterator storedListIterator = storedList.listIterator(iFirst); + while (storedListIterator.nextIndex() <= iLast) { + KnuthElement element = (KnuthElement) storedListIterator + .next(); + // some elements in storedList (i.e. penalty items) were created + // by this BlockLM, and must be ignored + if (element.getLayoutManager() != this) { + splitList.add(element); + splitLength += element.getW(); + lastLM = element.getLayoutManager(); + } + } + //System.out.println("addAreas riferito a storedList da " + + // iFirst + " a " + iLast); + //System.out.println("splitLength= " + splitLength + // + " (" + neededUnits(splitLength) + " unita') " + // + (neededUnits(splitLength) * bpUnit - splitLength) + " spazi"); + // add space before and / or after the paragraph + // to reach a multiple of bpUnit + if (bSpaceBefore && bSpaceAfter) { + foBlockSpaceBefore = new SpaceVal(getBlockContainerFO().getCommonMarginBlock().spaceBefore).getSpace(); + foBlockSpaceAfter = new SpaceVal(getBlockContainerFO().getCommonMarginBlock().spaceAfter).getSpace(); + adjustedSpaceBefore = (neededUnits(splitLength + + foBlockSpaceBefore.min + + foBlockSpaceAfter.min) + * bpUnit - splitLength) / 2; + adjustedSpaceAfter = neededUnits(splitLength + + foBlockSpaceBefore.min + + foBlockSpaceAfter.min) + * bpUnit - splitLength - adjustedSpaceBefore; + } else if (bSpaceBefore) { + adjustedSpaceBefore = neededUnits(splitLength + + foBlockSpaceBefore.min) + * bpUnit - splitLength; + } else { + adjustedSpaceAfter = neededUnits(splitLength + + foBlockSpaceAfter.min) + * bpUnit - splitLength; + } + //System.out.println("spazio prima = " + adjustedSpaceBefore + // + " spazio dopo = " + adjustedSpaceAfter + " totale = " + + // (adjustedSpaceBefore + adjustedSpaceAfter + splitLength)); + childPosIter = new KnuthPossPosIter(splitList, 0, splitList + .size()); + //} + } + + // if adjusted space before + if (bSpaceBefore) { + addBlockSpacing(0, new MinOptMax(adjustedSpaceBefore)); + } + + while ((childLM = childPosIter.getNextChildLM()) != null) { + // set last area flag + lc.setFlags(LayoutContext.LAST_AREA, + (layoutContext.isLastArea() && childLM == lastLM)); + /*LF*/lc.setStackLimit(layoutContext.getStackLimit()); + // Add the line areas to Area + childLM.addAreas(childPosIter, lc); + } + } else { + // if adjusted space before + if (bSpaceBefore) { + addBlockSpacing(0, new MinOptMax(adjustedSpaceBefore)); + } + //Add child areas inside the reference area + bcpos.getBreaker().addContainedAreas(); + } + + int bIndents = getBlockContainerFO().getCommonBorderPaddingBackground().getBPPaddingAndBorder(false); + + getCurrentPV().addMarkers(markers, false, false, true); + + flush(); + + // if adjusted space after + if (bSpaceAfter) { + addBlockSpacing(0, new MinOptMax(adjustedSpaceAfter)); + } + + viewportBlockArea = null; + referenceArea = null; + } + + public void addAreasOLDOLDOLD(PositionIterator parentIter, LayoutContext layoutContext) { getParentArea(null); @@ -450,8 +1024,8 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { }*/ BreakPoss bp1 = (BreakPoss)parentIter.peekNext(); - addID(fobj.getId()); - addMarkers(true, bp1.isFirstArea(), bp1.isLastArea()); + getPSLM().addIDToPage(getBlockContainerFO().getId()); + getCurrentPV().addMarkers(markers, true, bp1.isFirstArea(), bp1.isLastArea()); LayoutManager childLM; int iStartPos = 0; @@ -469,12 +1043,12 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { } flush(); - addMarkers(true, bp1.isFirstArea(), bp1.isLastArea()); + getCurrentPV().addMarkers(markers, true, bp1.isFirstArea(), bp1.isLastArea()); /* if (!isAbsoluteOrFixed()) { // if adjusted space after - foBlockSpaceAfter = new SpaceVal(fobj.getCommonMarginBlock().spaceAfter).getSpace(); + foBlockSpaceAfter = new SpaceVal(getBlockContainerFO().getCommonMarginBlock().spaceAfter).getSpace(); addBlockSpacing(adjust, foBlockSpaceAfter); }*/ @@ -501,11 +1075,11 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { viewportBlockArea.setBPD(vpContentBPD); } - TraitSetter.addBorders(viewportBlockArea, fobj.getCommonBorderPaddingBackground()); - TraitSetter.addBackground(viewportBlockArea, fobj.getCommonBorderPaddingBackground()); + TraitSetter.addBorders(viewportBlockArea, getBlockContainerFO().getCommonBorderPaddingBackground()); + TraitSetter.addBackground(viewportBlockArea, getBlockContainerFO().getCommonBorderPaddingBackground()); TraitSetter.addMargins(viewportBlockArea, - fobj.getCommonBorderPaddingBackground(), - fobj.getCommonMarginBlock()); + getBlockContainerFO().getCommonBorderPaddingBackground(), + getBlockContainerFO().getCommonMarginBlock()); viewportBlockArea.setCTM(absoluteCTM); viewportBlockArea.setClip(clip); @@ -574,15 +1148,16 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { //Handle display-align now that the used BPD can be determined usedBPD = referenceArea.getAllocBPD(); + /* done by the breaker now by inserting additional boxes if (!autoHeight & (usedBPD > 0)) { - if (fobj.getDisplayAlign() == EN_CENTER) { + if (getBlockContainerFO().getDisplayAlign() == EN_CENTER) { viewportBlockArea.setCTM(viewportBlockArea.getCTM().multiply( new CTM().translate(0, (relDims.bpd - usedBPD) / 2))); - } else if (fobj.getDisplayAlign() == EN_AFTER) { + } else if (getBlockContainerFO().getDisplayAlign() == EN_AFTER) { viewportBlockArea.setCTM(viewportBlockArea.getCTM().multiply( new CTM().translate(0, (relDims.bpd - usedBPD)))); } - } + }*/ // Fake a 0 height for absolute positioned blocks. int saveBPD = viewportBlockArea.getBPD(); @@ -595,6 +1170,54 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager { viewportBlockArea.setBPD(saveBPD); } } - + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#negotiateBPDAdjustment(int, org.apache.fop.layoutmgr.KnuthElement) + */ + public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) { + // TODO Auto-generated method stub + return 0; + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#discardSpace(org.apache.fop.layoutmgr.KnuthGlue) + */ + public void discardSpace(KnuthGlue spaceGlue) { + // TODO Auto-generated method stub + + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepTogether() + */ + 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(); + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithPrevious() + */ + public boolean mustKeepWithPrevious() { + return !getBlockContainerFO().getKeepWithPrevious().getWithinPage().isAuto() + || !getBlockContainerFO().getKeepWithPrevious().getWithinColumn().isAuto(); + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithNext() + */ + public boolean mustKeepWithNext() { + return !getBlockContainerFO().getKeepWithNext().getWithinPage().isAuto() + || !getBlockContainerFO().getKeepWithNext().getWithinColumn().isAuto(); + } + + /** + * convenience method that returns the BlockContainer node + */ + protected BlockContainer getBlockContainerFO() { + return (BlockContainer) fobj; + } } diff --git a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java index 74e29f784..5334bab97 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java @@ -18,6 +18,7 @@ package org.apache.fop.layoutmgr; +import java.util.LinkedList; import java.util.ListIterator; import java.util.List; @@ -35,9 +36,7 @@ import org.apache.fop.traits.MinOptMax; public class BlockLayoutManager extends BlockStackingLayoutManager { private static final int FINISHED_LEAF_POS = -2; - - private org.apache.fop.fo.flow.Block fobj; - + private Block curBlockArea; /** Iterator over the child layout managers. */ @@ -64,13 +63,14 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { private int iStartPos = 0; - private int referenceIPD = 0; //private int contentIPD = 0; /** The list of child BreakPoss instances. */ protected List childBreaks = new java.util.ArrayList(); private boolean isfirst = true; + + private LineLayoutManager childLLM = null; /** * Creates a new BlockLayoutManager. @@ -78,15 +78,15 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { */ public BlockLayoutManager(org.apache.fop.fo.flow.Block inBlock) { super(inBlock); - fobj = inBlock; proxyLMiter = new ProxyLMiter(); - Font fs = fobj.getCommonFont().getFontState(fobj.getFOEventHandler().getFontInfo()); + Font fs = getBlockFO().getCommonFont().getFontState( + getBlockFO().getFOEventHandler().getFontInfo()); lead = fs.getAscender(); follow = -fs.getDescender(); middleShift = -fs.getXHeight() / 2; - lineHeight = fobj.getLineHeight().getOptimum().getLength().getValue(); + lineHeight = getBlockFO().getLineHeight().getOptimum().getLength().getValue(); } /** @@ -95,8 +95,18 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { * if defined for the block. */ protected void initProperties() { - foBlockSpaceBefore = new SpaceVal(fobj.getCommonMarginBlock().spaceBefore).getSpace(); + foBlockSpaceBefore = new SpaceVal(getBlockFO().getCommonMarginBlock().spaceBefore).getSpace(); prevFoBlockSpaceAfter = foBlockSpaceAfter; +/*LF*/ bpUnit = 0; //layoutProps.blockProgressionUnit; +/*LF*/ if (bpUnit == 0) { +/*LF*/ // use optimum space values +/*LF*/ adjustedSpaceBefore = getBlockFO().getCommonMarginBlock().spaceBefore.getSpace().getOptimum().getLength().getValue(); +/*LF*/ adjustedSpaceAfter = getBlockFO().getCommonMarginBlock().spaceAfter.getSpace().getOptimum().getLength().getValue(); +/*LF*/ } else { +/*LF*/ // use minimum space values +/*LF*/ adjustedSpaceBefore = getBlockFO().getCommonMarginBlock().spaceBefore.getSpace().getMinimum().getLength().getValue(); +/*LF*/ adjustedSpaceAfter = getBlockFO().getCommonMarginBlock().spaceAfter.getSpace().getMinimum().getLength().getValue(); +/*LF*/ } } /** @@ -155,7 +165,7 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { */ private LineLayoutManager createLineManager(LayoutManager firstlm) { LineLayoutManager llm; - llm = new LineLayoutManager(fobj, lineHeight, lead, follow, middleShift); + llm = new LineLayoutManager(getBlockFO(), lineHeight, lead, follow, middleShift); List inlines = new java.util.ArrayList(); inlines.add(firstlm); while (proxyLMiter.hasNext()) { @@ -173,15 +183,15 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { private int getIPIndents() { int iIndents = 0; - iIndents += fobj.getCommonMarginBlock().startIndent.getValue(); - iIndents += fobj.getCommonMarginBlock().endIndent.getValue(); + iIndents += getBlockFO().getCommonMarginBlock().startIndent.getValue(); + iIndents += getBlockFO().getCommonMarginBlock().endIndent.getValue(); return iIndents; } /** * @see org.apache.fop.layoutmgr.LayoutManager#getNextBreakPoss(org.apache.fop.layoutmgr.LayoutContext) */ - public BreakPoss getNextBreakPoss(LayoutContext context) { + public BreakPoss getNextBreakPossOLDOLDOLD(LayoutContext context) { LayoutManager curLM; // currently active LM //int refipd = context.getRefIPD(); @@ -204,8 +214,8 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { BreakPoss lastPos = null; // Set context for percentage property values. - fobj.setLayoutDimension(PercentBase.BLOCK_IPD, contentipd); - fobj.setLayoutDimension(PercentBase.BLOCK_BPD, -1); + getBlockFO().setLayoutDimension(PercentBase.BLOCK_IPD, contentipd); + getBlockFO().setLayoutDimension(PercentBase.BLOCK_BPD, -1); while ((curLM = getChildLM()) != null) { // Make break positions and return blocks! @@ -263,7 +273,7 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { if (getChildLM() == null || over) { if (getChildLM() == null) { setFinished(true); - stackSize.add(new SpaceVal(fobj.getCommonMarginBlock().spaceAfter).getSpace()); + stackSize.add(new SpaceVal(getBlockFO().getCommonMarginBlock().spaceAfter).getSpace()); } BreakPoss breakPoss = new BreakPoss( new LeafPosition(this, childBreaks.size() - 1)); @@ -289,10 +299,42 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { return breakPoss; } + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepTogether() + */ + public boolean mustKeepTogether() { + //TODO Keeps will have to be more sophisticated sooner or later + return ((BlockLevelLayoutManager)getParent()).mustKeepTogether() + || !getBlockFO().getKeepTogether().getWithinPage().isAuto() + || !getBlockFO().getKeepTogether().getWithinColumn().isAuto(); + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithPrevious() + */ + public boolean mustKeepWithPrevious() { + return !getBlockFO().getKeepWithPrevious().getWithinPage().isAuto() + || !getBlockFO().getKeepWithPrevious().getWithinColumn().isAuto(); + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithNext() + */ + public boolean mustKeepWithNext() { + return !getBlockFO().getKeepWithNext().getWithinPage().isAuto() + || !getBlockFO().getKeepWithNext().getWithinColumn().isAuto(); + } + + //TODO this method is no longer used + public BreakPoss getNextBreakPoss(LayoutContext context) { + setFinished(true); + return null; + } + /** * @see org.apache.fop.layoutmgr.LayoutManager#addAreas(org.apache.fop.layoutmgr.PositionIterator, org.apache.fop.layoutmgr.LayoutContext) */ - public void addAreas(PositionIterator parentIter, + public void addAreasOLDOLDOLD(PositionIterator parentIter, LayoutContext layoutContext) { getParentArea(null); @@ -305,8 +347,9 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { foBlockSpaceBefore = null; if (!isBogus()) { - addID(fobj.getId()); - addMarkers(true, bp1.isFirstArea(), bp1.isLastArea()); + getPSLM().addIDToPage(getBlockFO().getId()); + getCurrentPV().addMarkers(markers, true, bp1.isFirstArea(), + bp1.isLastArea()); } try { @@ -328,17 +371,183 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { } } finally { if (!isBogus()) { - addMarkers(false, bp1.isFirstArea(), bp1.isLastArea()); + getCurrentPV().addMarkers(markers, false, bp1.isFirstArea(), + bp1.isLastArea()); } flush(); // if adjusted space after - foBlockSpaceAfter = new SpaceVal(fobj.getCommonMarginBlock().spaceAfter).getSpace(); + foBlockSpaceAfter = new SpaceVal(getBlockFO().getCommonMarginBlock().spaceAfter).getSpace(); addBlockSpacing(adjust, foBlockSpaceAfter); curBlockArea = null; } } + public void addAreas(PositionIterator parentIter, + LayoutContext layoutContext) { + /* LF *///System.out.println(" BLM.addAreas>"); + getParentArea(null); + + // if this will create the first block area in a page + // and display-align is bottom or center, add space before + if (layoutContext.getSpaceBefore() > 0) { + addBlockSpacing(0.0, new MinOptMax(layoutContext.getSpaceBefore())); + } + + getPSLM().addIDToPage(getBlockFO().getId()); + //addMarkersToPV(true, bp1.isFirstArea(), bp1.isLastArea()); + getCurrentPV().addMarkers(markers, true, true, false); + + LayoutManager childLM = null; + LayoutManager lastLM = null; + LayoutContext lc = new LayoutContext(0); + /* LF */// set space after in the LayoutContext for children + /* LF */if (layoutContext.getSpaceAfter() > 0) { + /* LF */lc.setSpaceAfter(layoutContext.getSpaceAfter()); + /* LF */} + /* LF */PositionIterator childPosIter; + + // "unwrap" the NonLeafPositions stored in parentIter + // and put them in a new list; + LinkedList positionList = new LinkedList(); + Position pos; + boolean bSpaceBefore = false; + boolean bSpaceAfter = false; + while (parentIter.hasNext()) { + pos = (Position) parentIter.next(); + //log.trace("pos = " + pos.getClass().getName() + "; " + pos); + Position innerPosition = pos; + if (pos instanceof NonLeafPosition) { + //Not all elements are wrapped + innerPosition = ((NonLeafPosition) pos).getPosition(); + } + if (innerPosition == null) { + // pos was created by this BlockLM and was inside an element + // representing space before or after + // this means the space was not discarded + if (positionList.size() == 0) { + // pos was in the element representing space-before + bSpaceBefore = true; + //log.trace(" space before"); + } else { + // pos was in the element representing space-after + bSpaceAfter = true; + //log.trace(" space-after"); + } + } else if (innerPosition.getLM() == this + && !(innerPosition instanceof MappingPosition)) { + // pos was created by this BlockLM and was inside a penalty + // allowing or forbidding a page break + // nothing to do + //log.trace(" penalty"); + } else { + // innerPosition was created by another LM + positionList.add(innerPosition); + lastLM = innerPosition.getLM(); + //log.trace(" " + innerPosition.getClass().getName()); + } + } + + if (bpUnit == 0) { + // the Positions in positionList were inside the elements + // created by the LineLM + childPosIter = new StackingIter(positionList.listIterator()); + } else { + // the Positions in positionList were inside the elements + // created by the BlockLM in the createUnitElements() method + //if (((Position) positionList.getLast()) instanceof + // LeafPosition) { + // // the last item inside positionList is a LeafPosition + // // (a LineBreakPosition, more precisely); this means that + // // the whole paragraph is on the same page + // System.out.println("paragrafo intero"); + // childPosIter = new KnuthPossPosIter(storedList, 0, + // storedList.size()); + //} else { + // // the last item inside positionList is a Position; + // // this means that the paragraph has been split + // // between consecutive pages + LinkedList splitList = new LinkedList(); + int splitLength = 0; + int iFirst = ((MappingPosition) positionList.getFirst()).getFirstIndex(); + int iLast = ((MappingPosition) positionList.getLast()).getLastIndex(); + // copy from storedList to splitList all the elements from + // iFirst to iLast + ListIterator storedListIterator = storedList.listIterator(iFirst); + while (storedListIterator.nextIndex() <= iLast) { + KnuthElement element = (KnuthElement) storedListIterator + .next(); + // some elements in storedList (i.e. penalty items) were created + // by this BlockLM, and must be ignored + if (element.getLayoutManager() != this) { + splitList.add(element); + splitLength += element.getW(); + lastLM = element.getLayoutManager(); + } + } + //System.out.println("addAreas riferito a storedList da " + + // iFirst + " a " + iLast); + //System.out.println("splitLength= " + splitLength + // + " (" + neededUnits(splitLength) + " unita') " + // + (neededUnits(splitLength) * bpUnit - splitLength) + " spazi"); + // add space before and / or after the paragraph + // to reach a multiple of bpUnit + if (bSpaceBefore && bSpaceAfter) { + foBlockSpaceBefore = new SpaceVal(getBlockFO().getCommonMarginBlock().spaceBefore).getSpace(); + foBlockSpaceAfter = new SpaceVal(getBlockFO().getCommonMarginBlock().spaceAfter).getSpace(); + adjustedSpaceBefore = (neededUnits(splitLength + + foBlockSpaceBefore.min + + foBlockSpaceAfter.min) + * bpUnit - splitLength) / 2; + adjustedSpaceAfter = neededUnits(splitLength + + foBlockSpaceBefore.min + + foBlockSpaceAfter.min) + * bpUnit - splitLength - adjustedSpaceBefore; + } else if (bSpaceBefore) { + adjustedSpaceBefore = neededUnits(splitLength + + foBlockSpaceBefore.min) + * bpUnit - splitLength; + } else { + adjustedSpaceAfter = neededUnits(splitLength + + foBlockSpaceAfter.min) + * bpUnit - splitLength; + } + //System.out.println("spazio prima = " + adjustedSpaceBefore + // + " spazio dopo = " + adjustedSpaceAfter + " totale = " + + // (adjustedSpaceBefore + adjustedSpaceAfter + splitLength)); + childPosIter = new KnuthPossPosIter(splitList, 0, splitList + .size()); + //} + } + + // if adjusted space before + if (bSpaceBefore) { + addBlockSpacing(0, new MinOptMax(adjustedSpaceBefore)); + } + + while ((childLM = childPosIter.getNextChildLM()) != null) { + // set last area flag + lc.setFlags(LayoutContext.LAST_AREA, + (layoutContext.isLastArea() && childLM == lastLM)); + /*LF*/lc.setStackLimit(layoutContext.getStackLimit()); + // Add the line areas to Area + childLM.addAreas(childPosIter, lc); + } + + int bIndents = getBlockFO().getCommonBorderPaddingBackground().getBPPaddingAndBorder(false); + + getCurrentPV().addMarkers(markers, false, false, true); + + flush(); + + // if adjusted space after + if (bSpaceAfter) { + addBlockSpacing(0, new MinOptMax(adjustedSpaceAfter)); + } + + curBlockArea = null; + } + /** * Return an Area which can contain the passed childArea. The childArea * may not yet have any content, but it has essential traits set. @@ -354,20 +563,21 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { if (curBlockArea == null) { curBlockArea = new Block(); + TraitSetter.addBreaks(curBlockArea, + getBlockFO().getBreakBefore(), getBlockFO().getBreakAfter()); + // Must get dimensions from parent area //Don't optimize this line away. It can have ugly side-effects. /*Area parentArea =*/ parentLM.getParentArea(curBlockArea); // set traits TraitSetter.addBorders(curBlockArea, - fobj.getCommonBorderPaddingBackground()); + getBlockFO().getCommonBorderPaddingBackground()); TraitSetter.addBackground(curBlockArea, - fobj.getCommonBorderPaddingBackground()); + getBlockFO().getCommonBorderPaddingBackground()); TraitSetter.addMargins(curBlockArea, - fobj.getCommonBorderPaddingBackground(), - fobj.getCommonMarginBlock()); - TraitSetter.addBreaks(curBlockArea, - fobj.getBreakBefore(), fobj.getBreakAfter()); + getBlockFO().getCommonBorderPaddingBackground(), + getBlockFO().getCommonMarginBlock()); // Set up dimensions // Get reference IPD from parentArea @@ -421,5 +631,12 @@ public class BlockLayoutManager extends BlockStackingLayoutManager { LayoutManager lm = resetPos.getLM(); } } + + /** + * convenience method that returns the Block node + */ + protected org.apache.fop.fo.flow.Block getBlockFO() { + return (org.apache.fop.fo.flow.Block) fobj; + } } diff --git a/src/java/org/apache/fop/layoutmgr/BlockLevelLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockLevelLayoutManager.java new file mode 100644 index 000000000..a523830dd --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/BlockLevelLayoutManager.java @@ -0,0 +1,51 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr; + +/** + * The interface for LayoutManagers which generate block areas + */ +public interface BlockLevelLayoutManager extends LayoutManager { + + static final int NO_ADJUSTMENT = -1; + static final int SPACE_BEFORE_ADJUSTMENT = 0; + static final int SPACE_AFTER_ADJUSTMENT = 1; + static final int LINE_NUMBER_ADJUSTMENT = 2; + static final int LINE_HEIGHT_ADJUSTMENT = 3; + + int negotiateBPDAdjustment(int adj, KnuthElement lastElement); + + void discardSpace(KnuthGlue spaceGlue); + + /** + * @return true if this element must be kept together + */ + boolean mustKeepTogether(); + + /** + * @return true if this element must be kept with the previous element. + */ + boolean mustKeepWithPrevious(); + + /** + * @return true if this element must be kept with the next element. + */ + boolean mustKeepWithNext(); + +} diff --git a/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java index 582c5c285..ca9c8caad 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java @@ -18,17 +18,28 @@ package org.apache.fop.layoutmgr; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + import org.apache.fop.area.Area; import org.apache.fop.area.BlockParent; import org.apache.fop.area.Block; +import org.apache.fop.datatypes.PercentBase; +import org.apache.fop.fo.Constants; import org.apache.fop.fo.FObj; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.fo.properties.CommonMarginBlock; +import org.apache.fop.fo.properties.SpaceProperty; import org.apache.fop.traits.MinOptMax; /** * Base LayoutManager class for all areas which stack their child * areas in the block-progression direction, such as Flow, Block, ListBlock. */ -public abstract class BlockStackingLayoutManager extends AbstractLayoutManager { +public abstract class BlockStackingLayoutManager extends AbstractLayoutManager + implements BlockLevelLayoutManager { /** * Reference to FO whose areas it's managing or to the traits * of the FO. @@ -36,8 +47,24 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager { //protected LayoutManager curChildLM = null; AbstractLayoutManager also defines this! protected BlockParent parentArea = null; + /*LF*/ + /** Value of the block-progression-unit (non-standard property) */ + protected int bpUnit = 0; + /** space-before value adjusted for block-progression-unit handling */ + protected int adjustedSpaceBefore = 0; + /** space-after value adjusted for block-progression-unit handling */ + protected int adjustedSpaceAfter = 0; + /** Only used to store the original list when createUnitElements is called */ + protected LinkedList storedList = null; + protected FObj fobj; + private boolean bBreakBeforeServed = false; + private boolean bSpaceBeforeServed = false; + protected int referenceIPD = 0; + /*LF*/ + public BlockStackingLayoutManager(FObj node) { super(node); + fobj = node; } private BreakCost evaluateBreakCost(Area parent, Area child) { @@ -135,5 +162,1053 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager { } } + /** + * @param len length in millipoints to span with bp units + * @return the minimum integer n such that n * bpUnit >= len + */ + protected int neededUnits(int len) { + return (int) Math.ceil((float)len / bpUnit); + } + + public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + /* LF *///System.err.println("BLM.getNextKnuthElements> keep-together = " + // + layoutProps.keepTogether.getType()); + /* LF *///System.err.println(" keep-with-previous = " + + // layoutProps.keepWithPrevious.getType()); + /* LF *///System.err.println(" keep-with-next = " + + // layoutProps.keepWithNext.getType()); + BlockLevelLayoutManager curLM; // currently active LM + BlockLevelLayoutManager prevLM = null; // previously active LM + + referenceIPD = context.getRefIPD(); + int iIndents = 0; + int bIndents = 0; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + iIndents = ((org.apache.fop.fo.flow.Block) fobj).getCommonMarginBlock().startIndent.getValue() + + ((org.apache.fop.fo.flow.Block) fobj).getCommonMarginBlock().endIndent.getValue(); + bIndents = ((org.apache.fop.fo.flow.Block) fobj).getCommonBorderPaddingBackground().getBPPaddingAndBorder(false); + } + int ipd = referenceIPD - iIndents; + + MinOptMax stackSize = new MinOptMax(); + + // Set context for percentage property values. + fobj.setLayoutDimension(PercentBase.BLOCK_IPD, ipd); + fobj.setLayoutDimension(PercentBase.BLOCK_BPD, -1); + + LinkedList returnedList = null; + LinkedList contentList = new LinkedList(); + LinkedList returnList = new LinkedList(); + Position returnPosition = new NonLeafPosition(this, null); + + if (!bBreakBeforeServed) { + try { + if (addKnuthElementsForBreakBefore(returnList, returnPosition)) { + return returnList; + } + } finally { + bBreakBeforeServed = true; + } + } + + if (!bSpaceBeforeServed) { + addKnuthElementsForSpaceBefore(returnList, returnPosition, alignment); + bSpaceBeforeServed = true; + } + + addKnuthElementsForBorderPaddingBefore(returnList, returnPosition); + + while ((curLM = (BlockLevelLayoutManager) getChildLM()) != null) { + LayoutContext childLC = new LayoutContext(0); + if (curLM instanceof LineLayoutManager) { + // curLM is a LineLayoutManager + // set stackLimit for lines + childLC.setStackLimit(new MinOptMax(ipd/* + * - iIndents - + * iTextIndent + */)); + childLC.setRefIPD(ipd); + } else { + // curLM is a ? + childLC.setStackLimit(MinOptMax.subtract(context + .getStackLimit(), stackSize)); + childLC.setRefIPD(referenceIPD); + } + + // get elements from curLM + returnedList = curLM.getNextKnuthElements(childLC, alignment); + if (returnedList.size() == 1 + && ((KnuthElement) returnedList.getFirst()).isPenalty() + && ((KnuthPenalty) returnedList.getFirst()).getP() == -KnuthElement.INFINITE) { + // a descendant of this block has break-before + if (returnList.size() == 0) { + // the first child (or its first child ...) has + // break-before; + // all this block, including space before, will be put in + // the + // following page + bSpaceBeforeServed = false; + } + contentList.addAll(returnedList); + + /* extension: conversione di tutta la sequenza fin'ora ottenuta */ + if (bpUnit > 0) { + storedList = contentList; + contentList = createUnitElements(contentList); + } + /* end of extension */ + + // "wrap" the Position inside each element + // moving the elements from contentList to returnList + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } else { + if (prevLM != null) { + // there is a block handled by prevLM + // before the one handled by curLM + if (mustKeepTogether() + || prevLM.mustKeepWithNext() + || curLM.mustKeepWithPrevious()) { + // add an infinite penalty to forbid a break between + // blocks + contentList.add(new KnuthPenalty(0, + KnuthElement.INFINITE, false, + new Position(this), false)); + } else if (!((KnuthElement) contentList.getLast()).isGlue()) { + // add a null penalty to allow a break between blocks + contentList.add(new KnuthPenalty(0, 0, false, + new Position(this), false)); + } else { + // the last element in contentList is a glue; + // it is a feasible breakpoint, there is no need to add + // a penalty + } + } + if (returnedList.size() == 0) { + //Avoid NoSuchElementException below (happens with empty blocks) + continue; + } + contentList.addAll(returnedList); + if (((KnuthElement) returnedList.getLast()).isPenalty() + && ((KnuthPenalty) returnedList.getLast()).getP() == -KnuthElement.INFINITE) { + // a descendant of this block has break-after + if (curLM.isFinished()) { + // there is no other content in this block; + // it's useless to add space after before a page break + setFinished(true); + } + + /* extension: conversione di tutta la sequenza fin'ora ottenuta */ + if (bpUnit > 0) { + storedList = contentList; + contentList = createUnitElements(contentList); + } + /* end of extension */ + + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } + /* + if (allocatedSpace.min > context.getStackLimit().max) { + log.debug("Allocated space exceeds stack limit, returning early."); + return returnList; + }*/ + } + prevLM = curLM; + } + + /* Extension: conversione di tutta la sequenza fin'ora ottenuta */ + if (bpUnit > 0) { + storedList = contentList; + contentList = createUnitElements(contentList); + } + /* end of extension */ + + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + addKnuthElementsForBorderPaddingAfter(returnList, returnPosition); + addKnuthElementsForSpaceAfter(returnList, returnPosition, alignment); + addKnuthElementsForBreakAfter(returnList, returnPosition); + + setFinished(true); + + return returnList; + } + + public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) { +/*LF*/ //System.out.println(" BLM.negotiateBPDAdjustment> " + adj); +/*LF*/ //System.out.println(" lastElement e' " + (lastElement.isPenalty() ? "penalty" : (lastElement.isGlue() ? "glue" : "box" ))); +/*LF*/ //System.out.println(" position e' " + lastElement.getPosition().getClass().getName()); +/*LF*/ //System.out.println(" " + (bpUnit > 0 ? "unit" : "")); + Position innerPosition = ((NonLeafPosition) lastElement.getPosition()).getPosition(); + + if (innerPosition == null && lastElement.isGlue()) { + // this adjustment applies to space-before or space-after of this block + if (((KnuthGlue) lastElement).getAdjustmentClass() == SPACE_BEFORE_ADJUSTMENT) { + // this adjustment applies to space-before + adjustedSpaceBefore += adj; +/*LF*/ //System.out.println(" BLM.negotiateBPDAdjustment> spazio prima: " + adj); + } else { + // this adjustment applies to space-after + adjustedSpaceAfter += adj; +/*LF*/ //System.out.println(" BLM.negotiateBPDAdjustment> spazio dopo: " + adj); + } + return adj; + } else if (innerPosition instanceof MappingPosition) { + // this block has block-progression-unit > 0: the adjustment can concern + // - the space-before or space-after of this block, + // - the line number of a descendant of this block + if (lastElement.isGlue()) { + // lastElement is a glue +/*LF*/ //System.out.println(" BLM.negotiateBPDAdjustment> bpunit con glue"); + ListIterator storedListIterator = storedList.listIterator(((MappingPosition) innerPosition).getFirstIndex()); + int newAdjustment = 0; + while (storedListIterator.nextIndex() <= ((MappingPosition) innerPosition).getLastIndex()) { + KnuthElement storedElement = (KnuthElement) storedListIterator.next(); + if (storedElement.isGlue()) { + newAdjustment += ((BlockLevelLayoutManager) storedElement.getLayoutManager()).negotiateBPDAdjustment(adj - newAdjustment, storedElement); +/*LF*/ //System.out.println(" BLM.negotiateBPDAdjustment> (progressivo) righe: " + newAdjustment); + } + } + newAdjustment = (newAdjustment > 0 ? bpUnit * neededUnits(newAdjustment) + : - bpUnit * neededUnits(- newAdjustment)); + return newAdjustment; + } else { + // lastElement is a penalty: this means that the paragraph + // has been split between consecutive pages: + // this may involve a change in the number of lines +/*LF*/ //System.out.println(" BLM.negotiateBPDAdjustment> bpunit con penalty"); + KnuthPenalty storedPenalty = (KnuthPenalty) + storedList.get(((MappingPosition) innerPosition).getLastIndex()); + if (storedPenalty.getW() > 0) { + // the original penalty has width > 0 +/*LF*/ //System.out.println(" BLM.negotiateBPDAdjustment> chiamata passata"); + return ((BlockLevelLayoutManager) storedPenalty.getLayoutManager()) + .negotiateBPDAdjustment(storedPenalty.getW(), (KnuthElement) storedPenalty); + } else { + // the original penalty has width = 0 + // the adjustment involves only the spaces before and after +/*LF*/ //System.out.println(" BLM.negotiateBPDAdjustment> chiamata gestita"); + return adj; + } + } + } else if (innerPosition.getLM() != this) { + // this adjustment concerns another LM + NonLeafPosition savedPos = (NonLeafPosition) lastElement.getPosition(); + lastElement.setPosition(innerPosition); + int returnValue = ((BlockLevelLayoutManager) lastElement.getLayoutManager()).negotiateBPDAdjustment(adj, lastElement); + lastElement.setPosition(savedPos); +/*LF*/ //System.out.println(" BLM.negotiateBPDAdjustment> righe: " + returnValue); + return returnValue; + } else { + // this should never happen + System.err.println("BlockLayoutManager.negotiateBPDAdjustment(): unexpected Position"); + return 0; + } + } + + public void discardSpace(KnuthGlue spaceGlue) { +/*LF*/ //System.out.println(" BLM.discardSpace> " + spaceGlue.getPosition().getClass().getName()); + Position innerPosition = ((NonLeafPosition) spaceGlue.getPosition()).getPosition(); + +/*LF*/ if (innerPosition == null || innerPosition.getLM() == this) { + // if this block has block-progression-unit > 0, innerPosition can be + // a MappingPosition + // spaceGlue represents space before or space after of this block + if (spaceGlue.getAdjustmentClass() == SPACE_BEFORE_ADJUSTMENT) { + // space-before must be discarded + adjustedSpaceBefore = 0; + } else { + // space-after must be discarded + adjustedSpaceAfter = 0; + //TODO Why are both cases handled in the same way? + } +/*LF*/ } else { + // this element was not created by this BlockLM + NonLeafPosition savedPos = (NonLeafPosition)spaceGlue.getPosition(); + spaceGlue.setPosition(innerPosition); + ((BlockLevelLayoutManager) spaceGlue.getLayoutManager()).discardSpace(spaceGlue); + spaceGlue.setPosition(savedPos); + } + } + + public LinkedList getChangedKnuthElements(List oldList, /*int flaggedPenalty,*/ int alignment) { +/*LF*/ //System.out.println(""); +/*LF*/ //System.out.println(" BLM.getChangedKnuthElements> inizio: oldList.size() = " + oldList.size()); + ListIterator oldListIterator = oldList.listIterator(); + KnuthElement returnedElement; + KnuthElement currElement = null; + KnuthElement prevElement = null; + LinkedList returnedList = new LinkedList(); + LinkedList returnList = new LinkedList(); + int fromIndex = 0; + + // "unwrap" the Positions stored in the elements + KnuthElement oldElement = null; + while (oldListIterator.hasNext()) { + oldElement = (KnuthElement)oldListIterator.next(); + Position innerPosition = ((NonLeafPosition) oldElement.getPosition()).getPosition(); +/*LF*/ //System.out.println(" BLM> unwrapping: " + (oldElement.isBox() ? "box " : (oldElement.isGlue() ? "glue " : "penalty")) + " creato da " + oldElement.getLayoutManager().getClass().getName()); +/*LF*/ //System.out.println(" BLM> unwrapping: " + oldElement.getPosition().getClass().getName()); + if (innerPosition != null) { + // oldElement was created by a descendant of this BlockLM + oldElement.setPosition(innerPosition); + } else { + // thisElement was created by this BlockLM + // modify its position in order to recognize it was not created + // by a child + oldElement.setPosition(new Position(this)); + } + } + + // create the iterator + List workList; + if (bpUnit == 0) { + workList = oldList; + } else { + // the storedList must be used instead of oldList; + // find the index of the first element of returnedList + // corresponding to the first element of oldList + oldListIterator = oldList.listIterator(); + KnuthElement el = (KnuthElement) oldListIterator.next(); + while (!(el.getPosition() instanceof MappingPosition)) { + el = (KnuthElement) oldListIterator.next(); + } + int iFirst = ((MappingPosition) el.getPosition()).getFirstIndex(); + + // find the index of the last element of returnedList + // corresponding to the last element of oldList + oldListIterator = oldList.listIterator(oldList.size()); + el = (KnuthElement) oldListIterator.previous(); + while (!(el.getPosition() instanceof MappingPosition)) { + el = (KnuthElement) oldListIterator.previous(); + } + int iLast = ((MappingPosition) el.getPosition()).getLastIndex(); + +/*LF*/ //System.out.println(" si usa storedList da " + iFirst + " a " + iLast + " compresi su " + storedList.size() + " elementi totali"); + workList = storedList.subList(iFirst, iLast + 1); + } + ListIterator workListIterator = workList.listIterator(); + +/*LF*/ //System.out.println(""); +/*LF*/ //System.out.println(" BLM.getChangedKnuthElements> workList.size() = " + workList.size() + " da 0 a " + (workList.size() - 1)); + + while (workListIterator.hasNext()) { + currElement = (KnuthElement) workListIterator.next(); +/*LF*/ //System.out.println("elemento n. " + workListIterator.previousIndex() + " nella workList"); + if (prevElement != null + && prevElement.getLayoutManager() != currElement.getLayoutManager()) { + // prevElement is the last element generated by the same LM + BlockLevelLayoutManager prevLM = (BlockLevelLayoutManager) + prevElement.getLayoutManager(); + BlockLevelLayoutManager currLM = (BlockLevelLayoutManager) + currElement.getLayoutManager(); + boolean bSomethingAdded = false; + if (prevLM != this) { +/*LF*/ //System.out.println(" BLM.getChangedKnuthElements> chiamata da " + fromIndex + " a " + workListIterator.previousIndex() + " su " + prevLM.getClass().getName()); + returnedList.addAll(prevLM.getChangedKnuthElements(workList.subList(fromIndex, workListIterator.previousIndex()), + /*flaggedPenalty,*/ alignment)); + bSomethingAdded = true; + } else { + // prevLM == this + // do nothing +/*LF*/ //System.out.println(" BLM.getChangedKnuthElements> elementi propri, ignorati, da " + fromIndex + " a " + workListIterator.previousIndex() + " su " + prevLM.getClass().getName()); + } + fromIndex = workListIterator.previousIndex(); + + // there is another block after this one + if (bSomethingAdded + && (this.mustKeepTogether() + || prevLM.mustKeepWithNext() + || currLM.mustKeepWithPrevious())) { + // add an infinite penalty to forbid a break between blocks + returnedList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, new Position(this), false)); + } else if (bSomethingAdded && !((KnuthElement) returnedList.getLast()).isGlue()) { + // add a null penalty to allow a break between blocks + returnedList.add(new KnuthPenalty(0, 0, false, new Position(this), false)); + } + } + prevElement = currElement; + } + if (currElement != null) { + BlockLevelLayoutManager currLM = (BlockLevelLayoutManager) + currElement.getLayoutManager(); + if (currLM != this) { +/*LF*/ //System.out.println(" BLM.getChangedKnuthElements> chiamata da " + fromIndex + " a " + oldList.size() + " su " + currLM.getClass().getName()); + returnedList.addAll(currLM.getChangedKnuthElements(workList.subList(fromIndex, workList.size()), + /*flaggedPenalty,*/ alignment)); + } else { + // currLM == this + // there are no more elements to add + // remove the last penalty added to returnedList + if (returnedList.size() > 0) { + returnedList.removeLast(); + } +/*LF*/ //System.out.println(" BLM.getChangedKnuthElements> elementi propri, ignorati, da " + fromIndex + " a " + workList.size()); + } + } + + // append elements representing space-before + boolean spaceBeforeIsConditional = true; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + spaceBeforeIsConditional = + ((org.apache.fop.fo.flow.Block) fobj).getCommonMarginBlock().spaceBefore.getSpace().isDiscard(); + } + if (bpUnit > 0 + || adjustedSpaceBefore != 0) { + if (!spaceBeforeIsConditional) { + // add elements to prevent the glue to be discarded + returnList.add(new KnuthBox(0, + new NonLeafPosition(this, null), false)); + returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, + new NonLeafPosition(this, null), false)); + } + if (bpUnit > 0) { + returnList.add(new KnuthGlue(0, 0, 0, + SPACE_BEFORE_ADJUSTMENT, new NonLeafPosition(this, null), true)); + } else { + returnList.add(new KnuthGlue(adjustedSpaceBefore, 0, 0, + SPACE_BEFORE_ADJUSTMENT, new NonLeafPosition(this, null), true)); + } + } + +/*LF*/ //System.out.println(" BLM.getChangedKnuthElements> intermedio: returnedList.size() = " + returnedList.size()); + +/* estensione: conversione complessiva */ +/*LF*/ if (bpUnit > 0) { +/*LF*/ storedList = returnedList; +/*LF*/ returnedList = createUnitElements(returnedList); +/*LF*/ } +/* estensione */ + + // "wrap" the Position stored in each element of returnedList + // and add elements to returnList + ListIterator listIter = returnedList.listIterator(); + while (listIter.hasNext()) { + returnedElement = (KnuthElement)listIter.next(); + returnedElement.setPosition(new NonLeafPosition(this, returnedElement.getPosition())); + returnList.add(returnedElement); + } + + // append elements representing space-after + boolean spaceAfterIsConditional = true; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + spaceAfterIsConditional = + ((org.apache.fop.fo.flow.Block) fobj).getCommonMarginBlock().spaceAfter.getSpace().isDiscard(); + } + if (bpUnit > 0 + || adjustedSpaceAfter != 0) { + if (!spaceAfterIsConditional) { + returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, + new NonLeafPosition(this, null), false)); + } + if (bpUnit > 0) { + returnList.add(new KnuthGlue(0, 0, 0, + SPACE_AFTER_ADJUSTMENT, new NonLeafPosition(this, null), + (!spaceAfterIsConditional) ? false : true)); + } else { + returnList.add(new KnuthGlue(adjustedSpaceAfter, 0, 0, + SPACE_AFTER_ADJUSTMENT, new NonLeafPosition(this, null), + (!spaceAfterIsConditional) ? false : true)); + } + if (!spaceAfterIsConditional) { + returnList.add(new KnuthBox(0, + new NonLeafPosition(this, null), true)); + } + } + +/*LF*/ //System.out.println(" BLM.getChangedKnuthElements> fine: returnList.size() = " + returnList.size()); + return returnList; + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepTogether() + */ + public boolean mustKeepTogether() { + return false; + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithPrevious() + */ + public boolean mustKeepWithPrevious() { + return false; + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithNext() + */ + public boolean mustKeepWithNext() { + return false; + } + + /** + * Creates Knuth elements for before border padding and adds them to the return list. + * @param returnList return list to add the additional elements to + * @param returnPosition applicable return position + */ + protected void addKnuthElementsForBorderPaddingBefore(LinkedList returnList, + Position returnPosition) { + //Border and Padding (before) + CommonBorderPaddingBackground borderAndPadding = null; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + borderAndPadding = + ((org.apache.fop.fo.flow.Block) fobj).getCommonBorderPaddingBackground(); + } else if (fobj instanceof org.apache.fop.fo.flow.BlockContainer) { + borderAndPadding = + ((org.apache.fop.fo.flow.BlockContainer) fobj).getCommonBorderPaddingBackground(); + } + if (borderAndPadding != null) { + //TODO Handle conditionality + int bpBefore = borderAndPadding.getBorderBeforeWidth(false) + + borderAndPadding.getPaddingBefore(false); + if (bpBefore > 0) { + returnList.add(new KnuthBox(bpBefore, returnPosition, true)); + } + } + } + + /** + * Creates Knuth elements for after border padding and adds them to the return list. + * @param returnList return list to add the additional elements to + * @param returnPosition applicable return position + */ + protected void addKnuthElementsForBorderPaddingAfter(LinkedList returnList, + Position returnPosition) { + //Border and Padding (after) + CommonBorderPaddingBackground borderAndPadding = null; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + borderAndPadding = + ((org.apache.fop.fo.flow.Block) fobj).getCommonBorderPaddingBackground(); + } else if (fobj instanceof org.apache.fop.fo.flow.BlockContainer) { + borderAndPadding = + ((org.apache.fop.fo.flow.BlockContainer) fobj).getCommonBorderPaddingBackground(); + } + if (borderAndPadding != null) { + //TODO Handle conditionality + int bpAfter = borderAndPadding.getBorderAfterWidth(false) + + borderAndPadding.getPaddingAfter(false); + if (bpAfter > 0) { + returnList.add(new KnuthBox(bpAfter, returnPosition, true)); + } + } + } + + /** + * Creates Knuth elements for break-before and adds them to the return list. + * @param returnList return list to add the additional elements to + * @param returnPosition applicable return position + * @return true if an element has been added due to a break-before. + */ + protected boolean addKnuthElementsForBreakBefore(LinkedList returnList, + Position returnPosition) { + int breakBefore = -1; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + breakBefore = ((org.apache.fop.fo.flow.Block) fobj).getBreakBefore(); + } else if (fobj instanceof org.apache.fop.fo.flow.BlockContainer) { + breakBefore = ((org.apache.fop.fo.flow.BlockContainer) fobj).getBreakBefore(); + } + if (breakBefore == EN_PAGE + || breakBefore == EN_COLUMN + || breakBefore == EN_EVEN_PAGE + || breakBefore == EN_ODD_PAGE) { + // return a penalty element, representing a forced page break + returnList.add(new KnuthPenalty(0, -KnuthElement.INFINITE, false, + breakBefore, returnPosition, false)); + return true; + } else { + return false; + } + } + + /** + * Creates Knuth elements for break-after and adds them to the return list. + * @param returnList return list to add the additional elements to + * @param returnPosition applicable return position + * @return true if an element has been added due to a break-after. + */ + protected boolean addKnuthElementsForBreakAfter(LinkedList returnList, + Position returnPosition) { + int breakAfter = -1; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + breakAfter = ((org.apache.fop.fo.flow.Block) fobj).getBreakAfter(); + } else if (fobj instanceof org.apache.fop.fo.flow.BlockContainer) { + breakAfter = ((org.apache.fop.fo.flow.BlockContainer) fobj).getBreakAfter(); + } + if (breakAfter == EN_PAGE + || breakAfter == EN_COLUMN + || breakAfter == EN_EVEN_PAGE + || breakAfter == EN_ODD_PAGE) { + // add a penalty element, representing a forced page break + returnList.add(new KnuthPenalty(0, -KnuthElement.INFINITE, false, + breakAfter, returnPosition, false)); + return true; + } else { + return false; + } + } + + /** + * Creates Knuth elements for space-before and adds them to the return list. + * @param returnList return list to add the additional elements to + * @param returnPosition applicable return position + * @param alignment vertical alignment + */ + protected void addKnuthElementsForSpaceBefore(LinkedList returnList, + Position returnPosition, int alignment) { + SpaceProperty spaceBefore = null; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + spaceBefore = ((org.apache.fop.fo.flow.Block) fobj).getCommonMarginBlock().spaceBefore; + } else if (fobj instanceof org.apache.fop.fo.flow.BlockContainer) { + spaceBefore = ((org.apache.fop.fo.flow.BlockContainer) fobj).getCommonMarginBlock().spaceBefore; + } + // append elements representing space-before + if (bpUnit > 0 + || spaceBefore != null + && !(spaceBefore.getMinimum().getLength().getValue() == 0 + && spaceBefore.getMaximum().getLength().getValue() == 0)) { + if (spaceBefore != null && !spaceBefore.getSpace().isDiscard()) { + // add elements to prevent the glue to be discarded + returnList.add(new KnuthBox(0, returnPosition, false)); + returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, + false, returnPosition, false)); + } + if (bpUnit > 0) { + returnList.add(new KnuthGlue(0, 0, 0, + BlockLevelLayoutManager.SPACE_BEFORE_ADJUSTMENT, + returnPosition, true)); + } else /*if (alignment == EN_JUSTIFY)*/ { + returnList.add(new KnuthGlue( + spaceBefore.getOptimum().getLength().getValue(), + spaceBefore.getMaximum().getLength().getValue() + - spaceBefore.getOptimum().getLength().getValue(), + spaceBefore.getOptimum().getLength().getValue() + - spaceBefore.getMinimum().getLength().getValue(), + BlockLevelLayoutManager.SPACE_BEFORE_ADJUSTMENT, + returnPosition, true)); + } /*else { + returnList.add(new KnuthGlue( + spaceBefore.getOptimum().getLength().getValue(), + 0, 0, BlockLevelLayoutManager.SPACE_BEFORE_ADJUSTMENT, + returnPosition, true)); + }*/ + } + } + + /** + * Creates Knuth elements for space-after and adds them to the return list. + * @param returnList return list to add the additional elements to + * @param returnPosition applicable return position + * @param alignment vertical alignment + */ + protected void addKnuthElementsForSpaceAfter(LinkedList returnList, Position returnPosition, + int alignment) { + SpaceProperty spaceAfter = null; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + spaceAfter = ((org.apache.fop.fo.flow.Block) fobj).getCommonMarginBlock().spaceAfter; + } else if (fobj instanceof org.apache.fop.fo.flow.Block) { + spaceAfter = ((org.apache.fop.fo.flow.BlockContainer) fobj).getCommonMarginBlock().spaceAfter; + } + // append elements representing space-after + if (bpUnit > 0 + || spaceAfter != null + && !(spaceAfter.getMinimum().getLength().getValue() == 0 + && spaceAfter.getMaximum().getLength().getValue() == 0)) { + if (spaceAfter != null && !spaceAfter.getSpace().isDiscard()) { + returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, + false, returnPosition, false)); + } + if (bpUnit > 0) { + returnList.add(new KnuthGlue(0, 0, 0, + BlockLevelLayoutManager.SPACE_AFTER_ADJUSTMENT, + returnPosition, true)); + } else /*if (alignment == EN_JUSTIFY)*/ { + returnList.add(new KnuthGlue( + spaceAfter.getOptimum().getLength().getValue(), + spaceAfter.getMaximum().getLength().getValue() + - spaceAfter.getOptimum().getLength().getValue(), + spaceAfter.getOptimum().getLength().getValue() + - spaceAfter.getMinimum().getLength().getValue(), + BlockLevelLayoutManager.SPACE_AFTER_ADJUSTMENT, returnPosition, + (!spaceAfter.getSpace().isDiscard()) ? false : true)); + } /*else { + returnList.add(new KnuthGlue( + spaceAfter.getOptimum().getLength().getValue(), 0, 0, + BlockLevelLayoutManager.SPACE_AFTER_ADJUSTMENT, returnPosition, + (!spaceAfter.getSpace().isDiscard()) ? false : true)); + }*/ + if (spaceAfter != null && !spaceAfter.getSpace().isDiscard()) { + returnList.add(new KnuthBox(0, returnPosition, true)); + } + } + } + + protected LinkedList createUnitElements(LinkedList oldList) { + //System.out.println(" "); + //System.out.println("Inizio conversione: " + oldList.size() + " elementi, spazio minimo prima= " + layoutProps.spaceBefore.getSpace().min + // + " spazio minimo dopo= " + layoutProps.spaceAfter.getSpace().min); + // add elements at the beginning and at the end of oldList + // representing minimum spaces + LayoutManager lm = ((KnuthElement)oldList.getFirst()).getLayoutManager(); + boolean bAddedBoxBefore = false; + boolean bAddedBoxAfter = false; + if (adjustedSpaceBefore > 0) { + oldList.addFirst(new KnuthBox(adjustedSpaceBefore, + new Position(lm), true)); + bAddedBoxBefore = true; + } + if (adjustedSpaceAfter > 0) { + oldList.addLast(new KnuthBox(adjustedSpaceAfter, + new Position(lm), true)); + bAddedBoxAfter = true; + } + + MinOptMax totalLength = new MinOptMax(0); + MinOptMax totalUnits = new MinOptMax(0); + LinkedList newList = new LinkedList(); + + //System.out.println(" "); + //System.out.println(" Prima scansione"); + // scan the list once to compute total min, opt and max length + ListIterator oldListIterator = oldList.listIterator(); + while (oldListIterator.hasNext()) { + KnuthElement element = (KnuthElement) oldListIterator.next(); + if (element.isBox()) { +/*LF*/ totalLength.add(new MinOptMax(element.getW())); +/*LF*/ //System.out.println("box " + element.getW()); +/*LF*/ } else if (element.isGlue()) { +/*LF*/ totalLength.min -= ((KnuthGlue) element).getZ(); +/*LF*/ totalLength.max += ((KnuthGlue) element).getY(); +/*LF*/ //leafValue = ((LeafPosition) element.getPosition()).getLeafPos(); +/*LF*/ //System.out.println("glue " + element.getW() + " + " + ((KnuthGlue) element).getY() + " - " + ((KnuthGlue) element).getZ()); +/*LF*/ } else { +/*LF*/ //System.out.println((((KnuthPenalty)element).getP() == KnuthElement.INFINITE ? "PENALTY " : "penalty ") + element.getW()); + } + } + // compute the total amount of "units" + totalUnits = new MinOptMax(neededUnits(totalLength.min), + neededUnits(totalLength.opt), + neededUnits(totalLength.max)); + //System.out.println(" totalLength= " + totalLength); + //System.out.println(" unita'= " + totalUnits); + + //System.out.println(" "); + //System.out.println(" Seconda scansione"); + // scan the list once more, stopping at every breaking point + // in order to compute partial min, opt and max length + // and create the new elements + oldListIterator = oldList.listIterator(); + boolean bPrevIsBox = false; + MinOptMax lengthBeforeBreak = new MinOptMax(0); + MinOptMax lengthAfterBreak = (MinOptMax) totalLength.clone(); + MinOptMax unitsBeforeBreak; + MinOptMax unitsAfterBreak; + MinOptMax unsuppressibleUnits = new MinOptMax(0); + int firstIndex = 0; + int lastIndex = -1; + while (oldListIterator.hasNext()) { + KnuthElement element = (KnuthElement) oldListIterator.next(); + lastIndex ++; + if (element.isBox()) { + lengthBeforeBreak.add(new MinOptMax(element.getW())); + lengthAfterBreak.subtract(new MinOptMax(element.getW())); + bPrevIsBox = true; + } else if (element.isGlue()) { + lengthBeforeBreak.min -= ((KnuthGlue) element).getZ(); + lengthAfterBreak.min += ((KnuthGlue) element).getZ(); + lengthBeforeBreak.max += ((KnuthGlue) element).getY(); + lengthAfterBreak.max -= ((KnuthGlue) element).getY(); + bPrevIsBox = false; + } else { + lengthBeforeBreak.add(new MinOptMax(element.getW())); + bPrevIsBox = false; + } + + // create the new elements + if (element.isPenalty() && ((KnuthPenalty) element).getP() < KnuthElement.INFINITE + || element.isGlue() && bPrevIsBox + || !oldListIterator.hasNext()) { + // suppress elements after the breaking point + int iStepsForward = 0; + while (oldListIterator.hasNext()) { + KnuthElement el = (KnuthElement) oldListIterator.next(); + iStepsForward++; + if (el.isGlue()) { + // suppressed glue + lengthAfterBreak.min += ((KnuthGlue) el).getZ(); + lengthAfterBreak.max -= ((KnuthGlue) el).getY(); + } else if (el.isPenalty()) { + // suppressed penalty, do nothing + } else { + // box, end of suppressions + break; + } + } + // compute the partial amount of "units" before and after the break + unitsBeforeBreak = new MinOptMax(neededUnits(lengthBeforeBreak.min), + neededUnits(lengthBeforeBreak.opt), + neededUnits(lengthBeforeBreak.max)); + unitsAfterBreak = new MinOptMax(neededUnits(lengthAfterBreak.min), + neededUnits(lengthAfterBreak.opt), + neededUnits(lengthAfterBreak.max)); + + // rewind the iterator and lengthAfterBreak + for (int i = 0; i < iStepsForward; i++) { + KnuthElement el = (KnuthElement) oldListIterator.previous(); + if (el.isGlue()) { + lengthAfterBreak.min -= ((KnuthGlue) el).getZ(); + lengthAfterBreak.max += ((KnuthGlue) el).getY(); + } + } + + // compute changes in length, stretch and shrink + int uLengthChange = unitsBeforeBreak.opt + unitsAfterBreak.opt - totalUnits.opt; + int uStretchChange = (unitsBeforeBreak.max + unitsAfterBreak.max - totalUnits.max) + - (unitsBeforeBreak.opt + unitsAfterBreak.opt - totalUnits.opt); + int uShrinkChange = (unitsBeforeBreak.opt + unitsAfterBreak.opt - totalUnits.opt) + - (unitsBeforeBreak.min + unitsAfterBreak.min - totalUnits.min); + + // compute the number of normal, stretch and shrink unit + // that must be added to the new sequence + int uNewNormal = unitsBeforeBreak.opt - unsuppressibleUnits.opt; + int uNewStretch = (unitsBeforeBreak.max - unitsBeforeBreak.opt) + - (unsuppressibleUnits.max - unsuppressibleUnits.opt); + int uNewShrink = (unitsBeforeBreak.opt - unitsBeforeBreak.min) + - (unsuppressibleUnits.opt - unsuppressibleUnits.min); + +/*LF*/ //System.out.println("(" + unsuppressibleUnits.min + "-" + unsuppressibleUnits.opt + "-" + unsuppressibleUnits.max + ") " +/*LF*/ // + " -> " + unitsBeforeBreak.min + "-" + unitsBeforeBreak.opt + "-" + unitsBeforeBreak.max +/*LF*/ // + " + " + unitsAfterBreak.min + "-" + unitsAfterBreak.opt + "-" + unitsAfterBreak.max +/*LF*/ // + (uLengthChange != 0 ? " [length " + uLengthChange + "] " : "") +/*LF*/ // + (uStretchChange != 0 ? " [stretch " + uStretchChange + "] " : "") +/*LF*/ // + (uShrinkChange != 0 ? " [shrink " + uShrinkChange + "]" : "") +/*LF*/ // ); + + // create the MappingPosition which will be stored in the new elements + // correct firstIndex and lastIndex + int firstIndexCorrection = 0; + int lastIndexCorrection = 0; + if (bAddedBoxBefore) { + if (firstIndex != 0) { + firstIndexCorrection ++; + } + lastIndexCorrection ++; + } + if (bAddedBoxAfter && lastIndex == (oldList.size() - 1)) { + lastIndexCorrection ++; + } + MappingPosition mappingPos = new MappingPosition(this, + firstIndex - firstIndexCorrection, + lastIndex - lastIndexCorrection); + + // new box + newList.add(new KnuthBox((uNewNormal - uLengthChange) * bpUnit, + mappingPos, + false)); + unsuppressibleUnits.add(new MinOptMax(uNewNormal - uLengthChange)); + //System.out.println(" box " + (uNewNormal - uLengthChange)); + + // new infinite penalty, glue and box, if necessary + if (uNewStretch - uStretchChange > 0 + || uNewShrink - uShrinkChange > 0) { + int iStretchUnits = (uNewStretch - uStretchChange > 0 ? (uNewStretch - uStretchChange) : 0); + int iShrinkUnits = (uNewShrink - uShrinkChange > 0 ? (uNewShrink - uShrinkChange) : 0); + newList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, + mappingPos, + false)); + newList.add(new KnuthGlue(0, + iStretchUnits * bpUnit, + iShrinkUnits * bpUnit, + LINE_NUMBER_ADJUSTMENT, + mappingPos, + false)); + //System.out.println(" PENALTY"); + //System.out.println(" glue 0 " + iStretchUnits + " " + iShrinkUnits); + unsuppressibleUnits.max += iStretchUnits; + unsuppressibleUnits.min -= iShrinkUnits; + if (!oldListIterator.hasNext()) { + newList.add(new KnuthBox(0, + mappingPos, + false)); + //System.out.println(" box 0"); + } + } + + // new breaking sequence + if (uStretchChange != 0 + || uShrinkChange != 0) { + // new infinite penalty, glue, penalty and glue + newList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, + mappingPos, + false)); + newList.add(new KnuthGlue(0, + uStretchChange * bpUnit, + uShrinkChange * bpUnit, + LINE_NUMBER_ADJUSTMENT, + mappingPos, + false)); + newList.add(new KnuthPenalty(uLengthChange * bpUnit, + 0, false, element.getPosition(), false)); + newList.add(new KnuthGlue(0, + - uStretchChange * bpUnit, + - uShrinkChange * bpUnit, + LINE_NUMBER_ADJUSTMENT, + mappingPos, + false)); + //System.out.println(" PENALTY"); + //System.out.println(" glue 0 " + uStretchChange + " " + uShrinkChange); + //System.out.println(" penalty " + uLengthChange + " * unit"); + //System.out.println(" glue 0 " + (- uStretchChange) + " " + (- uShrinkChange)); + } else if (oldListIterator.hasNext()){ + // new penalty + newList.add(new KnuthPenalty(uLengthChange * bpUnit, + 0, false, + mappingPos, + false)); + //System.out.println(" penalty " + uLengthChange + " * unit"); + } + // update firstIndex + firstIndex = lastIndex + 1; + } + + if (element.isPenalty()) { + lengthBeforeBreak.add(new MinOptMax(-element.getW())); + } + + } + + // remove elements at the beginning and at the end of oldList + // representing minimum spaces + if (adjustedSpaceBefore > 0) { + oldList.removeFirst(); + } + if (adjustedSpaceAfter > 0) { + oldList.removeLast(); + } + + // if space-before.conditionality is "discard", correct newList + boolean correctFirstElement = false; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + correctFirstElement = + ((org.apache.fop.fo.flow.Block) fobj).getCommonMarginBlock().spaceBefore.getSpace().isDiscard(); + } + if (correctFirstElement) { + // remove the wrong element + KnuthBox wrongBox = (KnuthBox) newList.removeFirst(); + // if this paragraph is at the top of a page, the space before + // must be ignored; compute the length change + int decreasedLength = (neededUnits(totalLength.opt) + - neededUnits(totalLength.opt - adjustedSpaceBefore)) + * bpUnit; + // insert the correct elements + newList.addFirst(new KnuthBox(wrongBox.getW() - decreasedLength, + wrongBox.getPosition(), false)); + newList.addFirst(new KnuthGlue(decreasedLength, 0, 0, SPACE_BEFORE_ADJUSTMENT, + wrongBox.getPosition(), false)); + //System.out.println(" rimosso box " + neededUnits(wrongBox.getW())); + //System.out.println(" aggiunto glue " + neededUnits(decreasedLength) + " 0 0"); + //System.out.println(" aggiunto box " + neededUnits(wrongBox.getW() - decreasedLength)); + } + + // if space-after.conditionality is "discard", correct newList + boolean correctLastElement = false; + if (fobj instanceof org.apache.fop.fo.flow.Block) { + correctLastElement = + ((org.apache.fop.fo.flow.Block) fobj).getCommonMarginBlock().spaceAfter.getSpace().isDiscard(); + } + if (correctLastElement) { + // remove the wrong element + KnuthBox wrongBox = (KnuthBox) newList.removeLast(); + // if the old sequence is box(h) penalty(inf) glue(x,y,z) box(0) + // (it cannot be parted and has some stretch or shrink) + // the wrong box is the first one, not the last one + LinkedList preserveList = new LinkedList(); + if (wrongBox.getW() == 0) { + preserveList.add(wrongBox); + preserveList.addFirst((KnuthGlue) newList.removeLast()); + preserveList.addFirst((KnuthPenalty) newList.removeLast()); + wrongBox = (KnuthBox) newList.removeLast(); + } + + // if this paragraph is at the bottom of a page, the space after + // must be ignored; compute the length change + int decreasedLength = (neededUnits(totalLength.opt) + - neededUnits(totalLength.opt - adjustedSpaceAfter)) + * bpUnit; + // insert the correct box + newList.addLast(new KnuthBox(wrongBox.getW() - decreasedLength, + wrongBox.getPosition(), false)); + // add preserved elements + if (preserveList.size() > 0) { + newList.addAll(preserveList); + } + // insert the correct glue + newList.addLast(new KnuthGlue(decreasedLength, 0, 0, SPACE_AFTER_ADJUSTMENT, + wrongBox.getPosition(), false)); + //System.out.println(" rimosso box " + neededUnits(wrongBox.getW())); + //System.out.println(" aggiunto box " + neededUnits(wrongBox.getW() - decreasedLength)); + //System.out.println(" aggiunto glue " + neededUnits(decreasedLength) + " 0 0"); + } + + return newList; + } + + protected static class StackingIter extends PositionIterator { + StackingIter(Iterator parentIter) { + super(parentIter); + } + + protected LayoutManager getLM(Object nextObj) { + return ((Position) nextObj).getLM(); + } + + protected Position getPos(Object nextObj) { + return ((Position) nextObj); + } + } + + protected static class MappingPosition extends Position { + private int iFirstIndex; + private int iLastIndex; + + public MappingPosition(LayoutManager lm, int first, int last) { + super(lm); + iFirstIndex = first; + iLastIndex = last; + } + + public int getFirstIndex() { + return iFirstIndex; + } + + public int getLastIndex() { + return iLastIndex; + } + } + + /** + * "wrap" the Position inside each element moving the elements from + * SourceList to targetList + * @param sourceList source list + * @param targetList target list receiving the wrapped position elements + */ + protected void wrapPositionElements(List sourceList, List targetList) { + ListIterator listIter = sourceList.listIterator(); + while (listIter.hasNext()) { + KnuthElement tempElement; + tempElement = (KnuthElement) listIter.next(); + if (tempElement.getLayoutManager() != this) { + tempElement.setPosition(new NonLeafPosition(this, + tempElement.getPosition())); + } + targetList.add(tempElement); + } + } + } diff --git a/src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java b/src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java new file mode 100644 index 000000000..c0d87a772 --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java @@ -0,0 +1,783 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr; + +import java.util.ArrayList; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.traits.MinOptMax; + +/** + * The set of nodes is sorted into lines indexed into activeLines. + * The nodes in each line are linked together in a single linked list by the + * KnuthNode.next field. The activeLines array contains a link to the head of + * the linked list in index 'line*2' and a link to the tail at index 'line*2+1'. + *

+ * The set of active nodes can be traversed by + *

+ * for (int line = startLine; line < endLine; line++) {
+ *     for (KnuthNode node = getNode(line); node != null; node = node.next) {
+ *         // Do something with 'node'
+ *     }
+ * }
+ * 
+ */ +public abstract class BreakingAlgorithm { + // parameters of Knuth's algorithm: + // penalty value for flagged penalties + private int flaggedPenalty = 50; + // demerit for consecutive lines ending at flagged penalties + private int repeatedFlaggedDemerit = 50; + // demerit for consecutive lines belonging to incompatible fitness classes + private int incompatibleFitnessDemerit = 50; + // suggested modification to the "optimum" number of lines + private int looseness = 0; + + /** + * The threshold for considering breaks to be acceptable. + */ + private double threshold; + + /** + * The paragraph of KnuthElements. + */ + private KnuthSequence par; + + /** + * The width of a line. + */ + private int lineWidth = 0; + private boolean force = false; + + protected KnuthNode lastDeactivatedNode = null; + private KnuthNode lastTooLong; + private KnuthNode lastTooShort; + private KnuthNode lastDeactivated; + + protected int alignment; + protected int alignmentLast; + protected boolean bFirst; + + /** + * The set of active nodes. + */ + private KnuthNode[] activeLines; + + /** + * The number of active nodes. + */ + protected int activeNodeCount; + + /** + * The lowest available line in the set of active nodes. + */ + protected int startLine = 0; + + /** + * The highest + 1 available line in the set of active nodes. + */ + protected int endLine = 0; + + /** + * The total width of all elements handled so far. + */ + private int totalWidth; + + /** + * The total stretch of all elements handled so far. + */ + private int totalStretch = 0; + + /** + * The total shrink of all elements handled so far. + */ + private int totalShrink = 0; + + private BestRecords best; + private KnuthNode[] positions; + + private static final int INFINITE_RATIO = 1000; + + protected static Log log = LogFactory.getLog(KnuthParagraph.class); + + public BreakingAlgorithm(int align, int alignLast, + boolean first) { + alignment = align; + alignmentLast = alignLast; + bFirst = first; + this.best = new BestRecords(); + } + + + // this class represent a feasible breaking point + public class KnuthNode { + // index of the breakpoint represented by this node + public int position; + + // number of the line ending at this breakpoint + public int line; + + // fitness class of the line ending at his breakpoint + public int fitness; + + // accumulated width of the KnuthElements + public int totalWidth; + + // accumulated stretchability of the KnuthElements + public int totalStretch; + + // accumulated shrinkability of the KnuthElements + public int totalShrink; + + // adjustment ratio if the line ends at this breakpoint + public double adjustRatio; + + // available stretch of the line ending at this breakpoint + public int availableShrink; + + // available shrink of the line ending at this breakpoint + public int availableStretch; + + // difference between target and actual line width + public int difference; + + // minimum total demerits up to this breakpoint + public double totalDemerits; + + // best node for the preceding breakpoint + public KnuthNode previous; + + // next possible node in the same line + public KnuthNode next; + + + public KnuthNode(int position, int line, int fitness, + int totalWidth, int totalStretch, int totalShrink, + double adjustRatio, int availableShrink, int availableStretch, int difference, + double totalDemerits, KnuthNode previous) { + this.position = position; + this.line = line; + this.fitness = fitness; + this.totalWidth = totalWidth; + this.totalStretch = totalStretch; + this.totalShrink = totalShrink; + this.adjustRatio = adjustRatio; + this.availableShrink = availableShrink; + this.availableStretch = availableStretch; + this.difference = difference; + this.totalDemerits = totalDemerits; + this.previous = previous; + } + + public String toString() { + return ""; + } + } + + // this class stores information about how the nodes + // which could start a line + // ending at the current element + private class BestRecords { + private static final double INFINITE_DEMERITS = Double.POSITIVE_INFINITY; + //private static final double INFINITE_DEMERITS = 1E11; + + private double bestDemerits[] = new double[4]; + private KnuthNode bestNode[] = new KnuthNode[4]; + private double bestAdjust[] = new double[4]; + private int bestDifference[] = new int[4]; + private int bestAvailableShrink[] = new int[4]; + private int bestAvailableStretch[] = new int[4]; + private int bestIndex = -1; + + public BestRecords() { + reset(); + } + + public void addRecord(double demerits, KnuthNode node, double adjust, + int availableShrink, int availableStretch, int difference, int fitness) { + if (demerits > bestDemerits[fitness]) { + log.error("New demerits value greter than the old one"); + } + bestDemerits[fitness] = demerits; + bestNode[fitness] = node; + bestAdjust[fitness] = adjust; + bestAvailableShrink[fitness] = availableShrink; + bestAvailableStretch[fitness] = availableStretch; + bestDifference[fitness] = difference; + if (bestIndex == -1 || demerits < bestDemerits[bestIndex]) { + bestIndex = fitness; + } + } + + public boolean hasRecords() { + return (bestIndex != -1); + } + + public boolean notInfiniteDemerits(int fitness) { + return (bestDemerits[fitness] != INFINITE_DEMERITS); + } + + public double getDemerits(int fitness) { + return bestDemerits[fitness]; + } + + public KnuthNode getNode(int fitness) { + return bestNode[fitness]; + } + + public double getAdjust(int fitness) { + return bestAdjust[fitness]; + } + + public int getAvailableShrink(int fitness) { + return bestAvailableShrink[fitness]; + } + + public int getAvailableStretch(int fitness) { + return bestAvailableStretch[fitness]; + } + + public int getDifference(int fitness) { + return bestDifference[fitness]; + } + + public double getMinDemerits() { + if (bestIndex != -1) { + return getDemerits(bestIndex); + } else { + // anyway, this should never happen + return INFINITE_DEMERITS; + } + } + + public void reset() { + for (int i = 0; i < 4; i ++) { + bestDemerits[i] = INFINITE_DEMERITS; + bestNode[i] = null; + bestAdjust[i] = 0.0; + bestDifference[i] = 0; + bestAvailableShrink[i] = 0; + bestAvailableStretch[i] = 0; + } + bestIndex = -1; + } + } + + + + public abstract void updateData1(int total, double demerits) ; + + public abstract void updateData2(KnuthNode bestActiveNode, + KnuthSequence sequence, + int total) ; + + public int findBreakingPoints(KnuthSequence par, int lineWidth, + double threshold, boolean force, + boolean hyphenationAllowed) { + this.par = par; + this.threshold = threshold; + this.force = force; + this.lineWidth = lineWidth; + this.totalWidth = 0; + this.totalStretch = 0; + this.totalShrink = 0; + + activeLines = new KnuthNode[20]; + + // reset lastTooShort and lastTooLong, as they could be not null + // because of previous calls to findBreakingPoints + lastTooShort = lastTooLong = null; + // reset startLine and endLine + startLine = endLine = 0; + // current element in the paragraph + KnuthElement thisElement = null; + // previous element in the paragraph is a KnuthBox? + boolean previousIsBox = false; + + // index of the first KnuthBox in the sequence + int firstBoxIndex = 0; + while (alignment != org.apache.fop.fo.Constants.EN_CENTER + && ! ((KnuthElement) par.get(firstBoxIndex)).isBox()) { + firstBoxIndex++; + } + + // create an active node representing the starting point + activeLines = new KnuthNode[20]; + addNode(0, new KnuthNode(firstBoxIndex, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, null)); + + if (log.isTraceEnabled()) { + log.trace("Looping over " + par.size() + " box objects"); + } + + KnuthNode lastForced = getNode(0); + + // main loop + for (int i = 0; i < par.size(); i++) { + thisElement = getElement(i); + if (thisElement.isBox()) { + // a KnuthBox object is not a legal line break + totalWidth += thisElement.getW(); + previousIsBox = true; + } else if (thisElement.isGlue()) { + // a KnuthGlue object is a legal line break + // only if the previous object is a KnuthBox + if (previousIsBox) { + considerLegalBreak(thisElement, i); + } + totalWidth += thisElement.getW(); + totalStretch += thisElement.getY(); + totalShrink += thisElement.getZ(); + previousIsBox = false; + } else { + // a KnuthPenalty is a legal line break + // only if its penalty is not infinite; + // if hyphenationAllowed is false, ignore flagged penalties + if (((KnuthPenalty) thisElement).getP() + < KnuthElement.INFINITE + && (hyphenationAllowed || !((KnuthPenalty) thisElement).isFlagged())) { + considerLegalBreak(thisElement, i); + } + previousIsBox = false; + } + if (activeNodeCount == 0) { + if (!force) { + log.debug("Could not find a set of breaking points " + threshold); + return 0; + } + if (lastTooShort == null || lastForced.position == lastTooShort.position) { + lastForced = lastTooLong; + } else { + lastForced = lastTooShort; + } + + log.debug("Restarting at node " + lastForced); + lastForced.totalDemerits = 0; + addNode(lastForced.line, lastForced); + i = lastForced.position; + startLine = lastForced.line; + endLine = startLine + 1; + totalWidth = lastForced.totalWidth; + totalStretch = lastForced.totalStretch; + totalShrink = lastForced.totalShrink; + lastTooShort = lastTooLong = null; + } + } + if (log.isTraceEnabled()) { + log.trace("Main loop completed " + activeNodeCount); + log.trace("Active nodes=" + toString("")); + } + + // there is at least one set of breaking points + // select one or more active nodes, removing the others from the list + int line = filterActiveNodes(); + + // for each active node, create a set of breaking points + for (int i = startLine; i < endLine; i++) { + for (KnuthNode node = getNode(line); node != null; node = node.next) { + updateData1(node.line, node.totalDemerits); + calculateBreakPoints(node, par, node.line); + } + } + + activeLines = null; + return line; + } + + private void considerLegalBreak(KnuthElement element, int elementIdx) { + + if (log.isTraceEnabled()) { + log.trace("Feasible breakpoint at " + par.indexOf(element) + " " + totalWidth + "+" + totalStretch + "-" + totalShrink); + log.trace("\tCurrent active node list: " + activeNodeCount + " " + this.toString("\t")); + } + + lastDeactivated = null; + lastTooLong = null; + for (int line = startLine; line < endLine; line++) { + for (KnuthNode node = getNode(line); node != null; node = node.next) { + if (node.position == elementIdx) { + continue; + } + int difference = computeDifference(node, element); + double r = computeAdjustmentRatio(node, difference); + int availableShrink = totalShrink - node.totalShrink; + int availableStretch = totalStretch - node.totalStretch; + if (log.isTraceEnabled()) { + log.trace("\tr=" + r); + log.trace("\tline=" + line); + } + + // The line would be too long. + if (r < -1 || element.isForcedBreak()) { + // Deactivate node. + if (log.isTraceEnabled()) { + log.trace("Removing " + node); + } + removeNode(line, node); + lastDeactivated = compareNodes(lastDeactivated, node); + } + + // The line is within the available shrink and the threshold. + if (r >= -1 && r <= threshold) { + int fitnessClass = computeFitness(r); + double demerits = computeDemerits(node, element, fitnessClass, r); + + if (log.isTraceEnabled()) { + log.trace("\tDemerits=" + demerits); + log.trace("\tFitness class=" + fitnessClass); + } + + if (demerits < best.getDemerits(fitnessClass)) { + // updates best demerits data + best.addRecord(demerits, node, r, availableShrink, availableStretch, + difference, fitnessClass); + lastTooShort = null; + } + } + + // The line is way too short, but we are in forcing mode, so a node is + // calculated and stored in lastValidNode. + if (force && (r <= -1 || r > threshold)) { + int fitnessClass = computeFitness(r); + double demerits = computeDemerits(node, element, fitnessClass, r); + if (r <= -1) { + if (lastTooLong == null || demerits < lastTooLong.totalDemerits) { + lastTooLong = new KnuthNode(elementIdx, line + 1, fitnessClass, + totalWidth, totalStretch, totalShrink, + r, availableShrink, availableStretch, + difference, demerits, node); + if (log.isTraceEnabled()) { + log.trace("Picking tooLong " + lastTooLong); + } + } + } else { + if (lastTooShort == null || demerits <= lastTooShort.totalDemerits) { + lastTooShort = new KnuthNode(elementIdx, line + 1, fitnessClass, + totalWidth, totalStretch, totalShrink, + r, availableShrink, availableStretch, + difference, demerits, node); + if (log.isTraceEnabled()) { + log.trace("Picking tooShort " + lastTooShort); + } + } + } + } + } + addBreaks(line, elementIdx); + } + } + + private void addBreaks(int line, int elementIdx) { + if (!best.hasRecords()) { + return; + } + + int newWidth = totalWidth; + int newStretch = totalStretch; + int newShrink = totalShrink; + + for (int i = elementIdx; i < par.size(); i++) { + KnuthElement tempElement = getElement(i); + if (tempElement.isBox()) { + break; + } else if (tempElement.isGlue()) { + newWidth += tempElement.getW(); + newStretch += tempElement.getY(); + newShrink += tempElement.getZ(); + } else if (tempElement.isForcedBreak() && i != elementIdx) { + break; + } + } + + // add nodes to the active nodes list + double minimumDemerits = best.getMinDemerits() + incompatibleFitnessDemerit; + for (int i = 0; i <= 3; i++) { + if (best.notInfiniteDemerits(i) && best.getDemerits(i) <= minimumDemerits) { + // the nodes in activeList must be ordered + // by line number and position; + if (log.isTraceEnabled()) { + log.trace("\tInsert new break in list of " + activeNodeCount); + } + KnuthNode newNode = new KnuthNode(elementIdx, line + 1, i, + newWidth, newStretch, newShrink, + best.getAdjust(i), + best.getAvailableShrink(i), + best.getAvailableStretch(i), + best.getDifference(i), + best.getDemerits(i), + best.getNode(i)); + addNode(line + 1, newNode); + } + } + best.reset(); + } + + /** + * Return the difference between the line width and the width of the break that + * ends in 'element'. + * @param activeNode + * @param element + * @return The difference in width. Positive numbers mean extra space in the line, + * negative number that the line overflows. + */ + private int computeDifference(KnuthNode activeNode, KnuthElement element) { + // compute the adjustment ratio + int actualWidth = totalWidth - activeNode.totalWidth; + if (element.isPenalty()) { + actualWidth += element.getW(); + } + return lineWidth - actualWidth; + } + + /** + * Return the adjust ration needed to make up for the difference. A ration of + * + * @param activeNode + * @param difference + * @return The ration. + */ + private double computeAdjustmentRatio(KnuthNode activeNode, int difference) { + // compute the adjustment ratio + if (difference > 0) { + int maxAdjustment = totalStretch - activeNode.totalStretch; + if (maxAdjustment > 0) { + return (double) difference / maxAdjustment; + } else { + return INFINITE_RATIO; + } + } else if (difference < 0) { + int maxAdjustment = totalShrink - activeNode.totalShrink; + if (maxAdjustment > 0) { + return (double) difference / maxAdjustment; + } else { + return -INFINITE_RATIO; + } + } else { + return 0; + } + } + + /** + * Figure out the fitness class of this line (tight, loose, + * very tight or very loose). + * @param r + * @return + */ + private int computeFitness(double r) { + int newFitnessClass; + if (r < -0.5) { + return 0; + } else if (r <= 0.5) { + return 1; + } else if (r <= 1) { + return 2; + } else { + return 3; + } + } + + private double computeDemerits(KnuthNode activeNode, KnuthElement element, + int fitnessClass, double r) { + double demerits = 0; + // compute demerits + double f = Math.abs(r); + f = 1 + 100 * f * f * f; + if (element.isPenalty() && element.getP() >= 0) { + f += element.getP(); + demerits = f * f; + } else if (element.isPenalty() && !element.isForcedBreak()) { + double penalty = element.getP(); + demerits = f * f - penalty * penalty; + } else { + demerits = f * f; + } + + if (element.isPenalty() && ((KnuthPenalty) element).isFlagged() + && getElement(activeNode.position).isPenalty() + && ((KnuthPenalty) getElement(activeNode.position)).isFlagged()) { + // add demerit for consecutive breaks at flagged penalties + demerits += repeatedFlaggedDemerit; + } + if (Math.abs(fitnessClass - activeNode.fitness) > 1) { + // add demerit for consecutive breaks + // with very different fitness classes + demerits += incompatibleFitnessDemerit; + } + demerits += activeNode.totalDemerits; + return demerits; + } + + /** + * Return the element at index idx in the paragraph. + * @param idx index of the element. + * @return + */ + private KnuthElement getElement(int idx) { + return (KnuthElement) par.get(idx); + } + + /** + * Compare two KnuthNodes and return the node with the least demerit. + * @param node1 The first knuth node. + * @param node2 The other knuth node. + * @return + */ + protected KnuthNode compareNodes(KnuthNode node1, KnuthNode node2) { + if (node1 == null || node2.position > node1.position) { + return node2; + } + if (node2.position == node1.position) { + if (node2.totalDemerits < node1.totalDemerits) { + return node2; + } + } + return node1; + } + + /** + * Add a KnuthNode at the end of line 'line'. + * If this is the first node in the line, adjust endLine accordingly. + * @param line + * @param node + */ + private void addNode(int line, KnuthNode node) { + int headIdx = line * 2; + if (headIdx >= activeLines.length) { + KnuthNode[] oldList = activeLines; + activeLines = new KnuthNode[headIdx + headIdx]; + System.arraycopy(oldList, 0, activeLines, 0, oldList.length); + } + node.next = null; + if (activeLines[headIdx + 1] != null) { + activeLines[headIdx + 1].next = node; + } else { + activeLines[headIdx] = node; + endLine = line+1; + } + activeLines[headIdx + 1] = node; + activeNodeCount++; + } + + /** + * Remove the first node in line 'line'. If the line then becomes empty, adjust the + * startLine accordingly. + * @param line + * @param node + */ + protected void removeNode(int line, KnuthNode node) { + KnuthNode n = getNode(line); + if (n != node) { + log.error("Should be first"); + } else { + activeLines[line*2] = node.next; + if (node.next == null) { + activeLines[line*2+1] = null; + } + while (startLine < endLine && getNode(startLine) == null) { + startLine++; + } + } + activeNodeCount--; + } + + protected KnuthNode getNode(int line) { + return activeLines[line * 2]; + } + + /** + * Return true if the position 'idx' is a legal breakpoint. + * @param idx + * @return + */ + private boolean isLegalBreakpoint(int idx) { + KnuthElement elm = getElement(idx); + if (elm.isPenalty() && elm.getP() != KnuthElement.INFINITE) { + return true; + } else if (idx > 0 && elm.isGlue() && getElement(idx-1).isBox()) { + return true; + } else { + return false; + } + } + + public int getDifference(int line) { + return positions[line].difference; + } + + public double getAdjustRatio(int line) { + return positions[line].adjustRatio; + } + + public int getStart(int line) { + KnuthNode previous = positions[line].previous; + return line == 0 ? 0 : previous.position + 1; + } + + public int getEnd(int line) { + return positions[line].position; + } + + /** + * Return a string representation of a MinOptMax in the form of a + * "width+stretch-shrink". Useful only for debugging. + * @param mom + * @return + */ + private static String width(MinOptMax mom) { + return mom.opt + "+" + (mom.max - mom.opt) + "-" + (mom.opt - mom.min); + + } + + public String toString(String prepend) { + StringBuffer sb = new StringBuffer(); + sb.append("[\n"); + for (int i = startLine; i < endLine; i++) { + for (KnuthNode node = getNode(i); node != null; node = node.next) { + sb.append(prepend + "\t" + node + ",\n"); + } + } + sb.append(prepend + "]"); + return sb.toString(); + } + + protected abstract int filterActiveNodes() ; + + private void calculateBreakPoints(KnuthNode node, KnuthSequence par, + int total) { + KnuthNode bestActiveNode = node; + // use bestActiveNode to determine the optimum breakpoints + for (int i = node.line; i > 0; i--) { + updateData2(bestActiveNode, par, total); + bestActiveNode = bestActiveNode.previous; + } + } +} diff --git a/src/java/org/apache/fop/layoutmgr/CharacterLayoutManager.java b/src/java/org/apache/fop/layoutmgr/CharacterLayoutManager.java index 3a067d524..2f3661089 100644 --- a/src/java/org/apache/fop/layoutmgr/CharacterLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/CharacterLayoutManager.java @@ -137,7 +137,7 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { // node is a fo:Character if (letterSpaceIPD.min == letterSpaceIPD.max) { // constant letter space, only return a box - returnList.add(new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, + returnList.add(new KnuthInlineBox(areaInfo.ipdArea.opt, areaInfo.lead, areaInfo.total, areaInfo.middle, new LeafPosition(this, 0), false)); } else { @@ -145,14 +145,14 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { // at the moment the character is supposed to have no letter spaces, // but returning this sequence allows us to change only one element // if addALetterSpaceTo() is called - returnList.add(new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, + returnList.add(new KnuthInlineBox(areaInfo.ipdArea.opt, areaInfo.lead, areaInfo.total, areaInfo.middle, new LeafPosition(this, 0), false)); returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, new LeafPosition(this, -1), true)); returnList.add(new KnuthGlue(0, 0, 0, new LeafPosition(this, -1), true)); - returnList.add(new KnuthBox(0, 0, 0, 0, + returnList.add(new KnuthInlineBox(0, 0, 0, 0, new LeafPosition(this, -1), true)); } @@ -171,7 +171,7 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { if (letterSpaceIPD.min == letterSpaceIPD.max) { // constant letter space, return a new box - return new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, + return new KnuthInlineBox(areaInfo.ipdArea.opt, areaInfo.lead, areaInfo.total, areaInfo.middle, new LeafPosition(this, 0), false); } else { @@ -220,7 +220,7 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { if (letterSpaceIPD.min == letterSpaceIPD.max || areaInfo.iLScount == 0) { // constant letter space, or no letter space - returnList.add(new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, + returnList.add(new KnuthInlineBox(areaInfo.ipdArea.opt, areaInfo.lead, areaInfo.total, areaInfo.middle, new LeafPosition(this, 0), false)); if (areaInfo.bHyphenated) { @@ -231,7 +231,7 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { } else { // adjustable letter space returnList.add - (new KnuthBox(areaInfo.ipdArea.opt + (new KnuthInlineBox(areaInfo.ipdArea.opt - areaInfo.iLScount * letterSpaceIPD.opt, areaInfo.lead, areaInfo.total, areaInfo.middle, new LeafPosition(this, 0), false)); @@ -242,7 +242,7 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { areaInfo.iLScount * letterSpaceIPD.max - letterSpaceIPD.opt, areaInfo.iLScount * letterSpaceIPD.opt - letterSpaceIPD.min, new LeafPosition(this, -1), true)); - returnList.add(new KnuthBox(0, 0, 0, 0, + returnList.add(new KnuthInlineBox(0, 0, 0, 0, new LeafPosition(this, -1), true)); if (areaInfo.bHyphenated) { returnList.add @@ -256,7 +256,7 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { } protected void addId() { - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); } } diff --git a/src/java/org/apache/fop/layoutmgr/ContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/ContentLayoutManager.java index 093439fba..626532fa4 100644 --- a/src/java/org/apache/fop/layoutmgr/ContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/ContentLayoutManager.java @@ -19,21 +19,15 @@ package org.apache.fop.layoutmgr; import org.apache.fop.apps.FOUserAgent; -import org.apache.fop.fo.FObj; import org.apache.fop.fo.Constants; import org.apache.fop.fo.pagination.Title; -import org.apache.fop.fo.flow.Marker; import org.apache.fop.area.Area; -import org.apache.fop.area.AreaTreeHandler; import org.apache.fop.area.LineArea; import org.apache.fop.area.inline.InlineArea; -import org.apache.fop.area.Resolvable; -import org.apache.fop.area.PageViewport; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; -import java.util.Map; import java.util.ArrayList; import org.apache.fop.traits.MinOptMax; @@ -47,7 +41,6 @@ import org.apache.commons.logging.LogFactory; */ public class ContentLayoutManager implements InlineLevelLayoutManager { private FOUserAgent userAgent; - private AreaTreeHandler areaTreeHandler; private Area holder; private int stackSize; private LayoutManager parentLM; @@ -117,7 +110,7 @@ public class ContentLayoutManager implements InlineLevelLayoutManager { while (contentIter.hasNext()) { KnuthElement element = (KnuthElement) contentIter.next(); if (element.isBox()) { - KnuthBox box = (KnuthBox) element; + KnuthInlineBox box = (KnuthInlineBox) element; if (box.getLead() > lineLead) { lineLead = box.getLead(); } @@ -205,10 +198,6 @@ public class ContentLayoutManager implements InlineLevelLayoutManager { return userAgent; } - /** @see org.apache.fop.layoutmgr.LayoutManager */ - public void setFObj(FObj fobj) { - } - /** @see org.apache.fop.layoutmgr.LayoutManager */ public void setParent(LayoutManager lm) { parentLM = lm; @@ -252,44 +241,6 @@ public class ContentLayoutManager implements InlineLevelLayoutManager { public void getWordChars(StringBuffer sbChars, Position bp1, Position bp2) { } - /** @see org.apache.fop.layoutmgr.LayoutManager */ - public String getCurrentPageNumberString() { - return parentLM.getCurrentPageNumberString(); - } - - /** @see org.apache.fop.layoutmgr.LayoutManager */ - public PageViewport resolveRefID(String ref) { - return parentLM.resolveRefID(ref); - } - - /** @see org.apache.fop.layoutmgr.LayoutManager */ - public void addIDToPage(String id) { - parentLM.addIDToPage(id); - } - - /** @see org.apache.fop.layoutmgr.LayoutManager */ - public void addUnresolvedArea(String id, Resolvable res) { - parentLM.addUnresolvedArea(id, res); - } - - /** @see org.apache.fop.layoutmgr.LayoutManager */ - public void addMarkerMap(Map marks, boolean starting, boolean isfirst, boolean islast) { - parentLM.addMarkerMap(marks, starting, isfirst, islast); - } - - /** @see org.apache.fop.layoutmgr.LayoutManager */ - public Marker retrieveMarker(String name, int pos, int boundary) { - return parentLM.retrieveMarker(name, pos, boundary); - } - - /** - * @see org.apache.fop.layoutmgr.LayoutManager - * @return the AreaTreeHandler object. - */ - public AreaTreeHandler getAreaTreeHandler() { - return parentLM.getAreaTreeHandler(); - } - /** * @see org.apache.fop.layoutmgr.LayoutManager#preLoadNext */ @@ -358,8 +309,8 @@ public class ContentLayoutManager implements InlineLevelLayoutManager { return contentList; } - public KnuthElement addALetterSpaceTo(KnuthElement element) { - return element; + public List addALetterSpaceTo(List oldList) { + return oldList; } public void getWordChars(StringBuffer sbChars, Position pos) { @@ -373,10 +324,13 @@ public class ContentLayoutManager implements InlineLevelLayoutManager { } public LinkedList getChangedKnuthElements(List oldList, - int flaggedPenalty, + /*int flaggedPenalty,*/ int alignment) { return null; } - + + public PageSequenceLayoutManager getPSLM() { + return parentLM.getPSLM(); + } } diff --git a/src/java/org/apache/fop/layoutmgr/ElementListUtils.java b/src/java/org/apache/fop/layoutmgr/ElementListUtils.java new file mode 100644 index 000000000..c1100d00f --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/ElementListUtils.java @@ -0,0 +1,136 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr; + +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +import org.apache.fop.traits.MinOptMax; + +/** + * Utilities for Knuth element lists. + */ +public class ElementListUtils { + + /** + * Removes all legal breaks in an element list. + * @param elements the element list + */ + public static void removeLegalBreaks(LinkedList elements) { + ListIterator i = elements.listIterator(); + while (i.hasNext()) { + KnuthElement el = (KnuthElement)i.next(); + if (el.isPenalty()) { + KnuthPenalty penalty = (KnuthPenalty)el; + //Convert all penalties no break inhibitors + if (penalty.getP() < KnuthPenalty.INFINITE) { + i.set(new KnuthPenalty(penalty.getW(), KnuthPenalty.INFINITE, + penalty.isFlagged(), penalty.getPosition(), penalty.isAuxiliary())); + } + } else if (el.isGlue()) { + i.previous(); + if (el.isBox()) { + i.next(); + i.add(new KnuthPenalty(0, KnuthPenalty.INFINITE, false, + /*new Position(getTableLM())*/null, false)); + } + } + } + } + + /** + * Removes all legal breaks in an element list. A constraint can be specified to limit the + * range in which the breaks are removed. Legal breaks occuring before at least + * constraint.opt space is filled will be removed. + * @param elements the element list + * @param constraint min/opt/max value to restrict the range in which the breaks are removed. + * @return true if the opt constraint is bigger than the list contents + */ + public static boolean removeLegalBreaks(LinkedList elements, MinOptMax constraint) { + int len = 0; + ListIterator i = elements.listIterator(); + while (i.hasNext()) { + KnuthElement el = (KnuthElement)i.next(); + if (el.isPenalty()) { + KnuthPenalty penalty = (KnuthPenalty)el; + //Convert all penalties no break inhibitors + if (penalty.getP() < KnuthPenalty.INFINITE) { + i.set(new KnuthPenalty(penalty.getW(), KnuthPenalty.INFINITE, + penalty.isFlagged(), penalty.getPosition(), penalty.isAuxiliary())); + } + } else if (el.isGlue()) { + len += el.getW(); + i.previous(); + if (el.isBox()) { + i.next(); + i.add(new KnuthPenalty(0, KnuthPenalty.INFINITE, false, + /*new Position(getTableLM())*/null, false)); + } + } else { + len += el.getW(); + } + if (len > constraint.opt) { + return false; + } + } + return true; + } + + /** + * Calculates the content length of the given element list. Warning: It doesn't take any + * stretch and shrink possibilities into account. + * @param elems the element list + * @param start element at which to start + * @param end element at which to stop + * @return the content length + */ + public static int calcContentLength(List elems, int start, int end) { + ListIterator iter = elems.listIterator(start); + int count = end - start + 1; + int len = 0; + while (iter.hasNext()) { + KnuthElement el = (KnuthElement)iter.next(); + if (el.isBox()) { + len += el.getW(); + } else if (el.isGlue()) { + len += el.getW(); + } else { + //log.debug("Ignoring penalty: " + el); + //ignore penalties + } + count--; + if (count == 0) { + break; + } + } + return len; + } + + /** + * Calculates the content length of the given element list. Warning: It doesn't take any + * stretch and shrink possibilities into account. + * @param elems the element list + * @return the content length + */ + public static int calcContentLength(List elems) { + return calcContentLength(elems, 0, elems.size() - 1); + } + +} diff --git a/src/java/org/apache/fop/layoutmgr/ExternalGraphicLayoutManager.java b/src/java/org/apache/fop/layoutmgr/ExternalGraphicLayoutManager.java index bad48b8a1..18c3640eb 100644 --- a/src/java/org/apache/fop/layoutmgr/ExternalGraphicLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/ExternalGraphicLayoutManager.java @@ -208,7 +208,7 @@ public class ExternalGraphicLayoutManager extends LeafNodeLayoutManager { } protected void addId() { - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); } } diff --git a/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java b/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java index f249941c0..7a8386dd2 100644 --- a/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java @@ -19,13 +19,14 @@ package org.apache.fop.layoutmgr; import org.apache.fop.datatypes.PercentBase; -import org.apache.fop.fo.flow.Marker; import org.apache.fop.fo.pagination.Flow; import org.apache.fop.area.Area; import org.apache.fop.area.BlockParent; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; -import org.apache.fop.traits.MinOptMax; +import java.util.ListIterator; /** * LayoutManager for an fo:flow object. @@ -33,8 +34,8 @@ import org.apache.fop.traits.MinOptMax; * This LM is responsible for getting columns of the appropriate size * and filling them with block-level areas generated by its children. */ -public class FlowLayoutManager extends BlockStackingLayoutManager { - +public class FlowLayoutManager extends BlockStackingLayoutManager + implements BlockLevelLayoutManager { private Flow fobj; /** List of break possibilities */ @@ -51,6 +52,22 @@ public class FlowLayoutManager extends BlockStackingLayoutManager { */ private int numSubsequentOverflows = 0; +/*LF*/ + private static class StackingIter extends PositionIterator { + StackingIter(Iterator parentIter) { + super(parentIter); + } + + protected LayoutManager getLM(Object nextObj) { + return ((Position) nextObj).getLM(); + } + + protected Position getPos(Object nextObj) { + return ((Position) nextObj); + } + } +/*LF*/ + /** * This is the top level layout manager. * It is created by the PageSequence FO. @@ -61,9 +78,7 @@ public class FlowLayoutManager extends BlockStackingLayoutManager { fobj = node; } - /** - * @see org.apache.fop.layoutmgr.LayoutManager#getNextBreakPoss(LayoutContext) - */ + /* public BreakPoss getNextBreakPoss(LayoutContext context) { // currently active LM @@ -131,26 +146,266 @@ public class FlowLayoutManager extends BlockStackingLayoutManager { new LeafPosition(this, blockBreaks.size() - 1)); } return null; + }*/ + + + /** + * "wrap" the Position inside each element moving the elements from + * SourceList to targetList + * @param sourceList source list + * @param targetList target list receiving the wrapped position elements + */ + protected void wrapPositionElements(List sourceList, List targetList) { + ListIterator listIter = sourceList.listIterator(); + while (listIter.hasNext()) { + KnuthElement tempElement; + tempElement = (KnuthElement) listIter.next(); + //if (tempElement.getLayoutManager() != this) { + tempElement.setPosition(new NonLeafPosition(this, + tempElement.getPosition())); + //} + targetList.add(tempElement); + } + } + + +//TODO Reintroduce emergency counter (generate error to avoid endless loop) + public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + // set layout dimensions + fobj.setLayoutDimension(PercentBase.BLOCK_IPD, context.getRefIPD()); + fobj.setLayoutDimension(PercentBase.BLOCK_BPD, context.getStackLimit().opt); + + // currently active LM + BlockLevelLayoutManager curLM; + BlockLevelLayoutManager prevLM = null; + //MinOptMax stackSize = new MinOptMax(); + LinkedList returnedList; + LinkedList returnList = new LinkedList(); + + while ((curLM = ((BlockLevelLayoutManager) getChildLM())) != null) { + if (curLM.generatesInlineAreas()) { + log.error("inline area not allowed under flow - ignoring"); + curLM.setFinished(true); + continue; + } + + // Set up a LayoutContext + //MinOptMax bpd = context.getStackLimit(); + + LayoutContext childLC = new LayoutContext(0); + childLC.setStackLimit(context.getStackLimit()); + childLC.setRefIPD(context.getRefIPD()); + + // get elements from curLM + returnedList = curLM.getNextKnuthElements(childLC, alignment); + //log.debug("FLM.getNextKnuthElements> returnedList.size() = " + returnedList.size()); + + // "wrap" the Position inside each element + LinkedList tempList = returnedList; + returnedList = new LinkedList(); + wrapPositionElements(tempList, returnedList); + + if (returnedList.size() == 1 + && ((KnuthElement)returnedList.getFirst()).isPenalty() + && ((KnuthPenalty)returnedList.getFirst()).getP() == -KnuthElement.INFINITE) { + // a descendant of this flow has break-before + returnList.addAll(returnedList); + return returnList; + } else { + if (returnList.size() > 0) { + // there is a block before this one + if (prevLM.mustKeepWithNext() + || curLM.mustKeepWithPrevious()) { + // add an infinite penalty to forbid a break between blocks + returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, new Position(this), false)); + } else if (!((KnuthElement) returnList.getLast()).isGlue()) { + // add a null penalty to allow a break between blocks + returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false)); + } + } +/*LF*/ if (returnedList.size() > 0) { // controllare! + returnList.addAll(returnedList); + if (((KnuthElement)returnedList.getLast()).isPenalty() + && ((KnuthPenalty)returnedList.getLast()).getP() == -KnuthElement.INFINITE) { + // a descendant of this flow has break-after +/*LF*/ //System.out.println("FLM - break after!!"); + return returnList; + } +/*LF*/ } + } + prevLM = curLM; + } + + setFinished(true); + + if (returnList.size() > 0) { + return returnList; + } else { + return null; + } + } + + public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) { + log.debug(" FLM.negotiateBPDAdjustment> " + adj); + + if (lastElement.getPosition() instanceof NonLeafPosition) { + // this element was not created by this FlowLM + NonLeafPosition savedPos = (NonLeafPosition)lastElement.getPosition(); + lastElement.setPosition(savedPos.getPosition()); + int returnValue = ((BlockLevelLayoutManager) lastElement.getLayoutManager()).negotiateBPDAdjustment(adj, lastElement); + lastElement.setPosition(savedPos); + log.debug(" FLM.negotiateBPDAdjustment> result " + returnValue); + return returnValue; + } else { + return 0; + } + } + + public void discardSpace(KnuthGlue spaceGlue) { + log.debug(" FLM.discardSpace> "); + + if (spaceGlue.getPosition() instanceof NonLeafPosition) { + // this element was not created by this FlowLM + NonLeafPosition savedPos = (NonLeafPosition)spaceGlue.getPosition(); + spaceGlue.setPosition(savedPos.getPosition()); + ((BlockLevelLayoutManager) spaceGlue.getLayoutManager()).discardSpace(spaceGlue); + spaceGlue.setPosition(savedPos); + } + } + + public boolean mustKeepTogether() { + return false; + } + + public boolean mustKeepWithPrevious() { + return false; + } + + public boolean mustKeepWithNext() { + return false; + } + + public LinkedList getChangedKnuthElements(List oldList, /*int flaggedPenalty,*/ int alignment) { + ListIterator oldListIterator = oldList.listIterator(); + KnuthElement returnedElement; + LinkedList returnedList = new LinkedList(); + LinkedList returnList = new LinkedList(); + KnuthElement prevElement = null; + KnuthElement currElement = null; + int fromIndex = 0; + +/*LF*/ //System.out.println(""); +/*LF*/ //System.out.println("FLM.getChangedKnuthElements> prima dell'unwrap, oldList.size() = " + oldList.size() + " da 0 a " + (oldList.size() - 1)); + // "unwrap" the Positions stored in the elements + KnuthElement oldElement; + while (oldListIterator.hasNext()) { + oldElement = (KnuthElement)oldListIterator.next(); + if (oldElement.getPosition() instanceof NonLeafPosition) { + // oldElement was created by a descendant of this FlowLM + oldElement.setPosition(((NonLeafPosition)oldElement.getPosition()).getPosition()); + } else { + // thisElement was created by this FlowLM, remove it + oldListIterator.remove(); + } + } + // reset the iterator + oldListIterator = oldList.listIterator(); + +/*LF*/ //System.out.println("FLM.getChangedKnuthElements> dopo l'unwrap, oldList.size() = " + oldList.size() + " da 0 a " + (oldList.size() - 1)); + + while (oldListIterator.hasNext()) { + currElement = (KnuthElement) oldListIterator.next(); +/*LF*/ //System.out.println("elemento n. " + oldListIterator.previousIndex() + " nella oldList"); + if (prevElement != null + && prevElement.getLayoutManager() != currElement.getLayoutManager()) { + // prevElement is the last element generated by the same LM + BlockLevelLayoutManager prevLM = (BlockLevelLayoutManager) + prevElement.getLayoutManager(); + BlockLevelLayoutManager currLM = (BlockLevelLayoutManager) + currElement.getLayoutManager(); +/*LF*/ //System.out.println("FLM.getChangedKnuthElements> chiamata da " + fromIndex + " a " + oldListIterator.previousIndex()); + returnedList.addAll(prevLM.getChangedKnuthElements(oldList.subList(fromIndex, oldListIterator.previousIndex()), + /*flaggedPenalty,*/ alignment)); + fromIndex = oldListIterator.previousIndex(); + + // there is another block after this one + if (prevLM.mustKeepWithNext() + || currLM.mustKeepWithPrevious()) { + // add an infinite penalty to forbid a break between blocks + returnedList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, new Position(this), false)); + } else if (!((KnuthElement) returnedList.getLast()).isGlue()) { + // add a null penalty to allow a break between blocks + returnedList.add(new KnuthPenalty(0, 0, false, new Position(this), false)); + } + } + prevElement = currElement; + } + if (currElement != null) { + BlockLevelLayoutManager currLM = (BlockLevelLayoutManager) + currElement.getLayoutManager(); +/*LF*/ //System.out.println("FLM.getChangedKnuthElements> chiamata da " + fromIndex + " a " + oldList.size()); + returnedList.addAll(currLM.getChangedKnuthElements(oldList.subList(fromIndex, oldList.size()), + /*flaggedPenalty,*/ alignment)); + } + + // "wrap" the Position stored in each element of returnedList + // and add elements to returnList + ListIterator listIter = returnedList.listIterator(); + while (listIter.hasNext()) { + returnedElement = (KnuthElement)listIter.next(); + if (returnedElement.getLayoutManager() != this) { + returnedElement.setPosition(new NonLeafPosition(this, returnedElement.getPosition())); + } + returnList.add(returnedElement); + } + + return returnList; } /** * @see org.apache.fop.layoutmgr.LayoutManager#addAreas(PositionIterator, LayoutContext) */ public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { - - LayoutManager childLM; + AreaAdditionUtil.addAreas(parentIter, layoutContext); + /* + LayoutManager childLM = null; LayoutContext lc = new LayoutContext(0); + LayoutManager firstLM = null; + LayoutManager lastLM = null; + + // "unwrap" the NonLeafPositions stored in parentIter + // and put them in a new list; + LinkedList positionList = new LinkedList(); + Position pos; while (parentIter.hasNext()) { - LeafPosition lfp = (LeafPosition) parentIter.next(); - // Add the block areas to Area - PositionIterator breakPosIter = new BreakPossPosIter( - blockBreaks, iStartPos, lfp.getLeafPos() + 1); - iStartPos = lfp.getLeafPos() + 1; - while ((childLM = breakPosIter.getNextChildLM()) != null) { - childLM.addAreas(breakPosIter, lc); + pos = (Position)parentIter.next(); + if (pos instanceof NonLeafPosition) { + // pos was created by a child of this FlowLM + positionList.add(((NonLeafPosition) pos).getPosition()); + lastLM = ((NonLeafPosition) pos).getPosition().getLM(); + if (firstLM == null) { + firstLM = lastLM; + } + } else { + // pos was created by this FlowLM, so it must be ignored } } + StackingIter childPosIter = new StackingIter(positionList.listIterator()); + while ((childLM = childPosIter.getNextChildLM()) != null) { + // Add the block areas to Area + lc.setFlags(LayoutContext.FIRST_AREA, childLM == firstLM); + lc.setFlags(LayoutContext.LAST_AREA, childLM == lastLM); + // set space before for the first LM, in order to implement + // display-align = center or after + lc.setSpaceBefore((childLM == firstLM ? layoutContext.getSpaceBefore() : 0)); + // set space after for each LM, in order to implement + // display-align = distribute + lc.setSpaceAfter(layoutContext.getSpaceAfter()); + lc.setStackLimit(layoutContext.getStackLimit()); + childLM.addAreas(childPosIter, lc); + }*/ + flush(); } @@ -184,17 +439,5 @@ public class FlowLayoutManager extends BlockStackingLayoutManager { reset(null); } } - - /** - * Retrieve marker is not allowed in the flow so this reports an - * error and returns null. - * - * @see org.apache.fop.layoutmgr.LayoutManager - */ - public Marker retrieveMarker(String name, int pos, int boundary) { - // error cannot retrieve markers in flow - log.error("Cannot retrieve a marker from the flow"); - return null; - } } diff --git a/src/java/org/apache/fop/layoutmgr/ICLayoutManager.java b/src/java/org/apache/fop/layoutmgr/ICLayoutManager.java index f6e2a4cd8..e03947d2e 100644 --- a/src/java/org/apache/fop/layoutmgr/ICLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/ICLayoutManager.java @@ -44,6 +44,6 @@ public class ICLayoutManager extends LeafNodeLayoutManager { } protected void addId() { - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); } } diff --git a/src/java/org/apache/fop/layoutmgr/InlineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/InlineLayoutManager.java index c20270670..b02e96c98 100755 --- a/src/java/org/apache/fop/layoutmgr/InlineLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/InlineLayoutManager.java @@ -179,6 +179,7 @@ public class InlineLayoutManager extends InlineStackingLayoutManager return null; } + /* public KnuthElement addALetterSpaceTo(KnuthElement element) { NonLeafPosition savedPos = (NonLeafPosition) element.getPosition(); element.setPosition(savedPos.getPosition()); @@ -325,6 +326,6 @@ public class InlineLayoutManager extends InlineStackingLayoutManager returnList.add(returnedElement); } return returnList; - } + }*/ } diff --git a/src/java/org/apache/fop/layoutmgr/InlineLevelLayoutManager.java b/src/java/org/apache/fop/layoutmgr/InlineLevelLayoutManager.java index 767e901b6..98d7c35e5 100644 --- a/src/java/org/apache/fop/layoutmgr/InlineLevelLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/InlineLevelLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2004 The Apache Software Foundation. + * Copyright 2004-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package org.apache.fop.layoutmgr; -import java.util.LinkedList; import java.util.List; /** @@ -26,25 +25,15 @@ import java.util.List; */ public interface InlineLevelLayoutManager extends LayoutManager { - /** - * Get a sequence of KnuthElements representing the content - * of the node assigned to the LM - * - * @param context the LayoutContext used to store layout information - * @param alignment the desired text alignement - * @return the list of KnuthElements - */ - LinkedList getNextKnuthElements(LayoutContext context, int alignment); - /** * Tell the LM to modify its data, adding a letter space - * to the word fragment represented by the given element, - * and returning a corrected element + * to the word fragment represented by the given elements, + * and returning the corrected elements * - * @param element the element which must be given one more letter space - * @return the new element replacing the old one + * @param oldList the elements which must be given one more letter space + * @return the new elements replacing the old ones */ - KnuthElement addALetterSpaceTo(KnuthElement element); + List addALetterSpaceTo(List oldList); /** * Get the word chars corresponding to the given position @@ -70,15 +59,4 @@ public interface InlineLevelLayoutManager extends LayoutManager { */ boolean applyChanges(List oldList); - /** - * Get a sequence of KnuthElements representing the content - * of the node assigned to the LM, after changes have been applied - * - * @param oldList the elements to replace - * @param flaggedPenalty the penalty value for hyphenated lines - * @param alignment the desired text alignment - * @return the updated list of KnuthElements - */ - LinkedList getChangedKnuthElements(List oldList, int flaggedPenalty, - int alignment); } diff --git a/src/java/org/apache/fop/layoutmgr/InlineStackingLayoutManager.java b/src/java/org/apache/fop/layoutmgr/InlineStackingLayoutManager.java index 92fd5d7d6..199314576 100644 --- a/src/java/org/apache/fop/layoutmgr/InlineStackingLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/InlineStackingLayoutManager.java @@ -20,11 +20,14 @@ package org.apache.fop.layoutmgr; import java.util.LinkedList; import java.util.Iterator; +import java.util.List; import java.util.ListIterator; import java.util.HashMap; import org.apache.fop.fo.FObj; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.SpaceProperty; +import org.apache.fop.traits.InlineProps; import org.apache.fop.traits.SpaceVal; import org.apache.fop.area.Area; import org.apache.fop.area.inline.InlineArea; @@ -37,7 +40,8 @@ import org.apache.fop.traits.MinOptMax; * which stack children in the inline direction, such as Inline or * Line. It should not be instantiated directly. */ -public class InlineStackingLayoutManager extends AbstractLayoutManager { +public class InlineStackingLayoutManager extends AbstractLayoutManager + implements InlineLevelLayoutManager { private static class StackingIter extends PositionIterator { @@ -69,7 +73,7 @@ public class InlineStackingLayoutManager extends AbstractLayoutManager { private Area currentArea; // LineArea or InlineParent private BreakPoss prevBP; - protected LayoutContext childLC ; + protected LayoutContext childLC; private LayoutManager lastChildLM = null; // Set when return last breakposs private boolean bAreaCreated = false; @@ -534,7 +538,7 @@ public class InlineStackingLayoutManager extends AbstractLayoutManager { // Current child layout context protected LayoutContext getContext() { - return childLC ; + return childLC; } protected void addSpace(Area parentArea, MinOptMax spaceRange, @@ -558,5 +562,214 @@ public class InlineStackingLayoutManager extends AbstractLayoutManager { } } } + + public LinkedList getNextKnuthElements(LayoutContext lc, int alignment) { + InlineLevelLayoutManager curLM; + + // the list returned by child LM + LinkedList returnedList; + KnuthElement returnedElement; + + // the list which will be returned to the parent LM + LinkedList returnList = new LinkedList(); + + SpaceSpecifier leadingSpace = lc.getLeadingSpace(); + + if (lc.startsNewArea()) { + // First call to this LM in new parent "area", but this may + // not be the first area created by this inline + childLC = new LayoutContext(lc); + lc.getLeadingSpace().addSpace(new SpaceVal(getSpaceStart())); + + // Check for "fence" + if (hasLeadingFence(!lc.isFirstArea())) { + // Reset leading space sequence for child areas + leadingSpace = new SpaceSpecifier(false); + } + // Reset state variables + clearPrevIPD(); // Clear stored prev content dimensions + } + + while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) { + // get KnuthElements from curLM + returnedList = curLM.getNextKnuthElements(lc, alignment); + if (returnedList != null) { + // "wrap" the Position stored in each element of returnedList + ListIterator listIter = returnedList.listIterator(); + while (listIter.hasNext()) { + returnedElement = (KnuthElement) listIter.next(); + returnedElement.setPosition + (new NonLeafPosition(this, + returnedElement.getPosition())); + returnList.add(returnedElement); + } + return returnList; + } else { + // curLM returned null because it finished; + // just iterate once more to see if there is another child + } + } + setFinished(true); + return null; + } + + public List addALetterSpaceTo(List oldList) { + // old list contains only a box, or the sequence: box penalty glue box + + ListIterator oldListIterator = oldList.listIterator(); + KnuthElement element = null; + // "unwrap" the Position stored in each element of oldList + while (oldListIterator.hasNext()) { + element = (KnuthElement) oldListIterator.next(); + element.setPosition(((NonLeafPosition)element.getPosition()).getPosition()); + } + + oldList = ((InlineLevelLayoutManager) + element.getLayoutManager()).addALetterSpaceTo(oldList); + + // "wrap" againg the Position stored in each element of oldList + oldListIterator = oldList.listIterator(); + while (oldListIterator.hasNext()) { + element = (KnuthElement) oldListIterator.next(); + element.setPosition(new NonLeafPosition(this, element.getPosition())); + } + + return oldList; + } + + public void getWordChars(StringBuffer sbChars, Position pos) { + Position newPos = ((NonLeafPosition) pos).getPosition(); + ((InlineLevelLayoutManager) + newPos.getLM()).getWordChars(sbChars, newPos); + } + + public void hyphenate(Position pos, HyphContext hc) { + Position newPos = ((NonLeafPosition) pos).getPosition(); + ((InlineLevelLayoutManager) + newPos.getLM()).hyphenate(newPos, hc); + } + + public boolean applyChanges(List oldList) { + // "unwrap" the Positions stored in the elements + ListIterator oldListIterator = oldList.listIterator(); + KnuthElement oldElement; + while (oldListIterator.hasNext()) { + oldElement = (KnuthElement) oldListIterator.next(); + oldElement.setPosition + (((NonLeafPosition) oldElement.getPosition()).getPosition()); + } + // reset the iterator + oldListIterator = oldList.listIterator(); + + InlineLevelLayoutManager prevLM = null; + InlineLevelLayoutManager currLM; + int fromIndex = 0; + + boolean bSomethingChanged = false; + while(oldListIterator.hasNext()) { + oldElement = (KnuthElement) oldListIterator.next(); + currLM = (InlineLevelLayoutManager) oldElement.getLayoutManager(); + // initialize prevLM + if (prevLM == null) { + prevLM = currLM; + } + + if (currLM != prevLM || !oldListIterator.hasNext()) { + if (oldListIterator.hasNext()) { + bSomethingChanged + = prevLM.applyChanges(oldList.subList(fromIndex, oldListIterator.previousIndex())) + || bSomethingChanged; + prevLM = currLM; + fromIndex = oldListIterator.previousIndex(); + } else if (currLM == prevLM) { + bSomethingChanged + = prevLM.applyChanges(oldList.subList(fromIndex, oldList.size())) + || bSomethingChanged; + } else { + bSomethingChanged + = prevLM.applyChanges(oldList.subList(fromIndex, oldListIterator.previousIndex())) + || bSomethingChanged; + bSomethingChanged + = currLM.applyChanges(oldList.subList(oldListIterator.previousIndex(), oldList.size())) + || bSomethingChanged; + } + } + } + + // "wrap" again the Positions stored in the elements + oldListIterator = oldList.listIterator(); + while (oldListIterator.hasNext()) { + oldElement = (KnuthElement) oldListIterator.next(); + oldElement.setPosition + (new NonLeafPosition(this, oldElement.getPosition())); + } + return bSomethingChanged; + } + + public LinkedList getChangedKnuthElements(List oldList, /*int flaggedPenalty,*/ int alignment) { + // "unwrap" the Positions stored in the elements + ListIterator oldListIterator = oldList.listIterator(); + KnuthElement oldElement; + while (oldListIterator.hasNext()) { + oldElement = (KnuthElement) oldListIterator.next(); + oldElement.setPosition + (((NonLeafPosition) oldElement.getPosition()).getPosition()); + } + // reset the iterator + oldListIterator = oldList.listIterator(); + + KnuthElement returnedElement; + LinkedList returnedList = new LinkedList(); + LinkedList returnList = new LinkedList(); + InlineLevelLayoutManager prevLM = null; + InlineLevelLayoutManager currLM; + int fromIndex = 0; + + while(oldListIterator.hasNext()) { + oldElement = (KnuthElement) oldListIterator.next(); + currLM = (InlineLevelLayoutManager) oldElement.getLayoutManager(); + if (prevLM == null) { + prevLM = currLM; + } + + if (currLM != prevLM || !oldListIterator.hasNext()) { + if (oldListIterator.hasNext()) { + returnedList.addAll + (prevLM.getChangedKnuthElements + (oldList.subList(fromIndex, + oldListIterator.previousIndex()), + /*flaggedPenalty,*/ alignment)); + prevLM = currLM; + fromIndex = oldListIterator.previousIndex(); + } else if (currLM == prevLM) { + returnedList.addAll + (prevLM.getChangedKnuthElements + (oldList.subList(fromIndex, oldList.size()), + /*flaggedPenalty,*/ alignment)); + } else { + returnedList.addAll + (prevLM.getChangedKnuthElements + (oldList.subList(fromIndex, + oldListIterator.previousIndex()), + /*flaggedPenalty,*/ alignment)); + returnedList.addAll + (currLM.getChangedKnuthElements + (oldList.subList(oldListIterator.previousIndex(), + oldList.size()), + /*flaggedPenalty,*/ alignment)); + } + } + } + + // "wrap" the Position stored in each element of returnedList + ListIterator listIter = returnedList.listIterator(); + while (listIter.hasNext()) { + returnedElement = (KnuthElement) listIter.next(); + returnedElement.setPosition + (new NonLeafPosition(this, returnedElement.getPosition())); + returnList.add(returnedElement); + } + return returnList; + } } diff --git a/src/java/org/apache/fop/layoutmgr/InstreamForeignObjectLM.java b/src/java/org/apache/fop/layoutmgr/InstreamForeignObjectLM.java index dce3fa461..2d50c2270 100644 --- a/src/java/org/apache/fop/layoutmgr/InstreamForeignObjectLM.java +++ b/src/java/org/apache/fop/layoutmgr/InstreamForeignObjectLM.java @@ -19,7 +19,6 @@ package org.apache.fop.layoutmgr; // Java -import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; // FOP @@ -194,7 +193,7 @@ public class InstreamForeignObjectLM extends LeafNodeLayoutManager { * @see org.apache.fop.layoutmgr.LeafNodeLayoutManager#addId() */ protected void addId() { - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); } } diff --git a/src/java/org/apache/fop/layoutmgr/KnuthBlockBox.java b/src/java/org/apache/fop/layoutmgr/KnuthBlockBox.java new file mode 100644 index 000000000..896532ebb --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/KnuthBlockBox.java @@ -0,0 +1,41 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr; + +import org.apache.fop.traits.MinOptMax; + +public class KnuthBlockBox extends KnuthBox { + + private MinOptMax ipdRange; + private int bpd; + + public KnuthBlockBox(int w, MinOptMax range, int bpdim, Position pos, boolean bAux) { + super(w, pos, bAux); + ipdRange = (MinOptMax) range.clone(); + bpd = bpdim; + } + + public MinOptMax getIPDRange() { + return (MinOptMax) ipdRange.clone(); + } + + public int getBPD() { + return bpd; + } +} \ No newline at end of file diff --git a/src/java/org/apache/fop/layoutmgr/KnuthBox.java b/src/java/org/apache/fop/layoutmgr/KnuthBox.java index 8479f77d6..77f463068 100644 --- a/src/java/org/apache/fop/layoutmgr/KnuthBox.java +++ b/src/java/org/apache/fop/layoutmgr/KnuthBox.java @@ -32,49 +32,33 @@ package org.apache.fop.layoutmgr; * positioning, and the methods used to get them. */ public class KnuthBox extends KnuthElement { - private int lead; - private int total; - private int middle; /** * Create a new KnuthBox. * * @param w the width of this box - * @param l the height of this box above the main baseline - * @param t the total height of this box - * @param m the height of this box above and below the middle baseline * @param pos the Position stored in this box * @param bAux is this box auxiliary? */ - public KnuthBox(int w, int l, int t, int m, Position pos, boolean bAux) { + public KnuthBox(int w, Position pos, boolean bAux) { super(w, pos, bAux); - lead = l; - total = t; - middle = m; } + /** @see org.apache.fop.layoutmgr.KnuthElement#isBox() */ public boolean isBox() { return true; } - /** - * Return the height of this box above the main baseline. - */ - public int getLead() { - return lead; - } - - /** - * Return the total height of this box. - */ - public int getTotal() { - return total; - } - - /** - * Return the height of this box above and below the middle baseline. - */ - public int getMiddle() { - return middle; + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer(64); + if (isAuxiliary()) { + sb.append("aux. "); + } + sb.append("box"); + sb.append(" w="); + sb.append(getW()); + return sb.toString(); } + } diff --git a/src/java/org/apache/fop/layoutmgr/KnuthGlue.java b/src/java/org/apache/fop/layoutmgr/KnuthGlue.java index e377ce351..299266ed8 100644 --- a/src/java/org/apache/fop/layoutmgr/KnuthGlue.java +++ b/src/java/org/apache/fop/layoutmgr/KnuthGlue.java @@ -45,8 +45,10 @@ package org.apache.fop.layoutmgr; * to get these values. */ public class KnuthGlue extends KnuthElement { + private int stretchability; private int shrinkability; + private int adjustmentClass = -1; /** * Create a new KnuthGlue. @@ -63,21 +65,48 @@ public class KnuthGlue extends KnuthElement { shrinkability = z; } + public KnuthGlue(int w, int y, int z, + int iAdjClass, Position pos, boolean bAux) { + super(w, pos, bAux); + stretchability = y; + shrinkability = z; + adjustmentClass = iAdjClass; + } + + /** @see org.apache.fop.layoutmgr.KnuthElement#isGlue() */ public boolean isGlue() { return true; } - /** - * Return the stretchability of this glue. - */ + /** @return the stretchability of this glue. */ public int getY() { return stretchability; } - /** - * Return the shrinkability of this glue. - */ + /** @return the shrinkability of this glue. */ public int getZ() { return shrinkability; } + + /** @return the adjustment class (or role) of this glue. */ + public int getAdjustmentClass() { + return adjustmentClass; + } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer(64); + if (isAuxiliary()) { + sb.append("aux. "); + } + sb.append("glue"); + sb.append(" w=").append(getW()); + sb.append(" stretch=").append(getY()); + sb.append(" shrink=").append(getZ()); + if (getAdjustmentClass() >= 0) { + sb.append(" adj-class=").append(getAdjustmentClass()); + } + return sb.toString(); + } + } diff --git a/src/java/org/apache/fop/layoutmgr/KnuthInlineBox.java b/src/java/org/apache/fop/layoutmgr/KnuthInlineBox.java new file mode 100644 index 000000000..cca2bbbb5 --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/KnuthInlineBox.java @@ -0,0 +1,64 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr; + +public class KnuthInlineBox extends KnuthBox { + + private int lead; + private int total; + private int middle; + + /** + * Create a new KnuthBox. + * + * @param w the width of this box + * @param l the height of this box above the main baseline + * @param t the total height of this box + * @param m the height of this box above and below the middle baseline + * @param pos the Position stored in this box + * @param bAux is this box auxiliary? + */ + public KnuthInlineBox(int w, int l, int t, int m, Position pos, boolean bAux) { + super(w, pos, bAux); + lead = l; + total = t; + middle = m; + } + + /** + * @return the height of this box above the main baseline. + */ + public int getLead() { + return lead; + } + + /** + * @return the total height of this box. + */ + public int getTotal() { + return total; + } + + /** + * @return the height of this box above and below the middle baseline. + */ + public int getMiddle() { + return middle; + } +} \ No newline at end of file diff --git a/src/java/org/apache/fop/layoutmgr/KnuthParagraph.java b/src/java/org/apache/fop/layoutmgr/KnuthParagraph.java index baf3492d6..cf08b6315 100644 --- a/src/java/org/apache/fop/layoutmgr/KnuthParagraph.java +++ b/src/java/org/apache/fop/layoutmgr/KnuthParagraph.java @@ -15,9 +15,10 @@ */ /* $Id$ */ + package org.apache.fop.layoutmgr; -import java.util.ArrayList; +import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -60,7 +61,7 @@ public class KnuthParagraph { /** * The paragraph of KnuthElements. */ - private ArrayList par; + private List par; /** * The width of a line. @@ -114,7 +115,7 @@ public class KnuthParagraph { protected static Log log = LogFactory.getLog(KnuthParagraph.class); - public KnuthParagraph(ArrayList par) { + public KnuthParagraph(List par) { this.best = new BestRecords(); this.par = par; } diff --git a/src/java/org/apache/fop/layoutmgr/KnuthPenalty.java b/src/java/org/apache/fop/layoutmgr/KnuthPenalty.java index 83b013132..e0b528867 100644 --- a/src/java/org/apache/fop/layoutmgr/KnuthPenalty.java +++ b/src/java/org/apache/fop/layoutmgr/KnuthPenalty.java @@ -37,8 +37,12 @@ package org.apache.fop.layoutmgr; * be chosen as breaking points for consecutive lines. */ public class KnuthPenalty extends KnuthElement { + + public static final int FLAGGED_PENALTY = 50; + private int penalty; private boolean bFlagged; + private int breakClass = -1; /** * Create a new KnuthPenalty. @@ -55,6 +59,14 @@ public class KnuthPenalty extends KnuthElement { bFlagged = f; } + public KnuthPenalty(int w, int p, boolean f, + int iBreakClass, Position pos, boolean bAux) { + super(w, pos, bAux); + penalty = p; + bFlagged = f; + breakClass = iBreakClass; + } + public boolean isPenalty() { return true; } @@ -76,4 +88,36 @@ public class KnuthPenalty extends KnuthElement { public boolean isForcedBreak() { return penalty == -KnuthElement.INFINITE; } + + public int getBreakClass() { + return breakClass; + } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer(64); + if (isAuxiliary()) { + sb.append("aux. "); + } + sb.append("penalty"); + sb.append(" p="); + if (getP() < 0) { + sb.append("-"); + } + if (Math.abs(getP()) == INFINITE) { + sb.append("INFINITE"); + } else { + sb.append(getP()); + } + if (isFlagged()) { + sb.append(" [flagged]"); + } + sb.append(" w="); + sb.append(getW()); + if (isForcedBreak()) { + sb.append(" (forced break)"); + } + return sb.toString(); + } + } diff --git a/src/java/org/apache/fop/layoutmgr/KnuthPossPosIter.java b/src/java/org/apache/fop/layoutmgr/KnuthPossPosIter.java index 1b6a03cf1..0b157c050 100644 --- a/src/java/org/apache/fop/layoutmgr/KnuthPossPosIter.java +++ b/src/java/org/apache/fop/layoutmgr/KnuthPossPosIter.java @@ -1,5 +1,5 @@ /* - * Copyright 2004 The Apache Software Foundation. + * Copyright 2004-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,14 +26,22 @@ public class KnuthPossPosIter extends PositionIterator { /** * Main constructor - * @param bpList List of break possibilities + * @param elementList List of Knuth elements * @param startPos starting position * @param endPos ending position */ - public KnuthPossPosIter(List bpList, int startPos, int endPos) { - super(bpList.listIterator(startPos)); + public KnuthPossPosIter(List elementList, int startPos, int endPos) { + super(elementList.listIterator(startPos)); iterCount = endPos - startPos; } + + /** + * Auxiliary constructor + * @param elementList List of Knuth elements + */ + public KnuthPossPosIter(List elementList) { + this(elementList, 0, elementList.size()); + } // Check position < endPos diff --git a/src/java/org/apache/fop/layoutmgr/KnuthSequence.java b/src/java/org/apache/fop/layoutmgr/KnuthSequence.java new file mode 100644 index 000000000..55465f83d --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/KnuthSequence.java @@ -0,0 +1,83 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr; + +import java.util.ArrayList; + +/** + * Represents a list of Knuth elements. + */ +public class KnuthSequence extends ArrayList { + /** Number of elements to ignore at the beginning of the list. */ + public int ignoreAtStart = 0; + /** Number of elements to ignore at the end of the list. */ + public int ignoreAtEnd = 0; + + /** + * Creates a new and empty list. + */ + public KnuthSequence() { + super(); + } + + /** + * Marks the start of the sequence. + */ + public void startSequence() { + } + + /** + * @return a finalized sequence. + */ + public KnuthSequence endSequence() { + // remove glue and penalty item at the end of the paragraph + while (this.size() > ignoreAtStart + && !((KnuthElement)this.get(this.size() - 1)).isBox()) { + this.remove(this.size() - 1); + } + if (this.size() > ignoreAtStart) { + // add the elements representing the space at the end of the last line + // and the forced break + this.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, null, false)); + this.add(new KnuthGlue(0, 10000000, 0, null, false)); + this.add(new KnuthPenalty(0, -KnuthElement.INFINITE, false, null, false)); + ignoreAtEnd = 3; + return this; + } else { + this.clear(); + return null; + } + } + + public KnuthElement getLast() { + int idx = size(); + if (idx == 0) { + return null; + } + return (KnuthElement) get(idx - 1); + } + + public KnuthElement removeLast() { + int idx = size(); + if (idx == 0) { + return null; + } + return (KnuthElement) remove(idx - 1); + } +} diff --git a/src/java/org/apache/fop/layoutmgr/LayoutContext.java b/src/java/org/apache/fop/layoutmgr/LayoutContext.java index a6f41d5c3..6452f75bc 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutContext.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutContext.java @@ -87,6 +87,10 @@ public class LayoutContext { private int iLineHeight; private int iBaseline; private int iMiddleShift; + private int iTopShift; /*LF*/ + private int iBottomShift; /*LF*/ + private int iSpaceBefore; /*LF*/ + private int iSpaceAfter; /*LF*/ public LayoutContext(LayoutContext parentLC) { this.flags = parentLC.flags; @@ -100,6 +104,10 @@ public class LayoutContext { this.iLineHeight = parentLC.iLineHeight; this.iBaseline = parentLC.iBaseline; this.iMiddleShift = parentLC.iMiddleShift; +/*LF*/ this.iTopShift = parentLC.iTopShift; +/*LF*/ this.iBottomShift = parentLC.iBottomShift; +/*LF*/ this.iSpaceBefore = parentLC.iSpaceBefore; +/*LF*/ this.iSpaceAfter = parentLC.iSpaceAfter; // Copy other fields as necessary. Use clone??? } @@ -235,6 +243,38 @@ public class LayoutContext { return iBaseline + iMiddleShift; } + public void setTopShift(int ts) { + iTopShift = ts; + } + + public int getTopBaseline() { + return iBaseline + iTopShift; + } + + public void setBottomShift(int bs) { + iBottomShift = bs; + } + + public int getBottomBaseline() { + return iBaseline + iBottomShift; + } + + public int getSpaceBefore() { + return iSpaceBefore; + } + + public void setSpaceBefore(int sp) { + iSpaceBefore = sp; + } + + public int getSpaceAfter() { + return iSpaceAfter; + } + + public void setSpaceAfter(int sp) { + iSpaceAfter = sp; + } + public String toString() { return "Layout Context:" + "\nStack Limit: \t" + (getStackLimit() == null ? "null" : getStackLimit().toString()) + diff --git a/src/java/org/apache/fop/layoutmgr/LayoutManager.java b/src/java/org/apache/fop/layoutmgr/LayoutManager.java index 93fd36df5..6c3187117 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutManager.java @@ -18,31 +18,16 @@ package org.apache.fop.layoutmgr; +import java.util.LinkedList; import java.util.List; -import java.util.Map; - -import org.apache.fop.fo.flow.Marker; import org.apache.fop.area.Area; -import org.apache.fop.area.Resolvable; -import org.apache.fop.area.PageViewport; -import org.apache.fop.area.AreaTreeHandler; -import org.apache.fop.fo.FObj; /** * The interface for all LayoutManagers. */ public interface LayoutManager { - /** - * Set the FO object for this layout manager. - * For layout managers that are created without an FO - * this may not be called. - * - * @param obj the FO object for this layout manager - */ - void setFObj(FObj obj); - /** * Set the parent layout manager. * The parent layout manager is required for adding areas. @@ -62,6 +47,12 @@ public interface LayoutManager { */ void initialize(); + /** + * Get the active PageSequenceLayoutManager instance for this + * layout process. + */ + PageSequenceLayoutManager getPSLM(); + /** * Generates inline areas. * This is used to check if the layout manager generates inline @@ -167,68 +158,6 @@ public interface LayoutManager { */ void addAreas(PositionIterator posIter, LayoutContext context); - /** - * Get the string of the current page number. - * - * @return the string for the current page number - */ - String getCurrentPageNumberString(); - - /** - * Resolve the id reference. - * This is called by an area looking for an id reference. - * If the id reference is not found then it should add a resolvable object. - * - * @param ref the id reference - * @return the page containing the id reference or null if not found - */ - PageViewport resolveRefID(String ref); - - /** - * Add an id to the page. - * (todo) add the location of the area on the page - * - * @param id the id reference to add. - */ - void addIDToPage(String id); - - /** - * Add an unresolved area. - * The is used to add a resolvable object to the page for a given id. - * - * @param id the id reference this object needs for resolving - * @param res the resolvable object - */ - void addUnresolvedArea(String id, Resolvable res); - - /** - * Add the marker. - * A number of formatting objects may contain markers. This - * method is used to add those markers to the page. - * - * @param name the marker class name - * @param starting if the area being added is starting or ending - * @param isfirst if the area being added has is-first trait - * @param islast if the area being added has is-last trait - */ - void addMarkerMap(Map marks, boolean starting, boolean isfirst, boolean islast); - - /** - * Retrieve a marker. - * This method is used when retrieve a marker. - * - * @param name the class name of the marker - * @param pos the retrieve position - * @param boundary the boundary for retrieving the marker - * @return the layout manaager of the retrieved marker if any - */ - Marker retrieveMarker(String name, int pos, int boundary); - - /** - * @return the AreaTreeHandler object. - */ - AreaTreeHandler getAreaTreeHandler(); - /** * Load next child LMs, up to child LM index pos * @param pos index up to which child LMs are requested @@ -254,4 +183,27 @@ public interface LayoutManager { * @param newLMs the list of LMs to be added */ void addChildLMs(List newLMs); + + /** + * Get a sequence of KnuthElements representing the content + * of the node assigned to the LM + * + * @param context the LayoutContext used to store layout information + * @param alignment the desired text alignement + * @return the list of KnuthElements + */ + LinkedList getNextKnuthElements(LayoutContext context, int alignment); + + /** + * Get a sequence of KnuthElements representing the content + * of the node assigned to the LM, after changes have been applied + * + * @param oldList the elements to replace + * @param flaggedPenalty the penalty value for hyphenated lines + * @param alignment the desired text alignment + * @return the updated list of KnuthElements + */ + LinkedList getChangedKnuthElements(List oldList, /*int flaggedPenalty,*/ + int alignment); + } diff --git a/src/java/org/apache/fop/layoutmgr/LayoutManagerMaker.java b/src/java/org/apache/fop/layoutmgr/LayoutManagerMaker.java index 43b62d6c6..b0d723444 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutManagerMaker.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutManagerMaker.java @@ -1,5 +1,5 @@ /* - * Copyright 2004 The Apache Software Foundation. + * Copyright 2004-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,9 @@ package org.apache.fop.layoutmgr; import java.util.List; import org.apache.fop.fo.FONode; -import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.pagination.PageSequence; +import org.apache.fop.area.AreaTreeHandler; + /** * The interface for all LayoutManager makers @@ -34,14 +36,24 @@ public interface LayoutManagerMaker { public void makeLayoutManagers(FONode node, List lms); /** - * Make the LayoutManager for the node. - * If not exactly one LayoutManagers is made, - * a FOPException is thrown. + * Make a specific LayoutManager for the node. + * If not exactly one LayoutManagers is available, + * an IllegalStateException is thrown. * @param node the FO node for which the LayoutManagers are made * @return The created LayoutManager + * @throws IllegalStateException if not exactly one + * LayoutManager is available for the requested node + */ + public LayoutManager makeLayoutManager(FONode node); + + /** + * Make a PageSequenceLayoutManager object. + * @param ath the AreaTreeHandler object the PSLM interacts with + * @param ps the fo:page-sequence object this PSLM will process + * @return The created PageSequenceLayoutManager object */ - public LayoutManager makeLayoutManager(FONode node) - throws FOPException; + public PageSequenceLayoutManager makePageSequenceLayoutManager( + AreaTreeHandler ath, PageSequence ps); } diff --git a/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java b/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java index 85bc59c96..4b235c09d 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java @@ -21,14 +21,11 @@ import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import java.util.List; -import java.util.ListIterator; import java.util.Iterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.fop.apps.FOPException; - import org.apache.fop.fo.FONode; import org.apache.fop.fo.FOText; import org.apache.fop.fo.FObjMixed; @@ -61,13 +58,14 @@ import org.apache.fop.fo.pagination.Flow; import org.apache.fop.fo.pagination.PageSequence; import org.apache.fop.fo.pagination.StaticContent; import org.apache.fop.fo.pagination.Title; +import org.apache.fop.area.AreaTreeHandler; import org.apache.fop.layoutmgr.list.ListBlockLayoutManager; import org.apache.fop.layoutmgr.list.ListItemLayoutManager; -import org.apache.fop.layoutmgr.table.Body; +/*import org.apache.fop.layoutmgr.table.Body; import org.apache.fop.layoutmgr.table.Cell; import org.apache.fop.layoutmgr.table.Column; -import org.apache.fop.layoutmgr.table.Row; +import org.apache.fop.layoutmgr.table.Row;*/ import org.apache.fop.layoutmgr.table.TableLayoutManager; /** @@ -88,7 +86,7 @@ public class LayoutManagerMapping implements LayoutManagerMaker { /** * Initializes the set of maker objects associated with this LayoutManagerMapping */ - private void initialize() { + protected void initialize() { makers.put(FOText.class, new FOTextLayoutManagerMaker()); makers.put(FObjMixed.class, new Maker()); makers.put(BidiOverride.class, new BidiOverrideLayoutManagerMaker()); @@ -112,14 +110,13 @@ public class LayoutManagerMapping implements LayoutManagerMaker { makers.put(PageNumber.class, new PageNumberLayoutManagerMaker()); makers.put(PageNumberCitation.class, new PageNumberCitationLayoutManagerMaker()); - makers.put(PageSequence.class, new PageSequenceLayoutManagerMaker()); makers.put(Table.class, new TableLayoutManagerMaker()); - makers.put(TableBody.class, new TableBodyLayoutManagerMaker()); - makers.put(TableColumn.class, new TableColumnLayoutManagerMaker()); - makers.put(TableRow.class, new TableRowLayoutManagerMaker()); - makers.put(TableCell.class, new TableCellLayoutManagerMaker()); - makers.put(TableFooter.class, new TableBodyLayoutManagerMaker()); - makers.put(TableHeader.class, new TableBodyLayoutManagerMaker()); + makers.put(TableBody.class, new /*TableBodyLayoutManager*/Maker()); + makers.put(TableColumn.class, new /*TableColumnLayoutManager*/Maker()); + makers.put(TableRow.class, new /*TableRowLayoutManager*/Maker()); + makers.put(TableCell.class, new /*TableCellLayoutManager*/Maker()); + makers.put(TableFooter.class, new /*TableBodyLayoutManager*/Maker()); + makers.put(TableHeader.class, new /*TableBodyLayoutManager*/Maker()); makers.put(Flow.class, new FlowLayoutManagerMaker()); makers.put(StaticContent.class, new StaticContentLayoutManagerMaker()); makers.put(Wrapper.class, new WrapperLayoutManagerMaker()); @@ -141,22 +138,26 @@ public class LayoutManagerMapping implements LayoutManagerMaker { /** * @see org.apache.fop.layoutmgr.LayoutManagerMaker#makeLayoutManager(FONode) */ - public LayoutManager makeLayoutManager(FONode node) - throws FOPException { + public LayoutManager makeLayoutManager(FONode node) { List lms = new ArrayList(); makeLayoutManagers(node, lms); if (lms.size() == 0) { - throw new FOPException("No LayoutManager for class " + throw new IllegalStateException("LayoutManager for class " + node.getClass() - + "; 1 was required"); + + " is missing."); } else if (lms.size() > 1) { - throw new FOPException("More than 1 LayoutManager for class " + throw new IllegalStateException("Duplicate LayoutManagers for class " + node.getClass() - + "; 1 was required"); + + " found, only one may be declared."); } return (LayoutManager) lms.get(0); } + public PageSequenceLayoutManager makePageSequenceLayoutManager( + AreaTreeHandler ath, PageSequence ps) { + return new PageSequenceLayoutManager(ath, ps); + } + public static class Maker { public void make(FONode node, List lms) { // no layout manager @@ -305,14 +306,9 @@ public class LayoutManagerMapping implements LayoutManagerMaker { } } - public static class PageSequenceLayoutManagerMaker extends Maker { - public void make(FONode node, List lms) { - lms.add(new PageSequenceLayoutManager((PageSequence) node)); - } - } - public static class TableLayoutManagerMaker extends Maker { + /* private List getColumnLayoutManagerList(Table table, TableLayoutManager tlm) { List columnLMs = null; List columns = table.getColumns(); @@ -347,19 +343,21 @@ public class LayoutManagerMapping implements LayoutManagerMaker { } } return columnLMs; - } + }*/ public void make(FONode node, List lms) { Table table = (Table) node; TableLayoutManager tlm = new TableLayoutManager(table); + /* List columnLMs = getColumnLayoutManagerList(table, tlm); if (columnLMs != null) { tlm.setColumns(columnLMs); - } + }*/ lms.add(tlm); } } - + + /* public static class TableBodyLayoutManagerMaker extends Maker { public void make(FONode node, List lms) { lms.add(new Body((TableBody) node)); @@ -383,7 +381,7 @@ public class LayoutManagerMapping implements LayoutManagerMaker { public void make(FONode node, List lms) { lms.add(new Cell((TableCell) node)); } - } + }*/ public static class FlowLayoutManagerMaker extends Maker { public void make(FONode node, List lms) { diff --git a/src/java/org/apache/fop/layoutmgr/LeaderLayoutManager.java b/src/java/org/apache/fop/layoutmgr/LeaderLayoutManager.java index 4cb87a419..328592794 100644 --- a/src/java/org/apache/fop/layoutmgr/LeaderLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LeaderLayoutManager.java @@ -263,7 +263,7 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager { lead, total, middle); // node is a fo:Leader - returnList.add(new KnuthBox(0, areaInfo.lead, areaInfo.total, + returnList.add(new KnuthInlineBox(0, areaInfo.lead, areaInfo.total, areaInfo.middle, new LeafPosition(this, -1), true)); returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, @@ -273,7 +273,7 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager { areaInfo.ipdArea.max - areaInfo.ipdArea.opt, areaInfo.ipdArea.opt - areaInfo.ipdArea.min, new LeafPosition(this, 0), false)); - returnList.add(new KnuthBox(0, areaInfo.lead, areaInfo.total, + returnList.add(new KnuthInlineBox(0, areaInfo.lead, areaInfo.total, areaInfo.middle, new LeafPosition(this, -1), true)); @@ -308,7 +308,7 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager { LinkedList returnList = new LinkedList(); - returnList.add(new KnuthBox(0, areaInfo.lead, areaInfo.total, + returnList.add(new KnuthInlineBox(0, areaInfo.lead, areaInfo.total, areaInfo.middle, new LeafPosition(this, -1), true)); returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, @@ -318,7 +318,7 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager { areaInfo.ipdArea.max - areaInfo.ipdArea.opt, areaInfo.ipdArea.opt - areaInfo.ipdArea.min, new LeafPosition(this, 0), false)); - returnList.add(new KnuthBox(0, areaInfo.lead, areaInfo.total, + returnList.add(new KnuthInlineBox(0, areaInfo.lead, areaInfo.total, areaInfo.middle, new LeafPosition(this, -1), true)); @@ -327,6 +327,6 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager { } protected void addId() { - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); } } diff --git a/src/java/org/apache/fop/layoutmgr/LeafNodeLayoutManager.java b/src/java/org/apache/fop/layoutmgr/LeafNodeLayoutManager.java index b81adc686..a3dcba111 100644 --- a/src/java/org/apache/fop/layoutmgr/LeafNodeLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LeafNodeLayoutManager.java @@ -77,6 +77,12 @@ public class LeafNodeLayoutManager extends AbstractLayoutManager super(node); } + /** + * Create a Leaf node layout mananger. + */ + public LeafNodeLayoutManager() { + } + /** * get the inline area. * @param context the context used to create the area @@ -194,10 +200,10 @@ public class LeafNodeLayoutManager extends AbstractLayoutManager curArea.setOffset(context.getMiddleBaseline() - bpd / 2); break; case EN_TOP: - //curArea.setOffset(0); + curArea.setOffset(context.getTopBaseline()); break; case EN_BOTTOM: - curArea.setOffset(context.getLineHeight() - bpd); + curArea.setOffset(context.getBottomBaseline() - bpd); break; case EN_BASELINE: default: @@ -269,21 +275,19 @@ public class LeafNodeLayoutManager extends AbstractLayoutManager // node is a fo:ExternalGraphic, fo:InstreamForeignObject, // fo:PageNumber or fo:PageNumberCitation - returnList.add(new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, + returnList.add(new KnuthInlineBox(areaInfo.ipdArea.opt, areaInfo.lead, areaInfo.total, areaInfo.middle, new LeafPosition(this, 0), false)); setFinished(true); return returnList; } - public void getWordChars(StringBuffer sbChars, Position bp) { + public List addALetterSpaceTo(List oldList) { + // return the unchanged elements + return oldList; } - public KnuthElement addALetterSpaceTo(KnuthElement element) { - // return the unchanged box object - return new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, - areaInfo.total, areaInfo.middle, - new LeafPosition(this, 0), false); + public void getWordChars(StringBuffer sbChars, Position pos) { } public void hyphenate(Position pos, HyphContext hc) { @@ -295,7 +299,7 @@ public class LeafNodeLayoutManager extends AbstractLayoutManager } public LinkedList getChangedKnuthElements(List oldList, - int flaggedPenalty, + /*int flaggedPenalty,*/ int alignment) { if (isFinished()) { return null; @@ -305,9 +309,9 @@ public class LeafNodeLayoutManager extends AbstractLayoutManager // fobj is a fo:ExternalGraphic, fo:InstreamForeignObject, // fo:PageNumber or fo:PageNumberCitation - returnList.add(new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, - areaInfo.total, areaInfo.middle, - new LeafPosition(this, 0), true)); + returnList.add(new KnuthInlineBox(areaInfo.ipdArea.opt, areaInfo.lead, + areaInfo.total, areaInfo.middle, + new LeafPosition(this, 0), true)); setFinished(true); return returnList; diff --git a/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java index e21d2e5b8..0f72ef124 100644 --- a/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java @@ -25,7 +25,6 @@ import org.apache.fop.fo.properties.CommonHyphenation; import org.apache.fop.hyphenation.Hyphenation; import org.apache.fop.hyphenation.Hyphenator; import org.apache.fop.area.LineArea; -import org.apache.fop.area.Resolvable; import java.util.ListIterator; import java.util.Iterator; @@ -46,31 +45,11 @@ import org.apache.fop.traits.MinOptMax; * creates a line area to contain the inline areas added by the * child layout managers. */ -public class LineLayoutManager extends InlineStackingLayoutManager { +public class LineLayoutManager extends InlineStackingLayoutManager + implements BlockLevelLayoutManager { + private Block fobj; - /** - * Create a new Line Layout Manager. - * This is used by the block layout manager to create - * line managers for handling inline areas flowing into line areas. - * - * @param lh the default line height - * @param l the default lead, from top to baseline - * @param f the default follow, from baseline to bottom - */ - public LineLayoutManager(Block node, int lh, int l, int f, int ms) { - super(node); - fobj = node; - // the child FObj are owned by the parent BlockLM - // this LM has all its childLMs preloaded - fobjIter = null; - lineHeight = lh; - lead = l; - follow = f; - middleShift = ms; - initialize(); // Normally done when started by parent! - } - /** * @see org.apache.fop.layoutmgr.AbstractLayoutManager#initProperties() */ @@ -96,36 +75,51 @@ public class LineLayoutManager extends InlineStackingLayoutManager { private static class LineBreakPosition extends LeafPosition { // int iPos; int iParIndex; // index of the Paragraph this Position refers to + int availableShrink; + int availableStretch; + int difference; double dAdjust; // Percentage to adjust (stretch or shrink) double ipdAdjust; // Percentage to adjust (stretch or shrink) int startIndent; int lineHeight; + int lineWidth; int baseline; + int topShift; + int bottomShift; LineBreakPosition(LayoutManager lm, int index, int iBreakIndex, - double ipdA, double adjust, int ind, int lh, int bl) { + int shrink, int stretch, int diff, + double ipdA, double adjust, int ind, + int lh, int lw, int bl, int ts, int bs) { super(lm, iBreakIndex); - // iPos = iBreakIndex; + availableShrink = shrink; + availableStretch = stretch; + difference = diff; iParIndex = index; ipdAdjust = ipdA; dAdjust = adjust; startIndent = ind; lineHeight = lh; + lineWidth = lw; baseline = bl; + topShift = ts; + bottomShift = bs; } } /** Break positions returned by inline content. */ - private List vecInlineBreaks = new ArrayList(); + private List vecInlineBreaks = new java.util.ArrayList(); private BreakPoss prevBP = null; // Last confirmed break position private int bTextAlignment = EN_JUSTIFY; private int bTextAlignmentLast; private int effectiveAlignment; private Length textIndent; + private int iIndents = 0; private CommonHyphenation hyphProps; + //private LayoutProps layoutProps; private int lineHeight; private int lead; @@ -133,8 +127,8 @@ public class LineLayoutManager extends InlineStackingLayoutManager { // offset of the middle baseline with respect to the main baseline private int middleShift; - private ArrayList knuthParagraphs = null; - private ArrayList breakpoints = null; + private List knuthParagraphs = null; + private List breakpoints = null; private int iReturnedLBP = 0; private int iStartElement = 0; private int iEndElement = 0; @@ -143,6 +137,10 @@ public class LineLayoutManager extends InlineStackingLayoutManager { // penalty value for flagged penalties private int flaggedPenalty = 50; + private LineLayoutPossibilities lineLayouts; + private List lineLayoutsList; + private int iLineWidth = 0; + // this constant is used to create elements when text-align is center: // every TextLM descendant of LineLM must use the same value, // otherwise the line breaking algorithm does not find the right @@ -164,20 +162,37 @@ public class LineLayoutManager extends InlineStackingLayoutManager { } // this class represents a paragraph - public class Paragraph extends ArrayList { - // number of KnuthElements added by the LineLayoutManager - public int ignoreAtStart = 0; - public int ignoreAtEnd = 0; - // minimum space at the end of the last line (in millipoints) - public int lineFillerWidth; - - public void startParagraph(int lineWidth) { + private class Paragraph extends KnuthSequence { + // space at the end of the last line (in millipoints) + private MinOptMax lineFiller; + private int textAlignment; + private int textAlignmentLast; + private int textIndent; + private int lineWidth; + // the LM which created the paragraph + private LineLayoutManager layoutManager; + + public Paragraph(LineLayoutManager llm, int alignment, int alignmentLast, + int indent) { + super(); + layoutManager = llm; + textAlignment = alignment; + textAlignmentLast = alignmentLast; + textIndent = indent; + } + + public void startParagraph(int lw) { + lineWidth = lw; + startSequence(); + } + + public void startSequence() { // set the minimum amount of empty space at the end of the // last line if (bTextAlignment == EN_CENTER) { - lineFillerWidth = 0; + lineFiller = new MinOptMax(0); } else { - lineFillerWidth = (int)(lineWidth / 12); + lineFiller = new MinOptMax(0, (int)(lineWidth / 12), lineWidth); } // add auxiliary elements at the beginning of the paragraph @@ -190,17 +205,24 @@ public class LineLayoutManager extends InlineStackingLayoutManager { // add the element representing text indentation // at the beginning of the first paragraph if (knuthParagraphs.size() == 0 - && textIndent.getValue() != 0) { - this.add(new KnuthBox(textIndent.getValue(), 0, 0, 0, + && fobj.getTextIndent().getValue() != 0) { + this.add(new KnuthInlineBox(fobj.getTextIndent().getValue(), 0, 0, 0, null, false)); ignoreAtStart ++; } } public void endParagraph() { + KnuthSequence finishedPar = this.endSequence(); + if (finishedPar != null) { + knuthParagraphs.add(finishedPar); + } + } + + public KnuthSequence endSequence() { // remove glue and penalty item at the end of the paragraph while (this.size() > ignoreAtStart - && !((KnuthElement) this.get(this.size() - 1)).isBox()) { + && !((KnuthElement)this.get(this.size() - 1)).isBox()) { this.remove(this.size() - 1); } if (this.size() > ignoreAtStart) { @@ -215,10 +237,11 @@ public class LineLayoutManager extends InlineStackingLayoutManager { // add the elements representing the space // at the end of the last line // and the forced break - this.add(new KnuthPenalty(0, KnuthElement.INFINITE, + this.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, null, false)); - this.add(new KnuthGlue(lineFillerWidth, 10000000, 0, - null, false)); + this.add(new KnuthGlue(lineFiller.opt, + lineFiller.max - lineFiller.opt, + lineFiller.opt - lineFiller.min, null, false)); this.add(new KnuthPenalty(0, -KnuthElement.INFINITE, false, null, false)); ignoreAtEnd = 3; @@ -228,27 +251,237 @@ public class LineLayoutManager extends InlineStackingLayoutManager { false, null, false)); ignoreAtEnd = 1; } - knuthParagraphs.add(this); + return this; + } else { + this.clear(); + return null; } } - public KnuthElement getLast() { - int idx = size(); - if (idx == 0) { - return null; + } + + private class LineBreakingAlgorithm extends BreakingAlgorithm { + private LineLayoutManager thisLLM; + private int pageAlignment; + private int activePossibility; + private int addedPositions; + private int textIndent; + private int fillerMinWidth; + private int lineHeight; + private int lead; + private int follow; + private int middleshift; + private int maxDiff; + private static final double MAX_DEMERITS = 10e6; + + public LineBreakingAlgorithm (int pageAlign, + int textAlign, int textAlignLast, + int indent, int fillerWidth, + int lh, int ld, int fl, int ms, boolean first, + LineLayoutManager llm) { + super(textAlign, textAlignLast, first); + pageAlignment = pageAlign; + textIndent = indent; + fillerMinWidth = fillerWidth; + lineHeight = lh; + lead = ld; + follow = fl; + middleshift = ms; + thisLLM = llm; + activePossibility = -1; + maxDiff = fobj.getWidows() >= fobj.getOrphans() + ? fobj.getWidows() + : fobj.getOrphans(); + } + + public void updateData1(int lineCount, double demerits) { + lineLayouts.addPossibility(lineCount, demerits); + log.trace("Layout possibility in " + lineCount + " lines; break at position:"); + } + + public void updateData2(KnuthNode bestActiveNode, + KnuthSequence par, + int total) { + // compute indent and adjustment ratio, according to + // the value of text-align and text-align-last + int indent = 0; + int difference = (bestActiveNode.line < total) ? bestActiveNode.difference : bestActiveNode.difference + fillerMinWidth; + int textAlign = (bestActiveNode.line < total) ? alignment : alignmentLast; + indent += (textAlign == Constants.EN_CENTER) ? + difference / 2 : + (textAlign == Constants.EN_END) ? difference : 0; + indent += (bestActiveNode.line == 1 && bFirst) ? + textIndent : 0; + double ratio = (textAlign == Constants.EN_JUSTIFY + || bestActiveNode.adjustRatio < 0) ? bestActiveNode.adjustRatio : 0; + + // add nodes at the beginning of the list, as they are found + // backwards, from the last one to the first one + + // the first time this method is called, initialize activePossibility + if (activePossibility == -1) { + activePossibility = 0; + addedPositions = 0; + } + + if (addedPositions == lineLayouts.getLineCount(activePossibility)) { + activePossibility ++; + addedPositions = 0; + //System.out.println(" "); + } + + //System.out.println("LLM> (" + (lineLayouts.getLineNumber(activePossibility) - addedPositions) + ") difference = " + difference + " ratio = " + ratio); + lineLayouts.addBreakPosition(makeLineBreakPosition(par, + (bestActiveNode.line > 1 ? bestActiveNode.previous.position + 1: 0), + bestActiveNode.position, + bestActiveNode.availableShrink - (addedPositions > 0 ? 0 : ((Paragraph)par).lineFiller.opt - ((Paragraph)par).lineFiller.min), bestActiveNode.availableStretch, difference, ratio, indent), + activePossibility); + addedPositions ++; + } + + /* reset activePossibility, as if breakpoints have not yet been computed + */ + public void resetAlgorithm() { + activePossibility = -1; + } + + private LineBreakPosition makeLineBreakPosition(KnuthSequence par, + int firstElementIndex, + int lastElementIndex, + int availableShrink, int availableStretch, int difference, + double ratio, + int indent) { + // line height calculation + + int halfLeading = (lineHeight - lead - follow) / 2; + // height before the main baseline + int lineLead = lead; + // maximum size of top and bottom alignment + int maxtb = follow; + // max size of middle alignment before and after the middle baseline + int middlefollow = maxtb; + + // if line-stacking-strategy is "font-height", the line height + // is not affected by its content + if (fobj.getLineStackingStrategy() != EN_FONT_HEIGHT) { + ListIterator inlineIterator + = par.listIterator(firstElementIndex); + for (int j = firstElementIndex; + j <= lastElementIndex; + j++) { + KnuthElement element = (KnuthElement) inlineIterator.next(); + if (element.isBox()) { + if (((KnuthInlineBox) element).getLead() > lineLead) { + lineLead = ((KnuthInlineBox) element).getLead(); + } + if (((KnuthInlineBox) element).getTotal() > maxtb) { + maxtb = ((KnuthInlineBox) element).getTotal(); + } + if (((KnuthInlineBox) element).getMiddle() > lineLead + middleShift) { + lineLead += ((KnuthInlineBox) element).getMiddle() + - lineLead - middleShift; + } + if (((KnuthInlineBox) element).getMiddle() > middlefollow - middleShift) { + middlefollow += ((KnuthInlineBox) element).getMiddle() + - middlefollow + middleShift; + } + } + } + + if (maxtb - lineLead > middlefollow) { + middlefollow = maxtb - lineLead; + } } - return (KnuthElement) get(idx - 1); + + //lineLead += halfLeading; + //middlefollow += lineHeight - lead - follow - halfLeading; + + constantLineHeight = lineLead + middlefollow + (lineHeight - lead - follow); + //System.out.println("desired height: " + lineHeight + " actual height: " + (lineLead + middlefollow + (lineHeight - lead - follow)) + " halfleading = " + halfLeading + " and " + (lineHeight - lead - follow - halfLeading)); + + return new LineBreakPosition(thisLLM, + knuthParagraphs.indexOf(par), + lastElementIndex, + availableShrink, availableStretch, difference, ratio, 0, indent, + lineLead + middlefollow + (lineHeight - lead - follow), iLineWidth, + lineLead + halfLeading, + - lineLead, middlefollow); + } + + public int findBreakingPoints(Paragraph par, int lineWidth, + double threshold, boolean force, + boolean hyphenationAllowed) { + return super.findBreakingPoints(par, lineWidth, threshold, force, hyphenationAllowed); } - public KnuthElement removeLast() { - int idx = size(); - if (idx == 0) { - return null; + protected int filterActiveNodes() { + KnuthNode bestActiveNode = null; + + if (pageAlignment == EN_JUSTIFY) { + // leave all active nodes and find the optimum line number + //System.out.println("LBA.filterActiveNodes> " + activeNodeCount + " layouts"); + for (int i = startLine; i < endLine; i++) { + for (KnuthNode node = getNode(i); node != null; node = node.next) { + //System.out.println(" + lines = " + node.line + " demerits = " + node.totalDemerits); + bestActiveNode = compareNodes(bestActiveNode, node); + } + } + + // scan the node set once again and remove some nodes + //System.out.println("LBA.filterActiveList> layout selection"); + for (int i = startLine; i < endLine; i++) { + for (KnuthNode node = getNode(i); node != null; node = node.next) { + //if (Math.abs(node.line - bestActiveNode.line) > maxDiff) { + //if (false) { + if (node.line != bestActiveNode.line + && node.totalDemerits > MAX_DEMERITS) { + //System.out.println(" XXX lines = " + node.line + " demerits = " + node.totalDemerits); + removeNode(i, node); + } else { + //System.out.println(" ok lines = " + node.line + " demerits = " + node.totalDemerits); + } + } + } + } else { + // leave only the active node with fewest total demerits + for (int i = startLine; i < endLine; i++) { + for (KnuthNode node = getNode(i); node != null; node = node.next) { + bestActiveNode = compareNodes(bestActiveNode, node); + if (node != bestActiveNode) { + removeNode(i, node); + } + } + } } - return (KnuthElement) remove(idx - 1); + return bestActiveNode.line; } } + + private int constantLineHeight = 12000; + + + /** + * Create a new Line Layout Manager. + * This is used by the block layout manager to create + * line managers for handling inline areas flowing into line areas. + * + * @param lh the default line height + * @param l the default lead, from top to baseline + * @param f the default follow, from baseline to bottom + */ + public LineLayoutManager(Block block, int lh, int l, int f, int ms) { + super(block); + fobj = block; + // the child FObj are owned by the parent BlockLM + // this LM has all its childLMs preloaded + fobjIter = null; + lineHeight = lh; + lead = l; + follow = f; + middleShift = ms; + initialize(); // Normally done when started by parent! + } /** * Call child layout managers to generate content. @@ -257,7 +490,13 @@ public class LineLayoutManager extends InlineStackingLayoutManager { * @param context the layout context for finding breaks * @return the next break position */ + // this method is no longer used public BreakPoss getNextBreakPoss(LayoutContext context) { + setFinished(true); + return null; + } + + public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { // Get a break from currently active child LM // Set up constraints for inline level managers InlineLevelLayoutManager curLM ; // currently active LM @@ -265,8 +504,6 @@ public class LineLayoutManager extends InlineStackingLayoutManager { // IPD remaining in line MinOptMax availIPD = context.getStackLimit(); - LayoutContext inlineLC = new LayoutContext(context); - clearPrevIPD(); int iPrevLineEnd = vecInlineBreaks.size(); @@ -276,101 +513,14 @@ public class LineLayoutManager extends InlineStackingLayoutManager { prevBP = null; //PHASE 1: Create Knuth elements - if (knuthParagraphs == null) { // it's the first time this method is called knuthParagraphs = new ArrayList(); // here starts Knuth's algorithm - KnuthElement thisElement = null; - LinkedList returnedList = null; - - // convert all the text in a sequence of paragraphs made - // of KnuthBox, KnuthGlue and KnuthPenalty objects - boolean bPrevWasKnuthBox = false; - KnuthBox prevBox = null; - - Paragraph knuthPar = new Paragraph(); - knuthPar.startParagraph(availIPD.opt); - while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) { - if ((returnedList - = curLM.getNextKnuthElements(inlineLC, - effectiveAlignment)) - != null) { - // look at the first element - thisElement = (KnuthElement) returnedList.getFirst(); - if (thisElement.isBox() && !thisElement.isAuxiliary() - && bPrevWasKnuthBox) { - prevBox = (KnuthBox) knuthPar.removeLast(); - // if there are two consecutive KnuthBoxes the - // first one does not represent a whole word, - // so it must be given one more letter space - if (!prevBox.isAuxiliary()) { - // if letter spacing is constant, - // only prevBox needs to be replaced; - knuthPar.add(((InlineLevelLayoutManager) - prevBox.getLayoutManager()) - .addALetterSpaceTo(prevBox)); - } else { - // prevBox is the last element - // in the sub-sequence - // - // the letter space is added to , - // while the other elements are not changed - KnuthBox auxBox = prevBox; - KnuthGlue auxGlue - = (KnuthGlue) knuthPar.removeLast(); - KnuthPenalty auxPenalty - = (KnuthPenalty) knuthPar.removeLast(); - prevBox = (KnuthBox) knuthPar.getLast(); - knuthPar.add(auxPenalty); - knuthPar.add(((InlineLevelLayoutManager) - prevBox.getLayoutManager()) - .addALetterSpaceTo(prevBox)); - knuthPar.add(auxBox); - } - } - - // look at the last element - KnuthElement lastElement = (KnuthElement) returnedList.getLast(); - boolean bForceLinefeed = false; - if (lastElement.isBox()) { - bPrevWasKnuthBox = true; - } else { - bPrevWasKnuthBox = false; - if (lastElement.isPenalty() - && ((KnuthPenalty) lastElement).getP() - == -KnuthPenalty.INFINITE) { - // a penalty item whose value is -inf - // represents a preserved linefeed, - // wich forces a line break - bForceLinefeed = true; - returnedList.removeLast(); - } - } - - // add the new elements to the paragraph - knuthPar.addAll(returnedList); - if (bForceLinefeed) { - if (knuthPar.size() == 0) { - //only a forced linefeed on this line - //-> compensate with a zero width box - knuthPar.add(new KnuthBox(0, 0, 0, 0, - null, false)); - } - knuthPar.endParagraph(); - knuthPar = new Paragraph(); - knuthPar.startParagraph(availIPD.opt); - bPrevWasKnuthBox = false; - } - } else { - // curLM returned null; this can happen - // if it has nothing more to layout, - // so just iterate once more to see - // if there are other children - } - } - knuthPar.endParagraph(); + //TODO availIPD should not really be used here, so we can later support custom line + //widths for for each line (side-floats, differing available IPD after page break) + collectInlineKnuthElements(context, availIPD); } else { // this method has been called before // all line breaks are already calculated @@ -383,7 +533,8 @@ public class LineLayoutManager extends InlineStackingLayoutManager { } //PHASE 2: Create line breaks - + return findOptimalLineBreakingPoints(alignment); + /* LineBreakPosition lbp = null; if (breakpoints == null) { // find the optimal line breaking points for each paragraph @@ -395,10 +546,11 @@ public class LineLayoutManager extends InlineStackingLayoutManager { currPar = (Paragraph) paragraphsIterator.previous(); findBreakingPoints(currPar, context.getStackLimit().opt); } - } + }*/ //PHASE 3: Return lines + /* // get a break point from the list lbp = (LineBreakPosition) breakpoints.get(iReturnedLBP ++); if (iReturnedLBP == breakpoints.size()) { @@ -409,6 +561,109 @@ public class LineLayoutManager extends InlineStackingLayoutManager { curLineBP.setFlag(BreakPoss.ISLAST, isFinished()); curLineBP.setStackingSize(new MinOptMax(lbp.lineHeight)); return curLineBP; + */ + } + + /** + * Phase 1 of Knuth algorithm: Collect all inline Knuth elements before determining line breaks. + * @param context the LayoutContext + * @param availIPD available IPD for line (should be removed!) + */ + private void collectInlineKnuthElements(LayoutContext context, MinOptMax availIPD) { + LayoutContext inlineLC = new LayoutContext(context); + + InlineLevelLayoutManager curLM; + KnuthElement thisElement = null; + LinkedList returnedList = null; + iLineWidth = context.getStackLimit().opt; + + // convert all the text in a sequence of paragraphs made + // of KnuthBox, KnuthGlue and KnuthPenalty objects + boolean bPrevWasKnuthBox = false; + KnuthBox prevBox = null; + + Paragraph knuthPar = new Paragraph(this, + bTextAlignment, bTextAlignmentLast, + textIndent.getValue()); + knuthPar.startParagraph(availIPD.opt); + while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) { + if ((returnedList + = curLM.getNextKnuthElements(inlineLC, + effectiveAlignment)) + != null) { + // look at the first element + thisElement = (KnuthElement) returnedList.getFirst(); + if (thisElement.isBox() && !thisElement.isAuxiliary() + && bPrevWasKnuthBox) { + prevBox = (KnuthBox) knuthPar.removeLast(); + LinkedList oldList = new LinkedList(); + // if there are two consecutive KnuthBoxes the + // first one does not represent a whole word, + // so it must be given one more letter space + if (!prevBox.isAuxiliary()) { + // if letter spacing is constant, + // only prevBox needs to be replaced; + oldList.add(prevBox); + } else { + // prevBox is the last element + // in the sub-sequence + // + // the letter space is added to , + // while the other elements are not changed + oldList.add(prevBox); + oldList.addFirst((KnuthGlue) knuthPar.removeLast()); + oldList.addFirst((KnuthPenalty) knuthPar.removeLast()); + } + // adding a letter space could involve, according to the text + // represented by oldList, replacing a glue element or adding + // new elements + knuthPar.addAll(((InlineLevelLayoutManager) + prevBox.getLayoutManager()) + .addALetterSpaceTo(oldList)); + } + + // look at the last element + KnuthElement lastElement = (KnuthElement) returnedList.getLast(); + boolean bForceLinefeed = false; + if (lastElement.isBox()) { + bPrevWasKnuthBox = true; + } else { + bPrevWasKnuthBox = false; + if (lastElement.isPenalty() + && ((KnuthPenalty) lastElement).getP() + == -KnuthPenalty.INFINITE) { + // a penalty item whose value is -inf + // represents a preserved linefeed, + // wich forces a line break + bForceLinefeed = true; + returnedList.removeLast(); + } + } + + // add the new elements to the paragraph + knuthPar.addAll(returnedList); + if (bForceLinefeed) { + if (knuthPar.size() == 0) { + //only a forced linefeed on this line + //-> compensate with a zero width box + knuthPar.add(new KnuthInlineBox(0, 0, 0, 0, + null, false)); + } + knuthPar.endParagraph(); + knuthPar = new Paragraph(this, + bTextAlignment, bTextAlignmentLast, + textIndent.getValue()); + knuthPar.startParagraph(availIPD.opt); + bPrevWasKnuthBox = false; + } + } else { + // curLM returned null; this can happen + // if it has nothing more to layout, + // so just iterate once more to see + // if there are other children + } + } + knuthPar.endParagraph(); } /** @@ -421,6 +676,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager { * into lines * @param lineWidth the desired length ot the lines */ + /* private void findBreakingPoints(Paragraph par, int lineWidth) { // maximum adjustment ratio permitted float maxAdjustment = 1; @@ -508,18 +764,19 @@ public class LineLayoutManager extends InlineStackingLayoutManager { j++) { KnuthElement element = (KnuthElement) inlineIterator.next(); if (element.isBox()) { - if (((KnuthBox) element).getLead() > lineLead) { - lineLead = ((KnuthBox) element).getLead(); + KnuthInlineBox box = (KnuthInlineBox)element; + if (box.getLead() > lineLead) { + lineLead = box.getLead(); } - if (((KnuthBox) element).getTotal() > maxtb) { - maxtb = ((KnuthBox) element).getTotal(); + if (box.getTotal() > maxtb) { + maxtb = box.getTotal(); } - if (((KnuthBox) element).getMiddle() > lineLead + middleShift) { - lineLead += ((KnuthBox) element).getMiddle() + if (box.getMiddle() > lineLead + middleShift) { + lineLead += box.getMiddle() - lineLead - middleShift; } - if (((KnuthBox) element).getMiddle() > middlefollow - middleShift) { - middlefollow += ((KnuthBox) element).getMiddle() + if (box.getMiddle() > middlefollow - middleShift) { + middlefollow += box.getMiddle() - middlefollow + middleShift; } } @@ -536,9 +793,368 @@ public class LineLayoutManager extends InlineStackingLayoutManager { ratio, 0, indent, lineLead + middlefollow, lineLead)); + }*/ + + + /** + * Phase 2 of Knuth algorithm: find optimal break points. + * @param alignment alignmenr of the paragraph + * @return a list of Knuth elements representing broken lines + */ + private LinkedList findOptimalLineBreakingPoints(int alignment) { + + // find the optimal line breaking points for each paragraph + ListIterator paragraphsIterator + = knuthParagraphs.listIterator(knuthParagraphs.size()); + Paragraph currPar = null; + LineBreakingAlgorithm alg; + lineLayoutsList = new ArrayList(knuthParagraphs.size()); + while (paragraphsIterator.hasPrevious()) { + lineLayouts = new LineLayoutPossibilities(); + currPar = (Paragraph) paragraphsIterator.previous(); + double maxAdjustment = 1; + int iBPcount = 0; + alg = new LineBreakingAlgorithm(alignment, + bTextAlignment, bTextAlignmentLast, + textIndent.getValue(), currPar.lineFiller.opt, + lineHeight, lead, follow, middleShift, + (knuthParagraphs.indexOf(currPar) == 0), + this); + + if (hyphProps.hyphenate == EN_TRUE) { + findHyphenationPoints(currPar); + } + + // first try + boolean bHyphenationAllowed = false; + iBPcount = alg.findBreakingPoints(currPar, + iLineWidth, + maxAdjustment, false, bHyphenationAllowed); + if (iBPcount == 0 || alignment == EN_JUSTIFY) { + // if the first try found a set of breaking points, save them + if (iBPcount > 0) { + alg.resetAlgorithm(); + lineLayouts.savePossibilities(false); + } else { + // the first try failed + log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment); + } + + // now try something different + log.debug("Hyphenation possible? " + (hyphProps.hyphenate == EN_TRUE)); + if (hyphProps.hyphenate == EN_TRUE) { + // consider every hyphenation point as a legal break + bHyphenationAllowed = true; + } else { + // try with a higher threshold + maxAdjustment = 5; + } + + if ((iBPcount + = alg.findBreakingPoints(currPar, + iLineWidth, + maxAdjustment, false, bHyphenationAllowed)) == 0) { + // the second try failed too, try with a huge threshold + // and force the algorithm to find + // a set of breaking points + log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment + + (hyphProps.hyphenate == EN_TRUE ? " and hyphenation" : "")); + maxAdjustment = 20; + iBPcount + = alg.findBreakingPoints(currPar, + iLineWidth, + maxAdjustment, true, bHyphenationAllowed); + } + + // use non-hyphenated breaks, when possible + lineLayouts.restorePossibilities(); + + /* extension (not in the XSL FO recommendation): if vertical alignment + is justify and the paragraph has only one layout, try using + shorter or longer lines */ + //TODO This code snippet is disabled. Reenable? + if (false && alignment == EN_JUSTIFY && bTextAlignment == EN_JUSTIFY) { + //System.out.println("LLM.getNextKnuthElements> layouts with more lines? " + lineLayouts.canUseMoreLines()); + //System.out.println(" layouts with fewer lines? " + lineLayouts.canUseLessLines()); + if (!lineLayouts.canUseMoreLines()) { + alg.resetAlgorithm(); + lineLayouts.savePossibilities(true); + // try with shorter lines + int savedLineWidth = iLineWidth; + iLineWidth = (int) (iLineWidth * 0.95); + iBPcount + = alg.findBreakingPoints(currPar, + iLineWidth, + maxAdjustment, true, bHyphenationAllowed); + // use normal lines, when possible + lineLayouts.restorePossibilities(); + iLineWidth = savedLineWidth; + } + if (!lineLayouts.canUseLessLines()) { + alg.resetAlgorithm(); + lineLayouts.savePossibilities(true); + // try with longer lines + int savedLineWidth = iLineWidth; + iLineWidth = (int) (iLineWidth * 1.05); + iBPcount + = alg.findBreakingPoints(currPar, + iLineWidth, + maxAdjustment, true, bHyphenationAllowed); + // use normal lines, when possible + lineLayouts.restorePossibilities(); + iLineWidth = savedLineWidth; + } + //System.out.println("LLM.getNextKnuthElements> now, layouts with more lines? " + lineLayouts.canUseMoreLines()); + //System.out.println(" now, layouts with fewer lines? " + lineLayouts.canUseLessLines()); + } + } + lineLayoutsList.add(0, lineLayouts); + } + + + setFinished(true); + + //Post-process the line breaks found + return postProcessLineBreaks(alignment); + } + + private LinkedList postProcessLineBreaks(int alignment) { + + LinkedList returnList = new LinkedList(); + + for (int p = 0; p < knuthParagraphs.size(); p ++) { + // null penalty between paragraphs + if (p > 0 + && !((BlockLevelLayoutManager) parentLM).mustKeepTogether()) { + returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false)); + } + + lineLayouts = (LineLayoutPossibilities)lineLayoutsList.get(p); + + if (alignment == EN_JUSTIFY) { + /* justified vertical alignment (not in the XSL FO recommendation): + create a multi-layout sequence whose elements will contain + a conventional Position */ + Position returnPosition = new LeafPosition(this, p); + createElements(returnList, lineLayouts, returnPosition); + } else { + /* "normal" vertical alignment: create a sequence whose boxes + represent effective lines, and contain LineBreakPositions */ + Position returnPosition = new LeafPosition(this, p); + for (int i = 0; + i < lineLayouts.getChosenLineCount(); + i++) { + if (!((BlockLevelLayoutManager) parentLM).mustKeepTogether() + && i >= fobj.getOrphans() + && i <= lineLayouts.getChosenLineCount() - fobj.getWidows() + && returnList.size() > 0) { + // null penalty allowing a page break between lines + returnList.add(new KnuthPenalty(0, 0, false, returnPosition, false)); + } + returnList.add(new KnuthBox(((LineBreakPosition) lineLayouts.getChosenPosition(i)).lineHeight, + lineLayouts.getChosenPosition(i), false)); + } + } + } + + return returnList; } + private void createElements(List list, LineLayoutPossibilities lineLayouts, + Position elementPosition) { + /* number of normal, inner lines */ + int nInnerLines = 0; + /* number of lines that can be used in order to fill more space */ + int nOptionalLines = 0; + /* number of lines that can be used in order to fill more space + only if the paragraph is not parted */ + int nConditionalOptionalLines = 0; + /* number of lines that can be omitted in order to fill less space */ + int nEliminableLines = 0; + /* number of lines that can be omitted in order to fill less space + only if the paragraph is not parted */ + int nConditionalEliminableLines = 0; + /* number of the first unbreakable lines */ + int nFirstLines = fobj.getOrphans(); + /* number of the last unbreakable lines */ + int nLastLines = fobj.getWidows(); + /* sub-sequence used to separate the elements representing different lines */ + List breaker = new LinkedList(); + + /* comment out the next lines in order to test particular situations */ + if (fobj.getOrphans() + fobj.getWidows() <= lineLayouts.getMinLineCount()) { + nInnerLines = lineLayouts.getMinLineCount() - (fobj.getOrphans() + fobj.getWidows()); + nOptionalLines = lineLayouts.getMaxLineCount() - lineLayouts.getOptLineCount(); + nEliminableLines = lineLayouts.getOptLineCount() - lineLayouts.getMinLineCount(); + } else if (fobj.getOrphans() + fobj.getWidows() <= lineLayouts.getOptLineCount()) { + nOptionalLines = lineLayouts.getMaxLineCount() - lineLayouts.getOptLineCount(); + nEliminableLines = lineLayouts.getOptLineCount() - (fobj.getOrphans() + fobj.getWidows()); + nConditionalEliminableLines = (fobj.getOrphans() + fobj.getWidows()) - lineLayouts.getMinLineCount(); + } else if (fobj.getOrphans() + fobj.getWidows() <= lineLayouts.getMaxLineCount()) { + nOptionalLines = lineLayouts.getMaxLineCount() - (fobj.getOrphans() + fobj.getWidows()); + nConditionalOptionalLines = (fobj.getOrphans() + fobj.getWidows()) - lineLayouts.getOptLineCount(); + nConditionalEliminableLines = lineLayouts.getOptLineCount() - lineLayouts.getMinLineCount(); + nFirstLines -= nConditionalOptionalLines; + } else { + nConditionalOptionalLines = lineLayouts.getMaxLineCount() - lineLayouts.getOptLineCount(); + nConditionalEliminableLines = lineLayouts.getOptLineCount() - lineLayouts.getMinLineCount(); + nFirstLines = lineLayouts.getOptLineCount(); + nLastLines = 0; + } + /* comment out the previous lines in order to test particular situations */ + + /* use these lines to test particular situations + nInnerLines = 0; + nOptionalLines = 1; + nConditionalOptionalLines = 2; + nEliminableLines = 0; + nConditionalEliminableLines = 0; + nFirstLines = 1; + nLastLines = 3; + */ + + if (nLastLines != 0 + && (nConditionalOptionalLines > 0 || nConditionalEliminableLines > 0)) { + breaker.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false)); + breaker.add(new KnuthGlue(0, -nConditionalOptionalLines * constantLineHeight, + -nConditionalEliminableLines * constantLineHeight, + LINE_NUMBER_ADJUSTMENT, elementPosition, false)); + breaker.add(new KnuthPenalty(nConditionalOptionalLines * constantLineHeight, + 0, false, elementPosition, false)); + breaker.add(new KnuthGlue(0, nConditionalOptionalLines * constantLineHeight, + nConditionalEliminableLines * constantLineHeight, + LINE_NUMBER_ADJUSTMENT, elementPosition, false)); + } else if (nLastLines != 0) { + breaker.add(new KnuthPenalty(0, 0, false, elementPosition, false)); + } + + //System.out.println("first=" + nFirstLines + " inner=" + nInnerLines + // + " optional=" + nOptionalLines + " eliminable=" + nEliminableLines + // + " last=" + nLastLines + // + " (condOpt=" + nConditionalOptionalLines + " condEl=" + nConditionalEliminableLines + ")"); + + // creation of the elements: + // first group of lines + list.add(new KnuthBox(nFirstLines * constantLineHeight, elementPosition, + (nLastLines == 0 + && nConditionalOptionalLines == 0 + && nConditionalEliminableLines == 0 ? true : false))); + if (nConditionalOptionalLines > 0 + || nConditionalEliminableLines > 0) { + list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false)); + list.add(new KnuthGlue(0, nConditionalOptionalLines * constantLineHeight, + nConditionalEliminableLines * constantLineHeight, + LINE_NUMBER_ADJUSTMENT, elementPosition, false)); + list.add(new KnuthBox(0, elementPosition, + (nLastLines == 0 ? true : false))); + } + + // optional lines + for (int i = 0; i < nOptionalLines; i++) { + list.addAll(breaker); + list.add(new KnuthBox(0, elementPosition, false)); + list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false)); + list.add(new KnuthGlue(0, 1 * constantLineHeight, 0, + LINE_NUMBER_ADJUSTMENT, elementPosition, false)); + list.add(new KnuthBox(0, elementPosition, false)); + } + + // eliminable lines + for (int i = 0; i < nEliminableLines; i++) { + list.addAll(breaker); + list.add(new KnuthBox(1 * constantLineHeight, elementPosition, false)); + list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false)); + list.add(new KnuthGlue(0, 0, 1 * constantLineHeight, + LINE_NUMBER_ADJUSTMENT, elementPosition, false)); + list.add(new KnuthBox(0, elementPosition, false)); + } + + // inner lines + for (int i = 0; i < nInnerLines; i++) { + list.addAll(breaker); + list.add(new KnuthBox(1 * constantLineHeight, elementPosition, false)); + } + + // last group of lines + if (nLastLines > 0) { + list.addAll(breaker); + list.add(new KnuthBox(nLastLines * constantLineHeight, + elementPosition, true)); + } + } + + public boolean mustKeepTogether() { + return false; + } + + public boolean mustKeepWithPrevious() { + return false; + } + + public boolean mustKeepWithNext() { + return false; + } + + public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) { + LeafPosition pos = (LeafPosition)lastElement.getPosition(); + int totalAdj = adj; + //if (lastElement.isPenalty()) { + // totalAdj += lastElement.getW(); + //} + //int lineNumberDifference = (int)((double) totalAdj / constantLineHeight); + int lineNumberDifference = (int) Math.round((double) totalAdj / constantLineHeight + (adj > 0 ? - 0.4 : 0.4)); + //System.out.println(" LLM> variazione calcolata = " + ((double) totalAdj / constantLineHeight) + " variazione applicata = " + lineNumberDifference); + lineLayouts = (LineLayoutPossibilities)lineLayoutsList.get(pos.getLeafPos()); + lineNumberDifference = lineLayouts.applyLineCountAdjustment(lineNumberDifference); + return lineNumberDifference * constantLineHeight; + } + + public void discardSpace(KnuthGlue spaceGlue) { + } + + public LinkedList getChangedKnuthElements(List oldList, int alignment) { + LinkedList returnList = new LinkedList(); + for (int p = 0; + p < knuthParagraphs.size(); + p ++) { + lineLayouts = (LineLayoutPossibilities)lineLayoutsList.get(p); + //System.out.println("demerits of the chosen layout: " + lineLayouts.getChosenDemerits()); + for (int i = 0; + i < lineLayouts.getChosenLineCount(); + i ++) { + if (!((BlockLevelLayoutManager) parentLM).mustKeepTogether() + && i >= fobj.getOrphans() + && i <= lineLayouts.getChosenLineCount() - fobj.getWidows()) { + // null penalty allowing a page break between lines + returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false)); + } + LineBreakPosition lbp = (LineBreakPosition) lineLayouts.getChosenPosition(i); + //System.out.println("LLM.getChangedKnuthElements> lineWidth= " + lbp.lineWidth + " difference= " + lbp.difference); + //System.out.println(" shrink= " + lbp.availableShrink + " stretch= " + lbp.availableStretch); + + //System.out.println("linewidth= " + lbp.lineWidth + " difference= " + lbp.difference + " indent= " + lbp.startIndent); + MinOptMax contentIPD; + if (alignment == EN_JUSTIFY) { + contentIPD = new MinOptMax( + lbp.lineWidth - lbp.difference - lbp.availableShrink, + lbp.lineWidth - lbp.difference, + lbp.lineWidth - lbp.difference + lbp.availableStretch); + } else if (alignment == EN_CENTER) { + contentIPD = new MinOptMax(lbp.lineWidth - 2 * lbp.startIndent); + } else if (alignment == EN_END) { + contentIPD = new MinOptMax(lbp.lineWidth - lbp.startIndent); + } else { + contentIPD = new MinOptMax(lbp.lineWidth - lbp.difference + lbp.startIndent); + } + returnList.add(new KnuthBlockBox(lbp.lineHeight, + contentIPD, + (lbp.ipdAdjust != 0 ? lbp.lineWidth - lbp.difference : 0), + lbp, false)); + } + } + return returnList; + } /** * find hyphenation points for every word int the current paragraph @@ -658,7 +1274,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager { = currUpdate.inlineLM.getChangedKnuthElements (currPar.subList(fromIndex + iAddedElements, toIndex + iAddedElements), - flaggedPenalty, effectiveAlignment); + /*flaggedPenalty,*/ effectiveAlignment); // remove the old elements currPar.subList(fromIndex + iAddedElements, toIndex + iAddedElements).clear(); @@ -823,7 +1439,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager { setFinished(false); iReturnedLBP--; } - while ((LineBreakPosition) breakpoints.get(iReturnedLBP) + while ((LineBreakPosition) lineLayouts.getChosenPosition(iReturnedLBP) != (LineBreakPosition) resetPos) { iReturnedLBP--; } @@ -839,123 +1455,121 @@ public class LineLayoutManager extends InlineStackingLayoutManager { */ public void addAreas(PositionIterator parentIter, LayoutContext context) { - addAreas(parentIter, 0.0); - - //vecInlineBreaks.clear(); - prevBP = null; - } - - // Generate and add areas to parent area - // Set size etc - // dSpaceAdjust should reference extra space in the BPD - /** - * Add the areas with the associated space adjustment. - * - * @param parentIter the iterator of breaks positions - * @param dSpaceAdjust the space adjustment - */ - public void addAreas(PositionIterator parentIter, double dSpaceAdjust) { LayoutManager childLM; LayoutContext lc = new LayoutContext(0); int iCurrParIndex; while (parentIter.hasNext()) { - ListIterator paragraphIterator = null; - KnuthElement tempElement = null; - // the TLM which created the last KnuthElement in this line - LayoutManager lastLM = null; - - LineBreakPosition lbp = (LineBreakPosition) parentIter.next(); - LineArea lineArea = new LineArea(); - lineArea.setStartIndent(lbp.startIndent); - lineArea.setBPD(lbp.lineHeight); - lc.setBaseline(lbp.baseline); - lc.setLineHeight(lbp.lineHeight); - lc.setMiddleShift(middleShift); - setCurrentArea(lineArea); - - iCurrParIndex = lbp.iParIndex; - Paragraph currPar = (Paragraph) knuthParagraphs.get(iCurrParIndex); - iEndElement = lbp.getLeafPos(); - - // ignore the first elements added by the LineLayoutManager - iStartElement += (iStartElement == 0) ? currPar.ignoreAtStart : 0; - - // ignore the last elements added by the LineLayoutManager - iEndElement -= (iEndElement == (currPar.size() - 1)) - ? currPar.ignoreAtEnd : 0; - - // ignore the last element in the line if it is a KnuthGlue object - paragraphIterator = currPar.listIterator(iEndElement); - tempElement = (KnuthElement) paragraphIterator.next(); - if (tempElement.isGlue()) { - iEndElement --; - // this returns the same KnuthElement - paragraphIterator.previous(); - tempElement = (KnuthElement) paragraphIterator.previous(); - } - lastLM = tempElement.getLayoutManager(); - - // ignore KnuthGlue and KnuthPenalty objects - // at the beginning of the line - paragraphIterator = currPar.listIterator(iStartElement); - tempElement = (KnuthElement) paragraphIterator.next(); - while (!tempElement.isBox() && paragraphIterator.hasNext()) { + Position pos = (Position) parentIter.next(); + if (pos instanceof LineBreakPosition) { + ListIterator paragraphIterator = null; + KnuthElement tempElement = null; + // the TLM which created the last KnuthElement in this line + LayoutManager lastLM = null; + + LineBreakPosition lbp = (LineBreakPosition) pos; + LineArea lineArea = new LineArea(); + lineArea.setStartIndent(lbp.startIndent); + lineArea.setBPD(lbp.lineHeight); + lc.setBaseline(lbp.baseline); + lc.setLineHeight(lbp.lineHeight); + lc.setMiddleShift(middleShift); + lc.setTopShift(lbp.topShift); + lc.setBottomShift(lbp.bottomShift); + + iCurrParIndex = lbp.iParIndex; + Paragraph currPar = (Paragraph) knuthParagraphs.get(iCurrParIndex); + iEndElement = lbp.getLeafPos(); + + // ignore the first elements added by the LineLayoutManager + iStartElement += (iStartElement == 0) ? currPar.ignoreAtStart : 0; + + // ignore the last elements added by the LineLayoutManager + iEndElement -= (iEndElement == (currPar.size() - 1)) + ? currPar.ignoreAtEnd : 0; + + // ignore the last element in the line if it is a KnuthGlue object + paragraphIterator = currPar.listIterator(iEndElement); tempElement = (KnuthElement) paragraphIterator.next(); - iStartElement ++; - } - - // Add the inline areas to lineArea - PositionIterator inlinePosIter - = new KnuthPossPosIter(currPar, iStartElement, - iEndElement + 1); - - iStartElement = lbp.getLeafPos() + 1; - if (iStartElement == currPar.size()) { - // advance to next paragraph - iStartElement = 0; - } - - lc.setSpaceAdjust(lbp.dAdjust); - lc.setIPDAdjust(lbp.ipdAdjust); - lc.setLeadingSpace(new SpaceSpecifier(true)); - lc.setTrailingSpace(new SpaceSpecifier(false)); - lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true); - setChildContext(lc); - while ((childLM = inlinePosIter.getNextChildLM()) != null) { - lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM)); - childLM.addAreas(inlinePosIter, lc); - lc.setLeadingSpace(lc.getTrailingSpace()); + if (tempElement.isGlue()) { + iEndElement --; + // this returns the same KnuthElement + paragraphIterator.previous(); + tempElement = (KnuthElement) paragraphIterator.previous(); + } + lastLM = tempElement.getLayoutManager(); + + // ignore KnuthGlue and KnuthPenalty objects + // at the beginning of the line + paragraphIterator = currPar.listIterator(iStartElement); + tempElement = (KnuthElement) paragraphIterator.next(); + while (!tempElement.isBox() && paragraphIterator.hasNext()) { + tempElement = (KnuthElement) paragraphIterator.next(); + iStartElement ++; + } + + // Add the inline areas to lineArea + PositionIterator inlinePosIter + = new KnuthPossPosIter(currPar, iStartElement, + iEndElement + 1); + + iStartElement = lbp.getLeafPos() + 1; + if (iStartElement == currPar.size()) { + // advance to next paragraph + iStartElement = 0; + } + + lc.setSpaceAdjust(lbp.dAdjust); + lc.setIPDAdjust(lbp.ipdAdjust); + lc.setLeadingSpace(new SpaceSpecifier(true)); lc.setTrailingSpace(new SpaceSpecifier(false)); + lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true); + + /* extension (not in the XSL FO recommendation): if the left and right margins + have been optimized, recompute indents and / or adjust ratio, according + to the paragraph horizontal alignment */ + if (false && bTextAlignment == EN_JUSTIFY) { + // re-compute space adjust ratio + int updatedDifference = context.getStackLimit().opt - lbp.lineWidth + lbp.difference; + double updatedRatio = 0.0; + if (updatedDifference > 0) { + updatedRatio = (float) updatedDifference / lbp.availableStretch; + } else if (updatedDifference < 0) { + updatedRatio = (float) updatedDifference / lbp.availableShrink; + } + lc.setIPDAdjust(updatedRatio); + //System.out.println("LLM.addAreas> old difference = " + lbp.difference + " new difference = " + updatedDifference); + //System.out.println(" old ratio = " + lbp.ipdAdjust + " new ratio = " + updatedRatio); + } else if (false && bTextAlignment == EN_CENTER) { + // re-compute indent + int updatedIndent = lbp.startIndent + (context.getStackLimit().opt - lbp.lineWidth) / 2; + lineArea.setStartIndent(updatedIndent); + } else if (false && bTextAlignment == EN_END) { + // re-compute indent + int updatedIndent = lbp.startIndent + (context.getStackLimit().opt - lbp.lineWidth); + lineArea.setStartIndent(updatedIndent); + } + + setCurrentArea(lineArea); + setChildContext(lc); + while ((childLM = inlinePosIter.getNextChildLM()) != null) { + lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM)); + childLM.addAreas(inlinePosIter, lc); + lc.setLeadingSpace(lc.getTrailingSpace()); + lc.setTrailingSpace(new SpaceSpecifier(false)); + } + + // when can this be null? + // if display-align is distribute, add space after + if (context.getSpaceAfter() > 0 + && (!context.isLastArea() || parentIter.hasNext())) { + lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter()); + } + parentLM.addChildArea(lineArea); + } else { + // pos was the Position inside a penalty item, nothing to do } - // when can this be null? - if (lc.getTrailingSpace() != null) { - addSpace(lineArea, lc.getTrailingSpace().resolve(true), - lc.getSpaceAdjust()); - } - parentLM.addChildArea(lineArea); } setCurrentArea(null); // ?? necessary } - - /** - * Add an unresolved area. - * If a child layout manager needs to add an unresolved area - * for page reference or linking then this intercepts it for - * line area handling. - * A line area may need to have the inline areas adjusted - * to properly fill the line area. This adds a resolver that - * resolves the inline area and can do the necessary - * adjustments to the line and inline areas. - * - * @param id the id reference of the resolvable - * @param res the resolvable object - */ - public void addUnresolvedArea(String id, Resolvable res) { - // create a resolvable class that handles ipd - // adjustment for the current line - - parentLM.addUnresolvedArea(id, res); - } - } diff --git a/src/java/org/apache/fop/layoutmgr/LineLayoutPossibilities.java b/src/java/org/apache/fop/layoutmgr/LineLayoutPossibilities.java new file mode 100644 index 000000000..865121f7d --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/LineLayoutPossibilities.java @@ -0,0 +1,236 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class LineLayoutPossibilities { + + /** logger instance */ + protected static Log log = LogFactory.getLog(LineLayoutPossibilities.class); + + private class Possibility { + private int lineCount; + private double demerits; + private List breakPositions; + + private Possibility(int lc, double dem) { + lineCount = lc; + demerits = dem; + breakPositions = new java.util.ArrayList(lc); + } + + private int getLineCount() { + return lineCount; + } + + private double getDemerits() { + return demerits; + } + + private void addBreakPosition(Position pos) { + // Positions are always added with index 0 because + // they are created backward, from the last one to + // the first one + breakPositions.add(0, pos); + } + + private Position getBreakPosition(int i) { + return (Position)breakPositions.get(i); + } + } + + private List possibilitiesList; + private List savedPossibilities; + private int minimumIndex; + private int optimumIndex; + private int maximumIndex; + private int chosenIndex; + private int savedOptLineCount; + + public LineLayoutPossibilities() { + possibilitiesList = new java.util.ArrayList(); + savedPossibilities = new java.util.ArrayList(); + optimumIndex = -1; + } + + public void addPossibility(int ln, double dem) { + possibilitiesList.add(new Possibility(ln, dem)); + if (possibilitiesList.size() == 1) { + // first Possibility added + minimumIndex = 0; + optimumIndex = 0; + maximumIndex = 0; + chosenIndex = 0; + } else { + if (dem < ((Possibility)possibilitiesList.get(optimumIndex)).getDemerits()) { + optimumIndex = possibilitiesList.size() - 1; + chosenIndex = optimumIndex; + } + if (ln < ((Possibility)possibilitiesList.get(minimumIndex)).getLineCount()) { + minimumIndex = possibilitiesList.size() - 1; + } + if (ln > ((Possibility)possibilitiesList.get(maximumIndex)).getLineCount()) { + maximumIndex = possibilitiesList.size() - 1; + } + } + } + + /* save in a different array the computed Possibilities, + * so possibilitiesList is ready to store different Possibilities + */ + public void savePossibilities(boolean bSaveOptLineCount) { + if (bSaveOptLineCount) { + savedOptLineCount = getOptLineCount(); + } else { + savedOptLineCount = 0; + } + savedPossibilities = possibilitiesList; + possibilitiesList = new java.util.ArrayList(); + } + + /* replace the Possibilities stored in possibilitiesList with + * the ones stored in savedPossibilities and having the same line number + */ + public void restorePossibilities() { + int index = 0; + while (savedPossibilities.size() > 0) { + Possibility restoredPossibility = (Possibility) savedPossibilities.remove(0); + if (restoredPossibility.getLineCount() < getMinLineCount()) { + // if the line number of restoredPossibility is less than the minimum one, + // add restoredPossibility at the beginning of the list + possibilitiesList.add(0, restoredPossibility); + // update minimumIndex + minimumIndex = 0; + // shift the other indexes; + optimumIndex ++; + maximumIndex ++; + chosenIndex ++; + } else if (restoredPossibility.getLineCount() > getMaxLineCount()) { + // if the line number of restoredPossibility is greater than the maximum one, + // add restoredPossibility at the end of the list + possibilitiesList.add(possibilitiesList.size(), restoredPossibility); + // update maximumIndex + maximumIndex = possibilitiesList.size() - 1; + index = maximumIndex; + } else { + // find the index of the Possibility that will be replaced + while (index < maximumIndex + && getLineCount(index) < restoredPossibility.getLineCount()) { + index ++; + } + if (getLineCount(index) == restoredPossibility.getLineCount()) { + possibilitiesList.set(index, restoredPossibility); + } else { + // this should not happen + log.error("LineLayoutPossibilities restorePossibilities()," + + " min= " + getMinLineCount() + + " max= " + getMaxLineCount() + + " restored= " + restoredPossibility.getLineCount()); + return; + } + } + // update optimumIndex and chosenIndex + if (savedOptLineCount == 0 && getDemerits(optimumIndex) > restoredPossibility.getDemerits() + || savedOptLineCount != 0 && restoredPossibility.getLineCount() == savedOptLineCount) { + optimumIndex = index; + chosenIndex = optimumIndex; + } + } +/*LF*/ //System.out.println(">> minLineCount = " + getMinLineCount() + " optLineCount = " + getOptLineCount() + " maxLineCount() = " + getMaxLineCount()); + } + + public void addBreakPosition(Position pos, int i) { + ((Possibility)possibilitiesList.get(i)).addBreakPosition(pos); + } + + public boolean canUseMoreLines() { + return (getOptLineCount() < getMaxLineCount()); + } + + public boolean canUseLessLines() { + return (getMinLineCount() < getOptLineCount()); + } + + public int getMinLineCount() { + return getLineCount(minimumIndex); + } + + public int getOptLineCount() { + return getLineCount(optimumIndex); + } + + public int getMaxLineCount() { + return getLineCount(maximumIndex); + } + + public int getChosenLineCount() { + return getLineCount(chosenIndex); + } + + public int getLineCount(int i) { + return ((Possibility)possibilitiesList.get(i)).getLineCount(); + } + + public double getChosenDemerits() { + return getDemerits(chosenIndex); + } + + public double getDemerits(int i) { + return ((Possibility)possibilitiesList.get(i)).getDemerits(); + } + + public int getPossibilitiesNumber() { + return possibilitiesList.size(); + } + + public Position getChosenPosition(int i) { + return ((Possibility)possibilitiesList.get(chosenIndex)).getBreakPosition(i); + } + + public int applyLineCountAdjustment(int adj) { + if (adj >= (getMinLineCount() - getChosenLineCount()) + && adj <= (getMaxLineCount() - getChosenLineCount()) + && getLineCount(chosenIndex + adj) == getChosenLineCount() + adj) { + chosenIndex += adj; + log.debug("chosenLineCount= " + (getChosenLineCount() - adj) + " adjustment= " + adj + + " => chosenLineCount= " + getLineCount(chosenIndex)); + return adj; + } else { + // this should not happen! + log.warn("Cannot apply the desired line count adjustment."); + return 0; + } + } + + public void printAll() { + System.out.println("++++++++++"); + System.out.println(" " + possibilitiesList.size() + " possibility':"); + for (int i = 0; i < possibilitiesList.size(); i ++) { + System.out.println(" " + ((Possibility)possibilitiesList.get(i)).getLineCount() + + (i == optimumIndex ? " *" : "") + + (i == minimumIndex ? " -" : "") + + (i == maximumIndex ? " +" : "")); + } + System.out.println("++++++++++"); + } +} diff --git a/src/java/org/apache/fop/layoutmgr/MinOptMaxUtil.java b/src/java/org/apache/fop/layoutmgr/MinOptMaxUtil.java index 63cec1a7e..a17748e24 100644 --- a/src/java/org/apache/fop/layoutmgr/MinOptMaxUtil.java +++ b/src/java/org/apache/fop/layoutmgr/MinOptMaxUtil.java @@ -63,6 +63,17 @@ public class MinOptMaxUtil { } } + public static void extendMinimum(MinOptMax mom, int len, boolean optToLen) { + if (mom.min < len) { + mom.min = len; + mom.opt = Math.max(mom.min, mom.opt); + if (optToLen) { + mom.opt = Math.min(mom.min, len); + } + mom.max = Math.max(mom.opt, mom.max); + } + } + /** * After a calculation on a MinOptMax, this can be called to set opt to * a new effective value. @@ -77,4 +88,21 @@ public class MinOptMaxUtil { } } + /** + * Converts a LengthRangeProperty to a MinOptMax. + * @param prop LengthRangeProperty + * @return the requested MinOptMax instance + */ + public static MinOptMax toMinOptMax(LengthRangeProperty prop) { + MinOptMax mom = new MinOptMax( + (prop.getMinimum().isAuto() + ? 0 : prop.getMinimum().getLength().getValue()), + (prop.getOptimum().isAuto() + ? 0 : prop.getOptimum().getLength().getValue()), + (prop.getMinimum().isAuto() + ? Integer.MAX_VALUE + : prop.getMaximum().getLength().getValue())); + return mom; + } + } diff --git a/src/java/org/apache/fop/layoutmgr/NonLeafPosition.java b/src/java/org/apache/fop/layoutmgr/NonLeafPosition.java index 3b828a80c..7f21a54d3 100644 --- a/src/java/org/apache/fop/layoutmgr/NonLeafPosition.java +++ b/src/java/org/apache/fop/layoutmgr/NonLeafPosition.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 1999-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,5 +30,18 @@ public class NonLeafPosition extends Position { public Position getPosition() { return subPos; } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("NonLeafPos("); + if (getPosition() != null) { + sb.append(getPosition().toString()); + } else { + sb.append("null"); + } + sb.append(")"); + return sb.toString(); + } } diff --git a/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java b/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java new file mode 100644 index 000000000..766ca99c8 --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr; + +import java.util.LinkedList; + +import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition; + +class PageBreakingAlgorithm extends BreakingAlgorithm { + private LayoutManager topLevelLM; + private LinkedList pageBreaks = null; + + public PageBreakingAlgorithm(LayoutManager topLevelLM, + int alignment, int alignmentLast) { + super(alignment, alignmentLast, true); + this.topLevelLM = topLevelLM; + } + + public LinkedList getPageBreaks() { + return pageBreaks; + } + + public void insertPageBreakAsFirst(PageBreakPosition pageBreak) { + if (pageBreaks == null) { + pageBreaks = new LinkedList(); + } + pageBreaks.addFirst(pageBreak); + } + + public void updateData1(int total, double demerits) { + } + + public void updateData2(KnuthNode bestActiveNode, + KnuthSequence sequence, + int total) { + //int difference = (bestActiveNode.line < total) ? bestActiveNode.difference : bestActiveNode.difference + fillerMinWidth; + int difference = bestActiveNode.difference; + int blockAlignment = (bestActiveNode.line < total) ? alignment : alignmentLast; + double ratio = (blockAlignment == org.apache.fop.fo.Constants.EN_JUSTIFY + || bestActiveNode.adjustRatio < 0) ? bestActiveNode.adjustRatio : 0; + + + // add nodes at the beginning of the list, as they are found + // backwards, from the last one to the first one + System.out.println("BBA> difference= " + difference + " ratio= " + ratio + + " posizione= " + bestActiveNode.position); + insertPageBreakAsFirst(new PageBreakPosition(this.topLevelLM, + bestActiveNode.position, ratio, difference)); + } + + protected int filterActiveNodes() { + // leave only the active node with fewest total demerits + KnuthNode bestActiveNode = null; + for (int i = startLine; i < endLine; i++) { + for (KnuthNode node = getNode(i); node != null; node = node.next) { + bestActiveNode = compareNodes(bestActiveNode, node); + if (node != bestActiveNode) { + removeNode(i, node); + } + } + } + return bestActiveNode.line; + } + +} \ No newline at end of file diff --git a/src/java/org/apache/fop/layoutmgr/PageNumberCitationLayoutManager.java b/src/java/org/apache/fop/layoutmgr/PageNumberCitationLayoutManager.java index d2755fea3..f03973694 100644 --- a/src/java/org/apache/fop/layoutmgr/PageNumberCitationLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/PageNumberCitationLayoutManager.java @@ -58,7 +58,7 @@ public class PageNumberCitationLayoutManager extends LeafNodeLayoutManager { public void addAreas(PositionIterator posIter, LayoutContext context) { super.addAreas(posIter, context); if (!resolved) { - parentLM.addUnresolvedArea(fobj.getRefId(), (Resolvable) curArea); + getPSLM().addUnresolvedArea(fobj.getRefId(), (Resolvable) curArea); } } @@ -71,7 +71,7 @@ public class PageNumberCitationLayoutManager extends LeafNodeLayoutManager { * return a resolvable area */ private InlineArea getPageNumberCitationInlineArea(LayoutManager parentLM) { - PageViewport page = parentLM.resolveRefID(fobj.getRefId()); + PageViewport page = getPSLM().getFirstPVWithID(fobj.getRefId()); InlineArea inline = null; if (page != null) { String str = page.getPageNumberString(); @@ -118,7 +118,7 @@ public class PageNumberCitationLayoutManager extends LeafNodeLayoutManager { } protected void addId() { - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); } } diff --git a/src/java/org/apache/fop/layoutmgr/PageNumberLayoutManager.java b/src/java/org/apache/fop/layoutmgr/PageNumberLayoutManager.java index 89298bb69..382ff9dfd 100644 --- a/src/java/org/apache/fop/layoutmgr/PageNumberLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/PageNumberLayoutManager.java @@ -46,7 +46,7 @@ public class PageNumberLayoutManager extends LeafNodeLayoutManager { public InlineArea get(LayoutContext context) { // get page string from parent, build area TextArea inline = new TextArea(); - String str = parentLM.getCurrentPageNumberString(); + String str = getCurrentPV().getPageNumberString(); int width = 0; for (int count = 0; count < str.length(); count++) { width += font.getCharWidth(str.charAt(count)); @@ -69,7 +69,7 @@ public class PageNumberLayoutManager extends LeafNodeLayoutManager { } protected void addId() { - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); } } diff --git a/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java b/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java index d0cc95e46..bd7cac98a 100644 --- a/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java @@ -20,119 +20,93 @@ package org.apache.fop.layoutmgr; import org.apache.fop.apps.FOPException; -import org.apache.fop.area.CTM; import org.apache.fop.area.AreaTreeHandler; import org.apache.fop.area.AreaTreeModel; import org.apache.fop.area.Area; import org.apache.fop.area.PageViewport; -import org.apache.fop.area.NormalFlow; import org.apache.fop.area.LineArea; -import org.apache.fop.area.Page; import org.apache.fop.area.RegionViewport; -import org.apache.fop.area.RegionReference; -import org.apache.fop.area.BodyRegion; -import org.apache.fop.area.Span; -import org.apache.fop.area.BeforeFloat; -import org.apache.fop.area.Footnote; import org.apache.fop.area.Resolvable; -import org.apache.fop.area.Trait; import org.apache.fop.datatypes.PercentBase; -import org.apache.fop.datatypes.FODimension; -import org.apache.fop.fo.FObj; import org.apache.fop.fo.Constants; import org.apache.fop.fo.flow.Marker; +import org.apache.fop.fo.flow.RetrieveMarker; import org.apache.fop.fo.pagination.PageSequence; import org.apache.fop.fo.pagination.Region; -import org.apache.fop.fo.pagination.RegionBody; +import org.apache.fop.fo.pagination.SideRegion; import org.apache.fop.fo.pagination.SimplePageMaster; import org.apache.fop.fo.pagination.StaticContent; -import org.apache.fop.fo.properties.CommonMarginBlock; +import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.awt.Rectangle; -import java.util.Iterator; -import java.awt.geom.Rectangle2D; -import org.apache.fop.traits.MinOptMax; /** - * LayoutManager for a PageSequence. + * LayoutManager for a PageSequence. This class is instantiated by + * area.AreaTreeHandler for each fo:page-sequence found in the + * input document. */ public class PageSequenceLayoutManager extends AbstractLayoutManager { - private PageSequence pageSeq; - - private static class BlockBreakPosition extends LeafPosition { - protected BreakPoss breakps; - - protected BlockBreakPosition(LayoutManager lm, BreakPoss bp) { - super(lm, 0); - breakps = bp; - } - } - - private int startPageNum = 0; - private int currentPageNum = 0; - private String pageNumberString; - private boolean isFirstPage = true; - - /** Current page being worked on. */ - private PageViewport curPage; - - /** Current span being filled */ - private Span curSpan; - - /** Current normal-flow-reference-area being filled. */ - private NormalFlow curFlow; - - private int flowBPD = 0; - private int flowIPD = 0; /** - * AreaTreeHandler which activates this PSLM. + * AreaTreeHandler which activates the PSLM and controls + * the rendering of its pages. */ private AreaTreeHandler areaTreeHandler; /** - * AreaTreeModel that this PSLM sends pages to. + * fo:page-sequence formatting object being + * processed by this class */ - private AreaTreeModel areaTreeModel; + private PageSequence pageSeq; + + /** + * Current page-viewport-area being filled by + * the PSLM. + */ + private PageViewport curPV = null; /** - * This is the SimplePageMaster that should be used to create the page. It - * will be equal to the PageSequence's simplePageMaster, if it exists, or - * to the correct member of the PageSequence's pageSequenceMaster, if that - * is in effect instead. + * Zero-based index of column (Normal Flow) in span (of the PV) + * being filled. See XSL Rec description of fo:region-body + * and fop.Area package classes for more information. */ - private SimplePageMaster currentSimplePageMaster; + private int curFlowIdx = -1; /** - * Constructor - activated by AreaTreeHandler for each - * fo:page-sequence in the input FO stream - * - * @param pageseq the page-sequence formatting object + * The FlowLayoutManager object, which processes + * the single fo:flow of the fo:page-sequence */ - public PageSequenceLayoutManager(PageSequence pageSeq) { - super(pageSeq); - this.pageSeq = pageSeq; - } + private FlowLayoutManager childFLM = null; /** - * Set the AreaTreeHandler - * @param areaTreeHandler the area tree handler object + * The collection of StaticContentLayoutManager objects that + * are associated with this Page Sequence, keyed by flow-name. */ - public void setAreaTreeHandler(AreaTreeHandler areaTreeHandler) { - this.areaTreeHandler = areaTreeHandler; - areaTreeModel = areaTreeHandler.getAreaTreeModel(); + //private HashMap staticContentLMs = new HashMap(4); + + private int startPageNum = 0; + private int currentPageNum = 0; + + /** + * Constructor + * + * @param ath the area tree handler object + * @param pseq fo:page-sequence to process + */ + public PageSequenceLayoutManager(AreaTreeHandler ath, PageSequence pseq) { + super(pseq); + this.areaTreeHandler = ath; + this.pageSeq = pseq; } /** * @see org.apache.fop.layoutmgr.LayoutManager - * @return the AreaTreeHandler object + * @return the LayoutManagerMaker object */ - public AreaTreeHandler getAreaTreeHandler() { - return areaTreeHandler; + public LayoutManagerMaker getLayoutManagerMaker() { + return areaTreeHandler.getLayoutManagerMaker(); } /** @@ -142,42 +116,106 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { */ public void activateLayout() { startPageNum = pageSeq.getStartingPageNumber(); - currentPageNum = startPageNum; - pageNumberString = pageSeq.makeFormattedPageNumber(currentPageNum); + currentPageNum = startPageNum - 1; LineArea title = null; if (pageSeq.getTitleFO() != null) { - ContentLayoutManager clm = - new ContentLayoutManager(pageSeq.getTitleFO(), this); - title = (LineArea) clm.getParentArea(null); // can improve + ContentLayoutManager clm = new ContentLayoutManager(pageSeq + .getTitleFO(), this); + title = (LineArea) clm.getParentArea(null); } - areaTreeModel.startPageSequence(title); + areaTreeHandler.getAreaTreeModel().startPageSequence(title); log.debug("Starting layout"); - makeNewPage(false, false); - flowIPD = curFlow.getIPD(); - - BreakPoss bp; - LayoutContext childLC = new LayoutContext(0); - while (!isFinished()) { - if ((bp = getNextBreakPoss(childLC)) != null) { - addAreas((BlockBreakPosition)bp.getPosition()); - // add static areas and resolve any new id areas - // finish page and add to area tree - finishPage(); - currentPageNum++; - pageNumberString = pageSeq.makeFormattedPageNumber(currentPageNum); - } - } - // TODO: Don't decrement currentPageNum when no pages are generated - currentPageNum--; + curPV = makeNewPage(false, true, false); + + PageBreaker breaker = new PageBreaker(this); + int flowBPD = (int) curPV.getBodyRegion().getBPD(); + breaker.doLayout(flowBPD); + finishPage(); - pageSeq.getRoot().notifyPageSequenceFinished(currentPageNum, (currentPageNum - startPageNum) + 1); + pageSeq.getRoot().notifyPageSequenceFinished(currentPageNum, + (currentPageNum - startPageNum) + 1); log.debug("Ending layout"); } + private class PageBreaker extends AbstractBreaker { + + private PageSequenceLayoutManager pslm; + private boolean firstPart = true; + + public PageBreaker(PageSequenceLayoutManager pslm) { + this.pslm = pslm; + } + + protected LayoutContext createLayoutContext() { + LayoutContext lc = new LayoutContext(0); + int flowIPD = curPV.getCurrentSpan().getColumnWidth(); + lc.setRefIPD(flowIPD); + return lc; + } + + protected LayoutManager getTopLevelLM() { + return pslm; + } + + protected LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + return pslm.getNextKnuthElements(context, alignment); + } + + protected int getCurrentDisplayAlign() { + return curPV.getSPM().getRegion(Constants.FO_REGION_BODY).getDisplayAlign(); + } + + protected boolean hasMoreContent() { + return !isFinished(); + } + + protected void addAreas(PositionIterator posIter, LayoutContext context) { + getCurrentChildLM().addAreas(posIter, context); + } + + protected void doPhase3(PageBreakingAlgorithm alg, int partCount, + BlockSequence originalList, BlockSequence effectiveList) { + //Directly add areas after finding the breaks + addAreas(alg, partCount, originalList, effectiveList); + } + + protected void startPart(BlockSequence list, boolean bIsFirstPage) { + if (curPV == null) { + throw new IllegalStateException("curPV must not be null"); + } else { + //firstPart is necessary because we need the first page before we start the + //algorithm so we have a BPD and IPD. This may subject to change later when we + //start handling more complex cases. + if (!firstPart) { + if (curFlowIdx < curPV.getCurrentSpan().getColumnCount()-1) { + curFlowIdx++; + } else { + // if this is the first page that will be created by + // the current BlockSequence, it could have a break + // condition that must be satisfied; + // otherwise, we may simply need a new page + handleBreakTrait(bIsFirstPage ? list.getStartOn() : Constants.EN_PAGE); + } + } + } + // add static areas and resolve any new id areas + // finish page and add to area tree + firstPart = false; + } + + protected void finishPart() { + } + + protected LayoutManager getCurrentChildLM() { + return childFLM; + } + + } + /** @see org.apache.fop.layoutmgr.LayoutManager#isBogus() */ public boolean isBogus() { return false; @@ -191,79 +229,70 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { * @param context the layout context for finding breaks * @return the break for the page */ - public BreakPoss getNextBreakPoss(LayoutContext context) { + public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { LayoutManager curLM; // currently active LM while ((curLM = getChildLM()) != null) { - BreakPoss bp = null; +/*LF*/ LinkedList returnedList = null; +/*LF*/ if (childFLM == null && (curLM instanceof FlowLayoutManager)) { +/*LF*/ childFLM = (FlowLayoutManager)curLM; +/*LF*/ } else { +/*LF*/ if (curLM != childFLM) { +/*LF*/ System.out.println("PLM> figlio sconosciuto (invalid child LM)"); +/*LF*/ } +/*LF*/ } LayoutContext childLC = new LayoutContext(0); - childLC.setStackLimit(new MinOptMax(flowBPD)); - childLC.setRefIPD(flowIPD); + childLC.setStackLimit(context.getStackLimit()); + childLC.setRefIPD(context.getRefIPD()); if (!curLM.isFinished()) { + int flowIPD = curPV.getCurrentSpan().getColumnWidth(); + int flowBPD = (int) curPV.getBodyRegion().getBPD(); pageSeq.setLayoutDimension(PercentBase.REFERENCE_AREA_IPD, flowIPD); pageSeq.setLayoutDimension(PercentBase.REFERENCE_AREA_BPD, flowBPD); - bp = curLM.getNextBreakPoss(childLC); +/*LF*/ returnedList = curLM.getNextKnuthElements(childLC, alignment); } - if (bp != null) { - return new BreakPoss( - new BlockBreakPosition(curLM, bp)); + if (returnedList != null) { + return returnedList; } } setFinished(true); return null; } - /** - * Get the current page number string. - * This returns the formatted string for the current page. - * - * @return the formatted page number string - */ - public String getCurrentPageNumberString() { - return pageNumberString; - } - /** * Provides access to the current page. * @return the current PageViewport */ - public PageViewport getCurrentPageViewport() { - return this.curPage; + public PageViewport getCurrentPV() { + return curPV; } /** - * Resolve a reference ID. - * This resolves a reference ID and returns the first PageViewport - * that contains the reference ID or null if reference not found. + * Provides access to this object + * @return this PageSequenceLayoutManager instance + */ + public PageSequenceLayoutManager getPSLM() { + return this; + } + + /** + * This returns the first PageViewport that contains an id trait + * matching the idref argument, or null if no such PV exists. * - * @param id the reference ID to lookup - * @return the first page viewport that contains the reference + * @param idref the idref trait needing to be resolved + * @return the first PageViewport that contains the ID trait */ - public PageViewport resolveRefID(String id) { - List list = areaTreeHandler.getPageViewportsContainingID(id); + public PageViewport getFirstPVWithID(String idref) { + List list = areaTreeHandler.getPageViewportsContainingID(idref); if (list != null && list.size() > 0) { return (PageViewport) list.get(0); } return null; } - /** - * Add the areas to the current page. - * Given the page break position this adds the areas to the current - * page. - * - * @param bbp the block break position - */ - public void addAreas(BlockBreakPosition bbp) { - List list = new java.util.ArrayList(); - list.add(bbp.breakps); - bbp.getLM().addAreas(new BreakPossPosIter(list, 0, - 1), null); - } - /** * Add an ID reference to the current page. * When adding areas the area adds its ID reference. @@ -273,40 +302,34 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { * @param id the ID reference to add */ public void addIDToPage(String id) { - areaTreeHandler.associateIDWithPageViewport(id, curPage); + if (id != null && id.length() > 0) { + areaTreeHandler.associateIDWithPageViewport(id, curPV); + } } /** - * Add an unresolved area to the layout manager. - * The Page layout manager handles the unresolved ID - * reference by adding to the current page and then adding - * the page as a resolvable to the area tree. - * This is so that the area tree can resolve the reference - * and the page can serialize the resolvers if required. + * Identify an unresolved area (one needing an idref to be + * resolved, e.g. the internal-destination of an fo:basic-link) + * for both the AreaTreeHandler and PageViewport object. + * + * The AreaTreeHandler keeps a document-wide list of idref's + * and the PV's needing them to be resolved. It uses this to + * send notifications to the PV's when an id has been resolved. + * + * The PageViewport keeps lists of id's needing resolving, along + * with the child areas (page-number-citation, basic-link, etc.) + * of the PV needing their resolution. * * @param id the ID reference to add * @param res the resolvable object that needs resolving */ public void addUnresolvedArea(String id, Resolvable res) { - // add to the page viewport so it can serialize - curPage.addUnresolvedIDRef(id, res); - // add unresolved to tree - areaTreeHandler.addUnresolvedIDRef(id, curPage); - } - - /** - * Add the marker to the page layout manager. - * - * @see org.apache.fop.layoutmgr.LayoutManager - */ - public void addMarkerMap(Map marks, boolean starting, boolean isfirst, boolean islast) { - //getLogger().debug("adding markers: " + marks + ":" + start); - // add markers to page on area tree - curPage.addMarkers(marks, starting, isfirst, islast); + curPV.addUnresolvedIDRef(id, res); + areaTreeHandler.addUnresolvedIDRef(id, curPV); } /** - * Retrieve a marker from this layout manager. + * Bind the RetrieveMarker to the corresponding Marker subtree. * If the boundary is page then it will only check the * current page. For page-sequence and document it will * lookup preceding pages from the area tree and try to find @@ -317,14 +340,19 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { * Therefore we use last-ending-within-page (Constants.EN_LEWP) * as the position. * - * @param name the marker class name to lookup - * @param pos the position to locate the marker - * @param boundary the boundary for locating the marker - * @return the layout manager for the marker contents + * @param rm the RetrieveMarker instance whose properties are to + * used to find the matching Marker. + * @return a bound RetrieveMarker instance, or null if no Marker + * could be found. */ - public Marker retrieveMarker(String name, int pos, int boundary) { + public RetrieveMarker resolveRetrieveMarker(RetrieveMarker rm) { + AreaTreeModel areaTreeModel = areaTreeHandler.getAreaTreeModel(); + String name = rm.getRetrieveClassName(); + int pos = rm.getRetrievePosition(); + int boundary = rm.getRetrieveBoundary(); + // get marker from the current markers on area tree - Marker mark = (Marker)curPage.getMarker(name, pos); + Marker mark = (Marker)curPV.getMarker(name, pos); if (mark == null && boundary != EN_PAGE) { // go back over pages until mark found // if document boundary then keep going @@ -339,7 +367,7 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { PageViewport pv = areaTreeModel.getPage(seq, page); mark = (Marker)pv.getMarker(name, Constants.EN_LEWP); if (mark != null) { - return mark; + break; } page--; if (page < 0 && doc && seq > 1) { @@ -351,71 +379,55 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { if (mark == null) { log.debug("found no marker with name: " + name); + return null; + } else { + rm.bindMarker(mark); + return rm; } - - return mark; } - /** - * For now, only handle normal flow areas. - * @see org.apache.fop.layoutmgr.LayoutManager#addChildArea(org.apache.fop.area.Area) - */ - public void addChildArea(Area childArea) { - if (childArea == null) { - return; - } - if (childArea.getAreaClass() == Area.CLASS_NORMAL) { - getParentArea(childArea); - } else { - // todo: all the others! + private PageViewport makeNewPage(boolean bIsBlank, boolean bIsFirst, boolean bIsLast) { + if (curPV != null) { + finishPage(); } - } - private PageViewport makeNewPage(boolean bIsBlank, boolean bIsLast) { - finishPage(); + currentPageNum++; + String pageNumberString = pageSeq.makeFormattedPageNumber(currentPageNum); try { // create a new page - currentSimplePageMaster = pageSeq.getSimplePageMasterToUse( - currentPageNum, isFirstPage, bIsBlank); - Region body = currentSimplePageMaster.getRegion(FO_REGION_BODY); + SimplePageMaster spm = pageSeq.getSimplePageMasterToUse( + currentPageNum, bIsFirst, bIsBlank); + + Region body = spm.getRegion(FO_REGION_BODY); if (!pageSeq.getMainFlow().getFlowName().equals(body.getRegionName())) { + // this is fine by the XSL Rec (fo:flow's flow-name can be mapped to + // any region), but we don't support it yet. throw new FOPException("Flow '" + pageSeq.getMainFlow().getFlowName() + "' does not map to the region-body in page-master '" - + currentSimplePageMaster.getMasterName() + "'"); + + spm.getMasterName() + "'. FOP presently " + + "does not support this."); } - curPage = createPageAreas(currentSimplePageMaster); - isFirstPage = false; + curPV = new PageViewport(spm); } catch (FOPException fopex) { throw new IllegalArgumentException("Cannot create page: " + fopex.getMessage()); } - curPage.setPageNumberString(pageNumberString); + curPV.setPageNumberString(pageNumberString); if (log.isDebugEnabled()) { - log.debug("[" + curPage.getPageNumberString() + "]"); + log.debug("[" + curPV.getPageNumberString() + (bIsBlank ? "*" : "") + "]"); } - flowBPD = (int) curPage.getBodyRegion().getBPD(); - createSpan(curPage.getBodyRegion().getColumnCount()); - return curPage; + curPV.createSpan(false); + curFlowIdx = 0; + return curPV; } - private void createSpan(int numCols) { - // get Width or Height as IPD for span - RegionViewport rv = curPage.getPage().getRegionViewport(FO_REGION_BODY); - int ipdWidth = (int) rv.getRegion().getIPD() - - rv.getBorderAndPaddingWidthStart() - rv.getBorderAndPaddingWidthEnd(); - - // currently hardcoding to one column, replace with numCols when ready - curSpan = new Span(1 /* numCols */, ipdWidth); - - //curSpan.setPosition(BPD, newpos); - curPage.getBodyRegion().getMainReference().addSpan(curSpan); - curFlow = curSpan.getNormalFlow(0); - } - - private void layoutStaticContent(int regionID) { - Region reg = currentSimplePageMaster.getRegion(regionID); + /* TODO: See if can initialize the SCLM's just once for + * the page sequence, instead of after every page. + */ + private void layoutSideRegion(int regionID) { + SideRegion reg = (SideRegion)curPV.getSPM().getRegion(regionID); if (reg == null) { return; } @@ -423,59 +435,31 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { if (sc == null) { return; } - - RegionViewport rv = curPage.getPage().getRegionViewport(regionID); + + RegionViewport rv = curPV.getPage().getRegionViewport(regionID); StaticContentLayoutManager lm; - try { - lm = (StaticContentLayoutManager) - areaTreeHandler.getLayoutManagerMaker().makeLayoutManager(sc); - } catch (FOPException e) { - log.error - ("Failed to create a StaticContentLayoutManager for flow " - + sc.getFlowName() - + "; no static content will be laid out:"); - log.error(e.getMessage()); - return; - } - lm.initialize(); - lm.setRegionReference(rv.getRegion()); - lm.setParent(this); - LayoutContext childLC = new LayoutContext(0); - childLC.setStackLimit(new MinOptMax((int)curPage.getViewArea().getHeight())); - childLC.setRefIPD(rv.getRegion().getIPD()); - while (!lm.isFinished()) { - BreakPoss bp = lm.getNextBreakPoss(childLC); - if (bp != null) { - List vecBreakPoss = new java.util.ArrayList(); - vecBreakPoss.add(bp); - lm.addAreas(new BreakPossPosIter(vecBreakPoss, 0, - vecBreakPoss.size()), null); - } else { - log.error("bp==null cls=" + reg.getRegionName()); - } - } - //lm.flush(); + lm = (StaticContentLayoutManager) + areaTreeHandler.getLayoutManagerMaker().makeLayoutManager(sc); + lm.setTargetRegion(rv.getRegionReference()); + lm.setParent(this); + lm.doLayout(reg); lm.reset(null); } private void finishPage() { - if (curPage == null) { - curSpan = null; - curFlow = null; - return; - } - // Layout static content into the regions - layoutStaticContent(FO_REGION_BEFORE); - layoutStaticContent(FO_REGION_AFTER); - layoutStaticContent(FO_REGION_START); - layoutStaticContent(FO_REGION_END); + // Layout side regions + layoutSideRegion(FO_REGION_BEFORE); + layoutSideRegion(FO_REGION_AFTER); + layoutSideRegion(FO_REGION_START); + layoutSideRegion(FO_REGION_END); // Queue for ID resolution and rendering - areaTreeModel.addPage(curPage); - curPage = null; - curSpan = null; - curFlow = null; + areaTreeHandler.getAreaTreeModel().addPage(curPV); + log.debug("page finished: " + curPV.getPageNumberString() + + ", current num: " + currentPageNum); + curPV = null; + curFlowIdx = -1; } - + /** * This is called from FlowLayoutManager when it needs to start * a new flow container (while generating areas). @@ -487,103 +471,41 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { */ public Area getParentArea(Area childArea) { int aclass = childArea.getAreaClass(); + if (aclass == Area.CLASS_NORMAL) { - // todo: how to get properties from the Area??? - // Need span, break - int breakVal = Constants.EN_AUTO; - Integer breakBefore = (Integer)childArea.getTrait(Trait.BREAK_BEFORE); - if (breakBefore != null) { - breakVal = breakBefore.intValue(); - } - if (breakVal != Constants.EN_AUTO) { - // We may be forced to make new page - handleBreak(breakVal); - } else if (curPage == null) { - log.debug("curPage is null. Making new page"); - makeNewPage(false, false); - } - // Now we should be on the right kind of page - boolean bNeedNewSpan = false; - /* Determine if a new span is needed. From the XSL - * fo:region-body definition, if an fo:block has a span="ALL" - * (i.e., span all columns defined for the region-body), it - * must be placed in a span-reference-area whose - * column-count = 1. If its span-value is "NONE", - * place in a normal Span whose column-count is what - * is defined for the region-body. - */ // temporarily hardcoded to EN_NONE. - int span = Constants.EN_NONE; // childArea.getSpan() - int numColsNeeded; - if (span == Constants.EN_ALL) { - numColsNeeded = 1; - } else { // EN_NONE - numColsNeeded = curPage.getBodyRegion().getColumnCount(); - } - if (curSpan == null) { // should never happen, remove? - bNeedNewSpan = true; - } else if (numColsNeeded != curSpan.getColumnCount()) { - // need a new Span, with numColsNeeded columns - if (curSpan.getColumnCount() > 1) { - // finished with current span, so balance - // its columns to make them the same "height" - // balanceColumns(); // TODO: implement - } - bNeedNewSpan = true; - } - if (bNeedNewSpan) { - createSpan(numColsNeeded); - } else if (curFlow == null) { // should not happen - curFlow = curSpan.addAdditionalNormalFlow(); - } - return curFlow; - } else { - if (curPage == null) { - makeNewPage(false, false); - } - // Now handle different kinds of areas - if (aclass == Area.CLASS_BEFORE_FLOAT) { - BeforeFloat bf = curPage.getBodyRegion().getBeforeFloat(); - if (bf == null) { - bf = new BeforeFloat(); - curPage.getBodyRegion().setBeforeFloat(bf); - } - return bf; - } else if (aclass == Area.CLASS_FOOTNOTE) { - Footnote fn = curPage.getBodyRegion().getFootnote(); - if (fn == null) { - fn = new Footnote(); - curPage.getBodyRegion().setFootnote(fn); - } - return fn; - } - // todo!!! other area classes (side-float, absolute, fixed) - return null; + return curPV.getCurrentSpan().getNormalFlow(curFlowIdx); + } else if (aclass == Area.CLASS_BEFORE_FLOAT) { + return curPV.getBodyRegion().getBeforeFloat(); + } else if (aclass == Area.CLASS_FOOTNOTE) { + return curPV.getBodyRegion().getFootnote(); } + // todo!!! other area classes (side-float, absolute, fixed) + return null; } /** * Depending on the kind of break condition, make new column * or page. May need to make an empty page if next page would * not have the desired "handedness". - * - * @param breakVal the break value to handle + * @param breakVal - value of break-before or break-after trait. */ - private void handleBreak(int breakVal) { + private void handleBreakTrait(int breakVal) { if (breakVal == Constants.EN_COLUMN) { - if (curSpan != null - && curSpan.getNormalFlowCount() < curSpan.getColumnCount()) { + if (curFlowIdx < curPV.getCurrentSpan().getColumnCount()) { // Move to next column - curFlow = curSpan.addAdditionalNormalFlow(); - return; + curFlowIdx++; + } else { + curPV = makeNewPage(false, false, false); } - // else need new page - breakVal = Constants.EN_PAGE; + return; } - if (needEmptyPage(breakVal)) { - curPage = makeNewPage(true, false); + log.debug("handling break-before after page " + currentPageNum + + " breakVal=" + breakVal); + if (needBlankPageBeforeNew(breakVal)) { + curPV = makeNewPage(true, false, false); } if (needNewPage(breakVal)) { - curPage = makeNewPage(false, false); + curPV = makeNewPage(false, false, false); } } @@ -594,130 +516,39 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { * Note that if not all content is placed, we aren't sure whether * it will flow onto another page or not, so we'd probably better * block until the queue of layoutable stuff is empty! + * @param breakVal - value of break-before or break-after trait. */ - private boolean needEmptyPage(int breakValue) { - - if (breakValue == Constants.EN_PAGE || curPage.getPage().isEmpty()) { + private boolean needBlankPageBeforeNew(int breakVal) { + if (breakVal == Constants.EN_PAGE || (curPV.getPage().isEmpty())) { // any page is OK or we already have an empty page return false; - } - else { + } else { /* IF we are on the kind of page we need, we'll need a new page. */ - if (currentPageNum%2 != 0) { - // Current page is odd - return (breakValue == Constants.EN_ODD_PAGE); - } - else { - return (breakValue == Constants.EN_EVEN_PAGE); + if (currentPageNum % 2 == 0) { // even page + return (breakVal == Constants.EN_EVEN_PAGE); + } else { // odd page + return (breakVal == Constants.EN_ODD_PAGE); } } } /** - * See if need to generate a new page for a forced break condition. + * See if need to generate a new page + * @param breakVal - value of break-before or break-after trait. */ - private boolean needNewPage(int breakValue) { - if (curPage != null && curPage.getPage().isEmpty()) { - if (breakValue == Constants.EN_PAGE) { + private boolean needNewPage(int breakVal) { + if (curPV.getPage().isEmpty()) { + if (breakVal == Constants.EN_PAGE) { return false; } - else if (currentPageNum%2 != 0) { - // Current page is odd - return (breakValue == Constants.EN_EVEN_PAGE); + else if (currentPageNum % 2 == 0) { // even page + return (breakVal == Constants.EN_ODD_PAGE); } - else { - return (breakValue == Constants.EN_ODD_PAGE); + else { // odd page + return (breakVal == Constants.EN_EVEN_PAGE); } - } - else { + } else { return true; } } - - private PageViewport createPageAreas(SimplePageMaster spm) { - int pageWidth = spm.getPageWidth().getValue(); - int pageHeight = spm.getPageHeight().getValue(); - - // Set the page dimension as the toplevel containing block for margin. - ((FObj) pageSeq.getParent()).setLayoutDimension(PercentBase.BLOCK_IPD, pageWidth); - ((FObj) pageSeq.getParent()).setLayoutDimension(PercentBase.BLOCK_BPD, pageHeight); - - // Get absolute margin properties (top, left, bottom, right) - CommonMarginBlock mProps = spm.getCommonMarginBlock(); - - /* Create the page reference area rectangle (0,0 is at top left - * of the "page media" and y increases - * when moving towards the bottom of the page. - * The media rectangle itself is (0,0,pageWidth,pageHeight). - */ - Rectangle pageRefRect = - new Rectangle(mProps.marginLeft.getValue(), mProps.marginTop.getValue(), - pageWidth - mProps.marginLeft.getValue() - mProps.marginRight.getValue(), - pageHeight - mProps.marginTop.getValue() - mProps.marginBottom.getValue()); - - Page page = new Page(); // page reference area - - // Set up the CTM on the page reference area based on writing-mode - // and reference-orientation - FODimension reldims = new FODimension(0, 0); - CTM pageCTM = CTM.getCTMandRelDims(spm.getReferenceOrientation(), - spm.getWritingMode(), pageRefRect, reldims); - - // Create a RegionViewport/ reference area pair for each page region - RegionReference rr = null; - for (Iterator regenum = spm.getRegions().values().iterator(); - regenum.hasNext();) { - Region r = (Region)regenum.next(); - RegionViewport rvp = makeRegionViewport(r, reldims, pageCTM); - r.setLayoutDimension(PercentBase.BLOCK_IPD, rvp.getIPD()); - r.setLayoutDimension(PercentBase.BLOCK_BPD, rvp.getBPD()); - if (r.getNameId() == FO_REGION_BODY) { - rr = new BodyRegion((RegionBody) r); - } else { - rr = new RegionReference(r.getNameId()); - } - setRegionPosition(r, rr, rvp.getViewArea()); - rvp.setRegion(rr); - page.setRegionViewport(r.getNameId(), rvp); - } - - return new PageViewport(page, new Rectangle(0, 0, pageWidth, pageHeight)); - } - - /** - * Creates a RegionViewport Area object for this pagination Region. - * @param reldims relative dimensions - * @param pageCTM page coordinate transformation matrix - * @return the new region viewport - */ - private RegionViewport makeRegionViewport(Region r, FODimension reldims, CTM pageCTM) { - Rectangle2D relRegionRect = r.getViewportRectangle(reldims); - Rectangle2D absRegionRect = pageCTM.transform(relRegionRect); - // Get the region viewport rectangle in absolute coords by - // transforming it using the page CTM - RegionViewport rv = new RegionViewport(absRegionRect); - rv.setBPD((int)relRegionRect.getHeight()); - rv.setIPD((int)relRegionRect.getWidth()); - TraitSetter.addBackground(rv, r.getCommonBorderPaddingBackground()); - return rv; - } - - /** - * Set the region position inside the region viewport. - * This sets the transform that is used to place the contents of - * the region. - * - * @param r the region reference area - * @param absRegVPRect The region viewport rectangle in "absolute" coordinates - * where x=distance from left, y=distance from bottom, width=right-left - * height=top-bottom - */ - private void setRegionPosition(Region r, RegionReference rr, - Rectangle2D absRegVPRect) { - FODimension reldims = new FODimension(0, 0); - rr.setCTM(CTM.getCTMandRelDims(r.getReferenceOrientation(), - r.getWritingMode(), absRegVPRect, reldims)); - rr.setIPD(reldims.ipd); - rr.setBPD(reldims.bpd); - } } diff --git a/src/java/org/apache/fop/layoutmgr/Position.java b/src/java/org/apache/fop/layoutmgr/Position.java index 8c0369637..40fc573a9 100644 --- a/src/java/org/apache/fop/layoutmgr/Position.java +++ b/src/java/org/apache/fop/layoutmgr/Position.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 1999-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,5 +37,18 @@ public class Position { public Position getPosition() { return null; } + + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("Position"); + if (getLM() != null) { + sb.append(" {"); + sb.append(getLM()); + sb.append("}"); + } + return sb.toString(); + } } diff --git a/src/java/org/apache/fop/layoutmgr/PositionIterator.java b/src/java/org/apache/fop/layoutmgr/PositionIterator.java index 3ecf56486..ae38b96d5 100644 --- a/src/java/org/apache/fop/layoutmgr/PositionIterator.java +++ b/src/java/org/apache/fop/layoutmgr/PositionIterator.java @@ -28,7 +28,7 @@ public abstract class PositionIterator implements Iterator { private LayoutManager childLM; private boolean bHasNext; - PositionIterator(Iterator pIter) { + protected PositionIterator(Iterator pIter) { parentIter = pIter; lookAhead(); //checkNext(); diff --git a/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java index 5effaf429..aaed403d4 100644 --- a/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java @@ -21,11 +21,16 @@ package org.apache.fop.layoutmgr; import org.apache.fop.area.RegionReference; import org.apache.fop.area.Area; import org.apache.fop.area.Block; +import org.apache.fop.datatypes.PercentBase; +import org.apache.fop.fo.pagination.Region; +import org.apache.fop.fo.pagination.SideRegion; import org.apache.fop.fo.pagination.StaticContent; +import org.apache.fop.traits.MinOptMax; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; -import java.util.Map; +import java.util.ListIterator; /** * LayoutManager for an fo:flow object. @@ -34,8 +39,7 @@ import java.util.Map; * and filling them with block-level areas generated by its children. */ public class StaticContentLayoutManager extends BlockStackingLayoutManager { - - private RegionReference region; + private RegionReference targetRegion; private List blockBreaks = new ArrayList(); public StaticContentLayoutManager(StaticContent node) { @@ -46,10 +50,106 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager { * Sets the region reference * @param region region reference */ - public void setRegionReference(RegionReference region) { - this.region = region; + public void setTargetRegion(RegionReference targetRegion) { + this.targetRegion = targetRegion; } + /** + * @return the region-reference-area that this + * static content is directed to. + */ + public RegionReference getTargetRegion() { + return targetRegion; + } + + /** + * @see org.apache.fop.layoutmgr.LayoutManager#getNextKnuthElements(org.apache.fop.layoutmgr.LayoutContext, int) + */ + public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + // set layout dimensions + fobj.setLayoutDimension(PercentBase.BLOCK_IPD, context.getRefIPD()); + fobj.setLayoutDimension(PercentBase.BLOCK_BPD, context.getStackLimit().opt); + + //TODO Copied from elsewhere. May be worthwhile to factor out the common parts. + // currently active LM + BlockLevelLayoutManager curLM; + BlockLevelLayoutManager prevLM = null; + MinOptMax stackSize = new MinOptMax(); + LinkedList returnedList; + LinkedList returnList = new LinkedList(); + + while ((curLM = ((BlockLevelLayoutManager) getChildLM())) != null) { + if (curLM.generatesInlineAreas()) { + log.error("inline area not allowed under flow - ignoring"); + curLM.setFinished(true); + continue; + } + + // Set up a LayoutContext + MinOptMax bpd = context.getStackLimit(); + BreakPoss bp; + bp = null; + + LayoutContext childLC = new LayoutContext(0); + boolean breakPage = false; + childLC.setStackLimit(MinOptMax.subtract(bpd, stackSize)); + childLC.setRefIPD(context.getRefIPD()); + + // get elements from curLM + returnedList = curLM.getNextKnuthElements(childLC, alignment); +/*LF*/ //System.out.println("FLM.getNextKnuthElements> returnedList.size() = " + returnedList.size()); + + // "wrap" the Position inside each element + LinkedList tempList = returnedList; + KnuthElement tempElement; + returnedList = new LinkedList(); + ListIterator listIter = tempList.listIterator(); + while (listIter.hasNext()) { + tempElement = (KnuthElement)listIter.next(); + tempElement.setPosition(new NonLeafPosition(this, tempElement.getPosition())); + returnedList.add(tempElement); + } + + if (returnedList.size() == 1 + && ((KnuthElement)returnedList.getFirst()).isPenalty() + && ((KnuthPenalty)returnedList.getFirst()).getP() == -KnuthElement.INFINITE) { + // a descendant of this flow has break-before + returnList.addAll(returnedList); + return returnList; + } else { + if (returnList.size() > 0) { + // there is a block before this one + if (prevLM.mustKeepWithNext() + || curLM.mustKeepWithPrevious()) { + // add an infinite penalty to forbid a break between blocks + returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, new Position(this), false)); + } else if (!((KnuthElement) returnList.getLast()).isGlue()) { + // add a null penalty to allow a break between blocks + returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false)); + } + } +/*LF*/ if (returnedList.size() > 0) { // controllare! + returnList.addAll(returnedList); + if (((KnuthElement)returnedList.getLast()).isPenalty() + && ((KnuthPenalty)returnedList.getLast()).getP() == -KnuthElement.INFINITE) { + // a descendant of this flow has break-after +/*LF*/ //System.out.println("FLM - break after!!"); + return returnList; + } +/*LF*/ } + } + prevLM = curLM; + } + + setFinished(true); + + if (returnList.size() > 0) { + return returnList; + } else { + return null; + } + } + /** * @see org.apache.fop.layoutmgr.LayoutManager#getNextBreakPoss(LayoutContext) */ @@ -86,7 +186,9 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager { * @see org.apache.fop.layoutmgr.LayoutManager#addAreas(PositionIterator, LayoutContext) */ public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { + AreaAdditionUtil.addAreas(parentIter, layoutContext); + /* LayoutManager childLM; int iStartPos = 0; LayoutContext lc = new LayoutContext(0); @@ -103,8 +205,9 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager { } blockBreaks.clear(); + */ flush(); - region = null; + targetRegion = null; } @@ -115,25 +218,106 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager { * @see org.apache.fop.layoutmgr.LayoutManager#addChildArea(Area) */ public void addChildArea(Area childArea) { - region.addBlock((Block)childArea); + targetRegion.addBlock((Block)childArea); } /** * @see org.apache.fop.layoutmgr.LayoutManager#getParentArea(Area) */ public Area getParentArea(Area childArea) { - return region; + return targetRegion; } - /** - * Markers are not allowed in static areas so this reports an - * error and does nothing. - * - * @see org.apache.fop.layoutmgr.LayoutManager - */ - public void addMarker(Map marks, boolean start, boolean isfirst) { - // error markers not allowed in static - log.error("Cannot add marker to static areas"); + public void doLayout(SideRegion region) { + MinOptMax range = new MinOptMax(targetRegion.getIPD()); + StaticContentBreaker breaker = new StaticContentBreaker(region, this, range); + breaker.doLayout(targetRegion.getBPD()); + if (breaker.isOverflow()) { + if (region.getOverflow() == EN_ERROR_IF_OVERFLOW) { + //TODO throw layout exception + } + log.warn("static-content overflows the available area."); + } } + + private class StaticContentBreaker extends AbstractBreaker { + + private Region region; + private StaticContentLayoutManager lm; + private MinOptMax ipd; + boolean overflow = false; + + public StaticContentBreaker(Region region, StaticContentLayoutManager lm, MinOptMax ipd) { + this.region = region; + this.lm = lm; + this.ipd = ipd; + } + + public boolean isOverflow() { + return this.overflow; + } + + protected LayoutManager getTopLevelLM() { + return lm; + } + + protected LayoutContext createLayoutContext() { + LayoutContext lc = super.createLayoutContext(); + lc.setRefIPD(ipd.opt); + return lc; + } + + protected LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + LayoutManager curLM; // currently active LM + LinkedList returnList = new LinkedList(); + + while ((curLM = getChildLM()) != null) { + LayoutContext childLC = new LayoutContext(0); + childLC.setStackLimit(context.getStackLimit()); + childLC.setRefIPD(context.getRefIPD()); + + LinkedList returnedList = null; + if (!curLM.isFinished()) { + returnedList = curLM.getNextKnuthElements(childLC, alignment); + } + if (returnedList != null) { + lm.wrapPositionElements(returnedList, returnList); + //returnList.addAll(returnedList); + } + } + setFinished(true); + return returnList; + } + + protected int getCurrentDisplayAlign() { + return region.getDisplayAlign(); + } + + protected boolean hasMoreContent() { + return !lm.isFinished(); + } + + protected void addAreas(PositionIterator posIter, LayoutContext context) { + AreaAdditionUtil.addAreas(posIter, context); + } + + protected void doPhase3(PageBreakingAlgorithm alg, int partCount, + BlockSequence originalList, BlockSequence effectiveList) { + //Directly add areas after finding the breaks + addAreas(alg, partCount, originalList, effectiveList); + if (partCount > 1) { + overflow = true; + } + } + + protected void finishPart() { + //nop for static content + } + + protected LayoutManager getCurrentChildLM() { + return null; //TODO NYI + } + + } } diff --git a/src/java/org/apache/fop/layoutmgr/TextLayoutManager.java b/src/java/org/apache/fop/layoutmgr/TextLayoutManager.java index 4d5fe4130..13d6d89ad 100644 --- a/src/java/org/apache/fop/layoutmgr/TextLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/TextLayoutManager.java @@ -37,8 +37,7 @@ import org.apache.fop.traits.MinOptMax; * LayoutManager for text (a sequence of characters) which generates one * or more inline areas. */ -public class TextLayoutManager extends AbstractLayoutManager - implements InlineLevelLayoutManager { +public class TextLayoutManager extends LeafNodeLayoutManager { /** * Store information about each potential text area. @@ -131,6 +130,7 @@ public class TextLayoutManager extends AbstractLayoutManager * @param node The FOText object to be rendered */ public TextLayoutManager(FOText node) { + super(); foText = node; textArray = new char[node.endIndex - node.startIndex]; @@ -645,10 +645,10 @@ public class TextLayoutManager extends AbstractLayoutManager textArea.setOffset(context.getMiddleBaseline() + fs.getXHeight() / 2); break; case EN_TOP: - textArea.setOffset(fs.getAscender()); + textArea.setOffset(context.getTopBaseline() + fs.getAscender()); break; case EN_BOTTOM: - textArea.setOffset(context.getLineHeight() - bpd + fs.getAscender()); + textArea.setOffset(context.getBottomBaseline() - bpd + fs.getAscender()); break; case EN_BASELINE: default: @@ -706,7 +706,7 @@ public class TextLayoutManager extends AbstractLayoutManager - 6 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, new LeafPosition(this, -1), true)); returnList.add - (new KnuthBox(0, 0, 0, 0, + (new KnuthInlineBox(0, 0, 0, 0, new LeafPosition(this, -1), true)); returnList.add (new KnuthPenalty(0, KnuthElement.INFINITE, false, @@ -724,14 +724,14 @@ public class TextLayoutManager extends AbstractLayoutManager (short) 1, (short) 0, wordSpaceIPD, false)); returnList.add - (new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, + (new KnuthGlue(0, 3 * wordSpaceIPD.opt, 0, new LeafPosition(this, vecAreaInfo.size() - 1), false)); returnList.add (new KnuthPenalty(0, 0, false, new LeafPosition(this, -1), true)); returnList.add (new KnuthGlue(wordSpaceIPD.opt, - - 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, + - 3 * wordSpaceIPD.opt, 0, new LeafPosition(this, -1), true)); iNextStart ++; break; @@ -791,42 +791,64 @@ public class TextLayoutManager extends AbstractLayoutManager for (; iTempStart < textArray.length && textArray[iTempStart] != SPACE && textArray[iTempStart] != NBSPACE - && textArray[iTempStart] != NEWLINE; + && textArray[iTempStart] != NEWLINE + && !(iTempStart > iNextStart + && alignment == EN_JUSTIFY + && BREAK_CHARS.indexOf(textArray[iTempStart - 1]) >= 0); iTempStart++) { wordIPD.add(fs.getCharWidth(textArray[iTempStart])); } - wordIPD.add(MinOptMax.multiply(letterSpaceIPD, (iTempStart - iThisStart - 1))); + int iLetterSpaces = iTempStart - iThisStart - 1; + wordIPD.add(MinOptMax.multiply(letterSpaceIPD, iLetterSpaces)); vecAreaInfo.add (new AreaInfo(iThisStart, iTempStart, (short) 0, - (short) (iTempStart - iThisStart - 1), + (short) iLetterSpaces, wordIPD, false)); if (letterSpaceIPD.min == letterSpaceIPD.max) { // constant letter space; simply return a box // whose width includes letter spaces returnList.add - (new KnuthBox(wordIPD.opt, lead, total, middle, + (new KnuthInlineBox(wordIPD.opt, lead, total, middle, new LeafPosition(this, vecAreaInfo.size() - 1), false)); - iNextStart = iTempStart; } else { // adjustable letter space; // some other KnuthElements are needed returnList.add - (new KnuthBox(wordIPD.opt - (iTempStart - iThisStart - 1) * letterSpaceIPD.opt, + (new KnuthInlineBox(wordIPD.opt - iLetterSpaces * letterSpaceIPD.opt, lead, total, middle, new LeafPosition(this, vecAreaInfo.size() - 1), false)); returnList.add (new KnuthPenalty(0, KnuthElement.INFINITE, false, new LeafPosition(this, -1), true)); returnList.add - (new KnuthGlue((iTempStart - iThisStart - 1) * letterSpaceIPD.opt, - (iTempStart - iThisStart - 1) * (letterSpaceIPD.max - letterSpaceIPD.opt), - (iTempStart - iThisStart - 1) * (letterSpaceIPD.opt - letterSpaceIPD.min), + (new KnuthGlue(iLetterSpaces * letterSpaceIPD.opt, + iLetterSpaces * (letterSpaceIPD.max - letterSpaceIPD.opt), + iLetterSpaces * (letterSpaceIPD.opt - letterSpaceIPD.min), new LeafPosition(this, -1), true)); returnList.add - (new KnuthBox(0, lead, total, middle, - new LeafPosition(this, -1), true)); - iNextStart = iTempStart; + (new KnuthInlineBox(0, lead, total, middle, + new LeafPosition(this, -1), true)); } + // if the last character is '-' or '/', it could be used as a line end; + // add a flagged penalty element and glue element representing a suppressible + // letter space if the next character is not a space + if (BREAK_CHARS.indexOf(textArray[iTempStart - 1]) >= 0 + && iTempStart < textArray.length + && textArray[iTempStart] != SPACE + && textArray[iTempStart] != NBSPACE) { + returnList.add + (new KnuthPenalty(0, KnuthPenalty.FLAGGED_PENALTY, true, + new LeafPosition(this, -1), false)); + returnList.add + (new KnuthGlue(letterSpaceIPD.opt, + letterSpaceIPD.max - letterSpaceIPD.opt, + letterSpaceIPD.opt - letterSpaceIPD.min, + new LeafPosition(this, -1), false)); + // update the information in the AreaInfo, adding one more letter space + AreaInfo ai = (AreaInfo) vecAreaInfo.get(vecAreaInfo.size() - 1); + ai.iLScount ++; + } + iNextStart = iTempStart; } } // end of while setFinished(true); @@ -837,19 +859,38 @@ public class TextLayoutManager extends AbstractLayoutManager } } - public KnuthElement addALetterSpaceTo(KnuthElement element) { - LeafPosition pos = (LeafPosition) element.getPosition(); + public List addALetterSpaceTo(List oldList) { + // old list contains only a box, or the sequence: box penalty glue box; + // look at the Position stored in the first element in oldList + // which is always a box + ListIterator oldListIterator = oldList.listIterator(); + LeafPosition pos = (LeafPosition) ((KnuthBox) oldListIterator.next()).getPosition(); AreaInfo ai = (AreaInfo) vecAreaInfo.get(pos.getLeafPos()); ai.iLScount ++; ai.ipdArea.add(letterSpaceIPD); - if (letterSpaceIPD.min == letterSpaceIPD.max) { - return new KnuthBox(ai.ipdArea.opt, lead, total, middle, pos, false); + if (BREAK_CHARS.indexOf(textArray[iTempStart - 1]) >= 0) { + // the last character could be used as a line break + // append new elements to oldList + oldListIterator = oldList.listIterator(oldList.size()); + oldListIterator.add(new KnuthPenalty(0, KnuthPenalty.FLAGGED_PENALTY, true, + new LeafPosition(this, -1), false)); + oldListIterator.add(new KnuthGlue(letterSpaceIPD.opt, + letterSpaceIPD.max - letterSpaceIPD.opt, + letterSpaceIPD.opt - letterSpaceIPD.min, + new LeafPosition(this, -1), false)); + } else if (letterSpaceIPD.min == letterSpaceIPD.max) { + // constant letter space: replace the box + oldListIterator.set(new KnuthInlineBox(ai.ipdArea.opt, lead, total, middle, pos, false)); } else { - return new KnuthGlue(ai.iLScount * letterSpaceIPD.opt, - ai.iLScount * (letterSpaceIPD.max - letterSpaceIPD.opt), - ai.iLScount * (letterSpaceIPD.opt - letterSpaceIPD.min), - new LeafPosition(this, -1), true); + // adjustable letter space: replace the glue + oldListIterator.next(); // this would return the penalty element + oldListIterator.next(); // this would return the glue element + oldListIterator.set(new KnuthGlue(ai.iLScount * letterSpaceIPD.opt, + ai.iLScount * (letterSpaceIPD.max - letterSpaceIPD.opt), + ai.iLScount * (letterSpaceIPD.opt - letterSpaceIPD.min), + new LeafPosition(this, -1), true)); } + return oldList; } public void hyphenate(Position pos, HyphContext hc) { @@ -948,7 +989,7 @@ public class TextLayoutManager extends AbstractLayoutManager } public LinkedList getChangedKnuthElements(List oldList, - int flaggedPenalty, + /*int flaggedPenalty,*/ int alignment) { if (isFinished()) { return null; @@ -960,13 +1001,21 @@ public class TextLayoutManager extends AbstractLayoutManager AreaInfo ai = (AreaInfo) vecAreaInfo.get(iReturnedIndex); if (ai.iWScount == 0) { // ai refers either to a word or a word fragment + + // if the last character is '-' or '/' and the next character is not a space + // one of the letter spaces must be represented using a penalty and a glue, + // and its width must be subtracted + if (BREAK_CHARS.indexOf(textArray[ai.iBreakIndex - 1]) >= 0 + && ai.iLScount == (ai.iBreakIndex - ai.iStartIndex)) { + ai.ipdArea.add(new MinOptMax(-letterSpaceIPD.min, -letterSpaceIPD.opt, -letterSpaceIPD.max)); + } if (letterSpaceIPD.min == letterSpaceIPD.max) { returnList.add - (new KnuthBox(ai.ipdArea.opt, lead, total, middle, + (new KnuthInlineBox(ai.ipdArea.opt, lead, total, middle, new LeafPosition(this, iReturnedIndex), false)); } else { returnList.add - (new KnuthBox(ai.ipdArea.opt + (new KnuthInlineBox(ai.ipdArea.opt - ai.iLScount * letterSpaceIPD.opt, lead, total, middle, new LeafPosition(this, iReturnedIndex), false)); @@ -979,17 +1028,37 @@ public class TextLayoutManager extends AbstractLayoutManager ai.iLScount * (letterSpaceIPD.opt - letterSpaceIPD.min), new LeafPosition(this, -1), true)); returnList.add - (new KnuthBox(0, 0, 0, 0, + (new KnuthInlineBox(0, 0, 0, 0, new LeafPosition(this, -1), true)); } if (ai.bHyphenated) { returnList.add - (new KnuthPenalty(hyphIPD, flaggedPenalty, true, + (new KnuthPenalty(hyphIPD, KnuthPenalty.FLAGGED_PENALTY, true, new LeafPosition(this, -1), false)); } + // if the last character is '-' or '/', it could be used as a line end; + // add a flagged penalty element and a glue element representing a suppressible + // letter space if the next character is not a space + if (BREAK_CHARS.indexOf(textArray[ai.iBreakIndex - 1]) >= 0 + && ai.iLScount == (ai.iBreakIndex - ai.iStartIndex)) { + returnList.add + (new KnuthPenalty(0, KnuthPenalty.FLAGGED_PENALTY, true, + new LeafPosition(this, -1), false)); + returnList.add + (new KnuthGlue(letterSpaceIPD.opt, + letterSpaceIPD.max - letterSpaceIPD.opt, + letterSpaceIPD.opt - letterSpaceIPD.min, + new LeafPosition(this, -1), false)); + } iReturnedIndex ++; } else { // ai refers to a space + if (textArray[ai.iStartIndex] == NBSPACE) { + returnList.add + (new KnuthPenalty(0, KnuthElement.INFINITE, false, + new LeafPosition(this, -1), + false)); + } switch (alignment) { case EN_CENTER : returnList.add @@ -1003,7 +1072,7 @@ public class TextLayoutManager extends AbstractLayoutManager - 6 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, new LeafPosition(this, -1), true)); returnList.add - (new KnuthBox(0, 0, 0, 0, + (new KnuthInlineBox(0, 0, 0, 0, new LeafPosition(this, -1), true)); returnList.add (new KnuthPenalty(0, KnuthElement.INFINITE, false, diff --git a/src/java/org/apache/fop/layoutmgr/list/Item.java b/src/java/org/apache/fop/layoutmgr/list/Item.java index ef7403c54..64089245f 100644 --- a/src/java/org/apache/fop/layoutmgr/list/Item.java +++ b/src/java/org/apache/fop/layoutmgr/list/Item.java @@ -27,14 +27,16 @@ import org.apache.fop.layoutmgr.LeafPosition; import org.apache.fop.layoutmgr.BreakPoss; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.BreakPossPosIter; import org.apache.fop.layoutmgr.Position; +import org.apache.fop.layoutmgr.NonLeafPosition; import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.traits.MinOptMax; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.LinkedList; /** * LayoutManager for a table-cell FO. @@ -50,6 +52,20 @@ public class Item extends BlockStackingLayoutManager { private int xoffset; private int itemIPD; + private static class StackingIter extends PositionIterator { + StackingIter(Iterator parentIter) { + super(parentIter); + } + + protected LayoutManager getLM(Object nextObj) { + return ((Position) nextObj).getLM(); + } + + protected Position getPos(Object nextObj) { + return ((Position) nextObj); + } + } + /** * Create a new Cell layout manager. */ @@ -152,6 +168,11 @@ public class Item extends BlockStackingLayoutManager { xoffset = off; } + public LinkedList getChangedKnuthElements(List oldList, int alignment) { + //log.debug(" Item.getChanged>"); + return super.getChangedKnuthElements(oldList, alignment); + } + /** * Add the areas for the break points. * The list item contains block stacking layout managers @@ -166,26 +187,43 @@ public class Item extends BlockStackingLayoutManager { int nameId = fobj.getNameId(); if (nameId == FO_LIST_ITEM_LABEL) { - addID(((ListItemLabel) fobj).getId()); + getPSLM().addIDToPage(((ListItemLabel) fobj).getId()); } else if (nameId == FO_LIST_ITEM_BODY) { - addID(((ListItemBody) fobj).getId()); + getPSLM().addIDToPage(((ListItemBody) fobj).getId()); } - LayoutManager childLM; - int iStartPos = 0; + LayoutManager childLM = null; LayoutContext lc = new LayoutContext(0); + LayoutManager firstLM = null; + LayoutManager lastLM = null; + + // "unwrap" the NonLeafPositions stored in parentIter + // and put them in a new list; + LinkedList positionList = new LinkedList(); + Position pos; while (parentIter.hasNext()) { - LeafPosition lfp = (LeafPosition) parentIter.next(); - // Add the block areas to Area - PositionIterator breakPosIter = - new BreakPossPosIter(childBreaks, iStartPos, - lfp.getLeafPos() + 1); - iStartPos = lfp.getLeafPos() + 1; - while ((childLM = breakPosIter.getNextChildLM()) != null) { - childLM.addAreas(breakPosIter, lc); + pos = (Position)parentIter.next(); + if (pos instanceof NonLeafPosition) { + // pos was created by a child of this ListBlockLM + positionList.add(((NonLeafPosition) pos).getPosition()); + lastLM = ((NonLeafPosition) pos).getPosition().getLM(); + if (firstLM == null) { + firstLM = lastLM; + } + } else { + // pos was created by this ListBlockLM, so it must be ignored } } + StackingIter childPosIter = new StackingIter(positionList.listIterator()); + while ((childLM = childPosIter.getNextChildLM()) != null) { + // Add the block areas to Area + lc.setFlags(LayoutContext.FIRST_AREA, childLM == firstLM); + lc.setFlags(LayoutContext.LAST_AREA, childLM == lastLM); + lc.setStackLimit(layoutContext.getStackLimit()); + childLM.addAreas(childPosIter, lc); + } + /* if (borderProps != null) { TraitSetter.addBorders(curBlockArea, borderProps); diff --git a/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java index 48d78f875..d57bef99f 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java @@ -25,8 +25,8 @@ import org.apache.fop.layoutmgr.LeafPosition; import org.apache.fop.layoutmgr.BreakPoss; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.BreakPossPosIter; import org.apache.fop.layoutmgr.Position; +import org.apache.fop.layoutmgr.NonLeafPosition; import org.apache.fop.layoutmgr.TraitSetter; import org.apache.fop.area.Area; import org.apache.fop.area.Block; @@ -34,6 +34,8 @@ import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.SpaceVal; import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; /** @@ -46,14 +48,26 @@ public class ListBlockLayoutManager extends BlockStackingLayoutManager { private Block curBlockArea; - private int referenceIPD = 0; - private List bodyBreaks = new ArrayList(); //TODO space-before|after: handle space-resolution rules private MinOptMax spaceBefore; private MinOptMax spaceAfter; + private static class StackingIter extends PositionIterator { + StackingIter(Iterator parentIter) { + super(parentIter); + } + + protected LayoutManager getLM(Object nextObj) { + return ((Position) nextObj).getLM(); + } + + protected Position getPos(Object nextObj) { + return ((Position) nextObj); + } + } + /* private class SectionPosition extends LeafPosition { protected List list; @@ -64,7 +78,7 @@ public class ListBlockLayoutManager extends BlockStackingLayoutManager { }*/ /** - * Create a new table layout manager. + * Create a new list block layout manager. * @param node list-block to create the layout manager for */ public ListBlockLayoutManager(ListBlock node) { @@ -166,6 +180,11 @@ public class ListBlockLayoutManager extends BlockStackingLayoutManager { return null; } + public LinkedList getChangedKnuthElements(List oldList, int alignment) { + //log.debug("LBLM.getChangedKnuthElements>"); + return super.getChangedKnuthElements(oldList, alignment); + } + /** * The table area is a reference area that contains areas for * columns, bodies, rows and the contents are in cells. @@ -182,26 +201,40 @@ public class ListBlockLayoutManager extends BlockStackingLayoutManager { addBlockSpacing(adjust, spaceBefore); spaceBefore = null; - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); // the list block contains areas stacked from each list item - //int listHeight = 0; - - LayoutManager childLM; - int iStartPos = 0; + LayoutManager childLM = null; LayoutContext lc = new LayoutContext(0); + LayoutManager firstLM = null; + LayoutManager lastLM = null; + + // "unwrap" the NonLeafPositions stored in parentIter + // and put them in a new list; + LinkedList positionList = new LinkedList(); + Position pos; while (parentIter.hasNext()) { - LeafPosition lfp = (LeafPosition) parentIter.next(); - // Add the block areas to Area - PositionIterator breakPosIter = new BreakPossPosIter( - bodyBreaks, iStartPos, lfp.getLeafPos() + 1); - iStartPos = lfp.getLeafPos() + 1; - while ((childLM = (LayoutManager)breakPosIter.getNextChildLM()) != null) { - childLM.addAreas(breakPosIter, lc); + pos = (Position)parentIter.next(); + if (pos instanceof NonLeafPosition + && ((NonLeafPosition) pos).getPosition().getLM() != this) { + // pos was created by a child of this ListBlockLM + positionList.add(((NonLeafPosition) pos).getPosition()); + lastLM = ((NonLeafPosition) pos).getPosition().getLM(); + if (firstLM == null) { + firstLM = lastLM; + } } } + StackingIter childPosIter = new StackingIter(positionList.listIterator()); + while ((childLM = childPosIter.getNextChildLM()) != null) { + // Add the block areas to Area + lc.setFlags(LayoutContext.FIRST_AREA, childLM == firstLM); + lc.setFlags(LayoutContext.LAST_AREA, childLM == lastLM); + lc.setStackLimit(layoutContext.getStackLimit()); + childLM.addAreas(childPosIter, lc); + } flush(); diff --git a/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java index 1d2038067..36da5f68d 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java @@ -27,17 +27,22 @@ import org.apache.fop.layoutmgr.LeafPosition; import org.apache.fop.layoutmgr.BreakPoss; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.BreakPossPosIter; import org.apache.fop.layoutmgr.Position; +import org.apache.fop.layoutmgr.NonLeafPosition; import org.apache.fop.layoutmgr.TraitSetter; +import org.apache.fop.layoutmgr.KnuthElement; +import org.apache.fop.layoutmgr.KnuthBox; +import org.apache.fop.layoutmgr.KnuthPenalty; +import org.apache.fop.layoutmgr.KnuthPossPosIter; import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.SpaceVal; -import java.util.Iterator; import java.util.ArrayList; import java.util.List; +import java.util.LinkedList; +import java.util.ListIterator; /** * LayoutManager for a list-item FO. @@ -49,11 +54,11 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager { private Item label; private Item body; - private int referenceIPD = 0; - private Block curBlockArea = null; - //private List cellList = null; + private LinkedList labelList = null; + private LinkedList bodyList = null; + private int listItemHeight; //TODO space-before|after: handle space-resolution rules @@ -68,6 +73,38 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager { } } + private class ListItemPosition extends Position { + private int iLabelFirstIndex; + private int iLabelLastIndex; + private int iBodyFirstIndex; + private int iBodyLastIndex; + + public ListItemPosition(LayoutManager lm, int labelFirst, int labelLast, + int bodyFirst, int bodyLast) { + super(lm); + iLabelFirstIndex = labelFirst; + iLabelLastIndex = labelLast; + iBodyFirstIndex = bodyFirst; + iBodyLastIndex = bodyLast; + } + + public int getLabelFirstIndex() { + return iLabelFirstIndex; + } + + public int getLabelLastIndex() { + return iLabelLastIndex; + } + + public int getBodyFirstIndex() { + return iBodyFirstIndex; + } + + public int getBodyLastIndex() { + return iBodyLastIndex; + } + } + /** * Create a new list item layout manager. * @param node list-item to create the layout manager for @@ -226,6 +263,213 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager { return breakPoss; } + /** + * @see org.apache.fop.layoutmgr.LayoutManager#getNextKnuthElements(org.apache.fop.layoutmgr.LayoutContext, int) + */ + public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + referenceIPD = context.getRefIPD(); + + // label + labelList = label.getNextKnuthElements(context, alignment); + + // body + bodyList = body.getNextKnuthElements(context, alignment); + + // create a combined list + LinkedList returnedList = getCombinedKnuthElementsForListItem(labelList, bodyList); + + // "wrap" the Position inside each element + LinkedList tempList = returnedList; + KnuthElement tempElement; + returnedList = new LinkedList(); + ListIterator listIter = tempList.listIterator(); + while (listIter.hasNext()) { + tempElement = (KnuthElement)listIter.next(); + tempElement.setPosition(new NonLeafPosition(this, tempElement.getPosition())); + returnedList.add(tempElement); + } + + setFinished(true); + return returnedList; + } + + private LinkedList getCombinedKnuthElementsForListItem(LinkedList labelElements, + LinkedList bodyElements) { + //Copy elements to array lists to improve element access performance + List[] elementLists = {new ArrayList(labelElements), + new ArrayList(bodyElements)}; + int[] fullHeights = {calcItemHeightFromContents(elementLists[0]), + calcItemHeightFromContents(elementLists[1])}; + int[] partialHeights = {0, 0}; + int[] start = {-1, -1}; + int[] end = {-1, -1}; + + int totalHeight = Math.max(fullHeights[0], fullHeights[1]); + int step; + int addedBoxHeight = 0; + + LinkedList returnList = new LinkedList(); + while ((step = getNextStep(elementLists, start, end, partialHeights)) + > 0) { + // compute penalty height and box height + int penaltyHeight = step + + getMaxRemainingHeight(fullHeights, partialHeights) + - totalHeight; + int boxHeight = step - addedBoxHeight - penaltyHeight; + + // add the new elements + addedBoxHeight += boxHeight; + ListItemPosition stepPosition = new ListItemPosition(this, + start[0], end[0], start[1], end[1]); + returnList.add(new KnuthBox(boxHeight, stepPosition, false)); + if (addedBoxHeight < totalHeight) { + returnList.add(new KnuthPenalty(penaltyHeight, 0, false, stepPosition, false)); + } + } + + return returnList; + } + + private int calcItemHeightFromContents(List elements, int start, int end) { + ListIterator iter = elements.listIterator(start); + int count = end - start + 1; + int len = 0; + while (iter.hasNext()) { + KnuthElement el = (KnuthElement)iter.next(); + if (el.isBox()) { + len += el.getW(); + } else if (el.isGlue()) { + len += el.getW(); + } else { + log.debug("Ignoring penalty: " + el); + //ignore penalties + } + count--; + if (count == 0) { + break; + } + } + return len; + } + + private int calcItemHeightFromContents(List elements) { + return calcItemHeightFromContents(elements, 0, elements.size() - 1); + } + + private int getNextStep(List[] elementLists, int[] start, int[] end, int[] partialHeights) { + // backup of partial heights + int[] backupHeights = {partialHeights[0], partialHeights[1]}; + + // set starting points + start[0] = end[0] + 1; + start[1] = end[1] + 1; + + // get next possible sequence for label and body + int seqCount = 0; + for (int i = 0; i < start.length; i++) { + while (end[i] + 1 < elementLists[i].size()) { + end[i]++; + KnuthElement el = (KnuthElement)elementLists[i].get(end[i]); + if (el.isPenalty()) { + if (el.getP() < KnuthElement.INFINITE) { + //First legal break point + break; + } + } else if (el.isGlue()) { + KnuthElement prev = (KnuthElement)elementLists[i].get(end[i] - 1); + if (prev.isBox()) { + //Second legal break point + break; + } + partialHeights[i] += el.getW(); + } else { + partialHeights[i] += el.getW(); + } + } + if (end[i] < start[i]) { + partialHeights[i] = backupHeights[i]; + } else { + seqCount++; + } + } + if (seqCount == 0) { + return 0; + } + + // determine next step + int step; + if (backupHeights[0] == 0 && backupHeights[1] == 0) { + // this is the first step: choose the maximum increase, so that + // the smallest area in the first page will contain at least + // a label area and a body area + step = Math.max((end[0] >= start[0] ? partialHeights[0] : Integer.MIN_VALUE), + (end[1] >= start[1] ? partialHeights[1] : Integer.MIN_VALUE)); + } else { + // this is not the first step: choose the minimum increase + step = Math.min((end[0] >= start[0] ? partialHeights[0] : Integer.MAX_VALUE), + (end[1] >= start[1] ? partialHeights[1] : Integer.MAX_VALUE)); + } + + // reset bigger-than-step sequences + for (int i = 0; i < partialHeights.length; i++) { + if (partialHeights[i] > step) { + partialHeights[i] = backupHeights[i]; + end[i] = start[i] - 1; + } + } + + return step; + } + + private int getMaxRemainingHeight(int[] fullHeights, int[] partialHeights) { + return Math.max(fullHeights[0] - partialHeights[0], + fullHeights[1] - partialHeights[1]); + } + + /** + * @see org.apache.fop.layoutmgr.LayoutManager#getChangedKnuthElements(java.util.List, int) + */ + public LinkedList getChangedKnuthElements(List oldList, int alignment) { +/*LF*/ //log.debug(" LILM.getChanged> label"); + // label + labelList = label.getChangedKnuthElements(labelList, alignment); + +/*LF*/ //log.debug(" LILM.getChanged> body"); + // body + // "unwrap" the Positions stored in the elements + ListIterator oldListIterator = oldList.listIterator(); + KnuthElement oldElement = null; + while (oldListIterator.hasNext()) { + oldElement = (KnuthElement)oldListIterator.next(); + Position innerPosition = ((NonLeafPosition) oldElement.getPosition()).getPosition(); +/*LF*/ //System.out.println(" BLM> unwrapping: " + (oldElement.isBox() ? "box " : (oldElement.isGlue() ? "glue " : "penalty")) + " creato da " + oldElement.getLayoutManager().getClass().getName()); +/*LF*/ //System.out.println(" BLM> unwrapping: " + oldElement.getPosition().getClass().getName()); + if (innerPosition != null) { + // oldElement was created by a descendant of this BlockLM + oldElement.setPosition(innerPosition); + } else { + // thisElement was created by this BlockLM + // modify its position in order to recognize it was not created + // by a child + oldElement.setPosition(new Position(this)); + } + } + + LinkedList returnedList = body.getChangedKnuthElements(oldList, alignment); + // "wrap" the Position inside each element + LinkedList tempList = returnedList; + KnuthElement tempElement; + returnedList = new LinkedList(); + ListIterator listIter = tempList.listIterator(); + while (listIter.hasNext()) { + tempElement = (KnuthElement)listIter.next(); + tempElement.setPosition(new NonLeafPosition(this, tempElement.getPosition())); + returnedList.add(tempElement); + } + + return returnedList; + } + /** * Add the areas for the break points. * This sets the offset of each cell as it is added. @@ -242,26 +486,62 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager { addBlockSpacing(adjust, spaceBefore); spaceBefore = null; - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); - Item childLM; LayoutContext lc = new LayoutContext(0); + + // "unwrap" the NonLeafPositions stored in parentIter + LinkedList positionList = new LinkedList(); + Position pos; while (parentIter.hasNext()) { - ItemPosition lfp = (ItemPosition) parentIter.next(); - // Add the block areas to Area + pos = (Position) parentIter.next(); + if (pos instanceof NonLeafPosition) { + // pos contains a ListItemPosition created by this ListBlockLM + positionList.add(((NonLeafPosition) pos).getPosition()); + } + } - for (Iterator iter = lfp.cellBreaks.iterator(); iter.hasNext();) { - List cellsbr = (List)iter.next(); - PositionIterator breakPosIter; - breakPosIter = new BreakPossPosIter(cellsbr, 0, cellsbr.size()); + // use the first and the last ListItemPosition to determine the + // corresponding indexes in the original labelList and bodyList + int labelFirstIndex = ((ListItemPosition) positionList.getFirst()).getLabelFirstIndex(); + int labelLastIndex = ((ListItemPosition) positionList.getLast()).getLabelLastIndex(); + int bodyFirstIndex = ((ListItemPosition) positionList.getFirst()).getBodyFirstIndex(); + int bodyLastIndex = ((ListItemPosition) positionList.getLast()).getBodyLastIndex(); + + // add label areas + if (labelFirstIndex <= labelLastIndex) { + KnuthPossPosIter labelIter = new KnuthPossPosIter(labelList, + labelFirstIndex, labelLastIndex + 1); + lc.setFlags(LayoutContext.FIRST_AREA, layoutContext.isFirstArea()); + lc.setFlags(LayoutContext.LAST_AREA, layoutContext.isLastArea()); + // TO DO: use the right stack limit for the label + lc.setStackLimit(layoutContext.getStackLimit()); + label.addAreas(labelIter, lc); + } - while ((childLM = (Item)breakPosIter.getNextChildLM()) != null) { - childLM.addAreas(breakPosIter, lc); - } - } + // reset the area bpd after adding the label areas and before adding the body areas + int savedBPD = 0; + if (labelFirstIndex <= labelLastIndex + && bodyFirstIndex <= bodyLastIndex) { + savedBPD = curBlockArea.getBPD(); + curBlockArea.setBPD(0); + } + + // add body areas + if (bodyFirstIndex <= bodyLastIndex) { + KnuthPossPosIter bodyIter = new KnuthPossPosIter(bodyList, + bodyFirstIndex, bodyLastIndex + 1); + lc.setFlags(LayoutContext.FIRST_AREA, layoutContext.isFirstArea()); + lc.setFlags(LayoutContext.LAST_AREA, layoutContext.isLastArea()); + // TO DO: use the right stack limit for the body + lc.setStackLimit(layoutContext.getStackLimit()); + body.addAreas(bodyIter, lc); } - curBlockArea.setBPD(listItemHeight); + // after adding body areas, set the maximum area bpd + if (curBlockArea.getBPD() < savedBPD) { + curBlockArea.setBPD(savedBPD); + } flush(); diff --git a/src/java/org/apache/fop/layoutmgr/table/Body.java b/src/java/org/apache/fop/layoutmgr/table/Body.java deleted file mode 100644 index dbdf10b0a..000000000 --- a/src/java/org/apache/fop/layoutmgr/table/Body.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 1999-2005 The Apache Software Foundation. - * - * Licensed 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.layoutmgr.table; - -import java.util.List; - -import org.apache.fop.fo.flow.TableBody; -import org.apache.fop.layoutmgr.LayoutManager; -import org.apache.fop.layoutmgr.BlockStackingLayoutManager; -import org.apache.fop.layoutmgr.LeafPosition; -import org.apache.fop.layoutmgr.BreakPoss; -import org.apache.fop.layoutmgr.LayoutContext; -import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.BreakPossPosIter; -import org.apache.fop.layoutmgr.Position; -import org.apache.fop.layoutmgr.TraitSetter; -import org.apache.fop.area.Area; -import org.apache.fop.area.Block; -import org.apache.fop.traits.MinOptMax; - -/** - * LayoutManager for a table-header, table-footer and table body FO. - * These fo objects have either rows or cells underneath. - * Cells are organised into rows. - */ -public class Body extends BlockStackingLayoutManager { - private TableBody fobj; - - private List columns; - - private int xoffset; - private int yoffset; - private int bodyHeight; - - //private Block curBlockArea; - - private List childBreaks = new java.util.ArrayList(); - - /** - * Create a new body layout manager. - * @param node the table-body FO - */ - public Body(TableBody node) { - super(node); - fobj = node; - } - - /** @return the table-body|header|footer FO */ - public TableBody getFObj() { - return this.fobj; - } - - /** - * Set the columns from the table. - * - * @param cols the list of columns used for this body - */ - public void setColumns(List cols) { - columns = cols; - } - - /** - * Breaks for this layout manager are of the form of before - * or after a row and inside a row. - * - * @param context the layout context for finding the breaks - * @return the next break possibility - */ - public BreakPoss getNextBreakPoss(LayoutContext context) { - Row curLM; // currently active LM - - MinOptMax stackSize = new MinOptMax(); - BreakPoss lastPos = null; - - while ((curLM = (Row)getChildLM()) != null) { - // Make break positions - // Set up a LayoutContext - int ipd = context.getRefIPD(); - BreakPoss bp; - - LayoutContext childLC = new LayoutContext(0); - childLC.setStackLimit( - MinOptMax.subtract(context.getStackLimit(), - stackSize)); - childLC.setRefIPD(ipd); - - curLM.setColumns(columns); - - boolean over = false; - - while (!curLM.isFinished()) { - if ((bp = curLM.getNextBreakPoss(childLC)) != null) { - if (stackSize.opt + bp.getStackingSize().opt > context.getStackLimit().max) { - // reset to last break - if (lastPos != null) { - LayoutManager lm = lastPos.getLayoutManager(); - lm.resetPosition(lastPos.getPosition()); - if (lm != curLM) { - curLM.resetPosition(null); - } - } else { - curLM.resetPosition(null); - } - over = true; - break; - } - stackSize.add(bp.getStackingSize()); - lastPos = bp; - childBreaks.add(bp); - - if (bp.nextBreakOverflows()) { - over = true; - break; - } - - childLC.setStackLimit(MinOptMax.subtract( - context.getStackLimit(), stackSize)); - } - } - BreakPoss breakPoss = new BreakPoss( - new LeafPosition(this, childBreaks.size() - 1)); - if (over) { - breakPoss.setFlag(BreakPoss.NEXT_OVERFLOWS, true); - } - breakPoss.setStackingSize(stackSize); - return breakPoss; - } - - setFinished(true); - return null; - } - - /** - * Set the x offset of this body within the table. - * This is used to set the row offsets. - * @param off the x offset - */ - public void setXOffset(int off) { - xoffset = off; - } - - /** - * Set the y offset of this body within the table. - * This is used to set the row offsets. - * - * @param off the y offset position - */ - public void setYOffset(int off) { - yoffset = off; - } - - /** - * Add the areas for the break points. - * This sets the offset of each row as it is added. - * - * @param parentIter the position iterator - * @param layoutContext the layout context for adding areas - */ - public void addAreas(PositionIterator parentIter, - LayoutContext layoutContext) { - getParentArea(null); - - Row childLM; - int iStartPos = 0; - LayoutContext lc = new LayoutContext(0); - int rowoffset = 0; - while (parentIter.hasNext()) { - LeafPosition lfp = (LeafPosition) parentIter.next(); - // Add the block areas to Area - PositionIterator breakPosIter - = new BreakPossPosIter(childBreaks, iStartPos, - lfp.getLeafPos() + 1); - iStartPos = lfp.getLeafPos() + 1; - int lastheight = 0; - while ((childLM = (Row)breakPosIter.getNextChildLM()) != null) { - childLM.setXOffset(xoffset); - childLM.setYOffset(yoffset + rowoffset); - childLM.addAreas(breakPosIter, lc); - lastheight = childLM.getRowHeight(); - } - rowoffset += lastheight; - } - bodyHeight = rowoffset; - - flush(); - - childBreaks.clear(); - //curBlockArea = null; - } - - /** - * Get the body height of the body after adjusting. - * Should only be called after adding the body areas. - * - * @return the body height of this body - */ - public int getBodyHeight() { - return bodyHeight; - } - - /** - * Return an Area which can contain the passed childArea. The childArea - * may not yet have any content, but it has essential traits set. - * In general, if the LayoutManager already has an Area it simply returns - * it. Otherwise, it makes a new Area of the appropriate class. - * It gets a parent area for its area by calling its parent LM. - * Finally, based on the dimensions of the parent area, it initializes - * its own area. This includes setting the content IPD and the maximum - * BPD. - * - * @param childArea the child area - * @return the parent are of the child - */ - public Area getParentArea(Area childArea) { - return parentLM.getParentArea(childArea); - } - - /** - * Add the child area. - * The table-header, table-footer and table-body areas return - * the areas return by the children. - * - * @param childArea the child area to add - */ - public void addChildArea(Area childArea) { - parentLM.addChildArea(childArea); - } - - /** - * Reset the position of the layout manager. - * - * @param resetPos the position to reset to - */ - public void resetPosition(Position resetPos) { - if (resetPos == null) { - reset(null); - } - } - - /** - * Create a body area. - * This area has the background and width set. - * - * @return the new body area - */ - public Area createColumnArea() { - Area curBlockArea = new Block(); - - TraitSetter.addBackground(curBlockArea, fobj.getCommonBorderPaddingBackground()); - return curBlockArea; - } - -} - diff --git a/src/java/org/apache/fop/layoutmgr/table/Caption.java b/src/java/org/apache/fop/layoutmgr/table/Caption.java index 0fd99849c..a311270b4 100644 --- a/src/java/org/apache/fop/layoutmgr/table/Caption.java +++ b/src/java/org/apache/fop/layoutmgr/table/Caption.java @@ -138,7 +138,7 @@ public class Caption extends BlockStackingLayoutManager { public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { getParentArea(null); - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); LayoutManager childLM; int iStartPos = 0; diff --git a/src/java/org/apache/fop/layoutmgr/table/Cell.java b/src/java/org/apache/fop/layoutmgr/table/Cell.java index 5a68aeb66..ca8475ba5 100644 --- a/src/java/org/apache/fop/layoutmgr/table/Cell.java +++ b/src/java/org/apache/fop/layoutmgr/table/Cell.java @@ -23,30 +23,39 @@ import org.apache.fop.fo.flow.Table; import org.apache.fop.fo.flow.TableCell; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.LengthRangeProperty; +import org.apache.fop.layoutmgr.AreaAdditionUtil; +import org.apache.fop.layoutmgr.BlockLevelLayoutManager; import org.apache.fop.layoutmgr.BlockStackingLayoutManager; +import org.apache.fop.layoutmgr.KnuthElement; +import org.apache.fop.layoutmgr.KnuthGlue; +import org.apache.fop.layoutmgr.KnuthPenalty; import org.apache.fop.layoutmgr.LayoutManager; import org.apache.fop.layoutmgr.LeafPosition; import org.apache.fop.layoutmgr.BreakPoss; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.MinOptMaxUtil; +import org.apache.fop.layoutmgr.NonLeafPosition; import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.BreakPossPosIter; import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.TraitSetter; import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.area.Trait; import org.apache.fop.traits.MinOptMax; +import org.apache.tools.ant.taskdefs.condition.IsSet; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; /** * LayoutManager for a table-cell FO. * A cell contains blocks. These blocks fill the cell. */ -public class Cell extends BlockStackingLayoutManager { +public class Cell extends BlockStackingLayoutManager implements BlockLevelLayoutManager { + private TableCell fobj; + private PrimaryGridUnit gridUnit; private Block curBlockArea; @@ -65,16 +74,17 @@ public class Cell extends BlockStackingLayoutManager { private int borderAndPaddingBPD; private boolean emptyCell = true; - /** List of Lists containing GridUnit instances, one List per row. */ + /** List of Lists containing OldGridUnit instances, one List per row. */ private List rows = new java.util.ArrayList(); /** * Create a new Cell layout manager. * @node table-cell FO for which to create the LM */ - public Cell(TableCell node) { + public Cell(TableCell node, PrimaryGridUnit pgu) { super(node); fobj = node; + this.gridUnit = pgu; } /** @return the table-cell FO */ @@ -82,6 +92,10 @@ public class Cell extends BlockStackingLayoutManager { return this.fobj; } + private boolean isSeparateBorderModel() { + return fobj.isSeparateBorderModel(); + } + /** * @see org.apache.fop.layoutmgr.AbstractLayoutManager#initProperties() */ @@ -90,7 +104,7 @@ public class Cell extends BlockStackingLayoutManager { borderAndPaddingBPD = 0; borderAndPaddingBPD += fobj.getCommonBorderPaddingBackground().getBorderBeforeWidth(false); borderAndPaddingBPD += fobj.getCommonBorderPaddingBackground().getBorderAfterWidth(false); - if (!fobj.isSeparateBorderModel()) { + if (!isSeparateBorderModel()) { borderAndPaddingBPD /= 2; } borderAndPaddingBPD += fobj.getCommonBorderPaddingBackground().getPaddingBefore(false); @@ -121,22 +135,12 @@ public class Cell extends BlockStackingLayoutManager { private int getIPIndents() { int iIndents = 0; - startBorderWidth = 0; - endBorderWidth = 0; - for (int i = 0; i < rows.size(); i++) { - List gridUnits = (List)rows.get(i); - startBorderWidth = Math.max(startBorderWidth, - ((GridUnit)gridUnits.get(0)). - effBorders.getBorderStartWidth(false)); - endBorderWidth = Math.max(endBorderWidth, - ((GridUnit)gridUnits.get(gridUnits.size() - 1)). - effBorders.getBorderEndWidth(false)); - } - //iIndents += fobj.getCommonBorderPaddingBackground().getBorderStartWidth(false); + int[] startEndBorderWidths = gridUnit.getStartEndBorderWidths(); + startBorderWidth += startEndBorderWidths[0]; + endBorderWidth += startEndBorderWidths[1]; iIndents += startBorderWidth; - //iIndents += fobj.getCommonBorderPaddingBackground().getBorderEndWidth(false); iIndents += endBorderWidth; - if (!fobj.isSeparateBorderModel()) { + if (!isSeparateBorderModel()) { iIndents /= 2; } iIndents += fobj.getCommonBorderPaddingBackground().getPaddingStart(false); @@ -144,6 +148,111 @@ public class Cell extends BlockStackingLayoutManager { return iIndents; } + /** + * @see org.apache.fop.layoutmgr.LayoutManager#getNextKnuthElements(org.apache.fop.layoutmgr.LayoutContext, int) + */ + public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + MinOptMax stackSize = new MinOptMax(); + MinOptMax stackLimit = new MinOptMax(context.getStackLimit()); + + BreakPoss lastPos = null; + + referenceIPD = context.getRefIPD(); + cellIPD = referenceIPD; + cellIPD -= getIPIndents(); + if (isSeparateBorderModel()) { + int borderSep = fobj.getBorderSeparation().getLengthPair() + .getIPD().getLength().getValue(); + cellIPD -= borderSep; + } + + LinkedList returnedList = null; + LinkedList contentList = new LinkedList(); + LinkedList returnList = new LinkedList(); + Position returnPosition = new NonLeafPosition(this, null); + + BlockLevelLayoutManager curLM; // currently active LM + BlockLevelLayoutManager prevLM = null; // previously active LM + while ((curLM = (BlockLevelLayoutManager) getChildLM()) != null) { + LayoutContext childLC = new LayoutContext(0); + // curLM is a ? + childLC.setStackLimit(MinOptMax.subtract(context + .getStackLimit(), stackLimit)); + childLC.setRefIPD(cellIPD); + + // get elements from curLM + returnedList = curLM.getNextKnuthElements(childLC, alignment); + if (returnedList.size() == 1 + && ((KnuthElement) returnedList.getFirst()).isPenalty() + && ((KnuthPenalty) returnedList.getFirst()).getP() == -KnuthElement.INFINITE) { + // a descendant of this block has break-before + if (returnList.size() == 0) { + // the first child (or its first child ...) has + // break-before; + // all this block, including space before, will be put in + // the + // following page + } + contentList.addAll(returnedList); + + // "wrap" the Position inside each element + // moving the elements from contentList to returnList + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } else { + if (prevLM != null) { + // there is a block handled by prevLM + // before the one handled by curLM + if (mustKeepTogether() + || prevLM.mustKeepWithNext() + || curLM.mustKeepWithPrevious()) { + // add an infinite penalty to forbid a break between + // blocks + contentList.add(new KnuthPenalty(0, + KnuthElement.INFINITE, false, + new Position(this), false)); + } else if (!((KnuthElement) contentList.getLast()).isGlue()) { + // add a null penalty to allow a break between blocks + contentList.add(new KnuthPenalty(0, 0, false, + new Position(this), false)); + } else { + // the last element in contentList is a glue; + // it is a feasible breakpoint, there is no need to add + // a penalty + } + } + contentList.addAll(returnedList); + if (returnedList.size() == 0) { + //Avoid NoSuchElementException below (happens with empty blocks) + continue; + } + if (((KnuthElement) returnedList.getLast()).isPenalty() + && ((KnuthPenalty) returnedList.getLast()).getP() == -KnuthElement.INFINITE) { + // a descendant of this block has break-after + if (curLM.isFinished()) { + // there is no other content in this block; + // it's useless to add space after before a page break + setFinished(true); + } + + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } + } + prevLM = curLM; + } + + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + setFinished(true); + return returnList; + } + /** * Get the next break possibility for this cell. * A cell contains blocks so there are breaks around the blocks @@ -152,7 +261,7 @@ public class Cell extends BlockStackingLayoutManager { * @param context the layout context * @return the next break possibility */ - public BreakPoss getNextBreakPoss(LayoutContext context) { + public BreakPoss getNextBreakPossOLDOLDOLD(LayoutContext context) { LayoutManager curLM; // currently active LM MinOptMax stackSize = new MinOptMax(); @@ -270,6 +379,16 @@ public class Cell extends BlockStackingLayoutManager { this.inRowIPDOffset = off; } + /** + * Set the content height for this cell. This method is used during + * addAreas() stage. + * + * @param h the height of the contents of this cell + */ + public void setContentHeight(int h) { + usedBPD = h; + } + /** * Set the row height that contains this cell. This method is used during * addAreas() stage. @@ -280,6 +399,21 @@ public class Cell extends BlockStackingLayoutManager { rowHeight = h; } + private int getContentHeight(int rowHeight, GridUnit gu) { + int bpd = rowHeight; + if (isSeparateBorderModel()) { + bpd -= gu.getPrimary().getBorders().getBorderBeforeWidth(false); + bpd -= gu.getPrimary().getBorders().getBorderAfterWidth(false); + } else { + bpd -= gu.getPrimary().getHalfMaxBorderWidth(); + } + CommonBorderPaddingBackground cbpb + = gu.getCell().getCommonBorderPaddingBackground(); + bpd -= cbpb.getPaddingBefore(false); + bpd -= cbpb.getPaddingAfter(false); + return bpd; + } + /** * Add the areas for the break points. * The cell contains block stacking layout managers @@ -291,40 +425,38 @@ public class Cell extends BlockStackingLayoutManager { public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { getParentArea(null); - BreakPoss bp1 = (BreakPoss)parentIter.peekNext(); - bBogus = !bp1.generatesAreas(); + //BreakPoss bp1 = (BreakPoss)parentIter.peekNext(); + bBogus = false;//!bp1.generatesAreas(); if (!isBogus()) { - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); } - if (fobj.isSeparateBorderModel()) { + if (isSeparateBorderModel()) { if (!emptyCell || fobj.showEmptyCells()) { TraitSetter.addBorders(curBlockArea, fobj.getCommonBorderPaddingBackground()); TraitSetter.addBackground(curBlockArea, fobj.getCommonBorderPaddingBackground()); } } else { TraitSetter.addBackground(curBlockArea, fobj.getCommonBorderPaddingBackground()); - //TODO Set these booleans right - boolean[] outer = new boolean[] {false, false, false, false}; - if (rows.size() == 1 && ((List)rows.get(0)).size() == 1) { + 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()) { //Can set the borders directly if there's no span - CommonBorderPaddingBackground effBorders = - ((GridUnit)((List)rows.get(0)).get(0)).effBorders; - //TODO Next line is a temporary hack! - TraitSetter.addCollapsingBorders(curBlockArea, - fobj.getCommonBorderPaddingBackground(), outer); TraitSetter.addCollapsingBorders(curBlockArea, - effBorders, outer); + gridUnit.getBorders(), outer); } else { int dy = yoffset; - for (int y = 0; y < rows.size(); y++) { - List gridUnits = (List)rows.get(y); + for (int y = 0; y < gridUnit.getRows().size(); y++) { + GridUnit[] gridUnits = (GridUnit[])gridUnit.getRows().get(y); int dx = xoffset; int lastRowHeight = 0; - for (int x = 0; x < gridUnits.size(); x++) { - GridUnit gu = (GridUnit)gridUnits.get(x); - if (!gu.effBorders.hasBorder()) { + for (int x = 0; x < gridUnits.length; x++) { + GridUnit gu = gridUnits[x]; + if (!gu.hasBorders()) { continue; } @@ -332,18 +464,35 @@ public class Cell extends BlockStackingLayoutManager { Block block = new Block(); block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); block.setPositioning(Block.ABSOLUTE); - block.setBPD(gu.row.getRowHeight()); - lastRowHeight = gu.row.getRowHeight(); - int ipd = gu.column.getWidth().getValue(); - int borderStartWidth = gu.effBorders.getBorderStartWidth(false) / 2; + + int bpd = getContentHeight(rowHeight, 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); + } + block.setBPD(bpd); + //TODO This needs to be fixed for row spanning + lastRowHeight = rowHeight; + int ipd = gu.getColumn().getColumnWidth().getValue(); + int borderStartWidth = gu.getBorders().getBorderStartWidth(false) / 2; ipd -= borderStartWidth; - ipd -= gu.effBorders.getBorderEndWidth(false) / 2; + ipd -= gu.getBorders().getBorderEndWidth(false) / 2; block.setIPD(ipd); block.setXOffset(dx + borderStartWidth); - block.setYOffset(dy); - TraitSetter.addCollapsingBorders(block, gu.effBorders, outer); + int halfCollapsingBorderHeight = 0; + if (!isSeparateBorderModel()) { + halfCollapsingBorderHeight += + gu.getBorders().getBorderBeforeWidth(false) / 2; + } + block.setYOffset(dy - halfCollapsingBorderHeight); + TraitSetter.addCollapsingBorders(block, gu.getBorders(), outer); parentLM.addChildArea(block); - dx += gu.column.getWidth().getValue(); + dx += gu.getColumn().getColumnWidth().getValue(); } dy += lastRowHeight; } @@ -364,24 +513,9 @@ public class Cell extends BlockStackingLayoutManager { } } - LayoutManager childLM; - int iStartPos = 0; - LayoutContext lc = new LayoutContext(0); - while (parentIter.hasNext()) { - LeafPosition lfp = (LeafPosition) parentIter.next(); - // Add the block areas to Area - PositionIterator breakPosIter = - new BreakPossPosIter(childBreaks, iStartPos, - lfp.getLeafPos() + 1); - iStartPos = lfp.getLeafPos() + 1; - while ((childLM = breakPosIter.getNextChildLM()) != null) { - childLM.addAreas(breakPosIter, lc); - } - } - + AreaAdditionUtil.addAreas(parentIter, layoutContext); - int contentBPD = rowHeight; - contentBPD -= borderAndPaddingBPD; + int contentBPD = getContentHeight(rowHeight, gridUnit); curBlockArea.setBPD(contentBPD); flush(); @@ -410,23 +544,28 @@ public class Cell extends BlockStackingLayoutManager { curBlockArea.setPositioning(Block.ABSOLUTE); int indent = 0; indent += startBorderWidth; - if (!fobj.isSeparateBorderModel()) { + if (!isSeparateBorderModel()) { indent /= 2; } indent += fobj.getCommonBorderPaddingBackground().getPaddingStart(false); // set position int halfBorderSep = 0; - if (fobj.isSeparateBorderModel()) { + if (isSeparateBorderModel()) { halfBorderSep = fobj.getBorderSeparation().getLengthPair() .getIPD().getLength().getValue() / 2; } - int halfCollapsingBorderHeight = 0; - if (!fobj.isSeparateBorderModel()) { - halfCollapsingBorderHeight += - fobj.getCommonBorderPaddingBackground().getBorderBeforeWidth(false) / 2; + int borderAdjust = 0; + if (!isSeparateBorderModel()) { + if (gridUnit.hasSpanning()) { + borderAdjust -= gridUnit.getHalfMaxBeforeBorderWidth(); + } else { + borderAdjust += gridUnit.getHalfMaxBeforeBorderWidth(); + } + } else { + //borderAdjust += gridUnit.getBorders().getBorderBeforeWidth(false); } curBlockArea.setXOffset(xoffset + inRowIPDOffset + halfBorderSep + indent); - curBlockArea.setYOffset(yoffset - halfCollapsingBorderHeight); + curBlockArea.setYOffset(yoffset - borderAdjust); curBlockArea.setIPD(cellIPD); //curBlockArea.setHeight(); @@ -461,5 +600,52 @@ public class Cell extends BlockStackingLayoutManager { } } + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#negotiateBPDAdjustment(int, org.apache.fop.layoutmgr.KnuthElement) + */ + public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) { + // TODO Auto-generated method stub + return 0; + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#discardSpace(org.apache.fop.layoutmgr.KnuthGlue) + */ + public void discardSpace(KnuthGlue spaceGlue) { + // TODO Auto-generated method stub + } + + /* (non-Javadoc) + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepTogether() + */ + public boolean mustKeepTogether() { + //TODO Keeps will have to be more sophisticated sooner or later + return ((BlockLevelLayoutManager)getParent()).mustKeepTogether()/* + || !fobj.getKeepTogether().getWithinPage().isAuto() + || !fobj.getKeepTogether().getWithinColumn().isAuto()*/; + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithPrevious() + */ + public boolean mustKeepWithPrevious() { + return false; //TODO FIX ME + /* + return !fobj.getKeepWithPrevious().getWithinPage().isAuto() + || !fobj.getKeepWithPrevious().getWithinColumn().isAuto(); + */ + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithNext() + */ + public boolean mustKeepWithNext() { + return false; //TODO FIX ME + /* + return !fobj.getKeepWithNext().getWithinPage().isAuto() + || !fobj.getKeepWithNext().getWithinColumn().isAuto(); + */ + } + } diff --git a/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModel.java b/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModel.java index 18999f055..c64bfc1b4 100644 --- a/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModel.java +++ b/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModel.java @@ -28,14 +28,26 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo; */ public abstract class CollapsingBorderModel { + /** before side */ + protected static final int BEFORE = CommonBorderPaddingBackground.BEFORE; + /** after side */ + protected static final int AFTER = CommonBorderPaddingBackground.AFTER; + /** start side */ + protected static final int START = CommonBorderPaddingBackground.START; + /** end side */ + protected static final int END = CommonBorderPaddingBackground.END; + + /** Flag: current grid unit is either start or end of the table. */ + public static final int VERTICAL_START_END_OF_TABLE = 1; + /** Indicates that the cell is/starts in the first row being painted on a particular page */ - public static final int FIRST_ROW_IN_TABLE_PART = 1; + //public static final int FIRST_ROW_IN_TABLE_PART = 1; /** Indicates that the cell is/ends in the last row being painted on a particular page */ - public static final int LAST_ROW_IN_TABLE_PART = 2; + //public static final int LAST_ROW_IN_TABLE_PART = 2; /** Indicates that the cell is/starts in the first row of a body/table-header/table-footer */ - public static final int FIRST_ROW_IN_GROUP = 4; + //public static final int FIRST_ROW_IN_GROUP = 4; /** Indicates that the cell is/end in the last row of a body/table-header/table-footer */ - public static final int LAST_ROW_IN_GROUP = 8; + //public static final int LAST_ROW_IN_GROUP = 8; private static CollapsingBorderModel collapse = null; private static CollapsingBorderModel collapseWithPrecedence = null; @@ -111,8 +123,8 @@ public abstract class CollapsingBorderModel { /** * Determines the winning BorderInfo. - * @param current cell info of the current element - * @param neighbour cell info of the neighbouring element + * @param current grid unit of the current element + * @param neighbour grid unit of the neighbouring element * @return the winning BorderInfo */ public abstract BorderInfo determineWinner( diff --git a/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModelEyeCatching.java b/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModelEyeCatching.java index e3e61033a..98d7fd5bc 100644 --- a/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModelEyeCatching.java +++ b/src/java/org/apache/fop/layoutmgr/table/CollapsingBorderModelEyeCatching.java @@ -19,7 +19,11 @@ package org.apache.fop.layoutmgr.table; import org.apache.fop.fo.Constants; -import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.fo.flow.Table; +import org.apache.fop.fo.flow.TableBody; +import org.apache.fop.fo.flow.TableCell; +import org.apache.fop.fo.flow.TableColumn; +import org.apache.fop.fo.flow.TableRow; import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo; /** @@ -29,48 +33,43 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo; */ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { - private static final int BEFORE = CommonBorderPaddingBackground.BEFORE; - private static final int AFTER = CommonBorderPaddingBackground.AFTER; - private static final int START = CommonBorderPaddingBackground.START; - private static final int END = CommonBorderPaddingBackground.END; - public BorderInfo determineWinner(GridUnit currentGridUnit, GridUnit otherGridUnit, int side, int flags) { final boolean vertical = isVerticalRelation(side); final int otherSide = getOtherSide(side); //Get cells - Cell currentCell = currentGridUnit.layoutManager; - Cell otherCell = null; + TableCell currentCell = currentGridUnit.getCell(); + TableCell otherCell = null; if (otherGridUnit != null) { - otherCell = otherGridUnit.layoutManager; + otherCell = otherGridUnit.getCell(); } //Get rows - Row currentRow = currentGridUnit.row; - Row otherRow = null; + TableRow currentRow = currentGridUnit.getRow(); + TableRow otherRow = null; if (vertical && otherCell != null) { - otherRow = otherGridUnit.row; + otherRow = otherGridUnit.getRow(); } //get bodies - Body currentBody = (Body)currentRow.getParent(); - Body otherBody = null; + TableBody currentBody = currentGridUnit.getBody(); + TableBody otherBody = null; if (otherRow != null) { - otherBody = (Body)otherRow.getParent(); + otherBody = otherGridUnit.getBody(); } //get columns - Column currentColumn = (Column)currentGridUnit.column; - Column otherColumn = null; + TableColumn currentColumn = currentGridUnit.getColumn(); + TableColumn otherColumn = null; if (otherGridUnit != null) { - otherColumn = (Column)otherGridUnit.column; + otherColumn = otherGridUnit.getColumn(); } //TODO get column groups //Get table - TableLayoutManager table = (TableLayoutManager)currentBody.getParent(); + Table table = currentGridUnit.getTable(); //---------------------------------------------------------------------- //We're creating two arrays containing the applicable BorderInfos for @@ -85,44 +84,51 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { if (otherGridUnit != null) { other[0] = otherGridUnit.getOriginalBorderInfoForCell(otherSide); } - if (side == BEFORE - || side == AFTER - || (currentColumn.isFirst() && side == START) - || (currentColumn.isLast() && side == END)) { + if ((currentRow != null) + && (side == BEFORE + || side == AFTER + || (currentGridUnit.getFlag(GridUnit.IN_FIRST_COLUMN) && side == START) + || (currentGridUnit.getFlag(GridUnit.IN_LAST_COLUMN) && side == END))) { //row - current[1] = currentRow.getFObj().getCommonBorderPaddingBackground().getBorderInfo(side); + current[1] = currentRow.getCommonBorderPaddingBackground().getBorderInfo(side); } if (otherRow != null) { //row - other[1] = otherRow.getFObj().getCommonBorderPaddingBackground().getBorderInfo(otherSide); + other[1] = otherRow.getCommonBorderPaddingBackground().getBorderInfo(otherSide); } - if ((side == BEFORE && currentRow.isFirstInBody()) - || (side == AFTER && currentRow.isLastInBody()) - || (currentColumn.isFirst() && side == START) - || (currentColumn.isLast() && side == END)) { + if ((side == BEFORE && currentGridUnit.getFlag(GridUnit.FIRST_IN_BODY)) + || (side == AFTER && currentGridUnit.getFlag(GridUnit.LAST_IN_BODY)) + || (currentGridUnit.getFlag(GridUnit.IN_FIRST_COLUMN) && side == START) + || (currentGridUnit.getFlag(GridUnit.IN_LAST_COLUMN) && side == END)) { //row group (=body, table-header or table-footer) - current[2] = currentBody.getFObj().getCommonBorderPaddingBackground().getBorderInfo(side); + current[2] = currentBody.getCommonBorderPaddingBackground().getBorderInfo(side); } - if ((otherSide == BEFORE && otherRow.isFirstInBody()) - || (otherSide == AFTER && otherRow.isLastInBody())) { + if (otherGridUnit != null + && otherBody != null + && ((otherSide == BEFORE && otherGridUnit.getFlag(GridUnit.FIRST_IN_BODY)) + || (otherSide == AFTER && otherGridUnit.getFlag(GridUnit.LAST_IN_BODY)))) { //row group (=body, table-header or table-footer) - other[2] = otherBody.getFObj().getCommonBorderPaddingBackground().getBorderInfo(otherSide); + other[2] = otherBody.getCommonBorderPaddingBackground().getBorderInfo(otherSide); } if ((side == BEFORE && otherGridUnit == null) || (side == AFTER && otherGridUnit == null) || (side == START) || (side == END)) { //column - current[3] = currentColumn.getFObj().getCommonBorderPaddingBackground().getBorderInfo(side); + current[3] = currentColumn.getCommonBorderPaddingBackground().getBorderInfo(side); } if (otherColumn != null) { //column - other[3] = otherColumn.getFObj().getCommonBorderPaddingBackground().getBorderInfo(otherSide); + other[3] = otherColumn.getCommonBorderPaddingBackground().getBorderInfo(otherSide); } //TODO current[4] and other[4] for column groups - if (otherGridUnit == null) { + if (otherGridUnit == null + && ((side == BEFORE && (flags & VERTICAL_START_END_OF_TABLE) > 0) + || (side == AFTER && (flags & VERTICAL_START_END_OF_TABLE) > 0) + || (side == START) + || (side == END))) { //table - current[5] = table.getTable().getCommonBorderPaddingBackground().getBorderInfo(side); + current[5] = table.getCommonBorderPaddingBackground().getBorderInfo(side); } //other[6] is always null, since it's always the same table @@ -136,7 +142,6 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { // *** Rule 2 *** if (!doRule2(current, other)) { - return null; //paint no border } // *** Rule 3 *** @@ -161,7 +166,7 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { } private BorderInfo doRule1(BorderInfo[] current, BorderInfo[] other) { - for (int i = 0; i < current.length - 1; i++) { + for (int i = 0; i < current.length; i++) { if ((current[i] != null) && (current[i].getStyle() == Constants.EN_HIDDEN)) { return current[i]; } @@ -174,7 +179,7 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { private boolean doRule2(BorderInfo[] current, BorderInfo[] other) { boolean found = false; - for (int i = 0; i < current.length - 1; i++) { + for (int i = 0; i < current.length; i++) { if ((current[i] != null) && (current[i].getStyle() != Constants.EN_NONE)) { found = true; break; @@ -190,7 +195,7 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { private BorderInfo doRule3(BorderInfo[] current, BorderInfo[] other) { int width = 0; //Find max border width - for (int i = 0; i < current.length - 1; i++) { + for (int i = 0; i < current.length; i++) { if ((current[i] != null) && (current[i].getRetainedWidth() > width)) { width = current[i].getRetainedWidth(); } @@ -201,13 +206,12 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { BorderInfo widest = null; int count = 0; //See if there's only one with the widest border - for (int i = 0; i < current.length - 1; i++) { + for (int i = 0; i < current.length; i++) { if ((current[i] != null) && (current[i].getRetainedWidth() == width)) { count++; if (widest == null) { widest = current[i]; } - break; } else { current[i] = null; //Discard the narrower ones } @@ -216,7 +220,6 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { if (widest == null) { widest = other[i]; } - break; } else { other[i] = null; //Discard the narrower ones } @@ -231,26 +234,24 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { private BorderInfo doRule4(BorderInfo[] current, BorderInfo[] other) { int pref = getPreferenceValue(Constants.EN_INSET); //Lowest preference //Find highest preference value - for (int i = 0; i < current.length - 1; i++) { + for (int i = 0; i < current.length; i++) { if (current[i] != null) { int currPref = getPreferenceValue(current[i].getStyle()); if (currPref > pref) { pref = currPref; - break; } } if (other[i] != null) { int currPref = getPreferenceValue(other[i].getStyle()); if (currPref > pref) { pref = currPref; - break; } } } BorderInfo preferred = null; int count = 0; //See if there's only one with the preferred border style - for (int i = 0; i < current.length - 1; i++) { + for (int i = 0; i < current.length; i++) { if (current[i] != null) { int currPref = getPreferenceValue(current[i].getStyle()); if (currPref == pref) { @@ -284,7 +285,7 @@ public class CollapsingBorderModelEyeCatching extends CollapsingBorderModel { } private BorderInfo doRule5(BorderInfo[] current, BorderInfo[] other) { - for (int i = 0; i < current.length - 1; i++) { + for (int i = 0; i < current.length; i++) { if (current[i] != null) { return current[i]; } diff --git a/src/java/org/apache/fop/layoutmgr/table/Column.java b/src/java/org/apache/fop/layoutmgr/table/Column.java deleted file mode 100644 index a09d6f788..000000000 --- a/src/java/org/apache/fop/layoutmgr/table/Column.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 1999-2005 The Apache Software Foundation. - * - * Licensed 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.layoutmgr.table; - -import org.apache.fop.datatypes.Length; -import org.apache.fop.layoutmgr.AbstractLayoutManager; -import org.apache.fop.layoutmgr.BreakPoss; -import org.apache.fop.layoutmgr.LayoutContext; -import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.TraitSetter; -import org.apache.fop.fo.flow.TableCell; -import org.apache.fop.fo.flow.TableColumn; -import org.apache.fop.area.Area; -import org.apache.fop.area.Block; - -/** - * LayoutManager for a table-column FO. - * The table creates an area for the table-column background, this class - * is used to do the area creation. This is used during the layout to handle - * column properties. - */ -public class Column extends AbstractLayoutManager { - - private TableColumn fobj; - - private Length columnWidth; - - /** - * Create a new column layout manager. - * @param node the table-column FO - */ - public Column(TableColumn node) { - super(node); - fobj = node; - columnWidth = fobj.getColumnWidth(); - } - - /** @return the table-column FO */ - public TableColumn getFObj() { - return this.fobj; - } - - /** @return true if the column is the first column */ - public boolean isFirst() { - return ((TableLayoutManager)getParent()).isFirst(this); - } - - /** @return true if the column is the last column */ - public boolean isLast() { - return ((TableLayoutManager)getParent()).isLast(this); - } - - /** - * Get the next break possibility. - * Columns do not create or return any areas. - * - * @param context the layout context - * @return the break possibility, always null - */ - public BreakPoss getNextBreakPoss(LayoutContext context) { - return null; - } - - /** - * Add the areas. - * Although this adds no areas it is used to add the id - * reference of the table-column. - * - * @param parentIter the position iterator - * @param layoutContext the context - */ - public void addAreas(PositionIterator parentIter, - LayoutContext layoutContext) { - } - - /** - * Get the parent area. - * This does nothing. - * - * @param childArea the child area - * @return always null - */ - public Area getParentArea(Area childArea) { - return null; - } - - /** - * Overrides the default column-with coming from the FO. - * @param width the new width to use - */ - public void setWidth(Length width) { - this.columnWidth = width; - } - - /** - * Get the width of this column. - * - * @return the width of the column - */ - public Length getWidth() { - return columnWidth; - } - - /** - * Create a column area. - * This area has the background and width set. - * The Body manager will then set the offset of the column. - * - * @return the new column area - */ - public Area createColumnArea() { - Area curBlockArea = new Block(); - - TraitSetter.addBackground(curBlockArea, fobj.getCommonBorderPaddingBackground()); - return curBlockArea; - } -} - diff --git a/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java b/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java new file mode 100644 index 000000000..c732763e7 --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java @@ -0,0 +1,140 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr.table; + +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.fo.flow.Table; +import org.apache.fop.fo.flow.TableColumn; + +/** + * Class holding a number of columns making up the column setup of a row. + */ +public class ColumnSetup { + + /** Logger **/ + private static Log log = LogFactory.getLog(ColumnSetup.class); + + private Table table; + private List columns = new java.util.ArrayList(); + private int maxColIndexReferenced = 0; + + public ColumnSetup(Table table) { + this.table = table; + prepareExplicitColumns(); + if (getColumnCount() == 0) { + createColumnsFromFirstRow(); + } + } + + private void prepareExplicitColumns() { + List rawCols = table.getColumns(); + if (rawCols != null) { + int colnum = 1; + ListIterator iter = rawCols.listIterator(); + while (iter.hasNext()) { + TableColumn col = (TableColumn)iter.next(); + if (col.hasColumnNumber()) { + colnum = col.getColumnNumber(); + } + for (int i = 0; i < col.getNumberColumnsRepeated(); i++) { + while (colnum > columns.size()) { + columns.add(null); + } + columns.set(colnum - 1, col); + colnum++; + } + } + //Post-processing the list (looking for gaps) + int pos = 1; + ListIterator ppIter = columns.listIterator(); + while (ppIter.hasNext()) { + TableColumn col = (TableColumn)ppIter.next(); + if (col == null) { + log.error("Found a gap in the table-columns at position " + pos); + } + pos++; + } + } + } + + /** + * Returns a column. If the index of the column is bigger than the number of explicitly + * defined columns the last column is returned. + * @param index index of the column (1 is the first column) + * @return the requested column + */ + public TableColumn getColumn(int index) { + int size = columns.size(); + if (index > size) { + maxColIndexReferenced = Math.max(maxColIndexReferenced, index); + return (TableColumn)columns.get(size - 1); + } else { + return (TableColumn)columns.get(index - 1); + } + } + + /** @see java.lang.Object#toString() */ + public String toString() { + return columns.toString(); + } + + /** @return the number of columns in the setup. */ + public int getColumnCount() { + if (maxColIndexReferenced > columns.size()) { + return maxColIndexReferenced; + } else { + return columns.size(); + } + } + + /** @return an Iterator over all columns */ + public Iterator iterator() { + return this.columns.iterator(); + } + + private void createColumnsFromFirstRow() { + //TODO Create oldColumns from first row here + //--> rule 2 in "fixed table layout", see CSS2, 17.5.2 + //Alternative: extend oldColumns on-the-fly, but in this case we need the + //new property evaluation context so proportional-column-width() works + //correctly. + if (columns.size() == 0) { + this.columns.add(table.getDefaultColumn()); + } + } + + /** + * @param col column index (1 is first column) + * @return the X offset of the requested column + */ + public int getXOffset(int col) { + int xoffset = 0; + for (int i = 1; i < col; i++) { + xoffset += getColumn(i).getColumnWidth().getValue(); + } + return xoffset; + } + +} diff --git a/src/java/org/apache/fop/layoutmgr/table/EffRow.java b/src/java/org/apache/fop/layoutmgr/table/EffRow.java new file mode 100644 index 000000000..675c29061 --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/table/EffRow.java @@ -0,0 +1,121 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr.table; + +import java.util.Iterator; +import java.util.List; + +import org.apache.fop.traits.MinOptMax; + +/** + * This class represents an effective row in a table and holds a list of grid units occupying + * the row as well as some additional values. + */ +public class EffRow { + + private List gridUnits = new java.util.ArrayList(); + private int index; + private int bodyType; + private MinOptMax height; + private MinOptMax explicitHeight; + + public EffRow(int index, int bodyType) { + this.index = index; + this.bodyType = bodyType; + this.height = height; + } + + public int getIndex() { + return this.index; + } + + public int getBodyType() { + return this.bodyType; + } + + public MinOptMax getHeight() { + return this.height; + } + + public void setHeight(MinOptMax mom) { + this.height = mom; + } + + public MinOptMax getExplicitHeight() { + return this.explicitHeight; + } + + public void setExplicitHeight(MinOptMax mom) { + this.explicitHeight = mom; + } + + public List getGridUnits() { + return gridUnits; + } + + /** + * Returns the grid unit at a given position. + * @param column index of the grid unit in the row (zero based) + * @return the requested grid unit. + */ + public GridUnit getGridUnit(int column) { + return (GridUnit)gridUnits.get(column); + } + + /** + * Returns the grid unit at a given position. In contrast to getGridUnit() this + * method returns null if there's no grid unit at the given position. The number of + * grid units for row x can be smaller than the number of grid units for row x-1. + * @param column index of the grid unit in the row (zero based) + * @return the requested grid unit or null if there's no grid unit at this position. + */ + public GridUnit safelyGetGridUnit(int column) { + if (column < gridUnits.size()) { + return (GridUnit)gridUnits.get(column); + } else { + return null; + } + } + + public void setFlagForAllGridUnits(int flag, boolean value) { + Iterator iter = gridUnits.iterator(); + while (iter.hasNext()) { + GridUnit gu = (GridUnit)iter.next(); + gu.setFlag(flag, value); + } + } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer("EffRow {"); + sb.append(index); + if (getBodyType() == TableRowIterator.BODY) { + sb.append(" in body"); + } else if (getBodyType() == TableRowIterator.HEADER) { + sb.append(" in header"); + } else { + sb.append(" in footer"); + } + sb.append(", ").append(height); + sb.append(", ").append(explicitHeight); + sb.append(", ").append(gridUnits.size()).append(" gu"); + sb.append("}"); + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/java/org/apache/fop/layoutmgr/table/EmptyGridUnit.java b/src/java/org/apache/fop/layoutmgr/table/EmptyGridUnit.java new file mode 100644 index 000000000..8d17420d8 --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/table/EmptyGridUnit.java @@ -0,0 +1,60 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr.table; + +import org.apache.fop.fo.flow.TableBody; +import org.apache.fop.fo.flow.TableColumn; +import org.apache.fop.fo.flow.TableRow; + +/** + * GridUnit subclass for empty grid units. + */ +public class EmptyGridUnit extends GridUnit { + + private TableRow row; + private TableBody body; + + /** + * @param row Optional table-row instance + * @param column table-column instance + * @param body table-body the grid unit belongs to + * @param startCol column index + */ + public EmptyGridUnit(TableRow row, TableColumn column, TableBody body, + int startCol) { + super(null, null, column, startCol, 0); + this.row = row; + this.body = body; + } + + /** @see org.apache.fop.layoutmgr.table.GridUnit#isPrimary() */ + public boolean isPrimary() { + return true; + } + + /** @see org.apache.fop.layoutmgr.table.GridUnit#getBody() */ + public TableBody getBody() { + return this.body; + } + + /** @see org.apache.fop.layoutmgr.table.GridUnit#getRow() */ + public TableRow getRow() { + return this.row; + } +} diff --git a/src/java/org/apache/fop/layoutmgr/table/GridUnit.java b/src/java/org/apache/fop/layoutmgr/table/GridUnit.java index e89f9d8ec..5b1d85d2b 100644 --- a/src/java/org/apache/fop/layoutmgr/table/GridUnit.java +++ b/src/java/org/apache/fop/layoutmgr/table/GridUnit.java @@ -18,45 +18,135 @@ package org.apache.fop.layoutmgr.table; +import org.apache.fop.fo.FONode; import org.apache.fop.fo.flow.Table; +import org.apache.fop.fo.flow.TableBody; +import org.apache.fop.fo.flow.TableCell; +import org.apache.fop.fo.flow.TableColumn; +import org.apache.fop.fo.flow.TableRow; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo; - +/** + * This class represents one grid unit inside a table. + */ public class GridUnit { + + /** Indicates that the grid unit is in the first column. */ + public static final int IN_FIRST_COLUMN = 0; + /** Indicates that the grid unit is in the last column. */ + public static final int IN_LAST_COLUMN = 1; + /** Indicates that the grid unit is in the first row (context: table). */ + public static final int FIRST_IN_TABLE = 2; + /** Indicates that the grid unit is in the first row (context: body). */ + public static final int FIRST_IN_BODY = 3; + /** Indicates that the grid unit is in the last row (context: body). */ + public static final int LAST_IN_BODY = 4; + /** Indicates that the grid unit is in the last row (context: table). */ + public static final int LAST_IN_TABLE = 5; - /** layout manager for the cell occupying this grid unit, may be null */ - public Cell layoutManager; - /** layout manager for the column that this grid unit belongs to */ - public Column column; - /** layout manager for the row that this grid unit belongs to */ - public Row row; + /** Primary grid unit */ + private PrimaryGridUnit primary; + /** Table cell which occupies this grid unit */ + private TableCell cell; + /** Table row which occupied this grid unit (may be null) */ + private TableRow row; + /** Table column that this grid unit belongs to */ + private TableColumn column; + + /** start index of grid unit within row in column direction */ + private int startCol; /** index of grid unit within cell in column direction */ - public int colSpanIndex; + private int colSpanIndex; /** index of grid unit within cell in row direction */ - public int rowSpanIndex; - /** effective borders for a cell slot (used for collapsing border model) */ - public CommonBorderPaddingBackground effBorders; + private int rowSpanIndex; + /** effective borders for a cell slot */ + private CommonBorderPaddingBackground effBorders; + /** flags for the grid unit */ + private byte flags = 0; + + + public GridUnit(TableCell cell, TableColumn column, int startCol, int colSpanIndex) { + this(null, cell, column, startCol, colSpanIndex); + } + + public GridUnit(PrimaryGridUnit primary, TableColumn column, int startCol, int colSpanIndex) { + this(primary, primary.getCell(), column, startCol, colSpanIndex); + } - public GridUnit(Cell layoutManager, int colSpanIndex) { - this.layoutManager = layoutManager; + protected GridUnit(PrimaryGridUnit primary, TableCell cell, TableColumn column, int startCol, int colSpanIndex) { + this.primary = primary; + this.cell = cell; + this.column = column; + this.startCol = startCol; this.colSpanIndex = colSpanIndex; - this.rowSpanIndex = 0; } - public GridUnit(Cell layoutManager) { - this(layoutManager, 0); + public TableCell getCell() { + return this.cell; + } + + public TableColumn getColumn() { + return this.column; + } + + public TableRow getRow() { + if (this.row != null) { + return this.row; + } else if (getCell().getParent() instanceof TableRow) { + return (TableRow)getCell().getParent(); + } else { + return null; + } + } + + /** + * Sets the table-row FO, if applicable. + * @param rowFO the table-row FO + */ + public void setRow(TableRow rowFO) { + this.row = rowFO; + } + + public TableBody getBody() { + FONode node = getCell(); + while (node != null && !(node instanceof TableBody)) { + node = node.getParent(); + } + return (TableBody)node; + } + + public Table getTable() { + FONode node = getBody(); + while (node != null && !(node instanceof Table)) { + node = node.getParent(); + } + return (Table)node; + } + + /** + * @return the primary grid unit if this is a spanned grid unit + */ + public PrimaryGridUnit getPrimary() { + return (isPrimary() ? (PrimaryGridUnit)this : this.primary); } - /** @return true if the grid unit is the primary of a cell */ - public boolean isPrimaryGridUnit() { - return (colSpanIndex == 0) && (rowSpanIndex == 0); + public boolean isPrimary() { + return false; + } + + public boolean isEmpty() { + return this.cell == null; + } + + public int getStartCol() { + return this.startCol; } /** @return true if the grid unit is the last in column spanning direction */ public boolean isLastGridUnitColSpan() { - if (layoutManager != null) { - return (colSpanIndex == layoutManager.getFObj().getNumberColumnsSpanned() - 1); + if (cell != null) { + return (colSpanIndex == cell.getNumberColumnsSpanned() - 1); } else { return true; } @@ -64,68 +154,136 @@ public class GridUnit { /** @return true if the grid unit is the last in column spanning direction */ public boolean isLastGridUnitRowSpan() { - if (layoutManager != null) { - return (rowSpanIndex == layoutManager.getFObj().getNumberRowsSpanned() - 1); + if (cell != null) { + return (rowSpanIndex == cell.getNumberRowsSpanned() - 1); } else { return true; } } - /** @return true if the cell is part of a span in column direction */ - public boolean isColSpan() { - return (colSpanIndex > 0); + /** + * @return the index of the grid unit inside a cell in row direction + */ + public int getRowSpanIndex() { + return this.rowSpanIndex; + } + + /** + * @return the index of the grid unit inside a cell in column direction + */ + public int getColSpanIndex() { + return this.colSpanIndex; } + /** + * Returns a BorderInfo instance for a side of the currently applicable cell before border + * resolution (i.e. the value from the FO). A return value of null indicates an empty cell. + * See CollapsingBorderModel(EyeCatching) where this method is used. + * @param side for which side to return the BorderInfo + * @return the requested BorderInfo instance or null if the grid unit is an empty cell + */ public BorderInfo getOriginalBorderInfoForCell(int side) { - if (layoutManager != null) { - return layoutManager.getFObj().getCommonBorderPaddingBackground().getBorderInfo(side); + if (cell != null) { + return cell.getCommonBorderPaddingBackground().getBorderInfo(side); } else { return null; } } /** - * Assign the borders from the given cell to this cell info. Used in + * @return the resolved normal borders for this grid unit + */ + public CommonBorderPaddingBackground getBorders() { + return this.effBorders; + } + + /** + * @return true if the grid unit has any borders. + */ + public boolean hasBorders() { + return (getBorders() != null) && getBorders().hasBorder(); + } + + /** + * Assigns the borders from the given cell to this cell info. Used in * case of separate border model. - * @param current cell to take the borders from */ - public void assignBorder(Cell current) { - if (current != null) { - this.effBorders = current.getFObj().getCommonBorderPaddingBackground(); + public void assignBorderForSeparateBorderModel() { + if (cell != null) { + this.effBorders = cell.getCommonBorderPaddingBackground(); } } /** - * Assign the borders directly. - * @param borders the borders to use + * Resolve collapsing borders for the given cell. Used in case of the collapsing border model. + * @param other neighbouring grid unit if any + * @param side the side to resolve (one of CommonBorderPaddingBackground.BEFORE|AFTER|START|END) */ - public void assignBorder(CommonBorderPaddingBackground borders) { - if (borders != null) { - this.effBorders = borders; - } + public void resolveBorder(GridUnit other, int side) { + resolveBorder(other, side, 0); } /** - * Resolve collapsing borders for the given cell and store the resulting - * borders in this cell info. Use in case of the collapsing border model. - * @param current cell to resolve borders for - * @param before cell before the current cell, if any - * @param after cell after the current cell, if any - * @param start cell preceeding the current cell, if any - * @param end cell succeeding of the current cell, if any + * Resolve collapsing borders for the given cell. Used in case of the collapsing border model. + * @param other neighbouring grid unit if any + * @param side the side to resolve (one of CommonBorderPaddingBackground.BEFORE|AFTER|START|END) + * @param resFlags flags for the border resolution */ - public static void resolveBorder(Table table, - CommonBorderPaddingBackground target, - GridUnit current, GridUnit other, int side) { - if (current == null) { - return; - } - + public void resolveBorder(GridUnit other, int side, int resFlags) { CollapsingBorderModel borderModel = CollapsingBorderModel.getBorderModelFor( - table.getBorderCollapse()); - target.setBorderInfo( - borderModel.determineWinner(current, other, - side, 0), side); + getTable().getBorderCollapse()); + if (this.effBorders == null) { + this.effBorders = new CommonBorderPaddingBackground(); + } + this.effBorders.setBorderInfo( + borderModel.determineWinner(this, other, + side, resFlags), side); } -} \ No newline at end of file + public boolean getFlag(int which) { + return (flags & (1 << which)) != 0; + } + + public void setFlag(int which, boolean value) { + if (value) { + flags |= (1 << which); //set flag + } else { + flags &= ~(1 << which); //clear flag + } + } + + /** + * @return the grid unit just below this grid unit if the cell is spanning. + */ + public GridUnit createNextRowSpanningGridUnit() { + if (isLastGridUnitRowSpan()) { + return null; + } else { + //cloning the current GridUnit with adjustments + GridUnit gu = new GridUnit(getPrimary(), getColumn(), startCol, colSpanIndex); + gu.rowSpanIndex = rowSpanIndex + 1; + return gu; + } + } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer(); + if (isEmpty()) { + sb.append("EMPTY"); + } else if (isPrimary()) { + sb.append("Primary"); + } + sb.append("GridUnit:"); + if (colSpanIndex > 0) { + sb.append(" colSpan=").append(colSpanIndex); + } + if (rowSpanIndex > 0) { + sb.append(" rowSpan=").append(rowSpanIndex); + } + sb.append(" startCol=").append(startCol); + sb.append(" flags=").append(Integer.toBinaryString(flags)); + return sb.toString(); + } + +} diff --git a/src/java/org/apache/fop/layoutmgr/table/PrimaryGridUnit.java b/src/java/org/apache/fop/layoutmgr/table/PrimaryGridUnit.java new file mode 100644 index 000000000..2695890a7 --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/table/PrimaryGridUnit.java @@ -0,0 +1,210 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr.table; + +import java.util.LinkedList; +import java.util.List; + +import org.apache.fop.fo.flow.TableCell; +import org.apache.fop.fo.flow.TableColumn; +import org.apache.fop.fo.properties.LengthRangeProperty; + +/** + * This class represents a primary grid unit of a spanned cell. + */ +public class PrimaryGridUnit extends GridUnit { + + /** Cell layout manager. */ + private Cell cellLM; + /** List of Knuth elements representing the contents of the cell. */ + private LinkedList elements; + /** Index of row where this cell starts */ + private int startRow; + /** Links to the spanned grid units. (List of GridUnit arrays, one array represents a row) */ + private List rows; + /** The calculated size of the cell's content. (cached value) */ + private int contentLength = -1; + + public PrimaryGridUnit(TableCell cell, TableColumn column, int startCol, int startRow) { + super(cell, column, startCol, 0); + this.startRow = startRow; + if (cell != null) { + cellLM = new Cell(cell, this); + } + } + + public Cell getCellLM() { + return cellLM; + } + + public boolean isPrimary() { + return true; + } + + public void setElements(LinkedList elements) { + this.elements = elements; + } + + public LinkedList getElements() { + return this.elements; + } + + /** + * @return Returns the half the maximum before border width of this cell. + */ + public int getHalfMaxBeforeBorderWidth() { + int value = 0; + if (getRows() != null) { + int before = 0; + //first row for before borders + GridUnit[] row = (GridUnit[])getRows().get(0); + for (int i = 0; i < row.length; i++) { + if (row[i].hasBorders()) { + before = Math.max(before, + row[i].getBorders().getBorderBeforeWidth(false)); + } + } + value += before / 2; + } else { + if (hasBorders()) { + value += getBorders().getBorderBeforeWidth(false) / 2; + } + } + return value; + } + + /** + * @return Returns the half the maximum after border width of this cell. + */ + public int getHalfMaxAfterBorderWidth() { + int value = 0; + if (getRows() != null) { + //Last row for after borders + int after = 0; + GridUnit[] row = (GridUnit[])getRows().get(getRows().size() - 1); + for (int i = 0; i < row.length; i++) { + if (row[i].hasBorders()) { + after = Math.max(after, row[i].getBorders().getBorderAfterWidth(false)); + } + } + value += after / 2; + } else { + if (hasBorders()) { + value += getBorders().getBorderAfterWidth(false) / 2; + } + } + return value; + } + + /** + * @return Returns the sum of half the maximum before and after border + * widths of this cell. + */ + public int getHalfMaxBorderWidth() { + return getHalfMaxBeforeBorderWidth() + getHalfMaxAfterBorderWidth(); + } + + /** @param value The length of the cell content to remember. */ + public void setContentLength(int value) { + this.contentLength = value; + } + + /** @return Returns the length of the cell content. */ + public int getContentLength() { + return contentLength; + } + + /** + * @return Returns the length of the cell content after the bpd/height attributes on cell + * and row have been taken into account. + */ + public int getEffectiveContentLength() { + int value = getContentLength(); + if (!getCell().getBlockProgressionDimension().getMinimum().isAuto()) { + value = Math.max(value, + getCell().getBlockProgressionDimension().getMinimum().getLength().getValue()); + } + if (getRow() != null + && !getRow().getBlockProgressionDimension().getMinimum().isAuto()) { + value = Math.max(value, + getRow().getBlockProgressionDimension().getMinimum().getLength().getValue()); + } + return value; + } + + /** @return true if cell/row has an explicit BPD/height */ + public boolean hasBPD() { + if (!getCell().getBlockProgressionDimension().getOptimum().isAuto()) { + return true; + } + if (getRow() != null + && !getRow().getBlockProgressionDimension().getOptimum().isAuto()) { + return true; + } + return false; + } + + public List getRows() { + return this.rows; + } + + public void addRow(GridUnit[] row) { + if (rows == null) { + rows = new java.util.ArrayList(); + } + rows.add(row); + } + + public int getStartRow() { + return this.startRow; + } + + public int[] getStartEndBorderWidths() { + int[] widths = new int[2]; + if (rows == null) { + widths[0] = getBorders().getBorderStartWidth(false); + widths[1] = getBorders().getBorderEndWidth(false); + } else { + for (int i = 0; i < rows.size(); i++) { + GridUnit[] gridUnits = (GridUnit[])rows.get(i); + widths[0] = Math.max(widths[0], + (gridUnits[0]). + getBorders().getBorderStartWidth(false)); + widths[1] = Math.max(widths[1], + (gridUnits[gridUnits.length - 1]). + getBorders().getBorderEndWidth(false)); + } + } + return widths; + } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer(super.toString()); + sb.append(" startRow=").append(startRow); + return sb.toString(); + } + + /** @return true if this cell spans over more than one grid unit. */ + public boolean hasSpanning() { + return (getCell().getNumberColumnsSpanned() > 1) + || (getCell().getNumberRowsSpanned() > 1); + } + +} diff --git a/src/java/org/apache/fop/layoutmgr/table/Row.java b/src/java/org/apache/fop/layoutmgr/table/Row.java deleted file mode 100644 index c3d06d435..000000000 --- a/src/java/org/apache/fop/layoutmgr/table/Row.java +++ /dev/null @@ -1,623 +0,0 @@ -/* - * Copyright 1999-2005 The Apache Software Foundation. - * - * Licensed 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.layoutmgr.table; - -import org.apache.fop.fo.FONode; -import org.apache.fop.fo.flow.Table; -import org.apache.fop.fo.flow.TableBody; -import org.apache.fop.fo.flow.TableCell; -import org.apache.fop.fo.flow.TableRow; -import org.apache.fop.fo.properties.CommonBorderPaddingBackground; -import org.apache.fop.fo.properties.LengthRangeProperty; -import org.apache.fop.layoutmgr.BlockStackingLayoutManager; -import org.apache.fop.layoutmgr.LayoutManager; -import org.apache.fop.layoutmgr.LeafPosition; -import org.apache.fop.layoutmgr.BreakPoss; -import org.apache.fop.layoutmgr.LayoutContext; -import org.apache.fop.layoutmgr.MinOptMaxUtil; -import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.BreakPossPosIter; -import org.apache.fop.layoutmgr.Position; -import org.apache.fop.layoutmgr.TraitSetter; -import org.apache.fop.area.Area; -import org.apache.fop.area.Block; -import org.apache.fop.area.Trait; -import org.apache.fop.traits.MinOptMax; - -import java.util.Iterator; -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; - -/** - * LayoutManager for a table-row FO. - * The row contains cells that are organised according to the columns. - * A break in a table row will contain breaks for each table cell. - * If there are row spanning cells then these cells belong to this row - * but effect the occupied columns of future rows. - */ -public class Row extends BlockStackingLayoutManager { - - private TableRow fobj; - - private List gridUnits = null; - private List columns = null; - private int referenceIPD; - private int rowHeight; - private int xoffset; - private int yoffset; - - private class RowPosition extends LeafPosition { - protected List cellBreaks; - protected RowPosition(LayoutManager lm, int pos, List l) { - super(lm, pos); - cellBreaks = l; - } - } - - /** - * Create a new row layout manager. - * - */ - public Row(TableRow node) { - super(node); - fobj = node; - } - - /** @return the table-row FO */ - public TableRow getFObj() { - return this.fobj; - } - - /** - * @return the table owning this row - */ - public Table getTable() { - FONode node = fobj.getParent(); - while (!(node instanceof Table)) { - node = node.getParent(); - } - return (Table)node; - } - - /** - * Set the columns from the table. - * - * @param cols the list of columns for this table - */ - public void setColumns(List cols) { - columns = cols; - } - - /** @return true if this is the layout manager for the first row in a body. */ - public boolean isFirstInBody() { - return ((TableBody)getFObj().getParent()).isFirst(getFObj()); - } - - /** @return true if this is the layout manager for the last row in a body. */ - public boolean isLastInBody() { - return ((TableBody)getFObj().getParent()).isLast(getFObj()); - } - - /** - * Gets the Column at a given index. - * @param index index of the column (index must be >= 1) - * @return the requested Column - */ - private Column getColumn(int index) { - int size = columns.size(); - if (index > size - 1) { - return (Column)columns.get(size - 1); - } else { - return (Column)columns.get(index - 1); - } - } - - private void prepareGridUnits() { - gridUnits = new java.util.ArrayList(); - List availableCells = new java.util.ArrayList(); - // add cells to list - while (childLMiter.hasNext()) { - curChildLM = (LayoutManager) childLMiter.next(); - curChildLM.setParent(this); - curChildLM.initialize(); - availableCells.add(curChildLM); - } - - //Transfer available cells to their slots - int colnum = 1; - ListIterator iter = availableCells.listIterator(); - while (iter.hasNext()) { - Cell cellLM = (Cell)iter.next(); - TableCell cell = cellLM.getFObj(); - if (cell.hasColumnNumber()) { - colnum = cell.getColumnNumber(); - } - while (colnum > gridUnits.size()) { - gridUnits.add(null); - } - if (gridUnits.get(colnum - 1) != null) { - log.error("Overlapping cell at position " + colnum); - } - //Add cell info for primary slot - GridUnit info = new GridUnit(cellLM); - info.row = this; - gridUnits.set(colnum - 1, info); - info.column = getColumn(colnum); - - //Add cell infos on spanned slots if any - for (int j = 1; j < cell.getNumberColumnsSpanned(); j++) { - colnum++; - GridUnit infoSpan = new GridUnit(cellLM, j); - infoSpan.row = this; - infoSpan.column = getColumn(colnum); - if (colnum > gridUnits.size()) { - gridUnits.add(infoSpan); - } else { - if (gridUnits.get(colnum - 1) != null) { - log.error("Overlapping cell at position " + colnum); - //TODO throw layout exception - } - gridUnits.set(colnum - 1, infoSpan); - } - } - colnum++; - } - - //Post-processing the list (looking for gaps and resolve start and end borders) - postProcessGridUnits(); - } - - private void postProcessGridUnits() { - for (int pos = 1; pos <= gridUnits.size(); pos++) { - GridUnit gu = (GridUnit)gridUnits.get(pos - 1); - - //Empty grid units - if (gu == null) { - //Add grid unit - gu = new GridUnit(null); - gu.row = this; - gu.column = getColumn(pos); - gridUnits.set(pos - 1, gu); - } - } - - //Border resolution now that the empty grid units are filled - for (int pos = 1; pos <= gridUnits.size(); pos++) { - GridUnit starting = (GridUnit)gridUnits.get(pos - 1); - - //Border resolution - if (getTable().isSeparateBorderModel()) { - starting.assignBorder(starting.layoutManager); - } else { - //Neighbouring grid unit at start edge - GridUnit start = null; - int find = pos - 1; - while (find >= 1) { - GridUnit candidate = (GridUnit)gridUnits.get(find - 1); - if (candidate.isLastGridUnitColSpan()) { - start = candidate; - break; - } - find--; - } - - //Ending grid unit for current cell - GridUnit ending = null; - if (starting.layoutManager != null) { - pos += starting.layoutManager.getFObj().getNumberColumnsSpanned() - 1; - } - ending = (GridUnit)gridUnits.get(pos - 1); - - //Neighbouring grid unit at end edge - GridUnit end = null; - find = pos + 1; - while (find <= gridUnits.size()) { - GridUnit candidate = (GridUnit)gridUnits.get(find - 1); - if (candidate.isPrimaryGridUnit()) { - end = candidate; - break; - } - find++; - } - CommonBorderPaddingBackground borders = new CommonBorderPaddingBackground(); - GridUnit.resolveBorder(getTable(), borders, starting, - (start != null ? start : null), - CommonBorderPaddingBackground.START); - starting.effBorders = borders; - if (starting != ending) { - borders = new CommonBorderPaddingBackground(); - } - GridUnit.resolveBorder(getTable(), borders, ending, - (end != null ? end : null), - CommonBorderPaddingBackground.END); - ending.effBorders = borders; - //Only start and end borders here, before and after during layout - //TODO resolve before and after borders during layout - } - } - } - - /** - * Get the cell info for a cell. - * - * @param pos the position of the cell (must be >= 1) - * @return the cell info object - */ - protected GridUnit getCellInfo(int pos) { - if (gridUnits == null) { - prepareGridUnits(); - } - if (pos <= gridUnits.size()) { - return (GridUnit)gridUnits.get(pos - 1); - } else { - return null; - } - } - - /** - * Get the next break possibility. - * A row needs to get the possible breaks for each cell - * in the row and find a suitable break across all cells. - * - * @param context the layout context for getting breaks - * @return the next break possibility - */ - public BreakPoss getNextBreakPoss(LayoutContext context) { - //LayoutManager curLM; // currently active LM - GridUnit curGridUnit; //currently active grid unit - - BreakPoss lastPos = null; - List breakList = new java.util.ArrayList(); - - int min = 0; - int opt = 0; - int max = 0; - - // This is used for the displacement of the individual cells - int ipdOffset = 0; - - int startColumn = 1; - boolean over = false; - - while ((curGridUnit = getCellInfo(startColumn)) != null) { - Cell cellLM = curGridUnit.layoutManager; - if (curGridUnit.isColSpan()) { - //skip spanned slots - startColumn++; - continue; - } - - List childBreaks = new ArrayList(); - MinOptMax stackSize = new MinOptMax(); - - // Set up a LayoutContext - // the ipd is from the current column - referenceIPD = context.getRefIPD(); - BreakPoss bp; - - LayoutContext childLC = new LayoutContext(0); - childLC.setStackLimit( - MinOptMax.subtract(context.getStackLimit(), - stackSize)); - - //Determine which columns this cell will occupy - List spannedGridUnits = new java.util.ArrayList(); - getGridUnitsForCell(cellLM, startColumn, spannedGridUnits); - int childRefIPD = 0; - for (int i = 0; i < spannedGridUnits.size(); i++) { - Column col = ((GridUnit)spannedGridUnits.get(i)).column; - childRefIPD += col.getWidth().getValue(); - } - childLC.setRefIPD(childRefIPD); - - if (cellLM != null) { - cellLM.addGridUnitsFromRow(spannedGridUnits); - cellLM.setInRowIPDOffset(ipdOffset); - while (!cellLM.isFinished()) { - if ((bp = cellLM.getNextBreakPoss(childLC)) != null) { - if (stackSize.opt + bp.getStackingSize().opt > context.getStackLimit().max) { - // reset to last break - if (lastPos != null) { - LayoutManager lm = lastPos.getLayoutManager(); - lm.resetPosition(lastPos.getPosition()); - if (lm != cellLM) { - cellLM.resetPosition(null); - } - } else { - cellLM.resetPosition(null); - } - over = true; - break; - } - stackSize.add(bp.getStackingSize()); - lastPos = bp; - childBreaks.add(bp); - - if (bp.nextBreakOverflows()) { - over = true; - break; - } - - childLC.setStackLimit(MinOptMax.subtract( - context.getStackLimit(), stackSize)); - } - } - startColumn += cellLM.getFObj().getNumberColumnsSpanned(); - } else { - //Skipping empty cells - //log.debug("empty cell at pos " + startColumn); - startColumn++; - } - - //Adjust in-row x offset for individual cells - ipdOffset += childRefIPD; - - - // the min is the maximum min of all cells - if (stackSize.min > min) { - min = stackSize.min; - } - // the optimum is the maximum of all optimums - if (stackSize.opt > opt) { - opt = stackSize.opt; - } - // the maximum is the largest maximum - if (stackSize.max > max) { - max = stackSize.max; - } - - if (childBreaks.size() > 0) { - breakList.add(childBreaks); - } - } - MinOptMax rowSize = new MinOptMax(min, opt, max); - LengthRangeProperty specifiedBPD = fobj.getBlockProgressionDimension(); - if (specifiedBPD.getEnum() != EN_AUTO) { - if ((specifiedBPD.getMaximum().getEnum() != EN_AUTO) - && (specifiedBPD.getMaximum().getLength().getValue() < rowSize.min)) { - log.warn("maximum height of row is smaller than the minimum " - + "height of its contents"); - } - MinOptMaxUtil.restrict(rowSize, specifiedBPD); - } - rowHeight = rowSize.opt; - - boolean fin = true; - startColumn = 1; - //Check if any of the cell LMs haven't finished, yet - while ((curGridUnit = getCellInfo(startColumn)) != null) { - Cell cellLM = curGridUnit.layoutManager; - if (cellLM == null) { - //skip empty cell - startColumn++; - continue; - } - if (!cellLM.isFinished()) { - fin = false; - break; - } - startColumn += cellLM.getFObj().getNumberColumnsSpanned(); - } - - setFinished(fin); - RowPosition rp = new RowPosition(this, breakList.size() - 1, breakList); - BreakPoss breakPoss = new BreakPoss(rp); - if (over) { - breakPoss.setFlag(BreakPoss.NEXT_OVERFLOWS, true); - } - breakPoss.setStackingSize(rowSize); - return breakPoss; - } - - /** - * Determines the grid units that are spanned by the given cell. - * @param cellLM table-cell LM - * @param startCell starting cell index (must be >= 1) - * @param spannedGridUnits List to receive the applicable grid units - */ - private void getGridUnitsForCell(Cell cellLM, int startCell, List spannedGridUnits) { - int count; - if (cellLM != null) { - count = cellLM.getFObj().getNumberColumnsSpanned(); - } else { - count = 1; - } - spannedGridUnits.clear(); - for (int i = 0; i < count; i++) { - spannedGridUnits.add(this.gridUnits.get(startCell + i - 1)); - } - } - - /** - * Reset the layoutmanager "iterator" so that it will start - * with the passed Position's generating LM - * on the next call to getChildLM. - * @param pos a Position returned by a child layout manager - * representing a potential break decision. - * If pos is null, then back up to the first child LM. - */ - protected void reset(Position pos) { - //LayoutManager curLM; // currently active LM - GridUnit curGridUnit; - int cellIndex = 1; - - if (pos == null) { - while ((curGridUnit = getCellInfo(cellIndex)) != null) { - if (curGridUnit.layoutManager != null) { - curGridUnit.layoutManager.resetPosition(null); - } - cellIndex++; - } - } else { - RowPosition rpos = (RowPosition)pos; - List breaks = rpos.cellBreaks; - - while ((curGridUnit = getCellInfo(cellIndex)) != null) { - if (curGridUnit.layoutManager != null) { - List childbreaks = (List)breaks.get(cellIndex); - curGridUnit.layoutManager.resetPosition( - (Position)childbreaks.get(childbreaks.size() - 1)); - } - cellIndex++; - } - } - - setFinished(false); - } - - /** - * Set the x position offset of this row. - * This is used to set the position of the areas returned by this row. - * - * @param off the x offset - */ - public void setXOffset(int off) { - xoffset = off; - } - - /** - * Set the y position offset of this row. - * This is used to set the position of the areas returned by this row. - * - * @param off the y offset - */ - public void setYOffset(int off) { - yoffset = off; - } - - /** - * Add the areas for the break points. - * This sets the offset of each cell as it is added. - * - * @param parentIter the position iterator - * @param layoutContext the layout context for adding areas - */ - public void addAreas(PositionIterator parentIter, - LayoutContext layoutContext) { - getParentArea(null); - BreakPoss bp1 = (BreakPoss)parentIter.peekNext(); - bBogus = !bp1.generatesAreas(); - if (!isBogus()) { - addID(fobj.getId()); - } - - Cell childLM; - int iStartPos = 0; - LayoutContext lc = new LayoutContext(0); - while (parentIter.hasNext()) { - RowPosition lfp = (RowPosition) parentIter.next(); - - //area exclusively for painting the row background - Block rowArea = getRowArea(); - if (rowArea != null) { - rowArea.setBPD(rowHeight); - rowArea.setIPD(referenceIPD); - rowArea.setXOffset(xoffset); - rowArea.setYOffset(yoffset); - parentLM.addChildArea(rowArea); - } - - for (Iterator iter = lfp.cellBreaks.iterator(); iter.hasNext();) { - List cellsbr = (List)iter.next(); - BreakPossPosIter breakPosIter; - breakPosIter = new BreakPossPosIter(cellsbr, 0, cellsbr.size()); - iStartPos = lfp.getLeafPos() + 1; - - while ((childLM = (Cell)breakPosIter.getNextChildLM()) != null) { - childLM.setXOffset(xoffset); - childLM.setYOffset(yoffset); - childLM.setRowHeight(rowHeight); - childLM.addAreas(breakPosIter, lc); - } - } - } - - flush(); - } - - /** - * Get the row height of the row after adjusting. - * Should only be called after adding the row areas. - * - * @return the row height of this row after adjustment - */ - public int getRowHeight() { - return rowHeight; - } - - /** - * Return an Area which can contain the passed childArea. The childArea - * may not yet have any content, but it has essential traits set. - * In general, if the LayoutManager already has an Area it simply returns - * it. Otherwise, it makes a new Area of the appropriate class. - * It gets a parent area for its area by calling its parent LM. - * Finally, based on the dimensions of the parent area, it initializes - * its own area. This includes setting the content IPD and the maximum - * BPD. - * - * @param childArea the child area - * @return the parent are for the child - */ - public Area getParentArea(Area childArea) { - return parentLM.getParentArea(childArea); - } - - /** - * Add the child. - * Rows return the areas returned by the child elements. - * This simply adds the area to the parent layout manager. - * - * @param childArea the child area - */ - public void addChildArea(Area childArea) { - parentLM.addChildArea(childArea); - } - - /** - * Reset the position of this layout manager. - * - * @param resetPos the position to reset to - */ - public void resetPosition(Position resetPos) { - if (resetPos == null) { - reset(null); - } - } - - - /** - * Get the area for this row for background. - * - * @return the row area - */ - public Block getRowArea() { - if (fobj.getCommonBorderPaddingBackground().hasBackground()) { - Block block = new Block(); - block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); - block.setPositioning(Block.ABSOLUTE); - TraitSetter.addBackground(block, fobj.getCommonBorderPaddingBackground()); - return block; - } else { - return null; - } - } - -} - diff --git a/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java index 638ee0681..639eaf3a2 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java @@ -139,7 +139,7 @@ public class TableAndCaptionLayoutManager extends BlockStackingLayoutManager { public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { getParentArea(null); - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); LayoutManager childLM; int iStartPos = 0; diff --git a/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java new file mode 100644 index 000000000..9958c2dae --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java @@ -0,0 +1,863 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr.table; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.fop.area.Block; +import org.apache.fop.area.Trait; +import org.apache.fop.fo.flow.Table; +import org.apache.fop.fo.flow.TableRow; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.fo.properties.LengthRangeProperty; +import org.apache.fop.layoutmgr.ElementListUtils; +import org.apache.fop.layoutmgr.KnuthBox; +import org.apache.fop.layoutmgr.KnuthElement; +import org.apache.fop.layoutmgr.KnuthPenalty; +import org.apache.fop.layoutmgr.KnuthPossPosIter; +import org.apache.fop.layoutmgr.LayoutContext; +import org.apache.fop.layoutmgr.LayoutManager; +import org.apache.fop.layoutmgr.MinOptMaxUtil; +import org.apache.fop.layoutmgr.Position; +import org.apache.fop.layoutmgr.PositionIterator; +import org.apache.fop.layoutmgr.TraitSetter; +import org.apache.fop.traits.MinOptMax; + +/** + * Layout manager for table contents, particularly managing the creation of combined element lists. + */ +public class TableContentLayoutManager { + + /** Logger **/ + private static Log log = LogFactory.getLog(TableContentLayoutManager.class); + + private TableLayoutManager tableLM; + private TableRowIterator trIter; + private TableRowIterator headerIter; + private TableRowIterator footerIter; + private LinkedList headerList; + private LinkedList footerList; + private int headerNetHeight = 0; + private int footerNetHeight = 0; + + private int startXOffset; + private int usedBPD; + + /** + * Main constructor + * @param parent Parent layout manager + */ + public TableContentLayoutManager(TableLayoutManager parent) { + this.tableLM = parent; + Table table = getTableLM().getTable(); + this.trIter = new TableRowIterator(table, getTableLM().getColumns(), TableRowIterator.BODY); + if (table.getTableHeader() != null) { + headerIter = new TableRowIterator(table, + getTableLM().getColumns(), TableRowIterator.HEADER); + } + if (table.getTableFooter() != null) { + footerIter = new TableRowIterator(table, + getTableLM().getColumns(), TableRowIterator.FOOTER); + } + } + + /** + * @return the table layout manager + */ + public TableLayoutManager getTableLM() { + return this.tableLM; + } + + /** @return true if the table uses the separate border model. */ + private boolean isSeparateBorderModel() { + return getTableLM().getTable().isSeparateBorderModel(); + } + + /** + * @return the column setup of this table + */ + public ColumnSetup getColumns() { + return getTableLM().getColumns(); + } + + /** @return the net header height */ + protected int getHeaderNetHeight() { + return this.headerNetHeight; + } + + /** @return the net footer height */ + protected int getFooterNetHeight() { + return this.headerNetHeight; + } + + /** @return the header element list */ + protected LinkedList getHeaderElements() { + return this.headerList; + } + + /** @return the footer element list */ + protected LinkedList getFooterElements() { + return this.footerList; + } + + /** + * @see org.apache.fop.layoutmgr.LayoutManager#getNextKnuthElements(org.apache.fop.layoutmgr.LayoutContext, int) + */ + public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + log.debug("==> Columns: " + getTableLM().getColumns()); + KnuthBox headerAsFirst = null; + KnuthBox headerAsSecondToLast = null; + KnuthBox footerAsLast = null; + if (headerIter != null) { + this.headerList = getKnuthElementsForRowIterator( + headerIter, context, alignment, TableRowIterator.HEADER); + ElementListUtils.removeLegalBreaks(this.headerList); + this.headerNetHeight = ElementListUtils.calcContentLength(this.headerList); + if (log.isDebugEnabled()) { + log.debug("==> Header: " + headerNetHeight + " - " + this.headerList); + } + TableHeaderFooterPosition pos = new TableHeaderFooterPosition( + getTableLM(), true, this.headerList); + KnuthBox box = new KnuthBox(headerNetHeight, pos, false); + if (getTableLM().getTable().omitHeaderAtBreak()) { + //We can simply add the table header at the beginning of the whole list + headerAsFirst = box; + //returnList.add(0, box); + } else { + headerAsSecondToLast = box; + //returnList.add(box); + } + } + if (footerIter != null) { + this.footerList = getKnuthElementsForRowIterator( + footerIter, context, alignment, TableRowIterator.FOOTER); + ElementListUtils.removeLegalBreaks(this.footerList); + this.footerNetHeight = ElementListUtils.calcContentLength(this.footerList); + if (log.isDebugEnabled()) { + log.debug("==> Footer: " + footerNetHeight + " - " + this.footerList); + } + if (true /*getTableLM().getTable().omitFooterAtBreak()*/) { + //We can simply add the table header at the end of the whole list + TableHeaderFooterPosition pos = new TableHeaderFooterPosition( + getTableLM(), false, this.footerList); + KnuthBox box = new KnuthBox(footerNetHeight, pos, false); + footerAsLast = box; + //returnList.add(box); + } + } + LinkedList returnList = getKnuthElementsForRowIterator( + trIter, context, alignment, TableRowIterator.BODY); + if (headerAsFirst != null) { + returnList.add(0, headerAsFirst); + } else if (headerAsSecondToLast != null) { + returnList.add(headerAsSecondToLast); + } + if (footerAsLast != null) { + returnList.add(footerAsLast); + } + return returnList; + } + + /** + * Creates Knuth elements by iterating over a TableRowIterator. + * @param iter TableRowIterator instance to fetch rows from + * @param context Active LayoutContext + * @param alignment alignment indicator + * @param bodyType Indicates what kind of body is being processed (BODY, HEADER or FOOTER) + * @return An element list + */ + private LinkedList getKnuthElementsForRowIterator(TableRowIterator iter, + LayoutContext context, int alignment, int bodyType) { + LinkedList returnList = new LinkedList(); + EffRow[] rowGroup = null; + while ((rowGroup = iter.getNextRowGroup()) != null) { + if (!isSeparateBorderModel()) { + resolveNormalBeforeAfterBordersForRowGroup(rowGroup, iter); + } + createElementsForRowGroup(context, alignment, bodyType, + returnList, rowGroup); + } + + //Remove last penalty + KnuthElement last = (KnuthElement)returnList.getLast(); + if (last.isPenalty() && last.getW() == 0 && last.getP() == 0) { + returnList.removeLast(); + } + return returnList; + } + + /** + * Resolves normal borders for a row group. + * @param iter Table row iterator to operate on + */ + private void resolveNormalBeforeAfterBordersForRowGroup(EffRow[] rowGroup, + TableRowIterator iter) { + for (int rgi = 0; rgi < rowGroup.length; rgi++) { + EffRow row = rowGroup[rgi]; + EffRow prev = iter.getCachedRow(row.getIndex() - 1); + EffRow next = iter.getCachedRow(row.getIndex() + 1); + if (next == null) { + //It wasn't read, yet, or we are at the last row + next = iter.getNextRow(); + iter.backToPreviewRow(); + } + if ((prev == null) && (iter == this.trIter) && (this.headerIter != null)) { + prev = this.headerIter.getLastRow(); + } + if ((next == null) && (iter == this.headerIter)) { + next = this.trIter.getFirstRow(); + } + if ((next == null) && (iter == this.trIter) && (this.footerIter != null)) { + next = this.footerIter.getFirstRow(); + } + if ((prev == null) && (iter == this.footerIter)) { + //TODO This could be bad for memory consumption because it already causes the + //whole body iterator to be prefetched! + prev = this.trIter.getLastRow(); + } + log.debug(prev + " - " + row + " - " + next); + + //Determine the grid units necessary for getting all the borders right + int guCount = row.getGridUnits().size(); + if (prev != null) { + guCount = Math.max(guCount, prev.getGridUnits().size()); + } + if (next != null) { + guCount = Math.max(guCount, next.getGridUnits().size()); + } + GridUnit gu = row.getGridUnit(0); + //Create empty grid units to hold resolved borders of neighbouring cells + //TODO maybe this needs to be done differently (and sooner) + for (int i = 0; i < guCount - row.getGridUnits().size(); i++) { + //TODO This block in untested! + int pos = row.getGridUnits().size() + i; + row.getGridUnits().add(new EmptyGridUnit(gu.getRow(), + this.tableLM.getColumns().getColumn(pos + 1), gu.getBody(), + pos)); + } + + //Now resolve normal borders + if (getTableLM().getTable().isSeparateBorderModel()) { + //nop, borders are already assigned at this point + } else { + for (int i = 0; i < row.getGridUnits().size(); i++) { + gu = row.getGridUnit(i); + GridUnit other; + int flags = 0; + if (prev != null && i < prev.getGridUnits().size()) { + other = prev.getGridUnit(i); + } else { + other = null; + } + if (other == null + || other.isEmpty() + || gu.isEmpty() + || gu.getPrimary() != other.getPrimary()) { + if ((iter == this.trIter) + && gu.getFlag(GridUnit.FIRST_IN_TABLE) + && (this.headerIter == null)) { + flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE; + } + if ((iter == this.headerIter) + && gu.getFlag(GridUnit.FIRST_IN_TABLE)) { + flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE; + } + gu.resolveBorder(other, + CommonBorderPaddingBackground.BEFORE, flags); + } + + flags = 0; + if (next != null && i < next.getGridUnits().size()) { + other = next.getGridUnit(i); + } else { + other = null; + } + if (other == null + || other.isEmpty() + || gu.isEmpty() + || gu.getPrimary() != other.getPrimary()) { + if ((iter == this.trIter) + && gu.getFlag(GridUnit.LAST_IN_TABLE) + && (this.footerIter == null)) { + flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE; + } + if ((iter == this.footerIter) + && gu.getFlag(GridUnit.LAST_IN_TABLE)) { + flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE; + } + gu.resolveBorder(other, + CommonBorderPaddingBackground.AFTER, flags); + } + } + + } + } + } + + /** + * Creates Knuth elements for a row group (see TableRowIterator.getNextRowGroup()). + * @param context Active LayoutContext + * @param alignment alignment indicator + * @param bodyType Indicates what kind of body is being processed (BODY, HEADER or FOOTER) + * @param returnList List to received the generated elements + * @param rowGroup row group to process + */ + private void createElementsForRowGroup(LayoutContext context, int alignment, + int bodyType, LinkedList returnList, + EffRow[] rowGroup) { + log.debug("Handling row group with " + rowGroup.length + " rows..."); + MinOptMax[] rowHeights = new MinOptMax[rowGroup.length]; + MinOptMax[] explicitRowHeights = new MinOptMax[rowGroup.length]; + EffRow row; + int maxColumnCount = 0; + List pgus = new java.util.ArrayList(); //holds a list of a row's primary grid units + for (int rgi = 0; rgi < rowGroup.length; rgi++) { + row = rowGroup[rgi]; + rowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE); + explicitRowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE); + + pgus.clear(); + TableRow tableRow = null; + int minContentHeight = 0; + int maxCellHeight = 0; + for (int j = 0; j < row.getGridUnits().size(); j++) { + maxColumnCount = Math.max(maxColumnCount, row.getGridUnits().size()); + GridUnit gu = row.getGridUnit(j); + if ((gu.isPrimary() || (gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan())) + && !gu.isEmpty()) { + PrimaryGridUnit primary = gu.getPrimary(); + + if (gu.isPrimary()) { + primary.getCellLM().setParent(tableLM); + + //Determine the table-row if any + if (tableRow == null && primary.getRow() != null) { + tableRow = primary.getRow(); + + //Check for bpd on row, see CSS21, 17.5.3 Table height algorithms + LengthRangeProperty bpd = tableRow.getBlockProgressionDimension(); + if (!bpd.getMinimum().isAuto()) { + minContentHeight = Math.max(minContentHeight, + bpd.getMinimum().getLength().getValue()); + } + MinOptMaxUtil.restrict(explicitRowHeights[rgi], bpd); + + } + + //Calculate width of cell + int spanWidth = 0; + for (int i = primary.getStartCol(); + i < primary.getStartCol() + primary.getCell().getNumberColumnsSpanned(); + i++) { + spanWidth += getTableLM().getColumns().getColumn(i + 1) + .getColumnWidth().getValue(); + } + LayoutContext childLC = new LayoutContext(0); + childLC.setStackLimit(context.getStackLimit()); //necessary? + childLC.setRefIPD(spanWidth); + + //Get the element list for the cell contents + LinkedList elems = primary.getCellLM().getNextKnuthElements(childLC, alignment); + primary.setElements(elems); + log.debug("Elements: " + elems); + } + + + //Calculate height of cell contents + primary.setContentLength(ElementListUtils.calcContentLength( + primary.getElements())); + maxCellHeight = Math.max(maxCellHeight, primary.getContentLength()); + + //Calculate height of row, see CSS21, 17.5.3 Table height algorithms + if (gu.isLastGridUnitRowSpan()) { + int effCellContentHeight = minContentHeight; + LengthRangeProperty bpd = primary.getCell().getBlockProgressionDimension(); + if (!bpd.getMinimum().isAuto()) { + effCellContentHeight = Math.max(effCellContentHeight, + bpd.getMinimum().getLength().getValue()); + } + if (gu.getRowSpanIndex() == 0) { + //TODO ATM only non-row-spanned cells are taken for this + MinOptMaxUtil.restrict(explicitRowHeights[rgi], bpd); + } + effCellContentHeight = Math.max(effCellContentHeight, + primary.getContentLength()); + int borderWidths; + if (isSeparateBorderModel()) { + borderWidths = primary.getBorders().getBorderBeforeWidth(false) + + primary.getBorders().getBorderAfterWidth(false); + } else { + borderWidths = primary.getHalfMaxBorderWidth(); + } + int padding = 0; + CommonBorderPaddingBackground cbpb + = primary.getCell().getCommonBorderPaddingBackground(); + padding += cbpb.getPaddingBefore(false); + padding += cbpb.getPaddingAfter(false); + int effRowHeight = effCellContentHeight + padding + borderWidths; + for (int previous = 0; previous < gu.getRowSpanIndex(); previous++) { + effRowHeight -= rowHeights[rgi - previous - 1].opt; + } + if (effRowHeight > rowHeights[rgi].min) { + //This is the new height of the (grid) row + MinOptMaxUtil.extendMinimum(rowHeights[rgi], effRowHeight, false); + row.setHeight(rowHeights[rgi]); + } + } + + if (gu.isPrimary()) { + pgus.add(primary); + } + } + } + + row.setExplicitHeight(explicitRowHeights[rgi]); + if (row.getHeight().opt > row.getExplicitHeight().max) { + log.warn("Contents of row " + row.getIndex() + " violate a maximum constraint " + + "in block-progression-dimension. Due to its contents the row grows " + + "to " + row.getHeight().opt + " millipoints. The row constraint resolve " + + "to " + row.getExplicitHeight()); + } + } + if (log.isDebugEnabled()) { + log.debug("rowGroup:"); + for (int i = 0; i < rowHeights.length; i++) { + log.debug(" height=" + rowHeights[i] + " explicit=" + explicitRowHeights[i]); + } + } + TableStepper stepper = new TableStepper(this); + LinkedList returnedList = stepper.getCombinedKnuthElementsForRowGroup( + rowGroup, maxColumnCount, bodyType); + if (returnedList != null) { + returnList.addAll(returnedList); + } + + } + + protected int getXOffsetOfGridUnit(GridUnit gu) { + int col = gu.getStartCol(); + return startXOffset + getTableLM().getColumns().getXOffset(col + 1); + } + + public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { + this.usedBPD = 0; + RowPainter painter = new RowPainter(layoutContext); + + List positions = new java.util.ArrayList(); + List footerElements = null; + Position lastPos = null; + while (parentIter.hasNext()) { + Position pos = (Position)parentIter.next(); + lastPos = pos; + if (pos instanceof TableHeaderFooterPosition) { + TableHeaderFooterPosition thfpos = (TableHeaderFooterPosition)pos; + //these positions need to be unpacked + if (thfpos.header) { + //header positions for the last part are the second-to-last element and need to + //be handled first before all other TableContentPositions + PositionIterator nestedIter = new KnuthPossPosIter(thfpos.nestedElements); + while (nestedIter.hasNext()) { + Position containedPos = (Position)nestedIter.next(); + if (containedPos instanceof TableContentPosition) { + TableContentPosition tcpos = (TableContentPosition)containedPos; + painter.handleTableContentPosition(tcpos); + } else { + log.debug("Ignoring position: " + containedPos); + } + } + painter.addAreasAndFlushRow(true); + } else { + //Positions for footers are simply added at the end + footerElements = thfpos.nestedElements; + } + } else if (pos instanceof TableHFPenaltyPosition) { + //ignore for now, see special handling below if break is at a penalty + //Only if the last position in this part/page us such a position it will be used + } else { + //leave order as is for the rest + positions.add(pos); + } + } + if (lastPos instanceof TableHFPenaltyPosition) { + TableHFPenaltyPosition penaltyPos = (TableHFPenaltyPosition)lastPos; + log.debug("Break at penalty!"); + if (penaltyPos.headerElements != null) { + //Header positions for the penalty position are in the last element and need to + //be handled first before all other TableContentPositions + PositionIterator nestedIter = new KnuthPossPosIter(penaltyPos.headerElements); + while (nestedIter.hasNext()) { + Position containedPos = (Position)nestedIter.next(); + if (containedPos instanceof TableContentPosition) { + TableContentPosition tcpos = (TableContentPosition)containedPos; + painter.handleTableContentPosition(tcpos); + } else { + log.debug("Ignoring position: " + containedPos); + } + } + painter.addAreasAndFlushRow(true); + } + if (penaltyPos.footerElements != null) { + footerElements = penaltyPos.footerElements; + } + } + + + Iterator posIter = positions.iterator(); + //Iterate over all steps + while (posIter.hasNext()) { + Position pos = (Position)posIter.next(); + if (pos instanceof TableContentPosition) { + TableContentPosition tcpos = (TableContentPosition)pos; + painter.handleTableContentPosition(tcpos); + } else { + log.debug("Ignoring position: " + pos); + } + } + painter.addAreasAndFlushRow(true); + + if (footerElements != null) { + //Positions for footers are simply added at the end + PositionIterator iter = new KnuthPossPosIter(footerElements); + while (iter.hasNext()) { + Position pos = (Position)iter.next(); + if (pos instanceof TableContentPosition) { + TableContentPosition tcpos = (TableContentPosition)pos; + painter.handleTableContentPosition(tcpos); + } else { + log.debug("Ignoring position: " + pos); + } + } + painter.addAreasAndFlushRow(true); + } + + painter.notifyEndOfSequence(); + this.usedBPD += painter.getAccumulatedBPD(); + } + + private class RowPainter { + + private TableRow rowFO = null; + private int colCount = getColumns().getColumnCount(); + private int yoffset = 0; + private int accumulatedBPD = 0; + private EffRow lastRow = null; + private LayoutContext layoutContext; + private int lastRowHeight = 0; + private int[] firstRow = new int[3]; + private Map[] rowOffsets = new Map[] {new java.util.HashMap(), + new java.util.HashMap(), new java.util.HashMap()}; + + //These three variables are our buffer to recombine the individual steps into cells + private PrimaryGridUnit[] gridUnits = new PrimaryGridUnit[colCount]; + private int[] start = new int[colCount]; + private int[] end = new int[colCount]; + private int[] partLength = new int[colCount]; + + public RowPainter(LayoutContext layoutContext) { + this.layoutContext = layoutContext; + Arrays.fill(firstRow, -1); + } + + public int getAccumulatedBPD() { + return this.accumulatedBPD; + } + + public void notifyEndOfSequence() { + this.accumulatedBPD += lastRowHeight; //for last row + } + + public void handleTableContentPosition(TableContentPosition tcpos) { + log.debug("===handleTableContentPosition(" + tcpos); + rowFO = null; + if (lastRow != tcpos.row && lastRow != null) { + addAreasAndFlushRow(false); + yoffset += lastRowHeight; + this.accumulatedBPD += lastRowHeight; + } + lastRow = tcpos.row; + Iterator partIter = tcpos.gridUnitParts.iterator(); + //Iterate over all grid units in the current step + while (partIter.hasNext()) { + GridUnitPart gup = (GridUnitPart)partIter.next(); + log.debug(">" + gup); + int colIndex = gup.pgu.getStartCol(); + if (gridUnits[colIndex] != gup.pgu) { + gridUnits[colIndex] = gup.pgu; + start[colIndex] = gup.start; + end[colIndex] = gup.end; + } else { + if (gup.end < end[colIndex]) { + throw new IllegalStateException("Internal Error: stepper problem"); + } + end[colIndex] = gup.end; + } + if (rowFO == null) { + //Find the row if any + rowFO = gridUnits[colIndex].getRow(); + } + } + } + + public int addAreasAndFlushRow(boolean forcedFlush) { + int actualRowHeight = 0; + int readyCount = 0; + + int bt = lastRow.getBodyType(); + rowOffsets[bt].put(new Integer(lastRow.getIndex()), new Integer(yoffset)); + + for (int i = 0; i < gridUnits.length; i++) { + if ((gridUnits[i] != null) + && (forcedFlush || (end[i] == gridUnits[i].getElements().size() - 1))) { + log.debug("getting len for " + i + " " + + start[i] + "-" + end[i]); + readyCount++; + int len = ElementListUtils.calcContentLength( + gridUnits[i].getElements(), start[i], end[i]); + partLength[i] = len; + log.debug("len of part: " + len); + if (start[i] == 0 && lastRow.getExplicitHeight().min > 0) { + len = Math.max(len, lastRow.getExplicitHeight().opt); + } + + //Now add the borders to the contentLength + if (isSeparateBorderModel()) { + len += gridUnits[i].getBorders().getBorderBeforeWidth(false); + len += gridUnits[i].getBorders().getBorderAfterWidth(false); + } + int startRow = Math.max(gridUnits[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); + } + } + if (readyCount == 0) { + return 0; + } + lastRowHeight = actualRowHeight; + + //Add areas for row + addRowBackgroundArea(rowFO, actualRowHeight, layoutContext.getRefIPD(), yoffset); + for (int i = 0; i < gridUnits.length; i++) { + GridUnit currentGU = lastRow.safelyGetGridUnit(i); + if ((gridUnits[i] != null) + && (forcedFlush || (end[i] == gridUnits[i].getElements().size() - 1)) + && (currentGU == null || currentGU.isLastGridUnitRowSpan())) { + //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]); + } + addAreasForCell(gridUnits[i], start[i], end[i], + layoutContext, lastRow, yoffset, + partLength[i], actualRowHeight); + gridUnits[i] = null; + start[i] = 0; + end[i] = 0; + partLength[i] = 0; + } + } + return actualRowHeight; + } + + private void addAreasForCell(PrimaryGridUnit pgu, int start, int end, + LayoutContext layoutContext, EffRow row, + int yoffset, int contentHeight, int rowHeight) { + int bt = row.getBodyType(); + if (firstRow[bt] < 0) { + firstRow[bt] = row.getIndex(); + } + //Determine the first row in this sequence + //TODO Maybe optimize since addAreasAndFlushRow uses almost the same code + int startRow = Math.max(pgu.getStartRow(), firstRow[bt]); + int effYOffset = ((Integer)rowOffsets[bt].get(new Integer(startRow))).intValue(); + int effCellHeight = rowHeight; + effCellHeight += yoffset - effYOffset; + log.debug("current row: " + row.getIndex()); + log.debug("start row: " + pgu.getStartRow() + " " + yoffset + " " + effYOffset); + log.debug("contentHeight: " + contentHeight + " rowHeight=" + rowHeight + + " effCellHeight=" + effCellHeight); + Cell cellLM = pgu.getCellLM(); + cellLM.setXOffset(getXOffsetOfGridUnit(pgu)); + cellLM.setYOffset(effYOffset); + cellLM.setContentHeight(contentHeight); + cellLM.setRowHeight(effCellHeight); + //cellLM.setRowHeight(row.getHeight().opt); + cellLM.addAreas(new KnuthPossPosIter(pgu.getElements(), + start, end + 1), layoutContext); + } + + } + + /** + * Get the area for a row for background. + * @param row the table-row object or null + * @return the row area or null if there's no background to paint + */ + public Block getRowArea(TableRow row) { + if (row == null || !row.getCommonBorderPaddingBackground().hasBackground()) { + return null; + } else { + Block block = new Block(); + block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); + block.setPositioning(Block.ABSOLUTE); + TraitSetter.addBackground(block, row.getCommonBorderPaddingBackground()); + return block; + } + } + + public void addRowBackgroundArea(TableRow row, int bpd, int ipd, int yoffset) { + //Add row background if any + Block rowBackground = getRowArea(row); + if (rowBackground != null) { + rowBackground.setBPD(bpd); + rowBackground.setIPD(ipd); + rowBackground.setXOffset(this.startXOffset); + rowBackground.setYOffset(yoffset); + getTableLM().addChildArea(rowBackground); + } + } + + + /** + * Sets the overall starting x-offset. Used for proper placement of cells. + * @param startXOffset starting x-offset (table's start-indent) + */ + public void setStartXOffset(int startXOffset) { + this.startXOffset = startXOffset; + } + + public int getUsedBPD() { + return this.usedBPD; + } + + protected static class GridUnitPart { + + protected PrimaryGridUnit pgu; + protected int start; + protected int end; + + protected GridUnitPart(PrimaryGridUnit pgu, int start, int end) { + this.pgu = pgu; + this.start = start; + this.end = end; + } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer("Part: "); + sb.append(start).append("-").append(end); + sb.append(" ").append(pgu); + return sb.toString(); + } + + } + + public static class TableContentPosition extends Position { + + protected List gridUnitParts; + protected EffRow row; + + protected TableContentPosition(LayoutManager lm, List gridUnitParts, + EffRow row) { + super(lm); + this.gridUnitParts = gridUnitParts; + this.row = row; + } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer("TableContentPosition {"); + sb.append(gridUnitParts); + sb.append("}"); + return sb.toString(); + } + } + + public static class TableHeaderFooterPosition extends Position { + + protected boolean header; + protected List nestedElements; + + protected TableHeaderFooterPosition(LayoutManager lm, + boolean header, List nestedElements) { + super(lm); + this.header = header; + this.nestedElements = nestedElements; + } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer("Table"); + sb.append(header ? "Header" : "Footer"); + sb.append("Position {"); + sb.append(nestedElements); + sb.append("}"); + return sb.toString(); + } + } + + public static class TableHFPenaltyPosition extends Position { + + protected List headerElements; + protected List footerElements; + + protected TableHFPenaltyPosition(LayoutManager lm) { + super(lm); + } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer("TableHFPenaltyPosition"); + sb.append(" {"); + sb.append("header:"); + sb.append(headerElements); + sb.append(", footer:"); + sb.append(footerElements); + sb.append("}"); + return sb.toString(); + } + } + + private class KnuthBoxCellWithBPD extends KnuthBox { + + private PrimaryGridUnit pgu; + + public KnuthBoxCellWithBPD(int w, PrimaryGridUnit pgu) { + super(w, null, true); + this.pgu = pgu; + } + } + +} diff --git a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java index 653c953c2..317a68447 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java @@ -21,14 +21,17 @@ package org.apache.fop.layoutmgr.table; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.PercentBase; import org.apache.fop.fo.flow.Table; +import org.apache.fop.fo.flow.TableColumn; import org.apache.fop.fo.properties.TableColLength; +import org.apache.fop.layoutmgr.BlockLevelLayoutManager; import org.apache.fop.layoutmgr.BlockStackingLayoutManager; +import org.apache.fop.layoutmgr.KnuthElement; +import org.apache.fop.layoutmgr.KnuthGlue; +import org.apache.fop.layoutmgr.KnuthPenalty; import org.apache.fop.layoutmgr.LayoutManager; import org.apache.fop.layoutmgr.LeafPosition; -import org.apache.fop.layoutmgr.BreakPoss; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.BreakPossPosIter; import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.TraitSetter; import org.apache.fop.area.Area; @@ -37,28 +40,26 @@ import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.SpaceVal; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; /** * LayoutManager for a table FO. - * A table consists of columns, table header, table footer and multiple + * A table consists of oldColumns, table header, table footer and multiple * table bodies. * The header, footer and body add the areas created from the table cells. - * The table then creates areas for the columns, bodies and rows + * The table then creates areas for the oldColumns, bodies and rows * the render background. */ -public class TableLayoutManager extends BlockStackingLayoutManager { +public class TableLayoutManager extends BlockStackingLayoutManager + implements BlockLevelLayoutManager { private Table fobj; - private List columns = null; + private TableContentLayoutManager contentLM; + private ColumnSetup columns = null; private Block curBlockArea; - private List bodyBreaks = new java.util.ArrayList(); - private BreakPoss headerBreak; - private BreakPoss footerBreak; - private boolean firstRowHandled = false; - private int referenceIPD; private boolean autoLayout = true; @@ -82,6 +83,7 @@ public class TableLayoutManager extends BlockStackingLayoutManager { public TableLayoutManager(Table node) { super(node); fobj = node; + this.columns = new ColumnSetup(node); } /** @return the table FO */ @@ -90,14 +92,12 @@ public class TableLayoutManager extends BlockStackingLayoutManager { } /** - * Set the columns for this table. - * - * @param cols the list of column layout managers + * @return the column setup for this table. */ - public void setColumns(List cols) { - columns = cols; + public ColumnSetup getColumns() { + return this.columns; } - + /** @see org.apache.fop.layoutmgr.AbstractLayoutManager#initProperties() */ protected void initProperties() { super.initProperties(); @@ -118,15 +118,11 @@ public class TableLayoutManager extends BlockStackingLayoutManager { } /** - * Get the next break possibility. - * The break possibility depends on the height of the header and footer - * and possible breaks inside the table body. - * - * @param context the layout context for finding breaks - * @return the next break possibility + * @see org.apache.fop.layoutmgr.LayoutManager#getNextKnuthElements(org.apache.fop.layoutmgr.LayoutContext, int) */ - public BreakPoss getNextBreakPoss(LayoutContext context) { - Body curLM; // currently active LM + public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { + + //Body curLM; // currently active LM referenceIPD = context.getRefIPD(); if (fobj.getInlineProgressionDimension().getOptimum().getEnum() != EN_AUTO) { @@ -146,200 +142,122 @@ public class TableLayoutManager extends BlockStackingLayoutManager { stackSize.add(spaceBefore); } - BreakPoss lastPos = null; - fobj.setLayoutDimension(PercentBase.BLOCK_IPD, referenceIPD); fobj.setLayoutDimension(PercentBase.BLOCK_BPD, context.getStackLimit().opt); fobj.setLayoutDimension(PercentBase.REFERENCE_AREA_IPD, referenceIPD); fobj.setLayoutDimension(PercentBase.REFERENCE_AREA_BPD, context.getStackLimit().opt); - if (columns == null) { - createColumnsFromFirstRow(); - } - // either works out table of column widths or if proportional-column-width function // is used works out total factor, so that value of single unit can be computed. int sumCols = 0; float factors = 0; - if (columns != null) { - for (Iterator i = columns.iterator(); i.hasNext(); ) { - Column column = (Column) i.next(); - Length width = column.getWidth(); - sumCols += width.getValue(); - if (width instanceof TableColLength) { - factors += ((TableColLength) width).getTableUnits(); - } + for (Iterator i = columns.iterator(); i.hasNext(); ) { + TableColumn column = (TableColumn) i.next(); + Length width = column.getColumnWidth(); + sumCols += width.getValue(); + if (width instanceof TableColLength) { + factors += ((TableColLength) width).getTableUnits(); } } - // sets TABLE_UNITS in case where one or more columns is defined using proportional-column-width + // sets TABLE_UNITS in case where one or more oldColumns is defined using + // proportional-column-width if (sumCols < contentIPD) { if (fobj.getLayoutDimension(PercentBase.TABLE_UNITS).floatValue() == 0.0) { fobj.setLayoutDimension(PercentBase.TABLE_UNITS, (contentIPD - sumCols) / factors); } } - - boolean headerFooterBuilt = false; - - while ((curLM = (Body)getChildLM()) != null) { - if (!headerFooterBuilt) { - //Calculate the headers and footers only when needed - MinOptMax headerSize = null; - if (getTable().getTableHeader() != null) { - if (!getTable().omitHeaderAtBreak() || !firstRowHandled) { - Body tableHeader = new Body(getTable().getTableHeader()); - tableHeader.setParent(this); - headerBreak = getHeight(tableHeader, context); - headerSize = headerBreak.getStackingSize(); - stackSize.add(headerSize); - } - } - //TODO Implement table-omit-footer-at-break once the page breaking - //is improved, so we don't have to do this twice - MinOptMax footerSize = null; - if (getTable().getTableFooter() != null) { - Body tableFooter = new Body(getTable().getTableFooter()); - tableFooter.setParent(this); - footerBreak = getHeight(tableFooter, context); - footerSize = footerBreak.getStackingSize(); - stackSize.add(footerSize); - } + LinkedList returnedList = null; + LinkedList contentList = new LinkedList(); + LinkedList returnList = new LinkedList(); + //Position returnPosition = new NonLeafPosition(this, null); + //Body prevLM = null; - if (stackSize.opt > context.getStackLimit().max) { - BreakPoss breakPoss = new BreakPoss( - new LeafPosition(this, 0)); - breakPoss.setFlag(BreakPoss.NEXT_OVERFLOWS, true); - breakPoss.setStackingSize(stackSize); - return breakPoss; + LayoutContext childLC = new LayoutContext(0); + childLC.setStackLimit( + MinOptMax.subtract(context.getStackLimit(), + stackSize)); + childLC.setRefIPD(context.getRefIPD()); + + contentLM = new TableContentLayoutManager(this); + returnedList = contentLM.getNextKnuthElements(childLC, alignment); + log.debug(returnedList); + + if (returnedList.size() == 1 + && ((KnuthElement) returnedList.getFirst()).isPenalty() + && ((KnuthPenalty) returnedList.getFirst()).getP() == -KnuthElement.INFINITE) { + // a descendant of this block has break-before + if (returnList.size() == 0) { + // the first child (or its first child ...) has + // break-before; + // all this block, including space before, will be put in + // the + // following page + //FIX ME + //bSpaceBeforeServed = false; } - headerFooterBuilt = true; - } - - // Make break positions - // Set up a LayoutContext - int ipd = context.getRefIPD(); - BreakPoss bp; - - LayoutContext childLC = new LayoutContext(0); - childLC.setStackLimit( - MinOptMax.subtract(context.getStackLimit(), - stackSize)); - childLC.setRefIPD(ipd); - - curLM.setColumns(columns); - - boolean over = false; - while (!curLM.isFinished()) { - if ((bp = curLM.getNextBreakPoss(childLC)) != null) { - if (stackSize.opt + bp.getStackingSize().opt > context.getStackLimit().max) { - // reset to last break - if (lastPos != null) { - LayoutManager lm = lastPos.getLayoutManager(); - lm.resetPosition(lastPos.getPosition()); - if (lm != curLM) { - curLM.resetPosition(null); - } - } else { - curLM.resetPosition(null); - } - over = true; - break; + contentList.addAll(returnedList); + + // "wrap" the Position inside each element + // moving the elements from contentList to returnList + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } else { + /* + if (prevLM != null) { + // there is a block handled by prevLM + // before the one handled by curLM + if (mustKeepTogether() + || prevLM.mustKeepWithNext() + || curLM.mustKeepWithPrevious()) { + // add an infinite penalty to forbid a break between + // blocks + contentList.add(new KnuthPenalty(0, + KnuthElement.INFINITE, false, + new Position(this), false)); + } else if (!((KnuthElement) contentList.getLast()).isGlue()) { + // add a null penalty to allow a break between blocks + contentList.add(new KnuthPenalty(0, 0, false, + new Position(this), false)); + } else { + // the last element in contentList is a glue; + // it is a feasible breakpoint, there is no need to add + // a penalty } - stackSize.add(bp.getStackingSize()); - lastPos = bp; - bodyBreaks.add(bp); - firstRowHandled = true; - - if (bp.nextBreakOverflows()) { - over = true; - break; + }*/ + contentList.addAll(returnedList); + /* + if (returnedList.size() == 0) { + //Avoid NoSuchElementException below (happens with empty blocks) + continue; + }*/ + if (((KnuthElement) returnedList.getLast()).isPenalty() + && ((KnuthPenalty) returnedList.getLast()).getP() == -KnuthElement.INFINITE) { + // a descendant of this block has break-after + if (false /*curLM.isFinished()*/) { + // there is no other content in this block; + // it's useless to add space after before a page break + setFinished(true); } - childLC.setStackLimit(MinOptMax.subtract( - context.getStackLimit(), stackSize)); + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; } - } - BreakPoss breakPoss = new BreakPoss( - new LeafPosition(this, bodyBreaks.size() - 1)); - if (over) { - breakPoss.setFlag(BreakPoss.NEXT_OVERFLOWS, true); - } - breakPoss.setStackingSize(stackSize); - return breakPoss; + } + wrapPositionElements(contentList, returnList); setFinished(true); - return null; - } - - private void createColumnsFromFirstRow() { - this.columns = new java.util.ArrayList(); - //TODO Create columns from first row here - //--> rule 2 in "fixed table layout", see CSS2, 17.5.2 - //Alternative: extend columns on-the-fly, but in this case we need the - //new property evaluation context so proportional-column-width() works - //correctly. - if (columns.size() == 0) { - Column col = new Column(getTable().getDefaultColumn()); - col.setParent(this); - this.columns.add(col); - } - } - - /** - * @param column the column to check - * @return true if the column is the first column - */ - public boolean isFirst(Column column) { - return (this.columns.size() == 0 || this.columns.get(0) == column); - } - - /** - * @param column the column to check - * @return true if the column is the last column - */ - public boolean isLast(Column column) { - return (this.columns.size() == 0 || this.columns.get(columns.size() - 1) == column); + return returnList; } - /** - * Get the break possibility and height of the table header or footer. - * - * @param lm the header or footer layout manager - * @param context the parent layout context - * @return the break possibility containing the stacking size - */ - protected BreakPoss getHeight(Body lm, LayoutContext context) { - int referenceIPD = context.getRefIPD(); - int contentIPD = referenceIPD - getIPIndents(); - BreakPoss bp; - - MinOptMax stackSize = new MinOptMax(); - - LayoutContext childLC = new LayoutContext(0); - childLC.setStackLimit(context.getStackLimit()); - childLC.setRefIPD(contentIPD); - - lm.setColumns(columns); - - List breaks = new java.util.ArrayList(); - while (!lm.isFinished()) { - if ((bp = lm.getNextBreakPoss(childLC)) != null) { - stackSize.add(bp.getStackingSize()); - breaks.add(bp); - childLC.setStackLimit(MinOptMax.subtract( - context.getStackLimit(), stackSize)); - } - } - BreakPoss breakPoss = new BreakPoss( - new SectionPosition(this, breaks.size() - 1, breaks)); - breakPoss.setStackingSize(stackSize); - return breakPoss; - } - /** * The table area is a reference area that contains areas for - * columns, bodies, rows and the contents are in cells. + * oldColumns, bodies, rows and the contents are in cells. * * @param parentIter the position iterator * @param layoutContext the layout context for adding areas @@ -347,7 +265,7 @@ public class TableLayoutManager extends BlockStackingLayoutManager { public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { getParentArea(null); - addID(fobj.getId()); + getPSLM().addIDToPage(fobj.getId()); // if adjusted space before double adjust = layoutContext.getSpaceAdjust(); @@ -359,49 +277,14 @@ public class TableLayoutManager extends BlockStackingLayoutManager { // add column, body then row areas int tableHeight = 0; - Body childLM; + //Body childLM; LayoutContext lc = new LayoutContext(0); - // add table header areas - if (headerBreak != null) { - SectionPosition pos = (SectionPosition)headerBreak.getPosition(); - List list = pos.list; - PositionIterator breakPosIter = new BreakPossPosIter(list, 0, list.size() + 1); - while ((childLM = (Body)breakPosIter.getNextChildLM()) != null) { - childLM.setXOffset(startXOffset); - childLM.addAreas(breakPosIter, lc); - tableHeight += childLM.getBodyHeight(); - } - } - - int iStartPos = 0; - while (parentIter.hasNext()) { - LeafPosition lfp = (LeafPosition) parentIter.next(); - // Add the block areas to Area - PositionIterator breakPosIter = - new BreakPossPosIter(bodyBreaks, iStartPos, - lfp.getLeafPos() + 1); - iStartPos = lfp.getLeafPos() + 1; - while ((childLM = (Body)breakPosIter.getNextChildLM()) != null) { - childLM.setXOffset(startXOffset); - childLM.setYOffset(tableHeight); - childLM.addAreas(breakPosIter, lc); - tableHeight += childLM.getBodyHeight(); - } - } - // add footer areas - if (footerBreak != null) { - SectionPosition pos = (SectionPosition)footerBreak.getPosition(); - List list = pos.list; - PositionIterator breakPosIter = new BreakPossPosIter(list, 0, list.size() + 1); - while ((childLM = (Body)breakPosIter.getNextChildLM()) != null) { - childLM.setXOffset(startXOffset); - childLM.setYOffset(tableHeight); - childLM.addAreas(breakPosIter, lc); - tableHeight += childLM.getBodyHeight(); - } - } + lc.setRefIPD(referenceIPD - getIPIndents()); + contentLM.setStartXOffset(startXOffset); + contentLM.addAreas(parentIter, lc); + tableHeight += contentLM.getUsedBPD(); curBlockArea.setBPD(tableHeight); @@ -420,7 +303,7 @@ public class TableLayoutManager extends BlockStackingLayoutManager { // if adjusted space after addBlockSpacing(adjust, spaceAfter); - bodyBreaks.clear(); + //bodyBreaks.clear(); curBlockArea = null; } @@ -474,5 +357,47 @@ public class TableLayoutManager extends BlockStackingLayoutManager { } } + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#negotiateBPDAdjustment(int, org.apache.fop.layoutmgr.KnuthElement) + */ + public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) { + // TODO Auto-generated method stub + return 0; + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#discardSpace(org.apache.fop.layoutmgr.KnuthGlue) + */ + public void discardSpace(KnuthGlue spaceGlue) { + // TODO Auto-generated method stub + + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepTogether() + */ + public boolean mustKeepTogether() { + //TODO Keeps will have to be more sophisticated sooner or later + return ((BlockLevelLayoutManager)getParent()).mustKeepTogether() + || !fobj.getKeepTogether().getWithinPage().isAuto() + || !fobj.getKeepTogether().getWithinColumn().isAuto(); + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithPrevious() + */ + public boolean mustKeepWithPrevious() { + return !fobj.getKeepWithPrevious().getWithinPage().isAuto() + || !fobj.getKeepWithPrevious().getWithinColumn().isAuto(); + } + + /** + * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithNext() + */ + public boolean mustKeepWithNext() { + return !fobj.getKeepWithNext().getWithinPage().isAuto() + || !fobj.getKeepWithNext().getWithinColumn().isAuto(); + } + } diff --git a/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java b/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java new file mode 100644 index 000000000..e9e8665bb --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/table/TableRowIterator.java @@ -0,0 +1,439 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr.table; + +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.fop.fo.flow.Table; +import org.apache.fop.fo.flow.TableBody; +import org.apache.fop.fo.flow.TableCell; +import org.apache.fop.fo.flow.TableColumn; +import org.apache.fop.fo.flow.TableRow; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; + + +/** + *

Iterator that lets the table layout manager step over all rows of a table. + *

+ *

Note: This class is not thread-safe. + *

+ */ +public class TableRowIterator { + + /** Selects the list of table-body elements for iteration. */ + public static final int BODY = 0; + /** Selects the table-header element for iteration. */ + public static final int HEADER = 1; + /** Selects the table-footer element for iteration. */ + public static final int FOOTER = 2; + + /** Logger **/ + private static Log log = LogFactory.getLog(TableRowIterator.class); + + /** The table on with this instance operates. */ + protected Table table; + private ColumnSetup columns; + private int type; + + /** Holds the current row (TableCell instances) */ + private List currentRow = new java.util.ArrayList(); + /** Holds the grid units of cell from the last row while will span over the current row + * (GridUnit instance) */ + private List lastRowsSpanningCells = new java.util.ArrayList(); + private int currentRowIndex = -1; + //TODO rows should later be a Jakarta Commons LinkedList so concurrent modifications while + //using a ListIterator are possible + /** List of cache rows. */ + private List rows = new java.util.ArrayList(); + //private int indexOfFirstRowInList; + private int currentIndex = -1; + + //prefetch state + private ListIterator bodyIterator = null; + private ListIterator childInBodyIterator = null; + + public TableRowIterator(Table table, ColumnSetup columns, int what) { + this.table = table; + this.columns = columns; + this.type = what; + switch(what) { + case HEADER: { + List bodyList = new java.util.ArrayList(); + bodyList.add(table.getTableHeader()); + this.bodyIterator = bodyList.listIterator(); + break; + } + case FOOTER: { + List bodyList = new java.util.ArrayList(); + bodyList.add(table.getTableFooter()); + this.bodyIterator = bodyList.listIterator(); + break; + } + default: { + this.bodyIterator = table.getChildNodes(); + } + } + } + + public void prefetchAll() { + while (prefetchNext()) { + log.trace("found row..."); + } + } + + /** + * Returns the next row group if any. A row group in this context is the minimum number of + * consecutive rows which contains all spanned grid units of its cells. + * @return the next row group, or null + */ + public EffRow[] getNextRowGroup() { + EffRow firstRowInGroup = getNextRow(); + if (firstRowInGroup == null) { + return null; + } + EffRow lastRowInGroup = firstRowInGroup; + int lastIndex = lastRowInGroup.getIndex(); + boolean allFinished = true; + do { + Iterator iter = lastRowInGroup.getGridUnits().iterator(); + while (iter.hasNext()) { + GridUnit gu = (GridUnit)iter.next(); + if (!gu.isLastGridUnitRowSpan()) { + allFinished = false; + break; + } + } + if (!allFinished) { + lastIndex = lastRowInGroup.getIndex(); + lastRowInGroup = getNextRow(); + if (lastRowInGroup == null) { + allFinished = true; + } + } + } while (!allFinished); + int rowCount = lastIndex - firstRowInGroup.getIndex() + 1; + EffRow[] rowGroup = new EffRow[rowCount]; + for (int i = 0; i < rowCount; i++) { + rowGroup[i] = getCachedRow(i + firstRowInGroup.getIndex()); + } + return rowGroup; + } + + public EffRow getNextRow() { + currentIndex++; + boolean moreRows = true; + while (moreRows && rows.size() < currentIndex + 1) { + moreRows = prefetchNext(); + } + if (currentIndex < rows.size()) { + return getCachedRow(currentIndex); + } else { + return null; + } + } + + public void backToPreviewRow() { + currentIndex--; + } + + public EffRow getFirstRow() { + if (rows.size() == 0) { + prefetchNext(); + } + return getCachedRow(0); + } + + public EffRow getLastRow() { + while (prefetchNext()) { + //nop + } + return getCachedRow(rows.size() - 1); + } + + public EffRow getCachedRow(int index) { + if (index < 0 || index >= rows.size()) { + return null; + } else { + return (EffRow)rows.get(index); + } + } + + private boolean prefetchNext() { + boolean firstInTable = false; + boolean firstInBody = false; + if (childInBodyIterator != null) { + if (!childInBodyIterator.hasNext()) { + //force skip on to next body + childInBodyIterator = null; + } + } + if (childInBodyIterator == null) { + if (bodyIterator.hasNext()) { + childInBodyIterator = ((TableBody)bodyIterator.next()).getChildNodes(); + if (rows.size() == 0) { + firstInTable = true; + } + firstInBody = true; + } else { + //no more rows + if (rows.size() > 0) { + getCachedRow(rows.size() - 1).setFlagForAllGridUnits( + GridUnit.LAST_IN_BODY, true); + if ((type == FOOTER || table.getTableFooter() == null) + && type != HEADER) { + getCachedRow(rows.size() - 1).setFlagForAllGridUnits( + GridUnit.LAST_IN_TABLE, true); + } + } + return false; + } + } + Object node = childInBodyIterator.next(); + this.currentRow.clear(); + this.currentRowIndex++; + TableRow rowFO = null; + if (node instanceof TableRow) { + rowFO = (TableRow)node; + ListIterator cellIterator = rowFO.getChildNodes(); + while (cellIterator.hasNext()) { + this.currentRow.add(cellIterator.next()); + } + } else if (node instanceof TableCell) { + this.currentRow.add(node); + if (!((TableCell)node).endsRow()) { + while (childInBodyIterator.hasNext()) { + TableCell cell = (TableCell)childInBodyIterator.next(); + if (cell.startsRow()) { + //next row already starts here, one step back + childInBodyIterator.previous(); + break; + } + this.currentRow.add(cell); + if (cell.endsRow()) { + break; + } + } + } + } else { + throw new IllegalStateException("Illegal class found: " + node.getClass().getName()); + } + EffRow gridUnits = buildGridRow(this.currentRow, rowFO); + if (firstInBody) { + gridUnits.setFlagForAllGridUnits(GridUnit.FIRST_IN_BODY, true); + } + if (firstInTable && (type == HEADER || table.getTableHeader() == null) + && type != FOOTER) { + gridUnits.setFlagForAllGridUnits(GridUnit.FIRST_IN_TABLE, true); + } + log.debug(gridUnits); + rows.add(gridUnits); + return true; + } + + private void safelySetListItem(List list, int position, Object obj) { + while (position >= list.size()) { + list.add(null); + } + list.set(position, obj); + } + + private Object safelyGetListItem(List list, int position) { + if (position >= list.size()) { + return null; + } else { + return list.get(position); + } + } + + private EffRow buildGridRow(List cells, TableRow rowFO) { + EffRow row = new EffRow(this.currentRowIndex, type); + List gridUnits = row.getGridUnits(); + + TableBody bodyFO = null; + + //Create all row-spanned grid units based on information from the last row + int colnum = 1; + ListIterator spanIter = lastRowsSpanningCells.listIterator(); + GridUnit[] horzSpan = null; + while (spanIter.hasNext()) { + GridUnit gu = (GridUnit)spanIter.next(); + if (gu != null) { + if (gu.getColSpanIndex() == 0) { + horzSpan = new GridUnit[gu.getCell().getNumberColumnsSpanned()]; + } + GridUnit newGU = gu.createNextRowSpanningGridUnit(); + newGU.setRow(rowFO); + safelySetListItem(gridUnits, colnum - 1, newGU); + horzSpan[newGU.getColSpanIndex()] = newGU; + if (newGU.isLastGridUnitColSpan()) { + //Add the array of row-spanned grid units to the primary grid unit + newGU.getPrimary().addRow(horzSpan); + horzSpan = null; + } + if (newGU.isLastGridUnitRowSpan()) { + spanIter.set(null); + } else { + spanIter.set(newGU); + } + } + colnum++; + } + + //Transfer available cells to their slots + colnum = 1; + ListIterator iter = cells.listIterator(); + while (iter.hasNext()) { + TableCell cell = (TableCell)iter.next(); + + if (cell.hasColumnNumber()) { + colnum = cell.getColumnNumber(); + } else { + //Skip columns with spanning grid units + while (safelyGetListItem(gridUnits, colnum - 1) != null) { + colnum++; + } + } + + if (safelyGetListItem(gridUnits, colnum - 1) != null) { + log.error("Overlapping cell at position " + colnum); + //TODO throw layout exception + } + TableColumn col = columns.getColumn(colnum); + + //Add grid unit for primary grid unit + PrimaryGridUnit gu = new PrimaryGridUnit(cell, col, colnum - 1, this.currentRowIndex); + safelySetListItem(gridUnits, colnum - 1, gu); + boolean hasRowSpanningLeft = !gu.isLastGridUnitRowSpan(); + if (hasRowSpanningLeft) { + safelySetListItem(lastRowsSpanningCells, colnum - 1, gu); + } + + if (gu.hasSpanning()) { + //Add grid units on spanned slots if any + horzSpan = new GridUnit[cell.getNumberColumnsSpanned()]; + horzSpan[0] = gu; + for (int j = 1; j < cell.getNumberColumnsSpanned(); j++) { + colnum++; + GridUnit guSpan = new GridUnit(gu, columns.getColumn(colnum), colnum - 1, j); + if (safelyGetListItem(gridUnits, colnum - 1) != null) { + log.error("Overlapping cell at position " + colnum); + //TODO throw layout exception + } + safelySetListItem(gridUnits, colnum - 1, guSpan); + if (hasRowSpanningLeft) { + safelySetListItem(lastRowsSpanningCells, colnum - 1, gu); + } + horzSpan[j] = guSpan; + } + gu.addRow(horzSpan); + } + + //Gather info for empty grid units (used later) + if (bodyFO == null) { + bodyFO = gu.getBody(); + } + + colnum++; + } + + //Post-processing the list (looking for gaps and resolve start and end borders) + fillEmptyGridUnits(gridUnits, rowFO, bodyFO); + resolveStartEndBorders(gridUnits); + + return row; + } + + private void fillEmptyGridUnits(List gridUnits, TableRow row, TableBody body) { + for (int pos = 1; pos <= gridUnits.size(); pos++) { + GridUnit gu = (GridUnit)gridUnits.get(pos - 1); + + //Empty grid units + if (gu == null) { + //Add grid unit + gu = new EmptyGridUnit(row, columns.getColumn(pos), body, + pos - 1); + gridUnits.set(pos - 1, gu); + } + + //Set flags + gu.setFlag(GridUnit.IN_FIRST_COLUMN, (pos == 1)); + gu.setFlag(GridUnit.IN_LAST_COLUMN, (pos == gridUnits.size())); + } + } + + private void resolveStartEndBorders(List gridUnits) { + for (int pos = 1; pos <= gridUnits.size(); pos++) { + GridUnit starting = (GridUnit)gridUnits.get(pos - 1); + + //Border resolution + if (table.isSeparateBorderModel()) { + starting.assignBorderForSeparateBorderModel(); + } else { + //Neighbouring grid unit at start edge + GridUnit start = null; + int find = pos - 1; + while (find >= 1) { + GridUnit candidate = (GridUnit)gridUnits.get(find - 1); + if (candidate.isLastGridUnitColSpan()) { + start = candidate; + break; + } + find--; + } + + //Ending grid unit for current cell + GridUnit ending = null; + if (starting.getCell() != null) { + pos += starting.getCell().getNumberColumnsSpanned() - 1; + } + ending = (GridUnit)gridUnits.get(pos - 1); + + //Neighbouring grid unit at end edge + GridUnit end = null; + find = pos + 1; + while (find <= gridUnits.size()) { + GridUnit candidate = (GridUnit)gridUnits.get(find - 1); + if (candidate.isPrimary()) { + end = candidate; + break; + } + find++; + } + //CommonBorderPaddingBackground borders = new CommonBorderPaddingBackground(); + starting.resolveBorder(start, + CommonBorderPaddingBackground.START); + //starting.setBorders(borders); + /* + if (starting != ending) { + borders = new CommonBorderPaddingBackground(); + }*/ + ending.resolveBorder(end, + CommonBorderPaddingBackground.END); + //ending.setBorders(borders); + //Only start and end borders here, before and after during layout + //TODO resolve before and after borders during layout + } + } + } + +} diff --git a/src/java/org/apache/fop/layoutmgr/table/TableStepper.java b/src/java/org/apache/fop/layoutmgr/table/TableStepper.java new file mode 100644 index 000000000..9600053ab --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/table/TableStepper.java @@ -0,0 +1,418 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.layoutmgr.table; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.layoutmgr.ElementListUtils; +import org.apache.fop.layoutmgr.KnuthBox; +import org.apache.fop.layoutmgr.KnuthElement; +import org.apache.fop.layoutmgr.KnuthPenalty; +import org.apache.fop.layoutmgr.table.TableContentLayoutManager.GridUnitPart; +import org.apache.fop.layoutmgr.table.TableContentLayoutManager.TableContentPosition; +import org.apache.fop.layoutmgr.table.TableContentLayoutManager.TableHFPenaltyPosition; + +/** + * This class processes row groups to create combined element lists for tables. + */ +public class TableStepper { + + /** Logger **/ + private static Log log = LogFactory.getLog(TableStepper.class); + + private TableContentLayoutManager tclm; + + private EffRow[] rowGroup; + private int totalHeight; + private int activeRow; + private List[] elementLists; + private int[] startRow; + private int[] start; + private int[] end; + private int[] widths; + private int[] baseWidth; + private int[] borderBefore; + private int[] borderAfter; + private boolean rowBacktrackForLastStep; + + /** + * Main constructor + * @param tclm The parent TableContentLayoutManager + */ + public TableStepper(TableContentLayoutManager tclm) { + this.tclm = tclm; + this.activeRow = 0; + } + + private void setup(int columnCount) { + elementLists = new List[columnCount]; + startRow = new int[columnCount]; + start = new int[columnCount]; + end = new int[columnCount]; + widths = new int[columnCount]; + baseWidth = new int[columnCount]; + borderBefore = new int[columnCount]; + borderAfter = new int[columnCount]; + Arrays.fill(end, -1); + } + + private EffRow getActiveRow() { + return rowGroup[activeRow]; + } + + private GridUnit getActiveGridUnit(int column) { + return getActiveRow().getGridUnit(column); + } + + private PrimaryGridUnit getActivePrimaryGridUnit(int column) { + return getActiveGridUnit(column).getPrimary(); + } + + private void calcTotalHeight() { + totalHeight = 0; + for (int i = 0; i < rowGroup.length; i++) { + totalHeight += rowGroup[i].getHeight().opt; + } + log.debug("totalHeight=" + totalHeight); + } + + private int getMaxRemainingHeight() { + int maxW = 0; + if (!rowBacktrackForLastStep) { + for (int i = 0; i < widths.length; i++) { + if (elementLists[i] == null) { + continue; + } + if (getActivePrimaryGridUnit(i).getCell().getNumberRowsSpanned() > 1) { + continue; + } + int len = widths[i]; + if (len > 0) { + len += borderBefore[i] + borderAfter[i]; + } + if (len == rowGroup[activeRow].getHeight().opt) { + //row is filled + maxW = 0; + break; + } + maxW = Math.max(maxW, rowGroup[activeRow].getHeight().opt - len); + } + } + for (int i = activeRow + 1; i < rowGroup.length; i++) { + maxW += rowGroup[i].getHeight().opt; + } + //log.debug("maxRemainingHeight=" + maxW); + return maxW; + } + + private void setupElementList(int column) { + GridUnit gu = getActiveGridUnit(column); + EffRow row = getActiveRow(); + if (gu.isPrimary() && !gu.isEmpty()) { + PrimaryGridUnit pgu = (PrimaryGridUnit)gu; + if (row.getExplicitHeight().min > 0) { + boolean contentsSmaller = ElementListUtils.removeLegalBreaks( + pgu.getElements(), row.getExplicitHeight()); + if (contentsSmaller) { + List list = new java.util.ArrayList(1); + list.add(new KnuthBoxCellWithBPD( + row.getExplicitHeight().opt, pgu)); + elementLists[column] = list; + } else { + //Copy elements (LinkedList) to array lists to improve + //element access performance + elementLists[column] = new java.util.ArrayList(pgu.getElements()); + } + } else { + //Copy elements (LinkedList) to array lists to improve + //element access performance + elementLists[column] = new java.util.ArrayList(pgu.getElements()); + } + if (isSeparateBorderModel()) { + borderBefore[column] = pgu.getBorders().getBorderBeforeWidth(false); + } else { + borderBefore[column] = pgu.getBorders().getBorderBeforeWidth(false) / 2; + } + start[column] = 0; + end[column] = -1; + widths[column] = 0; + startRow[column] = activeRow; + } + } + + private void initializeElementLists() { + for (int i = 0; i < start.length; i++) { + setupElementList(i); + } + } + + /** + * Creates the combined element list for a row group. + * @param rowGroup the row group + * @param maxColumnCount the maximum number of columns to expect + * @param bodyType Indicates what type of body is processed (boder, header or footer) + * @return the combined element list + */ + public LinkedList getCombinedKnuthElementsForRowGroup( + EffRow[] rowGroup, int maxColumnCount, int bodyType) { + this.rowGroup = rowGroup; + setup(maxColumnCount); + initializeElementLists(); + calcTotalHeight(); + + int laststep = 0; + int step; + int addedBoxLen = 0; + LinkedList returnList = new LinkedList(); + while ((step = getNextStep(laststep)) > 0) { + if (rowBacktrackForLastStep) { + //Even though we've already switched to the next row, we have to + //calculate as if we were still on the previous row + activeRow--; + } + int increase = step - laststep; + int penaltyLen = step + getMaxRemainingHeight() - totalHeight; + int boxLen = step - addedBoxLen - penaltyLen; + addedBoxLen += boxLen; + + //Put all involved grid units into a list + List gridUnitParts = new java.util.ArrayList(maxColumnCount); + for (int i = 0; i < start.length; i++) { + if (end[i] >= start[i]) { + PrimaryGridUnit pgu = getActivePrimaryGridUnit(i); + if (start[i] == 0 && end[i] == 0 + && elementLists[i].size() == 1 + && elementLists[i].get(0) instanceof KnuthBoxCellWithBPD) { + //Special case: Cell with fixed BPD + gridUnitParts.add(new GridUnitPart(pgu, + 0, pgu.getElements().size() - 1)); + } else { + gridUnitParts.add(new GridUnitPart(pgu, start[i], end[i])); + } + } + } + //log.debug(">>> guPARTS: " + gridUnitParts); + + //Create elements for step + int effPenaltyLen = penaltyLen; + if (isSeparateBorderModel()) { + CommonBorderPaddingBackground borders + = getTableLM().getTable().getCommonBorderPaddingBackground(); + effPenaltyLen += borders.getBorderBeforeWidth(false); + effPenaltyLen += borders.getBorderAfterWidth(false); + } + TableContentPosition tcpos = new TableContentPosition(getTableLM(), + gridUnitParts, getActiveRow()); + log.debug(" - " + rowBacktrackForLastStep + " - " + activeRow + " - " + tcpos); + returnList.add(new KnuthBox(boxLen, tcpos, false)); + TableHFPenaltyPosition penaltyPos = new TableHFPenaltyPosition(getTableLM()); + if (bodyType == TableRowIterator.BODY) { + if (!getTableLM().getTable().omitHeaderAtBreak()) { + effPenaltyLen += tclm.getHeaderNetHeight(); + penaltyPos.headerElements = tclm.getHeaderElements(); + } + if (!getTableLM().getTable().omitFooterAtBreak()) { + effPenaltyLen += tclm.getFooterNetHeight(); + penaltyPos.footerElements = tclm.getFooterElements(); + } + } + returnList.add(new KnuthPenalty(effPenaltyLen, 0, false, penaltyPos, false)); + + log.debug("step=" + step + " (+" + increase + ")" + + " box=" + boxLen + + " penalty=" + penaltyLen + + " effPenalty=" + effPenaltyLen); + + laststep = step; + if (rowBacktrackForLastStep) { + //If row was set to previous, restore now + activeRow++; + } + } + return returnList; + } + + private int getNextStep(int lastStep) { + int[] backupWidths = new int[start.length]; + System.arraycopy(widths, 0, backupWidths, 0, backupWidths.length); + + //set starting points + int rowPendingIndicator = 0; + for (int i = 0; i < start.length; i++) { + if (elementLists[i] == null) { + continue; + } + if (end[i] < elementLists[i].size()) { + start[i] = end[i] + 1; + if (end[i] + 1 < elementLists[i].size() + && getActivePrimaryGridUnit(i).isLastGridUnitRowSpan()) { + rowPendingIndicator++; + } + } else { + start[i] = -1; //end of list reached + end[i] = -1; + } + } + + if (rowPendingIndicator == 0) { + if (activeRow < rowGroup.length - 1) { + activeRow++; + log.debug("===> new row: " + activeRow); + initializeElementLists(); + for (int i = 0; i < backupWidths.length; i++) { + if (end[i] < 0) { + backupWidths[i] = 0; + } + } + } + } + + //Get next possible sequence for each cell + int seqCount = 0; + for (int i = 0; i < start.length; i++) { + if (elementLists[i] == null) { + continue; + } + while (end[i] + 1 < elementLists[i].size()) { + end[i]++; + KnuthElement el = (KnuthElement)elementLists[i].get(end[i]); + if (el.isPenalty()) { + if (el.getP() < KnuthElement.INFINITE) { + //First legal break point + break; + } + } else if (el.isGlue()) { + KnuthElement prev = (KnuthElement)elementLists[i].get(end[i] - 1); + if (prev.isBox()) { + //Second legal break point + break; + } + widths[i] += el.getW(); + } else { + widths[i] += el.getW(); + } + } + if (end[i] < start[i]) { + widths[i] = backupWidths[i]; + } else { + seqCount++; + } + //log.debug("part " + start[i] + "-" + end[i] + " " + widths[i]); + if (end[i] + 1 >= elementLists[i].size()) { + //element list for this cell is finished + if (isSeparateBorderModel()) { + borderAfter[i] = getActivePrimaryGridUnit(i) + .getBorders().getBorderAfterWidth(false); + } else { + borderAfter[i] = getActivePrimaryGridUnit(i).getHalfMaxAfterBorderWidth(); + } + } else { + //element list for this cell is not finished + if (isSeparateBorderModel()) { + borderAfter[i] = getActivePrimaryGridUnit(i) + .getBorders().getBorderAfterWidth(false); + } else { + //TODO fix me! + borderAfter[i] = getActivePrimaryGridUnit(i).getHalfMaxAfterBorderWidth(); + } + } + log.debug("borders before=" + borderBefore[i] + " after=" + borderAfter[i]); + } + if (seqCount == 0) { + return 0; + } + + //Determine smallest possible step + int minStep = Integer.MAX_VALUE; + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < widths.length; i++) { + baseWidth[i] = 0; + for (int prevRow = 0; prevRow < startRow[i]; prevRow++) { + baseWidth[i] += rowGroup[prevRow].getHeight().opt; + } + baseWidth[i] += borderBefore[i] + borderAfter[i]; + if (end[i] >= start[i]) { + int len = baseWidth[i] + widths[i]; + sb.append(len + " "); + minStep = Math.min(len, minStep); + } + } + log.debug("candidate steps: " + sb); + + //Check for constellations that would result in overlapping borders + /* + for (int i = 0; i < widths.length; i++) { + + }*/ + + //Reset bigger-than-minimum sequences + rowBacktrackForLastStep = false; + for (int i = 0; i < widths.length; i++) { + int len = baseWidth[i] + widths[i]; + if (len > minStep) { + widths[i] = backupWidths[i]; + end[i] = start[i] - 1; + if (baseWidth[i] + widths[i] > minStep) { + log.debug("Meeeeep!"); + rowBacktrackForLastStep = true; + } + } + } + if (log.isDebugEnabled()) { + /*StringBuffer*/ sb = new StringBuffer(); + for (int i = 0; i < widths.length; i++) { + if (end[i] >= start[i]) { + sb.append(i + ": " + start[i] + "-" + end[i] + "(" + widths[i] + "), "); + } else { + sb.append(i + ": skip, "); + } + } + log.debug(sb.toString()); + } + + return minStep; + } + + + /** @return true if the table uses the separate border model. */ + private boolean isSeparateBorderModel() { + return getTableLM().getTable().isSeparateBorderModel(); + } + + /** @return the table layout manager */ + private TableLayoutManager getTableLM() { + return this.tclm.getTableLM(); + } + + private class KnuthBoxCellWithBPD extends KnuthBox { + + private PrimaryGridUnit pgu; + + public KnuthBoxCellWithBPD(int w, PrimaryGridUnit pgu) { + super(w, null, true); + this.pgu = pgu; + } + } + +} diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java index 2492ab129..2bbd89fad 100644 --- a/src/java/org/apache/fop/render/AbstractRenderer.java +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -259,16 +259,16 @@ public abstract class AbstractRenderer currentBPPosition = 0; currentIPPosition = 0; - RegionReference region = port.getRegion(); + RegionReference regionReference = port.getRegionReference(); handleRegionTraits(port); // shouldn't the viewport have the CTM - startVParea(region.getCTM()); + startVParea(regionReference.getCTM()); // do after starting viewport area - if (region.getRegionClass() == FO_REGION_BODY) { - renderBodyRegion((BodyRegion) region); + if (regionReference.getRegionClass() == FO_REGION_BODY) { + renderBodyRegion((BodyRegion) regionReference); } else { - renderRegion(region); + renderRegion(regionReference); } endVParea(); } @@ -374,19 +374,24 @@ public abstract class AbstractRenderer Span span = null; List spans = mr.getSpans(); + int saveBPPos = currentBPPosition; for (int count = 0; count < spans.size(); count++) { span = (Span) spans.get(count); int offset = (mr.getWidth() - - (span.getColumnCount() - 1) * mr.getColumnGap()) - / span.getColumnCount() + mr.getColumnGap(); + - (mr.getColumnCount() - 1) * mr.getColumnGap()) + / mr.getColumnCount() + mr.getColumnGap(); for (int c = 0; c < span.getColumnCount(); c++) { NormalFlow flow = (NormalFlow) span.getNormalFlow(c); - renderFlow(flow); - currentIPPosition += offset; + if (flow != null) { + currentBPPosition = saveBPPos; + renderFlow(flow); + currentIPPosition += flow.getIPD(); + currentIPPosition += offset; + } } currentIPPosition = saveIPPos; - currentBPPosition += span.getHeight(); + currentBPPosition = saveBPPos + span.getHeight(); } } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index e7e4e8613..daefe87f4 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -517,7 +517,7 @@ public class PDFRenderer extends PrintRenderer { float width = (float)(viewArea.getWidth() / 1000f); float height = (float)(viewArea.getHeight() / 1000f); - if (region.getRegion().getRegionClass() == FO_REGION_BODY) { + if (region.getRegionReference().getRegionClass() == FO_REGION_BODY) { currentBPPosition = region.getBorderAndPaddingWidthBefore(); currentIPPosition = region.getBorderAndPaddingWidthStart(); } @@ -655,18 +655,18 @@ public class PDFRenderer extends PrintRenderer { } } - boolean b[] = new boolean[] { + boolean[] b = new boolean[] { (bpsBefore != null), (bpsEnd != null), (bpsAfter != null), (bpsStart != null)}; if (!b[0] && !b[1] && !b[2] && !b[3]) { return; } - float bw[] = new float[] { + float[] bw = new float[] { (b[0] ? bpsBefore.width / 1000f : 0.0f), (b[1] ? bpsEnd.width / 1000f : 0.0f), (b[2] ? bpsAfter.width / 1000f : 0.0f), (b[3] ? bpsStart.width / 1000f : 0.0f)}; - float clipw[] = new float[] { + float[] clipw = new float[] { BorderProps.getClippedWidth(bpsBefore) / 1000f, BorderProps.getClippedWidth(bpsEnd) / 1000f, BorderProps.getClippedWidth(bpsAfter) / 1000f, @@ -678,7 +678,7 @@ public class PDFRenderer extends PrintRenderer { width -= clipw[3]; width -= clipw[1]; - boolean slant[] = new boolean[] { + boolean[] slant = new boolean[] { (b[3] && b[0]), (b[0] && b[1]), (b[1] && b[2]), (b[2] && b[3])}; if (bpsBefore != null) { endTextObject(); @@ -710,7 +710,8 @@ public class PDFRenderer extends PrintRenderer { lineTo(sx2, innery); closePath(); clip(); - drawBorderLine(sx1a, outery, ex1a, innery, true, true, bpsBefore.style, bpsBefore.color); + drawBorderLine(sx1a, outery, ex1a, innery, true, true, + bpsBefore.style, bpsBefore.color); restoreGraphicsState(); } if (bpsEnd != null) { @@ -765,7 +766,7 @@ public class PDFRenderer extends PrintRenderer { if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) { sx1a -= clipw[3]; } - if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { ex1a += clipw[1]; } lineTo(ex1a, outery); @@ -785,7 +786,7 @@ public class PDFRenderer extends PrintRenderer { float sy1 = starty; float sy2 = (slant[0] ? sy1 + bw[0] - clipw[0] : sy1); float ey1 = sy1 + height; - float ey2 = (slant[3] ? ey1 - bw[2] + clipw[2]: ey1); + float ey2 = (slant[3] ? ey1 - bw[2] + clipw[2] : ey1); float outerx = startx - clipw[3]; float clipx = outerx + clipw[3]; float innerx = outerx + bw[3]; @@ -801,8 +802,8 @@ public class PDFRenderer extends PrintRenderer { if (bpsAfter != null && bpsAfter.mode == BorderProps.COLLAPSE_OUTER) { ey1a += clipw[2]; } - lineTo(outerx, sy1a); lineTo(outerx, ey1a); + lineTo(outerx, sy1a); } lineTo(clipx, sy1); lineTo(innerx, sy2); diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java index 8fccab4bd..74e993c4c 100644 --- a/src/java/org/apache/fop/render/xml/XMLRenderer.java +++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java @@ -378,7 +378,7 @@ public class XMLRenderer extends AbstractRenderer { addTraitAttributes(port); addAttribute("rect", port.getViewArea()); startElement("regionViewport", atts); - RegionReference region = port.getRegion(); + RegionReference region = port.getRegionReference(); atts.clear(); addAreaAttributes(region); addTraitAttributes(region); diff --git a/src/java/org/apache/fop/traits/LayoutProps.java b/src/java/org/apache/fop/traits/LayoutProps.java index d4dee2653..b0129f632 100644 --- a/src/java/org/apache/fop/traits/LayoutProps.java +++ b/src/java/org/apache/fop/traits/LayoutProps.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 1999-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.apache.fop.traits; +import org.apache.fop.datatypes.KeepValue; import org.apache.fop.fo.Constants; /** @@ -29,6 +30,13 @@ public class LayoutProps { public int breakBefore; // enum constant BreakBefore.xxx public int breakAfter; // enum constant BreakAfter.xxx + public KeepValue keepWithPrevious; /*LF*/ + public KeepValue keepWithNext; /*LF*/ + public KeepValue keepTogether; /*LF*/ + public int orphans; /*LF*/ + public int widows; /*LF*/ + public int blockProgressionUnit; /*LF*/ + public int lineStackingStrategy; /*LF*/ public boolean bIsSpan; public SpaceVal spaceBefore; public SpaceVal spaceAfter; diff --git a/test/layoutengine/testcases/breaks1.xml b/test/layoutengine/testcases/breaks1.xml index d0a5d8734..760e0697e 100644 --- a/test/layoutengine/testcases/breaks1.xml +++ b/test/layoutengine/testcases/breaks1.xml @@ -31,35 +31,35 @@ line1 - line2 + line2 break-before="column" line3 - line4 + line4 break-before="page" line5 - line6 + line6 break-before="even-page" line7 - line8 + line8 break-before="even-page" line9 - line10 + line10 break-before="odd-page" line11 - line12 + line12 break-before="odd-page" line13 - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/test/layoutengine/testcases/breaks2.xml b/test/layoutengine/testcases/breaks2.xml index 943152afd..beab46358 100644 --- a/test/layoutengine/testcases/breaks2.xml +++ b/test/layoutengine/testcases/breaks2.xml @@ -31,35 +31,35 @@ line1 - line2 + line2, last block had break-after="column" line3 - line4 + line4, last block had break-after="page" line5 - line6 + line6, last block had break-after="even-page" line7 - line8 + line8, last block had break-after="even-page" line9 - line10 + line10, last block had break-after="odd-page" line11 - line12 + line12, last block had break-after="odd-page" line13 - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/test/layoutengine/testcases/keep-together1.xml b/test/layoutengine/testcases/keep-together1.xml new file mode 100644 index 000000000..aa3c8085d --- /dev/null +++ b/test/layoutengine/testcases/keep-together1.xml @@ -0,0 +1,143 @@ + + + + + +

+ This test checks keep-together. +

+

+ Widows and Orphans are disabled in this test to avoid side-effects. +

+
+ + + + + + + + + + block1 + block2 + block3 + + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + + + block4 + block5 + + block6 + block7 + block8 + block9 + + block10 + + + + + block11 + block12 + block13 + + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + + + block14 + block15 + + block16 + block17 + block18 + block19 + + block20 + + + + + block21 + block22 + + block23 + block24 + block25 + + block26 + + + + + block31 + block32 + + + block33 + + block34 + block35 + + block36 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/test/layoutengine/testcases/keep-with-next1.xml b/test/layoutengine/testcases/keep-with-next1.xml new file mode 100644 index 000000000..120c2c806 --- /dev/null +++ b/test/layoutengine/testcases/keep-with-next1.xml @@ -0,0 +1,95 @@ + + + + + +

+ This test checks keep-with-next. +

+

+ Widows and Orphans are disabled in this test to avoid side-effects. +

+
+ + + + + + + + + + block1 + block2 + block3 + block4 + block5 + + block6 + block7 + block8 + block9 + block10 + + block11 + + + + + block21 + block22 + block23 + block24 + block25 + + block26 + block27 + block28 + block29 + block30 + + block31 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/test/layoutengine/testcases/keep-with-previous1.xml b/test/layoutengine/testcases/keep-with-previous1.xml new file mode 100644 index 000000000..8dd297802 --- /dev/null +++ b/test/layoutengine/testcases/keep-with-previous1.xml @@ -0,0 +1,95 @@ + + + + + +

+ This test checks keep-with-previous. +

+

+ Widows and Orphans are disabled in this test to avoid side-effects. +

+
+ + + + + + + + + + block1 + block2 + block3 + block4 + block5 + + block6 + block7 + block8 + block9 + block10 + + block11 + + + + + block21 + block22 + block23 + block24 + block25 + + block26 + block27 + block28 + block29 + block30 + + block31 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/test/layoutengine/testcases/multi-column1.xml b/test/layoutengine/testcases/multi-column1.xml new file mode 100644 index 000000000..af1741210 --- /dev/null +++ b/test/layoutengine/testcases/multi-column1.xml @@ -0,0 +1,72 @@ + + + + + +

+ This test checks multi-column documents. +

+
+ + + + + + + + + + + line1 + line2 + line3 + line4 + line5 + line6 + line7 + line8 + line9 + line10 + line11 + line12 + line13 + line14 + line15 + line16 + line17 + line18 + line19 + line20 + line21 + line22 + line23 + line24 + line25 + line26 + line27 + line28 + line29 + line30 + + + + + + + + +
diff --git a/test/layoutengine/testcases/multi-column2.xml b/test/layoutengine/testcases/multi-column2.xml new file mode 100644 index 000000000..a05d515e9 --- /dev/null +++ b/test/layoutengine/testcases/multi-column2.xml @@ -0,0 +1,76 @@ + + + + + +

+ This test checks multi-column documents. +

+
+ + + + + + + + + + + line1 + line2 + line3 + line4 + line5 + line6 + line7 + line8 + line9 + line10 + line11 + line12 + line13 + + + This line is spanned over all columns. + This line is spanned over all columns. + This line is spanned over all columns. + This line is spanned over all columns. + + + line1 + line2 + line3 + line4 + line5 + line6 + line7 + line8 + line9 + line10 + line11 + line12 + line13 + + + + + + + + +
diff --git a/test/layoutengine/testcases/page-master3.xml b/test/layoutengine/testcases/page-master3.xml index 1eda8648d..a0fd0b761 100644 --- a/test/layoutengine/testcases/page-master3.xml +++ b/test/layoutengine/testcases/page-master3.xml @@ -29,8 +29,8 @@ - - + + @@ -42,8 +42,10 @@ - - region-before + + region-before1 + region-before2 + region-before3 testing margin with reference-orientation="90" @@ -66,8 +68,8 @@ - + - + diff --git a/test/layoutengine/testcases/page-master4.xml b/test/layoutengine/testcases/page-master4.xml new file mode 100644 index 000000000..0aed299bd --- /dev/null +++ b/test/layoutengine/testcases/page-master4.xml @@ -0,0 +1,64 @@ + + + + + +

+ This test checks page-masters (all regions). +

+
+ + + + + + + + + + + + + + region-before1 + region-before2 + region-before3 + + + region-after1 with precedence and display-align="after" + + + region-start1 + region-start2 + region-start3 + + + region-end1 + region-end2 + region-end3 + + + testing all-regions1 + testing all-regions2 + testing all-regions3 + + + + + + +
-- 2.39.5