package org.apache.fop.layoutmgr;
-import java.util.LinkedList;
-import java.util.ListIterator;
-
+import java.util.ArrayList;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.traits.MinOptMax;
+
+/**
+ * The set of nodes is sorted into lines indexed into activeLines.
+ * The nodes in each line are linked together in a single linked list by the
+ * KnuthNode.next field. The activeLines array contains a link to the head of
+ * the linked list in index 'line*2' and a link to the tail at index 'line*2+1'.
+ * <p>
+ * The set of active nodes can be traversed by
+ * <pre>
+ * for (int line = startLine; line < endLine; line++) {
+ * for (KnuthNode node = getNode(line); node != null; node = node.next) {
+ * // Do something with 'node'
+ * }
+ * }
+ * </pre>
+ */
public abstract class BreakingAlgorithm {
+ // parameters of Knuth's algorithm:
+ // penalty value for flagged penalties
+ private int flaggedPenalty = 50;
+ // demerit for consecutive lines ending at flagged penalties
+ private int repeatedFlaggedDemerit = 50;
+ // demerit for consecutive lines belonging to incompatible fitness classes
+ private int incompatibleFitnessDemerit = 50;
+ // suggested modification to the "optimum" number of lines
+ private int looseness = 0;
+
+ /**
+ * The threshold for considering breaks to be acceptable.
+ */
+ private double threshold;
+
+ /**
+ * The paragraph of KnuthElements.
+ */
+ private KnuthSequence par;
+
+ /**
+ * The width of a line.
+ */
+ private int lineWidth = 0;
+ private boolean force = false;
+
+ protected KnuthNode lastDeactivatedNode = null;
+ private KnuthNode lastTooLong;
+ private KnuthNode lastTooShort;
+ private KnuthNode lastDeactivated;
+
+ protected int alignment;
+ protected int alignmentLast;
+ protected boolean bFirst;
+
+ /**
+ * The set of active nodes.
+ */
+ private KnuthNode[] activeLines;
+
+ /**
+ * The number of active nodes.
+ */
+ protected int activeNodeCount;
+
+ /**
+ * The lowest available line in the set of active nodes.
+ */
+ protected int startLine = 0;
+
+ /**
+ * The highest + 1 available line in the set of active nodes.
+ */
+ protected int endLine = 0;
+
+ /**
+ * The total width of all elements handled so far.
+ */
+ private int totalWidth;
+
+ /**
+ * The total stretch of all elements handled so far.
+ */
+ private int totalStretch = 0;
+
+ /**
+ * The total shrink of all elements handled so far.
+ */
+ private int totalShrink = 0;
+
+ private BestRecords best;
+ private KnuthNode[] positions;
+
+ private static final int INFINITE_RATIO = 1000;
+
+ protected static Log log = LogFactory.getLog(KnuthParagraph.class);
+
+ public BreakingAlgorithm(int align, int alignLast,
+ boolean first) {
+ alignment = align;
+ alignmentLast = alignLast;
+ bFirst = first;
+ this.best = new BestRecords();
+ }
- private final int KNUTH_ALGORITHM = 0;
- private final int FIRST_FIT_ALGORITHM = 1;
// this class represent a feasible breaking point
public class KnuthNode {
// adjustment ratio if the line ends at this breakpoint
public double adjustRatio;
-/*LF*/ public int availableShrink;
-/*LF*/ public int availableStretch;
+ // available stretch of the line ending at this breakpoint
+ public int availableShrink;
+
+ // available shrink of the line ending at this breakpoint
+ public int availableStretch;
// difference between target and actual line width
public int difference;
// best node for the preceding breakpoint
public KnuthNode previous;
+ // next possible node in the same line
+ public KnuthNode next;
+
+
public KnuthNode(int position, int line, int fitness,
int totalWidth, int totalStretch, int totalShrink,
double adjustRatio, int availableShrink, int availableStretch, int difference,
this.totalStretch = totalStretch;
this.totalShrink = totalShrink;
this.adjustRatio = adjustRatio;
-/*LF*/ this.availableShrink = availableShrink;
-/*LF*/ this.availableStretch = availableStretch;
+ this.availableShrink = availableShrink;
+ this.availableStretch = availableStretch;
this.difference = difference;
this.totalDemerits = totalDemerits;
this.previous = previous;
}
+
+ public String toString() {
+ return "<KnuthNode at " + position + " " +
+ totalWidth + "+" + totalStretch + "-" + totalShrink +
+ " line:" + line +
+ " prev:" + (previous != null ? previous.position : -1) +
+ " dem:" + totalDemerits +
+ ">";
+ }
}
// this class stores information about how the nodes
// which could start a line
// ending at the current element
private class BestRecords {
- private static final double INFINITE_DEMERITS = 1E11;
-
- private double bestDemerits[] = {
- INFINITE_DEMERITS, INFINITE_DEMERITS,
- INFINITE_DEMERITS, INFINITE_DEMERITS
- };
- private KnuthNode bestNode[] = {null, null, null, null};
- private double bestAdjust[] = {0.0, 0.0, 0.0, 0.0};
- private int bestDifference[] = {0, 0, 0, 0};
-/*LF*/ private int bestAvailableShrink[] = {0, 0, 0, 0};
-/*LF*/ private int bestAvailableStretch[] = {0, 0, 0, 0};
+ private static final double INFINITE_DEMERITS = Double.POSITIVE_INFINITY;
+ //private static final double INFINITE_DEMERITS = 1E11;
+
+ private double bestDemerits[] = new double[4];
+ private KnuthNode bestNode[] = new KnuthNode[4];
+ private double bestAdjust[] = new double[4];
+ private int bestDifference[] = new int[4];
+ private int bestAvailableShrink[] = new int[4];
+ private int bestAvailableStretch[] = new int[4];
private int bestIndex = -1;
public BestRecords() {
+ reset();
}
public void addRecord(double demerits, KnuthNode node, double adjust,
int availableShrink, int availableStretch, int difference, int fitness) {
if (demerits > bestDemerits[fitness]) {
- //log.error("New demerits value greter than the old one");
+ log.error("New demerits value greter than the old one");
}
bestDemerits[fitness] = demerits;
bestNode[fitness] = node;
bestAdjust[fitness] = adjust;
-/*LF*/ bestAvailableShrink[fitness] = availableShrink;
-/*LF*/ bestAvailableStretch[fitness] = availableStretch;
+ bestAvailableShrink[fitness] = availableShrink;
+ bestAvailableStretch[fitness] = availableStretch;
bestDifference[fitness] = difference;
if (bestIndex == -1 || demerits < bestDemerits[bestIndex]) {
bestIndex = fitness;
return bestAdjust[fitness];
}
-/*LF*/ public int getAvailableShrink(int fitness) {
-/*LF*/ return bestAvailableShrink[fitness];
-/*LF*/ }
+ public int getAvailableShrink(int fitness) {
+ return bestAvailableShrink[fitness];
+ }
-/*LF*/ public int getAvailableStretch(int fitness) {
-/*LF*/ return bestAvailableStretch[fitness];
-/*LF*/ }
+ public int getAvailableStretch(int fitness) {
+ return bestAvailableStretch[fitness];
+ }
public int getDifference(int fitness) {
return bestDifference[fitness];
return INFINITE_DEMERITS;
}
}
- }
-
- // parameters of Knuth's algorithm:
- // penalty value for flagged penalties
- private int flaggedPenalty = 50;
- // demerit for consecutive lines ending at flagged penalties
- private int repeatedFlaggedDemerit = 50;
- // demerit for consecutive lines belonging to incompatible fitness classes
- private int incompatibleFitnessDemerit = 50;
- // suggested modification to the "optimum" number of lines
- private int looseness = 0;
-
- private static final int INFINITE_RATIO = 1000;
- private static int MAX_DEMERITS_INCREASE = 1000;
- protected LinkedList activeList = null;
- protected LinkedList auxActiveList = null;
-// private LinkedList inactiveList = null;
- private LinkedList auxInactiveList = null;
- protected KnuthNode lastDeactivatedNode = null;
- private KnuthNode lastTooLong;
- private KnuthNode lastTooShort;
- private KnuthNode lastDeactivated;
+ public void reset() {
+ for (int i = 0; i < 4; i ++) {
+ bestDemerits[i] = INFINITE_DEMERITS;
+ bestNode[i] = null;
+ bestAdjust[i] = 0.0;
+ bestDifference[i] = 0;
+ bestAvailableShrink[i] = 0;
+ bestAvailableStretch[i] = 0;
+ }
+ bestIndex = -1;
+ }
+ }
- protected int alignment;
- protected int alignmentLast;
- protected boolean bFirst;
- public BreakingAlgorithm(int align, int alignLast,
- boolean first) {
- alignment = align;
- alignmentLast = alignLast;
- bFirst = first;
- }
public abstract void updateData1(int total, double demerits) ;
public int findBreakingPoints(KnuthSequence par, int lineWidth,
double threshold, boolean force,
boolean hyphenationAllowed) {
+ this.par = par;
+ this.threshold = threshold;
+ this.force = force;
+ this.lineWidth = lineWidth;
+ this.totalWidth = 0;
+ this.totalStretch = 0;
+ this.totalShrink = 0;
- findBreakingPoints(KNUTH_ALGORITHM, par, 0, lineWidth, threshold, force, hyphenationAllowed);
-
- boolean bForced = false;
- int optLineNumber;
- if (activeList.size() > 0) {
- // there is at least one set of breaking points
- // select one or more active nodes, removing the others from the list
- optLineNumber = filterActiveList();
-/* a causa delle modifiche di Finn Bock, non c'e' piu' bisogno di usare first fit */
-// } else if (force) {
-// // no set of breaking points, but must find one
-// // use a different algorithm
-///*LF*/ System.out.println("si ricorre all'algoritmo first fit dalla riga " + lastDeactivatedNode.line + " elemento " + (lastDeactivatedNode.position == 0 ? 0 :lastDeactivatedNode.position + 1));
-// findBreakingPoints(FIRST_FIT_ALGORITHM, par, lastDeactivatedNode.position == 0 ? 0 :lastDeactivatedNode.position + 1, lineWidth, threshold, force, hyphenationAllowed);
-// activeList.add(auxActiveList.getLast());
-// optLineNumber = ((KnuthNode) auxActiveList.getLast()).line;
-// bForced = true;
- } else {
- // no set of breaking points, do nothing
- return 0;
- }
-
- // now, activeList is not empty:
- // if bForced == true, there is ONLY one node in activeList
- // if bForced == false, there is AT LEAST one node in activeList
-/*LF*/ //System.out.println("BA> ora activeList.size() = " + activeList.size());
- ListIterator activeListIterator = activeList.listIterator();
- while (activeListIterator.hasNext()) {
- KnuthNode activeNode = (KnuthNode)activeListIterator.next();
- int totalLines;
- if (bForced) {
- updateData1(optLineNumber, 0);
- totalLines = optLineNumber;
- auxActiveList.clear();
- auxInactiveList.clear();
- } else {
- updateData1(activeNode.line, activeNode.totalDemerits);
- totalLines = activeNode.line;
- }
- calculateBreakPoints(activeNode, par, totalLines);
- }
-
- activeList.clear();
-// inactiveList.clear();
- return optLineNumber;
- }
-
-/* ---------------------------- */
- public int firstFit(KnuthSequence par, int lineWidth,
- double threshold, boolean force) {
- lastDeactivatedNode = new KnuthNode(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, null);
-
- findBreakingPoints(FIRST_FIT_ALGORITHM, par, 0, lineWidth, threshold, force, true);
-
-/*LF*/ activeList = new LinkedList();
-///*LF*/ inactiveList = new LinkedList();
- activeList.add(auxActiveList.getLast());
- int optLineNumber = ((KnuthNode) auxActiveList.getLast()).line;
-
- // now, activeList is not empty:
- // if bForced == true, there is ONLY one node in activeList
- // if bForced == false, there is AT LEAST one node in activeList
-/*LF*/ //System.out.println("BA> ora activeList.size() = " + activeList.size());
- ListIterator activeListIterator = activeList.listIterator();
- while (activeListIterator.hasNext()) {
- KnuthNode activeNode = (KnuthNode)activeListIterator.next();
- int totalLines;
- updateData1(optLineNumber, 0);
- totalLines = optLineNumber;
- auxActiveList.clear();
- auxInactiveList.clear();
- calculateBreakPoints(activeNode, par, totalLines);
- }
-
- activeList.clear();
-// inactiveList.clear();
- return optLineNumber;
- }
-/* ---------------------------- */
-
- private void findBreakingPoints(int algorithm,
- KnuthSequence par, int firstIndex,
- int lineWidth,
- double threshold, boolean force,
- boolean hyphenationAllowed) {
- int totalWidth = 0;
- int totalStretch = 0;
- int totalShrink = 0;
+ activeLines = new KnuthNode[20];
// reset lastTooShort and lastTooLong, as they could be not null
// because of previous calls to findBreakingPoints
lastTooShort = lastTooLong = null;
-
+ // reset startLine and endLine
+ startLine = endLine = 0;
// current element in the paragraph
KnuthElement thisElement = null;
- // previous element in the paragraph is a KnuthBox
+ // previous element in the paragraph is a KnuthBox?
boolean previousIsBox = false;
-/*LF*/ // index of the first KnuthBox in the sequence
-/*LF*/ int firstBoxIndex = firstIndex;
-/*LF*/ while (alignment != org.apache.fop.fo.Constants.EN_CENTER
-/*LF*/ && ! ((KnuthElement) par.get(firstBoxIndex)).isBox()) {
-/*LF*/ firstBoxIndex++;
-/*LF*/ }
-/*LF*/
+ // index of the first KnuthBox in the sequence
+ int firstBoxIndex = 0;
+ while (alignment != org.apache.fop.fo.Constants.EN_CENTER
+ && ! ((KnuthElement) par.get(firstBoxIndex)).isBox()) {
+ firstBoxIndex++;
+ }
+
// create an active node representing the starting point
- if (algorithm == KNUTH_ALGORITHM) {
- activeList = new LinkedList();
-/*LF*/ activeList.add(new KnuthNode(firstBoxIndex, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, null));
-// inactiveList = new LinkedList();
- } else {
- auxActiveList = new LinkedList();
-/*LF*/ auxActiveList.add(new KnuthNode(lastDeactivatedNode.position, lastDeactivatedNode.line, 1,
- 0, 0, 0,
- lastDeactivatedNode.adjustRatio,
- 0, 0, lastDeactivatedNode.difference,
- 0, lastDeactivatedNode.previous));
- auxInactiveList = new LinkedList();
+ activeLines = new KnuthNode[20];
+ addNode(0, new KnuthNode(firstBoxIndex, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, null));
+
+ if (log.isTraceEnabled()) {
+ log.trace("Looping over " + par.size() + " box objects");
}
+
+ KnuthNode lastForced = getNode(0);
- KnuthNode lastForced = (KnuthNode) activeList.getFirst();
// main loop
-/*LF*/ ListIterator paragraphIterator = par.listIterator(firstBoxIndex);
-/*LF*/ //System.out.println("");
-/*LF*/ //System.out.println("inizio main loop");
- while (paragraphIterator.hasNext()) {
- thisElement = (KnuthElement) paragraphIterator.next();
-/*LF*/ //System.out.println("main loop, elemento " + paragraphIterator.previousIndex());
+ for (int i = 0; i < par.size(); i++) {
+ thisElement = getElement(i);
if (thisElement.isBox()) {
// a KnuthBox object is not a legal line break
totalWidth += thisElement.getW();
// a KnuthGlue object is a legal line break
// only if the previous object is a KnuthBox
if (previousIsBox) {
- if (algorithm == KNUTH_ALGORITHM) {
- considerLegalBreak(par, lineWidth, thisElement,
- totalWidth, totalStretch, totalShrink,
- threshold);
- } else {
- considerLegalBreak2(par, lineWidth, thisElement,
- totalWidth, totalStretch, totalShrink,
- threshold);
- }
+ considerLegalBreak(thisElement, i);
}
totalWidth += thisElement.getW();
- totalStretch += ((KnuthGlue) thisElement).getY();
- totalShrink += ((KnuthGlue) thisElement).getZ();
+ totalStretch += thisElement.getY();
+ totalShrink += thisElement.getZ();
previousIsBox = false;
} else {
// a KnuthPenalty is a legal line break
if (((KnuthPenalty) thisElement).getP()
< KnuthElement.INFINITE
&& (hyphenationAllowed || !((KnuthPenalty) thisElement).isFlagged())) {
- if (algorithm == KNUTH_ALGORITHM) {
- considerLegalBreak(par, lineWidth, thisElement,
- totalWidth, totalStretch, totalShrink,
- threshold);
- } else {
- considerLegalBreak2(par, lineWidth, thisElement,
- totalWidth, totalStretch, totalShrink,
- threshold);
- }
+ considerLegalBreak(thisElement, i);
}
previousIsBox = false;
}
- /* *** inizio modifiche proposte da Finn Bock *** */
- if (activeList.size() == 0) {
+ if (activeNodeCount == 0) {
if (!force) {
- return;
+ log.debug("Could not find a set of breaking points " + threshold);
+ return 0;
}
-/*LF*/ //System.out.println(" ");
-/*LF*/ //System.out.println(" lastTooShort.position= " + (lastTooShort == null ? -1 : lastTooShort.position) + " lastTooLong.position= " + (lastTooLong == null ? -1 : lastTooLong.position));
if (lastTooShort == null || lastForced.position == lastTooShort.position) {
lastForced = lastTooLong;
} else {
lastForced = lastTooShort;
}
-/*LF*/ //System.out.println(" Restarting: position= " + lastForced.position);
-/*LF*/ //System.out.println(" ");
+ log.debug("Restarting at node " + lastForced);
lastForced.totalDemerits = 0;
- activeList.add(lastForced);
+ addNode(lastForced.line, lastForced);
+ i = lastForced.position;
+ startLine = lastForced.line;
+ endLine = startLine + 1;
totalWidth = lastForced.totalWidth;
totalStretch = lastForced.totalStretch;
totalShrink = lastForced.totalShrink;
lastTooShort = lastTooLong = null;
- while (paragraphIterator.nextIndex() > (lastForced.position + 1)) {
- paragraphIterator.previous();
- }
}
- /* *** fine modifiche proposte da Finn Bock *** */
}
- }
-
- private void considerLegalBreak2(LinkedList par, int lineWidth,
- KnuthElement element,
- int totalWidth, int totalStretch,
- int totalShrink, double threshold) {
- //System.out.println("(" + par.indexOf(element) + ")");
- KnuthNode startNode = (KnuthNode) auxActiveList.getFirst();
- KnuthNode endNode = auxActiveList.size() > 1 ? (KnuthNode) auxActiveList.getLast() : 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 (log.isTraceEnabled()) {
+ log.trace("Main loop completed " + activeNodeCount);
+ log.trace("Active nodes=" + toString(""));
}
- if (endNode == null
- || totalWidth + (element.isPenalty() ? element.getW() : 0) - startNode.totalWidth <= lineWidth
- || alignment == org.apache.fop.fo.Constants.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;
-
- if (endNode != null) {
- auxActiveList.removeLast();
- }
- auxActiveList.add(new KnuthNode(par.indexOf(element), startNode.line + 1, 0,
- newWidth, newStretch, newShrink,
- newRatio, 0, 0, newDifference, 0.0,
- startNode));
- //System.out.println("da " + startNode.position + " a " + par.indexOf(element));
- //System.out.println("difference = " + newDifference + " available = " + available + " ratio = " + newRatio);
- //System.out.println(" ");
- } else {
- // start a new line
+ // there is at least one set of breaking points
+ // select one or more active nodes, removing the others from the list
+ int line = filterActiveNodes();
- // compute difference and ratio
- int actualWidth = totalWidth - endNode.totalWidth;
- if (element.isPenalty()) {
- actualWidth += element.getW();
+ // for each active node, create a set of breaking points
+ for (int i = startLine; i < endLine; i++) {
+ for (KnuthNode node = getNode(line); node != null; node = node.next) {
+ updateData1(node.line, node.totalDemerits);
+ calculateBreakPoints(node, par, node.line);
}
- newDifference = lineWidth - actualWidth;
- int available = newDifference >= 0 ? totalStretch - endNode.totalStretch
- : totalShrink - endNode.totalShrink;
- newRatio = available != 0 ? (float) newDifference / available
- : 0;
-
- auxInactiveList.add(auxActiveList.removeFirst());
- auxActiveList.add(new KnuthNode(par.indexOf(element), endNode.line + 1, 0,
- newWidth, newStretch, newShrink,
- newRatio, 0, 0, newDifference, 0.0,
- endNode));
- //System.out.println("da " + endNode.position + " a " + par.indexOf(element));
- //System.out.println("difference = " + newDifference + " available = " + available + " ratio = " + newRatio);
- //System.out.println(" ");
}
- }
+ activeLines = null;
+ return line;
+ }
- private void considerLegalBreak(LinkedList par, int lineWidth,
- KnuthElement element,
- int totalWidth, int totalStretch,
- int totalShrink, double threshold) {
- KnuthNode activeNode = null;
+ private void considerLegalBreak(KnuthElement element, int elementIdx) {
- ListIterator activeListIterator = activeList.listIterator();
- if (activeListIterator.hasNext()) {
- activeNode = (KnuthNode)activeListIterator.next();
- } else {
- activeNode = null;
+ if (log.isTraceEnabled()) {
+ log.trace("Feasible breakpoint at " + par.indexOf(element) + " " + totalWidth + "+" + totalStretch + "-" + totalShrink);
+ log.trace("\tCurrent active node list: " + activeNodeCount + " " + this.toString("\t"));
}
lastDeactivated = null;
lastTooLong = null;
-
- while (activeNode != null) {
- BestRecords best = new BestRecords();
-
- // these are the new values that must be computed
- // in order to define a new active node
- int newLine = 0;
- int newFitnessClass = 0;
- int newWidth = 0;
- int newStretch = 0;
- int newShrink = 0;
- double newIPDAdjust = 0;
- double newDemerits = 0;
-
- while (activeNode != null) {
- // compute the line number
- newLine = activeNode.line + 1;
-
- // compute the adjustment ratio
- int actualWidth = totalWidth - activeNode.totalWidth;
- if (element.isPenalty()) {
- actualWidth += element.getW();
+ for (int line = startLine; line < endLine; line++) {
+ for (KnuthNode node = getNode(line); node != null; node = node.next) {
+ if (node.position == elementIdx) {
+ continue;
}
- int neededAdjustment = lineWidth - actualWidth;
- int maxAdjustment = 0;
-/*LF*/ int availableShrink = totalShrink - activeNode.totalShrink;
-/*LF*/ int availableStretch = totalStretch - activeNode.totalStretch;
- if (neededAdjustment > 0) {
- maxAdjustment = availableStretch;
- if (maxAdjustment > 0) {
- newIPDAdjust
- = (double) neededAdjustment / maxAdjustment;
- } else {
- newIPDAdjust = INFINITE_RATIO;
- }
- } else if (neededAdjustment < 0) {
- maxAdjustment = availableShrink;
- if (maxAdjustment > 0) {
- newIPDAdjust
- = (double) neededAdjustment / maxAdjustment;
- } else {
- newIPDAdjust = -INFINITE_RATIO;
- }
-/*LF*/ } else {
-/*LF*/ // neededAdjustment == 0
-/*LF*/ newIPDAdjust = 0;
+ int difference = computeDifference(node, element);
+ double r = computeAdjustmentRatio(node, difference);
+ int availableShrink = totalShrink - node.totalShrink;
+ int availableStretch = totalStretch - node.totalStretch;
+ if (log.isTraceEnabled()) {
+ log.trace("\tr=" + r);
+ log.trace("\tline=" + line);
}
- /* calcola demeriti e fitness class in ogni caso, poi,
- * a seconda che il coefficiente sia o meno nel range permesso,
- * aggiorna i record o i migliori nodi disattivati
- */
- // compute demerits and fitness class
- if (element.isPenalty()
- && ((KnuthPenalty) element).getP() >= 0) {
- newDemerits
- = Math.pow((1
- + 100 * Math.pow(Math.abs(newIPDAdjust), 3)
- + ((KnuthPenalty) element).getP()), 2);
- } else if (element.isPenalty()
- && ((KnuthPenalty)element).getP()
- > - KnuthElement.INFINITE) {
- /*> -INFINITE_RATIO) { ??? */
- newDemerits
- = Math.pow((1
- + 100 * Math.pow(Math.abs(newIPDAdjust), 3)), 2)
- - Math.pow(((KnuthPenalty) element).getP(), 2);
- } else {
- newDemerits
- = Math.pow((1
- + 100 * Math.pow(Math.abs(newIPDAdjust), 3)), 2);
- }
- if (element.isPenalty()
- && ((KnuthPenalty) element).isFlagged()
- && ((KnuthElement) par.get(activeNode.position)).isPenalty()
- && ((KnuthPenalty) par.get(activeNode.position)).isFlagged()) {
- // add demerit for consecutive breaks at flagged penalties
- newDemerits += repeatedFlaggedDemerit;
- }
- if (newIPDAdjust < -0.5) {
- newFitnessClass = 0;
- } else if (newIPDAdjust <= 0.5) {
- newFitnessClass = 1;
- } else if (newIPDAdjust <= 1) {
- newFitnessClass = 2;
- } else {
- newFitnessClass = 3;
- }
- if (Math.abs(newFitnessClass - activeNode.fitness) > 1) {
- // add demerit for consecutive breaks
- // with very different fitness classes
- newDemerits += incompatibleFitnessDemerit;
+ // The line would be too long.
+ if (r < -1 || element.isForcedBreak()) {
+ // Deactivate node.
+ if (log.isTraceEnabled()) {
+ log.trace("Removing " + node);
+ }
+ removeNode(line, node);
+ lastDeactivated = compareNodes(lastDeactivated, node);
}
- newDemerits += activeNode.totalDemerits;
-
-/*LF*/ //System.out.println(" possibile soluzione, da " + activeNode.position + " a " + par.indexOf(element));
- if ((-1 <= newIPDAdjust) && (newIPDAdjust <= threshold)) {
-/*LF*/ //System.out.println(" possibile soluzione buona");
- if (newDemerits < best.getDemerits(newFitnessClass)) {
+
+ // The line is within the available shrink and the threshold.
+ if (r >= -1 && r <= threshold) {
+ int fitnessClass = computeFitness(r);
+ double demerits = computeDemerits(node, element, fitnessClass, r);
+
+ if (log.isTraceEnabled()) {
+ log.trace("\tDemerits=" + demerits);
+ log.trace("\tFitness class=" + fitnessClass);
+ }
+
+ if (demerits < best.getDemerits(fitnessClass)) {
// updates best demerits data
- best.addRecord(newDemerits, activeNode, newIPDAdjust,
- availableShrink, availableStretch, neededAdjustment, newFitnessClass);
-/*LF*/ lastTooShort = null;
+ best.addRecord(demerits, node, r, availableShrink, availableStretch,
+ difference, fitnessClass);
+ lastTooShort = null;
}
- } else {
- // la linea e' troppo piena o troppo vuota
- if (newIPDAdjust <= -1) {
-/*LF*/ //System.out.println(" possibile soluzione lunga");
- if (lastTooLong == null || newDemerits < lastTooLong.totalDemerits) {
-/*LF*/ //System.out.println(" aggiornato lastTooLong, da " + activeNode.position + " a " + par.indexOf(element));
- lastTooLong = new KnuthNode(par.indexOf(element), newLine, newFitnessClass,
+ }
+
+ // The line is way too short, but we are in forcing mode, so a node is
+ // calculated and stored in lastValidNode.
+ if (force && (r <= -1 || r > threshold)) {
+ int fitnessClass = computeFitness(r);
+ double demerits = computeDemerits(node, element, fitnessClass, r);
+ if (r <= -1) {
+ if (lastTooLong == null || demerits < lastTooLong.totalDemerits) {
+ lastTooLong = new KnuthNode(elementIdx, line + 1, fitnessClass,
totalWidth, totalStretch, totalShrink,
- newIPDAdjust, availableShrink, availableStretch, neededAdjustment, newDemerits, activeNode);
+ r, availableShrink, availableStretch,
+ difference, demerits, node);
+ if (log.isTraceEnabled()) {
+ log.trace("Picking tooLong " + lastTooLong);
+ }
}
} else {
-/*LF*/ //System.out.println(" possibile soluzione corta");
-/*LF*/ //if (lastTooShort != null) {
-/*LF*/ // System.out.println(" vecchia soluzione corta da " + lastTooShort.previous.position + " a " + lastTooShort.position);
-/*LF*/ //}
- if (lastTooShort == null || newDemerits <= lastTooShort.totalDemerits) {
-/*LF*/ //System.out.println(" aggiornato lastTooShort, da " + activeNode.position + " a " + par.indexOf(element));
- lastTooShort = new KnuthNode(par.indexOf(element), newLine, newFitnessClass,
+ if (lastTooShort == null || demerits <= lastTooShort.totalDemerits) {
+ lastTooShort = new KnuthNode(elementIdx, line + 1, fitnessClass,
totalWidth, totalStretch, totalShrink,
- newIPDAdjust, availableShrink, availableStretch, neededAdjustment, newDemerits, activeNode);
+ r, availableShrink, availableStretch,
+ difference, demerits, node);
+ if (log.isTraceEnabled()) {
+ log.trace("Picking tooShort " + lastTooShort);
+ }
}
}
}
- /* disattivazione di un nodo */
- if (newIPDAdjust < -1
- || (element.isPenalty()
- && ((KnuthPenalty)element).getP()
- == -KnuthElement.INFINITE)
-/*LF*/ && !(activeNode.position
- == par.indexOf(element))) {
- // deactivate activeNode
- KnuthNode tempNode
- = (KnuthNode)activeListIterator.previous();
- int iCallNext = 0;
- while (tempNode != activeNode) {
- // this is not the node we meant to remove!
- tempNode = (KnuthNode)activeListIterator.previous();
- iCallNext ++;
- }
- activeListIterator.remove();
- for (int i = 0; i < iCallNext; i++) {
- activeListIterator.next();
- }
- }
+ }
+ addBreaks(line, elementIdx);
+ }
+ }
- if (activeListIterator.hasNext()) {
- activeNode = (KnuthNode) activeListIterator.next();
- } else {
- activeNode = null;
- break;
- }
- if (activeNode.line >= newLine) {
- break;
- }
- } // end of the inner while
-
- if (best.hasRecords()) {
- // compute width, stratchability and shrinkability
- 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;
- }
- }
+ private void addBreaks(int line, int elementIdx) {
+ if (!best.hasRecords()) {
+ return;
+ }
- // add nodes to the active nodes list
- for (int i = 0; i <= 3; i++) {
- if (best.notInfiniteDemerits(i)
- && best.getDemerits(i)
- <= (best.getMinDemerits()
- + incompatibleFitnessDemerit)) {
- // the nodes in activeList must be ordered
- // by line number and position;
- // so:
- // 1) advance in the list until the end,
- // or a node with a higher line number, is reached
-/*LF*/ int iStepsForward = 0;
-/*LF*/ KnuthNode tempNode;
-/*LF*/ while (activeListIterator.hasNext()) {
-/*LF*/ iStepsForward ++;
-/*LF*/ tempNode = (KnuthNode) activeListIterator.next();
-/*LF*/ if (tempNode.line > (best.getNode(i).line + 1)) {
-/*LF*/ activeListIterator.previous();
-/*LF*/ iStepsForward --;
-/*LF*/ break;
-/*LF*/ }
-/*LF*/ }
- // 2) add the new node
- activeListIterator.add
- (new KnuthNode(par.indexOf(element),
- best.getNode(i).line + 1, i,
- newWidth, newStretch, newShrink,
- best.getAdjust(i),
- best.getAvailableShrink(i), best.getAvailableStretch(i),
- best.getDifference(i),
- best.getDemerits(i),
- best.getNode(i)));
- // 3) go back
-/*LF*/ for (int j = 0;
-/*LF*/ j <= iStepsForward;
-/*LF*/ j ++) {
-/*LF*/ activeListIterator.previous();
-/*LF*/ }
- }
+ int newWidth = totalWidth;
+ int newStretch = totalStretch;
+ int newShrink = totalShrink;
+
+ for (int i = elementIdx; i < par.size(); i++) {
+ KnuthElement tempElement = getElement(i);
+ if (tempElement.isBox()) {
+ break;
+ } else if (tempElement.isGlue()) {
+ newWidth += tempElement.getW();
+ newStretch += tempElement.getY();
+ newShrink += tempElement.getZ();
+ } else if (tempElement.isForcedBreak() && i != elementIdx) {
+ break;
+ }
+ }
+
+ // add nodes to the active nodes list
+ double minimumDemerits = best.getMinDemerits() + incompatibleFitnessDemerit;
+ for (int i = 0; i <= 3; i++) {
+ if (best.notInfiniteDemerits(i) && best.getDemerits(i) <= minimumDemerits) {
+ // the nodes in activeList must be ordered
+ // by line number and position;
+ if (log.isTraceEnabled()) {
+ log.trace("\tInsert new break in list of " + activeNodeCount);
}
+ KnuthNode newNode = new KnuthNode(elementIdx, line + 1, i,
+ newWidth, newStretch, newShrink,
+ best.getAdjust(i),
+ best.getAvailableShrink(i),
+ best.getAvailableStretch(i),
+ best.getDifference(i),
+ best.getDemerits(i),
+ best.getNode(i));
+ addNode(line + 1, newNode);
}
- if (activeNode == null) {
- break;
+ }
+ best.reset();
+ }
+
+ /**
+ * Return the difference between the line width and the width of the break that
+ * ends in 'element'.
+ * @param activeNode
+ * @param element
+ * @return The difference in width. Positive numbers mean extra space in the line,
+ * negative number that the line overflows.
+ */
+ private int computeDifference(KnuthNode activeNode, KnuthElement element) {
+ // compute the adjustment ratio
+ int actualWidth = totalWidth - activeNode.totalWidth;
+ if (element.isPenalty()) {
+ actualWidth += element.getW();
+ }
+ return lineWidth - actualWidth;
+ }
+
+ /**
+ * Return the adjust ration needed to make up for the difference. A ration of
+ * <ul>
+ * <li>0 means that the break has the exact right width</li>
+ * <li>>= -1 && < 0 means that the break is to wider than the line,
+ * but within the minimim values of the glues.</li>
+ * <li>>0 && < 1 means that the break is smaller than the line width,
+ * but within the maximum values of the glues.</li>
+ * <li>> 1 means that the break is too small to make up for the glues.</li>
+ * </ul>
+ * @param activeNode
+ * @param difference
+ * @return The ration.
+ */
+ private double computeAdjustmentRatio(KnuthNode activeNode, int difference) {
+ // compute the adjustment ratio
+ if (difference > 0) {
+ int maxAdjustment = totalStretch - activeNode.totalStretch;
+ if (maxAdjustment > 0) {
+ return (double) difference / maxAdjustment;
+ } else {
+ return INFINITE_RATIO;
+ }
+ } else if (difference < 0) {
+ int maxAdjustment = totalShrink - activeNode.totalShrink;
+ if (maxAdjustment > 0) {
+ return (double) difference / maxAdjustment;
+ } else {
+ return -INFINITE_RATIO;
}
- } // end of the outer while
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Figure out the fitness class of this line (tight, loose,
+ * very tight or very loose).
+ * @param r
+ * @return
+ */
+ private int computeFitness(double r) {
+ int newFitnessClass;
+ if (r < -0.5) {
+ return 0;
+ } else if (r <= 0.5) {
+ return 1;
+ } else if (r <= 1) {
+ return 2;
+ } else {
+ return 3;
+ }
+ }
+
+ private double computeDemerits(KnuthNode activeNode, KnuthElement element,
+ int fitnessClass, double r) {
+ double demerits = 0;
+ // compute demerits
+ double f = Math.abs(r);
+ f = 1 + 100 * f * f * f;
+ if (element.isPenalty() && element.getP() >= 0) {
+ f += element.getP();
+ demerits = f * f;
+ } else if (element.isPenalty() && !element.isForcedBreak()) {
+ double penalty = element.getP();
+ demerits = f * f - penalty * penalty;
+ } else {
+ demerits = f * f;
+ }
+
+ if (element.isPenalty() && ((KnuthPenalty) element).isFlagged()
+ && getElement(activeNode.position).isPenalty()
+ && ((KnuthPenalty) getElement(activeNode.position)).isFlagged()) {
+ // add demerit for consecutive breaks at flagged penalties
+ demerits += repeatedFlaggedDemerit;
+ }
+ if (Math.abs(fitnessClass - activeNode.fitness) > 1) {
+ // add demerit for consecutive breaks
+ // with very different fitness classes
+ demerits += incompatibleFitnessDemerit;
+ }
+ demerits += activeNode.totalDemerits;
+ return demerits;
+ }
+
+ /**
+ * Return the element at index idx in the paragraph.
+ * @param idx index of the element.
+ * @return
+ */
+ private KnuthElement getElement(int idx) {
+ return (KnuthElement) par.get(idx);
+ }
+
+ /**
+ * Compare two KnuthNodes and return the node with the least demerit.
+ * @param node1 The first knuth node.
+ * @param node2 The other knuth node.
+ * @return
+ */
+ protected KnuthNode compareNodes(KnuthNode node1, KnuthNode node2) {
+ if (node1 == null || node2.position > node1.position) {
+ return node2;
+ }
+ if (node2.position == node1.position) {
+ if (node2.totalDemerits < node1.totalDemerits) {
+ return node2;
+ }
+ }
+ return node1;
+ }
+
+ /**
+ * Add a KnuthNode at the end of line 'line'.
+ * If this is the first node in the line, adjust endLine accordingly.
+ * @param line
+ * @param node
+ */
+ private void addNode(int line, KnuthNode node) {
+ int headIdx = line * 2;
+ if (headIdx >= activeLines.length) {
+ KnuthNode[] oldList = activeLines;
+ activeLines = new KnuthNode[headIdx + headIdx];
+ System.arraycopy(oldList, 0, activeLines, 0, oldList.length);
+ }
+ node.next = null;
+ if (activeLines[headIdx + 1] != null) {
+ activeLines[headIdx + 1].next = node;
+ } else {
+ activeLines[headIdx] = node;
+ endLine = line+1;
+ }
+ activeLines[headIdx + 1] = node;
+ activeNodeCount++;
+ }
+
+ /**
+ * Remove the first node in line 'line'. If the line then becomes empty, adjust the
+ * startLine accordingly.
+ * @param line
+ * @param node
+ */
+ protected void removeNode(int line, KnuthNode node) {
+ KnuthNode n = getNode(line);
+ if (n != node) {
+ log.error("Should be first");
+ } else {
+ activeLines[line*2] = node.next;
+ if (node.next == null) {
+ activeLines[line*2+1] = null;
+ }
+ while (startLine < endLine && getNode(startLine) == null) {
+ startLine++;
+ }
+ }
+ activeNodeCount--;
+ }
+
+ protected KnuthNode getNode(int line) {
+ return activeLines[line * 2];
+ }
+
+ /**
+ * Return true if the position 'idx' is a legal breakpoint.
+ * @param idx
+ * @return
+ */
+ private boolean isLegalBreakpoint(int idx) {
+ KnuthElement elm = getElement(idx);
+ if (elm.isPenalty() && elm.getP() != KnuthElement.INFINITE) {
+ return true;
+ } else if (idx > 0 && elm.isGlue() && getElement(idx-1).isBox()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public int getDifference(int line) {
+ return positions[line].difference;
+ }
+
+ public double getAdjustRatio(int line) {
+ return positions[line].adjustRatio;
+ }
+
+ public int getStart(int line) {
+ KnuthNode previous = positions[line].previous;
+ return line == 0 ? 0 : previous.position + 1;
+ }
+
+ public int getEnd(int line) {
+ return positions[line].position;
+ }
+
+ /**
+ * Return a string representation of a MinOptMax in the form of a
+ * "width+stretch-shrink". Useful only for debugging.
+ * @param mom
+ * @return
+ */
+ private static String width(MinOptMax mom) {
+ return mom.opt + "+" + (mom.max - mom.opt) + "-" + (mom.opt - mom.min);
+
+ }
+
+ public String toString(String prepend) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("[\n");
+ for (int i = startLine; i < endLine; i++) {
+ for (KnuthNode node = getNode(i); node != null; node = node.next) {
+ sb.append(prepend + "\t" + node + ",\n");
+ }
+ }
+ sb.append(prepend + "]");
+ return sb.toString();
}
- protected abstract int filterActiveList() ;
+ protected abstract int filterActiveNodes() ;
private void calculateBreakPoints(KnuthNode node, KnuthSequence par,
int total) {