Explorar el Código

Refactoring of Knuth line breaking code (including some speed improvements)

Bugzilla: #32612
Submitted by:	Finn Bock <bckfnn.at.worldonline.dk>


git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@198474 13f79535-47bb-0310-9956-ffa450edef68
tags/Root_Temp_KnuthStylePageBreaking
Jeremias Maerki hace 19 años
padre
commit
a392d777f6

+ 6
- 2
src/java/org/apache/fop/layoutmgr/KnuthBox.java Ver fichero

@@ -1,5 +1,5 @@
/*
* Copyright 2004 The Apache Software Foundation.
* Copyright 2004-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,12 +47,16 @@ public class KnuthBox extends KnuthElement {
* @param bAux is this box auxiliary?
*/
public KnuthBox(int w, int l, int t, int m, Position pos, boolean bAux) {
super(KNUTH_BOX, w, pos, bAux);
super(w, pos, bAux);
lead = l;
total = t;
middle = m;
}

public boolean isBox() {
return true;
}

/**
* Return the height of this box above the main baseline.
*/

+ 21
- 11
src/java/org/apache/fop/layoutmgr/KnuthElement.java Ver fichero

@@ -1,5 +1,5 @@
/*
* Copyright 2004 The Apache Software Foundation.
* Copyright 2004-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,13 +28,8 @@ package org.apache.fop.layoutmgr;
*/
public abstract class KnuthElement {

public static final int KNUTH_BOX = 0;
public static final int KNUTH_GLUE = 1;
public static final int KNUTH_PENALTY = 2;

public static final int INFINITE = 1000;

private int type;
private int width;
private Position position;
private boolean bIsAuxiliary;
@@ -48,8 +43,7 @@ public abstract class KnuthElement {
* @param pos the Position stored in this element
* @param bAux is this an auxiliary element?
*/
protected KnuthElement(int t, int w, Position pos, boolean bAux) {
type = t;
protected KnuthElement(int w, Position pos, boolean bAux) {
width = w;
position = pos;
bIsAuxiliary = bAux;
@@ -59,21 +53,21 @@ public abstract class KnuthElement {
* Return true if this element is a KnuthBox.
*/
public boolean isBox() {
return (type == KNUTH_BOX);
return false;
}

/**
* Return true if this element is a KnuthGlue.
*/
public boolean isGlue() {
return (type == KNUTH_GLUE);
return false;
}

/**
* Return true if this element is a KnuthPenalty.
*/
public boolean isPenalty() {
return (type == KNUTH_PENALTY);
return false;
}

/**
@@ -90,6 +84,22 @@ public abstract class KnuthElement {
return width;
}

public int getP() {
throw new RuntimeException("Element is not a penalty");
}

public int getY() {
throw new RuntimeException("Element is not a glue");
}

public int getZ() {
throw new RuntimeException("Element is not a glue");
}

public boolean isForcedBreak() {
return false;
}

/**
* Return the Position stored in this element.
*/

+ 6
- 2
src/java/org/apache/fop/layoutmgr/KnuthGlue.java Ver fichero

@@ -1,5 +1,5 @@
/*
* Copyright 2004 The Apache Software Foundation.
* Copyright 2004-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,11 +58,15 @@ public class KnuthGlue extends KnuthElement {
* @param bAux is this glue auxiliary?
*/
public KnuthGlue(int w, int y, int z, Position pos, boolean bAux) {
super(KNUTH_GLUE, w, pos, bAux);
super(w, pos, bAux);
stretchability = y;
shrinkability = z;
}

public boolean isGlue() {
return true;
}

/**
* Return the stretchability of this glue.
*/

+ 747
- 0
src/java/org/apache/fop/layoutmgr/KnuthParagraph.java Ver fichero

@@ -0,0 +1,747 @@
/*
* Copyright 2004-2005 The Apache Software Foundation.
*
* Licensed 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 java.util.ArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.traits.MinOptMax;

/**
* A knuth paragraph
*
* The set is sorted into lines indexed into activeLines.
* The nodes in each line is 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 class KnuthParagraph {
// 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 ArrayList par;
/**
* The width of a line.
*/
private int lineWidth = 0;
private boolean force = false;

private KnuthNode lastTooLong;
private KnuthNode lastTooShort;
private KnuthNode lastDeactivated;

/**
* The set of active nodes.
*/
private KnuthNode[] activeLines;
/**
* The number of active nodes.
*/
private int activeNodeCount;
/**
* The lowest available line in the set of active nodes.
*/
private int startLine = 0;

/**
* The highest + 1 available line in the set of active nodes.
*/
private 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 KnuthParagraph(ArrayList par) {
this.best = new BestRecords();
this.par = par;
}


// this class represent a feasible breaking point
private class KnuthNode {
// index of the breakpoint represented by this node
public int position;

// number of the line ending at this breakpoint
public int line;

// fitness class of the line ending at his breakpoint
public int fitness;

// accumulated width of the KnuthElements
public int totalWidth;

public int totalStretch;

public int totalShrink;

// adjustment ratio if the line ends at this breakpoint
public double adjustRatio;

// difference between target and actual line width
public int difference;

// minimum total demerits up to this breakpoint
public double totalDemerits;

// 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 difference,
double totalDemerits, KnuthNode previous) {
this.position = position;
this.line = line;
this.fitness = fitness;
this.totalWidth = totalWidth;
this.totalStretch = totalStretch;
this.totalShrink = totalShrink;
this.adjustRatio = adjustRatio;
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 = Double.POSITIVE_INFINITY;

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 bestIndex = -1;

public BestRecords() {
reset();
}

public void addRecord(double demerits, KnuthNode node, double adjust,
int difference, int fitness) {
if (demerits > bestDemerits[fitness]) {
log.error("New demerits value greter than the old one");
}
bestDemerits[fitness] = demerits;
bestNode[fitness] = node;
bestAdjust[fitness] = adjust;
bestDifference[fitness] = difference;
if (bestIndex == -1 || demerits < bestDemerits[bestIndex]) {
bestIndex = fitness;
}
}

public boolean hasRecords() {
return (bestIndex != -1);
}

public boolean notInfiniteDemerits(int fitness) {
return (bestDemerits[fitness] != INFINITE_DEMERITS);
}

public double getDemerits(int fitness) {
return bestDemerits[fitness];
}

public KnuthNode getNode(int fitness) {
return bestNode[fitness];
}

public double getAdjust(int fitness) {
return bestAdjust[fitness];
}

public int getDifference(int fitness) {
return bestDifference[fitness];
}

public double getMinDemerits() {
if (bestIndex != -1) {
return getDemerits(bestIndex);
} else {
// anyway, this should never happen
return INFINITE_DEMERITS;
}
}
public void reset() {
bestDemerits[0] = INFINITE_DEMERITS;
bestDemerits[1] = INFINITE_DEMERITS;
bestDemerits[2] = INFINITE_DEMERITS;
bestDemerits[3] = INFINITE_DEMERITS;
bestIndex = -1;
}
}

public int findBreakPoints(int lineWidth, double threshold, boolean force) {
this.lineWidth = lineWidth;
this.totalWidth = 0;
this.totalStretch = 0;
this.totalShrink = 0;
this.threshold = threshold;
this.force = force;
activeLines = new KnuthNode[20];
addNode(0, new KnuthNode(0, 0, 1, 0, 0, 0, 0, 0, 0, null));
boolean bForced = false;

// previous element in the paragraph is a KnuthBox
boolean previousIsBox = false;

if (log.isTraceEnabled()) {
log.trace("Looping over " + par.size() + " box objects");
}

KnuthNode lastForced = getNode(0);
// main loop
for (int i = 0; i < par.size(); i++) {
KnuthElement element = getElement(i);
if (element.isBox()) {
// a KnuthBox object is not a legal line break
totalWidth += element.getW();
previousIsBox = true;
} else if (element.isGlue()) {
// a KnuthGlue object is a legal line break
// only if the previous object is a KnuthBox
if (previousIsBox) {
considerLegalBreak(element, i);
}
totalWidth += element.getW();
totalStretch += element.getY();
totalShrink += element.getZ();
previousIsBox = false;
} else {
// a KnuthPenalty is a legal line break
// only if its penalty is not infinite
if (element.getP() < KnuthElement.INFINITE) {
considerLegalBreak(element, i);
}
previousIsBox = false;
}
if (activeNodeCount == 0) {
if (!force) {
log.debug("Could not find a set of breaking points " + threshold);
return 0;
}
/*
if (lastForced != null && lastForced.position == lastDeactivated.position) {
lastForced = lastTooShort != null ? lastTooShort : lastTooLong;
} else {
lastForced = lastDeactivated;
}
*/
if (lastTooShort == null || lastForced.position == lastTooShort.position) {
lastForced = lastTooLong;
} else {
lastForced = lastTooShort;
}

log.debug("Restarting at node " + lastForced);
lastForced.totalDemerits = 0;
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;
}
}
if (log.isTraceEnabled()) {
log.trace("Main loop completed " + activeNodeCount);
log.trace("Active nodes=" + toString(""));
}

// there is at least one set of breaking points
// choose the active node with fewest total demerits
KnuthNode bestActiveNode = findBestNode();
int line = bestActiveNode.line;
/*
if (looseness != 0) {
// choose the appropriate active node
int s = 0;
double bestDemerits = 0;
for (int i = 0; i < activeList.size(); i++) {
KnuthNode node = getNode(i);
int delta = node.line - line;
if (looseness <= delta && delta < s
|| s < delta && delta <= looseness) {
s = delta;
bestActiveNode = node;
bestDemerits = node.totalDemerits;
} else if (delta == s
&& node.totalDemerits < bestDemerits) {
bestActiveNode = node;
bestDemerits = node.totalDemerits;
}
}
line = bestActiveNode.line;
}
*/
// Reverse the list of nodes from bestActiveNode.
positions = new KnuthNode[line];
// use the chosen node to determine the optimum breakpoints
for (int i = line - 1; i >= 0; i--) {
positions[i] = bestActiveNode;
bestActiveNode = bestActiveNode.previous;
}
activeLines = null;
return positions.length;
}

private void considerLegalBreak(KnuthElement element, int elementIdx) {

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;
for (int line = startLine; line < endLine; line++) {
for (KnuthNode node = getNode(line); node != null; node = node.next) {
if (node.position == elementIdx) {
continue;
}
int difference = computeDifference(node, element);
double r = computeAdjustmentRatio(node, difference);
if (log.isTraceEnabled()) {
log.trace("\tr=" + r);
log.trace("\tline=" + line);
}
// 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);
}
// 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(demerits, node, r, difference, fitnessClass);
}
}
// 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,
r, difference, demerits, node);
if (log.isTraceEnabled()) {
log.trace("Picking tooLong " + lastTooLong);
}
}
} else {
if (lastTooShort == null || demerits <= lastTooShort.totalDemerits) {
lastTooShort = new KnuthNode(elementIdx, line + 1, fitnessClass,
totalWidth, totalStretch, totalShrink,
r, difference, demerits, node);
if (log.isTraceEnabled()) {
log.trace("Picking tooShort " + lastTooShort);
}
}
}
}
}
addBreaks(line, elementIdx);
}
}

private void addBreaks(int line, int elementIdx) {
if (!best.hasRecords()) {
return;
}

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.getDifference(i),
best.getDemerits(i),
best.getNode(i));
addNode(line + 1, newNode);
}
}
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>&gt;= -1 && &lt; 0 means that the break is to wider than the line,
* but within the minimim values of the glues.</li>
* <li>&gt;0 && &lt 1 means that the break is smaller than the line width,
* but within the maximum values of the glues.</li>
* <li>&gt 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;
}
} 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;
}
}

/**
* Find and return the KnuthNode in the active set of nodes with the
* lowest demerit.
*/
private KnuthNode findBestNode() {
// choose the active node with fewest total demerits
KnuthNode bestActiveNode = null;
for (int i = startLine; i < endLine; i++) {
for (KnuthNode node = getNode(i); node != null; node = node.next) {
bestActiveNode = compareNodes(bestActiveNode, node);
}
}
if (log.isTraceEnabled()) {
log.trace("Best demerits " + bestActiveNode.totalDemerits + " for paragraph size " + par.size());
}
return bestActiveNode;
}
/**
* Compare two KnuthNodes and return the node with the least demerit.
* @param node1 The first knuth node.
* @param node2 The other knuth node.
* @return
*/
private 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;
}

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);
}

/**
* 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
*/
private 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--;
}

private 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();
}
}

+ 10
- 2
src/java/org/apache/fop/layoutmgr/KnuthPenalty.java Ver fichero

@@ -1,5 +1,5 @@
/*
* Copyright 2004 The Apache Software Foundation.
* Copyright 2004-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,11 +50,15 @@ public class KnuthPenalty extends KnuthElement {
* @param bAux is this penalty auxiliary?
*/
public KnuthPenalty(int w, int p, boolean f, Position pos, boolean bAux) {
super(KNUTH_PENALTY, w, pos, bAux);
super(w, pos, bAux);
penalty = p;
bFlagged = f;
}

public boolean isPenalty() {
return true;
}

/**
* Return the penalty value of this penalty.
*/
@@ -68,4 +72,8 @@ public class KnuthPenalty extends KnuthElement {
public boolean isFlagged() {
return bFlagged;
}

public boolean isForcedBreak() {
return penalty == -KnuthElement.INFINITE;
}
}

+ 54
- 601
src/java/org/apache/fop/layoutmgr/LineLayoutManager.java Ver fichero

@@ -133,153 +133,22 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
// offset of the middle baseline with respect to the main baseline
private int middleShift;

// inline start pos when adding areas
private int iStartPos = 0;

private ArrayList knuthParagraphs = null;
private LinkedList activeList = null;
private ArrayList breakpoints = null;
private int iReturnedLBP = 0;
private int iStartElement = 0;
private int iEndElement = 0;

private KnuthNode bestDeactivatedNode = null;

// 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;

// this constant is used to create elements when text-align is center:
// every TextLM descendant of LineLM must use the same value,
// otherwise the line breaking algorithm does not find the right
// 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 {
// index of the breakpoint represented by this node
public int position;

// number of the line ending at this breakpoint
public int line;

// fitness class of the line ending at his breakpoint
public int fitness;

// accumulated width of the KnuthElements
public int totalWidth;

// accumulated stretchability of the KnuthElements
public int totalStretch;

// accumulated shrinkability of the KnuthElements
public int totalShrink;

// adjustment ratio if the line ends at this breakpoint
public double adjustRatio;

// difference between target and actual line width
public int difference;

// minimum total demerits up to this breakpoint
public double totalDemerits;

// best node for the preceding breakpoint
public KnuthNode previous;

public KnuthNode(int position, int line, int fitness,
int totalWidth, int totalStretch, int totalShrink,
double adjustRatio, int difference,
double totalDemerits, KnuthNode previous) {
this.position = position;
this.line = line;
this.fitness = fitness;
this.totalWidth = totalWidth;
this.totalStretch = totalStretch;
this.totalShrink = totalShrink;
this.adjustRatio = adjustRatio;
this.difference = difference;
this.totalDemerits = totalDemerits;
this.previous = previous;
}
}

// 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};
private int bestIndex = -1;

public BestRecords() {
}

public void addRecord(double demerits, KnuthNode node, double adjust,
int difference, int fitness) {
if (demerits > bestDemerits[fitness]) {
log.error("New demerits value greter than the old one");
}
bestDemerits[fitness] = demerits;
bestNode[fitness] = node;
bestAdjust[fitness] = adjust;
bestDifference[fitness] = difference;
if (bestIndex == -1 || demerits < bestDemerits[bestIndex]) {
bestIndex = fitness;
}
}

public boolean hasRecords() {
return (bestIndex != -1);
}

public boolean notInfiniteDemerits(int fitness) {
return (bestDemerits[fitness] != INFINITE_DEMERITS);
}

public double getDemerits(int fitness) {
return bestDemerits[fitness];
}

public KnuthNode getNode(int fitness) {
return bestNode[fitness];
}

public double getAdjust(int fitness) {
return bestAdjust[fitness];
}

public int getDifference(int fitness) {
return bestDifference[fitness];
}

public double getMinDemerits() {
if (bestIndex != -1) {
return getDemerits(bestIndex);
} else {
// anyway, this should never happen
return INFINITE_DEMERITS;
}
}
}

// this class is used to remember
// which was the first element in the paragraph
@@ -295,7 +164,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
}

// this class represents a paragraph
private class Paragraph extends LinkedList {
public class Paragraph extends ArrayList {
// number of KnuthElements added by the LineLayoutManager
public int ignoreAtStart = 0;
public int ignoreAtEnd = 0;
@@ -362,6 +231,22 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
knuthParagraphs.add(this);
}
}

public KnuthElement getLast() {
int idx = size();
if (idx == 0) {
return null;
}
return (KnuthElement) get(idx - 1);
}

public KnuthElement removeLast() {
int idx = size();
if (idx == 0) {
return null;
}
return (KnuthElement) remove(idx - 1);
}
}


@@ -376,10 +261,6 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
// Get a break from currently active child LM
// Set up constraints for inline level managers
InlineLevelLayoutManager curLM ; // currently active LM
BreakPoss prev = null;
BreakPoss bp = null; // proposed BreakPoss

ArrayList vecPossEnd = new ArrayList();

// IPD remaining in line
MinOptMax availIPD = context.getStackLimit();
@@ -427,7 +308,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
if (!prevBox.isAuxiliary()) {
// if letter spacing is constant,
// only prevBox needs to be replaced;
knuthPar.addLast(((InlineLevelLayoutManager)
knuthPar.add(((InlineLevelLayoutManager)
prevBox.getLayoutManager())
.addALetterSpaceTo(prevBox));
} else {
@@ -442,11 +323,11 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
KnuthPenalty auxPenalty
= (KnuthPenalty) knuthPar.removeLast();
prevBox = (KnuthBox) knuthPar.getLast();
knuthPar.addLast(auxPenalty);
knuthPar.addLast(((InlineLevelLayoutManager)
knuthPar.add(auxPenalty);
knuthPar.add(((InlineLevelLayoutManager)
prevBox.getLayoutManager())
.addALetterSpaceTo(prevBox));
knuthPar.addLast(auxBox);
knuthPar.add(auxBox);
}
}

@@ -545,8 +426,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
float maxAdjustment = 1;

// first try
if (!findBreakingPoints(par, lineWidth,
maxAdjustment, KNUTH_ALGORITHM)) {
if (!findBreakingPoints(par, lineWidth, maxAdjustment, false)) {
// 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) {
@@ -557,182 +437,55 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
maxAdjustment = 5;
}

if (!findBreakingPoints(par, lineWidth,
maxAdjustment, KNUTH_ALGORITHM)) {
if (!findBreakingPoints(par, lineWidth, maxAdjustment, false)) {
// 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)) {
if (!findBreakingPoints(par, lineWidth, maxAdjustment, true)) {
log.debug("No set of breaking points found, using first-fit algorithm");
findBreakingPoints(par, lineWidth,
maxAdjustment, FIRST_FIT_ALGORITHM);
}
}
}

// 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;
double bestDemerits = BestRecords.INFINITE_DEMERITS;
while (activeListIterator.hasNext()) {
tempNode = (KnuthNode) activeListIterator.next();
if (tempNode.totalDemerits < bestDemerits) {
bestActiveNode = tempNode;
bestDemerits = bestActiveNode.totalDemerits;
}
}
private boolean findBreakingPoints(Paragraph par, int lineWidth,
double threshold, boolean force) {
KnuthParagraph knuthPara = new KnuthParagraph(par);
int lines = knuthPara.findBreakPoints(lineWidth, threshold, force);
if (lines == 0) {
return false;
}
line = bestActiveNode.line;

if (looseness != 0) {
// choose the appropriate active node
activeListIterator = activeList.listIterator();
int s = 0;
while (activeListIterator.hasNext()) {
tempNode = (KnuthNode) activeListIterator.next();
int delta = tempNode.line - line;
if (looseness <= delta && delta < s
|| s < delta && delta <= looseness) {
s = delta;
bestActiveNode = tempNode;
bestDemerits = tempNode.totalDemerits;
} else if (delta == s
&& tempNode.totalDemerits < bestDemerits) {
bestActiveNode = tempNode;
bestDemerits = tempNode.totalDemerits;
}
for (int i = lines-1; i >= 0; i--) {
int line = i+1;
if (log.isTraceEnabled()) {
log.trace("Making line from " + knuthPara.getStart(i) + " to " +
knuthPara.getEnd(i));
}
line = bestActiveNode.line;
}

// use the chosen node to determine the optimum breakpoints
for (int i = line; i > 0; i--) {
// compute indent and adjustment ratio, according to
// the value of text-align and text-align-last
int indent = 0;
int difference = (bestActiveNode.line < line)
? bestActiveNode.difference
: bestActiveNode.difference + par.lineFillerWidth;
int textAlign = (bestActiveNode.line < line)

int difference = knuthPara.getDifference(i);
if (line == lines) {
difference += par.lineFillerWidth;
}
int textAlign = (line < lines)
? bTextAlignment : bTextAlignmentLast;
indent += (textAlign == EN_CENTER)
int indent = (textAlign == EN_CENTER)
? difference / 2
: (textAlign == EN_END) ? difference : 0;
indent += (bestActiveNode.line == 1
&& knuthParagraphs.indexOf(par) == 0)
indent += (line == 1 && knuthParagraphs.indexOf(par) == 0)
? textIndent.getValue() : 0;
double ratio = (textAlign == EN_JUSTIFY)
? bestActiveNode.adjustRatio : 0;

makeLineBreakPosition(par,
(i > 1 ? bestActiveNode.previous.position + 1: 0),
bestActiveNode.position,
0, ratio, indent);

bestActiveNode = bestActiveNode.previous;
}
activeList.clear();
}

/**
* 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;
}
}
? knuthPara.getAdjustRatio(i) : 0;

if (algorithm == KNUTH_ALGORITHM && activeList.size() > 0) {
// bestDeactivatedNode is useless, as the algorithm did not fail
bestDeactivatedNode = null;
int start = knuthPara.getStart(i);
int end = knuthPara.getEnd(i);
makeLineBreakPosition(par, start, end, 0, ratio, indent);
}
return (activeList.size() > 0);
return true;
}

private void makeLineBreakPosition(Paragraph par,
@@ -785,307 +538,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
lineLead));
}

private void considerLegalBreakKnuth(LinkedList par, int lineWidth,
KnuthElement element,
int totalWidth, int totalStretch,
int totalShrink, double threshold) {
KnuthNode activeNode = null;

ListIterator activeListIterator = activeList.listIterator();
if (activeListIterator.hasNext()) {
activeNode = (KnuthNode) activeListIterator.next();
} else {
activeNode = 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();
}
int neededAdjustment = lineWidth - actualWidth;
int maxAdjustment = 0;
if (neededAdjustment > 0) {
maxAdjustment = totalStretch - activeNode.totalStretch;
if (maxAdjustment > 0) {
newIPDAdjust
= (double) neededAdjustment / maxAdjustment;
} else {
newIPDAdjust = INFINITE_RATIO;
}
} else if (neededAdjustment < 0) {
maxAdjustment = totalShrink - activeNode.totalShrink;
if (maxAdjustment > 0) {
newIPDAdjust
= (double) neededAdjustment / maxAdjustment;
} else {
newIPDAdjust = INFINITE_RATIO;
}
} else {
// neededAdjustment == 0
newIPDAdjust = 0;
}
if (newIPDAdjust < -1
|| (element.isPenalty()
&& ((KnuthPenalty) element).getP()
== -KnuthElement.INFINITE)
&& !(activeNode.position == par.indexOf(element))) {
// deactivate activeNode:
// just remove it from the activeList; as long as there is
// an active node pointing to it, it will not be deleted
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 ++;
}
// 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();
}
}

if ((-1 <= newIPDAdjust) && (newIPDAdjust <= threshold)) {
// 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()
> -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;
}
newDemerits += activeNode.totalDemerits;
if (newDemerits < best.getDemerits(newFitnessClass)) {
// updates best demerits data
best.addRecord(newDemerits, activeNode, newIPDAdjust,
neededAdjustment, newFitnessClass);
}
}

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;
}
}

// 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
int iStepsForward = 0;
KnuthNode tempNode;
while (activeListIterator.hasNext()) {
iStepsForward ++;
tempNode = (KnuthNode) activeListIterator.next();
if (tempNode.line > (best.getNode(i).line + 1)) {
activeListIterator.previous();
iStepsForward --;
break;
}
}
// 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.getDifference(i),
best.getDemerits(i),
best.getNode(i)));
// 3) go back
for (int j = 0;
j <= iStepsForward;
j ++) {
activeListIterator.previous();
}
}
}
}
if (activeNode == null) {
break;
}
} // 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
@@ -1174,9 +627,9 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
// create iterator for the updateList
ListIterator updateListIterator = updateList.listIterator();
Update currUpdate = null;
int iPreservedElements = 0;
//int iPreservedElements = 0;
int iAddedElements = 0;
int iRemovedElements = 0;
//int iRemovedElements = 0;

while (updateListIterator.hasNext()) {
// ask the LMs to apply the changes and return
@@ -1250,7 +703,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager {
}
}

private BreakPoss getBestBP(ArrayList vecPossEnd) {
private BreakPoss getBestBP(List vecPossEnd) {
if (vecPossEnd.size() == 1) {
return ((BreakCost) vecPossEnd.get(0)).getBP();
}

+ 1
- 2
src/java/org/apache/fop/layoutmgr/TextLayoutManager.java Ver fichero

@@ -793,8 +793,7 @@ public class TextLayoutManager extends AbstractLayoutManager
&& textArray[iTempStart] != NBSPACE
&& textArray[iTempStart] != NEWLINE;
iTempStart++) {
wordIPD.add(
new MinOptMax(fs.getCharWidth(textArray[iTempStart])));
wordIPD.add(fs.getCharWidth(textArray[iTempStart]));
}
wordIPD.add(MinOptMax.multiply(letterSpaceIPD, (iTempStart - iThisStart - 1)));
vecAreaInfo.add

+ 35
- 1
src/java/org/apache/fop/traits/MinOptMax.java Ver fichero

@@ -1,5 +1,5 @@
/*
* Copyright 1999-2004 The Apache Software Foundation.
* Copyright 1999-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,6 +60,19 @@ public class MinOptMax implements java.io.Serializable, Cloneable {
this.max = max;
}

/**
* New min/opt/max with the three values.
*
* @param min the minimum value
* @param opt the optimum value
* @param max the maximum value
*/
public MinOptMax(MinOptMax op) {
this.min = op.min;
this.opt = op.opt;
this.max = op.max;
}

/**
* @see java.lang.Object#clone()
*/
@@ -115,6 +128,27 @@ public class MinOptMax implements java.io.Serializable, Cloneable {
max += op.max;
}

/**
* Adds another MinOptMax instance to this one.
* @param op the other instance
*/
public void add(int min, int opt, int max) {
this.min += min;
this.opt += opt;
this.max += max;
}

/**
* Adds another MinOptMax instance to this one.
* @param op the other instance
*/
public void add(int len) {
this.min += len;
this.opt += len;
this.max += len;
}


/**
* Subtracts from this instance using another.
* @param op the other instance

Cargando…
Cancelar
Guardar