123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- /*
- * 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.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<Integer> 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<Integer> 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<Integer> calculateIdealBreaks(int startPos) {
- List<ColumnContent> previousPreviousBreaks = null;
- List<ColumnContent> previousBreaks = null;
- List<ColumnContent> breaks = new ArrayList<ColumnContent>();
- 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<ColumnContent> columns) {
- int totalLength = 0;
- for (ColumnContent col : columns) {
- totalLength += calcContentLength(par, col.startIndex, col.endIndex);
- }
- return totalLength / columnCount;
- }
-
- private List<ColumnContent> getInitialBreaks(int startIndex, int averageColLength) {
- List<ColumnContent> initialColumns = new ArrayList<ColumnContent>();
- 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<KnuthElement> iter = par.listIterator(breakIndex);
- while (iter.hasNext() && !(iter.next() instanceof KnuthBox)) {
- startIndex++;
- }
- return startIndex;
- }
-
- private List<ColumnContent> sortElementsForBreaks(List<ColumnContent> 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<Integer> getElementIdBreaks(List<ColumnContent> breaks, int startPos) {
- List<Integer> elementIdBreaks = new ArrayList<Integer>();
- elementIdBreaks.add(startPos);
- for (ColumnContent column : breaks) {
- if (breaks.get(breaks.size() - 1).equals(column)) {
- continue;
- }
- elementIdBreaks.add(column.endIndex);
- }
- return elementIdBreaks;
- }
-
- private LinkedList<Integer> getPossibilityTrail(KnuthNode activeNode) {
- LinkedList<Integer> trail = new LinkedList<Integer>();
- KnuthNode previous = activeNode;
- do {
- trail.addFirst(previous.position);
- previous = previous.previous;
- } while (previous != null);
- return trail;
- }
- }
|