/* * 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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 Log log = LogFactory.getLog(BalancingColumnBreakingAlgorithm.class); private int columnCount; private int fullLen; private int idealPartLen; 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 dem = super.computeDemerits(activeNode, element, fitnessClass, r); if (log.isTraceEnabled()) { log.trace("original demerit=" + dem + " " + totalWidth + " line=" + activeNode.line + "/" + columnCount + " pos=" + activeNode.position + "/" + (par.size() - 1)); } int remParts = columnCount - activeNode.line; int curPos = par.indexOf(element); if (fullLen == 0) { fullLen = ElementListUtils.calcContentLength(par, activeNode.position, par.size() - 1); this.idealPartLen = (fullLen / columnCount); } int partLen = ElementListUtils.calcContentLength(par, activeNode.position, curPos - 1); int restLen = ElementListUtils.calcContentLength(par, curPos - 1, par.size() - 1); int avgRestLen = 0; if (remParts > 0) { avgRestLen = restLen / remParts; } if (log.isTraceEnabled()) { log.trace("remaining parts: " + remParts + " rest len: " + restLen + " avg=" + avgRestLen); } double balance = (idealPartLen - partLen) / 1000f; if (log.isTraceEnabled()) { log.trace("balance=" + balance); } double absBalance = Math.abs(balance); dem = absBalance; //Step 1: This does the rough balancing if (columnCount > 2) { if (balance > 0) { //shorter parts are less desired than longer ones dem = dem * 1.2f; } } else { if (balance < 0) { //shorter parts are less desired than longer ones dem = dem * 1.2f; } } //Step 2: This helps keep the trailing parts shorter than the previous ones dem += (avgRestLen) / 1000f; if (activeNode.line >= columnCount) { //We don't want more columns than available dem = Double.MAX_VALUE; } if (log.isTraceEnabled()) { log.trace("effective dem=" + dem + " " + totalWidth); } return dem; } }