From: Luca Furini Date: Thu, 9 Dec 2004 15:53:36 +0000 (+0000) Subject: * created a new method which is called only once by LineLM.getNextBreakPoss() and... X-Git-Tag: Root_Temp_KnuthStylePageBreaking~311 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=354934c1869e6b0000fde3fe1af3b3181a7865fc;p=xmlgraphics-fop.git * created a new method which is called only once by LineLM.getNextBreakPoss() and subsequently calls findBreakingPoints() with different parameters until a set of breaking point is found * renamed lastDeactivatedNode -> bestDeactivatedNode, and updated only if the node to deactivate is "better" than the old one (may need some fine-tuning) * instead of the fallback() method, which created only one lineBreakPosition, a new algorithm is used if Knuth's fails git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@198196 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java index aa3df1afe..c469ecadf 100644 --- a/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java @@ -141,7 +141,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager { private int iEndElement = 0; private int iCurrParIndex = 0; - private KnuthNode lastDeactivatedNode = null; + private KnuthNode bestDeactivatedNode = null; // parameters of Knuth's algorithm: // penalty value for flagged penalties @@ -159,6 +159,10 @@ public class LineLayoutManager extends InlineStackingLayoutManager { // break point public static final int DEFAULT_SPACE_WIDTH = 3336; private static final int INFINITE_RATIO = 1000; + private static final int MAX_DEMERITS_INCREASE = 1000; + // constants identifying the line breaking algorithm used + private static final int KNUTH_ALGORITHM = 0; + private static final int FIRST_FIT_ALGORITHM = 1; // this class represent a feasible breaking point private class KnuthNode { @@ -491,40 +495,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager { Paragraph currPar = null; while (paragraphsIterator.hasPrevious()) { currPar = (Paragraph) paragraphsIterator.previous(); - double maxAdjustment = 1; - int iBPcount = 0; - - // first try - if ((iBPcount - = findBreakingPoints(currPar, - context.getStackLimit().opt, - maxAdjustment, false)) == 0) { - // the first try failed, now try something different - log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment); - if (hyphProps.hyphenate == Constants.EN_TRUE) { - // consider every hyphenation point as a legal break - findHyphenationPoints(currPar); - } else { - // try with a higher threshold - maxAdjustment = 5; - } - - if ((iBPcount - = findBreakingPoints(currPar, - context.getStackLimit().opt, - maxAdjustment, false)) == 0) { - // the second try failed too, try with a huge threshold - // and force the algorithm to find - // a set of breaking points - log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment - + (hyphProps.hyphenate == Constants.EN_TRUE ? " and hyphenation" : "")); - maxAdjustment = 20; - iBPcount - = findBreakingPoints(currPar, - context.getStackLimit().opt, - maxAdjustment, true); - } - } + findBreakingPoints(currPar, context.getStackLimit().opt); } } else { // this method has been called before @@ -543,72 +514,65 @@ public class LineLayoutManager extends InlineStackingLayoutManager { return curLineBP; } - private int findBreakingPoints(Paragraph par, int lineWidth, - double threshold, boolean force) { - int totalWidth = 0; - int totalStretch = 0; - int totalShrink = 0; - boolean bForced = false; - - // current element in the paragraph - KnuthElement thisElement = null; - // previous element in the paragraph is a KnuthBox - boolean previousIsBox = false; - - // create an active node representing the starting point - activeList = new LinkedList(); - activeList.add(new KnuthNode(0, 0, 1, 0, 0, 0, 0, 0, 0, null)); - - // main loop - ListIterator paragraphIterator = par.listIterator(); - while (paragraphIterator.hasNext()) { - thisElement = (KnuthElement) paragraphIterator.next(); - if (thisElement.isBox()) { - // a KnuthBox object is not a legal line break - totalWidth += thisElement.getW(); - previousIsBox = true; - } else if (thisElement.isGlue()) { - // a KnuthGlue object is a legal line break - // only if the previous object is a KnuthBox - if (previousIsBox) { - considerLegalBreak(par, lineWidth, thisElement, - totalWidth, totalStretch, totalShrink, - threshold); - } - totalWidth += thisElement.getW(); - totalStretch += ((KnuthGlue) thisElement).getY(); - totalShrink += ((KnuthGlue) thisElement).getZ(); - previousIsBox = false; + /** + * Find a set of breaking points. + * This method is called only once by getNextBreakPoss, and it + * subsequently calls the other findBreakingPoints() method with + * different parameters, until a set of breaking points is found. + * + * @param par the list of elements that must be parted + * into lines + * @param lineWidth the desired length ot the lines + */ + private void findBreakingPoints(Paragraph par, int lineWidth) { + // maximum adjustment ratio permitted + float maxAdjustment = 1; + + // first try + if (!findBreakingPoints(par, lineWidth, + maxAdjustment, KNUTH_ALGORITHM)) { + // the first try failed, now try something different + log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment); + if (hyphProps.hyphenate == Constants.EN_TRUE) { + // consider every hyphenation point as a legal break + findHyphenationPoints(par); } else { - // a KnuthPenalty is a legal line break - // only if its penalty is not infinite - if (((KnuthPenalty) thisElement).getP() - < KnuthElement.INFINITE) { - considerLegalBreak(par, lineWidth, thisElement, - totalWidth, totalStretch, totalShrink, - threshold); - } - previousIsBox = false; + // try with a higher threshold + maxAdjustment = 5; } - } - if (activeList.size() == 0) { - if (force) { - activeList.add(lastDeactivatedNode); - bForced = true; - log.error("Could not find a set of breaking points"); - } else { - return 0; + if (!findBreakingPoints(par, lineWidth, + maxAdjustment, KNUTH_ALGORITHM)) { + // the second try failed too, try with a huge threshold; + // if this fails too, use a different algorithm + log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment + + (hyphProps.hyphenate == Constants.EN_TRUE ? " and hyphenation" : "")); + maxAdjustment = 20; + if (!findBreakingPoints(par, lineWidth, + maxAdjustment, KNUTH_ALGORITHM)) { + log.debug("No set of breaking points found, using first-fit algorithm"); + findBreakingPoints(par, lineWidth, + maxAdjustment, FIRST_FIT_ALGORITHM); + } } } - // there is at least one set of breaking points + // now: + // * if the Knuth's algorithm found at least a set of breaking point, + // activeList.size() >= 1 and bestDeactivatedNode == null + // * if the Knuth's algorithm failed, and the first-fit algorithm was + // called, activeList.size() == 1 and bestDeactivatedNode != null + + // number of lines that will be created + int line = 0; + // node representing the chosen last breakpoint + KnuthNode bestActiveNode = null; + + // if there are different sets of breaking points // choose the active node with fewest total demerits ListIterator activeListIterator = activeList.listIterator(); KnuthNode tempNode = null; - KnuthNode bestActiveNode = null; double bestDemerits = BestRecords.INFINITE_DEMERITS; - int line = 0; while (activeListIterator.hasNext()) { tempNode = (KnuthNode) activeListIterator.next(); if (tempNode.totalDemerits < bestDemerits) { @@ -644,10 +608,10 @@ public class LineLayoutManager extends InlineStackingLayoutManager { // compute indent and adjustment ratio, according to // the value of text-align and text-align-last int indent = 0; - int difference = (bestActiveNode.line < line || bForced) + int difference = (bestActiveNode.line < line) ? bestActiveNode.difference : bestActiveNode.difference + par.lineFillerWidth; - int textAlign = (bestActiveNode.line < line || bForced) + int textAlign = (bestActiveNode.line < line) ? bTextAlignment : bTextAlignmentLast; indent += (textAlign == EN_CENTER) ? difference / 2 @@ -660,23 +624,99 @@ public class LineLayoutManager extends InlineStackingLayoutManager { makeLineBreakPosition(par, (i > 1 ? bestActiveNode.previous.position + 1: 0), - bestActiveNode.position, + bestActiveNode.position, 0, ratio, indent); bestActiveNode = bestActiveNode.previous; } - if (bForced) { - fallback(par, line); - } activeList.clear(); - return line; } - private void fallback(Paragraph par, int line) { - makeLineBreakPosition(par, - lastDeactivatedNode.position, - par.size() - 1, - line, 0, 0); + /** + * Perform a line-breaking algorithm. + * + * @param par the list of elements that must be parted + * into lines + * @param lineWidth the desired length ot the lines + * @param threshold the maximum adjustment ratio permitted + * @param algorithm a constant identifying the algorithm to perform + * @return true if the algorithm succeeded, false if it failed + */ + private boolean findBreakingPoints(Paragraph par, int lineWidth, + double threshold, int algorithm) { + int totalWidth = 0; + int totalStretch = 0; + int totalShrink = 0; + + // current element in the paragraph + KnuthElement thisElement = null; + // previous element in the paragraph is a KnuthBox + boolean previousIsBox = false; + + // create an active node representing the starting point + activeList = new LinkedList(); + if (algorithm == KNUTH_ALGORITHM) { + bestDeactivatedNode = null; + activeList.add(new KnuthNode(0, 0, 1, 0, 0, 0, 0, 0, 0, null)); + } else { + activeList.add(new KnuthNode(bestDeactivatedNode.position, + bestDeactivatedNode.line, + 1, 0, 0, 0, + bestDeactivatedNode.adjustRatio, + bestDeactivatedNode.difference, 0, + bestDeactivatedNode.previous)); + } + + // main loop + ListIterator paragraphIterator = par.listIterator(); + while (paragraphIterator.hasNext()) { + thisElement = (KnuthElement) paragraphIterator.next(); + if (thisElement.isBox()) { + // a KnuthBox object is not a legal line break + totalWidth += thisElement.getW(); + previousIsBox = true; + } else if (thisElement.isGlue()) { + // a KnuthGlue object is a legal line break + // only if the previous object is a KnuthBox + if (previousIsBox) { + if (algorithm == KNUTH_ALGORITHM) { + considerLegalBreakKnuth(par, lineWidth, thisElement, + totalWidth, totalStretch, totalShrink, + threshold); + } else { + considerLegalBreakFirstFit(par, lineWidth, thisElement, + totalWidth, totalStretch, totalShrink, + threshold); + } + } + totalWidth += thisElement.getW(); + totalStretch += ((KnuthGlue) thisElement).getY(); + totalShrink += ((KnuthGlue) thisElement).getZ(); + previousIsBox = false; + } else { + // a KnuthPenalty is a legal line break + // only if its penalty is not infinite + if (((KnuthPenalty) thisElement).getP() + < KnuthElement.INFINITE) { + if (algorithm == KNUTH_ALGORITHM) { + considerLegalBreakKnuth(par, lineWidth, thisElement, + totalWidth, totalStretch, totalShrink, + threshold); + } else { + considerLegalBreakFirstFit(par, lineWidth, thisElement, + totalWidth, totalStretch, totalShrink, + threshold); + } + } + previousIsBox = false; + } + } + + if (algorithm == KNUTH_ALGORITHM && activeList.size() > 0) { + // bestDeactivatedNode is useless, as the algorithm did not fail + bestDeactivatedNode = null; + } + return (activeList.size() > 0); } private void makeLineBreakPosition(Paragraph par, @@ -728,10 +768,10 @@ public class LineLayoutManager extends InlineStackingLayoutManager { lineLead)); } - private void considerLegalBreak(LinkedList par, int lineWidth, - KnuthElement element, - int totalWidth, int totalStretch, - int totalShrink, double threshold) { + private void considerLegalBreakKnuth(LinkedList par, int lineWidth, + KnuthElement element, + int totalWidth, int totalStretch, + int totalShrink, double threshold) { KnuthNode activeNode = null; ListIterator activeListIterator = activeList.listIterator(); @@ -801,7 +841,15 @@ public class LineLayoutManager extends InlineStackingLayoutManager { tempNode = (KnuthNode) activeListIterator.previous(); iCallNext ++; } - lastDeactivatedNode = tempNode; + // use bestDeactivatedNode to keep a pointer to a "good" + // node that could be used if the algorithm fails + if (bestDeactivatedNode == null + || tempNode.line == bestDeactivatedNode.line + && tempNode.totalDemerits < bestDeactivatedNode.totalDemerits + || tempNode.line > bestDeactivatedNode.line + && tempNode.totalDemerits < bestDeactivatedNode.totalDemerits + MAX_DEMERITS_INCREASE) { + bestDeactivatedNode = tempNode; + } activeListIterator.remove(); for (int i = 0; i < iCallNext; i++) { activeListIterator.next(); @@ -938,6 +986,90 @@ public class LineLayoutManager extends InlineStackingLayoutManager { } // end of the outer while } + private void considerLegalBreakFirstFit(LinkedList par, int lineWidth, + KnuthElement element, + int totalWidth, int totalStretch, + int totalShrink, double threshold) { + KnuthNode startNode; + KnuthNode endNode; + endNode = (KnuthNode) activeList.getFirst(); + if (endNode.previous != null) { + startNode = endNode.previous; + } else { + startNode = endNode; + endNode = null; + } + + // these are the new values that must be computed + // in order to define a new active node + int newWidth; + int newStretch; + int newShrink; + int newDifference; + double newRatio; + + // compute width, stretch and shrink of the new node + newWidth = totalWidth; + newStretch = totalStretch; + newShrink = totalShrink; + ListIterator tempIterator = par.listIterator(par.indexOf(element)); + while (tempIterator.hasNext()) { + KnuthElement tempElement = (KnuthElement)tempIterator.next(); + if (tempElement.isBox()) { + break; + } else if (tempElement.isGlue()) { + newWidth += ((KnuthGlue) tempElement).getW(); + newStretch += ((KnuthGlue) tempElement).getY(); + newShrink += ((KnuthGlue) tempElement).getZ(); + } else if (((KnuthPenalty) tempElement).getP() + == -KnuthElement.INFINITE + && tempElement != element) { + break; + } + } + + if (endNode == null + || totalWidth + (element.isPenalty() ? element.getW() : 0) - startNode.totalWidth <= lineWidth + || bTextAlignment == EN_JUSTIFY + && totalWidth + (element.isPenalty() ? element.getW() : 0)- startNode.totalWidth - (totalShrink - startNode.totalShrink) <= lineWidth) { + // add content to the same line + // compute difference and ratio + int actualWidth = totalWidth - startNode.totalWidth; + if (element.isPenalty()) { + actualWidth += element.getW(); + } + newDifference = lineWidth - actualWidth; + int available = newDifference >= 0 ? totalStretch - startNode.totalStretch + : totalShrink - startNode.totalShrink; + newRatio = available != 0 ? (float) newDifference / available + : 0; + + activeList.removeFirst(); + activeList.add(new KnuthNode(par.indexOf(element), startNode.line + 1, 0, + newWidth, newStretch, newShrink, + newRatio, newDifference, 0.0, + startNode)); + } else { + // start a new line + // compute difference and ratio + int actualWidth = totalWidth - endNode.totalWidth; + if (element.isPenalty()) { + actualWidth += element.getW(); + } + newDifference = lineWidth - actualWidth; + int available = newDifference >= 0 ? totalStretch - endNode.totalStretch + : totalShrink - endNode.totalShrink; + newRatio = available != 0 ? (float) newDifference / available + : 0; + + activeList.removeFirst(); + activeList.add(new KnuthNode(par.indexOf(element), endNode.line + 1, 0, + newWidth, newStretch, newShrink, + newRatio, newDifference, 0.0, + endNode)); + } + } + /** * find hyphenation points for every word int the current paragraph * @ param currPar the paragraph whose words will be hyphenated