/* $Id: LineArea.java,v 1.44 2001/06/12 11:37:42 keiron Exp $
* Copyright (C) 2001 The Apache Software Foundation. All rights reserved.
* For details on use and redistribution please refer to the
* LICENSE file included with these sources.
*/
package org.apache.fop.layout;
//fop
import org.apache.fop.render.Renderer;
import org.apache.fop.messaging.MessageHandler;
import org.apache.fop.layout.inline.*;
import org.apache.fop.datatypes.IDNode;
import org.apache.fop.fo.properties.WrapOption;
import org.apache.fop.fo.properties.WhiteSpaceCollapse;
import org.apache.fop.fo.properties.TextAlign;
import org.apache.fop.fo.properties.TextAlignLast;
import org.apache.fop.fo.properties.LeaderPattern;
import org.apache.fop.fo.properties.Hyphenate;
import org.apache.fop.fo.properties.CountryMaker;
import org.apache.fop.fo.properties.LanguageMaker;
import org.apache.fop.fo.properties.LeaderAlignment;
import org.apache.fop.fo.properties.VerticalAlign;
import org.apache.fop.layout.hyphenation.Hyphenation;
import org.apache.fop.layout.hyphenation.Hyphenator;
//java
import java.util.Vector;
import java.util.Enumeration;
import java.awt.Rectangle;
public class LineArea extends Area {
protected int lineHeight;
protected int halfLeading;
protected int nominalFontSize;
protected int nominalGlyphHeight;
protected int allocationHeight;
protected int startIndent;
protected int endIndent;
private int placementOffset;
private FontState currentFontState; // not the nominal, which is
// in this.fontState
private float red, green, blue;
private int wrapOption;
private int whiteSpaceCollapse;
int vAlign;
/*hyphenation*/
HyphenationProps hyphProps;
/* the width of text that has definitely made it into the line
area */
protected int finalWidth = 0;
/* the position to shift a link rectangle in order to compensate for links embedded within a word*/
protected int embeddedLinkStart = 0;
/* the width of the current word so far */
// protected int wordWidth = 0;
/* values that prev (below) may take */
protected static final int NOTHING = 0;
protected static final int WHITESPACE = 1;
protected static final int TEXT = 2;
/* the character type of the previous character */
protected int prev = NOTHING;
/* the position in data[] of the start of the current word */
// protected int wordStart;
/* the length (in characters) of the current word */
// protected int wordLength = 0;
/* width of spaces before current word */
protected int spaceWidth = 0;
/* the inline areas that have not yet been added to the line
because subsequent characters to come (in a different addText)
may be part of the same word */
protected Vector pendingAreas = new Vector();
/* the width of the pendingAreas */
protected int pendingWidth = 0;
/* text-decoration of the previous text */
protected boolean prevUlState = false;
protected boolean prevOlState = false;
protected boolean prevLTState = false;
public LineArea(FontState fontState, int lineHeight,
int halfLeading, int allocationWidth, int startIndent,
int endIndent, LineArea prevLineArea) {
super(fontState);
this.currentFontState = fontState;
this.lineHeight = lineHeight;
this.nominalFontSize = fontState.getFontSize();
this.nominalGlyphHeight =
fontState.getAscender() - fontState.getDescender();
this.placementOffset = fontState.getAscender();
this.contentRectangleWidth =
allocationWidth - startIndent - endIndent;
this.fontState = fontState;
this.allocationHeight = this.nominalGlyphHeight;
this.halfLeading = this.lineHeight - this.allocationHeight;
this.startIndent = startIndent;
this.endIndent = endIndent;
if (prevLineArea != null) {
Enumeration e = prevLineArea.pendingAreas.elements();
while (e.hasMoreElements()) {
pendingAreas.addElement(e.nextElement());
}
pendingWidth = prevLineArea.getPendingWidth();
}
}
public void render(Renderer renderer) {
renderer.renderLineArea(this);
}
public int addPageNumberCitation(String refid, LinkSet ls) {
/* We should add code here to handle the case where the page number doesn't fit on the current line
*/
//Space must be alloted to the page number, so currently we give it 3 spaces
int width = currentFontState.width(currentFontState.mapChar(' '));
PageNumberInlineArea pia =
new PageNumberInlineArea(currentFontState, this.red,
this.green, this.blue, refid, width);
pia.setYOffset(placementOffset);
pendingAreas.addElement(pia);
pendingWidth += width;
prev = TEXT;
return -1;
}
/**
* adds text to line area
*
* @return int character position
*/
public int addText(char odata[], int start, int end, LinkSet ls,
TextState textState) {
// this prevents an array index out of bounds
// which occurs when some text is laid out again.
if(start == -1) return -1;
boolean overrun = false;
int wordStart = start;
int wordLength = 0;
int wordWidth = 0;
// With CID fonts, space isn't neccecary currentFontState.width(32)
int whitespaceWidth =
currentFontState.width(currentFontState.mapChar(' '));
char[] data = new char[odata.length];
char[] dataCopy = new char[odata.length];
System.arraycopy(odata, 0, data, 0, odata.length);
System.arraycopy(odata, 0, dataCopy, 0, odata.length);
boolean isText = false;
/* iterate over each character */
for (int i = start; i < end; i++) {
int charWidth;
/* get the character */
char c = data[i];
if (!((c == ' ') || (c == '\n') || (c == '\r') ||
(c == '\t'))) {
//c = data[i] = currentFontState.mapChar(c);
charWidth = currentFontState.width(currentFontState.mapChar(c));
isText = true;
if (charWidth <= 0)
charWidth = whitespaceWidth;
} else {
charWidth = whitespaceWidth;
isText = false;
if (prev == WHITESPACE) {
// if current & previous are WHITESPACE
if (this.whiteSpaceCollapse ==
WhiteSpaceCollapse.FALSE) {
if (c == ' ') {
spaceWidth += whitespaceWidth;
} else if (c == '\n') {
// force line break
return i;
} else if (c == '\t') {
spaceWidth += 8 * whitespaceWidth;
}
} // else ignore it
} else if (prev == TEXT) {
// if current is WHITESPACE and previous TEXT
// the current word made it, so
// add the space before the current word (if there
// was some)
if (spaceWidth > 0) {
InlineSpace is = new InlineSpace(spaceWidth);
if (prevUlState) {
is.setUnderlined(textState.getUnderlined());
}
if (prevOlState) {
is.setOverlined(textState.getOverlined());
}
if (prevLTState) {
is.setLineThrough(textState.getLineThrough());
}
addChild(is);
finalWidth += spaceWidth;
spaceWidth = 0;
}
// add any pending areas
Enumeration e = pendingAreas.elements();
while (e.hasMoreElements()) {
Box box = (Box) e.nextElement();
if (box instanceof InlineArea) {
if (ls != null) {
Rectangle lr = new Rectangle(finalWidth, 0,
((InlineArea) box).
getContentWidth(),
fontState.getFontSize());
ls.addRect(lr, this, (InlineArea)box);
}
}
addChild(box);
}
finalWidth += pendingWidth;
// reset pending areas array
pendingWidth = 0;
pendingAreas = new Vector();
// add the current word
if (wordLength > 0) {
WordArea ia = new WordArea(currentFontState,
this.red, this.green, this.blue,
new String(data, wordStart,
wordLength), wordWidth);
ia.setYOffset(placementOffset);
ia.setUnderlined(textState.getUnderlined());
prevUlState = textState.getUnderlined();
ia.setOverlined(textState.getOverlined());
prevOlState = textState.getOverlined();
ia.setLineThrough(textState.getLineThrough());
prevLTState = textState.getLineThrough();
ia.setVerticalAlign(vAlign);
addChild(ia);
if (ls != null) {
Rectangle lr = new Rectangle(finalWidth, 0,
ia.getContentWidth(),
fontState.getFontSize());
ls.addRect(lr, this, ia);
}
finalWidth += wordWidth;
// reset word width
wordWidth = 0;
}
// deal with this new whitespace following the
// word we just added
prev = WHITESPACE;
embeddedLinkStart = 0; //reset embeddedLinkStart since a space was encountered
spaceWidth = whitespaceWidth;
/*
here is the place for space-treatment value 'ignore':
if (this.spaceTreatment ==
SpaceTreatment.IGNORE) {
// do nothing
} else {
spaceWidth = currentFontState.width(32);
}
*/
if (this.whiteSpaceCollapse ==
WhiteSpaceCollapse.FALSE) {
if (c == '\n') {
// force a line break
return i;
} else if (c == '\t') {
spaceWidth = whitespaceWidth;
}
}
} else {
// if current is WHITESPACE and no previous
if (this.whiteSpaceCollapse ==
WhiteSpaceCollapse.FALSE) {
prev = WHITESPACE;
spaceWidth = whitespaceWidth;
} else {
// skip over it
wordStart++;
}
}
}
if(isText) { // current is TEXT
if (prev == WHITESPACE) {
// if current is TEXT and previous WHITESPACE
wordWidth = charWidth;
if ((finalWidth + spaceWidth + wordWidth) >
this.getContentWidth()) {
if (overrun)
MessageHandler.error(">");
if (this.wrapOption == WrapOption.WRAP)
return i;
}
prev = TEXT;
wordStart = i;
wordLength = 1;
} else if (prev == TEXT) {
wordLength++;
wordWidth += charWidth;
} else { // nothing previous
prev = TEXT;
wordStart = i;
wordLength = 1;
wordWidth = charWidth;
}
if ((finalWidth + spaceWidth + pendingWidth +
wordWidth) > this.getContentWidth()) {
// BREAK MID WORD
if (wordStart == start) { // if couldn't even fit
// first word
overrun = true;
// if not at start of line, return word start
// to try again on a new line
if (finalWidth > 0) {
return wordStart;
}
} else if (this.wrapOption == WrapOption.WRAP) {
if (hyphProps.hyphenate == Hyphenate.TRUE) {
return this.doHyphenation(dataCopy,i,wordStart,this.getContentWidth() - (finalWidth + spaceWidth + pendingWidth));
} else {
return wordStart;
}
}
}
}
} // end of iteration over text
if (prev == TEXT) {
if (spaceWidth > 0) {
InlineSpace pis = new InlineSpace(spaceWidth);
if (prevUlState) {
pis.setUnderlined(textState.getUnderlined());
}
if (prevOlState) {
pis.setOverlined(textState.getOverlined());
}
if (prevLTState) {
pis.setLineThrough(textState.getLineThrough());
}
pendingAreas.addElement(pis);
pendingWidth += spaceWidth;
spaceWidth = 0;
}
WordArea pia = new WordArea(currentFontState, this.red,
this.green, this.blue,
new String(data, wordStart, wordLength), wordWidth);
pia.setYOffset(placementOffset);
pia.setUnderlined(textState.getUnderlined());
prevUlState = textState.getUnderlined();
pia.setOverlined(textState.getOverlined());
prevOlState = textState.getOverlined();
pia.setLineThrough(textState.getLineThrough());
prevLTState = textState.getLineThrough();
pia.setVerticalAlign(vAlign);
if (ls != null) {
Rectangle lr = new Rectangle(finalWidth + spaceWidth +
embeddedLinkStart, spaceWidth,
pia.getContentWidth(), fontState.getFontSize());
ls.addRect(lr, this, pia);
}
embeddedLinkStart += wordWidth;
pendingAreas.addElement(pia);
pendingWidth += wordWidth;
wordWidth = 0;
}
if (overrun)
MessageHandler.error(">");
return -1;
}
/**
* adds a Leader; actually the method receives the leader properties
* and creates a leader area or an inline area which is appended to
* the children of the containing line area. <br>
* leader pattern use-content is not implemented.
*/
public void addLeader(int leaderPattern, int leaderLengthMinimum,
int leaderLengthOptimum, int leaderLengthMaximum,
int ruleStyle, int ruleThickness, int leaderPatternWidth,
int leaderAlignment) {
WordArea leaderPatternArea;
int leaderLength = 0;
char dotIndex = '.'; // currentFontState.mapChar('.');
int dotWidth =
currentFontState.width(currentFontState.mapChar(dotIndex));
char whitespaceIndex = ' ';//currentFontState.mapChar(' ');
int whitespaceWidth =
currentFontState.width(currentFontState.mapChar(whitespaceIndex));
int remainingWidth =
this.getContentWidth() - this.getCurrentXPosition();
/** checks whether leaderLenghtOptimum fits into rest of line;
* should never overflow, as it has been checked already in BlockArea
* first check: use remaining width if it smaller than optimum oder maximum
* */
if ((remainingWidth <= leaderLengthOptimum) ||
(remainingWidth <= leaderLengthMaximum)) {
leaderLength = remainingWidth;
} else if ((remainingWidth > leaderLengthOptimum) &&
( remainingWidth > leaderLengthMaximum)) {
leaderLength = leaderLengthMaximum;
} else if ((leaderLengthOptimum > leaderLengthMaximum) &&
(leaderLengthOptimum < remainingWidth)) {
leaderLength = leaderLengthOptimum;
}
//stop if leader-length is too small
if (leaderLength <= 0 ) {
return;
}
switch (leaderPattern) {
case LeaderPattern.SPACE:
//whitespace setting must be false for this
int whiteSpaceSetting = this.whiteSpaceCollapse;
this.changeWhiteSpaceCollapse(WhiteSpaceCollapse.FALSE);
pendingAreas.addElement(
this.buildSimpleLeader(whitespaceIndex,
leaderLength));
this.changeWhiteSpaceCollapse(whiteSpaceSetting);
break;
case LeaderPattern.RULE:
LeaderArea leaderArea =
new LeaderArea(fontState, red, green, blue, "",
leaderLength, leaderPattern, ruleThickness,
ruleStyle);
leaderArea.setYOffset(placementOffset);
pendingAreas.addElement(leaderArea);
break;
case LeaderPattern.DOTS:
//if the width of a dot is larger than leader-pattern-width
//ignore this property
if (leaderPatternWidth < dotWidth) {
leaderPatternWidth = 0;
}
//if value of leader-pattern-width is 'use-font-metrics' (0)
if (leaderPatternWidth == 0) {
pendingAreas.addElement(
this.buildSimpleLeader(dotIndex,
leaderLength));
} else {
//if leader-alignment is used, calculate space to insert before leader
//so that all dots will be parallel.
if (leaderAlignment == LeaderAlignment.REFERENCE_AREA) {
int spaceBeforeLeader = this.getLeaderAlignIndent(
leaderLength, leaderPatternWidth);
//appending indent space leader-alignment
//setting InlineSpace to false, so it is not used in line justification
if (spaceBeforeLeader != 0) {
pendingAreas.addElement(
new InlineSpace(spaceBeforeLeader,
false));
pendingWidth += spaceBeforeLeader;
//shorten leaderLength, otherwise - in case of
//leaderLength=remaining length - it will cut off the end of
//leaderlength
leaderLength -= spaceBeforeLeader;
}
}
// calculate the space to insert between the dots and create a
//inline area with this width
InlineSpace spaceBetweenDots =
new InlineSpace(leaderPatternWidth - dotWidth, false);
leaderPatternArea =
new WordArea(currentFontState, this.red,
this.green, this.blue, new String ("."),
dotWidth);
leaderPatternArea.setYOffset(placementOffset);
int dotsFactor = (int) Math.floor (
((double) leaderLength) /
((double) leaderPatternWidth));
//add combination of dot + space to fill leader
//is there a way to do this in a more effective way?
for (int i = 0; i < dotsFactor; i++) {
pendingAreas.addElement(leaderPatternArea);
pendingAreas.addElement(spaceBetweenDots);
}
//append at the end some space to fill up to leader length
pendingAreas.addElement( new InlineSpace(leaderLength -
dotsFactor * leaderPatternWidth));
}
break;
//leader pattern use-content not implemented.
case LeaderPattern.USECONTENT:
MessageHandler.errorln(
"leader-pattern=\"use-content\" not " + "supported by this version of Fop");
return;
}
//adds leader length to length of pending inline areas
pendingWidth += leaderLength;
//sets prev to TEXT and makes so sure, that also blocks only
//containing leaders are processed
prev = TEXT;
}
/**
* adds pending inline areas to the line area
* normally done, when the line area is filled and
* added as child to the parent block area
*/
public void addPending() {
if (spaceWidth > 0) {
addChild(new InlineSpace(spaceWidth));
finalWidth += spaceWidth;
spaceWidth = 0;
}
Enumeration e = pendingAreas.elements();
while (e.hasMoreElements()) {
Box box = (Box) e.nextElement();
addChild(box);
}
finalWidth += pendingWidth;
// reset pending areas array
pendingWidth = 0;
pendingAreas = new Vector();
}
/**
* aligns line area
*
*/
public void align(int type) {
int padding = 0;
switch (type) {
case TextAlign.START: // left
padding = this.getContentWidth() - finalWidth;
endIndent += padding;
break;
case TextAlign.END: // right
padding = this.getContentWidth() - finalWidth;
startIndent += padding;
break;
case TextAlign.CENTER: // center
padding = (this.getContentWidth() - finalWidth) / 2;
startIndent += padding;
endIndent += padding;
break;
case TextAlign.JUSTIFY: // justify
// first pass - count the spaces
int spaceCount = 0;
Enumeration e = children.elements();
while (e.hasMoreElements()) {
Box b = (Box) e.nextElement();
if (b instanceof InlineSpace) {
InlineSpace space = (InlineSpace) b;
if (space.getResizeable()) {
spaceCount++;
}
}
}
if (spaceCount > 0) {
padding = (this.getContentWidth() - finalWidth) /
spaceCount;
} else { // no spaces
padding = 0;
}
// second pass - add additional space
spaceCount = 0;
e = children.elements();
while (e.hasMoreElements()) {
Box b = (Box) e.nextElement();
if (b instanceof InlineSpace) {
InlineSpace space = (InlineSpace) b;
if (space.getResizeable()) {
space.setSize(space.getSize() + padding);
spaceCount++;
}
}
else if (b instanceof InlineArea) {
((InlineArea)b).setXOffset(spaceCount * padding);
}
}
}
}
/**
* Balance (vertically) the inline areas within this line.
*/
public void verticalAlign()
{
int superHeight = -this.placementOffset;
int maxHeight = this.allocationHeight;
Enumeration e = children.elements();
while (e.hasMoreElements()) {
Box b = (Box) e.nextElement();
if(b instanceof InlineArea) {
InlineArea ia = (InlineArea)b;
if(ia instanceof WordArea) {
ia.setYOffset(placementOffset);
}
if(ia.getHeight() > maxHeight) {
maxHeight = ia.getHeight();
}
int vert = ia.getVerticalAlign();
if(vert == VerticalAlign.SUPER) {
int fh = fontState.getAscender();
ia.setYOffset((int)(placementOffset - (2 * fh / 3.0)));
} else if(vert == VerticalAlign.SUB) {
int fh = fontState.getAscender();
ia.setYOffset((int)(placementOffset + (2 * fh / 3.0)));
}
} else {
}
}
// adjust the height of this line to the
// resulting alignment height.
this.allocationHeight = maxHeight;
}
public void changeColor(float red, float green, float blue) {
this.red = red;
this.green = green;
this.blue = blue;
}
public void changeFont(FontState fontState) {
this.currentFontState = fontState;
}
public void changeWhiteSpaceCollapse(int whiteSpaceCollapse) {
this.whiteSpaceCollapse = whiteSpaceCollapse;
}
public void changeWrapOption(int wrapOption) {
this.wrapOption = wrapOption;
}
public void changeVerticalAlign(int vAlign) {
this.vAlign = vAlign;
}
public int getEndIndent() {
return endIndent;
}
public int getHeight() {
return this.allocationHeight;
}
public int getPlacementOffset() {
return this.placementOffset;
}
public int getStartIndent() {
return startIndent;
}
public boolean isEmpty() {
return !(pendingAreas.size() > 0 || children.size() > 0);
// return (prev == NOTHING);
}
public Vector getPendingAreas() {
return pendingAreas;
}
public int getPendingWidth() {
return pendingWidth;
}
public void setPendingAreas(Vector areas) {
pendingAreas = areas;
}
public void setPendingWidth(int width) {
pendingWidth = width;
}
/**
* sets hyphenation related traits: language, country, hyphenate, hyphenation-character
* and minimum number of character to remain one the previous line and to be on the
* next line.
*/
public void changeHyphenation(HyphenationProps hyphProps) {
this.hyphProps = hyphProps;
}
/**
* creates a leader as String out of the given char and the leader length
* and wraps it in an InlineArea which is returned
*/
private InlineArea buildSimpleLeader(int charNumber, int leaderLength) {
int width = this.currentFontState.width(charNumber);
if (width == 0) {
char c = (char) charNumber;
MessageHandler.errorln("char " + c + " has width 0. Using width 100 instead.");
width = 100;
}
int factor = (int) Math.floor (leaderLength /
width);
char [] leaderChars = new char [factor];
char fillChar = (char) charNumber;
for (int i = 0; i < factor; i ++) {
leaderChars[i] = fillChar;
}
WordArea leaderPatternArea =
new WordArea(currentFontState, this.red, this.green,
this.blue, new String (leaderChars), leaderLength);
leaderPatternArea.setYOffset(placementOffset);
return leaderPatternArea;
}
/**
* calculates the width of space which has to be inserted before the
* start of the leader, so that all leader characters are aligned.
* is used if property leader-align is set. At the moment only the value
* for leader-align="reference-area" is supported.
*
*/
private int getLeaderAlignIndent (int leaderLength,
int leaderPatternWidth) {
//calculate position of used space in line area
double position = getCurrentXPosition();
//calculate factor of next leader pattern cycle
double nextRepeatedLeaderPatternCycle =
Math.ceil(position / leaderPatternWidth);
//calculate difference between start of next leader
//pattern cycle and already used space
double difference = (leaderPatternWidth *
nextRepeatedLeaderPatternCycle) - position;
return (int) difference;
}
/**
* calculates the used space in this line area
*/
private int getCurrentXPosition() {
return finalWidth + spaceWidth + startIndent + pendingWidth;
}
/**
* extracts a complete word from the character data
*/
private String getHyphenationWord (char [] characters, int wordStart) {
boolean wordendFound = false;
int counter = 0;
char [] newWord = new char [100]; //create a buffer
while ((!wordendFound) && ((wordStart + counter) < characters.length)) {
char tk = characters[wordStart+counter];
if (Character.isLetter(tk)) {
newWord[counter] = tk;
counter++;
} else {
wordendFound = true;
}
}
return new String (newWord,0,counter);
}
/** extracts word for hyphenation and calls hyphenation package,
* handles cases of inword punctuation and quotation marks at the beginning
* of words, but not in a internationalized way
*/
public int doHyphenation (char [] characters, int position, int wordStart, int remainingWidth) {
//check whether the language property has been set
if (this.hyphProps.language.equalsIgnoreCase("none")) {
MessageHandler.errorln("if property 'hyphenate' is used, a language must be specified");
return wordStart;
}
/** remaining part string of hyphenation */
StringBuffer remainingString = new StringBuffer();
/** for words with some inword punctuation like / or - */
StringBuffer preString = null;
/** char before the word, probably whitespace */
char startChar = ' ' ;//characters[wordStart-1];
/** in word punctuation character */
char inwordPunctuation;
/** the complete word handed to the hyphenator */
String wordToHyphenate;
//width of hyphenation character
int hyphCharWidth = this.currentFontState.width(currentFontState.mapChar(this.hyphProps.hyphenationChar));
remainingWidth -= hyphCharWidth;
//handles ' or " at the beginning of the word
if (characters[wordStart] == '"' || characters[wordStart] == '\'' ) {
remainingString.append(characters[wordStart]);
//extracts whole word from string
wordToHyphenate = getHyphenationWord(characters,wordStart+1);
} else {
wordToHyphenate = getHyphenationWord(characters,wordStart);
}
//if the extracted word is smaller than the remaining width
//we have a non letter character inside the word. at the moment
//we will only handle hard hyphens and slashes
if (getWordWidth(wordToHyphenate)< remainingWidth) {
inwordPunctuation = characters[wordStart+wordToHyphenate.length()];
if (inwordPunctuation == '-' ||
inwordPunctuation == '/' ) {
preString = new StringBuffer(wordToHyphenate);
preString = preString.append(inwordPunctuation);
wordToHyphenate = getHyphenationWord(characters,wordStart+wordToHyphenate.length()+1);
remainingWidth -= (getWordWidth(wordToHyphenate) +
this.currentFontState.width(currentFontState.mapChar(inwordPunctuation)));
}
}
//are there any hyphenation points
Hyphenation hyph = Hyphenator.hyphenate(hyphProps.language,
hyphProps.country, wordToHyphenate,hyphProps.hyphenationRemainCharacterCount,hyphProps.hyphenationPushCharacterCount);
//no hyphenation points and no inword non letter character
if (hyph == null && preString == null) {
if (remainingString.length() > 0) {
return wordStart-1;
} else {
return wordStart;
}
//no hyphenation points, but a inword non-letter character
} else if (hyph == null && preString != null){
remainingString.append(preString);
//is.addMapWord(startChar,remainingString);
this.addWord(startChar,remainingString);
return wordStart + remainingString.length();
//hyphenation points and no inword non-letter character
} else if (hyph != null && preString == null) {
int index = getFinalHyphenationPoint(hyph,remainingWidth);
if (index != -1) {
remainingString.append(hyph.getPreHyphenText(index));
remainingString.append(this.hyphProps.hyphenationChar);
//is.addMapWord(startChar,remainingString);
this.addWord(startChar,remainingString);
return wordStart + remainingString.length()-1;
}
//hyphenation points and a inword non letter character
} else if (hyph != null && preString != null) {
int index = getFinalHyphenationPoint(hyph,remainingWidth);
if (index != -1) {
remainingString.append(preString.append(hyph.getPreHyphenText(index)));
remainingString.append(this.hyphProps.hyphenationChar);
//is.addMapWord(startChar,remainingString);
this.addWord(startChar,remainingString);
return wordStart + remainingString.length()-1;
} else {
remainingString.append(preString) ;
//is.addMapWord(startChar,remainingString);
this.addWord(startChar,remainingString);
return wordStart + remainingString.length();
}
}
return wordStart;
}
/**
* Calculates the wordwidth of a string by first mapping the
* characteers in the string to glyphs in the current fontstate.
*/
private int getWordWidth(String word) {
return getWordWidth(word, true);
}
/** calculates the wordWidth using the actual fontstate
@param doMap if true, map the charaters in the string to glyphs in
the current fontstate before calculating width. If false,
assume that it's already done.
*/
private int getWordWidth (String word, boolean doMap) {
if (word == null)
return 0;
int wordLength = word.length();
int width = 0;
char [] characters = new char [wordLength];
word.getChars(0,wordLength,characters,0);
char currentChar;
for (int i = 0; i < wordLength; i++) {
if (doMap)
currentChar = currentFontState.mapChar(characters[i]);
else
currentChar=characters[i];
width += this.currentFontState.width(currentChar);
}
return width;
}
public int getRemainingWidth()
{
return this.getContentWidth() - this.getCurrentXPosition();
}
public void setLinkSet(LinkSet ls)
{
}
public void addInlineArea(Area box)
{
addPending();
addChild(box);
prev = TEXT;
finalWidth += box.getContentWidth();
}
public void addInlineSpace(InlineSpace is, int spaceWidth)
{
addChild(is);
finalWidth += spaceWidth;
// spaceWidth = 0;
}
/** adds a single character to the line area tree*/
public int addCharacter (char data, LinkSet ls, boolean ul) {
WordArea ia = null;
int remainingWidth =
this.getContentWidth() - this.getCurrentXPosition();
int width = this.currentFontState.width(currentFontState.mapChar(data));
//if it doesn't fit, return
if (width > remainingWidth) {
return org.apache.fop.fo.flow.Character.DOESNOT_FIT;
} else {
//if whitespace-collapse == true, discard character
if (Character.isSpaceChar(data) && whiteSpaceCollapse == WhiteSpaceCollapse.TRUE) {
return org.apache.fop.fo.flow.Character.OK;
}
//create new InlineArea
ia = new WordArea(currentFontState,
this.red, this.green, this.blue,
new Character(data).toString(),width);
ia.setYOffset(placementOffset);
ia.setUnderlined(ul);
pendingAreas.addElement(ia);
if (Character.isSpaceChar(data)) {
this.spaceWidth =+ width;
prev = LineArea.WHITESPACE;
} else {
pendingWidth += width;
prev = LineArea.TEXT;
}
return org.apache.fop.fo.flow.Character.OK;
}
}
/**
* Same as addWord except that characters in wordBuf is mapped
* to the current fontstate's encoding
*/
private void addMapWord (char startChar, StringBuffer wordBuf) {
StringBuffer mapBuf = new StringBuffer (wordBuf.length());
for (int i = 0; i < wordBuf.length(); i++) {
mapBuf.append(currentFontState.mapChar(wordBuf.charAt(i)));
}
addWord(startChar, mapBuf);
}
/** adds a InlineArea containing the String startChar+wordBuf to the line area children. */
private void addWord (char startChar, StringBuffer wordBuf) {
String word = (wordBuf != null) ? wordBuf.toString() : "";
WordArea hia;
int startCharWidth =
this.currentFontState.width(currentFontState.mapChar(startChar));
if (startChar == ' ') {
this.addChild(new InlineSpace(startCharWidth));
} else {
hia = new WordArea(currentFontState,
this.red, this.green, this.blue,
new Character(startChar).toString(),1);
hia.setYOffset(placementOffset);
this.addChild(hia);
}
int wordWidth = this.getWordWidth(word);
hia = new WordArea(currentFontState,
this.red, this.green, this.blue,
word,word.length());
hia.setYOffset(placementOffset);
this.addChild(hia);
//calculate the space needed
finalWidth += startCharWidth + wordWidth ;
}
/** extracts from a hyphenated word the best (most greedy) fit */
private int getFinalHyphenationPoint(Hyphenation hyph, int remainingWidth) {
int [] hyphenationPoints = hyph.getHyphenationPoints();
int numberOfHyphenationPoints = hyphenationPoints.length;
int index = -1;
String wordBegin = "";
int wordBeginWidth = 0;
for (int i = 0;i < numberOfHyphenationPoints; i++){
wordBegin = hyph.getPreHyphenText(i);
if (this.getWordWidth(wordBegin) > remainingWidth){
break;
}
index = i;
}
return index;
}
}