/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.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);
public static class PageBreakPosition extends LeafPosition {
double bpdAdjust; // Percentage to adjust (stretch or shrink)
int difference;
int footnoteFirstListIndex;
int footnoteFirstElementIndex;
int footnoteLastListIndex;
int footnoteLastElementIndex;
PageBreakPosition(LayoutManager lm, int iBreakIndex,
int ffli, int ffei, int flli, int flei,
double bpdA, int diff) {
super(lm, iBreakIndex);
bpdAdjust = bpdA;
difference = diff;
footnoteFirstListIndex = ffli;
footnoteFirstElementIndex = ffei;
footnoteLastListIndex = flli;
footnoteLastElementIndex = flei;
}
}
public class BlockSequence extends BlockKnuthSequence {
/** 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;
/**
* 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 iStartOn the kind of page the sequence should start on. One of EN_ANY, EN_COLUMN,
* EN_ODD_PAGE, EN_EVEN_PAGE.
* @param displayAlign the value for the display-align property
*/
public BlockSequence(int iStartOn, int displayAlign) {
super();
startOn = iStartOn;
this.displayAlign = displayAlign;
}
/**
* @return the kind of page the sequence should start on. One of EN_ANY, EN_COLUMN,
* EN_ODD_PAGE, 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)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
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;
}
}
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;
}
}
}
/** blockListIndex of the current BlockSequence in blockLists */
private int blockListIndex = 0;
private List blockLists = null;
protected int alignment;
private int alignmentLast;
protected MinOptMax footnoteSeparatorLength = new MinOptMax(0);
protected abstract int getCurrentDisplayAlign();
protected abstract boolean hasMoreContent();
protected abstract void addAreas(PositionIterator posIter, LayoutContext context);
protected abstract LayoutManager getTopLevelLM();
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;
}
/*
* This method is to contain the logic to determine the LM's
* getNextKnuthElements() implementation(s) that are to be called.
* @return LinkedList of Knuth elements.
*/
protected abstract LinkedList getNextKnuthElements(LayoutContext context, int alignment);
/** @return true if there's no content that could be handled. */
public boolean isEmpty() {
return (this.blockLists.size() == 0);
}
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
}
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)
*/
public void doLayout(int flowBPD) {
doLayout(flowBPD, false);
}
/**
* 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(new MinOptMax(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);
String pagina = (blockList.startOn == Constants.EN_ANY) ? "any page"
: (blockList.startOn == Constants.EN_ODD_PAGE) ? "odd page"
: "even page";
log.debug(" sequence starts on " + pagina);
}
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());
int iOptPageCount;
BlockSequence effectiveList;
if (getCurrentDisplayAlign() == Constants.EN_X_FILL) {
/* justification */
effectiveList = justifyBoxes(blockList, alg, flowBPD);
} else {
/* no justification */
effectiveList = blockList;
}
//iOptPageCount = alg.firstFit(effectiveList, flowBPD, 1, true);
alg.setConstantLineWidth(flowBPD);
iOptPageCount = alg.findBreakingPoints(effectiveList, /*flowBPD,*/
1, true, BreakingAlgorithm.ALL_BREAKS);
log.debug("PLM> iOptPageCount= " + iOptPageCount
+ " pageBreaks.size()= " + alg.getPageBreaks().size());
//*** Phase 3: Add areas ***
doPhase3(alg, iOptPageCount, 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) {
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
ListIterator effectiveListIterator = effectiveList.listIterator();
int startElementIndex = 0;
int endElementIndex = 0;
int lastBreak = -1;
for (int p = startPart; p < startPart + partCount; p++) {
PageBreakPosition pbp = (PageBreakPosition) 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 = " + 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
effectiveListIterator = effectiveList
.listIterator(startElementIndex);
KnuthElement firstElement;
while (effectiveListIterator.hasNext()
&& !(firstElement = (KnuthElement) effectiveListIterator.next())
.isBox()) {
/*
if (firstElement.isGlue() && firstElement.getLayoutManager() != null) {
// discard the space representd by the glue element
((BlockLevelLayoutManager) firstElement
.getLayoutManager())
.discardSpace((KnuthGlue) firstElement);
}*/
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 = (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.setStackLimitBP(new MinOptMax(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 LayoutContext
.
* Only used by the PSLM and called by getNextBlockList()
.
* @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) {
updateLayoutContext(childLC);
//Make sure the span change signal is reset
childLC.signalSpanChange(Constants.NOT_SET);
BlockSequence blockList;
LinkedList returnedList = getNextKnuthElements(childLC, alignment);
if (returnedList != null) {
if (returnedList.size() == 0) {
nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn);
return nextSequenceStartsOn;
}
blockList = new BlockSequence(nextSequenceStartsOn, getCurrentDisplayAlign());
//Only implemented by the PSLM
nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn);
Position breakPosition = null;
if (((KnuthElement) returnedList.getLast()).isForcedBreak()) {
KnuthPenalty breakPenalty = (KnuthPenalty)returnedList.removeLast();
breakPosition = breakPenalty.getPosition();
switch (breakPenalty.getBreakClass()) {
case Constants.EN_PAGE:
log.debug("PLM> break - PAGE");
nextSequenceStartsOn = Constants.EN_ANY;
break;
case Constants.EN_COLUMN:
log.debug("PLM> break - COLUMN");
//TODO Fix this when implementing multi-column layout
nextSequenceStartsOn = Constants.EN_COLUMN;
break;
case Constants.EN_ODD_PAGE:
log.debug("PLM> break - ODD PAGE");
nextSequenceStartsOn = Constants.EN_ODD_PAGE;
break;
case Constants.EN_EVEN_PAGE:
log.debug("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(breakPosition);
if (seq != null) {
this.blockLists.add(seq);
}
}
return nextSequenceStartsOn;
}
/**
* Returns the average width of all the lines in the given range.
* @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
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);
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(BlockSequence blockList, PageBreakingAlgorithm alg, int availableBPD) {
int iOptPageNumber;
alg.setConstantLineWidth(availableBPD);
iOptPageNumber = alg.findBreakingPoints(blockList, /*availableBPD,*/
1, true, BreakingAlgorithm.ALL_BREAKS);
log.debug("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();
if (log.isDebugEnabled()) {
log.debug("| 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()) {
//
log.debug("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);
}
}
}
}
log.debug("| line number adj= "
+ lineNumberMaxAdjustment);
log.debug("| space adj = "
+ spaceMaxAdjustment);
if (thisElement.isPenalty() && thisElement.getW() > 0) {
log.debug(" 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));
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.max
: -lineNumberMaxAdjustment.min));
adjustedDiff += adjustBlockSpaces(
blockSpacesList,
thisBreak.difference - adjustedDiff,
((thisBreak.difference - adjustedDiff) > 0 ? spaceMaxAdjustment.max
: -spaceMaxAdjustment.min));
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 spaceList, int difference, int total) {
if (log.isDebugEnabled()) {
log.debug("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());
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 lineList, int difference, int total) {
if (log.isDebugEnabled()) {
log.debug("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;
}
}