* 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;
}
/**
*
* @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;
+ }
}
* 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
}
/**
- * 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;
}
}
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;
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;
}
import java.util.List;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;
* 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;
}
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
*
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) {
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;
+ }
+
}
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;
}
}
+ 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;
+ }
}
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;
}
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);
}
-
}
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();
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
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) {
}
}
+ 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;
+ }
+ }
}
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???
package org.apache.fop.layoutmgr;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
*/
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();
}
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
*/
}
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;
+ }
+
}
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
* 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.
* @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();
*/
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);
}
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;
+ }
}
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
+import java.util.LinkedList;
+
import org.apache.fop.traits.MinOptMax;
/**
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();
}
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;
// 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
// 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();
}
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) {
}
}
- 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 {
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();
}
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;
} 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;
switch (textalign) {
case TextAlign.JUSTIFY:
if (realWidth != 0) {
- dAdjust = (double)(targetWith - realWidth) / realWidth;
+ dAdjust = (double) (targetWith - realWidth) / realWidth;
}
break;
case TextAlign.START:
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,
*/
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 ++;
}
}
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);
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));
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));
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;
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;
// 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 */
/** 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.
*
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);
}
/**
// 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));
*/
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]))))));
}
/**
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 =
*/
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) {
++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);
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()) {
if (c != SPACE) {
iNextStart++;
if (c != NEWLINE) {
- wordIPD += foText.textInfo.fs.getCharWidth(c);
+ wordIPD
+ += foText.textInfo.fs.getCharWidth(c);
} else {
iFlags |= BreakPoss.FORCE;
}
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???
}
}
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);
// 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);
}
* @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);
}
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));
+ }
+ }
}
* @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);
}
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();
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);
}