Index: src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java =================================================================== --- src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java (revision 653204) +++ src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java (working copy) @@ -127,7 +127,7 @@ /** 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; /** @@ -153,7 +153,7 @@ /** 1/2 of word-spacing value */ private SpaceVal halfWS; /** 1/2 of letter-spacing value */ - private SpaceVal halfLS; + private SpaceVal halfLS; private boolean hasChanged = false; private int returnedIndex = 0; @@ -165,7 +165,7 @@ private int lineStartBAP = 0; private int lineEndBAP = 0; - + private boolean keepTogether; private final Position auxiliaryPosition; @@ -178,7 +178,7 @@ 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); @@ -206,21 +206,133 @@ true); } + /** + * Helper class used when building the initial element list + */ + private class WordFragment { + + short startIndex; + short endIndex; + AreaInfo previousAreaInfo; + char boundingChar; + boolean breakOpportunity; + boolean lastIsSoftHyphen; + int[] kernValues; + MinOptMax ipd; + AreaInfo areaInfo; + + WordFragment(short startIndex, + short endIndex, + AreaInfo previousAreaInfo, + boolean breakOpportunity) { + this.startIndex = startIndex; + this.endIndex = endIndex; + this.previousAreaInfo = previousAreaInfo; + this.boundingChar = textArray[Math.min(textArray.length - 1, endIndex + 1)]; + this.breakOpportunity = breakOpportunity; + this.lastIsSoftHyphen = (endIndex < textArray.length + && textArray[endIndex] == CharUtilities.SOFT_HYPHEN); + this.kernValues = new int[endIndex - startIndex]; + } + + MinOptMax getIPD() { + if (ipd == null) { + ipd = new MinOptMax(0); + + char c, previous; + for (int i = startIndex; i < endIndex; i++) { + c = textArray[i]; + + //character width + int charWidth = font.getCharWidth(c); + ipd.add(charWidth); + + //kerning + if (font.hasKerning()) { + int kern = 0; + if (i > startIndex) { + previous = textArray[i - 1]; + kern = font.getKernValue(previous, c) * font.getFontSize() / 1000; + } else if (previousAreaInfo != null + && !previousAreaInfo.isSpace + && previousAreaInfo.breakIndex > 0) { + previous = textArray[previousAreaInfo.breakIndex - 1]; + kern = font.getKernValue(previous, c) * font.getFontSize() / 1000; + } + //log.info("Kerning between " + previous + " and " + c + ": " + kern); + kernValues[i - startIndex] = kern; + ipd.add(kern); + } + } + int letterSpaces = (endIndex - startIndex) - 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(boundingChar)) { + letterSpaces++; + } + ipd.add(MinOptMax.multiply(letterSpaceIPD, letterSpaces)); + } + return ipd; + } + + AreaInfo getAreaInfo() { + if (areaInfo == null) { + areaInfo = new AreaInfo(startIndex, endIndex, (short) 0, + (short) (((endIndex - startIndex) - 1) + (breakOpportunity && !isSpace(boundingChar) ? 1 : 0)), + getIPD(), lastIsSoftHyphen, false, breakOpportunity); + } + return areaInfo; + } + + void processAndAddTo(KnuthSequence sequence, int alignment) { + + for (int i = kernValues.length; --i >= 0;) { + if (kernValues[i] != 0) { + addToLetterAdjust((startIndex + i), kernValues[i]); + } + } + + if (font.hasKerning() + && breakOpportunity + && !isSpace(boundingChar) + && endIndex > 0 + && lastIsSoftHyphen) { + int kern = font.getKernValue(textArray[endIndex - 1], boundingChar) * font.getFontSize() / 1000; + if (kern != 0) { + addToLetterAdjust(endIndex, kern); + } + } + + // create the AreaInfo object + vecAreaInfo.add(getAreaInfo()); + + //add the elements + addElementsForAWordFragment( + sequence, + alignment, + areaInfo, + vecAreaInfo.size() - 1, + letterSpaceIPD); + + } + } + /** {@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: on either side of a word-space) halfWS = new SpaceVal(MinOptMax.multiply(ws.getSpace(), 0.5), @@ -239,7 +351,7 @@ // in the SpaceVal.makeWordSpacing() method letterSpaceIPD = ls.getSpace(); wordSpaceIPD = MinOptMax.add(new MinOptMax(spaceCharIPD), ws.getSpace()); - + keepTogether = foText.getKeepTogether().getWithinLine().getEnum() == Constants.EN_ALWAYS; } @@ -294,9 +406,9 @@ realWidth.add(MinOptMax.multiply(letterSpaceIPD, -1)); letterSpaceCount--; } - + for (int i = ai.startIndex; i < ai.breakIndex; i++) { - MinOptMax ladj = letterAdjustArray[i + 1]; + MinOptMax ladj = letterAdjustArray[i + 1]; if (ladj != null && ladj.isElastic()) { letterSpaceCount++; } @@ -323,7 +435,7 @@ difference = (int) ((double) (realWidth.opt - realWidth.min) * ipdAdjust); } - + // set letter space adjustment if (ipdAdjust > 0.0) { letterSpaceDim @@ -337,7 +449,7 @@ totalAdjust += (letterSpaceDim - letterSpaceIPD.opt) * letterSpaceCount; // set word space adjustment - // + // if (wordSpaceCount > 0) { wordSpaceDim += (difference - totalAdjust) / wordSpaceCount; } else { @@ -366,7 +478,7 @@ // 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 = + // space width = // = "normal" space width + letterSpaceAdjust + wordSpaceAdjust // = spaceCharIPD + letterSpaceAdjust + // + (wordSpaceDim - spaceCharIPD - 2 * letterSpaceAdjust) @@ -391,7 +503,7 @@ * @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 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 */ @@ -446,7 +558,7 @@ // here ends a new word // add a word to the TextArea if (isLastArea - && i == lastIndex + && i == lastIndex && areaInfo.isHyphenated) { len++; } @@ -471,7 +583,7 @@ } // String wordChars = new String(textArray, wordStartIndex, len); if (isLastArea - && i == lastIndex + && i == lastIndex && areaInfo.isHyphenated) { // add the hyphenation character wordChars.append(foText.getCommonHyphenation().getHyphChar(font)); @@ -483,12 +595,12 @@ } 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); @@ -507,7 +619,7 @@ || CharUtilities.isNonBreakableSpace(ch) || CharUtilities.isFixedWidthSpace(ch); } - + /** {@inheritDoc} */ public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { lineStartBAP = context.getLineStartBorderAndPaddingWidth(); @@ -524,6 +636,7 @@ thisStart = nextStart; boolean inWord = false; boolean inWhitespace = false; + boolean forceWrap = (foText.getWrapOption() == EN_WRAP); char ch = 0; while (nextStart < textArray.length) { ch = textArray[nextStart]; @@ -550,60 +663,25 @@ while (lastIndex > 0 && textArray[lastIndex - 1] == CharUtilities.SOFT_HYPHEN) { lastIndex--; } - int wordLength = lastIndex - thisStart; - boolean kerning = font.hasKerning(); - MinOptMax wordIPD = new MinOptMax(0); - for (int i = thisStart; i < lastIndex; i++) { - char c = textArray[i]; + WordFragment wordFragment = new WordFragment(thisStart, lastIndex, prevAi, breakOpportunity); + MinOptMax wordIPD = wordFragment.getIPD(); - //character width - int charWidth = font.getCharWidth(c); - wordIPD.add(charWidth); - - //kerning - if (kerning) { - int kern = 0; - if (i > thisStart) { - char previous = textArray[i - 1]; - kern = font.getKernValue(previous, c) * font.getFontSize() / 1000; - } else if (prevAi != null && !prevAi.isSpace && prevAi.breakIndex > 0) { - char previous = textArray[prevAi.breakIndex - 1]; - kern = font.getKernValue(previous, c) * font.getFontSize() / 1000; + if (forceWrap && wordIPD.min > context.getRefIPD()) { + //add separate elements for each character, so + //the LineLayoutManager can decide on the breaks + WordFragment tmpFragment; + for (short charIndex = thisStart; charIndex < lastIndex; ++charIndex) { + tmpFragment = new WordFragment( + charIndex, (short) (charIndex + 1), prevAi, true); + tmpFragment.processAndAddTo(sequence, alignment); + prevAi = tmpFragment.getAreaInfo(); } - if (kern != 0) { - //log.info("Kerning between " + previous + " and " + c + ": " + kern); - addToLetterAdjust(i, kern); - wordIPD.add(kern); + } else { + wordFragment.processAndAddTo(sequence, alignment); + prevAi = wordFragment.getAreaInfo(); } - } - } - 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(thisStart, lastIndex, (short) 0, - (short) iLetterSpaces, - wordIPD, textArray[lastIndex] == CharUtilities.SOFT_HYPHEN, false, breakOpportunity); - prevAi = ai; - vecAreaInfo.add(ai); + ai = null; tempStart = nextStart; - - //add the elements - addElementsForAWordFragment(sequence, alignment, ai, - vecAreaInfo.size() - 1, letterSpaceIPD); - ai = null; thisStart = nextStart; } } else if (inWhitespace) { @@ -613,7 +691,7 @@ ai = new AreaInfo(thisStart, nextStart, (short) (nextStart - thisStart), (short) 0, MinOptMax.multiply(wordSpaceIPD, nextStart - thisStart), - false, true, breakOpportunity); + false, true, breakOpportunity); vecAreaInfo.add(ai); prevAi = ai; @@ -642,9 +720,9 @@ returnList.add(sequence); } } - - if ((ch == CharUtilities.SPACE - && foText.getWhitespaceTreatment() == Constants.EN_PRESERVE) + + if ((ch == CharUtilities.SPACE + && foText.getWhitespaceTreatment() == Constants.EN_PRESERVE) || ch == CharUtilities.NBSPACE) { // preserved space or non-breaking space: // create the AreaInfo object @@ -657,7 +735,7 @@ MinOptMax ipd = new MinOptMax(font.getCharWidth(ch)); ai = new AreaInfo(nextStart, (short) (nextStart + 1), (short) 0, (short) 0, - ipd, false, true, breakOpportunity); + ipd, false, true, breakOpportunity); thisStart = (short) (nextStart + 1); } else if (ch == NEWLINE) { // linefeed; this can happen when linefeed-treatment="preserve" @@ -667,59 +745,36 @@ inWhitespace = ch == CharUtilities.SPACE && foText.getWhitespaceTreatment() != Constants.EN_PRESERVE; nextStart++; } // end of while - + // Process any last elements if (inWord) { - int lastIndex = nextStart; + short lastIndex = nextStart; if (textArray[nextStart - 1] == CharUtilities.SOFT_HYPHEN) { lastIndex--; } - int wordLength = lastIndex - thisStart; - boolean kerning = font.hasKerning(); - MinOptMax wordIPD = new MinOptMax(0); - for (int i = thisStart; i < lastIndex; i++) { - char c = textArray[i]; + WordFragment wordFragment = new WordFragment(thisStart, lastIndex, prevAi, false); + MinOptMax wordIPD = wordFragment.getIPD(); - //character width - int charWidth = font.getCharWidth(c); - wordIPD.add(charWidth); - - //kerning - if (kerning) { - int kern = 0; - if (i > thisStart) { - char previous = textArray[i - 1]; - kern = font.getKernValue(previous, c) * font.getFontSize() / 1000; - } else if (prevAi != null && !prevAi.isSpace) { - char previous = textArray[prevAi.breakIndex - 1]; - kern = font.getKernValue(previous, c) * font.getFontSize() / 1000; + if (forceWrap && wordIPD.min > context.getRefIPD()) { + //add separate elements for each character, so + //the LineLayoutManager can decide on the breaks + WordFragment tmpFragment; + for (short charIndex = thisStart; charIndex < lastIndex; ++charIndex) { + tmpFragment = new WordFragment( + charIndex, (short) (charIndex + 1), prevAi, true); + tmpFragment.processAndAddTo(sequence, alignment); + prevAi = tmpFragment.getAreaInfo(); } - if (kern != 0) { - //log.info("Kerning between " + previous + " and " + c + ": " + kern); - addToLetterAdjust(i, kern); - wordIPD.add(kern); + } else { + wordFragment.processAndAddTo(sequence, alignment); } - } - } - int iLetterSpaces = wordLength - 1; - wordIPD.add(MinOptMax.multiply(letterSpaceIPD, iLetterSpaces)); - - // create the AreaInfo object - ai = new AreaInfo(thisStart, (short)lastIndex, (short) 0, - (short) iLetterSpaces, - wordIPD, false, false, false); - vecAreaInfo.add(ai); + ai = null; tempStart = nextStart; - - // create the elements - addElementsForAWordFragment(sequence, alignment, ai, - vecAreaInfo.size() - 1, letterSpaceIPD); - ai = null; } else if (inWhitespace) { - ai = new AreaInfo(thisStart, (short) (nextStart), + ai = new AreaInfo(thisStart, nextStart, (short) (nextStart - thisStart), (short) 0, MinOptMax.multiply(wordSpaceIPD, nextStart - thisStart), - false, true, true); + false, true, true); vecAreaInfo.add(ai); // create the elements @@ -996,7 +1051,7 @@ switch (alignment) { case EN_CENTER: // centered text: - // if the second element is chosen as a line break these elements + // 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 baseList.add(new KnuthGlue(lineEndBAP, @@ -1017,7 +1072,7 @@ 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 + // 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 baseList.add(new KnuthGlue(lineEndBAP, @@ -1064,13 +1119,13 @@ baseList.add(makeZeroWidthPenalty(KnuthPenalty.INFINITE)); baseList.add(new KnuthGlue(lineStartBAP + ai.areaIPD.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 + // 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 baseList.add(new KnuthGlue(lineEndBAP, @@ -1091,7 +1146,7 @@ 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 + // 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) { @@ -1164,7 +1219,7 @@ } } } - } + } } private void addElementsForAWordFragment(List baseList, @@ -1190,7 +1245,7 @@ notifyPos(mainPosition), false)); } else { // adjustable letter spacing - int unsuppressibleLetterSpaces + int unsuppressibleLetterSpaces = suppressibleLetterSpace ? ai.letterSpaceCount - 1 : ai.letterSpaceCount; baseList.add (new KnuthInlineBox(ai.areaIPD.opt @@ -1205,7 +1260,7 @@ auxiliaryPosition, true)); baseList.add(makeAuxiliaryZeroWidthBox()); } - + // 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.isHyphenated) { @@ -1215,7 +1270,7 @@ widthIfNoBreakOccurs = letterAdjustArray[ai.breakIndex]; } //if (ai.breakIndex) - + // the word fragment ends at the end of a syllable: // if a break occurs the content width increases, // otherwise nothing happens @@ -1257,7 +1312,7 @@ baseList.add(new KnuthGlue(lineStartBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, auxiliaryPosition, true)); break; - + case EN_START : // fall through case EN_END : // left- or right-aligned text: @@ -1287,7 +1342,7 @@ auxiliaryPosition, false)); } break; - + default: // justified text, or last line justified: // just a flagged penalty @@ -1329,7 +1384,7 @@ } } } - + } - + }