ASF Bugzilla – Attachment 4773 Details for
Bug 16870
Hyphenation bug including bugfix : sporadic mutilation of hyphenated word
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
patch
LineArea.java (text/plain), 56.32 KB, created by
Chris Wewerka
on 2003-02-07 11:37:19 UTC
(
hide
)
Description:
patch
Filename:
MIME Type:
Creator:
Chris Wewerka
Created:
2003-02-07 11:37:19 UTC
Size:
56.32 KB
patch
obsolete
>/* > * $Id: LineArea.java,v 1.53.2.5 2002/02/11 00:43:45 chrisg 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.FOText; >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; >import org.apache.fop.configuration.Configuration; > >// java >import java.util.Vector; >import java.util.Enumeration; >import java.util.StringTokenizer; >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; > protected static final int MULTIBYTECHAR = 3; > > /* 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(); > Box b = null; > // There might be InlineSpaces at the beginning > // that should not be there - eat them > boolean eatMoreSpace = true; > int eatenWidth = 0; > > while (eatMoreSpace) { > if (e.hasMoreElements()) { > b = (Box)e.nextElement(); > if (b instanceof InlineSpace) { > InlineSpace is = (InlineSpace)b; > if (is.isEatable()) > eatenWidth += is.getSize(); > else > eatMoreSpace = false; > } else { > eatMoreSpace = false; > } > } else { > eatMoreSpace = false; > b = null; > } > } > > while (b != null) { > pendingAreas.addElement(b); > if (e.hasMoreElements()) > b = (Box)e.nextElement(); > else > b = null; > } > pendingWidth = prevLineArea.getPendingWidth() - eatenWidth; > } > } > > 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 neccesary currentFontState.width(32) > int whitespaceWidth = getCharWidth(' '); > > 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; > boolean isMultiByteChar = false; > > /* iterate over each character */ > for (int i = start; i < end; i++) { > int charWidth; > /* get the character */ > char c = data[i]; > if (!(isSpace(c) || (c == '\n') || (c == '\r') || (c == '\t') > || (c == '\u2028'))) { > charWidth = getCharWidth(c); > isText = true; > isMultiByteChar = (c > 127); > // Add support for zero-width spaces > if (charWidth <= 0 && c != '\u200B' && c != '\uFEFF') > charWidth = whitespaceWidth; > } else { > if ((c == '\n') || (c == '\r') || (c == '\t')) > charWidth = whitespaceWidth; > else > charWidth = getCharWidth(c); > > isText = false; > isMultiByteChar = false; > > if (prev == WHITESPACE) { > > // if current & previous are WHITESPACE > > if (this.whiteSpaceCollapse == WhiteSpaceCollapse.FALSE) { > if (isSpace(c)) { > spaceWidth += getCharWidth(c); > } else if (c == '\n' || c == '\u2028') { > // force line break > if (spaceWidth > 0) { > InlineSpace is = new InlineSpace(spaceWidth); > is.setUnderlined(textState.getUnderlined()); > is.setOverlined(textState.getOverlined()); > is.setLineThrough(textState.getLineThrough()); > addChild(is); > finalWidth += spaceWidth; > spaceWidth = 0; > } > return i + 1; > } else if (c == '\t') { > spaceWidth += 8 * whitespaceWidth; > } > } else if (c == '\u2028') { > // Line separator > // Breaks line even if WhiteSpaceCollapse = True > if (spaceWidth > 0) { > InlineSpace is = new InlineSpace(spaceWidth); > is.setUnderlined(textState.getUnderlined()); > is.setOverlined(textState.getOverlined()); > is.setLineThrough(textState.getLineThrough()); > addChild(is); > finalWidth += spaceWidth; > spaceWidth = 0; > } > return i + 1; > } > > } else if (prev == TEXT || prev == MULTIBYTECHAR ) { > > // 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) { > // The word might contain nonbreaking > // spaces. Split the word and add InlineSpace > // as necessary. All spaces inside the word > // Have a fixed width. > addSpacedWord(new String(data, wordStart, wordLength), > ls, finalWidth, 0, textState, false); > 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 = getCharWidth(c); > > /* > * here is the place for white-space-treatment value 'ignore': > * if (this.spaceTreatment == > * SpaceTreatment.IGNORE) { > * // do nothing > * } else { > * spaceWidth = currentFontState.width(32); > * } > */ > > > if (this.whiteSpaceCollapse == WhiteSpaceCollapse.FALSE) { > if (c == '\n' || c == '\u2028') { > // force a line break > return i + 1; > } else if (c == '\t') { > spaceWidth = whitespaceWidth; > } > } else if (c == '\u2028') { > return i + 1; > } > } else { > > // if current is WHITESPACE and no previous > > if (this.whiteSpaceCollapse == WhiteSpaceCollapse.FALSE) { > if (isSpace(c)) { > prev = WHITESPACE; > spaceWidth = getCharWidth(c); > } else if (c == '\n') { > // force line break > // textdecoration not used because spaceWidth is 0 > InlineSpace is = new InlineSpace(spaceWidth); > addChild(is); > return i + 1; > } else if (c == '\t') { > prev = WHITESPACE; > spaceWidth = 8 * whitespaceWidth; > } > > } else { > // skip over it > wordStart++; > } > } > > } > > if (isText) { // current is TEXT > > int curr = isMultiByteChar ? MULTIBYTECHAR : TEXT; > if (prev == WHITESPACE) { > > // if current is TEXT and previous WHITESPACE > > wordWidth = charWidth; > if ((finalWidth + spaceWidth + wordWidth) > > this.getContentWidth()) { > if (overrun) > MessageHandler.log("area contents overflows area"); > if (this.wrapOption == WrapOption.WRAP) { > return i; > } > } > prev = curr; > wordStart = i; > wordLength = 1; > } else if (prev == TEXT || prev == MULTIBYTECHAR ) { > if ( prev == TEXT && curr == TEXT || ! canBreakMidWord()) { > wordLength++; > wordWidth += charWidth; > } else { > >// if (spaceWidth > 0) { // for text-align="justify" > 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) { > // The word might contain nonbreaking > // spaces. Split the word and add InlineSpace > // as necessary. All spaces inside the word > // Have a fixed width. > addSpacedWord(new String(data, wordStart, wordLength), > ls, finalWidth, 0, textState, false); > finalWidth += wordWidth; > } > spaceWidth = 0; > wordStart = i; > wordLength = 1; > wordWidth = charWidth; > } > prev = curr; > } else { // nothing previous > > prev = curr; > wordStart = i; > wordLength = 1; > wordWidth = charWidth; > } > > if ((finalWidth + spaceWidth + pendingWidth + wordWidth) > > this.getContentWidth()) { > > // BREAK MID WORD >/* if (canBreakMidWord()) { > addSpacedWord(new String(data, wordStart, wordLength - 1), > ls, > finalWidth + spaceWidth > + embeddedLinkStart, spaceWidth, > textState, false); > finalWidth += wordWidth; > wordWidth = 0; > return i; > } >*/ > if (this.wrapOption == WrapOption.WRAP) { > > if (hyphProps.hyphenate == Hyphenate.TRUE) { > int ret = wordStart; >// Patched by Chris [CW] > ret = this.doHyphenation(dataCopy, i, wordStart, > this.getContentWidth() > - (finalWidth > + spaceWidth > /*+ pendingWidth*/), textState); >// End Patched by Chris [CW] > > // current word couldn't be hypenated > // couldn't fit first word > // I am at the beginning of my line > if ((ret == wordStart) && > (wordStart == start) && > (finalWidth == 0)) { > > MessageHandler.log("area contents overflows area"); > addSpacedWord(new String(data, wordStart, wordLength - 1), > ls, > finalWidth + spaceWidth > + embeddedLinkStart, > spaceWidth, textState, false); > > finalWidth += wordWidth; > wordWidth = 0; > ret = i; > } > return ret; > } else if (wordStart == start) { > // 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 { > return wordStart; > } > > } > } > } > } // end of iteration over text > > if (prev == TEXT || prev == MULTIBYTECHAR) { > > if (spaceWidth > 0) { > InlineSpace pis = new InlineSpace(spaceWidth); > // Make sure that this space doesn't occur as > // first thing in the next line > pis.setEatable(true); > 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; > } > > addSpacedWord(new String(data, wordStart, wordLength), ls, > finalWidth + spaceWidth + embeddedLinkStart, > spaceWidth, textState, true); > > embeddedLinkStart += wordWidth; > wordWidth = 0; > } > > if (overrun) > MessageHandler.log("area contents overflows area"); > 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.getRemainingWidth(); > > /** > * 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: > InlineSpace spaceArea = new InlineSpace(leaderLength); > pendingAreas.addElement(spaceArea); > 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(char c, int leaderLength) { > int width = this.currentFontState.width(currentFontState.mapChar(c)); > if (width == 0) { > 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]; > for (int i = 0; i < factor; i++) { > leaderChars[i] = c; // currentFontState.mapChar(c); > } > 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[characters.length]; // 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 > * Patched by Chris [CW] > */ > public int doHyphenation(char[] characters, int position, int wordStart, > int remainingWidth, TextState textState) { > // Patched by Chris [CW] > // Add Pending Objects because of Hyphenation bug e.g. in word "Altersvorsorgevertrag" > // this causes "Alte" to be added to the current line > StringBuffer pendingText = new StringBuffer(); > Enumeration e = pendingAreas.elements(); > while (e.hasMoreElements()) { > Box box = (Box)e.nextElement(); > if(box instanceof WordArea) { > WordArea pendingWordArea = (WordArea) box; > pendingText.append(pendingWordArea.getText()); >// pendingAreas.remove(box); > } > } > // finalWidth += pendingWidth; > // reset pending areas array if it is now empty >// if(pendingAreas.isEmpty()) { >// pendingWidth = 0; >// pendingAreas = new Vector(); >// } > // Patch continues a little bit down in this method > // End Patch by Chris [CW] > > // 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); > } > > // Patched by Chris [CW] > StringBuffer temp = new StringBuffer(pendingText.toString()); > // Concatenate pending text fragment with the current word fragment > // so that the word is seen by the Hyphenator as the single word > // it should be! > String wordPartToHyphenateInCurrentBuffer = wordToHyphenate.toString(); > wordToHyphenate = temp.append(wordToHyphenate).toString(); > > > // 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 + remainingString.length() > + wordPartToHyphenateInCurrentBuffer.length()]; > > if (inwordPunctuation == '-' || inwordPunctuation == '/') { > preString = new StringBuffer(wordToHyphenate); > preString = preString.append(inwordPunctuation); > wordToHyphenate = > getHyphenationWord(characters, > wordStart + remainingString.length() > + wordPartToHyphenateInCurrentBuffer.length() + 1); > remainingWidth -= > (getWordWidth(wordToHyphenate) > + this.currentFontState.width(currentFontState.mapChar(inwordPunctuation))); > } > } >// End Patched by Chris [CW] > if(wordToHyphenate.trim().equals("Rentenversicherung")) > System.out.println();// 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); >// Patched by Chris [CW] > return wordStart + remainingString.length() - pendingText.length(); >// End Patched by Chris [CW] > // 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); > // Patched by Chris [CW] > > // Remove pending word areas, because we insert the text of > // pending areas in front of the current buffer for hyphenation > Enumeration epend = pendingAreas.elements(); > while (epend.hasMoreElements()) { > Box box = (Box)epend.nextElement(); > if(box instanceof WordArea) { > WordArea pendingWordArea = (WordArea) box; > pendingAreas.remove(box); > } > } > // finalWidth += pendingWidth; // this is done in add(Spaced)Word for the prehyphen text > // reset pending areas array completely if it is now empty > if(pendingAreas.isEmpty()) { > pendingWidth = 0; > pendingAreas = new Vector(); > } > > // add prehypen-word to line area > this.addWord(startChar, remainingString); > > // determine if we hyphenate in the pending area or in the current buffer > int positionPostHyphen = wordStart + remainingString.length() - 1 - pendingText.length(); > if(positionPostHyphen < 0) {// the hyphenation happens somewhere in the pending Area! > // we have to add the possible rest of the characters of the complete word to the > // pending areas of this line area, so that the calling algorithm prints > // them in the next line > > // add rest of characters of the hyphenated word to pending > addSpacedWord(hyph.getPostHyphenText(index), null, 0, 0, textState, true); > > // return position in current buffer directly after the hyphenated word > return wordStart + remainingString.length() - 1 - pendingText.length() + hyph.getPostHyphenText(index).length(); > } > else { // hyphenation happened in current buffer, so nothing more to do > return positionPostHyphen; > } > // End Patched by Chris [CW] > } > // 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); >// Patched by Chris [CW] > // Remove pending word areas, because we insert the text of > // pending areas in front of the current buffer for hyphenation > Enumeration epend = pendingAreas.elements(); > while (epend.hasMoreElements()) { > Box box = (Box)epend.nextElement(); > if(box instanceof WordArea) { > WordArea pendingWordArea = (WordArea) box; > pendingAreas.remove(box); > } > } > // finalWidth += pendingWidth; // this is done in add(Spaced)Word for the prehyphen text > // reset pending areas array completely if it is now empty > if(pendingAreas.isEmpty()) { > pendingWidth = 0; > pendingAreas = new Vector(); > } > this.addWord(startChar, remainingString); > > return wordStart + remainingString.length() - 1 - pendingText.length(); >// End Patched by Chris [CW] > } else { > // Patched by Chris for JDK 1.3 compliance > remainingString.append(preString.toString()); > // End Patch by Chris > // is.addMapWord(startChar,remainingString); > this.addWord(startChar, remainingString); >// Patched by Chris [CW] > return wordStart + remainingString.length() - pendingText.length(); >// End Patched by Chris [CW] > } > } > return wordStart; > } > > > /** > * Calculates the wordWidth using the actual fontstate > */ > private int getWordWidth(String word) { > if (word == null) > return 0; > int wordLength = word.length(); > int width = 0; > char[] characters = new char[wordLength]; > word.getChars(0, wordLength, characters, 0); > > for (int i = 0; i < wordLength; i++) { > width += getCharWidth(characters[i]); > } > return width; > } > > public int getRemainingWidth() { > return this.getContentWidth() + startIndent - 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.getRemainingWidth(); > 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 = getCharWidth(startChar); > > if (isAnySpace(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; > } > > /** > * Checks if it's legal to break a word in the middle > * based on the current language property. > * @return true if legal to break word in the middle > */ > private boolean canBreakMidWord() { > boolean ret = false; > if (hyphProps != null && hyphProps.language != null > &&!hyphProps.language.equals("NONE")) { > String lang = hyphProps.language.toLowerCase(); > if ("zh".equals(lang) || "ja".equals(lang) || "ko".equals(lang) > || "vi".equals(lang)) > ret = true; > } > return ret; > } > > /** > * Helper method for getting the width of a unicode char > * from the current fontstate. > * This also performs some guessing on widths on various > * versions of space that might not exists in the font. > */ > private int getCharWidth(char c) { > int width; > > if ((c == '\n') || (c == '\r') || (c == '\t') || (c == '\u00A0')) { > width = getCharWidth(' '); > } else { > width = currentFontState.width(currentFontState.mapChar(c)); > if (width <= 0) { > // Estimate the width of spaces not represented in > // the font > int em = currentFontState.width(currentFontState.mapChar('m')); > int en = currentFontState.width(currentFontState.mapChar('n')); > if (em <= 0) > em = 500 * currentFontState.getFontSize(); > if (en <= 0) > en = em - 10; > > if (c == ' ') > width = em; > if (c == '\u2000') > width = en; > if (c == '\u2001') > width = em; > if (c == '\u2002') > width = em / 2; > if (c == '\u2003') > width = currentFontState.getFontSize(); > if (c == '\u2004') > width = em / 3; > if (c == '\u2005') > width = em / 4; > if (c == '\u2006') > width = em / 6; > if (c == '\u2007') > width = getCharWidth(' '); > if (c == '\u2008') > width = getCharWidth('.'); > if (c == '\u2009') > width = em / 5; > if (c == '\u200A') > width = 5; > if (c == '\u200B') > width = 100; > if (c == '\u202F') > width = getCharWidth(' ') / 2; > if (c == '\u3000') > width = getCharWidth(' ') * 2; > } > } > > return width; > } > > > /** > * Helper method to determine if the character is a > * space with normal behaviour. Normal behaviour means that > * it's not non-breaking > */ > private boolean isSpace(char c) { > if (c == ' ' || c == '\u2000' || // en quad > c == '\u2001' || // em quad > c == '\u2002' || // en space > c == '\u2003' || // em space > c == '\u2004' || // three-per-em space > c == '\u2005' || // four--per-em space > c == '\u2006' || // six-per-em space > c == '\u2007' || // figure space > c == '\u2008' || // punctuation space > c == '\u2009' || // thin space > c == '\u200A' || // hair space > c == '\u200B') // zero width space > return true; > else > return false; > } > > > /** > * Method to determine if the character is a nonbreaking > * space. > */ > private boolean isNBSP(char c) { > if (c == '\u00A0' || c == '\u202F' || // narrow no-break space > c == '\u3000' || // ideographic space > c == '\uFEFF') { // zero width no-break space > return true; > } else > return false; > } > > /** > * @return true if the character represents any kind of space > */ > private boolean isAnySpace(char c) { > boolean ret = (isSpace(c) || isNBSP(c)); > return ret; > } > > /** > * Add a word that might contain non-breaking spaces. > * Split the word into WordArea and InlineSpace and add it. > * If addToPending is true, add to pending areas. > */ > private void addSpacedWord(String word, LinkSet ls, int startw, > int spacew, TextState textState, > boolean addToPending) { > StringTokenizer st = new StringTokenizer(word, "\u00A0\u202F\u3000\uFEFF", true); > int extraw = 0; > while (st.hasMoreTokens()) { > String currentWord = st.nextToken(); > > if (currentWord.length() == 1 > && (isNBSP(currentWord.charAt(0)))) { > // Add an InlineSpace > int spaceWidth = getCharWidth(currentWord.charAt(0)); > if (spaceWidth > 0) { > InlineSpace is = new InlineSpace(spaceWidth); > extraw += spaceWidth; > if (prevUlState) { > is.setUnderlined(textState.getUnderlined()); > } > if (prevOlState) { > is.setOverlined(textState.getOverlined()); > } > if (prevLTState) { > is.setLineThrough(textState.getLineThrough()); > } > > if (addToPending) { > pendingAreas.addElement(is); > pendingWidth += spaceWidth; > } else { > addChild(is); > } > } > } else { > WordArea ia = new WordArea(currentFontState, this.red, > this.green, this.blue, > currentWord, > getWordWidth(currentWord)); > 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); > > if (addToPending) { > pendingAreas.addElement(ia); > pendingWidth += getWordWidth(currentWord); > } else { > addChild(ia); > } > if (ls != null) { > Rectangle lr = new Rectangle(startw + extraw, spacew, > ia.getContentWidth(), > fontState.getFontSize()); > ls.addRect(lr, this, ia); > } > } > } > } > >} >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 16870
: 4773