You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BalancingColumnBreakingAlgorithm.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.layoutmgr;
  19. import java.util.ArrayList;
  20. import java.util.Iterator;
  21. import java.util.LinkedList;
  22. import java.util.List;
  23. import org.apache.fop.traits.MinOptMax;
  24. /**
  25. * This is a the breaking algorithm that is responsible for balancing columns in multi-column
  26. * layout.
  27. */
  28. public class BalancingColumnBreakingAlgorithm extends PageBreakingAlgorithm {
  29. private int columnCount;
  30. private List<Integer> idealBreaks;
  31. public BalancingColumnBreakingAlgorithm(LayoutManager topLevelLM,
  32. PageProvider pageProvider,
  33. PageBreakingLayoutListener layoutListener,
  34. int alignment, int alignmentLast,
  35. MinOptMax footnoteSeparatorLength,
  36. boolean partOverflowRecovery,
  37. int columnCount) {
  38. super(topLevelLM, pageProvider, layoutListener,
  39. alignment, alignmentLast,
  40. footnoteSeparatorLength, partOverflowRecovery, false, false);
  41. this.columnCount = columnCount;
  42. this.considerTooShort = true; //This is important!
  43. }
  44. /** {@inheritDoc} */
  45. protected double computeDemerits(KnuthNode activeNode,
  46. KnuthElement element, int fitnessClass, double r) {
  47. double demerits = Double.MAX_VALUE;
  48. if (idealBreaks == null) {
  49. idealBreaks = calculateIdealBreaks(activeNode.position);
  50. }
  51. LinkedList<Integer> curPossibility = getPossibilityTrail(activeNode);
  52. boolean notIdeal = false;
  53. int idealDemerit = columnCount + 1 - curPossibility.size();
  54. if (curPossibility.size() > idealBreaks.size()) {
  55. return demerits;
  56. }
  57. for (int breakPos = 0; breakPos < curPossibility.size(); breakPos++) {
  58. if (curPossibility.get(breakPos) != 0
  59. && !curPossibility.get(breakPos).equals(idealBreaks.get(breakPos))) {
  60. notIdeal = true;
  61. break;
  62. }
  63. }
  64. if (!notIdeal) {
  65. demerits = idealDemerit;
  66. }
  67. return demerits;
  68. }
  69. private List<Integer> calculateIdealBreaks(int startPos) {
  70. List<ColumnContent> previousPreviousBreaks = null;
  71. List<ColumnContent> previousBreaks = null;
  72. List<ColumnContent> breaks = new ArrayList<ColumnContent>();
  73. breaks.add(new ColumnContent(startPos, par.size() - 1));
  74. do {
  75. previousPreviousBreaks = previousBreaks;
  76. previousBreaks = breaks;
  77. breaks = getInitialBreaks(startPos, getAverageColumnLength(breaks));
  78. } while (!breaks.equals(previousBreaks) && !breaks.equals(previousPreviousBreaks));
  79. breaks = sortElementsForBreaks(breaks);
  80. return getElementIdBreaks(breaks, startPos);
  81. }
  82. private static final class ColumnContent {
  83. public final int startIndex;
  84. public final int endIndex;
  85. ColumnContent(int startIndex, int endIndex) {
  86. this.startIndex = startIndex;
  87. this.endIndex = endIndex;
  88. }
  89. @Override
  90. public int hashCode() {
  91. return startIndex << 16 | endIndex;
  92. }
  93. @Override
  94. public boolean equals(Object obj) {
  95. if (!(obj instanceof ColumnContent)) {
  96. return false;
  97. } else {
  98. ColumnContent other = (ColumnContent) obj;
  99. return other.startIndex == startIndex && other.endIndex == endIndex;
  100. }
  101. }
  102. @Override
  103. public String toString() {
  104. return startIndex + "-" + endIndex;
  105. }
  106. }
  107. private int getAverageColumnLength(List<ColumnContent> columns) {
  108. int totalLength = 0;
  109. for (ColumnContent col : columns) {
  110. totalLength += calcContentLength(par, col.startIndex, col.endIndex);
  111. }
  112. return totalLength / columnCount;
  113. }
  114. private List<ColumnContent> getInitialBreaks(int startIndex, int averageColLength) {
  115. List<ColumnContent> initialColumns = new ArrayList<ColumnContent>();
  116. int colStartIndex = startIndex;
  117. int totalLength = 0;
  118. int idealBreakLength = averageColLength;
  119. int previousBreakLength = 0;
  120. int prevBreakIndex = startIndex;
  121. boolean prevIsBox = false;
  122. int colNumber = 1;
  123. for (int i = startIndex; i < par.size(); i++) {
  124. KnuthElement element = (KnuthElement) par.get(i);
  125. if (isLegalBreak(i, prevIsBox)) {
  126. int breakLength = totalLength
  127. + (element instanceof KnuthPenalty ? element.getWidth() : 0);
  128. if (breakLength > idealBreakLength && colNumber < columnCount) {
  129. int breakIndex;
  130. if (breakLength - idealBreakLength > idealBreakLength - previousBreakLength) {
  131. breakIndex = prevBreakIndex;
  132. totalLength = previousBreakLength;
  133. } else {
  134. breakIndex = element instanceof KnuthPenalty ? i : i - 1;
  135. totalLength = breakLength;
  136. }
  137. initialColumns.add(new ColumnContent(colStartIndex, breakIndex));
  138. i = getNextStartIndex(breakIndex);
  139. colStartIndex = i--;
  140. colNumber++;
  141. idealBreakLength += averageColLength;
  142. } else {
  143. previousBreakLength = breakLength;
  144. prevBreakIndex = element instanceof KnuthPenalty ? i : i - 1;
  145. prevIsBox = false;
  146. }
  147. } else {
  148. totalLength += element instanceof KnuthPenalty ? 0 : element.getWidth();
  149. prevIsBox = element instanceof KnuthBox;
  150. }
  151. }
  152. assert initialColumns.size() == columnCount - 1;
  153. initialColumns.add(new ColumnContent(colStartIndex, par.size() - 1));
  154. return initialColumns;
  155. }
  156. private int getNextStartIndex(int breakIndex) {
  157. int startIndex = breakIndex;
  158. @SuppressWarnings("unchecked")
  159. Iterator<KnuthElement> iter = par.listIterator(breakIndex);
  160. while (iter.hasNext() && !(iter.next() instanceof KnuthBox)) {
  161. startIndex++;
  162. }
  163. return startIndex;
  164. }
  165. private List<ColumnContent> sortElementsForBreaks(List<ColumnContent> breaks) {
  166. boolean changes;
  167. /* Relax factor to make balancing more visually appealing as in some cases
  168. * strict balancing would lead to ragged column endings. */
  169. int fFactor = 4000;
  170. do {
  171. changes = false;
  172. ColumnContent curColumn = breaks.get(breaks.size() - 1);
  173. int curColLength = calcContentLength(par, curColumn.startIndex, curColumn.endIndex);
  174. for (int colIndex = (breaks.size() - 1); colIndex > 0; colIndex--) {
  175. ColumnContent prevColumn = breaks.get(colIndex - 1);
  176. int prevColLength = calcContentLength(par, prevColumn.startIndex, prevColumn.endIndex);
  177. if (prevColLength < curColLength) {
  178. int newBreakIndex = curColumn.startIndex;
  179. boolean prevIsBox = true;
  180. while (newBreakIndex <= curColumn.endIndex && !(isLegalBreak(newBreakIndex, prevIsBox))) {
  181. newBreakIndex++;
  182. prevIsBox = par.get(newBreakIndex) instanceof KnuthBox;
  183. }
  184. if (newBreakIndex < curColumn.endIndex) {
  185. if (prevIsBox) {
  186. newBreakIndex--;
  187. }
  188. int newStartIndex = getNextStartIndex(newBreakIndex);
  189. int newPrevColLength = calcContentLength(par, prevColumn.startIndex, newBreakIndex);
  190. if (newPrevColLength <= fFactor + curColLength) {
  191. prevColumn = new ColumnContent(prevColumn.startIndex, newBreakIndex);
  192. breaks.set(colIndex - 1, prevColumn);
  193. breaks.set(colIndex, new ColumnContent(newStartIndex, curColumn.endIndex));
  194. prevColLength = calcContentLength(par, prevColumn.startIndex, newBreakIndex);
  195. changes = true;
  196. }
  197. }
  198. }
  199. curColLength = prevColLength;
  200. curColumn = prevColumn;
  201. }
  202. } while (changes);
  203. return breaks;
  204. }
  205. private boolean isLegalBreak(int index, boolean prevIsBox) {
  206. KnuthElement element = (KnuthElement) par.get(index);
  207. return element instanceof KnuthPenalty && element.getPenalty() < KnuthPenalty.INFINITE
  208. || prevIsBox && element instanceof KnuthGlue;
  209. }
  210. private int calcContentLength(KnuthSequence par, int startIndex, int endIndex) {
  211. return ElementListUtils.calcContentLength(par, startIndex, endIndex) + getPenaltyWidth(endIndex);
  212. }
  213. private int getPenaltyWidth(int index) {
  214. KnuthElement element = (KnuthElement) par.get(index);
  215. return element instanceof KnuthPenalty ? element.getWidth() : 0;
  216. }
  217. private List<Integer> getElementIdBreaks(List<ColumnContent> breaks, int startPos) {
  218. List<Integer> elementIdBreaks = new ArrayList<Integer>();
  219. elementIdBreaks.add(startPos);
  220. for (ColumnContent column : breaks) {
  221. if (breaks.get(breaks.size() - 1).equals(column)) {
  222. continue;
  223. }
  224. elementIdBreaks.add(column.endIndex);
  225. }
  226. return elementIdBreaks;
  227. }
  228. private LinkedList<Integer> getPossibilityTrail(KnuthNode activeNode) {
  229. LinkedList<Integer> trail = new LinkedList<Integer>();
  230. KnuthNode previous = activeNode;
  231. do {
  232. trail.addFirst(previous.position);
  233. previous = previous.previous;
  234. } while (previous != null);
  235. return trail;
  236. }
  237. }