return;
}
int textLength = ai.iBreakIndex - ai.iStartIndex;
- if (ai.iLScount == textLength
+ if (ai.iLScount == textLength && !ai.bHyphenated
&& context.isLastArea()) {
// the line ends at a character like "/" or "-";
// remove the letter space after the last character
// 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 a word fragment
if (wordStartIndex == -1) {
// here starts a new word
- wordStartIndex = areaInfo.iStartIndex;
+ 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
- int len = areaInfo.iBreakIndex - wordStartIndex;
- String wordChars = new String(textArray, wordStartIndex, len);
if (isLastArea
&& i == lastIndex
&& areaInfo.bHyphenated) {
- // add the hyphenation character
- wordChars += foText.getCommonHyphenation().hyphenationCharacter;
+ len++;
}
- int[] letterAdjust = new int[wordChars.length()];
- int lsCount = areaInfo.iLScount;
- for (int letter = 0; letter < len; letter++) {
- MinOptMax adj = letterAdjustArray[letter + wordStartIndex];
- if (letter > 0) {
- letterAdjust[letter] = (adj != null ? adj.opt : 0);
- }
- if (lsCount > 0) {
- letterAdjust[letter] += textArea.getTextLetterSpaceAdjust();
- lsCount--;
+ 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++;
}
}
- textArea.addWord(wordChars, 0, letterAdjust);
+ // String wordChars = new String(textArray, wordStartIndex, len);
+ if (isLastArea
+ && i == lastIndex
+ && areaInfo.bHyphenated) {
+ // add the hyphenation character
+ wordChars.append(foText.getCommonHyphenation().hyphenationCharacter);
+ }
+ textArea.addWord(wordChars.toString(), 0, letterAdjust);
wordStartIndex = -1;
}
}
LinkedList returnList = new LinkedList();
KnuthSequence sequence = new InlineKnuthSequence();
AreaInfo ai = null;
+ AreaInfo prevAi = null;
returnList.add(sequence);
LineBreakStatus lbs = new LineBreakStatus();
if (inWord) {
if (breakOpportunity || isSpace(ch) || ch == NEWLINE) {
//Word boundary found, process widths and kerning
- int wordLength = iNextStart - iThisStart;
+ 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 < iNextStart; i++) {
+ for (int i = iThisStart; i < lastIndex; i++) {
char c = textArray[i];
//character width
wordIPD.add(charWidth);
//kerning
- int kern = 0;
- if (kerning && (i > iThisStart)) {
- char previous = textArray[i - 1];
- kern = font.getKernValue(previous, c) * font.getFontSize() / 1000;
+ 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);
}
- 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;
wordIPD.add(MinOptMax.multiply(letterSpaceIPD, iLetterSpaces));
// create the AreaInfo object
- ai = new AreaInfo(iThisStart, iNextStart, (short) 0,
+ ai = new AreaInfo(iThisStart, (short)lastIndex, (short) 0,
(short) iLetterSpaces,
- wordIPD, false, false, breakOpportunity);
+ wordIPD, textArray[lastIndex] == CharUtilities.SOFT_HYPHEN, false, breakOpportunity);
vecAreaInfo.add(ai);
+ prevAi = ai;
iTempStart = iNextStart;
// create the elements
MinOptMax.multiply(wordSpaceIPD, iNextStart - iThisStart),
false, true, breakOpportunity);
vecAreaInfo.add(ai);
+ prevAi = ai;
// create the elements
sequence.addAll
} else {
if (ai != null) {
vecAreaInfo.add(ai);
+ prevAi = ai;
ai.breakOppAfter = ch == CharUtilities.SPACE || breakOpportunity;
sequence.addAll
(createElementsForASpace(alignment, ai, vecAreaInfo.size() - 1));
// Process any last elements
if (inWord) {
- int wordLength = iNextStart - iThisStart;
+ 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 < iNextStart; i++) {
+ for (int i = iThisStart; i < lastIndex; i++) {
char c = textArray[i];
//character width
wordIPD.add(charWidth);
//kerning
- int kern = 0;
- if (kerning && (i > iThisStart)) {
- char previous = textArray[i - 1];
- kern = font.getKernValue(previous, c) * font.getFontSize() / 1000;
+ 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);
}
- wordIPD.add(kern);
}
}
int iLetterSpaces = wordLength - 1;
wordIPD.add(MinOptMax.multiply(letterSpaceIPD, iLetterSpaces));
// create the AreaInfo object
- ai = new AreaInfo(iThisStart, iNextStart, (short) 0,
+ ai = new AreaInfo(iThisStart, (short)lastIndex, (short) 0,
(short) iLetterSpaces,
wordIPD, false, false, false);
vecAreaInfo.add(ai);
// 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;
+ boolean bSuppressibleLetterSpace = ai.breakOppAfter && !ai.bHyphenated;
if (letterSpaceWidth.min == letterSpaceWidth.max) {
// constant letter spacing
// 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));
+ 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));
+ wordElements.addAll(createElementsForAHyphen(alignment, 0, letterSpaceWidth, false));
}
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) {
+ int widthIfBreakOccurs, MinOptMax widthIfNoBreakOccurs, boolean softHyphen) {
if (widthIfNoBreakOccurs == null) {
widthIfNoBreakOccurs = ZERO_MINOPTMAX;
}
new LeafPosition(this, -1), true));
hyphenElements.add
(new KnuthPenalty(hyphIPD,
- KnuthPenalty.FLAGGED_PENALTY, true,
+ softHyphen ? SOFT_HYPHEN_PENALTY : KnuthPenalty.FLAGGED_PENALTY, true,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(-(lineEndBAP + lineStartBAP),
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthPenalty(widthIfBreakOccurs,
- KnuthPenalty.FLAGGED_PENALTY, true,
+ softHyphen ? SOFT_HYPHEN_PENALTY : KnuthPenalty.FLAGGED_PENALTY, true,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(widthIfNoBreakOccurs.opt - (lineStartBAP + lineEndBAP),
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthPenalty(widthIfBreakOccurs,
- KnuthPenalty.FLAGGED_PENALTY, true,
+ softHyphen ? SOFT_HYPHEN_PENALTY : KnuthPenalty.FLAGGED_PENALTY, true,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthGlue(widthIfNoBreakOccurs.opt,
new LeafPosition(this, -1), false));
hyphenElements.add
(new KnuthPenalty(widthIfBreakOccurs,
- KnuthPenalty.FLAGGED_PENALTY, true,
+ softHyphen ? SOFT_HYPHEN_PENALTY : KnuthPenalty.FLAGGED_PENALTY, true,
new LeafPosition(this, -1), false));
// extra elements representing a letter space that is suppressed
// if a break occurs
} else {
hyphenElements.add
(new KnuthPenalty(widthIfBreakOccurs,
- KnuthPenalty.FLAGGED_PENALTY, true,
+ softHyphen ? SOFT_HYPHEN_PENALTY : KnuthPenalty.FLAGGED_PENALTY, true,
new LeafPosition(this, -1), false));
// extra elements representing a letter space that is suppressed
// if a break occurs
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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$ -->
+<testcase>
+ <info>
+ <p>
+ This test checks some of the Soft Hyphen breaking rules.
+ </p>
+ </info>
+ <fo>
+ <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:svg="http://www.w3.org/2000/svg" language="en">
+ <fo:layout-master-set>
+ <fo:simple-page-master master-name="normal" page-width="2.5in" page-height="10in" margin="5pt">
+ <fo:region-body/>
+ </fo:simple-page-master>
+ </fo:layout-master-set>
+ <fo:page-sequence master-reference="normal" white-space-collapse="true">
+ <fo:flow flow-name="xsl-region-body" font-size="10pt">
+ <fo:block background-color="silver" font-size="8pt" margin="3pt 0pt 0pt 0pt">
+ BA -- Break Opportunity After (A) - Conditional Hyphen
+ </fo:block>
+ <fo:block background-color="yellow" margin="0pt 0pt 3pt 0pt">
+ ­Very­Long­Word­With­Lots­­­Of­­­Soft­­­Hyphens­­­Put­­­In­Between­And­Around­
+ </fo:block>
+ <fo:block background-color="silver" font-size="8pt" margin="3pt 0pt 0pt 0pt">
+ BA -- Break Opportunity After (A) - Conditional Hyphen with letter spacing
+ </fo:block>
+ <fo:block background-color="yellow" margin="0pt 0pt 3pt 0pt" letter-spacing="2pt">
+ ­Very­Long­Word­With­Lots­Of­Soft­Hyphens­Put­In­Between­And­Around­
+ </fo:block>
+ </fo:flow>
+ </fo:page-sequence>
+ </fo:root>
+ </fo>
+ <checks>
+ <eval expected="2" xpath="count(//flow/block[2]/lineArea)"/>
+ <eval expected="137270" xpath="//flow/block[2]/lineArea[1]/text/@ipd"/>
+ <eval expected="151750" xpath="//flow/block[2]/lineArea[2]/text/@ipd"/>
+
+ <eval expected="3" xpath="count(//flow/block[4]/lineArea)"/>
+ <eval expected="163480" xpath="//flow/block[4]/lineArea[1]/text/@ipd"/>
+ <eval expected="2000" xpath="//flow/block[4]/lineArea[1]/text/@tlsadjust"/>
+ <eval expected="168840" xpath="//flow/block[4]/lineArea[2]/text/@ipd"/>
+ <eval expected="2000" xpath="//flow/block[4]/lineArea[2]/text/@tlsadjust"/>
+ <eval expected="66030" xpath="//flow/block[4]/lineArea[3]/text/@ipd"/>
+ <eval expected="2000" xpath="//flow/block[4]/lineArea[3]/text/@tlsadjust"/>
+ </checks>
+</testcase>