Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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 915406 2010-02-23 16:13:59Z vhennebert $ */
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 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 breakIndex,
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 {
/** 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 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;
}
}
}
/** blockListIndex of the current BlockSequence in blockLists */
private int blockListIndex = 0;
private List blockLists = null;
protected int alignment;
private int alignmentLast;
protected MinOptMax footnoteSeparatorLength = MinOptMax.ZERO;
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 List getNextKnuthElements(LayoutContext context, int alignment);
protected List 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 (this.blockLists.isEmpty());
}
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)
* @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.ALL_BREAKS);
if (alg.getIPDdifference() != 0) {
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);
}
}
}
}
/**
* 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();
if (subPosition == null) {
return false;
} else {
return 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
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 = " + 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
effectiveListIterator = effectiveList
.listIterator(startElementIndex);
while (effectiveListIterator.hasNext()
&& !((KnuthElement) 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 = (KnuthElement)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 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) {
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 firstElements) {
updateLayoutContext(childLC);
//Make sure the span change signal is reset
childLC.signalSpanChange(Constants.NOT_SET);
BlockSequence blockList;
List 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) {
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 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 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().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(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 = MinOptMax.ZERO;
MinOptMax spaceMaxAdjustment = MinOptMax.ZERO;
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
Adjustment adjustment = ((KnuthGlue) thisElement).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(thisElement);
} else if (adjustment.equals(Adjustment.LINE_NUMBER_ADJUSTMENT)) {
// potential line number adjustment
lineNumberMaxAdjustment
= lineNumberMaxAdjustment.plusMax(thisElement.getStretch());
lineNumberMaxAdjustment
= lineNumberMaxAdjustment.minusMin(thisElement.getShrink());
adjustableLinesList.add(thisElement);
} 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 = (KnuthGlue) 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
spaceAdjustmentRatio = ((double) thisBreak.difference / (thisBreak.difference > 0
? spaceMaxAdjustment.getMax()
: spaceMaxAdjustment.getMin()));
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 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.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 lineList, int difference, int total) {
if (log.isDebugEnabled()) {
log.debug("AdjustLineNumbers: difference " + difference + " / " + total + " on " + lineList.size() + " elements");
}
ListIterator lineListIterator = lineList.listIterator();
int adjustedDiff = 0;
int partial = 0;
while (lineListIterator.hasNext()) {
KnuthGlue line = (KnuthGlue)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;
}
}