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
// 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 {
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
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) {
// 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
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,
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();
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();
} // 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