blob: e451a659ab439fc10434716778e8e6cdca01ff4c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.layoutmgr.inline;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.area.Trait;
import org.apache.fop.area.inline.TextArea;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FOText;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.layoutmgr.InlineKnuthSequence;
import org.apache.fop.layoutmgr.KnuthBox;
import org.apache.fop.layoutmgr.KnuthElement;
import org.apache.fop.layoutmgr.KnuthGlue;
import org.apache.fop.layoutmgr.KnuthPenalty;
import org.apache.fop.layoutmgr.KnuthSequence;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.LeafPosition;
import org.apache.fop.layoutmgr.Position;
import org.apache.fop.layoutmgr.PositionIterator;
import org.apache.fop.layoutmgr.TraitSetter;
import org.apache.fop.text.linebreak.LineBreakStatus;
import org.apache.fop.traits.MinOptMax;
import org.apache.fop.traits.SpaceVal;
import org.apache.fop.util.CharUtilities;
/**
* LayoutManager for text (a sequence of characters) which generates one
* or more inline areas.
*/
public class TextLayoutManager extends LeafNodeLayoutManager {
/**
* Store information about each potential text area.
* Index of character which ends the area, IPD of area, including
* any word-space and letter-space.
* Number of word-spaces?
*/
private class AreaInfo {
private short iStartIndex;
private short iBreakIndex;
private short iWScount;
private short iLScount;
private MinOptMax ipdArea;
private boolean bHyphenated;
private boolean isSpace;
private boolean breakOppAfter;
public AreaInfo(short iSIndex, short iBIndex, short iWS, short iLS,
MinOptMax ipd, boolean bHyph, boolean isSpace, boolean breakOppAfter) {
iStartIndex = iSIndex;
iBreakIndex = iBIndex;
iWScount = iWS;
iLScount = iLS;
ipdArea = ipd;
bHyphenated = bHyph;
this.isSpace = isSpace;
this.breakOppAfter = breakOppAfter;
}
public String toString() {
return "[ lscnt=" + iLScount
+ ", wscnt=" + iWScount
+ ", ipd=" + ipdArea.toString()
+ ", sidx=" + iStartIndex
+ ", bidx=" + iBreakIndex
+ ", hyph=" + bHyphenated
+ ", space=" + isSpace
+ "]";
}
}
// 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;
}
}
/**
* logging instance
*/
private static Log log = LogFactory.getLog(TextLayoutManager.class);
// Hold all possible breaks for the text in this LM's FO.
private ArrayList vecAreaInfo;
/** Non-space characters on which we can end a line. */
private static final String BREAK_CHARS = "-/";
/** Used to reduce instantiation of MinOptMax with zero length. Do not modify! */
private static final MinOptMax ZERO_MINOPTMAX = new MinOptMax(0);
private FOText foText;
private char[] textArray;
/**
* Contains an array of widths to adjust for kerning. The first entry can
* be used to influence the start position of the first letter. The entry i+1 defines the
* cursor advancement after the character i. A null entry means no special advancement.
*/
private MinOptMax[] letterAdjustArray; //size = textArray.length + 1
private Font font = null;
/** Start index of first character in this parent Area */
private short iAreaStart = 0;
/** Start index of next TextArea */
private short iNextStart = 0;
/** Size since last makeArea call, except for last break */
private MinOptMax ipdTotal;
/** Size including last break possibility returned */
// 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/1 of word-spacing value */
private SpaceVal ws;
/** 1/2 of word-spacing value */
private SpaceVal halfWS;
/** 1/2 of letter-spacing value */
private SpaceVal halfLS;
/** 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;
private AlignmentContext alignmentContext = null;
private int lineStartBAP = 0;
private int lineEndBAP = 0;
private boolean keepTogether;
/**
* Create a Text layout manager.
*
* @param node The FOText object to be rendered
*/
public TextLayoutManager(FOText node) {
super();
foText = node;
textArray = new char[node.endIndex - node.startIndex];
System.arraycopy(node.ca, node.startIndex, textArray, 0,
node.endIndex - node.startIndex);
letterAdjustArray = new MinOptMax[textArray.length + 1];
vecAreaInfo = new java.util.ArrayList();
}
/** {@inheritDoc} */
public void initialize() {
FontInfo fi = foText.getFOEventHandler().getFontInfo();
FontTriplet[] fontkeys = foText.getCommonFont().getFontState(fi);
font = fi.getFontInstance(fontkeys[0], foText.getCommonFont().fontSize.getValue(this));
// With CID fonts, space isn't neccesary currentFontState.width(32)
spaceCharIPD = font.getCharWidth(' ');
// Use hyphenationChar property
hyphIPD = foText.getCommonHyphenation().getHyphIPD(font);
SpaceVal ls = SpaceVal.makeLetterSpacing(foText.getLetterSpacing());
halfLS = new SpaceVal(MinOptMax.multiply(ls.getSpace(), 0.5),
ls.isConditional(), ls.isForcing(), ls.getPrecedence());
ws = SpaceVal.makeWordSpacing(foText.getWordSpacing(), ls, font);
// Make half-space: <space> on either side of a word-space)
halfWS = new SpaceVal(MinOptMax.multiply(ws.getSpace(), 0.5),
ws.isConditional(), ws.isForcing(), ws.getPrecedence());
// 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
// NOTE: The above is not quite correct. Read on in XSL 1.0, 7.16.2, letter-spacing
// set letter space and word space dimension;
// the default value "normal" was converted into a MinOptMax value
// in the SpaceVal.makeWordSpacing() method
letterSpaceIPD = ls.getSpace();
wordSpaceIPD = MinOptMax.add(new MinOptMax(spaceCharIPD), ws.getSpace());
keepTogether = foText.getKeepTogether().getWithinLine().getEnum() == Constants.EN_ALWAYS;
}
// TODO: see if we can use normal getNextBreakPoss for this with
// extra hyphenation information in LayoutContext
private boolean getHyphenIPD(HyphContext hc, MinOptMax hyphIPD) {
// Skip leading word-space before calculating count?
boolean bCanHyphenate = true;
int iStopIndex = iNextStart + hc.getNextHyphPoint();
if (textArray.length < iStopIndex) {
iStopIndex = textArray.length;
bCanHyphenate = false;
}
hc.updateOffset(iStopIndex - iNextStart);
for (; iNextStart < iStopIndex; iNextStart++) {
char c = textArray[iNextStart];
hyphIPD.opt += font.getCharWidth(c);
// letter-space?
}
// Need to include hyphen size too, but don't count it in the
// stored running total, since it would be double counted
// with later hyphenation points
return bCanHyphenate;
}
/**
* Generate and add areas to parent area.
* This can either generate an area for each TextArea and each space, or
* an area containing all text with a parameter controlling the size of
* the word space. The latter is most efficient for PDF generation.
* Set size of each area.
* @param posIter Iterator over Position information returned
* by this LayoutManager.
* @param context LayoutContext for adjustments
*/
public void addAreas(PositionIterator posIter, LayoutContext context) {
// Add word areas
AreaInfo ai = null;
int iWScount = 0;
int iLScount = 0;
int firstAreaInfoIndex = -1;
int lastAreaInfoIndex = 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 == null) {
continue; //Ignore elements without Positions
}
if (tbpNext.getLeafPos() != -1) {
ai = (AreaInfo) vecAreaInfo.get(tbpNext.getLeafPos());
if (firstAreaInfoIndex == -1) {
firstAreaInfoIndex = tbpNext.getLeafPos();
}
iWScount += ai.iWScount;
iLScount += ai.iLScount;
realWidth.add(ai.ipdArea);
lastAreaInfoIndex = tbpNext.getLeafPos();
}
}
if (ai == null) {
return;
}
int textLength = ai.iBreakIndex - ai.iStartIndex;
if (ai.iLScount == textLength && !ai.bHyphenated
&& context.isLastArea()) {
// the line ends at a character like "/" or "-";
// remove the letter space after the last character
realWidth.add(MinOptMax.multiply(letterSpaceIPD, -1));
iLScount--;
}
for (int i = ai.iStartIndex; i < ai.iBreakIndex; i++) {
MinOptMax ladj = letterAdjustArray[i + 1];
if (ladj != null && ladj.isElastic()) {
iLScount++;
}
}
// add hyphenation character if the last word is hyphenated
if (context.isLastArea() && ai.bHyphenated) {
realWidth.add(new MinOptMax(hyphIPD));
}
// Calculate adjustments
int iDifference = 0;
int iTotalAdjust = 0;
int iWordSpaceDim = wordSpaceIPD.opt;
int iLetterSpaceDim = letterSpaceIPD.opt;
double dIPDAdjust = context.getIPDAdjust();
double dSpaceAdjust = context.getSpaceAdjust(); // not used
// calculate total difference between real and available width
if (dIPDAdjust > 0.0) {
iDifference = (int) ((double) (realWidth.max - realWidth.opt)
* dIPDAdjust);
} else {
iDifference = (int) ((double) (realWidth.opt - realWidth.min)
* dIPDAdjust);
}
// set letter space adjustment
if (dIPDAdjust > 0.0) {
iLetterSpaceDim
+= (int) ((double) (letterSpaceIPD.max - letterSpaceIPD.opt)
* dIPDAdjust);
} else {
iLetterSpaceDim
+= (int) ((double) (letterSpaceIPD.opt - letterSpaceIPD.min)
* dIPDAdjust);
}
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;
if (iTotalAdjust != iDifference) {
// the applied adjustment is greater or smaller than the needed one
log.trace("TextLM.addAreas: error in word / letter space adjustment = "
+ (iTotalAdjust - iDifference));
// set iTotalAdjust = iDifference, so that the width of the TextArea
// will counterbalance the error and the other inline areas will be
// placed correctly
iTotalAdjust = iDifference;
}
TextArea t = createTextArea(realWidth, iTotalAdjust, context,
wordSpaceIPD.opt - spaceCharIPD,
firstAreaInfoIndex, lastAreaInfoIndex,
context.isLastArea());
// 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());
if (context.getIPDAdjust() != 0) {
// add information about space width
t.setSpaceDifference(wordSpaceIPD.opt - spaceCharIPD
- 2 * t.getTextLetterSpaceAdjust());
}
parentLM.addChildArea(t);
}
/**
* Create an inline word area.
* This creates a TextArea and sets up the various attributes.
*
* @param width the MinOptMax width of the content
* @param adjust the total ipd adjustment with respect to the optimal width
* @param context the layout context
* @param spaceDiff unused
* @param firstIndex the index of the first AreaInfo used for the TextArea
* @param lastIndex the index of the last AreaInfo used for the TextArea
* @param isLastArea is this TextArea the last in a line?
* @return the new text area
*/
protected TextArea createTextArea(MinOptMax width, int adjust,
LayoutContext context, int spaceDiff,
int firstIndex, int lastIndex, boolean isLastArea) {
TextArea textArea;
if (context.getIPDAdjust() == 0.0) {
// create just a TextArea
textArea = new TextArea();
} else {
// justified area: create a TextArea with extra info
// about potential adjustments
textArea = new TextArea(width.max - width.opt,
width.opt - width.min,
adjust);
}
textArea.setIPD(width.opt + adjust);
textArea.setBPD(font.getAscender() - font.getDescender());
textArea.setBaselineOffset(font.getAscender());
if (textArea.getBPD() == alignmentContext.getHeight()) {
textArea.setOffset(0);
} else {
textArea.setOffset(alignmentContext.getOffset());
}
// set the text of the TextArea, split into words and spaces
int wordStartIndex = -1;
AreaInfo areaInfo;
int len = 0;
for (int i = firstIndex; i <= lastIndex; i++) {
areaInfo = (AreaInfo) vecAreaInfo.get(i);
if (areaInfo.isSpace) {
// areaInfo stores information about spaces
// add the spaces - except zero-width spaces - to the TextArea
for (int j = areaInfo.iStartIndex; j < areaInfo.iBreakIndex; j++) {
char spaceChar = textArray[j];
if (!CharUtilities.isZeroWidthSpace(spaceChar)) {
textArea.addSpace(spaceChar, 0,
CharUtilities.isAdjustableSpace(spaceChar));
}
}
} else {
// areaInfo stores information about a word fragment
if (wordStartIndex == -1) {
// here starts a new word
wordStartIndex = i;
len = 0;
}
len += areaInfo.iBreakIndex - areaInfo.iStartIndex;
if (i == lastIndex || ((AreaInfo) vecAreaInfo.get(i + 1)).isSpace) {
// here ends a new word
// add a word to the TextArea
if (isLastArea
&& i == lastIndex
&& areaInfo.bHyphenated) {
len++;
}
StringBuffer wordChars = new StringBuffer(len);
int[] letterAdjust = new int[len];
int letter = 0;
for (int j = wordStartIndex; j <= i; j++) {
AreaInfo ai = (AreaInfo) vecAreaInfo.get(j);
int lsCount = ai.iLScount;
wordChars.append(textArray, ai.iStartIndex, ai.iBreakIndex - ai.iStartIndex);
for (int k = 0; k < ai.iBreakIndex - ai.iStartIndex; k++) {
MinOptMax adj = letterAdjustArray[ai.iStartIndex + k];
if (letter > 0) {
letterAdjust[letter] = (adj != null ? adj.opt : 0);
}
if (lsCount > 0) {
letterAdjust[letter] += textArea.getTextLetterSpaceAdjust();
lsCount--;
}
letter++;
}
}
// String wordChars = new String(textArray, wordStartIndex, len);
if (isLastArea
&& i == lastIndex
&& areaInfo.bHyphenated) {
// add the hyphenation character
wordChars.append(foText.getCommonHyphenation().getHyphChar(font));
}
textArea.addWord(wordChars.toString(), 0, letterAdjust);
wordStartIndex = -1;
}
}
}
TraitSetter.addFontTraits(textArea, font);
textArea.addTrait(Trait.COLOR, foText.getColor());
TraitSetter.addTextDecoration(textArea, foText.getTextDecoration());
return textArea;
}
private void addToLetterAdjust(int index, int width) {
if (letterAdjustArray[index] == null) {
letterAdjustArray[index] = new MinOptMax(width);
} else {
letterAdjustArray[index].add(width);
}
}
private void addToLetterAdjust(int index, MinOptMax width) {
if (letterAdjustArray[index] == null) {
letterAdjustArray[index] = new MinOptMax(width);
} else {
letterAdjustArray[index].add(width);
}
}
/**
* Indicates whether a character is a space in terms of this layout manager.
* @param ch the character
* @return true if it's a space
*/
private static boolean isSpace(final char ch) {
return ch == CharUtilities.SPACE
|| CharUtilities.isNonBreakableSpace(ch)
|| CharUtilities.isFixedWidthSpace(ch);
}
/** {@inheritDoc} */
public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
lineStartBAP = context.getLineStartBorderAndPaddingWidth();
lineEndBAP = context.getLineEndBorderAndPaddingWidth();
alignmentContext = context.getAlignmentContext();
LinkedList returnList = new LinkedList();
KnuthSequence sequence = new InlineKnuthSequence();
AreaInfo ai = null;
AreaInfo prevAi = null;
returnList.add(sequence);
LineBreakStatus lbs = new LineBreakStatus();
iThisStart = iNextStart;
boolean inWord = false;
boolean inWhitespace = false;
char ch = 0;
while (iNextStart < textArray.length) {
ch = textArray[iNextStart];
boolean breakOpportunity = false;
byte breakAction = keepTogether ? LineBreakStatus.PROHIBITED_BREAK : lbs.nextChar(ch);
switch (breakAction) {
case LineBreakStatus.COMBINING_PROHIBITED_BREAK:
case LineBreakStatus.PROHIBITED_BREAK:
break;
case LineBreakStatus.EXPLICIT_BREAK:
break;
case LineBreakStatus.COMBINING_INDIRECT_BREAK:
case LineBreakStatus.DIRECT_BREAK:
case LineBreakStatus.INDIRECT_BREAK:
breakOpportunity = true;
break;
default:
log.error("Unexpected breakAction: " + breakAction);
}
if (inWord) {
if (breakOpportunity || isSpace(ch) || CharUtilities.isLineBreakCharacter(ch)) {
//Word boundary found, process widths and kerning
int lastIndex = iNextStart;
while (lastIndex > 0 && textArray[lastIndex - 1] == CharUtilities.SOFT_HYPHEN) {
lastIndex--;
}
int wordLength = lastIndex - iThisStart;
boolean kerning = font.hasKerning();
MinOptMax wordIPD = new MinOptMax(0);
for (int i = iThisStart; i < lastIndex; i++) {
char c = textArray[i];
//character width
int charWidth = font.getCharWidth(c);
wordIPD.add(charWidth);
//kerning
if (kerning) {
int kern = 0;
if (i > iThisStart) {
char previous = textArray[i - 1];
kern = font.getKernValue(previous, c) * font.getFontSize() / 1000;
} else if (prevAi != null && !prevAi.isSpace && prevAi.iBreakIndex > 0) {
char previous = textArray[prevAi.iBreakIndex - 1];
kern = font.getKernValue(previous, c) * font.getFontSize() / 1000;
}
if (kern != 0) {
//log.info("Kerning between " + previous + " and " + c + ": " + kern);
addToLetterAdjust(i, kern);
wordIPD.add(kern);
}
}
}
if (kerning && breakOpportunity && !isSpace(ch) && lastIndex > 0 && textArray[lastIndex] == CharUtilities.SOFT_HYPHEN) {
int kern = font.getKernValue(textArray[lastIndex - 1], ch) * font.getFontSize() / 1000;
if (kern != 0) {
addToLetterAdjust(lastIndex, kern);
}
}
int iLetterSpaces = wordLength - 1;
// if there is a break opportunity and the next one
// is not a space, it could be used as a line end;
// add one more letter space, in case other text follows
if (breakOpportunity && !isSpace(ch)) {
iLetterSpaces++;
}
wordIPD.add(MinOptMax.multiply(letterSpaceIPD, iLetterSpaces));
// create the AreaInfo object
ai = new AreaInfo(iThisStart, (short)lastIndex, (short) 0,
(short) iLetterSpaces,
wordIPD, textArray[lastIndex] == CharUtilities.SOFT_HYPHEN, false, breakOpportunity);
vecAreaInfo.add(ai);
prevAi = ai;
iTempStart = iNextStart;
// create the elements
sequence.addAll(createElementsForAWordFragment(alignment, ai,
vecAreaInfo.size() - 1, letterSpaceIPD));
ai = null;
iThisStart = iNextStart;
}
} else if (inWhitespace) {
if (ch != CharUtilities.SPACE || breakOpportunity) {
// End of whitespace
// create the AreaInfo object
ai = new AreaInfo(iThisStart, (short) (iNextStart),
(short) (iNextStart - iThisStart), (short) 0,
MinOptMax.multiply(wordSpaceIPD, iNextStart - iThisStart),
false, true, breakOpportunity);
vecAreaInfo.add(ai);
prevAi = ai;
// create the elements
sequence.addAll
(createElementsForASpace(alignment, ai, vecAreaInfo.size() - 1));
ai = null;
iThisStart = iNextStart;
}
} else {
if (ai != null) {
vecAreaInfo.add(ai);
prevAi = ai;
ai.breakOppAfter = ch == CharUtilities.SPACE || breakOpportunity;
sequence.addAll
(createElementsForASpace(alignment, ai, vecAreaInfo.size() - 1));
ai = null;
}
if (breakAction == LineBreakStatus.EXPLICIT_BREAK) {
if (lineEndBAP != 0) {
sequence.add
(new KnuthGlue(lineEndBAP, 0, 0,
new LeafPosition(this, -1), true));
}
sequence.endSequence();
sequence = new InlineKnuthSequence();
returnList.add(sequence);
}
}
if ((ch == CharUtilities.SPACE
&& foText.getWhitespaceTreatment() == Constants.EN_PRESERVE)
|| ch == CharUtilities.NBSPACE) {
// preserved space or non-breaking space:
// create the AreaInfo object
ai = new AreaInfo(iNextStart, (short) (iNextStart + 1),
(short) 1, (short) 0,
wordSpaceIPD, false, true, breakOpportunity);
iThisStart = (short) (iNextStart + 1);
} else if (CharUtilities.isFixedWidthSpace(ch) || CharUtilities.isZeroWidthSpace(ch)) {
// create the AreaInfo object
MinOptMax ipd = new MinOptMax(font.getCharWidth(ch));
ai = new AreaInfo(iNextStart, (short) (iNextStart + 1),
(short) 0, (short) 0,
ipd, false, true, breakOpportunity);
iThisStart = (short) (iNextStart + 1);
} else if (CharUtilities.isLineBreakCharacter(ch)) {
// linefeed; this can happen when linefeed-treatment="preserve"
iThisStart = (short) (iNextStart + 1);
}
inWord = !isSpace(ch) && !CharUtilities.isLineBreakCharacter(ch);
inWhitespace = ch == CharUtilities.SPACE && foText.getWhitespaceTreatment() != Constants.EN_PRESERVE;
iNextStart++;
} // end of while
// Process any last elements
if (inWord) {
int lastIndex = iNextStart;
if (textArray[iNextStart - 1] == CharUtilities.SOFT_HYPHEN) {
lastIndex--;
}
int wordLength = lastIndex - iThisStart;
boolean kerning = font.hasKerning();
MinOptMax wordIPD = new MinOptMax(0);
for (int i = iThisStart; i < lastIndex; i++) {
char c = textArray[i];
//character width
int charWidth = font.getCharWidth(c);
wordIPD.add(charWidth);
//kerning
if (kerning) {
int kern = 0;
if (i > iThisStart) {
char previous = textArray[i - 1];
kern = font.getKernValue(previous, c) * font.getFontSize() / 1000;
} else if (prevAi != null && !prevAi.isSpace) {
char previous = textArray[prevAi.iBreakIndex - 1];
kern = font.getKernValue(previous, c) * font.getFontSize() / 1000;
}
if (kern != 0) {
//log.info("Kerning between " + previous + " and " + c + ": " + kern);
addToLetterAdjust(i, kern);
wordIPD.add(kern);
}
}
}
int iLetterSpaces = wordLength - 1;
wordIPD.add(MinOptMax.multiply(letterSpaceIPD, iLetterSpaces));
// create the AreaInfo object
ai = new AreaInfo(iThisStart, (short)lastIndex, (short) 0,
(short) iLetterSpaces,
wordIPD, false, false, false);
vecAreaInfo.add(ai);
iTempStart = iNextStart;
// create the elements
sequence.addAll(createElementsForAWordFragment(alignment, ai,
vecAreaInfo.size() - 1, letterSpaceIPD));
ai = null;
} else if (inWhitespace) {
ai = new AreaInfo(iThisStart, (short) (iNextStart),
(short) (iNextStart - iThisStart), (short) 0,
MinOptMax.multiply(wordSpaceIPD, iNextStart - iThisStart),
false, true, true);
vecAreaInfo.add(ai);
// create the elements
sequence.addAll
(createElementsForASpace(alignment, ai, vecAreaInfo.size() - 1));
ai = null;
} else if (ai != null) {
vecAreaInfo.add(ai);
ai.breakOppAfter = ch == CharUtilities.ZERO_WIDTH_SPACE;
sequence.addAll
(createElementsForASpace(alignment, ai, vecAreaInfo.size() - 1));
ai = null;
} else if (CharUtilities.isLineBreakCharacter(ch)) {
if (lineEndBAP != 0) {
sequence.add
(new KnuthGlue(lineEndBAP, 0, 0,
new LeafPosition(this, -1), true));
}
sequence.endSequence();
sequence = new InlineKnuthSequence();
returnList.add(sequence);
}
if (((List)returnList.getLast()).size() == 0) {
//Remove an empty sequence because of a trailing newline
returnList.removeLast();
}
setFinished(true);
if (returnList.size() > 0) {
return returnList;
} else {
return null;
}
}
/** {@inheritDoc} */
public List addALetterSpaceTo(List oldList) {
// old list contains only a box, or the sequence: box penalty glue box;
// look at the Position stored in the first element in oldList
// which is always a box
ListIterator oldListIterator = oldList.listIterator();
KnuthElement el = (KnuthElement)oldListIterator.next();
LeafPosition pos = (LeafPosition) ((KnuthBox) el).getPosition();
int idx = pos.getLeafPos();
//element could refer to '-1' position, for non-collapsed spaces (?)
if (idx > -1) {
AreaInfo ai = (AreaInfo) vecAreaInfo.get(idx);
ai.iLScount++;
ai.ipdArea.add(letterSpaceIPD);
if (BREAK_CHARS.indexOf(textArray[iTempStart - 1]) >= 0) {
// the last character could be used as a line break
// append new elements to oldList
oldListIterator = oldList.listIterator(oldList.size());
oldListIterator.add(new KnuthPenalty(0, KnuthPenalty.FLAGGED_PENALTY, true,
new LeafPosition(this, -1), false));
oldListIterator.add(new KnuthGlue(letterSpaceIPD.opt,
letterSpaceIPD.max - letterSpaceIPD.opt,
letterSpaceIPD.opt - letterSpaceIPD.min,
new LeafPosition(this, -1), false));
} else if (letterSpaceIPD.min == letterSpaceIPD.max) {
// constant letter space: replace the box
oldListIterator.set(new KnuthInlineBox(ai.ipdArea.opt, alignmentContext, pos, false));
} else {
// adjustable letter space: replace the glue
oldListIterator.next(); // this would return the penalty element
oldListIterator.next(); // this would return the glue element
oldListIterator.set(new KnuthGlue(ai.iLScount * letterSpaceIPD.opt,
ai.iLScount * (letterSpaceIPD.max - letterSpaceIPD.opt),
ai.iLScount * (letterSpaceIPD.opt - letterSpaceIPD.min),
new LeafPosition(this, -1), true));
}
}
return oldList;
}
/**
* remove the AreaInfo object represented by the given elements,
* so that it won't generate any element when getChangedKnuthElements
* will be called
*
* @param oldList the elements representing the word space
*/
public void removeWordSpace(List oldList) {
// find the element storing the Position whose value
// points to the AreaInfo object
ListIterator oldListIterator = oldList.listIterator();
if (((KnuthElement) ((LinkedList) oldList).getFirst()).isPenalty()) {
// non breaking space: oldList starts with a penalty
oldListIterator.next();
}
if (oldList.size() > 2) {
// alignment is either center, start or end:
// the first two elements does not store the needed Position
oldListIterator.next();
oldListIterator.next();
}
int leafValue = ((LeafPosition) ((KnuthElement) oldListIterator.next()).getPosition()).getLeafPos();
// only the last word space can be a trailing space!
if (leafValue == vecAreaInfo.size() - 1) {
vecAreaInfo.remove(leafValue);
} else {
log.error("trying to remove a non-trailing word space");
}
}
/** {@inheritDoc} */
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);
//log.info("Word: " + new String(textArray, iStartIndex, iStopIndex - iStartIndex));
for (int i = iStartIndex; i < iStopIndex; i++) {
char c = textArray[i];
newIPD.add(new MinOptMax(font.getCharWidth(c)));
//if (i > iStartIndex) {
if (i < iStopIndex) {
MinOptMax la = this.letterAdjustArray[i + 1];
if ((i == iStopIndex - 1) && bHyphenFollows) {
//the letter adjust here needs to be handled further down during
//element generation because it depends on hyph/no-hyph condition
la = null;
}
if (la != null) {
newIPD.add(la);
}
}
}
// 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, false, false),
((LeafPosition) pos).getLeafPos()));
bNothingChanged = false;
}
iStartIndex = iStopIndex;
}
if (!bChanged && !bNothingChanged) {
bChanged = true;
}
}
/** {@inheritDoc} */
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;
}
/** {@inheritDoc} */
public LinkedList getChangedKnuthElements(List oldList,
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
returnList.addAll
(createElementsForAWordFragment(alignment, ai, iReturnedIndex, letterSpaceIPD));
} else {
// ai refers to a space
returnList.addAll
(createElementsForASpace(alignment, ai, iReturnedIndex));
}
iReturnedIndex++;
} // end of while
setFinished(true);
//ElementListObserver.observe(returnList, "text-changed", null);
return returnList;
}
/** {@inheritDoc} */
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));
}
}
private LinkedList createElementsForASpace(int alignment,
AreaInfo ai, int leafValue) {
LinkedList spaceElements = new LinkedList();
LeafPosition mainPosition = new LeafPosition(this, leafValue);
if (!ai.breakOppAfter) {
// a non-breaking space
if (alignment == EN_JUSTIFY) {
// the space can stretch and shrink, and must be preserved
// when starting a line
spaceElements.add(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), true));
spaceElements.add(new KnuthPenalty(0, KnuthElement.INFINITE,
false, new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(ai.ipdArea.opt, ai.ipdArea.max - ai.ipdArea.opt,
ai.ipdArea.opt - ai.ipdArea.min, mainPosition, false));
} else {
// the space does not need to stretch or shrink, and must be
// preserved when starting a line
spaceElements.add(new KnuthInlineBox(ai.ipdArea.opt, null,
mainPosition, true));
}
} else {
if (textArray[ai.iStartIndex] != CharUtilities.SPACE
|| foText.getWhitespaceTreatment() == Constants.EN_PRESERVE) {
// a breaking space that needs to be preserved
switch (alignment) {
case EN_CENTER:
// centered text:
// if the second element is chosen as a line break these elements
// add a constant amount of stretch at the end of a line and at the
// beginning of the next one, otherwise they don't add any stretch
spaceElements.add(new KnuthGlue(lineEndBAP,
3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
spaceElements
.add(new KnuthPenalty(
0,
0, false,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(
- (lineStartBAP + lineEndBAP), -6
* LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), false));
spaceElements.add(new KnuthPenalty(0, KnuthElement.INFINITE,
false, new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(ai.ipdArea.opt + lineStartBAP,
3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
mainPosition, false));
break;
case EN_START: // fall through
case EN_END:
// left- or right-aligned text:
// if the second element is chosen as a line break these elements
// add a constant amount of stretch at the end of a line, otherwise
// they don't add any stretch
spaceElements.add(new KnuthGlue(lineEndBAP,
3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthPenalty(0, 0, false,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(
- (lineStartBAP + lineEndBAP), -3
* LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), false));
spaceElements.add(new KnuthPenalty(0,
KnuthElement.INFINITE, false, new LeafPosition(
this, -1), false));
spaceElements.add(new KnuthGlue(ai.ipdArea.opt + lineStartBAP, 0, 0,
mainPosition, false));
break;
case EN_JUSTIFY:
// justified text:
// the stretch and shrink depends on the space width
spaceElements.add(new KnuthGlue(lineEndBAP, 0, 0,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthPenalty(0, 0, false,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(
- (lineStartBAP + lineEndBAP), ai.ipdArea.max
- ai.ipdArea.opt, ai.ipdArea.opt - ai.ipdArea.min,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), false));
spaceElements.add(new KnuthPenalty(0,
KnuthElement.INFINITE, false, new LeafPosition(
this, -1), false));
spaceElements.add(new KnuthGlue(lineStartBAP + ai.ipdArea.opt, 0, 0,
mainPosition, false));
break;
default:
// last line justified, the other lines unjustified:
// use only the space stretch
spaceElements.add(new KnuthGlue(lineEndBAP, 0, 0,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthPenalty(0, 0, false,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(
- (lineStartBAP + lineEndBAP), ai.ipdArea.max
- ai.ipdArea.opt, 0,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), false));
spaceElements.add(new KnuthPenalty(0,
KnuthElement.INFINITE, false, new LeafPosition(
this, -1), false));
spaceElements.add(new KnuthGlue(lineStartBAP + ai.ipdArea.opt, 0, 0,
mainPosition, false));
}
} else {
// a (possible block) of breaking spaces
switch (alignment) {
case EN_CENTER:
// centered text:
// if the second element is chosen as a line break these elements
// add a constant amount of stretch at the end of a line and at the
// beginning of the next one, otherwise they don't add any stretch
spaceElements.add(new KnuthGlue(lineEndBAP,
3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
spaceElements
.add(new KnuthPenalty(
0, 0, false,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(ai.ipdArea.opt
- (lineStartBAP + lineEndBAP), -6
* LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
mainPosition, false));
spaceElements.add(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), false));
spaceElements.add(new KnuthPenalty(0, KnuthElement.INFINITE,
false, new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(lineStartBAP,
3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
break;
case EN_START: // fall through
case EN_END:
// left- or right-aligned text:
// if the second element is chosen as a line break these elements
// add a constant amount of stretch at the end of a line, otherwise
// they don't add any stretch
if (lineStartBAP != 0 || lineEndBAP != 0) {
spaceElements.add(new KnuthGlue(lineEndBAP,
3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthPenalty(0, 0, false,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(ai.ipdArea.opt
- (lineStartBAP + lineEndBAP), -3
* LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
mainPosition, false));
spaceElements.add(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), false));
spaceElements.add(new KnuthPenalty(0,
KnuthElement.INFINITE, false, new LeafPosition(
this, -1), false));
spaceElements.add(new KnuthGlue(lineStartBAP, 0, 0,
new LeafPosition(this, -1), false));
} else {
spaceElements.add(new KnuthGlue(0,
3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthPenalty(0, 0, false,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(ai.ipdArea.opt, -3
* LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
mainPosition, false));
}
break;
case EN_JUSTIFY:
// justified text:
// the stretch and shrink depends on the space width
if (lineStartBAP != 0 || lineEndBAP != 0) {
spaceElements.add(new KnuthGlue(lineEndBAP, 0, 0,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthPenalty(0, 0, false,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(
ai.ipdArea.opt - (lineStartBAP + lineEndBAP),
ai.ipdArea.max - ai.ipdArea.opt,
ai.ipdArea.opt - ai.ipdArea.min,
mainPosition, false));
spaceElements.add(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), false));
spaceElements.add(new KnuthPenalty(0,
KnuthElement.INFINITE, false, new LeafPosition(
this, -1), false));
spaceElements.add(new KnuthGlue(lineStartBAP, 0, 0,
new LeafPosition(this, -1), false));
} else {
spaceElements.add(new KnuthGlue(ai.ipdArea.opt,
ai.ipdArea.max - ai.ipdArea.opt,
ai.ipdArea.opt - ai.ipdArea.min,
mainPosition, false));
}
break;
default:
// last line justified, the other lines unjustified:
// use only the space stretch
if (lineStartBAP != 0 || lineEndBAP != 0) {
spaceElements.add(new KnuthGlue(lineEndBAP, 0, 0,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthPenalty(0, 0, false,
new LeafPosition(this, -1), false));
spaceElements.add(new KnuthGlue(
ai.ipdArea.opt - (lineStartBAP + lineEndBAP),
ai.ipdArea.max - ai.ipdArea.opt,
0, mainPosition, false));
spaceElements.add(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), false));
spaceElements.add(new KnuthPenalty(0,
KnuthElement.INFINITE, false, new LeafPosition(
this, -1), false));
spaceElements.add(new KnuthGlue(lineStartBAP, 0, 0,
new LeafPosition(this, -1), false));
} else {
spaceElements.add(new KnuthGlue(ai.ipdArea.opt,
ai.ipdArea.max - ai.ipdArea.opt, 0,
mainPosition, false));
}
}
}
}
return spaceElements;
}
private LinkedList createElementsForAWordFragment(int alignment,
AreaInfo ai, int leafValue, MinOptMax letterSpaceWidth) {
LinkedList wordElements = new LinkedList();
LeafPosition mainPosition = new LeafPosition(this, leafValue);
// if the last character of the word fragment is '-' or '/',
// the fragment could end a line; in this case, it loses one
// of its letter spaces;
boolean bSuppressibleLetterSpace = ai.breakOppAfter && !ai.bHyphenated;
if (letterSpaceWidth.min == letterSpaceWidth.max) {
// constant letter spacing
wordElements.add
(new KnuthInlineBox(
bSuppressibleLetterSpace
? ai.ipdArea.opt - letterSpaceWidth.opt
: ai.ipdArea.opt,
alignmentContext,
notifyPos(mainPosition), false));
} else {
// adjustable letter spacing
int unsuppressibleLetterSpaces
= bSuppressibleLetterSpace ? ai.iLScount - 1 : ai.iLScount;
wordElements.add
(new KnuthInlineBox(ai.ipdArea.opt
- ai.iLScount * letterSpaceWidth.opt,
alignmentContext,
notifyPos(mainPosition), false));
wordElements.add
(new KnuthPenalty(0, KnuthElement.INFINITE, false,
new LeafPosition(this, -1), true));
wordElements.add
(new KnuthGlue(unsuppressibleLetterSpaces * letterSpaceWidth.opt,
unsuppressibleLetterSpaces * (letterSpaceWidth.max - letterSpaceWidth.opt),
unsuppressibleLetterSpaces * (letterSpaceWidth.opt - letterSpaceWidth.min),
new LeafPosition(this, -1), true));
wordElements.add
(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), true));
}
// extra-elements if the word fragment is the end of a syllable,
// or it ends with a character that can be used as a line break
if (ai.bHyphenated) {
MinOptMax widthIfNoBreakOccurs = null;
if (ai.iBreakIndex < textArray.length) {
//Add in kerning in no-break condition
widthIfNoBreakOccurs = letterAdjustArray[ai.iBreakIndex];
}
//if (ai.iBreakIndex)
// the word fragment ends at the end of a syllable:
// if a break occurs the content width increases,
// otherwise nothing happens
wordElements.addAll(createElementsForAHyphen(alignment, hyphIPD, widthIfNoBreakOccurs, ai.breakOppAfter && ai.bHyphenated));
} else if (bSuppressibleLetterSpace) {
// the word fragment ends with a character that acts as a hyphen
// if a break occurs the width does not increase,
// otherwise there is one more letter space
wordElements.addAll(createElementsForAHyphen(alignment, 0, letterSpaceWidth, true));
}
return wordElements;
}
// static final int SOFT_HYPHEN_PENALTY = KnuthPenalty.FLAGGED_PENALTY / 10;
static final int SOFT_HYPHEN_PENALTY = 1;
private LinkedList createElementsForAHyphen(int alignment,
int widthIfBreakOccurs, MinOptMax widthIfNoBreakOccurs, boolean unflagged) {
if (widthIfNoBreakOccurs == null) {
widthIfNoBreakOccurs = ZERO_MINOPTMAX;
}
LinkedList hyphenElements = new LinkedList();
switch (alignment) {
case EN_CENTER :
// centered text:
/*
hyphenElements.add
(new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthPenalty(hyphIPD,
KnuthPenalty.FLAGGED_PENALTY, true,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(0,
- 6 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthInlineBox(0, 0, 0, 0,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthPenalty(0, KnuthElement.INFINITE, true,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
*/
hyphenElements.add
(new KnuthPenalty(0, KnuthElement.INFINITE, false,
new LeafPosition(this, -1), true));
hyphenElements.add
(new KnuthGlue(lineEndBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), true));
hyphenElements.add
(new KnuthPenalty(hyphIPD,
unflagged ? SOFT_HYPHEN_PENALTY : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(-(lineEndBAP + lineStartBAP),
-6 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), true));
hyphenElements.add
(new KnuthPenalty(0, KnuthElement.INFINITE, false,
new LeafPosition(this, -1), true));
hyphenElements.add
(new KnuthGlue(lineStartBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), true));
break;
case EN_START : // fall through
case EN_END :
// left- or right-aligned text:
/*
hyphenElements.add
(new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthPenalty(widthIfBreakOccurs,
KnuthPenalty.FLAGGED_PENALTY, true,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(widthIfNoBreakOccurs.opt,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
*/
if (lineStartBAP != 0 || lineEndBAP != 0) {
hyphenElements.add
(new KnuthPenalty(0, KnuthElement.INFINITE, false,
new LeafPosition(this, -1), true));
hyphenElements.add
(new KnuthGlue(lineEndBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthPenalty(widthIfBreakOccurs,
unflagged ? SOFT_HYPHEN_PENALTY : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(widthIfNoBreakOccurs.opt - (lineStartBAP + lineEndBAP),
-3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), false));
hyphenElements.add
(new KnuthPenalty(0, KnuthElement.INFINITE, false,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(lineStartBAP, 0, 0,
new LeafPosition(this, -1), false));
} else {
hyphenElements.add
(new KnuthPenalty(0, KnuthElement.INFINITE, false,
new LeafPosition(this, -1), true));
hyphenElements.add
(new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthPenalty(widthIfBreakOccurs,
unflagged ? SOFT_HYPHEN_PENALTY : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(widthIfNoBreakOccurs.opt,
-3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
new LeafPosition(this, -1), false));
}
break;
default:
// justified text, or last line justified:
// just a flagged penalty
/*
hyphenElements.add
(new KnuthPenalty(widthIfBreakOccurs,
KnuthPenalty.FLAGGED_PENALTY, true,
new LeafPosition(this, -1), false));
*/
if (lineStartBAP != 0 || lineEndBAP != 0) {
hyphenElements.add
(new KnuthPenalty(0, KnuthElement.INFINITE, false,
new LeafPosition(this, -1), true));
hyphenElements.add
(new KnuthGlue(lineEndBAP, 0, 0,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthPenalty(widthIfBreakOccurs,
unflagged ? SOFT_HYPHEN_PENALTY : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
new LeafPosition(this, -1), false));
// extra elements representing a letter space that is suppressed
// if a break occurs
if (widthIfNoBreakOccurs.min != 0
|| widthIfNoBreakOccurs.max != 0) {
hyphenElements.add
(new KnuthGlue(widthIfNoBreakOccurs.opt - (lineStartBAP + lineEndBAP),
widthIfNoBreakOccurs.max - widthIfNoBreakOccurs.opt,
widthIfNoBreakOccurs.opt - widthIfNoBreakOccurs.min,
new LeafPosition(this, -1), false));
} else {
hyphenElements.add
(new KnuthGlue(-(lineStartBAP + lineEndBAP), 0, 0,
new LeafPosition(this, -1), false));
}
hyphenElements.add
(new KnuthInlineBox(0, null,
notifyPos(new LeafPosition(this, -1)), false));
hyphenElements.add
(new KnuthPenalty(0, KnuthElement.INFINITE, false,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(lineStartBAP, 0, 0,
new LeafPosition(this, -1), false));
} else {
hyphenElements.add
(new KnuthPenalty(widthIfBreakOccurs,
unflagged ? SOFT_HYPHEN_PENALTY : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
new LeafPosition(this, -1), false));
// extra elements representing a letter space that is suppressed
// if a break occurs
if (widthIfNoBreakOccurs.min != 0
|| widthIfNoBreakOccurs.max != 0) {
hyphenElements.add
(new KnuthGlue(widthIfNoBreakOccurs.opt,
widthIfNoBreakOccurs.max - widthIfNoBreakOccurs.opt,
widthIfNoBreakOccurs.opt - widthIfNoBreakOccurs.min,
new LeafPosition(this, -1), false));
}
}
}
return hyphenElements;
}
}