org.apache.fop.layoutmgr.BalancingColumnBreakingAlgorithm Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The newest version!
/*
* 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: BalancingColumnBreakingAlgorithm.java 1427143 2012-12-31 14:46:01Z gadams $ */
package org.apache.fop.layoutmgr;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.fop.traits.MinOptMax;
/**
* This is a the breaking algorithm that is responsible for balancing columns in multi-column
* layout.
*/
public class BalancingColumnBreakingAlgorithm extends PageBreakingAlgorithm {
private int columnCount;
private List idealBreaks;
public BalancingColumnBreakingAlgorithm(LayoutManager topLevelLM,
PageProvider pageProvider,
PageBreakingLayoutListener layoutListener,
int alignment, int alignmentLast,
MinOptMax footnoteSeparatorLength,
boolean partOverflowRecovery,
int columnCount) {
super(topLevelLM, pageProvider, layoutListener,
alignment, alignmentLast,
footnoteSeparatorLength, partOverflowRecovery, false, false);
this.columnCount = columnCount;
this.considerTooShort = true; //This is important!
}
/** {@inheritDoc} */
protected double computeDemerits(KnuthNode activeNode,
KnuthElement element, int fitnessClass, double r) {
double demerits = Double.MAX_VALUE;
if (idealBreaks == null) {
idealBreaks = calculateIdealBreaks(activeNode.position);
}
LinkedList curPossibility = getPossibilityTrail(activeNode);
boolean notIdeal = false;
int idealDemerit = columnCount + 1 - curPossibility.size();
if (curPossibility.size() > idealBreaks.size()) {
return demerits;
}
for (int breakPos = 0; breakPos < curPossibility.size(); breakPos++) {
if (curPossibility.get(breakPos) != 0
&& !curPossibility.get(breakPos).equals(idealBreaks.get(breakPos))) {
notIdeal = true;
break;
}
}
if (!notIdeal) {
demerits = idealDemerit;
}
return demerits;
}
private List calculateIdealBreaks(int startPos) {
List previousPreviousBreaks = null;
List previousBreaks = null;
List breaks = new ArrayList();
breaks.add(new ColumnContent(startPos, par.size() - 1));
do {
previousPreviousBreaks = previousBreaks;
previousBreaks = breaks;
breaks = getInitialBreaks(startPos, getAverageColumnLength(breaks));
} while (!breaks.equals(previousBreaks) && !breaks.equals(previousPreviousBreaks));
breaks = sortElementsForBreaks(breaks);
return getElementIdBreaks(breaks, startPos);
}
private static final class ColumnContent {
public final int startIndex;
public final int endIndex;
ColumnContent(int startIndex, int endIndex) {
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public int hashCode() {
return startIndex << 16 | endIndex;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ColumnContent)) {
return false;
} else {
ColumnContent other = (ColumnContent) obj;
return other.startIndex == startIndex && other.endIndex == endIndex;
}
}
@Override
public String toString() {
return startIndex + "-" + endIndex;
}
}
private int getAverageColumnLength(List columns) {
int totalLength = 0;
for (ColumnContent col : columns) {
totalLength += calcContentLength(par, col.startIndex, col.endIndex);
}
return totalLength / columnCount;
}
private List getInitialBreaks(int startIndex, int averageColLength) {
List initialColumns = new ArrayList();
int colStartIndex = startIndex;
int totalLength = 0;
int idealBreakLength = averageColLength;
int previousBreakLength = 0;
int prevBreakIndex = startIndex;
boolean prevIsBox = false;
int colNumber = 1;
for (int i = startIndex; i < par.size(); i++) {
KnuthElement element = (KnuthElement) par.get(i);
if (isLegalBreak(i, prevIsBox)) {
int breakLength = totalLength
+ (element instanceof KnuthPenalty ? element.getWidth() : 0);
if (breakLength > idealBreakLength && colNumber < columnCount) {
int breakIndex;
if (breakLength - idealBreakLength > idealBreakLength - previousBreakLength) {
breakIndex = prevBreakIndex;
totalLength = previousBreakLength;
} else {
breakIndex = element instanceof KnuthPenalty ? i : i - 1;
totalLength = breakLength;
}
initialColumns.add(new ColumnContent(colStartIndex, breakIndex));
i = getNextStartIndex(breakIndex);
colStartIndex = i--;
colNumber++;
idealBreakLength += averageColLength;
} else {
previousBreakLength = breakLength;
prevBreakIndex = element instanceof KnuthPenalty ? i : i - 1;
prevIsBox = false;
}
} else {
totalLength += element instanceof KnuthPenalty ? 0 : element.getWidth();
prevIsBox = element instanceof KnuthBox;
}
}
assert initialColumns.size() == columnCount - 1;
initialColumns.add(new ColumnContent(colStartIndex, par.size() - 1));
return initialColumns;
}
private int getNextStartIndex(int breakIndex) {
int startIndex = breakIndex;
@SuppressWarnings("unchecked")
Iterator iter = par.listIterator(breakIndex);
while (iter.hasNext() && !(iter.next() instanceof KnuthBox)) {
startIndex++;
}
return startIndex;
}
private List sortElementsForBreaks(List breaks) {
boolean changes;
/* Relax factor to make balancing more visually appealing as in some cases
* strict balancing would lead to ragged column endings. */
int fFactor = 4000;
do {
changes = false;
ColumnContent curColumn = breaks.get(breaks.size() - 1);
int curColLength = calcContentLength(par, curColumn.startIndex, curColumn.endIndex);
for (int colIndex = (breaks.size() - 1); colIndex > 0; colIndex--) {
ColumnContent prevColumn = breaks.get(colIndex - 1);
int prevColLength = calcContentLength(par, prevColumn.startIndex, prevColumn.endIndex);
if (prevColLength < curColLength) {
int newBreakIndex = curColumn.startIndex;
boolean prevIsBox = true;
while (newBreakIndex <= curColumn.endIndex && !(isLegalBreak(newBreakIndex, prevIsBox))) {
newBreakIndex++;
prevIsBox = par.get(newBreakIndex) instanceof KnuthBox;
}
if (newBreakIndex < curColumn.endIndex) {
if (prevIsBox) {
newBreakIndex--;
}
int newStartIndex = getNextStartIndex(newBreakIndex);
int newPrevColLength = calcContentLength(par, prevColumn.startIndex, newBreakIndex);
if (newPrevColLength <= fFactor + curColLength) {
prevColumn = new ColumnContent(prevColumn.startIndex, newBreakIndex);
breaks.set(colIndex - 1, prevColumn);
breaks.set(colIndex, new ColumnContent(newStartIndex, curColumn.endIndex));
prevColLength = calcContentLength(par, prevColumn.startIndex, newBreakIndex);
changes = true;
}
}
}
curColLength = prevColLength;
curColumn = prevColumn;
}
} while (changes);
return breaks;
}
private boolean isLegalBreak(int index, boolean prevIsBox) {
KnuthElement element = (KnuthElement) par.get(index);
return element instanceof KnuthPenalty && element.getPenalty() < KnuthPenalty.INFINITE
|| prevIsBox && element instanceof KnuthGlue;
}
private int calcContentLength(KnuthSequence par, int startIndex, int endIndex) {
return ElementListUtils.calcContentLength(par, startIndex, endIndex) + getPenaltyWidth(endIndex);
}
private int getPenaltyWidth(int index) {
KnuthElement element = (KnuthElement) par.get(index);
return element instanceof KnuthPenalty ? element.getWidth() : 0;
}
private List getElementIdBreaks(List breaks, int startPos) {
List elementIdBreaks = new ArrayList();
elementIdBreaks.add(startPos);
for (ColumnContent column : breaks) {
if (breaks.get(breaks.size() - 1).equals(column)) {
continue;
}
elementIdBreaks.add(column.endIndex);
}
return elementIdBreaks;
}
private LinkedList getPossibilityTrail(KnuthNode activeNode) {
LinkedList trail = new LinkedList();
KnuthNode previous = activeNode;
do {
trail.addFirst(previous.position);
previous = previous.previous;
} while (previous != null);
return trail;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy