@@ -, +, @@ * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ * LayoutManager for a block-container FO. */ implements ConditionalElementListener { /** * logging instance */ private static Log log = LogFactory.getLog(BlockContainerLayoutManager.class); private BlockViewport viewportBlockArea; private Block referenceArea; private CommonAbsolutePosition abProps; private FODimension relDims; private CTM absoluteCTM; private Length width; private Length height; //private int vpContentIPD; private int vpContentBPD; // When viewport should grow with the content. private boolean autoHeight = true; private boolean inlineElementList = false; /* holds the (one-time use) fo:block space-before and -after properties. Large fo:blocks are split into multiple Area.Blocks to accomodate the subsequent regions (pages) they are placed on. space-before is applied at the beginning of the first Block and space-after at the end of the last Block used in rendering the fo:block. */ //TODO space-before|after: handle space-resolution rules private MinOptMax foBlockSpaceBefore; private MinOptMax foBlockSpaceAfter; private boolean discardBorderBefore; private boolean discardBorderAfter; private boolean discardPaddingBefore; private boolean discardPaddingAfter; private MinOptMax effSpaceBefore; private MinOptMax effSpaceAfter; /** * Create a new block container layout manager. * @param node block-container node to create the layout manager for. */ public BlockContainerLayoutManager(BlockContainer node) { super(node); } /** {@inheritDoc} */ public void initialize() { abProps = getBlockContainerFO().getCommonAbsolutePosition(); foBlockSpaceBefore = new SpaceVal(getBlockContainerFO().getCommonMarginBlock() .spaceBefore, this).getSpace(); foBlockSpaceAfter = new SpaceVal(getBlockContainerFO().getCommonMarginBlock() .spaceAfter, this).getSpace(); startIndent = getBlockContainerFO().getCommonMarginBlock().startIndent.getValue(this); endIndent = getBlockContainerFO().getCommonMarginBlock().endIndent.getValue(this); if (blockProgressionDirectionChanges()) { height = getBlockContainerFO().getInlineProgressionDimension() .getOptimum(this).getLength(); width = getBlockContainerFO().getBlockProgressionDimension() .getOptimum(this).getLength(); } else { height = getBlockContainerFO().getBlockProgressionDimension() .getOptimum(this).getLength(); width = getBlockContainerFO().getInlineProgressionDimension() .getOptimum(this).getLength(); } bpUnit = 0; //layoutProps.blockProgressionUnit; if (bpUnit == 0) { // use optimum space values adjustedSpaceBefore = getBlockContainerFO().getCommonMarginBlock() .spaceBefore.getSpace().getOptimum(this).getLength().getValue(this); adjustedSpaceAfter = getBlockContainerFO().getCommonMarginBlock() .spaceAfter.getSpace().getOptimum(this).getLength().getValue(this); } else { // use minimum space values adjustedSpaceBefore = getBlockContainerFO().getCommonMarginBlock() .spaceBefore.getSpace().getMinimum(this).getLength().getValue(this); adjustedSpaceAfter = getBlockContainerFO().getCommonMarginBlock() .spaceAfter.getSpace().getMinimum(this).getLength().getValue(this); } } private void resetSpaces() { this.discardBorderBefore = false; this.discardBorderAfter = false; this.discardPaddingBefore = false; this.discardPaddingAfter = false; this.effSpaceBefore = null; this.effSpaceAfter = null; } /** @return the content IPD */ protected int getRotatedIPD() { return getBlockContainerFO().getInlineProgressionDimension() .getOptimum(this).getLength().getValue(this); } private boolean needClip() { int overflow = getBlockContainerFO().getOverflow(); return (overflow == EN_HIDDEN || overflow == EN_ERROR_IF_OVERFLOW); } private int getBPIndents() { int indents = 0; /* TODO This is wrong isn't it? indents += getBlockContainerFO().getCommonMarginBlock() .spaceBefore.getOptimum(this).getLength().getValue(this); indents += getBlockContainerFO().getCommonMarginBlock() .spaceAfter.getOptimum(this).getLength().getValue(this); */ indents += getBlockContainerFO().getCommonBorderPaddingBackground() .getBPPaddingAndBorder(false, this); return indents; } private boolean isAbsoluteOrFixed() { return (abProps.absolutePosition == EN_ABSOLUTE) || (abProps.absolutePosition == EN_FIXED); } private boolean isFixed() { return (abProps.absolutePosition == EN_FIXED); } /** {@inheritDoc} */ public int getContentAreaBPD() { if (autoHeight) { return -1; } else { return this.vpContentBPD; } } /** {@inheritDoc} */ public List getNextKnuthElements(LayoutContext context, int alignment) { resetSpaces(); if (isAbsoluteOrFixed()) { return getNextKnuthElementsAbsolute(context, alignment); } autoHeight = false; //boolean rotated = (getBlockContainerFO().getReferenceOrientation() % 180 != 0); int maxbpd = context.getStackLimitBP().getOpt(); int allocBPD; if (height.getEnum() == EN_AUTO || (!height.isAbsolute() && getAncestorBlockAreaBPD() <= 0)) { //auto height when height="auto" or "if that dimension is not specified explicitly //(i.e., it depends on content's block-progression-dimension)" (XSL 1.0, 7.14.1) allocBPD = maxbpd; autoHeight = true; if (getBlockContainerFO().getReferenceOrientation() == 0) { //Cannot easily inline element list when ref-or="180" inlineElementList = true; } } else { allocBPD = height.getValue(this); //this is the content-height allocBPD += getBPIndents(); } vpContentBPD = allocBPD - getBPIndents(); referenceIPD = context.getRefIPD(); if (width.getEnum() == EN_AUTO) { updateContentAreaIPDwithOverconstrainedAdjust(); } else { int contentWidth = width.getValue(this); updateContentAreaIPDwithOverconstrainedAdjust(contentWidth); } double contentRectOffsetX = 0; contentRectOffsetX += getBlockContainerFO() .getCommonMarginBlock().startIndent.getValue(this); double contentRectOffsetY = 0; contentRectOffsetY += getBlockContainerFO() .getCommonBorderPaddingBackground().getBorderBeforeWidth(false); contentRectOffsetY += getBlockContainerFO() .getCommonBorderPaddingBackground().getPaddingBefore(false, this); updateRelDims(contentRectOffsetX, contentRectOffsetY, autoHeight); int availableIPD = referenceIPD - getIPIndents(); if (getContentAreaIPD() > availableIPD) { BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( getBlockContainerFO().getUserAgent().getEventBroadcaster()); eventProducer.objectTooWide(this, getBlockContainerFO().getName(), getContentAreaIPD(), context.getRefIPD(), getBlockContainerFO().getLocator()); } MinOptMax stackLimit = MinOptMax.getInstance(relDims.bpd); List returnedList; List contentList = new LinkedList(); List returnList = new LinkedList(); if (!breakBeforeServed) { breakBeforeServed = true; if (!context.suppressBreakBefore()) { if (addKnuthElementsForBreakBefore(returnList, context)) { return returnList; } } } if (!firstVisibleMarkServed) { addKnuthElementsForSpaceBefore(returnList, alignment); context.updateKeepWithPreviousPending(getKeepWithPrevious()); } addKnuthElementsForBorderPaddingBefore(returnList, !firstVisibleMarkServed); firstVisibleMarkServed = true; if (autoHeight && inlineElementList) { //Spaces, border and padding to be repeated at each break addPendingMarks(context); LayoutManager curLM; // currently active LM LayoutManager prevLM = null; // previously active LM while ((curLM = getChildLM()) != null) { LayoutContext childLC = new LayoutContext(0); childLC.copyPendingMarksFrom(context); // curLM is a ? childLC.setStackLimitBP(context.getStackLimitBP().minus(stackLimit)); childLC.setRefIPD(relDims.ipd); childLC.setWritingMode(getBlockContainerFO().getWritingMode()); if (curLM == this.childLMs.get(0)) { childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); //Handled already by the parent (break collapsing, see above) } // get elements from curLM returnedList = curLM.getNextKnuthElements(childLC, alignment); if (contentList.isEmpty() && childLC.isKeepWithPreviousPending()) { //Propagate keep-with-previous up from the first child context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); childLC.clearKeepWithPreviousPending(); } if (returnedList.size() == 1 && ((ListElement)returnedList.get(0)).isForcedBreak()) { // a descendant of this block has break-before /* if (returnList.size() == 0) { // the first child (or its first child ...) has // break-before; // all this block, including space before, will be put in // the // following page 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 addInBetweenBreak(contentList, context, childLC); } contentList.addAll(returnedList); if (returnedList.isEmpty()) { //Avoid NoSuchElementException below (happens with empty blocks) continue; } if (ElementListUtils.endsWithForcedBreak(returnedList)) { // 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; } } // propagate and clear context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); childLC.clearKeepsPending(); prevLM = curLM; } returnedList = new LinkedList(); wrapPositionElements(contentList, returnList); } else { returnList.add(refactoredBecauseOfDuplicateCode(contentRectOffsetX, contentRectOffsetY)); } addKnuthElementsForBorderPaddingAfter(returnList, true); addKnuthElementsForSpaceAfter(returnList, alignment); //All child content is processed. Only break-after can occur now, so... context.clearPendingMarks(); addKnuthElementsForBreakAfter(returnList, context); context.updateKeepWithNextPending(getKeepWithNext()); setFinished(true); return returnList; } private KnuthBox refactoredBecauseOfDuplicateCode(double contentRectOffsetX, double contentRectOffsetY) { MinOptMax range = MinOptMax.getInstance(relDims.ipd); BlockContainerBreaker breaker = new BlockContainerBreaker(this, range); breaker.doLayout(relDims.bpd, autoHeight); boolean contentOverflows = breaker.isOverflow(); if (autoHeight) { //Update content BPD now that it is known int newHeight = breaker.deferredAlg.totalWidth; if (blockProgressionDirectionChanges()) { setContentAreaIPD(newHeight); } else { vpContentBPD = newHeight; } updateRelDims(contentRectOffsetX, contentRectOffsetY, false); } Position bcPosition = new BlockContainerPosition(this, breaker); KnuthBox knuthBox = new KnuthBox(vpContentBPD, notifyPos(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) { BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( getBlockContainerFO().getUserAgent().getEventBroadcaster()); boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW); eventProducer.viewportOverflow(this, getBlockContainerFO().getName(), breaker.getOverflowAmount(), needClip(), canRecover, getBlockContainerFO().getLocator()); } return knuthBox; } private boolean blockProgressionDirectionChanges() { return getBlockContainerFO().getReferenceOrientation() % 180 != 0; } /** {@inheritDoc} */ public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, Position restartPosition, LayoutManager restartAtLM) { resetSpaces(); if (isAbsoluteOrFixed()) { return getNextKnuthElementsAbsolute(context, alignment); } autoHeight = false; //boolean rotated = (getBlockContainerFO().getReferenceOrientation() % 180 != 0); int maxbpd = context.getStackLimitBP().getOpt(); int allocBPD; if (height.getEnum() == EN_AUTO || (!height.isAbsolute() && getAncestorBlockAreaBPD() <= 0)) { //auto height when height="auto" or "if that dimension is not specified explicitly //(i.e., it depends on content's block-progression-dimension)" (XSL 1.0, 7.14.1) allocBPD = maxbpd; autoHeight = true; if (getBlockContainerFO().getReferenceOrientation() == 0) { //Cannot easily inline element list when ref-or="180" inlineElementList = true; } } else { allocBPD = height.getValue(this); //this is the content-height allocBPD += getBPIndents(); } vpContentBPD = allocBPD - getBPIndents(); referenceIPD = context.getRefIPD(); if (width.getEnum() == EN_AUTO) { updateContentAreaIPDwithOverconstrainedAdjust(); } else { int contentWidth = width.getValue(this); updateContentAreaIPDwithOverconstrainedAdjust(contentWidth); } double contentRectOffsetX = 0; contentRectOffsetX += getBlockContainerFO() .getCommonMarginBlock().startIndent.getValue(this); double contentRectOffsetY = 0; contentRectOffsetY += getBlockContainerFO() .getCommonBorderPaddingBackground().getBorderBeforeWidth(false); contentRectOffsetY += getBlockContainerFO() .getCommonBorderPaddingBackground().getPaddingBefore(false, this); updateRelDims(contentRectOffsetX, contentRectOffsetY, autoHeight); int availableIPD = referenceIPD - getIPIndents(); if (getContentAreaIPD() > availableIPD) { BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( getBlockContainerFO().getUserAgent().getEventBroadcaster()); eventProducer.objectTooWide(this, getBlockContainerFO().getName(), getContentAreaIPD(), context.getRefIPD(), getBlockContainerFO().getLocator()); } MinOptMax stackLimit = MinOptMax.getInstance(relDims.bpd); List returnedList; List contentList = new LinkedList(); List returnList = new LinkedList(); if (!breakBeforeServed) { breakBeforeServed = true; if (!context.suppressBreakBefore()) { if (addKnuthElementsForBreakBefore(returnList, context)) { return returnList; } } } if (!firstVisibleMarkServed) { addKnuthElementsForSpaceBefore(returnList, alignment); context.updateKeepWithPreviousPending(getKeepWithPrevious()); } addKnuthElementsForBorderPaddingBefore(returnList, !firstVisibleMarkServed); firstVisibleMarkServed = true; if (autoHeight && inlineElementList) { //Spaces, border and padding to be repeated at each break addPendingMarks(context); BlockLevelLayoutManager curLM; // currently active LM BlockLevelLayoutManager prevLM = null; // previously active LM LayoutContext childLC = new LayoutContext(0); if (lmStack.isEmpty()) { assert restartAtLM != null && restartAtLM.getParent() == this; curLM = (BlockLevelLayoutManager) restartAtLM; curLM.reset(); setCurrentChildLM(curLM); childLC.copyPendingMarksFrom(context); childLC.setStackLimitBP(context.getStackLimitBP().minus(stackLimit)); childLC.setRefIPD(relDims.ipd); childLC.setWritingMode(getBlockContainerFO().getWritingMode()); if (curLM == this.childLMs.get(0)) { childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); //Handled already by the parent (break collapsing, see above) } // get elements from curLM returnedList = curLM.getNextKnuthElements(childLC, alignment); } else { curLM = (BlockLevelLayoutManager) lmStack.pop(); setCurrentChildLM(curLM); childLC.copyPendingMarksFrom(context); childLC.setStackLimitBP(context.getStackLimitBP().minus(stackLimit)); childLC.setRefIPD(relDims.ipd); childLC.setWritingMode(getBlockContainerFO().getWritingMode()); if (curLM == this.childLMs.get(0)) { childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); //Handled already by the parent (break collapsing, see above) } // get elements from curLM returnedList = curLM.getNextKnuthElements(childLC, alignment, lmStack, restartPosition, restartAtLM); } if (contentList.isEmpty() && childLC.isKeepWithPreviousPending()) { //Propagate keep-with-previous up from the first child context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); childLC.clearKeepWithPreviousPending(); } if (returnedList.size() == 1 && ((ListElement)returnedList.get(0)).isForcedBreak()) { // a descendant of this block has break-before /* if (returnList.size() == 0) { // the first child (or its first child ...) has // break-before; // all this block, including space before, will be put in // the // following page 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 addInBetweenBreak(contentList, context, childLC); } contentList.addAll(returnedList); if (!returnedList.isEmpty()) { if (((ListElement) ListUtil.getLast(returnedList)) .isForcedBreak()) { // a descendant of this block has break-after if (curLM.isFinished()) { // there is no other content in this block; // it's useless to add space after before a page break setFinished(true); } returnedList = new LinkedList(); wrapPositionElements(contentList, returnList); return returnList; } } } // propagate and clear context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); childLC.clearKeepsPending(); prevLM = curLM; while ((curLM = (BlockLevelLayoutManager) getChildLM()) != null) { curLM.reset(); childLC = new LayoutContext(0); childLC.copyPendingMarksFrom(context); // curLM is a ? childLC.setStackLimitBP(context.getStackLimitBP().minus(stackLimit)); childLC.setRefIPD(relDims.ipd); childLC.setWritingMode(getBlockContainerFO().getWritingMode()); if (curLM == this.childLMs.get(0)) { childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); //Handled already by the parent (break collapsing, see above) } // get elements from curLM returnedList = curLM.getNextKnuthElements(childLC, alignment); if (contentList.isEmpty() && childLC.isKeepWithPreviousPending()) { //Propagate keep-with-previous up from the first child context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); childLC.clearKeepWithPreviousPending(); } if (returnedList.size() == 1 && ((ListElement)returnedList.get(0)).isForcedBreak()) { // a descendant of this block has break-before /* if (returnList.size() == 0) { // the first child (or its first child ...) has // break-before; // all this block, including space before, will be put in // the // following page 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 addInBetweenBreak(contentList, context, childLC); } contentList.addAll(returnedList); if (returnedList.isEmpty()) { //Avoid NoSuchElementException below (happens with empty blocks) continue; } if (((ListElement) ListUtil.getLast(returnedList)) .isForcedBreak()) { // a descendant of this block has break-after if (curLM.isFinished()) { // there is no other content in this block; // it's useless to add space after before a page break setFinished(true); } returnedList = new LinkedList(); wrapPositionElements(contentList, returnList); return returnList; } } // propagate and clear context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); childLC.clearKeepsPending(); prevLM = curLM; } returnedList = new LinkedList(); wrapPositionElements(contentList, returnList); } else { returnList.add(refactoredBecauseOfDuplicateCode(contentRectOffsetX, contentRectOffsetY)); } addKnuthElementsForBorderPaddingAfter(returnList, true); addKnuthElementsForSpaceAfter(returnList, alignment); //All child content is processed. Only break-after can occur now, so... context.clearPendingMarks(); addKnuthElementsForBreakAfter(returnList, context); context.updateKeepWithNextPending(getKeepWithNext()); setFinished(true); return returnList; } /** {@inheritDoc} */ public boolean isRestartable() { return true; } private List getNextKnuthElementsAbsolute(LayoutContext context, int alignment) { autoHeight = false; boolean bpDirectionChanges = blockProgressionDirectionChanges(); Point offset = getAbsOffset(); int allocBPD, allocIPD; if (height.getEnum() == EN_AUTO || (!height.isAbsolute() && getAncestorBlockAreaBPD() <= 0)) { //auto height when height="auto" or "if that dimension is not specified explicitly //(i.e., it depends on content's blockprogression-dimension)" (XSL 1.0, 7.14.1) allocBPD = 0; if (abProps.bottom.getEnum() != EN_AUTO) { int availHeight; if (isFixed()) { availHeight = (int)getCurrentPV().getViewArea().getHeight(); } else { availHeight = context.getStackLimitBP().getOpt(); } allocBPD = availHeight; allocBPD -= offset.y; if (abProps.bottom.getEnum() != EN_AUTO) { allocBPD -= abProps.bottom.getValue(this); if (allocBPD < 0) { //TODO Fix absolute b-c layout, layout may need to be defferred until //after page breaking when the size of the containing box is known. /* Warning disabled due to a interpretation mistake. * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2 log.error("The current combination of top and bottom properties results" + " in a negative extent for the block-container. 'bottom' may be" + " at most " + (allocBPD + abProps.bottom.getValue(this)) + " mpt," + " but was actually " + abProps.bottom.getValue(this) + " mpt." + " The nominal available height is " + availHeight + " mpt."); */ allocBPD = 0; } } else { if (allocBPD < 0) { /* Warning disabled due to a interpretation mistake. * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2 log.error("The current combination of top and bottom properties results" + " in a negative extent for the block-container. 'top' may be" + " at most " + availHeight + " mpt," + " but was actually " + offset.y + " mpt." + " The nominal available height is " + availHeight + " mpt."); */ allocBPD = 0; } } } else { allocBPD = context.getStackLimitBP().getOpt(); if (!bpDirectionChanges) { autoHeight = true; } } } else { allocBPD = height.getValue(this); //this is the content-height allocBPD += getBPIndents(); } if (width.getEnum() == EN_AUTO) { int availWidth; if (isFixed()) { availWidth = (int)getCurrentPV().getViewArea().getWidth(); } else { availWidth = context.getRefIPD(); } allocIPD = availWidth; if (abProps.left.getEnum() != EN_AUTO) { allocIPD -= abProps.left.getValue(this); } if (abProps.right.getEnum() != EN_AUTO) { allocIPD -= abProps.right.getValue(this); if (allocIPD < 0) { /* Warning disabled due to a interpretation mistake. * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2 log.error("The current combination of left and right properties results" + " in a negative extent for the block-container. 'right' may be" + " at most " + (allocIPD + abProps.right.getValue(this)) + " mpt," + " but was actually " + abProps.right.getValue(this) + " mpt." + " The nominal available width is " + availWidth + " mpt."); */ allocIPD = 0; } } else { if (allocIPD < 0) { /* Warning disabled due to a interpretation mistake. * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2 log.error("The current combination of left and right properties results" + " in a negative extent for the block-container. 'left' may be" + " at most " + allocIPD + " mpt," + " but was actually " + abProps.left.getValue(this) + " mpt." + " The nominal available width is " + availWidth + " mpt."); */ allocIPD = 0; } if (bpDirectionChanges) { autoHeight = true; } } } else { allocIPD = width.getValue(this); //this is the content-width allocIPD += getIPIndents(); } vpContentBPD = allocBPD - getBPIndents(); setContentAreaIPD(allocIPD - getIPIndents()); updateRelDims(0, 0, autoHeight); MinOptMax range = MinOptMax.getInstance(relDims.ipd); BlockContainerBreaker breaker = new BlockContainerBreaker(this, range); breaker.doLayout((autoHeight ? 0 : relDims.bpd), autoHeight); boolean contentOverflows = breaker.isOverflow(); if (autoHeight) { //Update content BPD now that it is known int newHeight = breaker.deferredAlg.totalWidth; if (bpDirectionChanges) { setContentAreaIPD(newHeight); } else { vpContentBPD = newHeight; } updateRelDims(0, 0, false); } List returnList = new LinkedList(); if (!breaker.isEmpty()) { Position bcPosition = new BlockContainerPosition(this, breaker); returnList.add(new KnuthBox(0, notifyPos(bcPosition), false)); //TODO Maybe check for page overflow when autoHeight=true if (!autoHeight & (contentOverflows)) { BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( getBlockContainerFO().getUserAgent().getEventBroadcaster()); boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW); eventProducer.viewportOverflow(this, getBlockContainerFO().getName(), breaker.getOverflowAmount(), needClip(), canRecover, getBlockContainerFO().getLocator()); } } setFinished(true); return returnList; } private void updateRelDims(double xOffset, double yOffset, boolean skipAutoHeight) { Rectangle2D rect = new Rectangle2D.Double( xOffset, yOffset, getContentAreaIPD(), this.vpContentBPD); relDims = new FODimension(0, 0); absoluteCTM = CTM.getCTMandRelDims( getBlockContainerFO().getReferenceOrientation(), getBlockContainerFO().getWritingMode(), rect, relDims); } 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; } /** {@inheritDoc} */ protected void observeElementList(List elementList) { ElementListObserver.observe(elementList, "block-container", bclm.getBlockContainerFO().getId()); } /** {@inheritDoc} */ protected boolean isPartOverflowRecoveryActivated() { //For block-containers, this must be disabled because of wanted overflow. return false; } /** {@inheritDoc} */ protected boolean isSinglePartFavored() { return true; } public int getDifferenceOfFirstPart() { PageBreakPosition pbp = (PageBreakPosition)this.deferredAlg.getPageBreaks().getFirst(); return pbp.difference; } public boolean isOverflow() { return !isEmpty() && ((deferredAlg.getPageBreaks().size() > 1) || (deferredAlg.totalWidth - deferredAlg.totalShrink) > deferredAlg.getLineWidth()); } public int getOverflowAmount() { return (deferredAlg.totalWidth - deferredAlg.totalShrink) - deferredAlg.getLineWidth(); } protected LayoutManager getTopLevelLM() { return bclm; } protected LayoutContext createLayoutContext() { LayoutContext lc = super.createLayoutContext(); lc.setRefIPD(ipd.getOpt()); lc.setWritingMode(getBlockContainerFO().getWritingMode()); return lc; } protected List getNextKnuthElements(LayoutContext context, int alignment) { LayoutManager curLM; // currently active LM List returnList = new LinkedList(); while ((curLM = getChildLM()) != null) { LayoutContext childLC = new LayoutContext(0); childLC.setStackLimitBP(context.getStackLimitBP()); childLC.setRefIPD(context.getRefIPD()); childLC.setWritingMode(getBlockContainerFO().getWritingMode()); List returnedList = null; if (!curLM.isFinished()) { returnedList = curLM.getNextKnuthElements(childLC, alignment); } if (returnedList != null) { bclm.wrapPositionElements(returnedList, returnList); } } SpaceResolver.resolveElementList(returnList); setFinished(true); return returnList; } protected int getCurrentDisplayAlign() { return getBlockContainerFO().getDisplayAlign(); } protected boolean hasMoreContent() { return !isFinished(); } protected void addAreas(PositionIterator posIter, LayoutContext context) { AreaAdditionUtil.addAreas(bclm, 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(PageBreakingAlgorithm alg, PageBreakPosition pbp) { //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. this.deferredAlg.removeAllPageBreaks(); this.addAreas(this.deferredAlg, this.deferredAlg.getPageBreaks().size(), this.deferredOriginalList, this.deferredEffectiveList); } /** * Starts the block-container breaking process. * * @param flowBPD the constant available block-progression-dimension (used for every part) * @param autoHeight true if warnings about overflows should be disabled because the * the BPD is really undefined (for footnote-separators, for example) */ public void doLayout(int flowBPD, boolean autoHeight) { LayoutContext childLC = createLayoutContext(); childLC.setStackLimitBP(MinOptMax.getInstance(flowBPD)); if (getCurrentDisplayAlign() == Constants.EN_X_FILL) { //EN_X_FILL is non-standard (by LF) alignment = Constants.EN_JUSTIFY; } else if (getCurrentDisplayAlign() == Constants.EN_X_DISTRIBUTE) { //EN_X_DISTRIBUTE is non-standard (by LF) alignment = Constants.EN_JUSTIFY; } else { alignment = Constants.EN_START; } alignmentLast = Constants.EN_START; if (isSinglePartFavored() && alignment == Constants.EN_JUSTIFY) { alignmentLast = Constants.EN_JUSTIFY; } childLC.setBPAlignment(alignment); BlockSequence blockList; this.blockLists = new java.util.ArrayList(); log.debug("PLM> flow BPD =" + flowBPD); //*** Phase 1: Get Knuth elements *** int nextSequenceStartsOn = Constants.EN_ANY; while (hasMoreContent()) { blockLists.clear(); nextSequenceStartsOn = getNextBlockList(childLC, nextSequenceStartsOn); //*** Phase 2: Alignment and breaking *** log.debug("PLM> blockLists.size() = " + blockLists.size()); for (blockListIndex = 0; blockListIndex < blockLists.size(); blockListIndex++) { blockList = (BlockSequence) blockLists.get(blockListIndex); //debug code start if (log.isDebugEnabled()) { log.debug(" blockListIndex = " + blockListIndex); log.debug(" sequence starts on " + getBreakClassName(blockList.startOn)); } observeElementList(blockList); //debug code end log.debug("PLM> start of algorithm (" + this.getClass().getName() + "), flow BPD =" + flowBPD); PageBreakingAlgorithm alg = new PageBreakingAlgorithm(getTopLevelLM(), getPageProvider(), createLayoutListener(), alignment, alignmentLast, footnoteSeparatorLength, isPartOverflowRecoveryActivated(), autoHeight, isSinglePartFavored()); BlockSequence effectiveList; if (getCurrentDisplayAlign() == Constants.EN_X_FILL) { /* justification */ effectiveList = justifyBoxes(blockList, alg, flowBPD); } else { /* no justification */ effectiveList = blockList; } alg.setConstantLineWidth(flowBPD); int optimalPageCount = alg.findBreakingPoints(effectiveList, 1, true, BreakingAlgorithm.ONLY_FORCED_BREAKS); //modified if (alg.getIPDdifference() != 0) { BreakingAlgorithm.KnuthNode optimalBreak = alg.getBestNodeBeforeIPDChange(); int positionIndex = optimalBreak.position; KnuthElement elementAtBreak = alg.getElement(positionIndex); Position positionAtBreak = elementAtBreak.getPosition(); if (!(positionAtBreak instanceof SpaceResolver.SpaceHandlingBreakPosition)) { throw new UnsupportedOperationException( "Don't know how to restart at position" + positionAtBreak); } /* Retrieve the original position wrapped into this space position */ positionAtBreak = positionAtBreak.getPosition(); LayoutManager restartAtLM = null; List firstElements = Collections.EMPTY_LIST; if (containsNonRestartableLM(positionAtBreak)) { if (alg.getIPDdifference() > 0) { EventBroadcaster eventBroadcaster = getCurrentChildLM().getFObj() .getUserAgent().getEventBroadcaster(); BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(eventBroadcaster); eventProducer.nonRestartableContentFlowingToNarrowerPage(this); } firstElements = new LinkedList(); boolean boxFound = false; Iterator iter = effectiveList.listIterator(positionIndex + 1); Position position = null; while (iter.hasNext() && (position == null || containsNonRestartableLM(position))) { positionIndex++; KnuthElement element = (KnuthElement) iter.next(); position = element.getPosition(); if (element.isBox()) { boxFound = true; firstElements.add(element); } else if (boxFound) { firstElements.add(element); } } if (position instanceof SpaceResolver.SpaceHandlingBreakPosition) { /* Retrieve the original position wrapped into this space position */ positionAtBreak = position.getPosition(); } else { positionAtBreak = null; } } if (positionAtBreak != null && positionAtBreak.getIndex() == -1) { /* * This is an indication that we are between two blocks * (possibly surrounded by another block), not inside a * paragraph. */ Position position; Iterator iter = effectiveList.listIterator(positionIndex + 1); do { KnuthElement nextElement = (KnuthElement) iter.next(); position = nextElement.getPosition(); } while (position == null || position instanceof SpaceResolver.SpaceHandlingPosition || position instanceof SpaceResolver.SpaceHandlingBreakPosition && position.getPosition().getIndex() == -1); LayoutManager surroundingLM = positionAtBreak.getLM(); while (position.getLM() != surroundingLM) { position = position.getPosition(); } restartAtLM = position.getPosition().getLM(); } log.trace("IPD changes after page " + optimalPageCount + " at index " + optimalBreak.position); addAreas(alg, optimalPageCount, blockList, effectiveList); blockLists.clear(); blockListIndex = -1; nextSequenceStartsOn = getNextBlockList(childLC, Constants.EN_COLUMN, positionAtBreak, restartAtLM, firstElements); } else { log.debug("PLM> iOptPageCount= " + optimalPageCount + " pageBreaks.size()= " + alg.getPageBreaks().size()); //*** Phase 3: Add areas *** doPhase3(alg, optimalPageCount, blockList, effectiveList); } } } } } private Point getAbsOffset() { int x = 0; int y = 0; if (abProps.left.getEnum() != EN_AUTO) { x = abProps.left.getValue(this); } else if (abProps.right.getEnum() != EN_AUTO && width.getEnum() != EN_AUTO) { x = getReferenceAreaIPD() - abProps.right.getValue(this) - width.getValue(this); } if (abProps.top.getEnum() != EN_AUTO) { y = abProps.top.getValue(this); } else if (abProps.bottom.getEnum() != EN_AUTO && height.getEnum() != EN_AUTO) { y = getReferenceAreaBPD() - abProps.bottom.getValue(this) - height.getValue(this); } return new Point(x, y); } /** {@inheritDoc} */ 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, MinOptMax.getInstance(layoutContext.getSpaceBefore())); } LayoutManager childLM; LayoutManager lastLM = null; LayoutContext lc = new LayoutContext(0); lc.setSpaceAdjust(layoutContext.getSpaceAdjust()); // 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; List positionList = new LinkedList(); Position pos; boolean bSpaceBefore = false; boolean bSpaceAfter = false; Position firstPos = null; Position lastPos = null; while (parentIter.hasNext()) { pos = (Position) parentIter.next(); if (pos.getIndex() >= 0) { if (firstPos == null) { firstPos = pos; } lastPos = pos; } Position innerPosition = pos; if (pos instanceof NonLeafPosition) { innerPosition = 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) { if (pos instanceof NonLeafPosition) { // 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.isEmpty() && bcpos == null) { // pos was in the element representing space-before bSpaceBefore = true; } else { // pos was in the element representing space-after bSpaceAfter = true; } } else { //ignore (probably a Position for a simple penalty between blocks) } } 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 } else { // innerPosition was created by another LM positionList.add(innerPosition); lastLM = innerPosition.getLM(); } } addId(); addMarkersToPage(true, isFirst(firstPos), isLast(lastPos)); 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 // 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 List splitList = new LinkedList(); int splitLength = 0; int iFirst = ((MappingPosition) positionList.get(0)) .getFirstIndex(); int iLast = ((MappingPosition) ListUtil.getLast(positionList)) .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.getWidth(); lastLM = element.getLayoutManager(); } } //log.debug("Adding areas from " + iFirst + " to " + iLast); //log.debug("splitLength= " + splitLength // + " (" + neededUnits(splitLength) + " units') " // + (neededUnits(splitLength) * bpUnit - splitLength) // + " spacing"); // add space before and / or after the paragraph // to reach a multiple of bpUnit if (bSpaceBefore && bSpaceAfter) { foBlockSpaceBefore = new SpaceVal(getBlockContainerFO() .getCommonMarginBlock().spaceBefore, this).getSpace(); foBlockSpaceAfter = new SpaceVal(getBlockContainerFO() .getCommonMarginBlock().spaceAfter, this).getSpace(); adjustedSpaceBefore = (neededUnits(splitLength + foBlockSpaceBefore.getMin() + foBlockSpaceAfter.getMin()) * bpUnit - splitLength) / 2; adjustedSpaceAfter = neededUnits(splitLength + foBlockSpaceBefore.getMin() + foBlockSpaceAfter.getMin()) * bpUnit - splitLength - adjustedSpaceBefore; } else if (bSpaceBefore) { adjustedSpaceBefore = neededUnits(splitLength + foBlockSpaceBefore.getMin()) * bpUnit - splitLength; } else { adjustedSpaceAfter = neededUnits(splitLength + foBlockSpaceAfter.getMin()) * bpUnit - splitLength; } //log.debug("space before = " + adjustedSpaceBefore // + " space after = " + adjustedSpaceAfter + " total = " + // (adjustedSpaceBefore + adjustedSpaceAfter + splitLength)); childPosIter = new KnuthPossPosIter(splitList, 0, splitList .size()); //} } while ((childLM = childPosIter.getNextChildLM()) != null) { // set last area flag lc.setFlags(LayoutContext.LAST_AREA, (layoutContext.isLastArea() && childLM == lastLM)); /*LF*/lc.setStackLimitBP(layoutContext.getStackLimitBP()); // Add the line areas to Area childLM.addAreas(childPosIter, lc); } } else { //Add child areas inside the reference area bcpos.getBreaker().addContainedAreas(); } addMarkersToPage(false, isFirst(firstPos), isLast(lastPos)); TraitSetter.addSpaceBeforeAfter(viewportBlockArea, layoutContext.getSpaceAdjust(), effSpaceBefore, effSpaceAfter); flush(); viewportBlockArea = null; referenceArea = null; resetSpaces(); notifyEndOfLayout(); } /** * Get the parent area for children of this block container. * This returns the current block container area * and creates it if required. * * {@inheritDoc} */ public Area getParentArea(Area childArea) { if (referenceArea == null) { boolean switchedProgressionDirection = blockProgressionDirectionChanges(); boolean allowBPDUpdate = autoHeight && !switchedProgressionDirection; viewportBlockArea = new BlockViewport(allowBPDUpdate); viewportBlockArea.addTrait(Trait.IS_VIEWPORT_AREA, Boolean.TRUE); viewportBlockArea.setIPD(getContentAreaIPD()); if (allowBPDUpdate) { viewportBlockArea.setBPD(0); } else { viewportBlockArea.setBPD(this.vpContentBPD); } transferForeignAttributes(viewportBlockArea); TraitSetter.setProducerID(viewportBlockArea, getBlockContainerFO().getId()); TraitSetter.addBorders(viewportBlockArea, getBlockContainerFO().getCommonBorderPaddingBackground(), discardBorderBefore, discardBorderAfter, false, false, this); TraitSetter.addPadding(viewportBlockArea, getBlockContainerFO().getCommonBorderPaddingBackground(), discardPaddingBefore, discardPaddingAfter, false, false, this); // TraitSetter.addBackground(viewportBlockArea, // getBlockContainerFO().getCommonBorderPaddingBackground(), // this); TraitSetter.addMargins(viewportBlockArea, getBlockContainerFO().getCommonBorderPaddingBackground(), startIndent, endIndent, this); viewportBlockArea.setCTM(absoluteCTM); viewportBlockArea.setClip(needClip()); /* if (getSpaceBefore() != 0) { viewportBlockArea.addTrait(Trait.SPACE_BEFORE, new Integer(getSpaceBefore())); } if (foBlockSpaceAfter.opt != 0) { viewportBlockArea.addTrait(Trait.SPACE_AFTER, new Integer(foBlockSpaceAfter.opt)); }*/ if (abProps.absolutePosition == EN_ABSOLUTE || abProps.absolutePosition == EN_FIXED) { Point offset = getAbsOffset(); viewportBlockArea.setXOffset(offset.x); viewportBlockArea.setYOffset(offset.y); } else { //nop } referenceArea = new Block(); referenceArea.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); TraitSetter.setProducerID(referenceArea, getBlockContainerFO().getId()); if (abProps.absolutePosition == EN_ABSOLUTE) { viewportBlockArea.setPositioning(Block.ABSOLUTE); } else if (abProps.absolutePosition == EN_FIXED) { viewportBlockArea.setPositioning(Block.FIXED); } // Set up dimensions // Must get dimensions from parent area /*Area parentArea =*/ parentLayoutManager.getParentArea(referenceArea); //int referenceIPD = parentArea.getIPD(); referenceArea.setIPD(relDims.ipd); // Get reference IPD from parentArea setCurrentArea(viewportBlockArea); // ??? for generic operations } return referenceArea; } /** * Add the child to the block container. * * {@inheritDoc} */ public void addChildArea(Area childArea) { if (referenceArea != null) { referenceArea.addBlock((Block) childArea); } } /** * Force current area to be added to parent area. * {@inheritDoc} */ protected void flush() { viewportBlockArea.addBlock(referenceArea, autoHeight); TraitSetter.addBackground(viewportBlockArea, getBlockContainerFO().getCommonBorderPaddingBackground(), this); super.flush(); } /** {@inheritDoc} */ public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) { // TODO Auto-generated method stub return 0; } /** {@inheritDoc} */ public void discardSpace(KnuthGlue spaceGlue) { // TODO Auto-generated method stub } /** {@inheritDoc} */ public KeepProperty getKeepTogetherProperty() { return getBlockContainerFO().getKeepTogether(); } /** {@inheritDoc} */ public KeepProperty getKeepWithPreviousProperty() { return getBlockContainerFO().getKeepWithPrevious(); } /** {@inheritDoc} */ public KeepProperty getKeepWithNextProperty() { return getBlockContainerFO().getKeepWithNext(); } /** * @return the BlockContainer node */ protected BlockContainer getBlockContainerFO() { return (BlockContainer) fobj; } // --------- Property Resolution related functions --------- // /** {@inheritDoc} */ public boolean getGeneratesReferenceArea() { return true; } /** {@inheritDoc} */ public boolean getGeneratesBlockArea() { return true; } /** {@inheritDoc} */ public void notifySpace(RelSide side, MinOptMax effectiveLength) { if (RelSide.BEFORE == side) { if (log.isDebugEnabled()) { log.debug(this + ": Space " + side + ", " + this.effSpaceBefore + "-> " + effectiveLength); } this.effSpaceBefore = effectiveLength; } else { if (log.isDebugEnabled()) { log.debug(this + ": Space " + side + ", " + this.effSpaceAfter + "-> " + effectiveLength); } this.effSpaceAfter = effectiveLength; } } /** {@inheritDoc} */ public void notifyBorder(RelSide side, MinOptMax effectiveLength) { if (effectiveLength == null) { if (RelSide.BEFORE == side) { this.discardBorderBefore = true; } else { this.discardBorderAfter = true; } } if (log.isDebugEnabled()) { log.debug(this + ": Border " + side + " -> " + effectiveLength); } } /** {@inheritDoc} */ public void notifyPadding(RelSide side, MinOptMax effectiveLength) { if (effectiveLength == null) { if (RelSide.BEFORE == side) { this.discardPaddingBefore = true; } else { this.discardPaddingAfter = true; } } if (log.isDebugEnabled()) { log.debug(this + ": Padding " + side + " -> " + effectiveLength); } }