diff options
author | Karen Lease <klease@apache.org> | 2002-05-22 20:20:50 +0000 |
---|---|---|
committer | Karen Lease <klease@apache.org> | 2002-05-22 20:20:50 +0000 |
commit | 963f7ee8c580b22d7cc6200e3a410fc01db8f223 (patch) | |
tree | 2f8e72f884b0b8fe3d2d6b7e0aaffbe8ed1db452 | |
parent | acf92c1f0a8e1697f03afdede7a9061f013f149e (diff) | |
download | xmlgraphics-fop-963f7ee8c580b22d7cc6200e3a410fc01db8f223.tar.gz xmlgraphics-fop-963f7ee8c580b22d7cc6200e3a410fc01db8f223.zip |
Add support for hyphenation and some space distribution among inline areas
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@194837 13f79535-47bb-0310-9956-ffa450edef68
10 files changed, 439 insertions, 252 deletions
diff --git a/src/org/apache/fop/layoutmgr/AbstractBPLayoutManager.java b/src/org/apache/fop/layoutmgr/AbstractBPLayoutManager.java index b3b3ffa01..57725fb4a 100644 --- a/src/org/apache/fop/layoutmgr/AbstractBPLayoutManager.java +++ b/src/org/apache/fop/layoutmgr/AbstractBPLayoutManager.java @@ -134,18 +134,6 @@ public abstract class AbstractBPLayoutManager extends AbstractLayoutManager } -// /** -// * Get the BreakPoss at the start of the next "area". -// * @param lc The LayoutContext for this LayoutManager. -// * @param bpPrevEnd The Position returned by the previous call -// * to getNextBreakPoss, or null if none. -// */ -// public BreakPoss getStartBreakPoss(LayoutContext lc, -// BreakPoss.Position bpPrevEnd) { -// return null; -// } - - /** * Generate and return the next break possibility. * Each layout manager must implement this. @@ -179,7 +167,12 @@ public abstract class AbstractBPLayoutManager extends AbstractLayoutManager } - public void addAreas(PositionIterator parentIter) { + public void addAreas(PositionIterator parentIter, double dSpaceAdjust) { + } + + + public void getWordChars(StringBuffer sbChars, + BreakPoss.Position bp1, BreakPoss.Position bp2) { } /* --------------------------------------------------------- diff --git a/src/org/apache/fop/layoutmgr/BPLayoutManager.java b/src/org/apache/fop/layoutmgr/BPLayoutManager.java index 32f7df9d7..26245b464 100644 --- a/src/org/apache/fop/layoutmgr/BPLayoutManager.java +++ b/src/org/apache/fop/layoutmgr/BPLayoutManager.java @@ -39,10 +39,6 @@ public interface BPLayoutManager extends LayoutManager { public BreakPoss getNextBreakPoss(LayoutContext context); - /** CURRENTLY NOT USED - public BreakPoss getStartBreakPoss(LayoutContext lc, - BreakPoss.Position bpPrevEnd); - **/ /** * Return a value indicating whether this LayoutManager has laid out @@ -62,10 +58,13 @@ public interface BPLayoutManager extends LayoutManager { * by BreakPoss.Position objectw which will be returned by the * Iterator. */ - public void addAreas(PositionIterator posIter) ; + public void addAreas(PositionIterator posIter, double dSpaceAdjust) ; public void init() ; public void resetPosition(BreakPoss.Position position); + public void getWordChars(StringBuffer sbChars, + BreakPoss.Position bp1, BreakPoss.Position bp2); + } diff --git a/src/org/apache/fop/layoutmgr/BreakPoss.java b/src/org/apache/fop/layoutmgr/BreakPoss.java index 80fa797c7..484988160 100644 --- a/src/org/apache/fop/layoutmgr/BreakPoss.java +++ b/src/org/apache/fop/layoutmgr/BreakPoss.java @@ -40,6 +40,8 @@ public class BreakPoss { // Set this flag if all fo:character generated Areas would // suppressed at the end or beginning of a line public static final int ALL_ARE_SUPPRESS_AT_LB = 0x80; + /** This break possibility is a hyphenation */ + public static final int HYPHENATED = 0x100; /** The top-level layout manager which generated this BreakPoss. */ diff --git a/src/org/apache/fop/layoutmgr/BreakPossPosIter.java b/src/org/apache/fop/layoutmgr/BreakPossPosIter.java index a868ac5e5..59a80cec1 100644 --- a/src/org/apache/fop/layoutmgr/BreakPossPosIter.java +++ b/src/org/apache/fop/layoutmgr/BreakPossPosIter.java @@ -35,6 +35,10 @@ public class BreakPossPosIter extends PositionIterator { return super.next(); } + public BreakPoss getBP() { + return (BreakPoss)peekNext(); + } + protected BPLayoutManager getLM(Object nextObj) { return ((BreakPoss)nextObj).getLayoutManager(); } diff --git a/src/org/apache/fop/layoutmgr/HyphContext.java b/src/org/apache/fop/layoutmgr/HyphContext.java new file mode 100644 index 000000000..d89fd704b --- /dev/null +++ b/src/org/apache/fop/layoutmgr/HyphContext.java @@ -0,0 +1,48 @@ +/* + * $Id$ + * Copyright (C) 2002 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.layoutmgr; + + +/** + * This class is used to pass information to the getNextBreakPoss() + * method concerning hyphenation. A reference to an instance of the + * class is contained in the LayoutContext object passed to each + * LayoutManager. It contains information concerning the hyphenation + * points in a word and the how many of those have previously been + * processed by a Layout Manager to generate size information. + */ +public class HyphContext { + private int[] m_hyphPoints; + private int m_iCurOffset=0; + private int m_iCurIndex=0; + + public HyphContext(int[] hyphPoints) { + m_hyphPoints = hyphPoints; + } + + public int getNextHyphPoint() { + for (; m_iCurIndex < m_hyphPoints.length; m_iCurIndex++) { + if (m_hyphPoints[m_iCurIndex] > m_iCurOffset) { + return (m_hyphPoints[m_iCurIndex] - m_iCurOffset); + } + } + return -1; // AT END! + } + + public boolean hasMoreHyphPoints() { + for (; m_iCurIndex < m_hyphPoints.length; m_iCurIndex++) { + if (m_hyphPoints[m_iCurIndex] > m_iCurOffset) { + return true; + } + } + return false; + } + + public void updateOffset(int iCharsProcessed) { + m_iCurOffset += iCharsProcessed; + } +} diff --git a/src/org/apache/fop/layoutmgr/InlineStackingBPLayoutManager.java b/src/org/apache/fop/layoutmgr/InlineStackingBPLayoutManager.java index 2a1763ea3..0f21d0e7b 100644 --- a/src/org/apache/fop/layoutmgr/InlineStackingBPLayoutManager.java +++ b/src/org/apache/fop/layoutmgr/InlineStackingBPLayoutManager.java @@ -15,6 +15,7 @@ import org.apache.fop.area.Area; import org.apache.fop.area.MinOptMax; import org.apache.fop.area.inline.InlineArea; import org.apache.fop.area.inline.InlineParent; +import org.apache.fop.area.inline.Space; import java.util.Iterator; import java.util.ListIterator; @@ -92,7 +93,6 @@ public class InlineStackingBPLayoutManager extends AbstractBPLayoutManager { protected void initProperties(PropertyManager propMgr) { // super.initProperties(propMgr); - System.err.println("InlineStackingBPLayoutManager.initProperties called"); m_inlineProps = propMgr.getInlineProps(); m_borderProps = propMgr.getBorderAndPadding(); // Calculdate border and padding size in BPD @@ -140,7 +140,6 @@ public class InlineStackingBPLayoutManager extends AbstractBPLayoutManager { } else { // Backup to start of first child layout manager - System.err.println("InlineStackingBPLM: resetPosition(null)"); m_prevBP = null; super.resetPosition(prevPos); } @@ -172,6 +171,7 @@ public class InlineStackingBPLayoutManager extends AbstractBPLayoutManager { return (MinOptMax)m_hmPrevIPD.get(lm); } + protected void clearPrevIPD() { m_hmPrevIPD.clear(); } @@ -210,8 +210,12 @@ public class InlineStackingBPLayoutManager extends AbstractBPLayoutManager { initChildLC(m_childLC, m_prevBP, lc.startsNewArea(), bFirstChildBP, leadingSpace); + if (lc.tryHyphenate()) { + m_childLC.setHyphContext(lc.getHyphContext()); + } - if (((bp = curLM.getNextBreakPoss(m_childLC)) != null)) { + if (((bp = curLM.getNextBreakPoss(m_childLC)) != null) || + (lc.tryHyphenate() && !lc.getHyphContext().hasMoreHyphPoints())) { break; } // If LM has no content, should it generate any area? If not, @@ -251,26 +255,6 @@ public class InlineStackingBPLayoutManager extends AbstractBPLayoutManager { } -// protected boolean couldEndLine(BreakPoss bp) { -// if (bp.canBreakAfter()) { -// return true; // no keep, ends on break char -// } -// else if (bp.isSuppressible()) { -// // NOTE: except at end of content for this LM!! -// // Never break after only space chars or any other sequence -// // of areas which would be suppressed at the end of the line. -// return false; -// } -// else { -// // See if could break before next area -// LayoutContext lc=new LayoutContext(); -// BPLayoutManager nextLM = getChildLM(); -// return (nextLM == null || -// nextLM.canBreakBefore(lc)); -// } -// } - - private BreakPoss makeBreakPoss(BreakPoss bp, LayoutContext lc, boolean bIsLast) { @@ -295,39 +279,9 @@ public class InlineStackingBPLayoutManager extends AbstractBPLayoutManager { myBP.setLeadingSpace(lc.getPendingSpace()); } -// if (lc.startsNewArea()) { -// if (hasLeadingFence(!lc.isFirstArea())) { -// // Space-start before first child area placed -// prevIPD.add(bp.resolveLeadingSpace()); -// } -// // Space-start sequence passed to ancestors -// myBP.setLeadingSpace(lc.getPendingSpace()); -// m_hmPrevIPD.put(bp.getLayoutManager(), prevIPD); -// } -// else { -// // In case of reset to a previous position, it may already -// // be calculated -// prevIPD = (MinOptMax)m_hmPrevIPD.get(bp.getLayoutManager()); -// if (prevIPD == null) { -// // ASSERT(m_prevBP.getLayoutManager() != bp.getLayoutManager()); -// /* This is first bp generated by child (in this parent area). -// * Calculate space-start on this area in combination with any -// * pending space-end with previous break. -// * Corresponds to Space between two child areas. -// */ -// prevIPD = -// (MinOptMax)m_hmPrevIPD.get(m_prevBP.getLayoutManager()); -// prevIPD = MinOptMax.add(prevIPD, bp.resolveLeadingSpace()); -// prevIPD.add(m_prevBP.getStackingSize()); -// m_hmPrevIPD.put(bp.getLayoutManager(), prevIPD); -// } -// } + // Add size of previous child areas which are finished bpDim.add(prevIPD); -// if (bp.isLastArea()) { -// m_childLC.setPendingSpace((SpaceSpecifier)bp.getTrailingSpace(). -// clone()); -// } SpaceSpecifier trailingSpace = bp.getTrailingSpace(); if (hasTrailingFence(!bIsLast)) { @@ -362,8 +316,6 @@ public class InlineStackingBPLayoutManager extends AbstractBPLayoutManager { // Space-start before first child area placed prevIPD.add(bp.resolveLeadingSpace()); } - // Space-start sequence passed to ancestors - // myBP.setLeadingSpace(lc.getPendingSpace()); m_hmPrevIPD.put(bp.getLayoutManager(), prevIPD); } else { @@ -377,8 +329,7 @@ public class InlineStackingBPLayoutManager extends AbstractBPLayoutManager { * pending space-end with previous break. * Corresponds to Space between two child areas. */ - prevIPD = - (MinOptMax)m_hmPrevIPD.get(prevBP.getLayoutManager()); + prevIPD = (MinOptMax)m_hmPrevIPD.get(prevBP.getLayoutManager()); prevIPD = MinOptMax.add(prevIPD, bp.resolveLeadingSpace()); prevIPD.add(prevBP.getStackingSize()); m_hmPrevIPD.put(bp.getLayoutManager(), prevIPD); @@ -387,15 +338,32 @@ public class InlineStackingBPLayoutManager extends AbstractBPLayoutManager { return prevIPD; } + public void getWordChars(StringBuffer sbChars, + BreakPoss.Position bp1, BreakPoss.Position bp2) { + WrappedPosition endPos = (WrappedPosition)bp2; + if (bp1 != null) { + WrappedPosition prevPos = (WrappedPosition)bp1; + if (prevPos.m_childLM == endPos.m_childLM) { + bp1 = prevPos.m_childPosition; + } + } + endPos.m_childLM.getWordChars(sbChars, bp1, endPos.m_childPosition); + } /****** protected BreakableText getText(BreakPoss prevBP, BreakPoss lastBP) { } *****/ - // Generate and add areas to parent area - // Set size etc - public void addAreas(PositionIterator parentIter) { + /** + * Generate and add areas to parent area. + * Set size of each area. + * @param parentIter Iterator over BreakPoss.Position information returned + * by this LayoutManager. + * @param dSpaceAdjust Factor controlling how much extra space to add + * in order to justify the line. + */ + public void addAreas(PositionIterator parentIter, double dSpaceAdjust) { // Make areas from start to end // Update childLM based on bpEnd // It might be a previous sibling of the current one! @@ -410,7 +378,7 @@ public class InlineStackingBPLayoutManager extends AbstractBPLayoutManager { StackingIter childPosIter = new StackingIter(parentIter); BPLayoutManager childLM ; while ((childLM = childPosIter.getNextChildLM())!= null) { - childLM.addAreas(childPosIter); + childLM.addAreas(childPosIter, dSpaceAdjust); } parentLM.addChild(m_inlineArea); diff --git a/src/org/apache/fop/layoutmgr/LayoutContext.java b/src/org/apache/fop/layoutmgr/LayoutContext.java index 12e693716..a3dd778ce 100644 --- a/src/org/apache/fop/layoutmgr/LayoutContext.java +++ b/src/org/apache/fop/layoutmgr/LayoutContext.java @@ -34,6 +34,7 @@ public class LayoutContext { */ public static final int SUPPRESS_LEADING_SPACE = 0x10; public static final int FIRST_AREA = 0x20; + public static final int TRY_HYPHENATE = 0x40; public int flags; // Contains some set of flags defined above @@ -56,11 +57,15 @@ public class LayoutContext { /** Current pending space-after or space-end from preceding area */ SpaceSpecifier m_pendingSpace; + /** Current hyphenation context. May be null. */ + private HyphContext m_hyphContext=null; + public LayoutContext(LayoutContext parentLC) { this.flags = parentLC.flags; this.refIPD = parentLC.refIPD; this.m_stackLimit = null; // Don't reference parent MinOptMax! this.m_pendingSpace = parentLC.m_pendingSpace; //??? + this.m_hyphContext = parentLC.m_hyphContext; // Copy other fields as necessary. Use clone??? } @@ -119,4 +124,16 @@ public class LayoutContext { public MinOptMax getStackLimit() { return m_stackLimit ; } + + public void setHyphContext(HyphContext hyphContext) { + m_hyphContext = hyphContext; + } + + public HyphContext getHyphContext() { + return m_hyphContext; + } + + public boolean tryHyphenate() { + return ((this.flags & TRY_HYPHENATE) != 0); + } } diff --git a/src/org/apache/fop/layoutmgr/LineBPLayoutManager.java b/src/org/apache/fop/layoutmgr/LineBPLayoutManager.java index 0517fc47f..ff3cffd64 100644 --- a/src/org/apache/fop/layoutmgr/LineBPLayoutManager.java +++ b/src/org/apache/fop/layoutmgr/LineBPLayoutManager.java @@ -12,6 +12,9 @@ import org.apache.fop.fo.FObj; import org.apache.fop.fo.TextInfo; import org.apache.fop.fo.PropertyManager; import org.apache.fop.layout.MarginProps; +import org.apache.fop.layout.HyphenationProps; +import org.apache.fop.layout.hyphenation.Hyphenation; +import org.apache.fop.layout.hyphenation.Hyphenator; import org.apache.fop.traits.BlockProps; import org.apache.fop.area.Area; import org.apache.fop.area.LineArea; @@ -62,6 +65,7 @@ public class LineBPLayoutManager extends private boolean m_bJustify = false; // True if fo:block text-align=JUSTIFY private int m_iTextIndent = 0; private int m_iIndents = 0; + private HyphenationProps m_hyphProps; private int lineHeight; private int lead; @@ -77,13 +81,12 @@ public class LineBPLayoutManager extends } protected void initProperties(PropertyManager propMgr) { - // super.initProperties(propMgr); - System.err.println("LineBPLayoutManager.initProperties called"); MarginProps marginProps = propMgr.getMarginProps(); m_iIndents = marginProps.startIndent + marginProps.endIndent; BlockProps blockProps = propMgr.getBlockProps(); m_bJustify = (blockProps.textAlign == TextAlign.JUSTIFY); m_iTextIndent = blockProps.firstIndent; + m_hyphProps = propMgr.getHyphenationProps(); } @@ -120,6 +123,7 @@ public class LineBPLayoutManager extends LayoutContext inlineLC = new LayoutContext(context); clearPrevIPD(); + int iPrevLineEnd = m_vecInlineBreaks.size(); while ((curLM = getChildLM()) != null) { // INITIALIZE LAYOUT CONTEXT FOR CALL TO CHILD LM @@ -129,39 +133,31 @@ public class LineBPLayoutManager extends (((BreakPoss)m_vecInlineBreaks.lastElement()). getLayoutManager() != curLM)); - initChildLC(inlineLC, bp, (bp==null), bFirstBPforLM, + // Need previous breakpoint! ATTENTION when backing up for hyphenation! + prevBP = (m_vecInlineBreaks.isEmpty())? null: + (BreakPoss)m_vecInlineBreaks.lastElement(); + initChildLC(inlineLC, prevBP, + (m_vecInlineBreaks.size()==iPrevLineEnd), bFirstBPforLM, new SpaceSpecifier(true)); -// if (bp == null) { -// // Start of a new line area -// inlineLC.setFlags(LayoutContext.FIRST_AREA, bFirstBPforLM ); -// inlineLC.setPendingSpace(new SpaceSpecifier(true)); -// } -// else if (bFirstBPforLM) { -// // Space-after sequence from previous "area" -// inlineLC.setFlags(LayoutContext.FIRST_AREA, true); -// inlineLC.setPendingSpace(bp.getTrailingSpace()); -// } -// else { -// inlineLC.setPendingSpace(null); -// } - /* If first BP in this line but line is not first in this * LM and previous line end decision was not forced (LINEFEED), * then set the SUPPRESS_LEADING_SPACE flag. */ inlineLC.setFlags(LayoutContext.SUPPRESS_LEADING_SPACE, - (bp == null && !m_vecInlineBreaks.isEmpty() && + (prevBP == null && !m_vecInlineBreaks.isEmpty() && ((BreakPoss)m_vecInlineBreaks.lastElement()). isForcedBreak()==false)); // GET NEXT POSSIBLE BREAK FROM CHILD LM - prevBP = bp; + // prevBP = bp; if ((bp = curLM.getNextBreakPoss(inlineLC, null)) != null) { // Add any space before and previous content dimension - MinOptMax prevIPD = updatePrevIPD(bp, prevBP, (prevBP==null), - inlineLC.isFirstArea()); + MinOptMax prevIPD = + updatePrevIPD(bp, prevBP, + (m_vecInlineBreaks.size()==iPrevLineEnd), + inlineLC.isFirstArea()); MinOptMax bpDim = MinOptMax.add(bp.getStackingSize(), prevIPD); // check if this bp fits in line @@ -175,41 +171,40 @@ public class LineBPLayoutManager extends // TODO: stop if linebreak is forced (NEWLINE) // PROBLEM: interaction with wrap which can be set // at lower levels! - System.err.println("BPdim=" + bpDim.opt); + // System.err.println("BPdim=" + bpDim.opt); // Check if proposed area would fit in line if (bpDim.min > availIPD.max) { // See if we have already found a potential break - if (vecPossEnd.size() > 0) break; + //if (vecPossEnd.size() > 0) break; // This break position doesn't fit // TODO: If we are in nowrap, we use it as is! if (m_bJustify || m_prevBP == null) { - // try to find a hyphenation point in the word - // which spans the queued breaks and the proposed bp - // Even if not justified, we must try to hyphenate if - // there is no breakpoint at all up to this point! - do { - bp = findHyphenPoss(m_prevBP, bp); - } while (bp != null && - (bp.getStackingSize().min > availIPD.max)); - if (bp == null) { - // Couldn't find a hyphenation point. The line - // will be "short". + // If we are already in a hyphenation loop, then stop. + + if (inlineLC.tryHyphenate()) { + break; } - else { - m_prevBP = bp; + // Otherwise, prepare to try hyphenation + if (!bBreakOK) { + // Make sure we collect the entire word! + m_vecInlineBreaks.add(bp); + continue; } - // Handle pendingIPD if any. The hyphenation point - // may be within the "pending" content or after it. - /* Make sure child LM are updated concerning the actual - * hyphenation BreakPoss for their next call! - */ + + inlineLC.setHyphContext(getHyphenContext(m_prevBP, bp)); + if (inlineLC.getHyphContext()==null) break; + inlineLC.setFlags(LayoutContext.TRY_HYPHENATE, true); + // Reset to previous acceptable break + reset(); } /* If we are not in justified text, we can end the line at * prevBP. */ - break; + else { + break; + } } else { // Add the BP to the list whether or not we can break @@ -229,7 +224,6 @@ public class LineBPLayoutManager extends * (ie, bpDim.opt closes to availIPD.opt), keeps * and hyphenation. */ - System.err.println("Found potential linebreak"); vecPossEnd.add(new BreakCost(bp, Math.abs(availIPD.opt - bpDim.opt ))); } @@ -246,13 +240,17 @@ public class LineBPLayoutManager extends * up. Just try the next child LM. */ } + if (inlineLC.tryHyphenate() && + !inlineLC.getHyphContext().hasMoreHyphPoints()) { + break; + } } // end of while on child LM if ((curLM = getChildLM())== null) { // No more content to layout! setFinished(true); } -if(bp == null) return null; + if(bp == null) return null; // Choose the best break if (!bp.isForcedBreak() && vecPossEnd.size()>0) { @@ -260,11 +258,7 @@ if(bp == null) return null; } // Backup child LM if necessary if (bp != m_prevBP) { - // Remove any pending breaks from the vector - while (m_vecInlineBreaks.lastElement()!=m_prevBP) { - m_vecInlineBreaks.remove(m_vecInlineBreaks.size()-1); - } - reset(m_prevBP.getLayoutManager(), m_prevBP.getPosition()); + reset(); } // Distribute space in the line MinOptMax actual = MinOptMax.add(m_prevBP.getStackingSize(), @@ -282,6 +276,14 @@ if(bp == null) return null; return makeLineBreak(m_prevBP, availIPD, actual, bJustify); } + + private void reset() { + while (m_vecInlineBreaks.lastElement()!=m_prevBP) { + m_vecInlineBreaks.remove(m_vecInlineBreaks.size()-1); + } + reset(m_prevBP.getLayoutManager(), m_prevBP.getPosition()); + } + protected boolean couldEndLine(BreakPoss bp) { if (bp.canBreakAfter()) { return true; // no keep, ends on break char @@ -303,6 +305,7 @@ if(bp == null) return null; } + private BreakPoss getBestBP(Vector vecPossEnd) { if (vecPossEnd.size()==1) { return ((BreakCost)vecPossEnd.elementAt(0)).getBP(); @@ -333,13 +336,62 @@ if(bp == null) return null; - private BreakPoss findHyphenPoss(BreakPoss prevBP, BreakPoss newBP) { + private HyphContext getHyphenContext(BreakPoss prevBP, BreakPoss newBP) { // Get a "word" to hyphenate by getting characters from all // pending break poss which are in m_vecInlineBreaks, starting - // with the position just AFTER prevBP.getPosition() - return null; + // with the position just AFTER prevBP.getPosition() + + m_vecInlineBreaks.add(newBP); + ListIterator bpIter = m_vecInlineBreaks. + listIterator(m_vecInlineBreaks.size()); + while (bpIter.hasPrevious() && bpIter.previous() != prevBP); + if (bpIter.next() != prevBP) { + System.err.println("findHyphenPoss: problem!"); + return null; + } + StringBuffer sbChars = new StringBuffer(30); + while (bpIter.hasNext()) { + BreakPoss bp = (BreakPoss)bpIter.next(); + if (bp.getLayoutManager() == prevBP.getLayoutManager()) { + bp.getLayoutManager().getWordChars(sbChars, prevBP.getPosition(), + bp.getPosition()); + } + else { + bp.getLayoutManager().getWordChars(sbChars, null, bp.getPosition()); + } + prevBP = bp; + } + m_vecInlineBreaks.remove(m_vecInlineBreaks.size()-1); // remove last + System.err.println("Word to hyphenate: " + sbChars.toString()); + + // Now find all hyphenation points in this word (get in an array of offsets) + // hyphProps are from the block level?. Note that according to the spec, + // they also "apply to" fo:character. I don't know what that means, since + // if we change language in the middle of a "word", the effect would seem + // quite strange! Or perhaps in that case, we say that it's several words. + // We probably should bring the hyphenation props up from the actual + // TextLM which generate the hyphenation buffer, since these properties + // inherit and could be specified on an inline or wrapper below the block + // level. + Hyphenation hyph = + Hyphenator.hyphenate(m_hyphProps.language, m_hyphProps.country, + sbChars.toString(), + m_hyphProps.hyphenationRemainCharacterCount, + m_hyphProps.hyphenationPushCharacterCount); + // They hyph structure contains the information we need + // Now start from prevBP: reset to that position, ask that LM to get + // a Position for the first hyphenation offset. If the offset isn't in + // its characters, it returns null, but must tell how many chars it had. + // Keep looking at currentBP using next hyphenation point until the + // returned size is greater than the available size or no more hyphenation + // points remain. Choose the best break. + if (hyph != null) { + return new HyphContext(hyph.getHyphenationPoints()); + } + else return null; } + private BreakPoss makeLineBreak(BreakPoss inlineBP, MinOptMax target, MinOptMax actual, boolean bJustify) { // make a new BP @@ -377,20 +429,28 @@ if(bp == null) return null; // Generate and add areas to parent area // Set size etc - public void addAreas(PositionIterator parentIter) { + // dSpaceAdjust should reference extra space in the BPD + public void addAreas(PositionIterator parentIter, double dSpaceAdjust) { BPLayoutManager childLM ; int iStartPos = 0; while (parentIter.hasNext()) { LineBreakPosition lbp = (LineBreakPosition)parentIter.next(); - System.err.println("lbp.endpos=" + lbp.m_iPos); m_lineArea = new LineArea(); // Add the inline areas to lineArea - PositionIterator inlinePosIter = + BreakPossPosIter inlinePosIter = new BreakPossPosIter(m_vecInlineBreaks, iStartPos, lbp.m_iPos+1); iStartPos = lbp.m_iPos+1; while ((childLM = inlinePosIter.getNextChildLM())!= null) { - childLM.addAreas(inlinePosIter); + BreakPoss bp = inlinePosIter.getBP(); + int iSpaceSize = getLeadingSpace(bp, lbp.m_dAdjust); + if (iSpaceSize != 0) { + System.err.println("Add leading space: " + iSpaceSize); + Space ls = new Space(); + ls.setWidth(iSpaceSize); + addChild(ls); + } + childLM.addAreas(inlinePosIter, lbp.m_dAdjust); } m_lineArea.verticalAlign(lineHeight, lead, follow); parentLM.addChild(m_lineArea); @@ -398,6 +458,26 @@ if(bp == null) return null; m_lineArea = null; } + protected int getLeadingSpace(BreakPoss bp, double dSpaceAdjust) { + MinOptMax leadSpace = bp.resolveLeadingSpace(); + if (leadSpace != null) { + int iAdjust=0; + if (dSpaceAdjust > 0.0) { + // Stretch by factor + iAdjust = (int)((double)(leadSpace.max - leadSpace.opt) * + dSpaceAdjust); + } + else if (dSpaceAdjust < 0.0) { + // Shrink by factor + iAdjust = (int)((double)(leadSpace.opt - leadSpace.min) * + dSpaceAdjust); + } + return leadSpace.opt + iAdjust; + } + else return 0; + } + + public boolean addChild(Area childArea) { // Make sure childArea is inline area if (childArea instanceof InlineArea) { @@ -440,7 +520,7 @@ if(bp == null) return null; } } } - addAreas(new BreakPossPosIter(vecBreakPoss, 0, vecBreakPoss.size())); + addAreas(new BreakPossPosIter(vecBreakPoss, 0, vecBreakPoss.size()), 0.0); return false; } diff --git a/src/org/apache/fop/layoutmgr/PositionIterator.java b/src/org/apache/fop/layoutmgr/PositionIterator.java index be8978312..cd98c4136 100644 --- a/src/org/apache/fop/layoutmgr/PositionIterator.java +++ b/src/org/apache/fop/layoutmgr/PositionIterator.java @@ -83,6 +83,10 @@ abstract class PositionIterator implements Iterator } } + protected Object peekNext() { + return m_nextObj; + } + public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException("PositionIterator doesn't support remove"); } diff --git a/src/org/apache/fop/layoutmgr/TextBPLayoutManager.java b/src/org/apache/fop/layoutmgr/TextBPLayoutManager.java index f1bec939d..7cf628e2c 100644 --- a/src/org/apache/fop/layoutmgr/TextBPLayoutManager.java +++ b/src/org/apache/fop/layoutmgr/TextBPLayoutManager.java @@ -52,10 +52,13 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { private class AreaInfo { short m_iStartIndex; short m_iBreakIndex; + short m_iWScount; MinOptMax m_ipdArea; - AreaInfo(short iStartIndex, short iBreakIndex, MinOptMax ipdArea) { + AreaInfo(short iStartIndex, short iBreakIndex, short iWScount, + MinOptMax ipdArea) { m_iStartIndex = iStartIndex; m_iBreakIndex = iBreakIndex; + m_iWScount = iWScount; m_ipdArea = ipdArea; } } @@ -71,19 +74,13 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { private TextInfo textInfo; private static final char NEWLINE = '\n'; - private static final char RETURN = '\r'; - private static final char TAB = '\t'; - private static final char SPACE = ' '; + private static final char SPACE = '\u0020'; // Normal space + private static final char NBSPACE = '\u00A0'; // Non-breaking space private static final char LINEBREAK = '\u2028'; private static final char ZERO_WIDTH_SPACE = '\u200B'; // byte order mark private static final char ZERO_WIDTH_NOBREAK_SPACE = '\uFEFF'; - /* values that prev (below) may take */ - protected static final int NOTHING = 0; - protected static final int WHITESPACE = 1; - protected static final int TEXT = 2; - /** Start index of first character in this parent Area */ private short m_iAreaStart = 0; /** Start index of next "word" */ @@ -94,6 +91,8 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { // private MinOptMax m_nextIPD= new MinOptMax(0); /** size of a space character (U+0020) glyph in current font */ private int m_spaceIPD; + /** size of the hyphen character glyph in current font */ + private int m_hyphIPD; /** 1/2 of word-spacing value */ private SpaceVal m_halfWS; /** Number of space characters after previous possible break position. */ @@ -109,6 +108,8 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { // With CID fonts, space isn't neccesary currentFontState.width(32) m_spaceIPD = CharUtilities.getCharWidth(' ', textInfo.fs); + // Use hyphenationChar property + m_hyphIPD = CharUtilities.getCharWidth('-', textInfo.fs); // Make half-space: <space> on either side of a word-space) SpaceVal ws = textInfo.wordSpacing; m_halfWS = new SpaceVal(MinOptMax.multiply(ws.space, 0.5), @@ -145,42 +146,17 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { } - // NOTE: currently not used. Remove if decide it isn't necessary! -// /** -// * Get the BreakPoss at the start of the next line. -// * @param bpPrevEnd The BreakPoss at the end of the previous line -// * or null if we should return the point at the beginning of this -// * text run. -// */ -// public BreakPoss getStartBreakPoss(LayoutContext lc, -// BreakPoss.Position bpPrevEnd) { -// BreakPoss bp = null; -// if (bpPrevEnd == null) { -// bp = new BreakPoss(this, new TextBreakPosition(0)); -// // Set minimum bpd (character ascent and descent) -// // Or do this at the line level??? -// } -// else { -// // Skip suppressible white-space -// // ASSERT (((TextBreakPosition)bpPrevEnd).m_iAreaIndex = -// // m_iNextStart) -// if ((lc.flags & LayoutContext.SUPPRESS_LEADING_SPACE)!=0) { -// /* Skip any leading word-space characters. */ -// for (; m_iNextStart < chars.length && -// chars[m_iNextStart]==SPACE; m_iNextStart++); -// } -// // If now at end, nothing to compose here! -// if (m_iNextStart >= chars.length) { -// return null; // Or an "empty" BreakPoss? -// } -// else { -// bp = new BreakPoss(this, -// new TextBreakPosition(m_iNextStart)); -// } -// } -// return bp; -// } - + public void getWordChars(StringBuffer sbChars, + BreakPoss.Position bp1, BreakPoss.Position bp2) { + TextBreakPosition endPos = (TextBreakPosition)bp2; + AreaInfo ai = + (AreaInfo) m_vecAreaInfo.elementAt(endPos.m_iAreaIndex); + // Skip all leading spaces for hyphenation + int i; + for (i=ai.m_iStartIndex;i < ai.m_iBreakIndex && + CharUtilities.isAnySpace(chars[i])==true ;i++); + sbChars.append(new String(chars, i, ai.m_iBreakIndex-i)); + } /** * Return value indicating whether the next area to be generated could @@ -204,13 +180,14 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { if (ai.m_iBreakIndex != m_iNextStart) { m_iNextStart = ai.m_iBreakIndex; m_vecAreaInfo.setSize(tbp.m_iAreaIndex+1); - System.err.println("Discarded previous text break pos"); + // TODO: reset or recalculate total IPD = sum of all word IPD + // up to the break position + m_ipdTotal = ai.m_ipdArea; setFinished(false); } } else { // Reset to beginning! - System.err.println("TextBPLM: resetPosition(null)"); m_vecAreaInfo.setSize(0); m_iNextStart = 0; setFinished(false); @@ -218,6 +195,30 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { } + // TODO: see if we can use normal getNextBreakPoss for this with + // extra hyphenation information in LayoutContext + private boolean getHyphenIPD(HyphContext hc, MinOptMax hyphIPD) { + // Skip leading word-space before calculating count? + boolean bCanHyphenate = true; + int iStopIndex = m_iNextStart + hc.getNextHyphPoint(); + + if (chars.length < iStopIndex || textInfo.bCanHyphenate==false ) { + iStopIndex = chars.length; + bCanHyphenate = false; + } + hc.updateOffset(iStopIndex - m_iNextStart); + + for (; m_iNextStart < iStopIndex; m_iNextStart++) { + char c = chars[m_iNextStart]; + hyphIPD.opt += CharUtilities.getCharWidth(c, textInfo.fs); + // letter-space? + } + // Need to include hyphen size too, but don't count it in the + // stored running total, since it would be double counted + // with later hyphenation points + return bCanHyphenate; + } + /** * Return the next break possibility that fits the constraints. * @param context An object specifying the flags and input information @@ -250,15 +251,17 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { */ m_ipdTotal = new MinOptMax(0); iFlags |= BreakPoss.ISFIRST; - // May have leading space too which can combine with a - // leading word-space or letter-space } - // HANDLE SUPPRESSED LEADING SPACES + /* HANDLE SUPPRESSED LEADING SPACES + * See W3C XSL Rec. 7.16.3. + * Suppress characters whose "suppress-at-line-break" property = "suppress" + * This can only be set on an explicit fo:character object. The default + * behavior is that U+0020 is suppressed; all other character codes are + * retained. + */ if (context.suppressLeadingSpace()) { - /* If any leading space characters, ignore them. */ - // NOTE: Skips word-space chars only, not other white-space! for (; m_iNextStart < chars.length && chars[m_iNextStart]==SPACE; m_iNextStart++); // If now at end, nothing to compose here! @@ -269,23 +272,33 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { } - // Start of this "word", plus any non-suppressed leading space - // This is any kind of white-space, not just word spaces - - short iThisStart = m_iNextStart; - MinOptMax spaceIPD = new MinOptMax(0); // Variable IPD - int wordIPD = 0; // Non-stretching IPD (length in base units) + /* Start of this "word", plus any non-suppressed leading space. + * Collapse any remaining word-space with leading space from + * ancestor FOs. + * Add up other leading space which is counted in the word IPD. + */ - // Handle inter-character spacing (word-space + letter-space) - // What about context.getPendingSpace() on first char in word? - SpaceSpecifier pendingSpace = new SpaceSpecifier(false); + SpaceSpecifier pendingSpace= new SpaceSpecifier(false); + short iThisStart = m_iNextStart; // Index of first character counted + MinOptMax spaceIPD = new MinOptMax(0); // Extra IPD from word-spacing + // Sum of glyph IPD of all characters in a word, inc. leading space + int wordIPD = 0; + short iWScount=0; // Count of word spaces for (; m_iNextStart < chars.length; m_iNextStart++) { char c = chars[m_iNextStart]; if (CharUtilities.isAnySpace(c)==false) break; - if (c==SPACE) { - pendingSpace.addSpace(m_halfWS); - spaceIPD.add(pendingSpace.resolve(false)); + if (c==SPACE || c==NBSPACE) { + ++iWScount; + // Counted as word-space + if (m_iNextStart == iThisStart && + (iFlags & BreakPoss.ISFIRST) !=0 ) { + context.getPendingSpace().addSpace(m_halfWS); + } + else { + pendingSpace.addSpace(m_halfWS); + spaceIPD.add(pendingSpace.resolve(false)); + } wordIPD += m_spaceIPD; // Space glyph IPD pendingSpace.clear(); pendingSpace.addSpace(m_halfWS); @@ -305,64 +318,79 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { else { // This FO ended with spaces. Return the BP iFlags |= BreakPoss.ALL_ARE_SUPPRESS_AT_LB; - // lc.trailingSpaceSeq.addSpace(m_halfWS); - // Need to make SpaceSpecifier from m_halfWS! - // Or at least a spaceval - return makeBreakPoss(iThisStart, spaceIPD, 0, pendingSpace, - iFlags); + return makeBreakPoss(iThisStart, spaceIPD, wordIPD, + context.getPendingSpace(), pendingSpace, + iFlags, iWScount); } + if (context.tryHyphenate()) { + // Get the size of the next syallable + MinOptMax hyphIPD = new MinOptMax(0); + if (getHyphenIPD(context.getHyphContext(), hyphIPD)) { + iFlags |= (BreakPoss.CAN_BREAK_AFTER | BreakPoss.HYPHENATED); + } + wordIPD += hyphIPD.opt; + } + else { // Look for a legal line-break: breakable white-space and certain // characters such as '-' which can serve as word breaks. // Don't look for hyphenation points here though - - for (; m_iNextStart < chars.length; m_iNextStart++) { - char c = chars[m_iNextStart]; - if ((c == NEWLINE) || - // Include any breakable white-space as break char - // even if fixed width - (textInfo.bWrap && - (CharUtilities.isSpace(c) || - s_breakChars.indexOf(c)>=0))) { - iFlags |= BreakPoss.CAN_BREAK_AFTER; - if (c != SPACE) { - m_iNextStart++; - if (c != NEWLINE) { - wordIPD += CharUtilities.getCharWidth(c, textInfo.fs); - } - else { - iFlags |= BreakPoss.FORCE; + for (; m_iNextStart < chars.length; m_iNextStart++) { + char c = chars[m_iNextStart]; + if ((c == NEWLINE) || + // Include any breakable white-space as break char + // even if fixed width + (textInfo.bWrap && + (CharUtilities.isSpace(c) || + s_breakChars.indexOf(c)>=0))) { + iFlags |= BreakPoss.CAN_BREAK_AFTER; + if (c != SPACE) { + m_iNextStart++; + if (c != NEWLINE) { + wordIPD += CharUtilities.getCharWidth(c, textInfo.fs); + } + else { + iFlags |= BreakPoss.FORCE; + } } - } - return makeBreakPoss(iThisStart, spaceIPD, wordIPD, null, - iFlags); - } - wordIPD += CharUtilities.getCharWidth(c, textInfo.fs); - // Note, if a normal non-breaking space, is it stretchable??? - // If so, keep a count of these embedded spaces. - } - return makeBreakPoss(iThisStart, spaceIPD, wordIPD, null, iFlags); + return makeBreakPoss(iThisStart, spaceIPD, wordIPD, + context.getPendingSpace(), null, + iFlags, iWScount); + } + wordIPD += CharUtilities.getCharWidth(c, textInfo.fs); + // Note, if a normal non-breaking space, is it stretchable??? + // If so, keep a count of these embedded spaces. + } + } + return makeBreakPoss(iThisStart, spaceIPD, wordIPD, + context.getPendingSpace(), null, iFlags, iWScount); } private BreakPoss makeBreakPoss(short iWordStart, MinOptMax spaceIPD, - int wordDim, + int wordDim, SpaceSpecifier leadingSpace, SpaceSpecifier trailingSpace, - int flags) + int flags, short iWScount) { MinOptMax ipd = new MinOptMax(wordDim); ipd.add(spaceIPD); + ipd.add(m_ipdTotal); // sum of all words so far in line + // Note: break position now stores total size to here // Position is the index of the info for this word in the vector - m_vecAreaInfo.add(new AreaInfo(iWordStart, m_iNextStart, ipd)); + m_vecAreaInfo.add(new AreaInfo(iWordStart, m_iNextStart, iWScount, ipd)); BreakPoss bp = new BreakPoss(this, new TextBreakPosition(m_vecAreaInfo.size()-1)); - - ipd.add(m_ipdTotal); // sum of all words so far in line - bp.setStackingSize(ipd); m_ipdTotal = ipd; - // TODO: make this correct (see Keiron's code below!) + if ((flags & BreakPoss.HYPHENATED)!=0) { + // Add the hyphen size, but don't change total IPD! + bp.setStackingSize(MinOptMax.add(ipd, new MinOptMax(m_hyphIPD))); + } + else { + bp.setStackingSize(ipd); + } + // TODO: make this correct (see Keiron's vertical alignment code) bp.setNonStackingSize(new MinOptMax(textInfo.lineHeight)); /* Set max ascender and descender (offset from baseline), @@ -382,28 +410,72 @@ public class TextBPLayoutManager extends AbstractBPLayoutManager { else { bp.setTrailingSpace(new SpaceSpecifier(false)); } - bp.setLeadingSpace(new SpaceSpecifier(false)); + if (leadingSpace != null) { + bp.setLeadingSpace(leadingSpace); + } + else { + bp.setLeadingSpace(new SpaceSpecifier(false)); + } return bp; } /** - * Add an area for each word and space (or one big one????) + * Generate and add areas to parent area. + * This can either generate an area for each "word" and each space, or + * an area containing all text with a parameter controlling the size of + * the word space. The latter is most efficient for PDF generation. + * Set size of each area. + * @param parentIter Iterator over BreakPoss.Position information returned + * by this LayoutManager. + * @param dSpaceAdjust Factor controlling how much extra space to add + * in order to justify the line. */ - public void addAreas(PositionIterator posIter) { + public void addAreas(PositionIterator posIter, double dSpaceAdjust) { // Add word areas - TextBreakPosition tbpStart, tbpNext; + AreaInfo ai=null ; + int iStart = -1; + int iWScount = 0; + + /* On first area created, add any leading space. + * Calculate word-space stretch value. + */ while (posIter.hasNext()) { - tbpNext = (TextBreakPosition)posIter.next(); - // System.err.println("tbp.pos = " + tbpNext.m_iAreaIndex); - AreaInfo ai = (AreaInfo)m_vecAreaInfo. - elementAt(tbpNext.m_iAreaIndex); - // Make an area containing all characters between start and end. - Word word = createWord(new String(chars, ai.m_iStartIndex, - ai.m_iBreakIndex- ai.m_iStartIndex), - ai.m_ipdArea.opt); - parentLM.addChild(word); + TextBreakPosition tbpNext = (TextBreakPosition)posIter.next(); + ai = (AreaInfo)m_vecAreaInfo.elementAt(tbpNext.m_iAreaIndex); + if (iStart == -1) { + iStart = ai.m_iStartIndex; + } + iWScount += ai.m_iWScount; + } + // Calculate total adjustment + int iAdjust = 0; + if (dSpaceAdjust > 0.0) { + // Stretch by factor + System.err.println("Potential stretch = " + + (ai.m_ipdArea.max - ai.m_ipdArea.opt)); + iAdjust = (int)((double)(ai.m_ipdArea.max - ai.m_ipdArea.opt) * + dSpaceAdjust); + } + else if (dSpaceAdjust < 0.0) { + // Shrink by factor + System.err.println("Potential shrink = " + + (ai.m_ipdArea.opt - ai.m_ipdArea.min)); + iAdjust = (int)((double)(ai.m_ipdArea.opt - ai.m_ipdArea.min) * + dSpaceAdjust); + } + System.err.println("Text adjustment factor = " + dSpaceAdjust + + " total=" + iAdjust); + if (iWScount > 0) { + System.err.println("Adjustment per word-space= " + iAdjust/iWScount); + } + // Make an area containing all characters between start and end. + Word word = createWord(new String(chars, iStart, ai.m_iBreakIndex - iStart), + ai.m_ipdArea.opt + iAdjust); + if (chars[iStart] == SPACE || chars[iStart] == NBSPACE ) { + // word.setLeadingSpace(m_halfWS); } + parentLM.addChild(word); } |