Index: layoutmgr/AbstractLayoutManager.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java,v
retrieving revision 1.19
diff -w -u -r1.19 AbstractLayoutManager.java
--- layoutmgr/AbstractLayoutManager.java	11 Aug 2004 22:56:48 -0000	1.19
+++ layoutmgr/AbstractLayoutManager.java	24 Aug 2004 14:38:31 -0000
@@ -31,6 +31,8 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import java.util.LinkedList;
+import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 
@@ -279,6 +281,41 @@
      * PROVIDE NULL IMPLEMENTATIONS OF METHODS from LayoutManager
      * interface which are declared abstract in AbstractLayoutManager.
      * ---------------------------------------------------------*/
+
+    public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
+        log.debug("null implementation of getNextKnuthElements() called!");
+        setFinished(true);
+        return null;
+    }
+
+    public KnuthElement addALetterSpaceTo(KnuthElement element) {
+        log.debug("null implementation of addALetterSpaceTo() called!");
+        return element;
+    }
+
+    public void getWordChars(StringBuffer sbChars, Position pos) {
+        log.debug("null implementation of getWordChars() called!");
+    }
+
+    public void hyphenate(Position pos, HyphContext hc) {
+        log.debug("null implementation of hyphenate called!");
+    }
+
+    public boolean applyChanges(List oldList) {
+        log.debug("null implementation of applyChanges() called!");
+        return false;
+    }
+
+    public LinkedList getChangedKnuthElements(List oldList, int flaggedPenalty, int alignment) {
+        log.debug("null implementation of getChangeKnuthElement() called!");
+        return null;
+    }
+
+    public int getWordSpaceIPD() {
+        log.debug("null implementation of getWordSpaceIPD() called!");
+        return 0;
+    }
+
     public Area getParentArea(Area childArea) {
         return null;
     }
Index: layoutmgr/ContentLayoutManager.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/layoutmgr/ContentLayoutManager.java,v
retrieving revision 1.11
diff -w -u -r1.11 ContentLayoutManager.java
--- layoutmgr/ContentLayoutManager.java	13 Jul 2004 00:16:22 -0000	1.11
+++ layoutmgr/ContentLayoutManager.java	24 Aug 2004 14:38:31 -0000
@@ -26,6 +26,7 @@
 import org.apache.fop.area.Resolveable;
 import org.apache.fop.area.PageViewport;
 
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.ArrayList;
@@ -240,6 +241,33 @@
     /** @see org.apache.fop.layoutmgr.LayoutManager */
     public Marker retrieveMarker(String name, int pos, int boundary) {
         return parentLM.retrieveMarker(name, pos, boundary);
+    }
+
+    public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
+        setFinished(true);
+        return null;
+    }
+
+    public KnuthElement addALetterSpaceTo(KnuthElement element) {
+        return element;
+    }
+
+    public void getWordChars(StringBuffer sbChars, Position pos) {
+    }
+
+    public void hyphenate(Position pos, HyphContext hc) {
+    }
+
+    public boolean applyChanges(List oldList) {
+        return false;
+    }
+
+    public LinkedList getChangedKnuthElements(List oldList, int flaggedPenalty, int alignment) {
+        return null;
+    }
+
+    public int getWordSpaceIPD() {
+        return 0;
     }
 }
 
Index: layoutmgr/InlineStackingLayoutManager.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/layoutmgr/InlineStackingLayoutManager.java,v
retrieving revision 1.9
diff -w -u -r1.9 InlineStackingLayoutManager.java
--- layoutmgr/InlineStackingLayoutManager.java	26 May 2004 04:22:39 -0000	1.9
+++ layoutmgr/InlineStackingLayoutManager.java	24 Aug 2004 14:38:34 -0000
@@ -18,6 +18,8 @@
 
 package org.apache.fop.layoutmgr;
 
+import java.util.List;
+import java.util.LinkedList;
 import java.util.Iterator;
 import java.util.ListIterator;
 import java.util.HashMap;
@@ -47,13 +49,12 @@
         }
 
         protected LayoutManager getLM(Object nextObj) {
-            return ((Position) nextObj).getPosition().getLM();
+            return ((Position) nextObj).getLM();
         }
 
         protected Position getPos(Object nextObj) {
-            return ((Position) nextObj).getPosition();
+            return ((Position) nextObj);
         }
-
     }
 
 
@@ -80,6 +81,8 @@
     private LayoutManager lastChildLM = null; // Set when return last breakposs
     private boolean bAreaCreated = false;
 
+    private LayoutManager currentLM = null;
+
     /** Used to store previous content IPD for each child LM. */
     private HashMap hmPrevIPD = new HashMap();
 
@@ -476,13 +479,29 @@
 
         context.getLeadingSpace().addSpace(inlineProps.spaceStart);
 
+        // "unwrap" the NonLeafPositions stored in parentIter
+        // and put them in a new list; 
+        // also set lastLM to be the LayoutManager which created
+        // the last Position: if the LAST_AREA flag is set in context,
+        // it must be also set in the LayoutContext given to lastLM,
+        // but unset in the LayoutContext given to the other LMs
+        LinkedList positionList = new LinkedList();
+        NonLeafPosition pos;
+        LayoutManager lastLM = null; // last child LM in this iterator
+        while (parentIter.hasNext()) {
+            pos = (NonLeafPosition)parentIter.next();
+            lastLM = pos.getPosition().getLM();
+            positionList.add(pos.getPosition());
+        }
+
+        StackingIter childPosIter = new StackingIter(positionList.listIterator());
 
-        // posIter iterates over positions returned by this LM
-        StackingIter childPosIter = new StackingIter(parentIter);
         LayoutManager prevLM = null;
         LayoutManager childLM ;
         while ((childLM = childPosIter.getNextChildLM()) != null) {
             //getContext().setTrailingSpace(new SpaceSpecifier(false));
+            getContext().setFlags(LayoutContext.LAST_AREA, context.isLastArea() 
+                                                           && childLM == lastLM);
             childLM.addAreas(childPosIter, getContext());
             getContext().setLeadingSpace(getContext().getTrailingSpace());
             getContext().setFlags(LayoutContext.RESOLVE_LEADING_SPACE,
@@ -581,5 +600,183 @@
         }
     }
 
+    public LinkedList getNextKnuthElements(LayoutContext lc, int alignment) {
+        LayoutManager curLM;
+
+        // the list returned by child LM
+        LinkedList returnedList;
+        KnuthElement returnedElement;
+
+        // the list which will be returned to the parent LM
+        LinkedList returnList = new LinkedList();
+
+        SpaceSpecifier leadingSpace = lc.getLeadingSpace();
+
+        if (lc.startsNewArea()) {
+            // First call to this LM in new parent "area", but this may
+            // not be the first area created by this inline
+            childLC = new LayoutContext(lc);
+            lc.getLeadingSpace().addSpace(inlineProps.spaceStart);
+
+            // Check for "fence"
+            if (hasLeadingFence(!lc.isFirstArea())) {
+                // Reset leading space sequence for child areas
+                leadingSpace = new SpaceSpecifier(false);
+            }
+            // Reset state variables
+            clearPrevIPD(); // Clear stored prev content dimensions
+        }
+
+        while ((curLM = getChildLM()) != null) {
+            // get KnuthElements from curLM
+            returnedList = curLM.getNextKnuthElements(lc, alignment);
+            if (returnedList != null) {
+                // "wrap" the Position stored in each element of returnedList
+                ListIterator listIter = returnedList.listIterator();
+                while (listIter.hasNext()) {
+                    returnedElement = (KnuthElement)listIter.next();
+                    returnedElement.setPosition(new NonLeafPosition(this, returnedElement.getPosition()));
+                    returnList.add(returnedElement);
+                }
+                return returnList;
+            } else {
+                // curLM returned null because it finished;
+                // just iterate once more to see if there is another child
+            }
+        }
+        setFinished(true);
+        return null;
+    }
+
+    public KnuthElement addALetterSpaceTo(KnuthElement element) {
+        NonLeafPosition savedPos = (NonLeafPosition)element.getPosition();
+        element.setPosition(savedPos.getPosition());
+
+        KnuthElement newElement = element.getLayoutManager().addALetterSpaceTo(element);
+        newElement.setPosition(new NonLeafPosition(this, newElement.getPosition()));
+        element.setPosition(savedPos);
+        return newElement;
+    }
+
+    public void getWordChars(StringBuffer sbChars, Position pos) {
+        ((NonLeafPosition)pos).getPosition().getLM().getWordChars(sbChars, ((NonLeafPosition)pos).getPosition());
+    }
+
+    public void hyphenate(Position pos, HyphContext hc) {
+        ((NonLeafPosition)pos).getPosition().getLM().hyphenate(((NonLeafPosition)pos).getPosition(), hc);
+    }
+
+    public boolean applyChanges(List oldList) {
+        // "unwrap" the Positions stored in the elements
+        ListIterator oldListIterator = oldList.listIterator();
+        KnuthElement oldElement;
+        while (oldListIterator.hasNext()) {
+            oldElement = (KnuthElement)oldListIterator.next();
+            oldElement.setPosition(((NonLeafPosition)oldElement.getPosition()).getPosition());
+        }
+        // reset the iterator
+        oldListIterator = oldList.listIterator();
+
+        LayoutManager prevLM = null;
+        LayoutManager currLM;
+        int fromIndex = 0;
+
+        boolean bSomethingChanged = false;
+        while(oldListIterator.hasNext()) {
+            oldElement = (KnuthElement)oldListIterator.next();
+            currLM = oldElement.getLayoutManager();
+            // initialize prevLM
+            if (prevLM == null) {
+                prevLM = currLM;
+            }
+
+            if (currLM != prevLM || !oldListIterator.hasNext()) {
+                if (oldListIterator.hasNext()) {
+                    bSomethingChanged = prevLM.applyChanges(oldList.subList(fromIndex, oldListIterator.previousIndex()))
+                                        || bSomethingChanged;
+                    prevLM = currLM;
+                    fromIndex = oldListIterator.previousIndex();
+                } else if (currLM == prevLM) {
+                    bSomethingChanged = prevLM.applyChanges(oldList.subList(fromIndex, oldList.size()))
+                                        || bSomethingChanged;
+                } else {
+                    bSomethingChanged = prevLM.applyChanges(oldList.subList(fromIndex, oldListIterator.previousIndex()))
+                                        || bSomethingChanged;
+                    bSomethingChanged = currLM.applyChanges(oldList.subList(oldListIterator.previousIndex(), oldList.size()))
+                                        || bSomethingChanged;
+                }
+            }
+        }
+
+        // "wrap" again the Positions stored in the elements
+        oldListIterator = oldList.listIterator();
+        while (oldListIterator.hasNext()) {
+            oldElement = (KnuthElement)oldListIterator.next();
+            oldElement.setPosition(new NonLeafPosition(this, oldElement.getPosition()));
+        }
+        return bSomethingChanged;
+    }
+
+    public LinkedList getChangedKnuthElements(List oldList, int flaggedPenalty, int alignment) {
+        // "unwrap" the Positions stored in the elements
+        ListIterator oldListIterator = oldList.listIterator();
+        KnuthElement oldElement;
+        while (oldListIterator.hasNext()) {
+            oldElement = (KnuthElement)oldListIterator.next();
+            oldElement.setPosition(((NonLeafPosition)oldElement.getPosition()).getPosition());
+        }
+        // reset the iterator
+        oldListIterator = oldList.listIterator();
+
+        KnuthElement returnedElement;
+        LinkedList returnedList = new LinkedList();
+        LinkedList returnList = new LinkedList();
+        LayoutManager prevLM = null;
+        LayoutManager currLM;
+        int fromIndex = 0;
+
+        while(oldListIterator.hasNext()) {
+            oldElement = (KnuthElement)oldListIterator.next();
+            currLM = oldElement.getLayoutManager();
+            if (prevLM == null) {
+                prevLM = currLM;
+            }
+
+            if (currLM != prevLM || !oldListIterator.hasNext()) {
+                if (oldListIterator.hasNext()) {
+                    returnedList.addAll(prevLM.getChangedKnuthElements(oldList.subList(fromIndex, oldListIterator.previousIndex()),
+                                                                       flaggedPenalty, alignment));
+                    prevLM = currLM;
+                    fromIndex = oldListIterator.previousIndex();
+                } else if (currLM == prevLM) {
+                    returnedList.addAll(prevLM.getChangedKnuthElements(oldList.subList(fromIndex, oldList.size()),
+                                                                       flaggedPenalty, alignment));
+                } else {
+                    returnedList.addAll(prevLM.getChangedKnuthElements(oldList.subList(fromIndex, oldListIterator.previousIndex()),
+                                                                       flaggedPenalty, alignment));
+                    returnedList.addAll(currLM.getChangedKnuthElements(oldList.subList(oldListIterator.previousIndex(), oldList.size()),
+                                                                       flaggedPenalty, alignment));
+                }
+            }
+        }
+
+        // "wrap" the Position stored in each element of returnedList
+        ListIterator listIter = returnedList.listIterator();
+        while (listIter.hasNext()) {
+            returnedElement = (KnuthElement)listIter.next();
+            returnedElement.setPosition(new NonLeafPosition(this, returnedElement.getPosition()));
+            returnList.add(returnedElement);
+        }
+        return returnList;
+    }
+
+    public int getWordSpaceIPD() {
+        LayoutManager firstChild = getChildLM();
+        if (firstChild != null) {
+            return firstChild.getWordSpaceIPD();
+        } else {
+            return 0;
+        }
+    }
 }
 
Index: layoutmgr/LayoutContext.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/layoutmgr/LayoutContext.java,v
retrieving revision 1.5
diff -w -u -r1.5 LayoutContext.java
--- layoutmgr/LayoutContext.java	27 Feb 2004 17:49:25 -0000	1.5
+++ layoutmgr/LayoutContext.java	24 Aug 2004 14:38:35 -0000
@@ -95,6 +95,7 @@
         this.trailingSpace = parentLC.trailingSpace; //???
         this.hyphContext = parentLC.hyphContext;
         this.dSpaceAdjust = parentLC.dSpaceAdjust;
+        this.ipdAdjust = parentLC.ipdAdjust;
         this.iLineHeight = parentLC.iLineHeight;
         this.iBaseline = parentLC.iBaseline;
         // Copy other fields as necessary. Use clone???
Index: layoutmgr/LayoutManager.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/layoutmgr/LayoutManager.java,v
retrieving revision 1.9
diff -w -u -r1.9 LayoutManager.java
--- layoutmgr/LayoutManager.java	13 Jul 2004 00:16:22 -0000	1.9
+++ layoutmgr/LayoutManager.java	24 Aug 2004 14:38:36 -0000
@@ -18,6 +18,8 @@
  
 package org.apache.fop.layoutmgr;
 
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.fop.fo.flow.Marker;
@@ -218,4 +220,17 @@
      */
     Marker retrieveMarker(String name, int pos, int boundary);
 
+    LinkedList getNextKnuthElements(LayoutContext context, int alignment);
+
+    KnuthElement addALetterSpaceTo(KnuthElement element);
+
+    void getWordChars(StringBuffer sbChars, Position pos);
+
+    void hyphenate(Position pos, HyphContext hc);
+
+    boolean applyChanges(List oldList);
+
+    LinkedList getChangedKnuthElements(List oldList, int flaggedPenalty, int alignment);
+
+    int getWordSpaceIPD();
 }
Index: layoutmgr/LineLayoutManager.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java,v
retrieving revision 1.23
diff -w -u -r1.23 LineLayoutManager.java
--- layoutmgr/LineLayoutManager.java	29 May 2004 09:07:59 -0000	1.23
+++ layoutmgr/LineLayoutManager.java	24 Aug 2004 14:38:42 -0000
@@ -34,6 +34,8 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.ArrayList;
+import java.util.LinkedList;
+
 import org.apache.fop.traits.MinOptMax;
 
 /**
@@ -58,6 +60,13 @@
         iIndents = marginProps.startIndent + marginProps.endIndent;
         BlockProps blockProps = pm.getBlockProps();
         bTextAlignment = blockProps.textAlign;
+        bTextAlignmentLast = blockProps.textAlignLast;
+        //
+        if (bTextAlignment != JUSTIFY && bTextAlignmentLast == JUSTIFY) {
+            effectiveAlignment = 0;
+        } else {
+            effectiveAlignment = bTextAlignment;
+        }
         textIndent = blockProps.firstIndent;
         hyphProps = pm.getHyphenationProps();
     }
@@ -93,6 +102,8 @@
 
     private BreakPoss prevBP = null; // Last confirmed break position
     private int bTextAlignment = TextAlign.JUSTIFY;
+    private int bTextAlignmentLast;
+    private int effectiveAlignment;
     private Length textIndent;
     private int iIndents = 0;
     private CommonHyphenation hyphProps;
@@ -104,6 +115,225 @@
     // inline start pos when adding areas
     private int iStartPos = 0;
 
+    private ArrayList knuthParagraphs = null;
+    private LinkedList activeList = null;
+    private LinkedList inactiveList = null;
+    private ArrayList breakpoints = null;
+    private int iReturnedLBP = 0;
+    private int iStartElement = 0;
+    private int iEndElement = 0;
+    private int iCurrParIndex = 0;
++

+    private KnuthNode lastDeactivatedNode = 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;
+
+    private static final int INFINITE_RATIO = 1000;
+
+    // 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
+    // returned by each LM
+    private class Update {
+        private LayoutManager inlineLM;
+        private int iFirstIndex;
+
+        public Update(LayoutManager lm, int index) {
+            inlineLM = lm;
+            iFirstIndex = index;
+        }
+    }
+
+    // this class represents a paragraph
+    private class Paragraph extends LinkedList {
+        // number of KnuthElements added by the LineLayoutManager
+        public int ignoreAtStart = 0;
+        public int ignoreAtEnd = 0;
+        // minimum space at the end of the last line (in millipoints)
+        public int lineFillerWidth;
+        // word space dimension (in millipoints)
+        private int wordSpaceIPD;
+
+        public void startParagraph(int lineWidth) {
+            // get the word space dimension, which needs to be known
+            // in order to center text
+            LayoutManager lm;
+            if ((lm = getChildLM()) != null) {
+                wordSpaceIPD = lm.getWordSpaceIPD();
+            }
+
+            // set the minimum amount of empty space at the end of the
+            // last line
+            if (bTextAlignment == CENTER) {
+                lineFillerWidth = 0; 
+            } else {
+                lineFillerWidth = (int)(lineWidth / 6); 
+            }
+
+            // add auxiliary elements at the beginning of the paragraph
+            if (bTextAlignment == CENTER && bTextAlignmentLast != JUSTIFY) {
+                this.add(new KnuthGlue(0, 3 * wordSpaceIPD, 0,
+                                       null, false));
+                ignoreAtStart ++;
+            }
+
+            // add the element representing text indentation
+            // at the beginning of the first paragraph
+            if (knuthParagraphs.size() == 0
+                && textIndent.getValue() != 0) {
+                this.add(new KnuthBox(textIndent.getValue(), 0, 0, 0,
+                                      null, false));
+                ignoreAtStart ++;
+            }
+        }
+
+        public void endParagraph() {
+            // remove glue and penalty item at the end of the paragraph
+            while (this.size() > ignoreAtStart
+                   && !((KnuthElement)this.get(this.size() - 1)).isBox()) {
+                this.remove(this.size() - 1);
+            }
+            if (this.size() > ignoreAtStart) {
+                if (bTextAlignment == CENTER && bTextAlignmentLast != JUSTIFY) {
+                    this.add(new KnuthGlue(0, 3 * wordSpaceIPD, 0,
+                                           null, false));
+                    this.add(new KnuthPenalty(0, -KnuthElement.INFINITE, false, null, false));
+                    ignoreAtEnd = 2;
+                } else if (bTextAlignmentLast != JUSTIFY) {
+                    // add the elements representing the space at the end of the last line
+                    // and the forced break
+                    this.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, null, false));
+                    this.add(new KnuthGlue(lineFillerWidth, 10000000, 0, null, false));
+                    this.add(new KnuthPenalty(0, -KnuthElement.INFINITE, false, null, false));
+                    ignoreAtEnd = 3;
+                } else {
+                    // add only the element representing the forced break
+                    this.add(new KnuthPenalty(0, -KnuthElement.INFINITE, false, null, false));
+                    ignoreAtEnd = 1;
+                }
+                knuthParagraphs.add(this);
+            }
+        }
+    }
+
+
     /**
      * Create a new Line Layout Manager.
      * This is used by the block layout manager to create
@@ -140,9 +370,6 @@
         // IPD remaining in line
         MinOptMax availIPD = context.getStackLimit();
 
-        // QUESTION: maybe LayoutContext holds the Properties which
-        // come from block-level?
-
         LayoutContext inlineLC = new LayoutContext(context);
 
         clearPrevIPD();
@@ -153,185 +380,629 @@
         }
         prevBP = null;
 
+        // here starts Knuth's algorithm
+        KnuthElement thisElement = null;
+        LinkedList returnedList = null;
+        LineBreakPosition lbp = null;
+
+        if (knuthParagraphs == null) {
+            // it's the first time this method is called
+            knuthParagraphs = new ArrayList();
+            breakpoints = new ArrayList();
+
+            // convert all the text in a sequence of paragraphs made
+            // of KnuthBox, KnuthGlue and KnuthPenalty objects
+            boolean bPrevWasKnuthBox = false;
+            KnuthBox prevBox = null;
+
+            Paragraph knuthPar = new Paragraph();
+            knuthPar.startParagraph(availIPD.opt);
         while ((curLM = getChildLM()) != null) {
-            // INITIALIZE LAYOUT CONTEXT FOR CALL TO CHILD LM
-            // First break for the child LM in each of its areas
-            boolean bFirstBPforLM = (vecInlineBreaks.isEmpty()
-                    || (((BreakPoss) vecInlineBreaks.get(vecInlineBreaks.size() - 1)).
-                                      getLayoutManager() != curLM));
-
-            // Need previous breakpoint! ATTENTION when backing up for hyphenation!
-            prev = (vecInlineBreaks.isEmpty())
-                    ? null
-                    : (BreakPoss) vecInlineBreaks.get(vecInlineBreaks.size() - 1);
-            initChildLC(inlineLC, prev,
-                        (vecInlineBreaks.size() == iPrevLineEnd),
-                        bFirstBPforLM, new SpaceSpecifier(true));
-
-
-            /* If first BP in this line but line is not first in this
-             * LM and previous line end decision was not forced (LINEFEED),
-             * then set the SUPPRESS_LEADING_SPACE flag.
-             */
-            inlineLC.setFlags(LayoutContext.SUPPRESS_LEADING_SPACE,
-                              (vecInlineBreaks.size() == iPrevLineEnd
-                               && !vecInlineBreaks.isEmpty()
-                               && ((BreakPoss) vecInlineBreaks.get(vecInlineBreaks.size() - 1)).
-                                    isForcedBreak() == false));
-
-            // GET NEXT POSSIBLE BREAK FROM CHILD LM
-            // prevBP = bp;
-            if ((bp = curLM.getNextBreakPoss(inlineLC)) != null) {
-                // Add any space before and previous content dimension
-                MinOptMax prevIPD = updatePrevIPD(bp, prev,
-                                                  (vecInlineBreaks.size() == iPrevLineEnd),
-                                                  inlineLC.isFirstArea());
-                MinOptMax bpDim =
-                  MinOptMax.add(bp.getStackingSize(), prevIPD);
-
-                // check if this bp fits in line
-                boolean bBreakOK = couldEndLine(bp);
-                if (bBreakOK) {
-                    /* Add any non-conditional trailing space, assuming we
-                     * end the line here. If we can't break here, we just
-                     * check if the content fits.
-                     */
-                    bpDim.add(bp.resolveTrailingSpace(true));
+                if ((returnedList = curLM.getNextKnuthElements(inlineLC, effectiveAlignment)) != null) {
+                    // if there are two consecutive KnuthBox, the first one 
+                    // does not represent a whole word, so it must be given
+                    // one more letter space
+                    thisElement = (KnuthElement)returnedList.getFirst();
+                    if (returnedList.size() > 1
+                        || !(thisElement.isPenalty() && ((KnuthPenalty)thisElement).getP() == -KnuthElement.INFINITE)) {
+                        if (thisElement.isBox() && !thisElement.isAuxiliary()
+                            && bPrevWasKnuthBox) {
+                            prevBox = (KnuthBox)knuthPar.removeLast();
+                            if (!prevBox.isAuxiliary()) {
+                                // if letter spacing is constant, only prevBox needs to be
+                                // replaced;
+                                knuthPar.addLast(prevBox.getLayoutManager().addALetterSpaceTo(prevBox));
+                            } else {
+                                // prevBox is the last element in the sub-sequence
+                                //   <box> <aux penalty> <aux glue> <aux box>
+                                // the letter space is added to <aux glue>, while the other
+                                // elements are not changed
+                                KnuthBox auxBox = prevBox;
+                                KnuthGlue auxGlue= (KnuthGlue)knuthPar.removeLast();
+                                KnuthPenalty auxPenalty = (KnuthPenalty)knuthPar.removeLast();
+                                prevBox = (KnuthBox)knuthPar.getLast();
+                                knuthPar.addLast(auxPenalty);
+                                knuthPar.addLast(prevBox.getLayoutManager().addALetterSpaceTo(prevBox));
+                                knuthPar.addLast(auxBox);
                 }
-                // TODO: stop if linebreak is forced (NEWLINE)
-                // PROBLEM: interaction with wrap which can be set
-                // at lower levels!
-                // System.err.println("BPdim=" + bpDim.opt);
-
-                // Check if proposed area would fit in line
-                if (bpDim.min > availIPD.max) {
-                    // See if we have already found a potential break
-                    //if (vecPossEnd.size() > 0) break;
+                        }
+                        if (((KnuthElement)returnedList.getLast()).isBox()) {
+                            bPrevWasKnuthBox = true;
+                        } else {
+                            bPrevWasKnuthBox = false;
+                        }
+                        // add the new elements to the paragraph
+                        knuthPar.addAll(returnedList);
+                    } else {
+                        // a list with a single penalty item whose value is -inf
+                        // represents a preserved linefeed, wich forces a line break
+                        knuthPar.endParagraph();
+                        knuthPar = new Paragraph();
+                        knuthPar.startParagraph(availIPD.opt);
+                        bPrevWasKnuthBox = false;
+                    }
+                } else {
+                    // curLM returned null; this can happen if it has nothing more to layout,
+                    // so just iterate once more to see if there are other chilren
+                }
+            }
+            knuthPar.endParagraph();
 
-                    // This break position doesn't fit
-                    // TODO: If we are in nowrap, we use it as is!
+            // find the optimal line breaking points for each paragraph
+            ListIterator paragraphsIterator = knuthParagraphs.listIterator(knuthParagraphs.size());
+            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.TRUE) {
-                        // If we are already in a hyphenation loop, then stop.
+                        // consider every hyphenation point as a legal break
+                        findHyphenationPoints(currPar);
+                    } else {
+                        // try with a higher threshold
+                        maxAdjustment = 5;
+                    }
 
-                        if (inlineLC.tryHyphenate()) {
-                            if (prevBP == null) {
-                                vecInlineBreaks.add(bp);
-                                prevBP = bp;
+                    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 braking points
+                        log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment
+                                         + (hyphProps.hyphenate == Constants.TRUE ? " and hyphenation" : ""));
+                        maxAdjustment = 20;
+                        iBPcount = findBreakingPoints(currPar, context.getStackLimit().opt, maxAdjustment, true);
                             }
-                            break;
                         }
-                        // Otherwise, prepare to try hyphenation
-                        if (!bBreakOK) {
-                            // Make sure we collect the entire word!
-                            vecInlineBreaks.add(bp);
-                            continue;
+            }
+        } else {
+            // this method has been called before
+            // all line breaks are already calculated
                         }
 
-                        inlineLC.setHyphContext(
-                          getHyphenContext((prevBP == null) ? prev : prevBP, bp));
-                        if (inlineLC.getHyphContext() == null) {
-                            if (prevBP == null) {
-                                vecInlineBreaks.add(bp);
-                                prevBP = bp;
+        // get a break point from the list
+        lbp = (LineBreakPosition)breakpoints.get(iReturnedLBP ++);
+        if (iReturnedLBP == breakpoints.size()) {
+            setFinished(true);
                             }
-                            break;
+
+        BreakPoss curLineBP = new BreakPoss(lbp);
+        curLineBP.setFlag(BreakPoss.ISLAST, isFinished());
+        curLineBP.setStackingSize(new MinOptMax(lbp.lineHeight));
+        return curLineBP;
                         }
-                        inlineLC.setFlags(LayoutContext.TRY_HYPHENATE,
-                                          true);
-                        // Reset to previous acceptable break
-                        resetBP((prevBP == null) ? prev : prevBP);
+
+    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));
+        inactiveList = new LinkedList();
+
+        // 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;
                     } else {
-                        /* If we are not in justified text, we can end the line at
-                         * prevBP.
-                         */
-                        if (prevBP == null) {
-                            vecInlineBreaks.add(bp);
-                            prevBP = bp;
+                // 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;
                         }
-                        break;
                     }
+
+        if (activeList.size() == 0) {
+            if (force) {
+                activeList.add(lastDeactivatedNode);
+                bForced = true;
+                log.error("Could not find a set of breking points");
                 } else {
-                    // Add the BP to the list whether or not we can break
-                    vecInlineBreaks.add(bp);
-                    // Handle end of this LM's areas
-                    if (bBreakOK) {
-                        prevBP = bp; // Save reference to this BP
-                        if (bp.isForcedBreak()) {
-                            break;
+                inactiveList.clear();
+                return 0;
                         }
-                        if (bpDim.max >= availIPD.min) {
-                            /* This is a possible line BP (line could be filled)
-                             * bpDim.max >= availIPD.min
-                             * Keep this as a possible break, depending on
-                             * "cost". We will choose lowest cost.
-                             * Cost depends on stretch
-                             * (ie, bpDim.opt closes to availIPD.opt), keeps
-                             * and hyphenation.
-                             */
-                            vecPossEnd.add(new BreakCost(bp,
-                                    Math.abs(availIPD.opt - bpDim.opt)));
                         }
-                        // Otherwise it's short
+
+        // there is at least one set 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) {
+                bestActiveNode = tempNode;
+                bestDemerits = bestActiveNode.totalDemerits;
+            }
+        }
+        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;
+                }
+            }
+            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 || bForced) ? bestActiveNode.difference : bestActiveNode.difference + par.lineFillerWidth;
+            int textAlign = (bestActiveNode.line < line || bForced) ? bTextAlignment : bTextAlignmentLast;
+            indent += (textAlign == CENTER) ?
+                          difference / 2 :
+                          (textAlign == END) ? difference : 0;
+            indent += (bestActiveNode.line == 1 && knuthParagraphs.indexOf(par) == 0) ? 
+                          textIndent.getValue() : 0;
+            double ratio = (textAlign == JUSTIFY) ? bestActiveNode.adjustRatio : 0;
+
+            // lead to baseline is
+            // max of: baseline fixed alignment and middle/2
+            // after baseline is
+            // max: top height-lead, middle/2 and bottom height-lead
+            int halfLeading = (lineHeight - lead - follow) / 2;
+            // height before baseline
+            int lineLead = lead + halfLeading;
+            // maximum size of top and bottom alignment
+            int maxtb = follow + halfLeading;
+            // max size of middle alignment below baseline
+            int middlefollow = maxtb;
+
+            // index of the first KnuthElement in this line
+            int firstElementIndex = 0;
+            if (line > 1) {
+                firstElementIndex = bestActiveNode.previous.position + 1;
+            }
+            ListIterator inlineIterator = par.listIterator(firstElementIndex);
+            for (int j = 0;
+                 j < (bestActiveNode.position - firstElementIndex + 1);
+                 j++) {
+                KnuthElement element = (KnuthElement)inlineIterator.next();
+                if (element.isBox()) {
+                    if (((KnuthBox)element).getLead() > lineLead) {
+                        lineLead = ((KnuthBox)element).getLead();
+                    }
+                    if (((KnuthBox)element).getTotal() > maxtb) {
+                        maxtb = ((KnuthBox)element).getTotal();
+                    }
+                    if (((KnuthBox)element).getMiddle() > middlefollow) {
+                        middlefollow = ((KnuthBox)element).getMiddle();
+                    }
+                }
+            }
+
+            if (maxtb - lineLead > middlefollow) {
+                middlefollow = maxtb - lineLead;
+            }
+
+            // add nodes at the beginning of the list, as they are found
+            // backwards, from the last one to the first one
+            breakpoints.add(0, new LineBreakPosition(this, bestActiveNode.position,
+                                                     ratio, 0, indent,
+                                                     lineLead + middlefollow, lineLead));
+            bestActiveNode = bestActiveNode.previous;
+        }
+        if (bForced) {
+            fallback(par, line);
+        }
+        activeList.clear();
+        inactiveList.clear();
+        return line;
+    }
+
+    private void fallback(Paragraph par, int line) {
+        // lead to baseline is
+        // max of: baseline fixed alignment and middle/2
+        // after baseline is
+        // max: top height-lead, middle/2 and bottom height-lead
+        int halfLeading = (lineHeight - lead - follow) / 2;
+        // height before baseline
+        int lineLead = lead + halfLeading;
+        // maximum size of top and bottom alignment
+        int maxtb = follow + halfLeading;
+        // max size of middle alignment below baseline
+        int middlefollow = maxtb;
+
+        ListIterator inlineIterator = par.listIterator(lastDeactivatedNode.position);
+        for (int j = lastDeactivatedNode.position;
+             j < (par.size());
+             j++) {
+            KnuthElement element = (KnuthElement)inlineIterator.next();
+            if (element.isBox()) {
+                if (((KnuthBox)element).getLead() > lineLead) {
+                    lineLead = ((KnuthBox)element).getLead();
+                }
+                if (((KnuthBox)element).getTotal() > maxtb) {
+                    maxtb = ((KnuthBox)element).getTotal();
+                }
+                if (((KnuthBox)element).getMiddle() > middlefollow) {
+                    middlefollow = ((KnuthBox)element).getMiddle();
+                }
+            }
+        }
+
+        if (maxtb - lineLead > middlefollow) {
+                    middlefollow = maxtb - lineLead;
+        }
+
+        breakpoints.add(line, new LineBreakPosition(this, par.size() - 1,
+                                                    0, 0, 0,
+                                                    lineLead + middlefollow, lineLead));
+    }
+
+
+    private void considerLegalBreak(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 {
-                        /* Can't end line here. */
+            activeNode = null;
                     }
-                } // end of bpDim.min <= availIPD.max
-            // end of getNextBreakPoss!=null on current child LM
+
+        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 {
-                /* The child LM can return a null BreakPoss if it has
-                 * nothing (more) to layout. This can happen when backing
-                 * up. Just try the next child LM.
-                 */
+                        newIPDAdjust = INFINITE_RATIO;
+                    }
+                } else if (neededAdjustment < 0) {
+                    maxAdjustment = totalShrink - activeNode.totalShrink;
+                    if (maxAdjustment > 0) {
+                        newIPDAdjust = (double)neededAdjustment / maxAdjustment;
+                    } else {
+                        newIPDAdjust = INFINITE_RATIO;
             }
-            if (inlineLC.tryHyphenate()
-                    && !inlineLC.getHyphContext().hasMoreHyphPoints()) {
+                } else {
+                    // neededAdjustment == 0
+                    newIPDAdjust = 0;
+                }
+                if (newIPDAdjust < -1
+                    || (element.isPenalty() && ((KnuthPenalty)element).getP() == -KnuthElement.INFINITE)
+                        && !(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 ++;
+                    }
+                    lastDeactivatedNode = tempNode;
+                    inactiveList.add(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;
             }
-        } // end of while on child LM
-        if ((curLM = getChildLM()) == null) {
-            // No more content to layout!
-            setFinished(true);
+                if (activeNode.line >= newLine) {
+                    break;
         }
+            } // end of the inner while
 
-        if (bp == null) {
-            return null;
+            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;
+                    }
         }
 
-        if (prevBP == null) {
-            BreakPoss prevLineEnd = (iPrevLineEnd == 0)
-                ? null
-                : (BreakPoss) vecInlineBreaks.get(iPrevLineEnd);
-            if (allAreSuppressible(prevLineEnd)) {
-                removeAllBP(prevLineEnd);
-                return null;
+                // 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
+    }
+
+    /**
+     * find hyphenation points for every word int the current paragraph
+     * @ param currPar the paragraph whose words will be hyphenated
+     */
+    private void findHyphenationPoints(Paragraph currPar){
+        // hyphenate every word
+        ListIterator currParIterator = currPar.listIterator(currPar.ignoreAtStart);
+        // list of TLM involved in hyphenation
+        LinkedList updateList = new LinkedList();
+        KnuthElement firstElement = null;
+        KnuthElement nextElement = null;
+        // current TextLayoutManager
+        LayoutManager currLM = null;
+        // number of KnuthBox elements containing word fragments
+        int boxCount;
+        // number of auxiliary KnuthElements between KnuthBoxes
+        int auxCount;
+        StringBuffer sbChars = null;
+
+        // find all hyphenation points
+        while (currParIterator.hasNext()) {
+            firstElement = (KnuthElement)currParIterator.next();
+            // 
+            if (firstElement.getLayoutManager() != currLM) {
+                currLM = firstElement.getLayoutManager();
+                if (currLM != null) { 
+                    updateList.add(new Update(currLM, currParIterator.previousIndex()));
             } else {
-                prevBP = bp;
+                    break;
             }
         }
 
-        // Choose the best break
-        if (!bp.isForcedBreak() && vecPossEnd.size() > 0) {
-            prevBP = getBestBP(vecPossEnd);
+            // collect word fragments, ignoring auxiliary elements;
+            // each word fragment was created by a different TextLM
+            if (firstElement.isBox() && !firstElement.isAuxiliary()) {
+                boxCount = 1;
+                auxCount = 0;
+                sbChars = new StringBuffer();
+                currLM.getWordChars(sbChars, firstElement.getPosition());
+                // look if next elements are boxes too
+                while (currParIterator.hasNext()) {
+                    nextElement = (KnuthElement)currParIterator.next();
+                    if (nextElement.isBox() && !nextElement.isAuxiliary()) {
+                        // a non-auxiliary KnuthBox: append word chars
+                        if (currLM != nextElement.getLayoutManager()) {
+                            currLM = nextElement.getLayoutManager();
+                            updateList.add(new Update(currLM, currParIterator.previousIndex()));
+                        }
+                        // append text to recreate the whole word
+                        boxCount ++;
+                        currLM.getWordChars(sbChars, nextElement.getPosition());
+                    } else if (!nextElement.isAuxiliary()) {
+                        // a non-auxiliary non-box KnuthElement: stop
+                        // go back to the last box or auxiliary element
+                        currParIterator.previous(); 
+                        break;
+                    } else {
+                        // an auxiliary KnuthElement: simply ignore it
+                        auxCount ++;
+                    }
         }
-        if (bp != prevBP) {
-            /** Remove BPs after prevBP
-                if condAllAreSuppressible returns true,
-                else backup child LM */
-            if (condAllAreSuppressible(prevBP)) {
-                removeAllBP(prevBP);
+                log.trace(" Word to hyphenate: " + sbChars.toString());
+                // find hyphenation points
+                HyphContext hc = getHyphenContext(sbChars);
+                // ask each LM to hyphenate its word fragment
+                if (hc != null) {
+                    KnuthElement element = null;
+                    for (int i = 0; i < (boxCount + auxCount); i++) {
+                        currParIterator.previous();
+                    }
+                    for (int i = 0; i < (boxCount + auxCount); i++) {
+                        element = (KnuthElement)currParIterator.next();
+                        if (element.isBox() && !element.isAuxiliary()) {
+                            element.getLayoutManager().hyphenate(element.getPosition(), hc);
             } else {
-                reset();
+                            // nothing to do, element is an auxiliary KnuthElement
+                        }
+                    }
+                }
             }
         }
+//        // reset currParIterator
+//        currParIterator = currPar.listIterator(currPar.ignoreAtStart);
+        // create iterator for the updateList
+        ListIterator updateListIterator = updateList.listIterator();
+        Update currUpdate = null;
+        int iPreservedElements = 0;
+        int iAddedElements = 0;
+        int iRemovedElements = 0;
+
+        while (updateListIterator.hasNext()) {
+            // ask the LMs to apply the changes and return 
+            // the new KnuthElements to replace the old ones
+            currUpdate = (Update)updateListIterator.next();
+            int fromIndex = currUpdate.iFirstIndex;
+            int toIndex;
+            if (updateListIterator.hasNext()) {
+                Update nextUpdate = (Update)updateListIterator.next();
+                toIndex = nextUpdate.iFirstIndex;
+                updateListIterator.previous();
+            } else {
+                // maybe this is not always correct!
+                toIndex = currPar.size() - currPar.ignoreAtEnd - iAddedElements;
+            }
 
-        // Don't justify last line in the sequence or if forced line-end
-        int talign = bTextAlignment;
-        if ((bTextAlignment == TextAlign.JUSTIFY
-                             && (prevBP.isForcedBreak()
-                             || isFinished()))) {
-            talign = TextAlign.START;
+            // applyChanges() returns true if the LM modifies its data,
+            // so it must return new KnuthElements to replace the old ones
+            if (currUpdate.inlineLM.applyChanges(currPar.subList(fromIndex + iAddedElements, toIndex + iAddedElements))) {
+                // insert the new KnuthElements
+                LinkedList newElements = null;
+                //while ((newElements = currUpdate.inlineLM.getChangedKnuthElements(currPar.subList(fromIndex + iAddedElements, toIndex + iAddedElements), flaggedPenalty, effectiveAlignment)) != null) {
+                newElements = currUpdate.inlineLM.getChangedKnuthElements(currPar.subList(fromIndex + iAddedElements, toIndex + iAddedElements), flaggedPenalty, effectiveAlignment);
+                // remove the old elements
+                currPar.subList(fromIndex + iAddedElements, toIndex + iAddedElements).clear();
+                // insert the new elements
+                currPar.addAll(fromIndex + iAddedElements, newElements);
+                iAddedElements += newElements.size() - (toIndex - fromIndex);
         }
-        return makeLineBreak(iPrevLineEnd, availIPD, talign);
+        }
+        updateListIterator = null;
+        updateList.clear();
     }
 
     private void resetBP(BreakPoss resetBP) {
@@ -435,38 +1106,8 @@
         }
     }
 
-    private HyphContext getHyphenContext(BreakPoss prev,
-                                         BreakPoss newBP) {
-        // Get a "word" to hyphenate by getting characters from all
-        // pending break poss which are in vecInlineBreaks, starting
-        // with the position just AFTER prev.getPosition()
-
-        vecInlineBreaks.add(newBP);
-        ListIterator bpIter =
-            vecInlineBreaks.listIterator(vecInlineBreaks.size());
-        while (bpIter.hasPrevious() && bpIter.previous() != prev) {
-        }
-        if (prev != null && bpIter.next() != prev) {
-            log.error("findHyphenPoss: problem!");
-            return null;
-        }
-        StringBuffer sbChars = new StringBuffer(30);
-        while (bpIter.hasNext()) {
-            BreakPoss bp = (BreakPoss) bpIter.next();
-            if (prev != null &&
-                bp.getLayoutManager() == prev.getLayoutManager()) {
-                bp.getLayoutManager().getWordChars(sbChars,
-                    prev.getPosition(), bp.getPosition());
-            } else {
-                bp.getLayoutManager().getWordChars(sbChars, null,
-                    bp.getPosition());
-            }
-            prev = bp;
-        }
-        vecInlineBreaks.remove(vecInlineBreaks.size() - 1); // remove last
-        log.debug("Word to hyphenate: " + sbChars.toString());
-
-        // Now find all hyphenation points in this word (get in an array of offsets)
+    private HyphContext getHyphenContext(StringBuffer sbChars) {
+        // Find all hyphenation points in this word (get in an array of offsets)
         // hyphProps are from the block level?. Note that according to the spec,
         // they also "apply to" fo:character. I don't know what that means, since
         // if we change language in the middle of a "word", the effect would seem
@@ -632,16 +1273,20 @@
      */
     public void resetPosition(Position resetPos) {
         if (resetPos == null) {
-            iStartPos = 0;
-            reset(null);
-            vecInlineBreaks.clear();
-            prevBP = null;
+            setFinished(false);
+            iReturnedLBP = 0;
         } else {
-            prevBP = (BreakPoss)vecInlineBreaks.get(((LineBreakPosition)resetPos).getLeafPos());
-            while (vecInlineBreaks.get(vecInlineBreaks.size() - 1) != prevBP) {
-                vecInlineBreaks.remove(vecInlineBreaks.size() - 1);
+            if (isFinished()) {
+                // if isFinished is true, iReturned LBP == breakpoints.size()
+                // and breakpoints.get(iReturnedLBP) would generate
+                // an IndexOutOfBoundException
+                setFinished(false);
+                iReturnedLBP--;
+            }
+            while ((LineBreakPosition)breakpoints.get(iReturnedLBP) != (LineBreakPosition)resetPos) {
+                iReturnedLBP --;
             }
-            reset(prevBP.getPosition());
+            iReturnedLBP ++;
         }
     }
 
@@ -672,6 +1317,11 @@
         LayoutManager childLM;
         LayoutContext lc = new LayoutContext(0);
         while (parentIter.hasNext()) {
+            ListIterator paragraphIterator = null;
+            KnuthElement tempElement = null;
+            // the TLM which created the last KnuthElement in this line
+            LayoutManager lastLM = null;
+
             LineBreakPosition lbp = (LineBreakPosition) parentIter.next();
             LineArea lineArea = new LineArea();
             lineArea.setStartIndent(lbp.startIndent);
@@ -679,11 +1329,44 @@
             lc.setBaseline(lbp.baseline);
             lc.setLineHeight(lbp.lineHeight);
             setCurrentArea(lineArea);
+
+            Paragraph currPar = (Paragraph)knuthParagraphs.get(iCurrParIndex);
+            iEndElement = lbp.getLeafPos();
+
+            // ignore the first elements added by the LineLayoutManager
+            iStartElement += (iStartElement == 0) ? currPar.ignoreAtStart : 0;
+
+            // ignore the last elements added by the LineLayoutManager
+            iEndElement -= (iEndElement == (currPar.size() - 1)) ? currPar.ignoreAtEnd : 0;
+
+            // ignore the last element in the line if it is a KnuthGlue object
+            paragraphIterator = currPar.listIterator(iEndElement);
+            tempElement = (KnuthElement)paragraphIterator.next();
+            if (tempElement.isGlue()) {
+                iEndElement --;
+                paragraphIterator.previous(); // this returns the same KnuthElement
+                tempElement = (KnuthElement)paragraphIterator.previous();
+            }
+            lastLM = tempElement.getLayoutManager();
+
+            // ignore KnuthGlue and KnuthPenalty objects at the beginning of the line
+            paragraphIterator = currPar.listIterator(iStartElement);
+            tempElement = (KnuthElement)paragraphIterator.next();
+            while (!tempElement.isBox() && paragraphIterator.hasNext()) {
+                tempElement = (KnuthElement)paragraphIterator.next();
+                iStartElement ++;
+            }
+
             // Add the inline areas to lineArea
-            PositionIterator inlinePosIter =
-              new BreakPossPosIter(vecInlineBreaks, iStartPos,
-                                   lbp.getLeafPos() + 1);
-            iStartPos = lbp.getLeafPos() + 1;
+            PositionIterator inlinePosIter = new KnuthPossPosIter(currPar, iStartElement, iEndElement + 1);
+
+            iStartElement = lbp.getLeafPos() + 1;
+            if (iStartElement == currPar.size()) {
+                // advance to next paragraph
+                iCurrParIndex ++;
+                iStartElement = 0;
+            }
+
             lc.setSpaceAdjust(lbp.dAdjust);
             lc.setIPDAdjust(lbp.ipdAdjust);
             lc.setLeadingSpace(new SpaceSpecifier(true));
@@ -691,6 +1374,7 @@
             lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
             setChildContext(lc);
             while ((childLM = inlinePosIter.getNextChildLM()) != null) {
+                lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM));
                 childLM.addAreas(inlinePosIter, lc);
                 lc.setLeadingSpace(lc.getTrailingSpace());
                 lc.setTrailingSpace(new SpaceSpecifier(false));
Index: layoutmgr/TextLayoutManager.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/layoutmgr/TextLayoutManager.java,v
retrieving revision 1.17
diff -w -u -r1.17 TextLayoutManager.java
--- layoutmgr/TextLayoutManager.java	29 May 2004 08:50:46 -0000	1.17
+++ layoutmgr/TextLayoutManager.java	24 Aug 2004 14:38:46 -0000
@@ -19,8 +19,12 @@
 package org.apache.fop.layoutmgr;
 
 import java.util.ArrayList;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.ListIterator;
 
 import org.apache.fop.fo.FOText;
+import org.apache.fop.fo.Constants;
 import org.apache.fop.traits.SpaceVal;
 import org.apache.fop.area.Trait;
 import org.apache.fop.area.inline.InlineArea;
@@ -45,18 +49,31 @@
         private short iStartIndex;
         private short iBreakIndex;
         private short iWScount;
+        private short iLScount;
         private MinOptMax ipdArea;
         private boolean bHyphenated;
-        public AreaInfo(short iSIndex, short iBIndex, short iWS,
+        public AreaInfo(short iSIndex, short iBIndex, short iWS, short iLS,
                  MinOptMax ipd, boolean bHyph) {
             iStartIndex = iSIndex;
             iBreakIndex = iBIndex;
             iWScount = iWS;
+            iLScount = iLS;
             ipdArea = ipd;
             bHyphenated = bHyph;
         }
     }
 
+    // this class stores information about changes in vecAreaInfo
+    // which are not yet applied
+    private class PendingChange {
+        public AreaInfo ai;
+        public int index;
+
+        public PendingChange(AreaInfo ai, int index) {
+            this.ai = ai;
+            this.index = index;
+        }
+    }
 
     // Hold all possible breaks for the text in this LM's FO.
     private ArrayList vecAreaInfo;
@@ -85,6 +102,8 @@
     // private MinOptMax nextIPD = new MinOptMax(0);
     /** size of a space character (U+0020) glyph in current font */
     private int spaceCharIPD;
+    private MinOptMax wordSpaceIPD;
+    private MinOptMax letterSpaceIPD;
     /** size of the hyphen character glyph in current font */
     private int hyphIPD;
     /** 1/2 of word-spacing value */
@@ -92,6 +111,12 @@
     /** Number of space characters after previous possible break position. */
     private int iNbSpacesPending;
 
+    private boolean bChanged = false;
+    private int iReturnedIndex = 0;
+    private short iThisStart = 0;
+    private short iTempStart = 0;
+    private LinkedList changeList = null;
+
     /**
      * Create a Text layout manager.
      *
@@ -114,6 +139,22 @@
         SpaceVal ws = foText.textInfo.wordSpacing;
         halfWS = new SpaceVal(MinOptMax.multiply(ws.getSpace(), 0.5),
                 ws.isConditional(), ws.isForcing(), ws.getPrecedence());
+        SpaceVal ls = foText.textInfo.letterSpacing;
+
+        // letter space applies only to consecutive non-space characters,
+        // while word space applies to space characters;
+        // i.e. the spaces in the string "A SIMPLE TEST" are:
+        //      A<<ws>>S<ls>I<ls>M<ls>P<ls>L<ls>E<<ws>>T<ls>E<ls>S<ls>T
+        // there is no letter space after the last character of a word,
+        // nor after a space character
+
+        // set letter space and word space dimension;
+        // the default value "normal" was converted into a MinOptMax value
+        // in the PropertyManager.getTextLayoutProps() method
+        letterSpaceIPD = new MinOptMax(ls.getSpace().min, ls.getSpace().opt, ls.getSpace().max);
+        wordSpaceIPD = new MinOptMax(spaceCharIPD + ws.getSpace().min,
+                                     spaceCharIPD + ws.getSpace().opt,
+                                     spaceCharIPD + ws.getSpace().max);
     }
 
     /**
@@ -397,9 +438,9 @@
         // Note: break position now stores total size to here
 
         // Position is the index of the info for this word in the vector
-        vecAreaInfo.add(
-          new AreaInfo(iWordStart, iNextStart, iWScount, ipd, 
-                       ((flags & BreakPoss.HYPHENATED) != 0)));
+//        vecAreaInfo.add(
+//          new AreaInfo(iWordStart, iNextStart, iWScount, ipd, 
+//                       ((flags & BreakPoss.HYPHENATED) != 0)));
         BreakPoss bp = new BreakPoss(
                          new LeafPosition(this, vecAreaInfo.size() - 1));
         ipdTotal = ipd;
@@ -449,91 +490,99 @@
      * @param context LayoutContext for adjustments
      */
     public void addAreas(PositionIterator posIter, LayoutContext context) {
+
         // Add word areas
         AreaInfo ai = null;
         int iStart = -1;
         int iWScount = 0;
+        int iLScount = 0;
+        MinOptMax realWidth = new MinOptMax(0);
 
         /* On first area created, add any leading space.
          * Calculate word-space stretch value.
          */
         while (posIter.hasNext()) {
             LeafPosition tbpNext = (LeafPosition) posIter.next();
+            //
+            if (tbpNext.getLeafPos() != -1) {
             ai = (AreaInfo) vecAreaInfo.get(tbpNext.getLeafPos());
             if (iStart == -1) {
                 iStart = ai.iStartIndex;
             }
             iWScount += ai.iWScount;
+                iLScount += ai.iLScount;
+                realWidth.add(ai.ipdArea);
+            }
         }
         if (ai == null) {
             return;
         }
 
-        // ignore newline character
+        // Make an area containing all characters between start and end.
+        InlineArea word = null;
         int adjust = 0;
+        
+        // ignore newline character
         if (textArray[ai.iBreakIndex - 1] == NEWLINE) {
             adjust = 1;
         }
         String str = new String(textArray, iStart, ai.iBreakIndex - iStart - adjust);
 
         // add hyphenation character if the last word is hyphenated
-        if (ai.bHyphenated) {
+        if (context.isLastArea() && ai.bHyphenated) {
             str += foText.textInfo.hyphChar;
-            ai.ipdArea = MinOptMax.add(ai.ipdArea, new MinOptMax(hyphIPD));
+            realWidth.add(new MinOptMax(hyphIPD));
         }
 
-        // Calculate total adjustment
-        int iRealWidth = ai.ipdArea.opt;
-        int iAdjust = 0;
+        // Calculate adjustments
+        int iDifference = 0;
+        int iTotalAdjust = 0;
+        int iWordSpaceDim = wordSpaceIPD.opt;
+        int iLetterSpaceDim = letterSpaceIPD.opt;
         double dIPDAdjust = context.getIPDAdjust();
-        double dSpaceAdjust = context.getSpaceAdjust();
+        double dSpaceAdjust = context.getSpaceAdjust(); // not used
+
+        // calculate total difference between real and available width
         if (dIPDAdjust > 0.0) {
-            iRealWidth += (int)((double)(ai.ipdArea.max - ai.ipdArea.opt) * dIPDAdjust);
-        }
-        else {
-            iRealWidth += (int)((double)(ai.ipdArea.opt - ai.ipdArea.min) * dIPDAdjust);
+            iDifference = (int)((double)(realWidth.max - realWidth.opt) * dIPDAdjust);
+        } else {
+            iDifference = (int)((double)(realWidth.opt - realWidth.min) * dIPDAdjust);
         }
-        iAdjust = (int)((double)(iRealWidth * dSpaceAdjust));
-        //System.err.println(" ");
-        //System.err.println("TextLayoutManager> recreated difference to fill= " + iAdjust);
-
-        // Make an area containing all characters between start and end.
-        InlineArea word = null;
 
-        if (" ".equals(str)) {
-            word = new Space();
-            word.setWidth(ai.ipdArea.opt + iAdjust);
+        // set letter space adjustment
+        if (dIPDAdjust > 0.0) {
+            iLetterSpaceDim += (int)((double)(letterSpaceIPD.max - letterSpaceIPD.opt) * dIPDAdjust);
         } else  {
-            TextArea t = createTextArea(str, iRealWidth + iAdjust, 
-                context.getBaseline());
-            if (iWScount > 0) {
-                //getLogger().error("Adjustment per word-space= " +
-                //                   iAdjust / iWScount);
-                t.setTextSpaceAdjust(iAdjust / iWScount);
-                //System.err.println("TextLayoutManager> word spaces= " + iWScount + " adjustment per word space= " + (iAdjust/iWScount));
-            }
-            word = t;
-        }
-        if ((textArray[iStart] == SPACE || textArray[iStart] == NBSPACE)
-                && context.getLeadingSpace().hasSpaces()) {
-            context.getLeadingSpace().addSpace(halfWS);
-        }
-        // Set LAST flag if done making characters
-        int iLastChar;
-        for (iLastChar = ai.iBreakIndex;
-                iLastChar < textArray.length && textArray[iLastChar] == SPACE;
-                iLastChar++) {
-            //nop
+            iLetterSpaceDim += (int)((double)(letterSpaceIPD.opt - letterSpaceIPD.min) * dIPDAdjust);
         }
-        context.setFlags(LayoutContext.LAST_AREA,
-                         iLastChar == textArray.length);
+        iTotalAdjust += (iLetterSpaceDim - letterSpaceIPD.opt) * iLScount;
 
-        // Can we have any trailing space? Yes, if last char was a space!
-        context.setTrailingSpace(new SpaceSpecifier(false));
-        if (textArray[ai.iBreakIndex - 1] == SPACE
-                || textArray[ai.iBreakIndex - 1] == NBSPACE) {
-            context.getTrailingSpace().addSpace(halfWS);
+        // set word space adjustment
+        // 
+        if (iWScount > 0) {
+            iWordSpaceDim += (int)((iDifference - iTotalAdjust) / iWScount);
+        } else {
+            // there are no word spaces in this area
         }
+        iTotalAdjust += (iWordSpaceDim - wordSpaceIPD.opt) * iWScount;
+
+        TextArea t = createTextArea(str, realWidth.opt + iTotalAdjust, context.getBaseline());
+
+        // iWordSpaceDim is computed in relation to wordSpaceIPD.opt
+        // but the renderer needs to know the adjustment in relation
+        // to the size of the space character in the current font;
+        // moreover, the pdf renderer adds the character spacing even to
+        // the last character of a word and to space characters: in order
+        // to avoid this, we must subtract the letter space width twice;
+        // the renderer will compute the space width as:
+        //   space width = 
+        //     = "normal" space width + letterSpaceAdjust + wordSpaceAdjust
+        //     = spaceCharIPD + letterSpaceAdjust +
+        //       + (iWordSpaceDim - spaceCharIPD -  2 * letterSpaceAdjust)
+        //     = iWordSpaceDim - letterSpaceAdjust
+        t.setTextLetterSpaceAdjust(iLetterSpaceDim);
+        t.setTextWordSpaceAdjust(iWordSpaceDim - spaceCharIPD - 2 * t.getTextLetterSpaceAdjust());
+        word = t;
         if (word != null) {
             parentLM.addChild(word);
         }
@@ -564,5 +613,302 @@
         return textArea;
     }
 
+    public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
+        LinkedList returnList = new LinkedList();
+
+        while (iNextStart < textArray.length) {
+            if (textArray[iNextStart] == SPACE) {
+                // normal, breaking space
+                switch (alignment) {
+                    case CENTER : vecAreaInfo.add(new AreaInfo(iNextStart, (short)(iNextStart + 1), (short)1, (short) 0,
+                                                               wordSpaceIPD, false));
+                                  returnList.add(new KnuthGlue(0, 3 * wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, vecAreaInfo.size() - 1), false));
+                                  returnList.add(new KnuthPenalty(0, 0, false,
+                                                                  new LeafPosition(this, -1), true));
+                                  returnList.add(new KnuthGlue(wordSpaceIPD.opt, - 6 * wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, -1), true));
+                                  returnList.add(new KnuthBox(0, 0, 0, 0,
+                                                              new LeafPosition(this, -1), true));
+                                  returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
+                                                                  new LeafPosition(this, -1), true));
+                                  returnList.add(new KnuthGlue(0, 3 * wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, -1), true));
+                                  iNextStart ++;
+                                  break;
+
+                    case START  : // fall through
+                    case END    : vecAreaInfo.add(new AreaInfo(iNextStart, (short)(iNextStart + 1), (short)1, (short) 0,
+                                                  wordSpaceIPD, false));
+                                  returnList.add(new KnuthGlue(0, 3 * wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, vecAreaInfo.size() - 1), false));
+                                  returnList.add(new KnuthPenalty(0, 0, false,
+                                                                  new LeafPosition(this, -1), true));
+                                  returnList.add(new KnuthGlue(wordSpaceIPD.opt, - 3 * wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, -1), true));
+                                  iNextStart ++;
+                                  break;
+
+                    case JUSTIFY: vecAreaInfo.add(new AreaInfo(iNextStart, (short)(iNextStart + 1), (short)1, (short) 0,
+                                                  wordSpaceIPD, false));
+                                  returnList.add(new KnuthGlue(wordSpaceIPD.opt, wordSpaceIPD.max - wordSpaceIPD.opt, wordSpaceIPD.opt - wordSpaceIPD.min,
+                                                               new LeafPosition(this, vecAreaInfo.size() - 1), false));
+                                  iNextStart ++;
+                                  break;
+
+                    default:      vecAreaInfo.add(new AreaInfo(iNextStart, (short)(iNextStart + 1), (short)1, (short) 0,
+                                                  wordSpaceIPD, false));
+                                  returnList.add(new KnuthGlue(wordSpaceIPD.opt, wordSpaceIPD.max - wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, vecAreaInfo.size() - 1), false));
+                                  iNextStart ++;
+                }
+            } else if (textArray[iNextStart] == NBSPACE) {
+                // non breaking space
+                vecAreaInfo.add(new AreaInfo(iNextStart, (short)(iNextStart + 1), (short)1, (short) 0,
+                                             wordSpaceIPD, false));
+                returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
+                                                new LeafPosition(this, vecAreaInfo.size() - 1), false));
+                returnList.add(new KnuthGlue(wordSpaceIPD.opt, wordSpaceIPD.max - wordSpaceIPD.opt, wordSpaceIPD.opt - wordSpaceIPD.min,
+                                             new LeafPosition(this, vecAreaInfo.size() - 1), false));
+                iNextStart ++;
+            } else if (textArray[iNextStart] == NEWLINE) {
+                // linefeed; this can happen when linefeed-treatment="preserve"
+                // the linefeed character is the first one in textArray,
+                // so we can just return a list with a penalty item
+                returnList.add(new KnuthPenalty(0, -KnuthElement.INFINITE, false, null, false));
+                iNextStart ++;
+                return returnList;
+            } else {
+                // the beginning of a word
+                iThisStart = iNextStart;
+                iTempStart = iNextStart;
+                MinOptMax wordIPD = new MinOptMax(0);
+                for (;
+                     iTempStart < textArray.length
+                     && textArray[iTempStart] != SPACE
+                     && textArray[iTempStart] != NBSPACE;
+                     iTempStart ++) {
+                    // ignore newline characters
+                    if (textArray[iTempStart] != NEWLINE) {
+                        wordIPD.add(new MinOptMax(foText.textInfo.fs.getCharWidth(textArray[iTempStart])));
+                    }
+                }
+                wordIPD.add(MinOptMax.multiply(letterSpaceIPD, (iTempStart - iThisStart - 1)));
+                vecAreaInfo.add(new AreaInfo(iThisStart, iTempStart, (short)0, (short)(iTempStart - iThisStart - 1),
+                                             wordIPD, false));
+                if (letterSpaceIPD.min == letterSpaceIPD.max) {
+                    // constant letter space; simply return a box
+                    // whose width includes letter spaces
+                    returnList.add(new KnuthBox(wordIPD.opt, 0, 0, 0,
+                                                new LeafPosition(this, vecAreaInfo.size() - 1), false));
+                    iNextStart = iTempStart;
+                } else {
+                    // adjustable letter space;
+                    // some other KnuthElements are needed
+                    returnList.add(new KnuthBox(wordIPD.opt - (iTempStart - iThisStart - 1) * letterSpaceIPD.opt, 0, 0, 0,
+                                                new LeafPosition(this, vecAreaInfo.size() - 1), false));
+                    returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
+                                                    new LeafPosition(this, -1), true));
+                    returnList.add(new KnuthGlue((iTempStart - iThisStart - 1) * letterSpaceIPD.opt,
+                                                 (iTempStart - iThisStart - 1) * (letterSpaceIPD.max - letterSpaceIPD.opt),
+                                                 (iTempStart - iThisStart - 1) * (letterSpaceIPD.opt - letterSpaceIPD.min),
+                                                 new LeafPosition(this, -1), true));
+                    returnList.add(new KnuthBox(0, 0, 0, 0,
+                                                new LeafPosition(this, -1), true));
+                    iNextStart = iTempStart;
+                }
+            }
+        } // end of while
+        setFinished(true);
+        if (returnList.size() > 0) {
+            return returnList;
+        } else {
+            return null;
+        }
+    }
+
+    public int getWordSpaceIPD() {
+        return wordSpaceIPD.opt;
+    }
+
+    public KnuthElement addALetterSpaceTo(KnuthElement element) {
+        LeafPosition pos = (LeafPosition)element.getPosition();
+        AreaInfo ai = (AreaInfo)vecAreaInfo.get(pos.getLeafPos());
+        ai.iLScount ++;
+        ai.ipdArea.add(letterSpaceIPD);
+        vecAreaInfo.set(pos.getLeafPos(), ai);
+        if (letterSpaceIPD.min == letterSpaceIPD.max) {
+            return new KnuthBox(ai.ipdArea.opt, 0, 0, 0, pos, false);
+        } else {
+            return new KnuthGlue(ai.iLScount * letterSpaceIPD.opt,
+                                 ai.iLScount * (letterSpaceIPD.max - letterSpaceIPD.opt),
+                                 ai.iLScount * (letterSpaceIPD.opt - letterSpaceIPD.min),
+                                 new LeafPosition(this, -1), true);
+        }
+    }
+
+    public void hyphenate(Position pos, HyphContext hc) {
+        AreaInfo ai = (AreaInfo) vecAreaInfo.get(((LeafPosition)pos).getLeafPos());
+        int iStartIndex = ai.iStartIndex;
+        int iStopIndex;
+        boolean bNothingChanged = true;
+
+        while (iStartIndex < ai.iBreakIndex) {
+            MinOptMax newIPD = new MinOptMax(0);
+            boolean bHyphenFollows;
+
+            if (hc.hasMoreHyphPoints()
+                && (iStopIndex = iStartIndex + hc.getNextHyphPoint()) <= ai.iBreakIndex) {
+                // iStopIndex is the index of the first character after an hyphenation point
+                bHyphenFollows = true;
+            } else {
+                // there are no more hyphenation points,
+                // or the next one is after ai.iBreakIndex
+                bHyphenFollows = false;
+                iStopIndex = ai.iBreakIndex;
+            }
+
+            hc.updateOffset(iStopIndex - iStartIndex);
+
+            for (int i = iStartIndex; i < iStopIndex; i++) {
+                char c = textArray[i];
+                newIPD.add(new MinOptMax(foText.textInfo.fs.getCharWidth(c)));
+            }
+            // add letter spaces
+            boolean bIsWordEnd = iStopIndex == ai.iBreakIndex
+                                 && ai.iLScount < (ai.iBreakIndex - ai.iStartIndex);
+            newIPD.add(MinOptMax.multiply(letterSpaceIPD, (bIsWordEnd?
+                                                             (iStopIndex - iStartIndex - 1):
+                                                             (iStopIndex - iStartIndex))));
+
+            if (!(bNothingChanged
+                  && iStopIndex == ai.iBreakIndex 
+                  && bHyphenFollows == false)) {
+                // the new AreaInfo object is not equal to the old one
+                if (changeList == null) {
+                    changeList = new LinkedList();
+                }
+                changeList.add(new PendingChange(new AreaInfo((short)iStartIndex, (short)iStopIndex,
+                                                              (short)0, (short)(bIsWordEnd? (iStopIndex - iStartIndex - 1): (iStopIndex - iStartIndex)),
+                                                              newIPD,
+                                                              bHyphenFollows),
+                                                 ((LeafPosition)pos).getLeafPos()));
+                bNothingChanged = false;
+            }
+            iStartIndex = iStopIndex;
+        }
+        if (!bChanged && !bNothingChanged) {
+            bChanged = true;
+        }
+    }
+
+    public boolean applyChanges(List oldList) {
+        setFinished(false);
+
+        if (changeList != null) {
+            int iAddedAI = 0;
+            int iRemovedAI = 0;
+            int iOldIndex = -1;
+            PendingChange currChange = null;
+            ListIterator changeListIterator = changeList.listIterator();
+            while (changeListIterator.hasNext()) {
+                currChange = (PendingChange)changeListIterator.next();
+                if (currChange.index != iOldIndex) {
+                    iRemovedAI ++;
+                    iAddedAI ++;
+                    iOldIndex = currChange.index;
+                    vecAreaInfo.remove(currChange.index + iAddedAI - iRemovedAI);
+                    vecAreaInfo.add(currChange.index + iAddedAI - iRemovedAI, currChange.ai);
+                } else {
+                    iAddedAI ++;
+                    vecAreaInfo.add(currChange.index + iAddedAI - iRemovedAI, currChange.ai);
+                }
+            }
+            changeList.clear();
+        }
+
+        iReturnedIndex = 0;
+        return bChanged;
+    }
+
+    public LinkedList getChangedKnuthElements(List oldList, int flaggedPenalty, int alignment) {
+        if (isFinished()) {
+            return null;
+        }
+
+        LinkedList returnList = new LinkedList();
+
+        while (iReturnedIndex < vecAreaInfo.size()) {
+            AreaInfo ai = (AreaInfo)vecAreaInfo.get(iReturnedIndex);
+            if (ai.iWScount == 0) {
+                // ai refers either to a word or a word fragment
+                if (letterSpaceIPD.min == letterSpaceIPD.max) {
+                    returnList.add(new KnuthBox(ai.ipdArea.opt, 0, 0, 0,
+                                                new LeafPosition(this, iReturnedIndex), false));
+                } else {
+                    returnList.add(new KnuthBox(ai.ipdArea.opt - ai.iLScount * letterSpaceIPD.opt, 0, 0, 0, 
+                                                new LeafPosition(this, iReturnedIndex), false));
+                    returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
+                                                    new LeafPosition(this, -1), true));
+                    returnList.add(new KnuthGlue(ai.iLScount * letterSpaceIPD.opt,
+                                                 ai.iLScount * (letterSpaceIPD.max - letterSpaceIPD.opt),
+                                                 ai.iLScount * (letterSpaceIPD.opt - letterSpaceIPD.min),
+                                                 new LeafPosition(this, -1), true));
+                    returnList.add(new KnuthBox(0, 0, 0, 0,
+                                                new LeafPosition(this, -1), true));
+                }
+                if (ai.bHyphenated) {
+                    returnList.add(new KnuthPenalty(hyphIPD, flaggedPenalty, true,
+                                                    new LeafPosition(this, -1), false));
+                }
+                iReturnedIndex ++;
+            } else {
+                // ai refers to a space
+                switch (alignment) {
+                    case CENTER : returnList.add(new KnuthGlue(0, 3 * wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, iReturnedIndex), false));
+                                  returnList.add(new KnuthPenalty(0, 0, false,
+                                                                  new LeafPosition(this, -1), true));
+                                  returnList.add(new KnuthGlue(wordSpaceIPD.opt, - 6 * wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, -1), true));
+                                  returnList.add(new KnuthBox(0, 0, 0, 0,
+                                                              new LeafPosition(this, -1), true));
+                                  returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
+                                                                  new LeafPosition(this, -1), true));
+                                  returnList.add(new KnuthGlue(0, 3 * wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, -1), true));
+                                  iReturnedIndex ++;
+                                  break;
+                    case START  : // fall through
+                    case END    : returnList.add(new KnuthGlue(0, 3 * wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, iReturnedIndex), false));
+                                  returnList.add(new KnuthPenalty(0, 0, false,
+                                                                  new LeafPosition(this, -1), true));
+                                  returnList.add(new KnuthGlue(wordSpaceIPD.opt, - 3 * wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, -1), true));
+                                  iReturnedIndex ++;
+                                  break;
+                    case JUSTIFY: returnList.add(new KnuthGlue(wordSpaceIPD.opt, wordSpaceIPD.max - wordSpaceIPD.opt, wordSpaceIPD.opt - wordSpaceIPD.min,
+                                                               new LeafPosition(this, iReturnedIndex), false));
+                                  iReturnedIndex ++;
+                                  break;
+
+                    default:      returnList.add(new KnuthGlue(wordSpaceIPD.opt, wordSpaceIPD.max - wordSpaceIPD.opt, 0,
+                                                               new LeafPosition(this, iReturnedIndex), false));
+                                  iReturnedIndex ++;
+                }
+            }
+        } // end of while
+        setFinished(true);
+        return returnList;
+    }
+
+    public void getWordChars(StringBuffer sbChars, Position pos) {
+        int iLeafValue = ((LeafPosition)pos).getLeafPos();
+        if (iLeafValue != -1) {
+            AreaInfo ai = (AreaInfo) vecAreaInfo.get(iLeafValue);
+            sbChars.append(new String(textArray, ai.iStartIndex, ai.iBreakIndex - ai.iStartIndex));
+        }
+    }
 }
 
cvs server: I know nothing about layoutmgr/LeafNodeLaoyutManager.java
Index: layoutmgr/CharacterLayoutManager.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/layoutmgr/CharacterLayoutManager.java,v
retrieving revision 1.1
diff -w -u -r1.1 CharacterLayoutManager.java
--- layoutmgr/CharacterLayoutManager.java	13 Aug 2004 12:29:51 -0000	1.1
+++ layoutmgr/CharacterLayoutManager.java	24 Aug 2004 14:38:47 -0000
@@ -19,13 +19,24 @@
 package org.apache.fop.layoutmgr;
 
 import org.apache.fop.fo.flow.Character;
+import org.apache.fop.fo.TextInfo;
 import org.apache.fop.area.inline.InlineArea;
+import org.apache.fop.area.Trait;
+import org.apache.fop.traits.MinOptMax;
+import org.apache.fop.traits.SpaceVal;
+
+import java.util.List;
+import java.util.LinkedList;
 
 /**
  * LayoutManager for the fo:character formatting object
  */
 public class CharacterLayoutManager extends LeafNodeLayoutManager {
 
+    private MinOptMax letterSpaceIPD;
+    private int hyphIPD;
+    private TextInfo textInfo;
+
     /**
      * Constructor
      *
@@ -36,6 +47,12 @@
         super(node);
         InlineArea inline = getCharacterInlineArea(node);
         setCurrentArea(inline);
+
+        textInfo = node.getPropertyManager().getTextLayoutProps(
+                        node.getFOInputHandler().getFontInfo());
+        SpaceVal ls = textInfo.letterSpacing;
+        letterSpaceIPD = new MinOptMax(ls.getSpace().min, ls.getSpace().opt, ls.getSpace().max);
+        hyphIPD = textInfo.fs.getCharWidth(textInfo.hyphChar);
     }
 
     private InlineArea getCharacterInlineArea(Character node) {
@@ -44,5 +61,187 @@
             new org.apache.fop.area.inline.Character(str.charAt(0));
         return ch;
     }
+
+    /**
+     * Offset this area.
+     * Offset the inline area in the bpd direction when adding the
+     * inline area.
+     * This is used for vertical alignment.
+     * Subclasses should override this if necessary.
+     * @param context the layout context used for adding the area
+     */
+    protected void offsetArea(LayoutContext context) {
+        int bpd = curArea.getHeight();
+        switch (alignment) {
+            case VerticalAlign.MIDDLE:
+                curArea.setOffset(context.getBaseline() - bpd / 2 /* - fontLead/2 */);
+            break;
+            case VerticalAlign.TOP:
+                //curArea.setOffset(0);
+            break;
+            case VerticalAlign.BOTTOM:
+                curArea.setOffset(context.getLineHeight() - bpd);
+            break;
+            case VerticalAlign.BASELINE:
+            default:
+                curArea.setOffset(context.getBaseline());
+            break;
+        }
+    }
+
+    public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
+        MinOptMax ipd;
+        curArea = get(context);
+        LinkedList returnList = new LinkedList();
+
+        if (curArea == null) {
+            setFinished(true);
+            return null;
+        }
+
+        ipd = new MinOptMax(textInfo.fs.getCharWidth(
+                            ((org.apache.fop.area.inline.Character)curArea).getChar().charAt(0)));
+
+        curArea.setIPD(ipd.opt);
+        curArea.setWidth(ipd.opt);
+        curArea.setHeight(textInfo.fs.getAscender()
+                          - textInfo.fs.getDescender());
+
+        // offset is set in the offsetArea() method
+        //curArea.setOffset(textInfo.fs.getAscender());
+        //curArea.setOffset(context.getBaseline()); 
+
+        curArea.addTrait(Trait.FONT_NAME, textInfo.fs.getFontName());
+        curArea.addTrait(Trait.FONT_SIZE,
+                         new Integer(textInfo.fs.getFontSize()));
+        curArea.addTrait(Trait.COLOR, textInfo.color);
+
+        int bpd = curArea.getHeight();
+        int lead = 0;
+        int total = 0;
+        int middle = 0;
+        switch (alignment) {
+            case VerticalAlign.MIDDLE  : middle = bpd / 2 ;
+                                         lead = bpd / 2 ;
+                                         break;
+            case VerticalAlign.TOP     : total = bpd;
+                                         break;
+            case VerticalAlign.BOTTOM  : total = bpd;
+                                         break;
+            case VerticalAlign.BASELINE:
+            default:                     lead = bpd;
+                                         break;
+        }
+
+        // create the AreaInfo object to store the computed values
+        areaInfo = new AreaInfo((short)0, ipd, false,
+                                lead, total, middle);
+
+        // node is a fo:Character
+        if (letterSpaceIPD.min == letterSpaceIPD.max) {
+            // constant letter space, only return a box
+            returnList.add(new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, areaInfo.total, areaInfo.middle,
+                                        new LeafPosition(this, 0), false));
+        } else {
+            // adjustable letter space, return a sequence of elements;
+            // at the moment the character is supposed to have no letter spaces,
+            // but returning this sequence allows us to change only one element
+            // if addALetterSpaceTo() is called
+            returnList.add(new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, areaInfo.total, areaInfo.middle,
+                                        new LeafPosition(this, 0), false));
+            returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
+                                            new LeafPosition(this, -1), true));
+            returnList.add(new KnuthGlue(0, 0, 0,
+                                         new LeafPosition(this, -1), true));
+            returnList.add(new KnuthBox(0, 0, 0, 0,
+                                        new LeafPosition(this, -1), true));
+        }
+
+        setFinished(true);
+        return returnList;
+    }
+
+    public void getWordChars(StringBuffer sbChars, Position bp) {
+        sbChars.append(((org.apache.fop.area.inline.Character)curArea).getChar());
+    }
+
+    public KnuthElement addALetterSpaceTo(KnuthElement element) {
+        areaInfo.iLScount ++;
+        areaInfo.ipdArea.add(letterSpaceIPD);
+
+        if (letterSpaceIPD.min == letterSpaceIPD.max) {
+            // constant letter space, return a new box
+            return new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, areaInfo.total, areaInfo.middle,
+                                new LeafPosition(this, 0), false);
+        } else {
+            // adjustable letter space, return a new glue
+            return new KnuthGlue(letterSpaceIPD.opt,
+                                 letterSpaceIPD.max - letterSpaceIPD.opt,
+                                 letterSpaceIPD.opt - letterSpaceIPD.min,
+                                 new LeafPosition(this, -1), true);
+        }
+    }
+
+    public void hyphenate(Position pos, HyphContext hc) {
+        if (hc.getNextHyphPoint() == 1) {
+            // the character ends a syllable
+            areaInfo.bHyphenated = true;
+            bSomethingChanged = true;
+        } else {
+            // hc.getNextHyphPoint() returned -1 (no more hyphenation points)
+            // or a number > 1;
+            // the character does not end a syllable
+        }
+        hc.updateOffset(1);
+    }
+
+    public boolean applyChanges(List oldList) {
+        setFinished(false);
+        if (bSomethingChanged) {
+            // there is nothing to do, eventual changes have already been applied
+            // in the hyphenate() method
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public LinkedList getChangedKnuthElements(List oldList, int flaggedPenalty, int alignment) {
+        if (isFinished()) {
+            return null;
+        }
+
+        LinkedList returnList = new LinkedList();
+
+        if (letterSpaceIPD.min == letterSpaceIPD.max || areaInfo.iLScount == 0) {
+            // constant letter space, or no letter space
+            returnList.add(new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, areaInfo.total, areaInfo.middle,
+                                        new LeafPosition(this, 0), false));
+            if (areaInfo.bHyphenated) {
+                returnList.add(new KnuthPenalty(hyphIPD, flaggedPenalty, true,
+                                                new LeafPosition(this, -1), false));
+            }
+        } else {
+            // adjustable letter space
+            returnList.add(new KnuthBox(areaInfo.ipdArea.opt - areaInfo.iLScount * letterSpaceIPD.opt, areaInfo.lead, areaInfo.total, areaInfo.middle,
+                                        new LeafPosition(this, 0), false));
+            returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
+                                            new LeafPosition(this, -1), true));
+            returnList.add(new KnuthGlue(areaInfo.iLScount * letterSpaceIPD.opt,
+                                         areaInfo.iLScount * letterSpaceIPD.max - letterSpaceIPD.opt,
+                                         areaInfo.iLScount * letterSpaceIPD.opt - letterSpaceIPD.min,
+                                         new LeafPosition(this, -1), true));
+            returnList.add(new KnuthBox(0, 0, 0, 0,
+                                        new LeafPosition(this, -1), true));
+            if (areaInfo.bHyphenated) {
+                returnList.add(new KnuthPenalty(hyphIPD, flaggedPenalty, true,
+                                                new LeafPosition(this, -1), false));
+            }
+        }
+
+        setFinished(true);
+        return returnList;
+    }
+
 }
 
Index: layoutmgr/LeaderLayoutManager.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/layoutmgr/LeaderLayoutManager.java,v
retrieving revision 1.1
diff -w -u -r1.1 LeaderLayoutManager.java
--- layoutmgr/LeaderLayoutManager.java	16 Aug 2004 04:11:42 -0000	1.1
+++ layoutmgr/LeaderLayoutManager.java	24 Aug 2004 14:38:48 -0000
@@ -30,6 +30,9 @@
 import org.apache.fop.fonts.Font;
 import org.apache.fop.traits.MinOptMax;
 
+import java.util.List;
+import java.util.LinkedList;
+
 /**
  * LayoutManager for the fo:leader formatting object
  */
@@ -137,4 +140,88 @@
         }
         return leaderArea;
      }
+
+    public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
+        MinOptMax ipd;
+        curArea = get(context);
+        LinkedList returnList = new LinkedList();
+
+        if (curArea == null) {
+            setFinished(true);
+            return null;
+        }
+
+        ipd = getAllocationIPD(context.getRefIPD());
+
+        int bpd = curArea.getHeight();
+        int lead = 0;
+        int total = 0;
+        int middle = 0;
+        switch (alignment) {
+            case VerticalAlign.MIDDLE  : middle = bpd / 2 ;
+                                         lead = bpd / 2 ;
+                                         break;
+            case VerticalAlign.TOP     : total = bpd;
+                                         break;
+            case VerticalAlign.BOTTOM  : total = bpd;
+                                         break;
+            case VerticalAlign.BASELINE:
+            default:                     lead = bpd;
+                                         break;
+        }
+
+        // create the AreaInfo object to store the computed values
+        areaInfo = new AreaInfo((short)0, ipd, false,
+                                lead, total, middle);
+
+        // node is a fo:Leader
+        returnList.add(new KnuthBox(0, areaInfo.lead, areaInfo.total, areaInfo.middle,
+                                    new LeafPosition(this, -1), true));
+        returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
+                                        new LeafPosition(this, -1), true));
+        returnList.add(new KnuthGlue(areaInfo.ipdArea.opt, areaInfo.ipdArea.max - areaInfo.ipdArea.opt, areaInfo.ipdArea.opt - areaInfo.ipdArea.min, 
+                                     new LeafPosition(this, 0), false));
+        returnList.add(new KnuthBox(0, areaInfo.lead, areaInfo.total, areaInfo.middle,
+                                    new LeafPosition(this, -1), true));
+
+        setFinished(true);
+        return returnList;
+    }
+
+    public KnuthElement addALetterSpaceTo(KnuthElement element) {
+        // return the unchanged glue object
+        return new KnuthGlue(areaInfo.ipdArea.opt, areaInfo.ipdArea.max - areaInfo.ipdArea.opt, areaInfo.ipdArea.opt - areaInfo.ipdArea.min, 
+                             new LeafPosition(this, 0), false);
+    }
+
+    public void hyphenate(Position pos, HyphContext hc) {
+        // use the AbstractLayoutManager.hyphenate() null implementation
+        super.hyphenate(pos, hc);
+    }
+
+    public boolean applyChanges(List oldList) {
+        setFinished(false);
+        return false;
+    }
+
+    public LinkedList getChangedKnuthElements(List oldList, int flaggedPenalty, int alignment) {
+        if (isFinished()) {
+            return null;
+        }
+
+        LinkedList returnList = new LinkedList();
+
+        returnList.add(new KnuthBox(0, areaInfo.lead, areaInfo.total, areaInfo.middle,
+                                    new LeafPosition(this, -1), true));
+        returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
+                                        new LeafPosition(this, -1), true));
+        returnList.add(new KnuthGlue(areaInfo.ipdArea.opt, areaInfo.ipdArea.max - areaInfo.ipdArea.opt, areaInfo.ipdArea.opt - areaInfo.ipdArea.min, 
+                                     new LeafPosition(this, 0), false));
+        returnList.add(new KnuthBox(0, areaInfo.lead, areaInfo.total, areaInfo.middle,
+                                    new LeafPosition(this, -1), true));
+
+        setFinished(true);
+        return returnList;
+    }
+
 }
Index: fo/PropertyManager.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/fo/PropertyManager.java,v
retrieving revision 1.32
diff -w -u -r1.32 PropertyManager.java
--- fo/PropertyManager.java	13 Aug 2004 09:05:15 -0000	1.32
+++ fo/PropertyManager.java	24 Aug 2004 14:38:51 -0000
@@ -473,15 +473,32 @@
             textInfo.wrapOption = propertyList.get(PR_WRAP_OPTION).getEnum();
             textInfo.bWrap = (textInfo.wrapOption == Constants.WRAP);
 
+            // if word-spacing or letter-spacing is "normal", convert it
+            // into a suitable MinOptMax value
             Property wordSpacing = propertyList.get(PR_WORD_SPACING);
+            Property letterSpacing = propertyList.get(PR_LETTER_SPACING);
             if (wordSpacing.getEnum() == NORMAL) {
-                textInfo.wordSpacing = new SpaceVal(new MinOptMax(0), true, true, 0);
+                if (letterSpacing.getEnum() == NORMAL) {
+                    // letter spaces are set to zero (or use different values?)
+                    textInfo.letterSpacing = new SpaceVal(new MinOptMax(0), true, true, 0);
+                } else {
+                    textInfo.letterSpacing = new SpaceVal(letterSpacing.getSpace());
+                }
+                // give word spaces the possibility to shrink by a third,
+                // and stretch by a half;
+                int spaceCharIPD = textInfo.fs.getCharWidth(' ');
+                textInfo.wordSpacing = new SpaceVal(MinOptMax.add(new MinOptMax(-spaceCharIPD / 3, 0, spaceCharIPD / 2),
+                                                                  MinOptMax.multiply(textInfo.letterSpacing.getSpace(), 2)),
+                                                    true, true, 0);
             } else {
                 textInfo.wordSpacing = new SpaceVal(wordSpacing.getSpace());
+                if (letterSpacing.getEnum() == NORMAL) {
+                    // letter spaces are set to zero (or use different values?)
+                    textInfo.letterSpacing = new SpaceVal(new MinOptMax(0), true, true, 0);
+                } else {
+                    textInfo.letterSpacing = new SpaceVal(letterSpacing.getSpace());
+                }
             }
-
-            /* textInfo.letterSpacing =
-               new SpaceVal(propertyList.get("letter-spacing").getSpace());*/
 
             textInfo.whiteSpaceCollapse =
               propertyList.get(PR_WHITE_SPACE_COLLAPSE).getEnum();
Index: area/inline/TextArea.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/area/inline/TextArea.java,v
retrieving revision 1.4
diff -w -u -r1.4 TextArea.java
--- area/inline/TextArea.java	25 Apr 2004 04:45:27 -0000	1.4
+++ area/inline/TextArea.java	24 Aug 2004 14:38:51 -0000
@@ -27,7 +27,8 @@
      * The text for this inline area
      */
     protected String text;
-    private int iTextSpaceAdjust = 0;
+    private int iTextWordSpaceAdjust = 0;
+    private int iTextLetterSpaceAdjust = 0;
 
     /**
      * Create a text inline area
@@ -54,21 +55,38 @@
     }
 
     /**
-     * Get text space adjust.
+     * Get text word space adjust.
      *
-     * @return the text space adjustment
+     * @return the text word space adjustment
      */
-    public int getTextSpaceAdjust() {
-        return iTextSpaceAdjust;
+    public int getTextWordSpaceAdjust() {
+        return iTextWordSpaceAdjust;
     }
 
     /**
-     * Set text space adjust.
+     * Set text word space adjust.
      *
-     * @param iTSadjust the text space adjustment
+     * @param iTWSadjust the text word space adjustment
      */
-    public void setTextSpaceAdjust(int iTSadjust) {
-        iTextSpaceAdjust = iTSadjust;
+    public void setTextWordSpaceAdjust(int iTWSadjust) {
+        iTextWordSpaceAdjust = iTWSadjust;
+    }
+    /**
+     * Get text letter space adjust.
+     *
+     * @return the text letter space adjustment
+     */
+    public int getTextLetterSpaceAdjust() {
+        return iTextLetterSpaceAdjust;
+    }
+
+    /**
+     * Set text letter space adjust.
+     *
+     * @param iTLSadjust the text letter space adjustment
+     */
+    public void setTextLetterSpaceAdjust(int iTLSadjust) {
+        iTextLetterSpaceAdjust = iTLSadjust;
     }
 }
 
Index: area/inline/Character.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/area/inline/Character.java,v
retrieving revision 1.4
diff -w -u -r1.4 Character.java
--- area/inline/Character.java	27 Feb 2004 17:40:58 -0000	1.4
+++ area/inline/Character.java	24 Aug 2004 14:38:52 -0000
@@ -23,15 +23,19 @@
  * This inline area holds a single character.
  */
 public class Character extends InlineArea {
-    private char character;
+    // use a String instead of a character because if this character
+    // ends a syllable the hyphenation character must be added
+    private String character;
+    private int iTextWordSpaceAdjust = 0;
+    private int iTextLetterSpaceAdjust = 0;
 
     /**
-     * Create a new characater inline area with the given character.
+     * Create a new character inline area with the given character.
      *
      * @param ch the character for this inline area
      */
     public Character(char ch) {
-        character = ch;
+        character = new String() + ch;
     }
 
     /**
@@ -39,8 +43,55 @@
      *
      * @return the character
      */
-    public char getChar() {
+    public String getChar() {
         return character;
+    }
+
+    /**
+     * Add the hyphenation character and its length.
+     *
+     * @param hyphChar the hyphenation character
+     * @param hyphSize the size of the hyphenation character
+     */
+    public void addHyphen(char hyphChar, int hyphSize) {
+        character += hyphChar;
+        this.setIPD(this.getIPD() + hyphSize);
+    }
+
+    /**
+     * Get text word space adjust.
+     *
+     * @return the text word space adjustment
+     */
+    public int getTextWordSpaceAdjust() {
+        return iTextWordSpaceAdjust;
+    }
+
+    /**
+     * Set text word space adjust.
+     *
+     * @param iTWSadjust the text word space adjustment
+     */
+    public void setTextWordSpaceAdjust(int iTWSadjust) {
+        iTextWordSpaceAdjust = iTWSadjust;
+    }
+
+    /**
+     * Get text letter space adjust.
+     *
+     * @return the text letter space adjustment
+     */
+    public int getTextLetterSpaceAdjust() {
+        return iTextLetterSpaceAdjust;
+    }
+
+    /**
+     * Set text letter space adjust.
+     *
+     * @param iTLSadjust the text letter space adjustment
+     */
+    public void setTextLetterSpaceAdjust(int iTLSadjust) {
+        iTextLetterSpaceAdjust = iTLSadjust;
     }
 }
 
Index: render/pdf/PDFRenderer.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/render/pdf/PDFRenderer.java,v
retrieving revision 1.46
diff -w -u -r1.46 PDFRenderer.java
--- render/pdf/PDFRenderer.java	9 Jul 2004 17:27:12 -0000	1.46
+++ render/pdf/PDFRenderer.java	24 Aug 2004 14:38:57 -0000
@@ -906,6 +906,70 @@
      * @see org.apache.fop.render.Renderer#renderCharacter(Character)
      */
     public void renderCharacter(Character ch) {
+        StringBuffer pdf = new StringBuffer();
+
+        String name = (String) ch.getTrait(Trait.FONT_NAME);
+        int size = ((Integer) ch.getTrait(Trait.FONT_SIZE)).intValue();
+
+        // This assumes that *all* CIDFonts use a /ToUnicode mapping
+        Typeface f = (Typeface) fontInfo.getFonts().get(name);
+        boolean useMultiByte = f.isMultiByte();
+
+        // String startText = useMultiByte ? "<FEFF" : "(";
+        String startText = useMultiByte ? "<" : "(";
+        String endText = useMultiByte ? "> " : ") ";
+
+        updateFont(name, size, pdf);
+        ColorType ct = (ColorType) ch.getTrait(Trait.COLOR);
+        if (ct != null) {
+            updateColor(ct, true, pdf);
+        }
+
+        // word.getOffset() = only height of text itself
+        // currentBlockIPPosition: 0 for beginning of line; nonzero
+        //  where previous line area failed to take up entire allocated space
+        int rx = currentBlockIPPosition + ipMarginOffset;
+        int bl = currentBPPosition + bpMarginOffset + ch.getOffset();
+
+/*        System.out.println("Text = " + ch.getTextArea() +
+            "; text width: " + ch.getWidth() +
+            "; BlockIP Position: " + currentBlockIPPosition +
+            "; currentBPPosition: " + currentBPPosition +
+            "; offset: " + ch.getOffset());
+*/
+        // Set letterSpacing
+        //float ls = fs.getLetterSpacing() / this.currentFontSize;
+        //pdf.append(ls).append(" Tc\n");
+
+        if (!textOpen || bl != prevWordY) {
+            closeText();
+
+            pdf.append("1 0 0 -1 " + (rx / 1000f) + " " + (bl / 1000f) + " Tm "
+                       + (ch.getTextLetterSpaceAdjust() / 1000f) + " Tc "
+                       + (ch.getTextWordSpaceAdjust() / 1000f) + " Tw [" + startText);
+            prevWordY = bl;
+            textOpen = true;
+        } else {
+                closeText();
+
+                pdf.append("1 0 0 -1 " + (rx / 1000f) + " " + (bl / 1000f) + " Tm "
+                           + (ch.getTextLetterSpaceAdjust() / 1000f) + " Tc "
+                           + (ch.getTextWordSpaceAdjust() / 1000f) + " Tw [" + startText);
+                textOpen = true;
+        }
+        prevWordWidth = ch.getWidth();
+        prevWordX = rx;
+
+        String s = ch.getChar();
+
+
+        FontMetrics metrics = fontInfo.getMetricsFor(name);
+        Font fs = new Font(name, metrics, size);
+        escapeText(s, fs, useMultiByte, pdf);
+        pdf.append(endText);
+
+        currentStream.add(pdf.toString());
+
         super.renderCharacter(ch);
     }
 
@@ -952,14 +1016,16 @@
             closeText();
 
             pdf.append("1 0 0 -1 " + (rx / 1000f) + " " + (bl / 1000f) + " Tm "
-                       + (text.getTextSpaceAdjust() / 1000f) + " Tw [" + startText);
+                       + (text.getTextLetterSpaceAdjust() / 1000f) + " Tc "
+                       + (text.getTextWordSpaceAdjust() / 1000f) + " Tw [" + startText);
             prevWordY = bl;
             textOpen = true;
         } else {
                 closeText();
 
                 pdf.append("1 0 0 -1 " + (rx / 1000f) + " " + (bl / 1000f) + " Tm "
-                           + (text.getTextSpaceAdjust() / 1000f) + " Tw [" + startText);
+                       + (text.getTextLetterSpaceAdjust() / 1000f) + " Tc "
+                       + (text.getTextWordSpaceAdjust() / 1000f) + " Tw [" + startText);
                 textOpen = true;
         }
         prevWordWidth = text.getWidth();
Index: render/xml/XMLRenderer.java
===================================================================
RCS file: /home/cvspublic/xml-fop/src/java/org/apache/fop/render/xml/XMLRenderer.java,v
retrieving revision 1.24
diff -w -u -r1.24 XMLRenderer.java
--- render/xml/XMLRenderer.java	9 Jul 2004 20:05:41 -0000	1.24
+++ render/xml/XMLRenderer.java	24 Aug 2004 14:38:58 -0000
@@ -427,7 +427,8 @@
         if (map != null) {
             prop = " props=\"" + getPropString(map) + "\"";
         }
-        writeElement("<text tsadjust=\"" + text.getTextSpaceAdjust() + "\""
+       writeElement("<text twsadjust=\"" + text.getTextWordSpaceAdjust() + "\""
+             + " tlsadjust=\"" + text.getTextLetterSpaceAdjust() + "\""
              + prop + ">" + text.getTextArea() + "</text>");
         super.renderText(text);
     }
