![JAR search and dependency download from the Maven repository](/logo.png)
com.adobe.xfa.text.TextSparseStream Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
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);
// }
//}