diff options
Diffstat (limited to 'src/java')
15 files changed, 2459 insertions, 349 deletions
diff --git a/src/java/org/apache/fop/area/inline/Character.java b/src/java/org/apache/fop/area/inline/Character.java index 9630614e0..bed1b194a 100644 --- a/src/java/org/apache/fop/area/inline/Character.java +++ b/src/java/org/apache/fop/area/inline/Character.java @@ -23,15 +23,19 @@ package org.apache.fop.area.inline; * 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 @@ public class Character extends InlineArea { * * @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; + } } diff --git a/src/java/org/apache/fop/area/inline/TextArea.java b/src/java/org/apache/fop/area/inline/TextArea.java index 110eaa03f..86bf35838 100644 --- a/src/java/org/apache/fop/area/inline/TextArea.java +++ b/src/java/org/apache/fop/area/inline/TextArea.java @@ -27,7 +27,8 @@ public class TextArea extends InlineArea { * 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 @@ public class TextArea extends InlineArea { } /** - * 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; } } diff --git a/src/java/org/apache/fop/fo/PropertyManager.java b/src/java/org/apache/fop/fo/PropertyManager.java index c3ab86847..51fbaf307 100644 --- a/src/java/org/apache/fop/fo/PropertyManager.java +++ b/src/java/org/apache/fop/fo/PropertyManager.java @@ -19,6 +19,7 @@ package org.apache.fop.fo; // FOP +import org.apache.fop.apps.FOPException; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fo.properties.Property; @@ -472,28 +473,47 @@ public class PropertyManager implements Constants { 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(); - - textInfo.lineHeight = this.propertyList.get( - PR_LINE_HEIGHT).getLength().getValue(); - + textInfo.whiteSpaceCollapse + = propertyList.get(PR_WHITE_SPACE_COLLAPSE).getEnum(); + textInfo.lineHeight + = this.propertyList.get(PR_LINE_HEIGHT).getLength().getValue(); textInfo.textTransform - = this.propertyList.get(PR_TEXT_TRANSFORM).getEnum(); - - textInfo.hyphChar = this.propertyList.get( - PR_HYPHENATION_CHARACTER).getCharacter(); - + = this.propertyList.get(PR_TEXT_TRANSFORM).getEnum(); + textInfo.hyphChar + = this.propertyList.get(PR_HYPHENATION_CHARACTER).getCharacter(); } return textInfo; } diff --git a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java index 7ce205e6c..6a4c043e7 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java @@ -33,6 +33,7 @@ import org.apache.commons.logging.LogFactory; import java.util.List; import java.util.ArrayList; +import java.util.LinkedList; import java.util.ListIterator; import java.util.Map; @@ -284,6 +285,44 @@ public abstract class AbstractLayoutManager implements LayoutManager, Constants * 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; } diff --git a/src/java/org/apache/fop/layoutmgr/CharacterLayoutManager.java b/src/java/org/apache/fop/layoutmgr/CharacterLayoutManager.java index 68c11a906..d028cd684 100644 --- a/src/java/org/apache/fop/layoutmgr/CharacterLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/CharacterLayoutManager.java @@ -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,14 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { 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 +63,202 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { 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, + // possible 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; + } + } diff --git a/src/java/org/apache/fop/layoutmgr/ContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/ContentLayoutManager.java index ba89110fd..9adb9fd00 100644 --- a/src/java/org/apache/fop/layoutmgr/ContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/ContentLayoutManager.java @@ -26,6 +26,7 @@ import org.apache.fop.area.AreaTreeHandler; import org.apache.fop.area.Resolveable; import org.apache.fop.area.PageViewport; +import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -294,5 +295,34 @@ public class ContentLayoutManager implements LayoutManager { } } + 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; + } } diff --git a/src/java/org/apache/fop/layoutmgr/InlineStackingLayoutManager.java b/src/java/org/apache/fop/layoutmgr/InlineStackingLayoutManager.java index f35961eee..a6562bef7 100644 --- a/src/java/org/apache/fop/layoutmgr/InlineStackingLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/InlineStackingLayoutManager.java @@ -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 @@ public class InlineStackingLayoutManager extends AbstractLayoutManager { } 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 @@ public class InlineStackingLayoutManager extends AbstractLayoutManager { 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(); @@ -465,21 +468,37 @@ public class InlineStackingLayoutManager extends AbstractLayoutManager { 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, - true); + getContext().setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true); prevLM = childLM; } - /* If has trailing fence, resolve trailing space specs from descendants. + /* If has trailing fence, + * resolve trailing space specs from descendants. * Otherwise, propagate any trailing space specs to parent LM via * the context object. * If the last child LM called return ISLAST in the context object @@ -554,11 +573,11 @@ public class InlineStackingLayoutManager extends AbstractLayoutManager { int iAdjust = spaceRange.opt; if (dSpaceAdjust > 0.0) { // Stretch by factor - iAdjust += (int)((double)(spaceRange.max + iAdjust += (int) ((double) (spaceRange.max - spaceRange.opt) * dSpaceAdjust); } else if (dSpaceAdjust < 0.0) { // Shrink by factor - iAdjust += (int)((double)(spaceRange.opt + iAdjust += (int) ((double) (spaceRange.opt - spaceRange.min) * dSpaceAdjust); } if (iAdjust != 0) { @@ -570,5 +589,208 @@ public class InlineStackingLayoutManager extends AbstractLayoutManager { } } + 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) { + Position newPos = ((NonLeafPosition) pos).getPosition(); + newPos.getLM().getWordChars(sbChars, newPos); + } + + public void hyphenate(Position pos, HyphContext hc) { + Position newPos = ((NonLeafPosition) pos).getPosition(); + newPos.getLM().hyphenate(newPos, 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; + } + } } diff --git a/src/java/org/apache/fop/layoutmgr/LayoutContext.java b/src/java/org/apache/fop/layoutmgr/LayoutContext.java index 59c674476..5cbcb132e 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutContext.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutContext.java @@ -95,6 +95,7 @@ public class LayoutContext { 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??? diff --git a/src/java/org/apache/fop/layoutmgr/LayoutManager.java b/src/java/org/apache/fop/layoutmgr/LayoutManager.java index 97a79a67b..10da478e0 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutManager.java @@ -18,6 +18,7 @@ package org.apache.fop.layoutmgr; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -245,4 +246,18 @@ public interface LayoutManager { */ void addChildLMs(List newLMs); + 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(); } diff --git a/src/java/org/apache/fop/layoutmgr/LeaderLayoutManager.java b/src/java/org/apache/fop/layoutmgr/LeaderLayoutManager.java index 8bcbb74de..ac3f81e13 100644 --- a/src/java/org/apache/fop/layoutmgr/LeaderLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LeaderLayoutManager.java @@ -30,6 +30,9 @@ import org.apache.fop.fo.flow.Leader; 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 */ @@ -139,4 +142,103 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager { } 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; + } + } diff --git a/src/java/org/apache/fop/layoutmgr/LeafNodeLayoutManager.java b/src/java/org/apache/fop/layoutmgr/LeafNodeLayoutManager.java index 61952e9e2..d9c2fd283 100644 --- a/src/java/org/apache/fop/layoutmgr/LeafNodeLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LeafNodeLayoutManager.java @@ -23,6 +23,9 @@ import org.apache.fop.area.inline.InlineArea; import org.apache.fop.fo.FObj; import org.apache.fop.traits.MinOptMax; +import java.util.List; +import java.util.LinkedList; + /** * Base LayoutManager for leaf-node FObj, ie: ones which have no children. * These are all inline objects. Most of them cannot be split (Text is @@ -35,10 +38,36 @@ public class LeafNodeLayoutManager extends AbstractLayoutManager { * The inline area that this leafnode will add. */ protected InlineArea curArea = null; - private int alignment; + protected int alignment; private int lead; private MinOptMax ipd; + protected boolean bSomethingChanged = false; + protected AreaInfo areaInfo = null; + + /** + * Store information about the inline area + */ + protected class AreaInfo { + protected short iLScount; + protected MinOptMax ipdArea; + protected boolean bHyphenated; + protected int lead; + protected int total; + protected int middle; + + public AreaInfo(short iLS, MinOptMax ipd, boolean bHyph, + int l, int t, int m) { + iLScount = iLS; + ipdArea = ipd; + bHyphenated = bHyph; + lead = l; + total = t; + middle = m; + } + } + + /** * Create a Leaf node layout mananger. * @param node the FObj to attach to this LM. @@ -196,12 +225,11 @@ public class LeafNodeLayoutManager extends AbstractLayoutManager { * @param context the layout context for adding the area */ public void addAreas(PositionIterator posIter, LayoutContext context) { - parentLM.addChild(curArea); - addID(); offsetArea(context); widthAdjustArea(context); + parentLM.addChild(curArea); while (posIter.hasNext()) { posIter.next(); @@ -243,11 +271,13 @@ public class LeafNodeLayoutManager extends AbstractLayoutManager { */ protected void widthAdjustArea(LayoutContext context) { double dAdjust = context.getIPDAdjust(); - int width = ipd.opt; + int width = areaInfo.ipdArea.opt; if (dAdjust < 0) { - width = (int)(width + dAdjust * (ipd.opt - ipd.min)); + width = (int) (width + dAdjust * (areaInfo.ipdArea.opt + - areaInfo.ipdArea.min)); } else if (dAdjust > 0) { - width = (int)(width + dAdjust * (ipd.max - ipd.opt)); + width = (int) (width + dAdjust * (areaInfo.ipdArea.max + - areaInfo.ipdArea.opt)); } curArea.setWidth(width); } @@ -260,5 +290,83 @@ public class LeafNodeLayoutManager extends AbstractLayoutManager { public boolean canBreakBefore(LayoutContext context) { return true; } + + 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:ExternalGraphic, fo:InstreamForeignObject, + // fo:PageNumber or fo:PageNumberCitation + returnList.add(new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, + areaInfo.total, areaInfo.middle, + new LeafPosition(this, 0), false)); + setFinished(true); + return returnList; + } + + public KnuthElement addALetterSpaceTo(KnuthElement element) { + // return the unchanged box object + return new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, + areaInfo.total, areaInfo.middle, + 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(); + + // fobj is a fo:ExternalGraphic, fo:InstreamForeignObject, + // fo:PageNumber or fo:PageNumberCitation + returnList.add(new KnuthBox(areaInfo.ipdArea.opt, areaInfo.lead, + areaInfo.total, areaInfo.middle, + new LeafPosition(this, 0), true)); + + setFinished(true); + return returnList; + } } diff --git a/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java index 70e923b15..2a097ecec 100644 --- a/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LineLayoutManager.java @@ -34,6 +34,8 @@ import java.util.ListIterator; 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 @@ public class LineLayoutManager extends InlineStackingLayoutManager { 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 @@ public class LineLayoutManager extends InlineStackingLayoutManager { 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,237 @@ public class LineLayoutManager extends InlineStackingLayoutManager { // 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 @@ -143,9 +385,6 @@ public class LineLayoutManager extends InlineStackingLayoutManager { // 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(); @@ -156,185 +395,701 @@ public class LineLayoutManager extends InlineStackingLayoutManager { } prevBP = null; - 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)); + // 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) { + 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; - - // This break position doesn't fit - // TODO: If we are in nowrap, we use it as is! - if (hyphProps.hyphenate == Constants.TRUE) { - // If we are already in a hyphenation loop, then stop. - - if (inlineLC.tryHyphenate()) { - if (prevBP == null) { - vecInlineBreaks.add(bp); - prevBP = bp; - } - break; } - // Otherwise, prepare to try hyphenation - if (!bBreakOK) { - // Make sure we collect the entire word! - vecInlineBreaks.add(bp); - continue; + if (((KnuthElement) returnedList.getLast()).isBox()) { + bPrevWasKnuthBox = true; + } else { + bPrevWasKnuthBox = false; } - - inlineLC.setHyphContext( - getHyphenContext((prevBP == null) ? prev : prevBP, bp)); - if (inlineLC.getHyphContext() == null) { - if (prevBP == null) { - vecInlineBreaks.add(bp); - prevBP = bp; - } - break; - } - inlineLC.setFlags(LayoutContext.TRY_HYPHENATE, - true); - // Reset to previous acceptable break - resetBP((prevBP == null) ? prev : prevBP); + // add the new elements to the paragraph + knuthPar.addAll(returnedList); } else { - /* If we are not in justified text, we can end the line at - * prevBP. - */ - if (prevBP == null) { - vecInlineBreaks.add(bp); - prevBP = bp; - } - break; + // 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 { - // 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; - } - 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 + // 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(); + + // 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) { + // consider every hyphenation point as a legal break + findHyphenationPoints(currPar); } else { - /* Can't end line here. */ + // try with a higher threshold + maxAdjustment = 5; } - } // end of bpDim.min <= availIPD.max - // end of getNextBreakPoss!=null on current child LM - } 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. - */ - } - if (inlineLC.tryHyphenate() - && !inlineLC.getHyphContext().hasMoreHyphPoints()) { - break; + + if ((iBPcount + = findBreakingPoints(currPar, + context.getStackLimit().opt, + maxAdjustment, false)) == 0) { + // the second try failed too, try with a huge threshold + // and force the algorithm to find + // a set of breaking points + log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment + + (hyphProps.hyphenate == Constants.TRUE ? " and hyphenation" : "")); + maxAdjustment = 20; + iBPcount + = findBreakingPoints(currPar, + context.getStackLimit().opt, + maxAdjustment, true); + } + } } - } // end of while on child LM - if ((curLM = getChildLM()) == null) { - // No more content to layout! + } else { + // this method has been called before + // all line breaks are already calculated + } + + // get a break point from the list + lbp = (LineBreakPosition) breakpoints.get(iReturnedLBP ++); + if (iReturnedLBP == breakpoints.size()) { setFinished(true); } - if (bp == null) { - return null; + BreakPoss curLineBP = new BreakPoss(lbp); + curLineBP.setFlag(BreakPoss.ISLAST, isFinished()); + curLineBP.setStackingSize(new MinOptMax(lbp.lineHeight)); + return curLineBP; + } + + private int findBreakingPoints(Paragraph par, int lineWidth, + double threshold, boolean force) { + int totalWidth = 0; + int totalStretch = 0; + int totalShrink = 0; + boolean bForced = false; + + // current element in the paragraph + KnuthElement thisElement = null; + // previous element in the paragraph is a KnuthBox + boolean previousIsBox = false; + + // create an active node representing the starting point + activeList = new LinkedList(); + activeList.add(new KnuthNode(0, 0, 1, 0, 0, 0, 0, 0, 0, null)); + 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 { + // 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; + } } - if (prevBP == null) { - BreakPoss prevLineEnd = (iPrevLineEnd == 0) - ? null - : (BreakPoss) vecInlineBreaks.get(iPrevLineEnd); - if (allAreSuppressible(prevLineEnd)) { - removeAllBP(prevLineEnd); - return null; + if (activeList.size() == 0) { + if (force) { + activeList.add(lastDeactivatedNode); + bForced = true; + log.error("Could not find a set of breaking points"); } else { - prevBP = bp; + inactiveList.clear(); + return 0; + } + } + + // 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(); + } + } + } - // Choose the best break - if (!bp.isForcedBreak() && vecPossEnd.size() > 0) { - prevBP = getBestBP(vecPossEnd); + 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 (bp != prevBP) { - /** Remove BPs after prevBP - if condAllAreSuppressible returns true, - else backup child LM */ - if (condAllAreSuppressible(prevBP)) { - removeAllBP(prevBP); - } else { - reset(); + 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(); + } } } - // 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; + if (maxtb - lineLead > middlefollow) { + middlefollow = maxtb - lineLead; } - return makeLineBreak(iPrevLineEnd, availIPD, talign); + + 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 { + activeNode = null; + } + + while (activeNode != null) { + BestRecords best = new BestRecords(); + + // these are the new values that must be computed + // in order to define a new active node + int newLine = 0; + int newFitnessClass = 0; + int newWidth = 0; + int newStretch = 0; + int newShrink = 0; + double newIPDAdjust = 0; + double newDemerits = 0; + + while (activeNode != null) { + // compute the line number + newLine = activeNode.line + 1; + + // compute the adjustment ratio + int actualWidth = totalWidth - activeNode.totalWidth; + if (element.isPenalty()) { + actualWidth += element.getW(); + } + int neededAdjustment = lineWidth - actualWidth; + int maxAdjustment = 0; + if (neededAdjustment > 0) { + maxAdjustment = totalStretch - activeNode.totalStretch; + if (maxAdjustment > 0) { + newIPDAdjust + = (double) neededAdjustment / maxAdjustment; + } else { + newIPDAdjust = INFINITE_RATIO; + } + } else if (neededAdjustment < 0) { + maxAdjustment = totalShrink - activeNode.totalShrink; + if (maxAdjustment > 0) { + newIPDAdjust + = (double) neededAdjustment / maxAdjustment; + } else { + newIPDAdjust = INFINITE_RATIO; + } + } else { + // neededAdjustment == 0 + newIPDAdjust = 0; + } + if (newIPDAdjust < -1 + || (element.isPenalty() + && ((KnuthPenalty) element).getP() + == -KnuthElement.INFINITE) + && !(activeNode.position == par.indexOf(element))) { + // deactivate activeNode + 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; + } + if (activeNode.line >= newLine) { + break; + } + } // end of the inner while + + if (best.hasRecords()) { + // compute width, stratchability and shrinkability + newWidth = totalWidth; + newStretch = totalStretch; + newShrink = totalShrink; + ListIterator tempIterator + = par.listIterator(par.indexOf(element)); + while (tempIterator.hasNext()) { + KnuthElement tempElement + = (KnuthElement) tempIterator.next(); + if (tempElement.isBox()) { + break; + } else if (tempElement.isGlue()) { + newWidth += ((KnuthGlue) tempElement).getW(); + newStretch += ((KnuthGlue) tempElement).getY(); + newShrink += ((KnuthGlue) tempElement).getZ(); + } else if (((KnuthPenalty) tempElement).getP() + == -KnuthElement.INFINITE + && tempElement != element) { + break; + } + } + + // add nodes to the active nodes list + for (int i = 0; i <= 3; i++) { + if (best.notInfiniteDemerits(i) + && best.getDemerits(i) + <= (best.getMinDemerits() + + incompatibleFitnessDemerit)) { + // the nodes in activeList must be ordered + // by line number and position; + // so: + // 1) advance in the list until the end, + // or a node with a higher line number, is reached + int iStepsForward = 0; + KnuthNode tempNode; + while (activeListIterator.hasNext()) { + iStepsForward ++; + tempNode = (KnuthNode) activeListIterator.next(); + if (tempNode.line > (best.getNode(i).line + 1)) { + activeListIterator.previous(); + iStepsForward --; + break; + } + } + // 2) add the new node + activeListIterator.add + (new KnuthNode(par.indexOf(element), + best.getNode(i).line + 1, i, + newWidth, newStretch, newShrink, + best.getAdjust(i), + best.getDifference(i), + best.getDemerits(i), + best.getNode(i))); + // 3) go back + for (int j = 0; + j <= iStepsForward; + j ++) { + activeListIterator.previous(); + } + } + } + } + if (activeNode == null) { + break; + } + } // end of the outer while + } + + /** + * 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 { + break; + } + } + + // 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 ++; + } + } + 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 { + // nothing to do, element is an auxiliary KnuthElement + } + } + } + } + } + + // 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; + } + + // 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; + 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); + } + } + updateListIterator = null; + updateList.clear(); } private void resetBP(BreakPoss resetBP) { @@ -438,57 +1193,33 @@ public class LineLayoutManager extends InlineStackingLayoutManager { } } - 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) - // 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 - // quite strange! Or perhaps in that case, we say that it's several words. + 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 quite strange! + // Or perhaps in that case, we say that it's several words. // We probably should bring the hyphenation props up from the actual - // TextLM which generate the hyphenation buffer, since these properties - // inherit and could be specified on an inline or wrapper below the block - // level. - Hyphenation hyph = Hyphenator.hyphenate(hyphProps.language, - hyphProps.country, sbChars.toString(), - hyphProps.hyphenationRemainCharacterCount, - hyphProps.hyphenationPushCharacterCount); + // TextLM which generate the hyphenation buffer, + // since these properties inherit and could be specified + // on an inline or wrapper below the block level. + Hyphenation hyph + = Hyphenator.hyphenate(hyphProps.language, + hyphProps.country, sbChars.toString(), + hyphProps.hyphenationRemainCharacterCount, + hyphProps.hyphenationPushCharacterCount); // They hyph structure contains the information we need // Now start from prev: reset to that position, ask that LM to get // a Position for the first hyphenation offset. If the offset isn't in - // its characters, it returns null, but must tell how many chars it had. + // its characters, it returns null, + // but must tell how many chars it had. // Keep looking at currentBP using next hyphenation point until the - // returned size is greater than the available size or no more hyphenation - // points remain. Choose the best break. + // returned size is greater than the available size + // or no more hyphenation points remain. Choose the best break. if (hyph != null) { return new HyphContext(hyph.getHyphenationPoints()); } else { @@ -529,7 +1260,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager { LayoutManager lastLM = null; for (Iterator iter = vecInlineBreaks.listIterator(prevLineEnd); iter.hasNext();) { - BreakPoss bp = (BreakPoss)iter.next(); + BreakPoss bp = (BreakPoss) iter.next(); if (bp.getLead() > lineLead) { lineLead = bp.getLead(); } @@ -573,7 +1304,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager { if (actual.opt > targetWith) { if (actual.opt - targetWith < (actual.opt - actual.min)) { ipdAdjust = -(actual.opt - targetWith) - / (float)(actual.opt - actual.min); + / (float) (actual.opt - actual.min); realWidth = targetWith; } else { ipdAdjust = -1; @@ -582,7 +1313,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager { } else { if (targetWith - actual.opt < actual.max - actual.opt) { ipdAdjust = (targetWith - actual.opt) - / (float)(actual.max - actual.opt); + / (float) (actual.max - actual.opt); realWidth = targetWith; } else { ipdAdjust = 1; @@ -597,7 +1328,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager { switch (textalign) { case TextAlign.JUSTIFY: if (realWidth != 0) { - dAdjust = (double)(targetWith - realWidth) / realWidth; + dAdjust = (double) (targetWith - realWidth) / realWidth; } break; case TextAlign.START: @@ -612,9 +1343,6 @@ public class LineLayoutManager extends InlineStackingLayoutManager { indent = targetWith - realWidth; break; } - //System.err.println(" "); - //System.err.println("LineLayoutManager> difference to fill= " + (targetWith - realWidth)); - //System.err.println("LineLayoutManager> ipdAdjust= " + ipdAdjust + " dAdjust= " + dAdjust); LineBreakPosition lbp; lbp = new LineBreakPosition(this, @@ -635,16 +1363,21 @@ public class LineLayoutManager extends InlineStackingLayoutManager { */ 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--; } - reset(prevBP.getPosition()); + while ((LineBreakPosition) breakpoints.get(iReturnedLBP) + != (LineBreakPosition) resetPos) { + iReturnedLBP --; + } + iReturnedLBP ++; } } @@ -675,6 +1408,11 @@ public class LineLayoutManager extends InlineStackingLayoutManager { 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); @@ -682,11 +1420,49 @@ public class LineLayoutManager extends InlineStackingLayoutManager { 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 --; + // this returns the same KnuthElement + paragraphIterator.previous(); + 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)); @@ -694,6 +1470,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager { 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)); diff --git a/src/java/org/apache/fop/layoutmgr/TextLayoutManager.java b/src/java/org/apache/fop/layoutmgr/TextLayoutManager.java index 59ddcc73a..e3abe293c 100644 --- a/src/java/org/apache/fop/layoutmgr/TextLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/TextLayoutManager.java @@ -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 @@ public class TextLayoutManager extends AbstractLayoutManager { 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, - MinOptMax ipd, boolean bHyph) { + 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 @@ public class TextLayoutManager extends AbstractLayoutManager { // 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 @@ public class TextLayoutManager extends AbstractLayoutManager { /** 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,23 @@ public class TextLayoutManager extends AbstractLayoutManager { 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); } /** @@ -141,8 +183,9 @@ public class TextLayoutManager extends AbstractLayoutManager { // Skip all leading spaces for hyphenation int i; for (i = ai.iStartIndex; - i < ai.iBreakIndex && CharUtilities.isAnySpace(textArray[i]) == true; - i++) { + i < ai.iBreakIndex + && CharUtilities.isAnySpace(textArray[i]) == true; + i++) { //nop } sbChars.append(new String(textArray, i, ai.iBreakIndex - i)); @@ -159,10 +202,12 @@ public class TextLayoutManager extends AbstractLayoutManager { */ public boolean canBreakBefore(LayoutContext context) { char c = textArray[iNextStart]; - return ((c == NEWLINE) || (foText.textInfo.bWrap + return ((c == NEWLINE) + || (foText.textInfo.bWrap && (CharUtilities.isBreakableSpace(c) - || (BREAK_CHARS.indexOf(c) >= 0 && (iNextStart == 0 - || Character.isLetterOrDigit(textArray[iNextStart-1])))))); + || (BREAK_CHARS.indexOf(c) >= 0 + && (iNextStart == 0 + || Character.isLetterOrDigit(textArray[iNextStart-1])))))); } /** @@ -174,8 +219,8 @@ public class TextLayoutManager extends AbstractLayoutManager { if (prevPos != null) { // ASSERT (prevPos.getLM() == this) if (prevPos.getLM() != this) { - log.error( - "TextLayoutManager.resetPosition: " + "LM mismatch!!!"); + log.error("TextLayoutManager.resetPosition: " + + "LM mismatch!!!"); } LeafPosition tbp = (LeafPosition) prevPos; AreaInfo ai = @@ -260,7 +305,7 @@ public class TextLayoutManager extends AbstractLayoutManager { */ if (context.suppressLeadingSpace()) { for (; iNextStart < textArray.length - && textArray[iNextStart] == SPACE; iNextStart++) { + && textArray[iNextStart] == SPACE; iNextStart++) { } // If now at end, nothing to compose here! if (iNextStart >= textArray.length) { @@ -293,7 +338,7 @@ public class TextLayoutManager extends AbstractLayoutManager { ++iWScount; // Counted as word-space if (iNextStart == iThisStart - && (iFlags & BreakPoss.ISFIRST) != 0) { + && (iFlags & BreakPoss.ISFIRST) != 0) { // If possible, treat as normal inter-word space if (context.getLeadingSpace().hasSpaces()) { context.getLeadingSpace().addSpace(halfWS); @@ -330,8 +375,8 @@ public class TextLayoutManager extends AbstractLayoutManager { iFlags |= BreakPoss.ALL_ARE_SUPPRESS_AT_LB; } return makeBreakPoss(iThisStart, spaceIPD, wordIPD, - context.getLeadingSpace(), pendingSpace, iFlags, - iWScount); + context.getLeadingSpace(), pendingSpace, + iFlags, iWScount); } if (context.tryHyphenate()) { @@ -356,7 +401,8 @@ public class TextLayoutManager extends AbstractLayoutManager { if (c != SPACE) { iNextStart++; if (c != NEWLINE) { - wordIPD += foText.textInfo.fs.getCharWidth(c); + wordIPD + += foText.textInfo.fs.getCharWidth(c); } else { iFlags |= BreakPoss.FORCE; } @@ -373,8 +419,8 @@ public class TextLayoutManager extends AbstractLayoutManager { iFlags |= BreakPoss.REST_ARE_SUPPRESS_AT_LB; } return makeBreakPoss(iThisStart, spaceIPD, wordIPD, - context.getLeadingSpace(), null, iFlags, - iWScount); + context.getLeadingSpace(), null, + iFlags, iWScount); } wordIPD += foText.textInfo.fs.getCharWidth(c); // Note, if a normal non-breaking space, is it stretchable??? @@ -382,12 +428,14 @@ public class TextLayoutManager extends AbstractLayoutManager { } } return makeBreakPoss(iThisStart, spaceIPD, wordIPD, - context.getLeadingSpace(), null, iFlags, iWScount); + context.getLeadingSpace(), null, + iFlags, iWScount); } private BreakPoss makeBreakPoss(short iWordStart, MinOptMax spaceIPD, int wordDim, - SpaceSpecifier leadingSpace, SpaceSpecifier trailingSpace, + SpaceSpecifier leadingSpace, + SpaceSpecifier trailingSpace, int flags, short iWScount) { MinOptMax ipd = new MinOptMax(wordDim); ipd.add(spaceIPD); @@ -397,16 +445,12 @@ public class TextLayoutManager extends AbstractLayoutManager { // 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))); - BreakPoss bp = new BreakPoss( - new LeafPosition(this, vecAreaInfo.size() - 1)); + BreakPoss bp = new BreakPoss(new LeafPosition(this, + vecAreaInfo.size() - 1)); ipdTotal = ipd; if ((flags & BreakPoss.HYPHENATED) != 0) { // Add the hyphen size, but don't change total IPD! - bp.setStackingSize( - MinOptMax.add(ipd, new MinOptMax(hyphIPD))); + bp.setStackingSize(MinOptMax.add(ipd, new MinOptMax(hyphIPD))); } else { bp.setStackingSize(ipd); } @@ -449,91 +493,108 @@ public class TextLayoutManager extends AbstractLayoutManager { * @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); + 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); + iLetterSpaceDim + += (int) ((double) (letterSpaceIPD.opt - letterSpaceIPD.min) + * dIPDAdjust); } - // Set LAST flag if done making characters - int iLastChar; - for (iLastChar = ai.iBreakIndex; - iLastChar < textArray.length && textArray[iLastChar] == SPACE; - iLastChar++) { - //nop - } - context.setFlags(LayoutContext.LAST_AREA, - iLastChar == textArray.length); - - // 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); + iTotalAdjust += (iLetterSpaceDim - letterSpaceIPD.opt) * iLScount; + + // 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); } @@ -552,17 +613,400 @@ public class TextLayoutManager extends AbstractLayoutManager { TextArea textArea = new TextArea(); textArea.setWidth(width); textArea.setHeight(foText.textInfo.fs.getAscender() - - foText.textInfo.fs.getDescender()); + - foText.textInfo.fs.getDescender()); textArea.setOffset(foText.textInfo.fs.getAscender()); textArea.setOffset(base); textArea.setTextArea(str); textArea.addTrait(Trait.FONT_NAME, foText.textInfo.fs.getFontName()); textArea.addTrait(Trait.FONT_SIZE, - new Integer(foText.textInfo.fs.getFontSize())); + new Integer(foText.textInfo.fs.getFontSize())); textArea.addTrait(Trait.COLOR, foText.textInfo.color); 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); + 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 a 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)); + } + } } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index b2a92158a..38ebf4fd6 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -905,6 +905,70 @@ public class PDFRenderer extends PrintRenderer { * @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); } @@ -951,14 +1015,16 @@ public class PDFRenderer extends PrintRenderer { 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(); diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java index 6a3be0847..91c7d9322 100644 --- a/src/java/org/apache/fop/render/xml/XMLRenderer.java +++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java @@ -427,7 +427,8 @@ public class XMLRenderer extends AbstractRenderer { 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); } |