diff options
author | Karen Lease <klease@apache.org> | 2002-04-28 21:31:00 +0000 |
---|---|---|
committer | Karen Lease <klease@apache.org> | 2002-04-28 21:31:00 +0000 |
commit | e7182108a113301eec9213b41ead70994d2c6a75 (patch) | |
tree | 003518dc73a1a5c77441b90c64c4ef89774bf0ac /src/org/apache/fop | |
parent | 0b8f96f941357fb49da8eca12e60164b77e4db47 (diff) | |
download | xmlgraphics-fop-e7182108a113301eec9213b41ead70994d2c6a75.tar.gz xmlgraphics-fop-e7182108a113301eec9213b41ead70994d2c6a75.zip |
New files for the BreakPoss(ibility) Layout Manager scheme
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@194759 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/org/apache/fop')
-rw-r--r-- | src/org/apache/fop/layoutmgr/AbstractBPLayoutManager.java | 134 | ||||
-rw-r--r-- | src/org/apache/fop/layoutmgr/BPLayoutManager.java | 67 | ||||
-rw-r--r-- | src/org/apache/fop/layoutmgr/BreakPoss.java | 206 | ||||
-rw-r--r-- | src/org/apache/fop/layoutmgr/BreakPossPosIter.java | 40 | ||||
-rw-r--r-- | src/org/apache/fop/layoutmgr/LayoutContext.java | 104 | ||||
-rw-r--r-- | src/org/apache/fop/layoutmgr/LineBPLayoutManager.java | 418 | ||||
-rw-r--r-- | src/org/apache/fop/layoutmgr/PositionIterator.java | 90 | ||||
-rw-r--r-- | src/org/apache/fop/layoutmgr/TextBPLayoutManager.java | 412 |
8 files changed, 1471 insertions, 0 deletions
diff --git a/src/org/apache/fop/layoutmgr/AbstractBPLayoutManager.java b/src/org/apache/fop/layoutmgr/AbstractBPLayoutManager.java new file mode 100644 index 000000000..9a0218f74 --- /dev/null +++ b/src/org/apache/fop/layoutmgr/AbstractBPLayoutManager.java @@ -0,0 +1,134 @@ +/* + * $Id$ + * 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.layoutmgr; + +import org.apache.fop.fo.FObj; +import org.apache.fop.fo.PropertyManager; +import org.apache.fop.fo.FONode; +import org.apache.fop.area.Area; + +import java.util.ListIterator; +import java.util.ArrayList; + +/** + * The base class for all BPLayoutManagers. + */ +public abstract class AbstractBPLayoutManager extends AbstractLayoutManager + implements BPLayoutManager { + + + /** True if this LayoutManager has handled all of its content. */ + private boolean m_bFinished = false; + + + public AbstractBPLayoutManager(FObj fobj) { + super(fobj); + } + + + /** + * This method provides a hook for a LayoutManager to intialize traits + * for the areas it will create, based on Properties set on its FO. + */ + protected final void initProperties() { + if (fobj != null) { + initProperties(fobj.getPropertyManager()); + } + } + + + /** + * This method provides a hook for a LayoutManager to intialize traits + * for the areas it will create, based on Properties set on its FO. + */ + protected void initProperties(PropertyManager pm) { + System.err.println("AbstractBPLayoutManager.initProperties"); + } + + + /** + * Tell whether this LayoutManager has handled all of its content. + * @return True if there are no more break possibilities, + * ie. the last one returned represents the end of the content. + */ + public boolean isFinished() { + return m_bFinished; + } + + public void setFinished(boolean bFinished) { + m_bFinished = bFinished; + } + + +// /** +// * 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. + * TODO: should this be abstract or is there some reasonable + * default implementation? + */ + public BreakPoss getNextBreakPoss(LayoutContext context) { + return getNextBreakPoss(context, null); + } + + + public BreakPoss getNextBreakPoss(LayoutContext context, + BreakPoss.Position prevBreakPoss) { + return null; + } + + /** + * Return value indicating whether the next area to be generated could + * start a new line or flow area. + * In general, if can't break at the current level, delegate to + * the first child LM. + * NOTE: should only be called if the START_AREA flag is set in context, + * since the previous sibling LM must have returned a BreakPoss which + * does not allow break-after. + * QUESTION: in block-stacked areas, does this mean some kind of keep + * condition, or is it only used for inline-stacked areas? + * Default implementation always returns true. + */ + public boolean canBreakBefore(LayoutContext context) { + return true; + } + + + public void addAreas(PositionIterator parentIter) { + } + + /* --------------------------------------------------------- + * PROVIDE NULL IMPLEMENTATIONS OF METHODS from LayoutManager + * interface which are declared abstract in AbstractLayoutManager. + * ---------------------------------------------------------*/ + public Area getParentArea(Area childArea) { + return null; + } + + protected boolean flush() { + return false; + } + + + + public boolean addChild(Area childArea) { + return false; + } +} + diff --git a/src/org/apache/fop/layoutmgr/BPLayoutManager.java b/src/org/apache/fop/layoutmgr/BPLayoutManager.java new file mode 100644 index 000000000..34bb5b126 --- /dev/null +++ b/src/org/apache/fop/layoutmgr/BPLayoutManager.java @@ -0,0 +1,67 @@ +/* + * $Id$ + * 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.layoutmgr; + + +import org.apache.fop.area.Area; + +/** + * The interface for all BreakPoss LayoutManagers. + */ +public interface BPLayoutManager extends LayoutManager { + + /** + * Return true if the next area which would be generated by this + * LayoutManager could start a new line (or flow for block-level FO). + */ + public boolean canBreakBefore(LayoutContext lc); + + /** + * Generate and return the next break possibility. + * @param context The layout context contains information about pending + * space specifiers from ancestor areas or previous areas, reference + * area inline-progression-dimension and various other layout-related + * information. + * @param prevBreakPosition If not null, gives a Position returned by + * this layout manager on a previous call to getNextBreakPoss. It may not + * be the previous one returned. The Layout Manager should return the next + * potential Break Possibility after prevBreakPosition. + * If prevBreakPosition is null, it should return the first possible + * BreakPoss. + */ + public BreakPoss getNextBreakPoss(LayoutContext context, + BreakPoss.Position prevBreakPosition); + + 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 + * all its content (or generated BreakPossibilities for all content.) + */ + public boolean isFinished() ; + + /** + * Set a flag indicating whether the LayoutManager has laid out all + * its content. This is generally called by the LM itself, but can + * be called by a parentLM when backtracking. + */ + public void setFinished(boolean isFinished) ; + + /** + * Tell the layout manager to add all the child areas implied + * by BreakPoss.Position objectw which will be returned by the + * Iterator. + */ + public void addAreas(PositionIterator posIter) ; + +} diff --git a/src/org/apache/fop/layoutmgr/BreakPoss.java b/src/org/apache/fop/layoutmgr/BreakPoss.java new file mode 100644 index 000000000..eb4ad4200 --- /dev/null +++ b/src/org/apache/fop/layoutmgr/BreakPoss.java @@ -0,0 +1,206 @@ +/* + * $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; + +import org.apache.fop.area.MinOptMax; +import org.apache.fop.traits.LayoutProps; + +/** + * Represents a break possibility for the layout manager. + * Used to pass information between different levels of layout manager concerning + * the break positions. In an inline context, before and after are interpreted as + * start and end. + * The m_position field is opaque but should represent meaningful information to + * the layout manager stored in m_lm. + * @author Karen Lease + */ +public class BreakPoss { + + /** + * Marker interface. Generally a LayoutManager class will include + * a class implementing this interface which it uses to store its + * own Break Position information. + */ + public interface Position { + } + + + /** Values for m_flags returned from lower level LM. */ + public static final int CAN_BREAK_AFTER= 0x01; // May break after + public static final int ISLAST= 0x02; // Last area generated by FO + public static final int ISFIRST= 0x04; // First area generated by FO + public static final int FORCE= 0x08; // Forced break (ie LF) + public static final int CAN_BREAK_BEFORE=0x10; + public static final int NEED_IPD = 0x20; + public static final int HAS_ANCHORS = 0x40; + // 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; + + + /** The top-level layout manager which generated this BreakPoss. */ + private BPLayoutManager m_lm; + + /** The opaque position object used by m_lm to record its + * break position. + */ + private Position m_position; + + /** + * The size range in the stacking direction of the area which would be + * generated if this BreakPoss were used. + */ + private MinOptMax m_stackSize; + + /** + * Max height above and below the baseline. These are cumulative. + */ + private int m_iMaxAscender; + private int m_iMaxDescender; + + /** Size in the non-stacking direction (perpendicular). */ + private MinOptMax m_nonStackSize; + + private long m_flags = 0; + private LayoutProps m_layoutProps = new LayoutProps(); + /** + private boolean m_bIsFirst=false; + private boolean m_bIsLast=false; + private boolean m_bCanBreakAfter; + private boolean m_bCanBreakBefore; + **/ + + /** Store space-after (or end) and space-before (or start) to be + * added if this break position is used. + */ + private SpaceSpecifier m_spaceSpecTrailing; + private SpaceSpecifier m_spaceSpecLeading; + + public BreakPoss(BPLayoutManager lm, Position position) { + this(lm,position,0); + } + + public BreakPoss(BPLayoutManager lm, Position position, int flags) { + m_lm = lm; + m_position = position; + m_flags = flags; + } + + /** + * The top-level layout manager responsible for this break + */ + public BPLayoutManager getLayoutManager() { + return m_lm; + } + + public void setLayoutManager(BPLayoutManager lm) { + m_lm = lm; + } + + /** + * An object representing the break position in this layout manager. + */ + public Position getPosition() { + return m_position; + } + + public void setPosition(Position pos) { + m_position = pos; + } + + public void setStackingSize(MinOptMax size) { + this.m_stackSize = size; + } + + public MinOptMax getStackingSize() { + return this.m_stackSize ; + } + + public void setNonStackingSize(MinOptMax size) { + this.m_nonStackSize = size; + } + + public MinOptMax getNonStackingSize() { + return this.m_nonStackSize ; + } + + public void setFlag(int flagBit) { + setFlag(flagBit, true); + } + + public void setFlag(int flagBit, boolean bSet) { + if (bSet) { + m_flags |= flagBit; + } + else { + m_flags &= ~flagBit; + } + } + + public boolean isLastArea() { + return ((m_flags & ISLAST) != 0); + } + + public boolean isFirstArea() { + return ((m_flags & ISFIRST) != 0); + } + + public boolean canBreakAfter() { + return ((m_flags & CAN_BREAK_AFTER) != 0); + } + + public boolean canBreakBefore() { + return ((m_flags & CAN_BREAK_BEFORE) != 0); + } + + public boolean isForcedBreak() { + return ((m_flags & FORCE) != 0); + } + + public boolean isSuppressible() { + return ((m_flags & ALL_ARE_SUPPRESS_AT_LB) != 0); + } + + public SpaceSpecifier getLeadingSpace() { + return m_spaceSpecLeading; + } + + public MinOptMax resolveLeadingSpace() { + if (m_spaceSpecLeading != null) { + return m_spaceSpecLeading.resolve(false); + } + else return new MinOptMax(0); + } + + public SpaceSpecifier getTrailingSpace() { + return m_spaceSpecTrailing; + } + + public MinOptMax resolveTrailingSpace(boolean bEndsRefArea) { + if (m_spaceSpecTrailing != null) { + return m_spaceSpecTrailing.resolve(bEndsRefArea); + } + else return new MinOptMax(0); + } + + + public void setLeadingSpace(SpaceSpecifier spaceSpecLeading) { + m_spaceSpecLeading = spaceSpecLeading; + } + + public void setTrailingSpace(SpaceSpecifier spaceSpecTrailing) { + m_spaceSpecTrailing = spaceSpecTrailing; + } + + public LayoutProps getLayoutProps() { + return m_layoutProps; + } + + public boolean checkIPD() { + return ((m_flags & NEED_IPD) != 0); + } +} diff --git a/src/org/apache/fop/layoutmgr/BreakPossPosIter.java b/src/org/apache/fop/layoutmgr/BreakPossPosIter.java new file mode 100644 index 000000000..0188b2731 --- /dev/null +++ b/src/org/apache/fop/layoutmgr/BreakPossPosIter.java @@ -0,0 +1,40 @@ +/* + * $Id$ + * 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.layoutmgr; + + +import java.util.List; + +public class BreakPossPosIter extends PositionIterator { + private int m_iterCount ; + + BreakPossPosIter(List bpList, int startPos, int endPos) { + super(bpList.listIterator(startPos)); + m_iterCount = endPos - startPos; + } + + // Check position < endPos + + protected boolean checkNext() { + return (m_iterCount > 0 && super.checkNext()); + } + + public Object next() { + --m_iterCount; + return super.next(); + } + + protected BPLayoutManager getLM(Object nextObj) { + return ((BreakPoss)nextObj).getLayoutManager(); + } + + protected BreakPoss.Position getPos(Object nextObj) { + return ((BreakPoss)nextObj).getPosition(); + } + +} diff --git a/src/org/apache/fop/layoutmgr/LayoutContext.java b/src/org/apache/fop/layoutmgr/LayoutContext.java new file mode 100644 index 000000000..bad0a871f --- /dev/null +++ b/src/org/apache/fop/layoutmgr/LayoutContext.java @@ -0,0 +1,104 @@ +/* + * $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; + +import org.apache.fop.area.MinOptMax; + +/** + * This class is used to pass information to the getNextBreakPoss() + * method. It is set up by higher level LM and used by lower level LM. + */ +public class LayoutContext { + /** + * Values for flags. + */ + public static final int LINEBREAK_AT_LF_ONLY = 0x01; + public static final int START_BLOCK = 0x02; + public static final int START_AREA = 0x02; // inline too + public static final int IPD_UNKNOWN = 0x04; + /** Signal to a Line LM that a higher level LM may provoke a change + * in the reference area, thus ref area IPD. The LineLM should return + * without looking for a line break. + */ + public static final int CHECK_REF_AREA = 0x08; + + /** + * If this flag is set, it indicates that any leading fo:character + * objects with suppress-at-line-break="suppress" should not generate + * areas. This is the case at the beginning of each new LineArea + * except the first. + */ + public static final int SUPPRESS_LEADING_SPACE = 0x10; + + + public int flags; // Contains some set of flags defined above + /** + * Total available stacking dimension for a "galley-level" layout + * manager (Line or Flow). It is passed by the parent LM. For LineLM, + * the block LM determines this based on indent properties. + * These LM <b>may</b> wish to pass this information down to lower + * level LM to allow them to optimize returned break possibilities. + */ + MinOptMax stackLimit; + + /** Current stacking dimension, either IPD or BPD, depending on + * caller. This is generally the value returned by a previous call + * to getNextBreakPoss(). + */ + MinOptMax m_stackSize; + + /** True if current top-level reference area is spanning. */ + boolean bIsSpan; + + /** inline-progression-dimension of nearest ancestor reference area */ + int refIPD; + + /** Current pending space-after or space-end from preceding area */ + SpaceSpecifier m_pendingSpace; + + public LayoutContext(LayoutContext parentLC) { + this.flags = parentLC.flags; + this.refIPD = parentLC.refIPD; + this.stackLimit = null; // Don't reference parent MinOptMax! + this.m_pendingSpace = parentLC.m_pendingSpace; //??? + // Copy other fields as necessary. Use clone??? + } + + public LayoutContext() { + this.flags = 0; + this.refIPD = 0; + stackLimit = new MinOptMax(0); + } + + public void setFlags(int flags) { + this.flags |= flags; + } + + public void unsetFlags(int flags) { + this.flags &= ~flags; + } + + public boolean isStart() { + return ((this.flags & START_BLOCK) != 0); + } + + public void setPendingSpace(SpaceSpecifier space) { + m_pendingSpace = space; + } + + public SpaceSpecifier getPendingSpace() { + return m_pendingSpace; + } + + public void setStackSize(MinOptMax stackSize) { + m_stackSize = stackSize; + } + + public MinOptMax getStackSize() { + return m_stackSize ; + } +} diff --git a/src/org/apache/fop/layoutmgr/LineBPLayoutManager.java b/src/org/apache/fop/layoutmgr/LineBPLayoutManager.java new file mode 100644 index 000000000..86417d024 --- /dev/null +++ b/src/org/apache/fop/layoutmgr/LineBPLayoutManager.java @@ -0,0 +1,418 @@ +/* + * $Id$ + * 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.layoutmgr; + + +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.traits.BlockProps; +import org.apache.fop.area.Area; +import org.apache.fop.area.LineArea; +import org.apache.fop.area.MinOptMax; +import org.apache.fop.area.inline.InlineArea; +import org.apache.fop.fo.properties.TextAlign; + +import org.apache.fop.area.inline.Word; +import org.apache.fop.area.inline.Space; +import org.apache.fop.area.inline.Character; + +import java.util.ListIterator; +import java.util.List; +import java.util.Vector; +import java.util.ArrayList; + + +/** + * BPLayoutManager for lines. It builds one or more lines containing + * inline areas generated by its sub layout managers. + */ +public class LineBPLayoutManager extends LineLayoutManager { + + /** + * Private class to store information about inline breaks. + * Each value holds the start and end indexes into a List of + * inline break positions. + */ + private static class LineBreakPosition implements BreakPoss.Position { + int m_iPos; + + LineBreakPosition(int iBreakIndex) { + m_iPos = iBreakIndex; + } + } + + private BPLayoutManager m_curChildLM=null; + private ListIterator m_childLMiter; + + private LineArea m_lineArea; // LineArea currently being filled + + /** Break positions returned by inline content. */ + private Vector m_vecInlineBreaks = new Vector(100); + + private SpaceSpecifier m_pendingSpace; + private BreakPoss m_prevBP = null; // Last confirmed break position + private boolean m_bJustify = false; // True if fo:block text-align=JUSTIFY + private int m_iTextIndent = 0; + private int m_iIndents = 0; + + + public LineBPLayoutManager(FObj fobj, List lms, int lh, int l, int f) { + super(fobj, lms, lh, l, f); + m_childLMiter = lms.listIterator(); + initProperties(); + } + + 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; + } + + + /** + * Return next child LayoutManager or null if there is none. + * Note: child must implement BPLayoutManager! If it doesn't, skip it + * and print a warning. + * The list of all child layout managers is in lmList (in superclass!) + */ + private BPLayoutManager getChildLM() { + if (m_curChildLM != null && !m_curChildLM.isFinished()) { + return m_curChildLM; + } + while (m_childLMiter.hasNext()) { + Object obj = m_childLMiter.next(); + if (obj instanceof BPLayoutManager) { + m_curChildLM = (BPLayoutManager)obj; + m_curChildLM.setParentLM(this); + return m_curChildLM; + } + else { + m_childLMiter.remove(); + System.err.println("WARNING: child of LineLPLayoutManager not a BPLayoutManager: " + obj.getClass().getName()); + } + } + return null; + } + + /** + * Reset the layoutmanager "iterator" so that it will start + * with the passed bplm on the next call to getChildLM. + * @param bplm Reset iterator to this LayoutManager. + */ + private void resetChildLM(BPLayoutManager bplm) { + if (bplm == null) return; + while (m_curChildLM != bplm && m_childLMiter.hasPrevious()) { + m_curChildLM = (BPLayoutManager)m_childLMiter.previous(); + } + if ( m_curChildLM.isFinished()) { + m_curChildLM.setFinished(false); + } + } + + + + /** + * Call child layout managers to generate content as long as they + * generate inline areas. If a block-level generating LM is found, + * finish any line being filled and return to the parent LM. + */ + public BreakPoss getNextBreakPoss(LayoutContext context, + BreakPoss.Position prevLineBP) { + // Get a break from currently active child LM + // Set up constraints for inline level managers + + if ((context.flags & LayoutContext.CHECK_REF_AREA) != 0) { + /* Return a BreakPoss indicating that higher level LM + * (page) should check reference area and possibly + * create a new one. + */ + return new BreakPoss(this, null, BreakPoss.NEED_IPD); + } + + BPLayoutManager prevLM = null; // previous active LM + BPLayoutManager curLM ; // currently active LM + BreakPoss bp=null; // proposed BreakPoss + + // IPD remaining in line + MinOptMax availIPD = context.stackLimit; + + // IPD of any unbreakable finished FO content + MinOptMax pendingIPD = null; + + // QUESTION: maybe LayoutContext holds the Properties which + // come from block-level? + + LayoutContext inlineLC = new LayoutContext(context); + inlineLC.setPendingSpace(new SpaceSpecifier(true)); + + while ((curLM = getChildLM()) != null) { + // INITIALIZE FLAGS FOR CALL TO CHILD LM + boolean bFirstBPforLM = (prevLM != curLM); + if (bFirstBPforLM) { + prevLM = curLM; + inlineLC.setFlags(LayoutContext.START_AREA); + if (bp != null) { + inlineLC.setPendingSpace(bp.getTrailingSpace()); + } + } + else { + inlineLC.unsetFlags(LayoutContext.START_AREA); + inlineLC.setPendingSpace(null); + } + /* If first BP in this line but line is not first in this + * LM and previous possible linebreak was not forced (LINEFEED), + * then set the SUPPRESS_LEADING_SPACE flag. + */ + if (bp == null && !m_vecInlineBreaks.isEmpty() && + ((BreakPoss)m_vecInlineBreaks.lastElement()). + isForcedBreak()==false) { + inlineLC.setFlags(LayoutContext.SUPPRESS_LEADING_SPACE); + } + else { + inlineLC.unsetFlags(LayoutContext.SUPPRESS_LEADING_SPACE); + } + // GET NEXT POSSIBLE BREAK FROM CHILD LM + if ((bp = curLM.getNextBreakPoss(inlineLC, + (m_prevBP !=null ? + m_prevBP.getPosition() : + null))) != null) { + // check if this bp fits in line + MinOptMax bpDim = (MinOptMax)bp.getStackingSize().clone(); + /* If first BP for this LM (in this call) + * add any leading space. + */ + if (bFirstBPforLM) { + if (pendingIPD != null) { + bpDim.add(pendingIPD); + } + bpDim.add(bp.resolveLeadingSpace()); + } + boolean bBreakOK = couldEndLine(bp); + if (bBreakOK) { + /* Add any non-conditional trailing space. */ + bpDim.add(bp.resolveTrailingSpace(true)); + } + // Check if proposed area would fit in line + if (bpDim.max < availIPD.min) { + // Break fits buts is short + if (bBreakOK) { + if (pendingIPD != null) { + availIPD.subtract(pendingIPD); + // Subtract space-start for this area, since + // we know at least part of it fits. + availIPD.subtract(bp.getLeadingSpace(). + resolve(false)); + pendingIPD = null; + // Add all pending BP list members to BP list + } + m_prevBP = bp; // Save reference to this BP + m_vecInlineBreaks.add(bp); + // Handle end of this LM's areas + if (bp.isLastArea()) { + /* NOTE: this doesn't include space-end since + * it may combine with space-start on the + * following FO's first area. + */ + availIPD.subtract(bp.getStackingSize()); + } + } + else { + /* Can't end line here, so mark size pending. + * This includes any previosly pending size, + * already calculated above. + */ + pendingIPD = bpDim; + // Add BP to the pending list + } + } + else if (bpDim.min > availIPD.max) { + // This break position doesn't fit + 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". + } + else { + m_prevBP = bp; + } + // 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! + */ + } + /* If we are not in justified text, we can end the line at + * prevBP. + */ + break; + } + else { + /* This is a possible line BP (line could be filled) + * bpDim.max >= availIPD.min + * Keep this as a possible break, depending on "cost". + * We will choose lowest cost. Cost depends on stretch + * (ie, bpDim.opt closes to availIPD.opt), keeps + * and hyphenation. + */ + m_prevBP = bp; + break; //??? + } + } // end of getNextBreakPoss!=null on current child LM + else { + /* What if the childLM returns null? + * No further break possibility in current sequence of + * inline LM. Previous break or end of area will be the + * ending for the current line. + * Otherwise we have filled the current line. + */ + } + } // end of while on child LM + if ((curLM = getChildLM())== null) { + // No more content to layout! + setFinished(true); + } + // Backup layoutmanager if necessary + resetChildLM(m_prevBP.getLayoutManager()); + return makeLineBreak(m_prevBP); + } + + /** + * Return whether we could end the line at the proposed Position. + * TODO: take keeps into account and distinguish the cost of a + * the break-completely forbidden or some non-0 cost. + * QUESTION: do we need to pass the current LineLM or childLM + * LayoutContext? + */ + private 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 findHyphenPoss(BreakPoss prevBP, BreakPoss newBP) { + return null; + } + + private BreakPoss makeLineBreak(BreakPoss inlineBP) { + // make a new BP + BreakPoss curLineBP = + new BreakPoss(this, + new LineBreakPosition(m_vecInlineBreaks.size()-1)); + + /* FIX ME!! + * Need to calculate line height based on all inline BP info + * for this line not just the current inlineBP! + */ + curLineBP.setFlag(BreakPoss.ISLAST, isFinished()); + curLineBP.setStackingSize(inlineBP.getNonStackingSize()); + return curLineBP; + } + + + // Generate and add areas to parent area + // Set size etc + public void addAreas(PositionIterator parentIter) { + 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 = + new BreakPossPosIter(m_vecInlineBreaks, + iStartPos, lbp.m_iPos+1); + iStartPos = lbp.m_iPos+1; + while (inlinePosIter.hasNext() && + (childLM = inlinePosIter.getNextChildLM())!= null) { + childLM.addAreas(inlinePosIter); + } + verticalAlign(m_lineArea); + parentLM.addChild(m_lineArea); + } + m_lineArea = null; + } + + public boolean addChild(Area childArea) { + // Make sure childArea is inline area + if (childArea instanceof InlineArea) { + m_lineArea.addInlineArea((InlineArea)childArea); + } + return false; + } + + // NOTE: PATCHED FOR NOW TO ADD BreakPoss stuff to Kerion's changes + public boolean generateAreas() { + // Make break positions and return lines! + // Set up a LayoutContext + int ipd = 0; + BreakPoss bp; + Vector vecBreakPoss = new Vector(20); + + LayoutContext childLC = new LayoutContext(); + // Force area creation on first call + // NOTE: normally not necessary + childLC.flags |= LayoutContext.CHECK_REF_AREA; + + while (!isFinished()) { + if ((bp = getNextBreakPoss(childLC, null)) != null) { + if (bp.checkIPD()) { + // Need IPD in order to layout lines! + // This is supposed to bubble up to PageLM to + // make the necessary flow reference area, depending + // on span and break-before flags set as the BreakPoss + // makes its way back up the call stack. + // Fake it for now! + parentLM.getParentArea(null); + ipd = parentLM.getContentIPD(); + childLC.flags &= ~LayoutContext.CHECK_REF_AREA; + childLC.stackLimit = new MinOptMax(ipd - m_iIndents - + m_iTextIndent); + } + else { + vecBreakPoss.add(bp); + // Reset stackLimit for non-first lines + childLC.stackLimit = new MinOptMax(ipd - m_iIndents); + } + } + } + addAreas(new BreakPossPosIter(vecBreakPoss, 0, vecBreakPoss.size())); + return false; + } + + +} + diff --git a/src/org/apache/fop/layoutmgr/PositionIterator.java b/src/org/apache/fop/layoutmgr/PositionIterator.java new file mode 100644 index 000000000..be8978312 --- /dev/null +++ b/src/org/apache/fop/layoutmgr/PositionIterator.java @@ -0,0 +1,90 @@ +/* + * $Id$ + * 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.layoutmgr; + + +import java.util.Iterator; +import java.util.NoSuchElementException; + +abstract class PositionIterator implements Iterator +{ + Iterator m_parentIter; + Object m_nextObj; + BPLayoutManager m_childLM; + boolean m_bHasNext; + + PositionIterator(Iterator parentIter) { + m_parentIter = parentIter; + lookAhead(); + //checkNext(); + } + + BPLayoutManager getNextChildLM() { + // Move to next "segment" of iterator, ie: new childLM + if (m_childLM == null && m_nextObj != null) { + m_childLM = getLM(m_nextObj); + m_bHasNext = true; + } + return m_childLM; + } + + abstract protected BPLayoutManager getLM(Object nextObj); + + abstract protected BreakPoss.Position getPos(Object nextObj); + + private void lookAhead() { + if (m_parentIter.hasNext()) { + m_bHasNext = true; + m_nextObj = m_parentIter.next(); + } + else { + endIter(); + } + } + + protected boolean checkNext() { + BPLayoutManager lm = getLM(m_nextObj); + if (m_childLM==null) { + m_childLM = lm; + } + else if (m_childLM != lm) { + // End of this sub-sequence with same child LM + m_bHasNext = false; + m_childLM = null; + return false; + } + return true; + } + + protected void endIter() { + m_bHasNext = false; + m_nextObj = null; + m_childLM = null; + } + + public boolean hasNext() { + return (m_bHasNext && checkNext()); + } + + + public Object next() throws NoSuchElementException { + if (m_bHasNext) { + Object retObj = getPos(m_nextObj); + lookAhead(); + return retObj; + } + else { + throw new NoSuchElementException("PosIter"); + } + } + + 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 new file mode 100644 index 000000000..8c99cbc3b --- /dev/null +++ b/src/org/apache/fop/layoutmgr/TextBPLayoutManager.java @@ -0,0 +1,412 @@ +/* + * $Id$ + * 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.layoutmgr; + +import org.apache.fop.fo.FObj; +import org.apache.fop.fo.TextInfo; +import org.apache.fop.traits.SpaceVal; +import org.apache.fop.area.Area; +import org.apache.fop.area.LineArea; +import org.apache.fop.area.MinOptMax; +import org.apache.fop.area.Trait; +import org.apache.fop.area.inline.InlineArea; +import org.apache.fop.area.inline.Word; +import org.apache.fop.area.inline.Space; +import org.apache.fop.util.CharUtilities; +import org.apache.fop.fo.properties.VerticalAlign; + +//import org.apache.fop.fo.properties.*; + +import java.util.Vector; // or use ArrayList ??? + +/** + * LayoutManager for text (a sequence of characters) which generates one + * or more inline areas. + */ +public class TextBPLayoutManager extends AbstractBPLayoutManager { + /** + * Private class to store information about the break index. + * the field stores the index in the vector of AreaInfo which + * corresponds to this break position. + * Note: fields are directly readable in this class + */ + private static class TextBreakPosition implements BreakPoss.Position { + short m_iAreaIndex; + + TextBreakPosition(int iAreaIndex) { + m_iAreaIndex = (short)iAreaIndex; + } + } + + /** + * Store information about each potential word area. + * Index of character which ends the area, IPD of area, including + * any word-space and letter-space. + * Number of word-spaces? + */ + private class AreaInfo { + short m_iStartIndex; + short m_iBreakIndex; + MinOptMax m_ipdArea; + AreaInfo(short iStartIndex, short iBreakIndex, MinOptMax ipdArea) { + m_iStartIndex = iStartIndex; + m_iBreakIndex = iBreakIndex; + m_ipdArea = ipdArea; + } + } + + + // Hold all possible breaks for the text in this LM's FO. + private Vector m_vecAreaInfo; + + /** Non-space characters on which we can end a line. */ + static private final String s_breakChars = "-/" ; + + private char[] chars; + 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 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" */ + private short m_iNextStart = 0; + /** Size since last makeArea call, except for last break */ + private MinOptMax m_ipdTotal ; + /** Size including last break possibility returned */ + // private MinOptMax m_nextIPD= new MinOptMax(0); + /** size of a space character (U+0020) glyph in current font */ + private int m_spaceIPD; + /** 1/2 of word-spacing value */ + private SpaceVal m_halfWS; + /** Number of space characters after previous possible break position. */ + private int m_iNbSpacesPending; + + + public TextBPLayoutManager(FObj fobj, char[] chars, + TextInfo textInfo) { + super(fobj); + this.chars = chars; + this.textInfo = textInfo; + this.m_vecAreaInfo = new Vector(chars.length/5); // Guess + + // With CID fonts, space isn't neccesary currentFontState.width(32) + m_spaceIPD = 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), + ws.bConditional, ws.bForcing, + ws.iPrecedence); + } + + + public boolean generatesInlineAreas() { + return true; + } + + /* METHODS FROM LeafNodeLayoutManager, + * used in Keiron's implemenation, but not here (yet at least). + */ + public int size() { + return 0; + } + + public InlineArea get(int index) { + return null; + } + + /** + * Generate inline areas for words in text. + */ + public boolean generateAreas() { + // Handle white-space characteristics. Maybe there is no area to + // generate.... + + // Iterate over characters and make text areas. + // Add each one to parent. Handle word-space. + return false; + } + + + // 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; +// } + + + /** + * Return value indicating whether the next area to be generated could + * start a new line. This should only be called in the "START" condition + * if a previous inline BP couldn't end the line. + * Return true if the first character is a potential linebreak character. + */ + public boolean canBreakBefore(LayoutContext context) { + char c = chars[m_iNextStart]; + return ((c == NEWLINE) || + ((context.flags & LayoutContext.LINEBREAK_AT_LF_ONLY)==0 && + (CharUtilities.isSpace(c) || s_breakChars.indexOf(c)>=0))); + } + + /** + * Return the next break possibility that fits the constraints. + * @param context An object specifying the flags and input information + * concerning the context of the BreakPoss. + * @para prevPos An object specifying the previous Position returned + * by a BreakPoss from this LM. It may be earlier than the current + * pointer when doing hyphenation or starting a new line. + * @return BreakPoss An object containing information about the next + * legal break position or the end of the text run if no break + * was found. + * <p>Assumptions: white-space-treatment and + * linefeed-treatment processing + * are already done, so there are no TAB or RETURN characters remaining. + * white-space-collapse handling is also done + * (but perhaps this shouldn't be true!) + * There may be LINEFEED characters if they weren't converted + * into spaces. A LINEFEED always forces a break. + */ + public BreakPoss getNextBreakPoss(LayoutContext context, + BreakPoss.Position prevPos) { + /* On first call in a new Line, the START_AREA + * flag in LC is set. + */ + + int iFlags = 0; + + if ((context.flags & LayoutContext.START_AREA)!=0) { + /* This could be first call on this LM, or the first call + * in a new (possible) LineArea. + */ + m_ipdTotal = new MinOptMax(0); + iFlags |= BreakPoss.ISFIRST; + } + + if (prevPos != null) { + TextBreakPosition tbp = (TextBreakPosition)prevPos; + AreaInfo ai = + (AreaInfo) m_vecAreaInfo.elementAt(tbp.m_iAreaIndex); + 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"); + } + } + + + // HANDLE SUPPRESSED LEADING SPACES + if ((context.flags & LayoutContext.SUPPRESS_LEADING_SPACE)!=0) { + /* 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! + if (m_iNextStart >= chars.length) { + return null; // Or an "empty" BreakPoss? + } + } + + + // 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) + + // Handle inter-character spacing (word-space + letter-space) + // What about context.getPendingSpace() on first char in word? + SpaceSpecifier pendingSpace = new SpaceSpecifier(false); + + 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)); + wordIPD += m_spaceIPD; // Space glyph IPD + pendingSpace.clear(); + pendingSpace.addSpace(m_halfWS); + } + else { + // If we have letter-space, so we apply this to fixed- + // width spaces (which are not word-space) also? + spaceIPD.add(pendingSpace.resolve(false)); + pendingSpace.clear(); + wordIPD += CharUtilities.getCharWidth(c, textInfo.fs); + } + } + + if (m_iNextStart < chars.length) { + spaceIPD.add(pendingSpace.resolve(false)); + } + 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); + } + + // 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; + } + } + 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); + } + + + private BreakPoss makeBreakPoss(short iWordStart, MinOptMax spaceIPD, + int wordDim, + SpaceSpecifier trailingSpace, + int flags) + { + MinOptMax ipd = new MinOptMax(wordDim); + ipd.add(spaceIPD); + + // Position is the index of the info for this word in the vector + m_vecAreaInfo.add(new AreaInfo(iWordStart, m_iNextStart, 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!) + bp.setNonStackingSize(new MinOptMax(textInfo.lineHeight)); + + /* Set max ascender and descender (offset from baseline), + * used for calculating the bpd of the line area containing + * this text. + */ + //bp.setDescender(textInfo.fs.getDescender()); + //bp.setAscender(textInfo.fs.getAscender()); + if (m_iNextStart == chars.length) { + flags |= BreakPoss.ISLAST; + setFinished(true); + } + bp.setFlag(flags); + if (trailingSpace != null) { + bp.setTrailingSpace(trailingSpace); + } + return bp; + } + + + /** + * Add an area for each word and space (or one big one????) + */ + public void addAreas(PositionIterator posIter) { + // Add word areas + TextBreakPosition tbpStart, tbpNext; + 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); + } + } + + + + protected Word createWord(String str, int width) { + Word curWordArea = new Word(); + curWordArea.setWidth(width); + curWordArea.setHeight(textInfo.fs.getAscender() - textInfo.fs.getDescender()); + curWordArea.setOffset(textInfo.fs.getAscender()); + curWordArea.info = new LayoutInfo(); + curWordArea.info.lead = textInfo.fs.getAscender(); + curWordArea.info.alignment = VerticalAlign.BASELINE; + curWordArea.info.blOffset = true; + + curWordArea.setWord(str); + Trait prop = new Trait(); + prop.propType = Trait.FONT_STATE; + prop.data = textInfo.fs; + curWordArea.addTrait(prop); + return curWordArea; + } + + +} + |