Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id: AbstractBreaker.java 1328581 2012-04-21 04:45:14Z gadams $ */ package org.apache.fop.layoutmgr; import java.util.Collections; import java.util.Iterator; 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.events.EventBroadcaster; import org.apache.fop.fo.Constants; import org.apache.fop.layoutmgr.BreakingAlgorithm.KnuthNode; import org.apache.fop.traits.MinOptMax; import org.apache.fop.util.ListUtil; /** * Abstract base class for breakers (page breakers, static region handlers etc.). */ public abstract class AbstractBreaker { /** logging instance */ protected static final Log log = LogFactory.getLog(AbstractBreaker.class); /** * A page break position. */ public static class PageBreakPosition extends LeafPosition { // Percentage to adjust (stretch or shrink) double bpdAdjust; // CSOK: VisibilityModifier int difference; // CSOK: VisibilityModifier int footnoteFirstListIndex; // CSOK: VisibilityModifier int footnoteFirstElementIndex; // CSOK: VisibilityModifier int footnoteLastListIndex; // CSOK: VisibilityModifier int footnoteLastElementIndex; // CSOK: VisibilityModifier PageBreakPosition(LayoutManager lm, int breakIndex, // CSOK: ParameterNumber int ffli, int ffei, int flli, int flei, double bpdA, int diff) { super(lm, breakIndex); bpdAdjust = bpdA; difference = diff; footnoteFirstListIndex = ffli; footnoteFirstElementIndex = ffei; footnoteLastListIndex = flli; footnoteLastElementIndex = flei; } } /** * Helper method, mainly used to improve debug/trace output * @param breakClassId the {@link Constants} enum value. * @return the break class name */ static String getBreakClassName(int breakClassId) { switch (breakClassId) { case Constants.EN_ALL: return "ALL"; case Constants.EN_ANY: return "ANY"; case Constants.EN_AUTO: return "AUTO"; case Constants.EN_COLUMN: return "COLUMN"; case Constants.EN_EVEN_PAGE: return "EVEN PAGE"; case Constants.EN_LINE: return "LINE"; case Constants.EN_NONE: return "NONE"; case Constants.EN_ODD_PAGE: return "ODD PAGE"; case Constants.EN_PAGE: return "PAGE"; default: return "??? (" + String.valueOf(breakClassId) + ")"; } } /** * Helper class, extending the functionality of the * basic {@link BlockKnuthSequence}. */ public class BlockSequence extends BlockKnuthSequence { private static final long serialVersionUID = -5348831120146774118L; /** Number of elements to ignore at the beginning of the list. */ int ignoreAtStart = 0; // CSOK: VisibilityModifier /** Number of elements to ignore at the end of the list. */ int ignoreAtEnd = 0; // CSOK: VisibilityModifier /** * 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; private int displayAlign; /** * Creates a new BlockSequence. * @param startOn the kind of page the sequence should start on. * One of {@link Constants#EN_ANY}, {@link Constants#EN_COLUMN}, * {@link Constants#EN_ODD_PAGE}, or {@link Constants#EN_EVEN_PAGE}. * @param displayAlign the value for the display-align property */ public BlockSequence(int startOn, int displayAlign) { super(); this.startOn = startOn; this.displayAlign = displayAlign; } /** * @return the kind of page the sequence should start on. * One of {@link Constants#EN_ANY}, {@link Constants#EN_COLUMN}, * {@link Constants#EN_ODD_PAGE}, or {@link Constants#EN_EVEN_PAGE}. */ public int getStartOn() { return this.startOn; } /** @return the value for the display-align property */ public int getDisplayAlign() { return this.displayAlign; } /** * Finalizes a Knuth sequence. * @return a finalized sequence. */ public KnuthSequence endSequence() { return endSequence(null); } /** * Finalizes a Knuth sequence. * @param breakPosition a Position instance for the last penalty (may be null) * @return a finalized sequence. */ public KnuthSequence endSequence(Position breakPosition) { // remove glue and penalty item at the end of the paragraph while (this.size() > ignoreAtStart && !((KnuthElement) ListUtil.getLast(this)).isBox()) { ListUtil.removeLast(this); } if (this.size() > ignoreAtStart) { // add the elements representing the space at the end of the last line // and the forced break if (getDisplayAlign() == Constants.EN_X_DISTRIBUTE && isSinglePartFavored()) { this.add(new KnuthPenalty(0, -KnuthElement.INFINITE, false, breakPosition, false)); ignoreAtEnd = 1; } else { 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, breakPosition, false)); ignoreAtEnd = 3; } return this; } else { this.clear(); return null; } } /** * Finalizes a this {@link BlockSequence}, adding a terminating * penalty-glue-penalty sequence * @param breakPosition a Position instance pointing to the last penalty * @return the finalized {@link BlockSequence} */ public BlockSequence endBlockSequence(Position breakPosition) { KnuthSequence temp = endSequence(breakPosition); if (temp != null) { BlockSequence returnSequence = new BlockSequence(startOn, displayAlign); returnSequence.addAll(temp); returnSequence.ignoreAtEnd = this.ignoreAtEnd; return returnSequence; } else { return null; } } } // used by doLayout and getNextBlockList* private List<BlockSequence> blockLists; private boolean empty = true; /** desired text alignment */ protected int alignment; private int alignmentLast; /** footnote separator length */ protected MinOptMax footnoteSeparatorLength = MinOptMax.ZERO; /** @return current display alignment */ protected abstract int getCurrentDisplayAlign(); /** @return true if content not exhausted */ protected abstract boolean hasMoreContent(); /** * Tell the layout manager to add all the child areas implied * by Position objects which will be returned by the * Iterator. * * @param posIter the position iterator * @param context the context */ protected abstract void addAreas(PositionIterator posIter, LayoutContext context); /** @return top level layout manager */ protected abstract LayoutManager getTopLevelLM(); /** @return current child layout manager */ protected abstract LayoutManager getCurrentChildLM(); /** * Controls the behaviour of the algorithm in cases where the first element of a part * overflows a line/page. * @return true if the algorithm should try to send the element to the next line/page. */ protected boolean isPartOverflowRecoveryActivated() { return true; } /** * @return true if one a single part should be produced if possible (ex. for block-containers) */ protected boolean isSinglePartFavored() { return false; } /** * Returns the PageProvider if any. PageBreaker overrides this method because each * page may have a different available BPD which needs to be accessible to the breaking * algorithm. * @return the applicable PageProvider, or null if not applicable */ protected PageProvider getPageProvider() { return null; } /** * Creates and returns a PageBreakingLayoutListener for the PageBreakingAlgorithm to * notify about layout problems. * @return the listener instance or null if no notifications are needed */ protected PageBreakingAlgorithm.PageBreakingLayoutListener createLayoutListener() { return null; } /** * 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 alignment * @return the list of KnuthElements */ protected abstract List<KnuthElement> getNextKnuthElements(LayoutContext context, int alignment); /** * 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 alignment * @param positionAtIPDChange last element on the part before an IPD change * @param restartAtLM the layout manager from which to restart, if IPD * change occurs between two LMs * @return the list of KnuthElements */ protected List<KnuthElement> getNextKnuthElements(LayoutContext context, int alignment, Position positionAtIPDChange, LayoutManager restartAtLM) { throw new UnsupportedOperationException("TODO: implement acceptable fallback"); } /** @return true if there's no content that could be handled. */ public boolean isEmpty() { return empty; } /** * Start part. * @param list a block sequence * @param breakClass a break class */ protected void startPart(BlockSequence list, int breakClass) { //nop } /** * This method is called when no content is available for a part. Used to force empty pages. */ protected void handleEmptyContent() { //nop } /** * Finish part. * @param alg a page breaking algorithm * @param pbp a page break posittion */ protected abstract void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp); /** * Creates the top-level LayoutContext for the breaker operation. * @return the top-level LayoutContext */ protected LayoutContext createLayoutContext() { return new LayoutContext(0); } /** * Used to update the LayoutContext in subclasses prior to starting a new element list. * @param context the LayoutContext to update */ protected void updateLayoutContext(LayoutContext context) { //nop } /** * Used for debugging purposes. Notifies all registered observers about the element list. * Override to set different parameters. * @param elementList the Knuth element list */ protected void observeElementList(List elementList) { ElementListObserver.observe(elementList, "breaker", null); } /** * Starts the page 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; blockLists = new java.util.ArrayList<BlockSequence>(); log.debug("PLM> flow BPD =" + flowBPD); int nextSequenceStartsOn = Constants.EN_ANY; while (hasMoreContent()) { blockLists.clear(); //*** Phase 1: Get Knuth elements *** nextSequenceStartsOn = getNextBlockList(childLC, nextSequenceStartsOn); empty = empty && blockLists.size() == 0; //*** Phases 2 and 3 *** log.debug("PLM> blockLists.size() = " + blockLists.size()); for (int blockListIndex = 0; blockListIndex < blockLists.size(); blockListIndex++) { blockList = 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 //*** Phase 2: Alignment and breaking *** 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.ALL_BREAKS); if (Math.abs(alg.getIPDdifference()) > 1) { addAreas(alg, optimalPageCount, blockList, effectiveList); // *** redo Phase 1 *** log.trace("IPD changes after page " + optimalPageCount); blockLists.clear(); nextSequenceStartsOn = getNextBlockListChangedIPD(childLC, alg, effectiveList); blockListIndex = -1; } else { log.debug("PLM> optimalPageCount= " + optimalPageCount + " pageBreaks.size()= " + alg.getPageBreaks().size()); //*** Phase 3: Add areas *** doPhase3(alg, optimalPageCount, blockList, effectiveList); } } } // done blockLists = null; } /** * Returns {@code true} if the given position or one of its descendants * corresponds to a non-restartable LM. * * @param position a position * @return {@code true} if there is a non-restartable LM in the hierarchy */ private boolean containsNonRestartableLM(Position position) { LayoutManager lm = position.getLM(); if (lm != null && !lm.isRestartable()) { return true; } else { Position subPosition = position.getPosition(); return subPosition != null && containsNonRestartableLM(subPosition); } } /** * 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) { addAreas(alg, 0, partCount, originalList, effectiveList); } /** * Phase 3 of Knuth algorithm: Adds the areas * @param alg PageBreakingAlgorithm instance which determined the breaks * @param startPart index of the first part (page) to be rendered * @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 startPart, int partCount, BlockSequence originalList, BlockSequence effectiveList) { LayoutContext childLC; // add areas int startElementIndex = 0; int endElementIndex = 0; int lastBreak = -1; for (int p = startPart; p < startPart + partCount; p++) { PageBreakPosition pbp = alg.getPageBreaks().get(p); //Check the last break position for forced breaks int lastBreakClass; if (p == 0) { lastBreakClass = effectiveList.getStartOn(); } else { ListElement lastBreakElement = effectiveList.getElement(endElementIndex); if (lastBreakElement.isPenalty()) { KnuthPenalty pen = (KnuthPenalty) lastBreakElement; lastBreakClass = pen.getBreakClass(); } else { lastBreakClass = Constants.EN_COLUMN; } } //the end of the new part endElementIndex = pbp.getLeafPos(); // ignore the first elements added by the // PageSequenceLayoutManager startElementIndex += (startElementIndex == 0) ? effectiveList.ignoreAtStart : 0; log.debug("PLM> part: " + (p + 1) + ", start at pos " + startElementIndex + ", break at pos " + endElementIndex + ", break class = " + getBreakClassName(lastBreakClass)); startPart(effectiveList, lastBreakClass); int displayAlign = getCurrentDisplayAlign(); //The following is needed by SpaceResolver.performConditionalsNotification() //further down as there may be important Position elements in the element list trailer int notificationEndElementIndex = endElementIndex; // 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 ListIterator<KnuthElement> effectiveListIterator = effectiveList.listIterator(startElementIndex); while (effectiveListIterator.hasNext() && !(effectiveListIterator.next()).isBox()) { startElementIndex++; } if (startElementIndex <= endElementIndex) { if (log.isDebugEnabled()) { log.debug(" addAreas from " + startElementIndex + " to " + endElementIndex); } childLC = new LayoutContext(0); // set the space adjustment ratio childLC.setSpaceAdjust(pbp.bpdAdjust); // 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 = effectiveListIterator.next(); if (tempEl.isBox() && tempEl.getWidth() > 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.setStackLimitBP(MinOptMax.getInstance(averageLineLength)); } } /* *** *** non-standard extension *** *** */ // Handle SpaceHandling(Break)Positions, see SpaceResolver! SpaceResolver.performConditionalsNotification(effectiveList, startElementIndex, notificationEndElementIndex, lastBreak); // Add areas now! addAreas(new KnuthPossPosIter(effectiveList, startElementIndex, endElementIndex + 1), childLC); } else { //no content for this part handleEmptyContent(); } finishPart(alg, pbp); lastBreak = endElementIndex; startElementIndex = pbp.getLeafPos() + 1; } } /** * Notifies the layout managers about the space and conditional length situation based on * the break decisions. * @param effectiveList Element list to be painted * @param startElementIndex start index of the part * @param endElementIndex end index of the part * @param lastBreak index of the last break element */ /** * Handles span changes reported through the <code>LayoutContext</code>. * Only used by the PSLM and called by <code>getNextBlockList()</code>. * @param childLC the LayoutContext * @param nextSequenceStartsOn previous value for break handling * @return effective value for break handling */ protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) { return nextSequenceStartsOn; } /** * 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 * @return the page on which the next content should appear after a hard break */ protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn) { return getNextBlockList(childLC, nextSequenceStartsOn, null, null, null); } /** * 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 positionAtIPDChange last element on the part before an IPD change * @param restartAtLM the layout manager from which to restart, if IPD * change occurs between two LMs * @param firstElements elements from non-restartable LMs on the new page * @return the page on which the next content should appear after a hard * break */ protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn, Position positionAtIPDChange, LayoutManager restartAtLM, List<KnuthElement> firstElements) { updateLayoutContext(childLC); //Make sure the span change signal is reset childLC.signalSpanChange(Constants.NOT_SET); BlockSequence blockList; List<KnuthElement> returnedList; if (firstElements == null) { returnedList = getNextKnuthElements(childLC, alignment); } else if (positionAtIPDChange == null) { /* * No restartable element found after changing IPD break. Simply add the * non-restartable elements found after the break. */ returnedList = firstElements; /* * Remove the last 3 penalty-filler-forced break elements that were added by * the Knuth algorithm. They will be re-added later on. */ ListIterator iter = returnedList.listIterator(returnedList.size()); for (int i = 0; i < 3; i++) { iter.previous(); iter.remove(); } } else { returnedList = getNextKnuthElements(childLC, alignment, positionAtIPDChange, restartAtLM); returnedList.addAll(0, firstElements); } if (returnedList != null) { if (returnedList.isEmpty()) { nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn); return nextSequenceStartsOn; } blockList = new BlockSequence(nextSequenceStartsOn, getCurrentDisplayAlign()); //Only implemented by the PSLM nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn); Position breakPosition = null; if (ElementListUtils.endsWithForcedBreak(returnedList)) { KnuthPenalty breakPenalty = (KnuthPenalty) ListUtil.removeLast(returnedList); breakPosition = breakPenalty.getPosition(); log.debug("PLM> break - " + getBreakClassName(breakPenalty.getBreakClass())); switch (breakPenalty.getBreakClass()) { case Constants.EN_PAGE: nextSequenceStartsOn = Constants.EN_ANY; break; case Constants.EN_COLUMN: //TODO Fix this when implementing multi-column layout nextSequenceStartsOn = Constants.EN_COLUMN; break; case Constants.EN_ODD_PAGE: nextSequenceStartsOn = Constants.EN_ODD_PAGE; break; case Constants.EN_EVEN_PAGE: nextSequenceStartsOn = Constants.EN_EVEN_PAGE; break; default: throw new IllegalStateException("Invalid break class: " + breakPenalty.getBreakClass()); } } blockList.addAll(returnedList); BlockSequence seq; seq = blockList.endBlockSequence(breakPosition); if (seq != null) { blockLists.add(seq); } } return nextSequenceStartsOn; } /** * @param childLC LayoutContext to use * @param alg the pagebreaking algorithm * @param effectiveList the list of Knuth elements to be reused * @return the page on which the next content should appear after a hard break */ private int getNextBlockListChangedIPD(LayoutContext childLC, PageBreakingAlgorithm alg, BlockSequence effectiveList) { int nextSequenceStartsOn; KnuthNode optimalBreak = alg.getBestNodeBeforeIPDChange(); int positionIndex = optimalBreak.position; log.trace("IPD changes at index " + positionIndex); 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<KnuthElement> firstElements = Collections.emptyList(); 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<KnuthElement>(); boolean boxFound = false; Iterator<KnuthElement> iter = effectiveList.listIterator(positionIndex + 1); Position position = null; while (iter.hasNext() && (position == null || containsNonRestartableLM(position))) { positionIndex++; KnuthElement element = 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<KnuthElement> iter = effectiveList.listIterator(positionIndex + 1); do { KnuthElement nextElement = 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(); } nextSequenceStartsOn = getNextBlockList(childLC, Constants.EN_COLUMN, positionAtBreak, restartAtLM, firstElements); return nextSequenceStartsOn; } /** * Returns the average width of all the lines in the given range. * @param effectiveList effective block list to work on * @param startElementIndex index of the element starting the range * @param endElementIndex index of the element ending the range * @return the average line length, 0 if there's no content */ private int optimizeLineLength(KnuthSequence effectiveList, int startElementIndex, int endElementIndex) { ListIterator<KnuthElement> effectiveListIterator; // optimize line length int boxCount = 0; int accumulatedLineLength = 0; int greatestMinimumLength = 0; effectiveListIterator = effectiveList.listIterator(startElementIndex); while (effectiveListIterator.nextIndex() <= endElementIndex) { KnuthElement tempEl = 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().getMin() > greatestMinimumLength) { greatestMinimumLength = blockBox.getIPDRange().getMin(); } } } int averageLineLength = 0; if (accumulatedLineLength > 0 && boxCount > 0) { averageLineLength = (int) (accumulatedLineLength / boxCount); log.debug("Average line length = " + averageLineLength); if (averageLineLength < greatestMinimumLength) { averageLineLength = greatestMinimumLength; log.debug(" Correction to: " + 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 // CSOK: MethodLength (BlockSequence blockList, PageBreakingAlgorithm alg, int availableBPD) { int optimalPageCount; alg.setConstantLineWidth(availableBPD); optimalPageCount = alg.findBreakingPoints(blockList, /*availableBPD,*/ 1, true, BreakingAlgorithm.ALL_BREAKS); log.debug("PLM> optimalPageCount= " + optimalPageCount); // ListIterator<KnuthElement> sequenceIterator = blockList.listIterator(); ListIterator<PageBreakPosition> breakIterator = alg.getPageBreaks().listIterator(); KnuthElement thisElement = null; PageBreakPosition thisBreak; int adjustedDiff; // difference already adjusted while (breakIterator.hasNext()) { thisBreak = breakIterator.next(); if (log.isDebugEnabled()) { log.debug("| first page: break= " + thisBreak.getLeafPos() + " difference= " + thisBreak.difference + " ratio= " + thisBreak.bpdAdjust); } 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 (sequenceIterator.hasNext()) { firstElement = sequenceIterator.next(); if (!firstElement.isBox()) { log.debug("PLM> ignoring glue or penalty element " + "at the beginning of the sequence"); if (firstElement.isGlue()) { ((BlockLevelLayoutManager) firstElement.getLayoutManager()) .discardSpace((KnuthGlue) firstElement); } } else { break; } } sequenceIterator.previous(); // scan the sub-sequence representing a page, // collecting information about potential adjustments MinOptMax lineNumberMaxAdjustment = MinOptMax.ZERO; MinOptMax spaceMaxAdjustment = MinOptMax.ZERO; LinkedList<KnuthGlue> blockSpacesList = new LinkedList<KnuthGlue>(); LinkedList<KnuthGlue> unconfirmedList = new LinkedList<KnuthGlue>(); LinkedList<KnuthGlue> adjustableLinesList = new LinkedList<KnuthGlue>(); boolean bBoxSeen = false; while (sequenceIterator.hasNext() && sequenceIterator.nextIndex() <= thisBreak.getLeafPos()) { thisElement = sequenceIterator.next(); if (thisElement.isGlue()) { // glue elements are used to represent adjustable // lines // and adjustable spaces between blocks KnuthGlue thisGlue = (KnuthGlue) thisElement; Adjustment adjustment = thisGlue.getAdjustmentClass(); if (adjustment.equals(Adjustment.SPACE_BEFORE_ADJUSTMENT) || adjustment.equals(Adjustment.SPACE_AFTER_ADJUSTMENT)) { // potential space adjustment // glue items before the first box or after the // last one // must be ignored unconfirmedList.add(thisGlue); } else if (adjustment.equals(Adjustment.LINE_NUMBER_ADJUSTMENT)) { // potential line number adjustment lineNumberMaxAdjustment = lineNumberMaxAdjustment.plusMax(thisElement.getStretch()); lineNumberMaxAdjustment = lineNumberMaxAdjustment.minusMin(thisElement.getShrink()); adjustableLinesList.add(thisGlue); } else if (adjustment.equals(Adjustment.LINE_HEIGHT_ADJUSTMENT)) { // potential line height adjustment } } else if (thisElement.isBox()) { if (!bBoxSeen) { // this is the first box met in this page bBoxSeen = true; } else { while (!unconfirmedList.isEmpty()) { // glue items in unconfirmedList were not after // the last box // in this page; they must be added to // blockSpaceList KnuthGlue blockSpace = unconfirmedList.removeFirst(); spaceMaxAdjustment = spaceMaxAdjustment.plusMax(blockSpace.getStretch()); spaceMaxAdjustment = spaceMaxAdjustment.minusMin(blockSpace.getShrink()); blockSpacesList.add(blockSpace); } } } } log.debug("| line number adj= " + lineNumberMaxAdjustment); log.debug("| space adj = " + spaceMaxAdjustment); if (thisElement.isPenalty() && thisElement.getWidth() > 0) { log.debug(" mandatory variation to the number of lines!"); ((BlockLevelLayoutManager) thisElement.getLayoutManager()) .negotiateBPDAdjustment(thisElement.getWidth(), thisElement); } if (thisBreak.bpdAdjust != 0 && (thisBreak.difference > 0 && thisBreak.difference <= spaceMaxAdjustment.getMax()) || (thisBreak.difference < 0 && thisBreak.difference >= spaceMaxAdjustment.getMin())) { // modify only the spaces between blocks adjustedDiff += adjustBlockSpaces(blockSpacesList, thisBreak.difference, (thisBreak.difference > 0 ? spaceMaxAdjustment.getMax() : -spaceMaxAdjustment.getMin())); log.debug("single space: " + (adjustedDiff == thisBreak.difference || thisBreak.bpdAdjust == 0 ? "ok" : "ERROR")); } else if (thisBreak.bpdAdjust != 0) { adjustedDiff += adjustLineNumbers(adjustableLinesList, thisBreak.difference, (thisBreak.difference > 0 ? lineNumberMaxAdjustment.getMax() : -lineNumberMaxAdjustment.getMin())); adjustedDiff += adjustBlockSpaces(blockSpacesList, thisBreak.difference - adjustedDiff, ((thisBreak.difference - adjustedDiff) > 0 ? spaceMaxAdjustment.getMax() : -spaceMaxAdjustment.getMin())); log.debug("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(), blockList.getDisplayAlign()); 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(); ElementListObserver.observe(effectiveList, "breaker-effective", null); alg.getPageBreaks().clear(); //Why this? return effectiveList; } private int adjustBlockSpaces(LinkedList<KnuthGlue> spaceList, int difference, int total) { if (log.isDebugEnabled()) { log.debug("AdjustBlockSpaces: difference " + difference + " / " + total + " on " + spaceList.size() + " spaces in block"); } ListIterator<KnuthGlue> spaceListIterator = spaceList.listIterator(); int adjustedDiff = 0; int partial = 0; while (spaceListIterator.hasNext()) { KnuthGlue blockSpace = spaceListIterator.next(); partial += (difference > 0 ? blockSpace.getStretch() : blockSpace.getShrink()); if (log.isDebugEnabled()) { log.debug("available = " + partial + " / " + total); log.debug("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<KnuthGlue> lineList, int difference, int total) { if (log.isDebugEnabled()) { log.debug("AdjustLineNumbers: difference " + difference + " / " + total + " on " + lineList.size() + " elements"); } ListIterator<KnuthGlue> lineListIterator = lineList.listIterator(); int adjustedDiff = 0; int partial = 0; while (lineListIterator.hasNext()) { KnuthGlue line = lineListIterator.next(); partial += (difference > 0 ? line.getStretch() : line.getShrink()); int newAdjust = ((BlockLevelLayoutManager) line.getLayoutManager()) .negotiateBPDAdjustment(((int) ((float) partial * difference / total)) - adjustedDiff, line); adjustedDiff += newAdjust; } return adjustedDiff; } }