All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xfa.text.TextSparseStream Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
package com.adobe.xfa.text;

import com.adobe.xfa.gfx.GFXEnv;
import com.adobe.xfa.ut.Storage;

/**
 * 

* The sparse stream represents a displayable stream whose content and * rendering are loaded on demand when needed, rather than up front. * Outwardly it behaves like any other displayable stream, allowing the * client to use the rich AXTE editing API for manipulating its content. *

*

* The client must derive its own implementation class from this * abstract base class, in order to handle the content access events. *

* @exclude from published api. */ public abstract class TextSparseStream extends TextStream { private final Storage moFrames = new Storage(); private TextContext mpoContext; private int meDirection = TextAttr.DIRECTION_NEUTRAL; private boolean mbLoadingFrame; /** * Default constructor. */ public TextSparseStream () { } /** * Copy constructor. * @param oSource - Source sparse stream to copy. */ public TextSparseStream (TextSparseStream oSource) { super (oSource); mpoContext = oSource.mpoContext; } /** * Copy constructor with graphics source information. *

* Copy all stream content from the source stream, but using the * optional graphic attribute pool. The display is not automatically * created. * @param oSource - Source text stream to copy content from. * @param poPool - Graphic attribute pool to use. */ // public TextSparseStream (TextStream oSource, GFXAttrPool poPool) { // super (oSource, poPool); // } /** * Constructor with source text string. *

* Create a text stream whose initial content is copied from the given * string. The text stream initially has no attribute pool association. * The display is not automatically created. * @param sSource - String whose contents are to be copied to the text * stream. */ public TextSparseStream (String sSource) { super (sSource); } /** * Set the display context. *

* A text context allows several text streams to share a number of * objects used in creating text displays. By sharing these objects, * they need not be created for each stream, an expensive operation. * @param poContext - Shared context to use. Context objects follow the * text reference counting model, so if the caller creates the context * on the stack, it must ensure the context continues to exist at least * until the text stream is destroyed. */ public void setContext (TextContext poContext) { mpoContext = poContext; } /** * Get the stream's text context object. *

* Overridden from the TextStream base class, this method returns a * pointer to the stream's text context. * @return Pointer to the text context; NULL if none is in effect. */ public TextContext getContext () { return mpoContext; } /** * Set the number of frames in the sparse stream. *

* If the given number is greater than the number of frames currently in * the stream, null frames are added to round out the number. If the * new number is smaller, frames (null and non-null) are removed from * the end of the stream. * @param nFrames - New number of frames to be contained in the stream. */ public void setFrameCount (int nFrames) { int nOldSize = moFrames.size(); if (nFrames < nOldSize) { releaseFrames (nFrames, true); } else if (nFrames > nOldSize) { updateLastLineFlag (false); moFrames.ensureCapacity (nFrames); for (int i = nOldSize; i < nFrames; i++) { moFrames.setSize (i + 1); // extend array incrementally moFrames.set (i, null); // in case exception below setFrameAt (i, new TextNullFrame()); } } } /** * Return the number of frames in the stream. * @return Number of frames in the stream. This includes both null and * non-null frames. */ public int getFrameCount () { return moFrames.size(); } /** * Retrieve a specific frame from the stream. * @param nIndex - Index of the desired frame. * Pointer to the requested frame. A null value indicates a null frame. */ public TextFrame getFrame (int nIndex) { return getFrame (nIndex, false); } /** * Insert a new frame into the stream. * @param nIndex - Insertion index. Effectively, the existing frame * with this index number and all that come after it have their indexes * incremented by one. The new frame gets this index number. If the * given index is beyond the end of the set of frames, the new frame is * appended at the end. * @param oFrame - New frame to append. AXTE makes a clone of this * object. * @param poLayout - Pointer to layout to load into the frame. If this * pointer is not null, the content in this layout object is added to * the sparse stream, and the frames layout is generated from this * layout object. If the pointer is null, the streams content from this * frame on is reflowed to account for the new frame. This might cause * the removal of frames at the end of the stream. * @return Pointer to the cloned frame added to the stream. */ public TextFrame insertFrame (int nIndex, TextFrame oFrame, TextLayout poLayout) { TextFrame poClone = oFrame.cloneFrame(); insertFrameRef (nIndex, poClone, poLayout); return poClone; } /** * Insert a new frame into the stream. * @param nIndex - Insertion index. Effectively, the existing frame * with this index number and all that come after it have their indexes * incremented by one. The new frame gets this index number. If the * given index is beyond the end of the set of frames, the new frame is * appended at the end. * @param poFrame - New frame to insert. This object is assumed to be * reference counted and AXTE adds its own reference. * @param poLayout - Pointer to layout to load into the frame. If this * pointer is not null, the content in this layout object is added to * the sparse stream, and the frames layout is generated from this * layout object. If the pointer is null, the streams content from this * frame on is reflowed to account for the new frame. This might cause * the removal of frames at the end of the stream. */ public void insertFrameRef (int nIndex, TextFrame poFrame, TextLayout poLayout) { if (nIndex >= moFrames.size()) { updateLastLineFlag (false); } moFrames.add (nIndex, null); // make sure we can open up the space first setFrameAt (nIndex, poFrame, poLayout); } /** * Append a new frame at the end of the stream. * @param oFrame - New frame to append. AXTE makes a clone of this * object. * @param poLayout - Pointer to layout to load into the frame. If this * pointer is not null, the content in this layout object is added to * the sparse stream, and the frames layout is generated from this * layout object. If the pointer is null, the streams content from this * frame on is reflowed to account for the new frame. Note: it doesnt * really make sense to pass a null pointer, as any attempt at reflow * will not generate content for the frame and it will be automatically * removed. * @return Pointer to the cloned frame added to the stream. */ public TextFrame appendFrame (TextFrame oFrame, TextLayout poLayout) { return insertFrame (moFrames.size(), oFrame, poLayout); } public TextFrame appendFrame (TextFrame oFrame) { return appendFrame (oFrame, null); } /** * Append a new frame at the end of the stream. * @param poFrame - New frame to insert. This object is assumed to be * reference counted and AXTE adds its own reference. * @param poLayout - Pointer to layout to load into the frame. If this * pointer is not null, the content in this layout object is added to * the sparse stream, and the frames layout is generated from this * layout object. If the pointer is null, the streams content from this * frame on is reflowed to account for the new frame. Note: it doesnt * really make sense to pass a null pointer, as any attempt at reflow * will not generate content for the frame and it will be automatically * removed. */ public void appendFrameRef (TextFrame poFrame, TextLayout poLayout) { insertFrameRef (moFrames.size(), poFrame, poLayout); } public void appendFrameRef (TextFrame oFrame) { appendFrameRef (oFrame, null); } /** * Change the definition of a frame. *

* This method serves three purposes: *

    *
  • * Load a previously null frame. *
  • *
  • * Change the geometry of a frame and reflow its text. *
  • *
  • * Change the geometry and content of a frame. *
  • *
      * @param nIndex - Index of the frame to update. It is an error if this * number is out of range. * @param oFrame - New frame to set. AXTE makes a clone of this object. * @param poLayout - Pointer to layout to load into the frame. If this * pointer is not null, the content in this layout object replaces the * former frames content, and the frames layout is regenerated from this * layout object. If the frame was previously null, this effectively * loads the frame. If this parameter is null, the streams content from * this frame on is reflowed to account for the new frame. This might * cause the addition or removal of frames at the end of the stream. * @return - Pointer to the cloned frame added to the stream. */ public TextFrame setFrame (int nIndex, TextFrame oFrame, TextLayout poLayout) { TextFrame poClone = oFrame.cloneFrame(); setFrameRef (nIndex, poClone, poLayout); return poClone; } /** * Change the definition of a frame. *

      * This method serves three purposes: *

        *
      • * Load a previously null frame. *
      • *
      • * Change the geometry of a frame and reflow its text. *
      • *
      • * Change the geometry and content of a frame. *
      • *
          * @param nIndex - Index of the frame to update. It is an error if this * number is out of range. * @param poFrame - New frame to set. This object is assumed to be * reference counted and AXTE adds its own reference. * @param poLayout - Pointer to layout to load into the frame. If this * pointer is not null, the content in this layout object replaces the * former frames content, and the frames layout is regenerated from this * layout object. If the frame was previously null, this effectively * loads the frame. If this parameter is null, the streams content from * this frame on is reflowed to account for the new frame. This might * cause the addition or removal of frames at the end of the stream. */ public void setFrameRef (int nIndex, TextFrame poFrame, TextLayout poLayout) { if (nIndex >= moFrames.size()) { setFrameCount (nIndex); // null frames up to, but not including, this one moFrames.setSize (nIndex + 1); moFrames.set (nIndex, null); } else { releaseFrame (nIndex, true, true, poLayout != null); } setFrameAt (nIndex, poFrame, poLayout); } /** * Remove a frame from a sparse stream. *

          * This can simply remove the frame definition from the streams flow, or * it can also remove the corresponding content from the stream. * @param nIndex - Index of the frame to remove. It is an error if this * number is out of range. * @param bRemoveContent - True if the corresponding content is to be * removed from the stream. False if this frame is to be removed from * the stream and text reflowed in the remaining frames. In this case, * AXTE may need to create new frames at the end of the stream. */ public void removeFrame (int nIndex, boolean bRemoveContent) { assert (nIndex < moFrames.size()); // If the content is being removed, need to suppress re-layout so that we // don't trigger a layout operation before all the dust has settled. // TextStream.layoutSuppressor oSuppressor (this); // Not removing content: can disable the layout suppressor. if (! bRemoveContent) { // oSuppressor.detach(); } // Removing content and this is the last with more than one frame: Check // for the special case where the last remaining frame ends in some sort // of forced line break. If it does, we need to remove that line break // as well. Otherwise, it will trigger a new empty frame in place of the // one just being removed--not what the user would expect. else if ((nIndex > 0) && ((nIndex + 1) == moFrames.size())) { TextFrame poFrame = getFrame (nIndex, true); TextFrame poPrev = getFrame (nIndex - 1); if ((poFrame != null) && (poPrev != null)) { TextPosnBase oEnd = poFrame.getStart(); oEnd.tighten (false); int[] result = oEnd.prevData (TextNullFrame.MODE_ALLOW, true); if ((result[0] == TextItem.PARA) || (result[1] == '\n')) { oEnd.deleteBack (1); } } } releaseFrame (nIndex, true, true, bRemoveContent); moFrames.remove (nIndex); } public void removeFrame (int nIndex) { removeFrame (nIndex, false); } /** * Obtain the direction override for this stream. *

          * A displayable stream can have a default (paragraph) direction set, * which determines what side to start on and influences word ordering * in true bidirectional text. If this value is neutral, the default * paragraph direction is determined from the attributes in effect at * the start of the text. * @return Default paragraph direction value. */ public int defaultDirection () { return meDirection; } /** * Change the default direction override for this stream. *

          * See the first overload of this method for how the default text * direction is used. * @param eNewDirection - New direction to set for this stream. */ public void defaultDirection (int eNewDirection) { if (eNewDirection == defaultDirection()) { return; } meDirection = eNewDirection; if (display() != null) { display().update(); } } /** * Create display with optional scroller and graphic environment. *

          * THis method allows the caller to create the stream's display by * passing optional scroller and graphic environment. Once the display * has been created, it exists until the displayable stream is deleted. * Subsequent calls to this method simply return the pointer to the * display that already exists. * @return Pointer to the text stream's display object. */ // public TextDisplay createDisplay (TextScroller poScroller, GFXEnv poEnv) { // TODO: public TextDisplay createDisplay () { // TODO: TextDisplay poDisplay = display(); // Note: if we have to create the display, do it as a two-step process. // Used to be one step, but led to problems if exceptions thrown in the // constructor. if (poDisplay == null) { poDisplay = new TextDisplay(); poDisplay.connectStream (this); // } else if (poDisplay.textConnect().scroller() == null) { // poDisplay.textConnect().scroller (poScroller); } return poDisplay; } /** * Create display with graphic connection object. *

          * THis method allows the caller to create the stream's display by * passing a graphic connection object. Once the display has been * created, it exists until the displayable stream is deleted. * Subsequent calls to this method simply return the pointer to the * display that already exists. * @param poGfxConnect - Graphic connection object to use in the * creation. * @return Pointer to the text stream's display object. */ // public TextDisplay createDisplay (GFXConnect poGfxConnect) { // TODO: // TextDisplay poDisplay = Display(); // Note: if we have to create the display, do it as a two-step process. // Used to be one step, but led to problems if exceptions thrown in the // constructor. // if (poDisplay == null) { // poDisplay = new TextDisplay (poGfxConnect); // poDisplay.connectStream (this); // } else if (poDisplay.textConnect().gfxConnect() == null) { // poDisplay.textConnect().gfxConnect (poGfxConnect); // } // return poDisplay; // } /** * Client-implemented event handler to load a frame's content. *

          * The client implementation is expected to call SetFrame() or * SetFrameRef() before returning. * @param nIndex - Index number of frame to load. The client can assume * that the frame is currently null. * @return Pointer to the frame that was loaded. This would be the * return value of SetFrame() or the pointer passed into SetFrameRef(). * The return value is for informational purposes only; the client does * not increment the reference count before returning. */ abstract public TextFrame onLoadFrame (int nIndex); /** * Notify the client that a new frame must be created at the end of the * stream to handle content overflow. *

          * This method is called if editing pushes content beyond the set of * frames established by the client. * @return Pointer to the frame that was created. The client can return * null, indicating a "text full" situation. The default implementation * returns null. AXTE takes over ownership of the reference. * Consequently, the client implementation must add a reference if * necessary. Typically, the client uses a Clone() method, which will * establish the reference count correctly. */ public TextFrame onNewFrame () { return null; } /** * Notify the client that a frame is about to be removed. *

          * This call is made before the frame is removed. * @param nIndex - Index number of frame about to be removed. * @param bForced - True if this is the result of a call to * RemoveFrame() or SetFrameCount(); false if the frame is being removed * from the end of the stream because of reflow. * @param bRemoveContent - True if the corresponding content is to be * removed from the stream (can occur only if this is a forced removal). * False if this frame is to be removed from the stream and text * reflowed in the remaining frames. Note that this parameter is * provided for informational purposes only. AXTE will take care of * removing the content. */ public void onRemoveFrame (int nIndex, boolean bForced, boolean bRemoveContent) { } /** * Post-removal callback. *

          * AXTE calls this method after a frame has been removed, but before * AXTE's reference is released. * @param nIndex - Index number of frame about to be removed. */ public void onFrameRemoved (int nIndex) { } TextContext forceContext () { if (mpoContext == null) { mpoContext = new TextContext(); } return mpoContext; } TextDisplay forceDisplay (TextAttr poDefaultAttr, GFXEnv poGfxEnv) { TextDisplay poDisplay = display(); if (poDisplay == null) { poDisplay = new TextDisplay(); } poDisplay.connectStream (this, false, poDefaultAttr); return poDisplay; } TextFrame getFrame (int nIndex, boolean bShowNullFrames) { TextFrame poResult = moFrames.get (nIndex); assert (poResult != null); if ((! bShowNullFrames) && (poResult.isNullFrame() != null)) { return null; } return poResult; } public TextFrame forceFrame (int nIndex, boolean bReflow) { TextFrame poFrame = null; if (nIndex >= moFrames.size()) { while (nIndex >= moFrames.size()) { poFrame = onNewFrame(); if (poFrame == null) { return null; } if (bReflow) { appendFrameRef (poFrame); } else { moFrames.add (poFrame); poFrame.setStream (this); } } } else { poFrame = getFrame (nIndex, true); assert (poFrame != null); if (poFrame.isNullFrame() != null) { poFrame = onLoadFrame (nIndex); assert (poFrame != null); } } return poFrame; } public TextFrame forceFrame (int nIndex) { return forceFrame (nIndex, false); } void replaceNullFrame (TextNullFrame poNullFrame) { int nFrameIndex = 0; while (nFrameIndex < moFrames.size()) { if (moFrames.get (nFrameIndex) == poNullFrame) { break; } nFrameIndex++; } if (nFrameIndex >= moFrames.size()) { return; } onLoadFrame (nFrameIndex); } void trimFramesOnReflow (int nNewSize) { if (nNewSize >= moFrames.size()) { return; } while (moFrames.size() > nNewSize) { int nLastIndex = moFrames.size() - 1; releaseFrame (nLastIndex, true, false); moFrames.setSize (nLastIndex); } updateLastLineFlag (true); } void updateLastLineFlag (boolean bLastLine, int nLastLineIndex) { if (moFrames.size() == 0) { return; } TextFrame poFrame = getFrame (moFrames.size() - 1); if (poFrame == null) { return; } int nLines = poFrame.getLineCount(); if (nLines == 0) { return; } int nLineIndex = nLines - 1; if ((nLastLineIndex >= 0) && (nLastLineIndex < nLineIndex)) { nLineIndex = nLastLineIndex; } DispLineWrapped poLine = poFrame.getLine (nLineIndex); poLine.setLastLineInStream (bLastLine); } void updateLastLineFlag (boolean bLastLine) { updateLastLineFlag (bLastLine, -1); } protected void populateFrame (int nFrameIndex, TextLayout poLayout) { TextPosn oFrameStart = new TextPosn(); setFrameStart (nFrameIndex, oFrameStart); loadLayout (nFrameIndex, getFrame (nFrameIndex, true), poLayout, oFrameStart); } boolean isLoadingFrame () { return mbLoadingFrame; } void setLoadingFrame (boolean bLoadingFrame) { mbLoadingFrame = bLoadingFrame; } private void releaseFrames (int nStartIndex, boolean bForced) { for (int i = nStartIndex; i < moFrames.size(); i++) { releaseFrame (i, bForced, bForced, bForced); } moFrames.setSize (nStartIndex); } // private void releaseFrames (int nStartIndex) { // releaseFrames (nStartIndex, false); // } // private void releaseFrames () { // releaseFrames (0, false); // } private void releaseFrame (int nIndex, boolean bInvokeCallback, boolean bForced, boolean bRemoveContent) { TextFrame poFrame = getFrame (nIndex, true); if (poFrame == null) { return; } TextNullFrame poNullFrame = poFrame.isNullFrame(); if (poNullFrame != null) { poFrame.getStart().removeNullFrame(); } else { if (bInvokeCallback) { onRemoveFrame (nIndex, bForced, bRemoveContent); } if (bRemoveContent) { removeFrameContent (nIndex); } if (bInvokeCallback) { onFrameRemoved (nIndex); } } moFrames.set (nIndex, null); poFrame.setStream (null); } private void releaseFrame (int nIndex, boolean bInvokeCallback, boolean bForced) { releaseFrame (nIndex, bInvokeCallback, bForced, false); } private void removeFrameContent (int nIndex) { // TBD: this doesn't work for nested fields. TextFrame poFrame = getFrame (nIndex, true); if (poFrame == null) { return; } int nNextFrame = nIndex + 1; int nEnd = (nNextFrame >= moFrames.size()) ? Integer.MAX_VALUE : getFrame(nNextFrame,true).getStart().index(); TextRange oRange = new TextRange (this, poFrame.getStart().index(), nEnd); if (! oRange.isEmpty()) { oRange.delete(); } } private void setFrameAt (int nFrameIndex, TextFrame poFrame, TextLayout poLayout) { assert (nFrameIndex < moFrames.size()); assert (moFrames.get (nFrameIndex) == null); // Hook the frame into our list. poFrame.setStream (this); moFrames.set (nFrameIndex, poFrame); // Determine the frame's start position and set it in the frame. TextPosnBase oStart = new TextPosnBase(); setFrameStart (nFrameIndex, oStart); poFrame.setStart (oStart); // Set up a position that will grow with the frame's content, as that // content gets added below. TextPosn oEnd = new TextPosn (oStart); oEnd.position (TextPosn.POSN_AFTER); // A null frame is a place-holder that may be subsequently be replaced // with a "real" frame. Null frames do not have lauout Each null frame // needs a "marker" in stream content, so that content operations can // detect an attempt to step into/over one and trigger its loading. TextNullFrame poNullFrame = poFrame.isNullFrame(); if (poNullFrame != null) { oStart.insertNullFrame (poNullFrame); } // Not a null frame: if it has layout, load that now. else if (poLayout != null) { loadLayout (nFrameIndex, poFrame, poLayout, oEnd); } // Otherwise it's a non-null frame with no layout. This will cause // reflow of all text from this point on. else { oEnd.associate (null); if (display() != null) { display().updateToEnd (poFrame.getStart().stream(), poFrame.getStart().index()); } } // Obscure: Each frame's position will advance for content insertions // only of those insertions strictly before the frame's position. An // insertion at the frame's start ends up being included in the frame's // content (which is normally the right thing). However, if the frame we // just set replaced a frame with no content, the next frame's start // position is the same as this frame's start position. If the next // frame also has no content, the subsequent frame also has the same // start position, and so on. Any content inserted with the current // frame may therefore also appear to be in some number of subsequent // frames. The loop interates through those frames and updates their // start postions until we find one that's beyond. if (oEnd.stream() != null) { for (int nNextFrame = nFrameIndex + 1; nNextFrame < moFrames.size(); nNextFrame++) { TextFrame poNextFrame = getFrame (nNextFrame, true); assert (poNextFrame != null); if (poNextFrame.getStart() != poFrame.getStart()) { break; } poNextFrame.setStart (oEnd); } } } private void setFrameAt (int nIndex, TextFrame poFrame) { setFrameAt (nIndex, poFrame, null); } private void setFrameStart (int nFrameIndex, TextPosnBase oFrameStart) { int nNextFrame = nFrameIndex + 1; if (nNextFrame < moFrames.size()) { oFrameStart.copyFrom (getFrame(nNextFrame,true).getStart()); } else { oFrameStart.associate (this, Integer.MAX_VALUE); } } private void loadLayout (int nIndex, TextFrame poFrame, TextLayout poLayout, TextPosn oEnd) { assert (false); } // private static void applyAttributes (TextPosn oStart, TextPosn oEnd, TextAttr poAttr) { // if (poAttr == null) { // return; // } // // oStart.tighten (true); // if (oStart.index() >= oEnd.index()) { // return; // } // //// Fill in any missing attributes because caller won't expect missing //// values to bleed from earlier attributes. // TextAttr oFullAttr = new TextAttr (true); // oFullAttr.override (poAttr); // // TextRange oRange = new TextRange (oStart.stream(), oStart.index(), oEnd.index()); // oRange.attribute (oFullAttr); // // oStart = oEnd; // oStart.position (TextPosn.POSN_BEFORE); // } } //public class LoadFrameManager { // private TextSparseStream mpoStream; // // public LoadFrameManager (TextSparseStream poStream) { // mpoStream = poStream; // assert (! mpoStream.isLoadingFrame()); // mpoStream.setLoadingFrame (true); // } // // public void finalize () { // mpoStream.setLoadingFrame (false); // } //}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy