com.adobe.xfa.text.TextDisplay 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.Rect;
import com.adobe.xfa.ut.Storage;
import com.adobe.xfa.ut.UnitSpan;
/**
* The text display plays a key role in the presentation of text, and in
* operations that map between graphic locations and text positions
* (e.g., move up one line).
*
* An application never creates a display directly. Instead, it asks a
* displayable stream (derived class of TextDispStr) to create one.
* Once created, the text display instance belongs to the displayable
* stream instance until the latter is destroyed. The application must
* never destroy a text display.
*
*
* While the presence of a text display enables a large number of
* operations, the display API is fairly light. Many display-related
* operations are handled through the text stream, position and range
* APIs.
*
*
* For more information, please see the external documentation.
*
* @exclude from published api.
*/
public class TextDisplay {
static class FindCaretInfo {
final int mFrameIndex;
final int mLineIndex;
final Rect mCaret;
FindCaretInfo (int frameIndex, int lineIndex, Rect caret) {
mFrameIndex = frameIndex;
mLineIndex = lineIndex;
mCaret = caret;
}
}
// public class UpdateIgnore { // TODO: what to do with this?
// private TextDisplay mpoDisplay;
//
// public UpdateIgnore (TextDisplay poDisplay) {
// mpoDisplay = poDisplay;
// if (mpoDisplay != null) {
// mpoDisplay.ignoreUpdates (true);
// }
// }
//
// public void finalize () {
// if (mpoDisplay != null) {
// mpoDisplay.ignoreUpdates (false);
// }
// }
// }
TextSparseStream mpoStream; // owning stream
// public TextGfxConnect moConnect; // invalidation connection
public GFXEnv mpoEnv; // graphic environment // TODO: this is never initialized
public int mnLineCount;
public int mnSuppressFormat; // suppress regeneration?
public int mnIgnoreUpdates; // ignore updates altogether?
public final DispChange moChange = new DispChange(); // stream change information
public final Storage moTabs = new Storage(); // embedded tab objects
public LocaleInfo moLocaleInfo; // locale info for layout
public TextBreakFinder mpoBreakFinder; // line break finder
public int meLayoutOrientation = TextAttr.ORIENTATION_HORIZONTAL;
public boolean mbHasFontSubstitution; // has font substitution taken place?
/**
* Render the display contents, using the properties provided.
*
* Given a set of rendering properties in an instance of class {link
* TextDrawInfo}, this method performs the appropriate rendering.
* @param oDrawInfo - Rendering properties. For more information,
* please see the description of {link TextDrawInfo}.
* @return False if truncate was requested and some text was truncated.
* true in all other cases.
*/
public boolean gfxDraw (TextDrawInfo oDrawInfo) {
Rect oTranslated = oDrawInfo.getInvalid();
if ((oTranslated == null) || oTranslated.isDegenerate()) {
oTranslated = computeClipRect (mpoStream, oDrawInfo.getInvalidDefault());
}
// GFXEnv oGfxEnv = oDrawInfo.getGfxEnv();
// moConnect.beginPaint (oGfxEnv, oTranslated);
// make sure EndPaint gets called on exit
// EndPaint oEndPaint (moConnect, oGfxEnv);
DrawParm oParm = new DrawParm (oDrawInfo);
oParm.setInvalid (oTranslated);
Rect oExtent;
if (oDrawInfo.getTruncate()) {
oExtent = computeClipRect (mpoStream);
oParm.setTruncate (oExtent);
}
FrameGfxDraw oDraw = new FrameGfxDraw (mpoStream, oParm); // TBD: what process level?
oDraw.processFrames();
return oDraw.fits();
}
/**
* Obtain the number of lines of text.
* @return Number of lines of text in the display.
*/
public int lines () {
return mnLineCount;
}
/**
* Determine a split point in the display.
*
* This method exists for applications that wish to split a text display
* across pages without actually modifying the underlying text. Given a
* vertical split point target, this method returns the bottom offset of
* the last line that fits entirely within the requested range. The
* apllication can then use this in constructing two invalidation
* rectangles to constrain the drawing of this text display in two
* separate gfxDraw() calls, one for each page.
* @param oTarget - Vertical split target.
* @param poSplitPosn - (optional) Pointer to text position object to
* receive the split position. If NULL (default) no position is
* returned.
* @return Split offset of last line that fits completely.
*/
// public UnitSpan split (UnitSpan oTarget, TextPosnBase poSplitPosn, boolean pbEndsInNewLine) {
// if (pbEndsInNewLine != null) {
// pbEndsInNewLine = false;
// }
//
// UnitSpan oSplit (oTarget);
// TextFrameOffsetFinder oFinder (mpoStream, oSplit);
//
// if (oFinder.processFrames()) { // split comes after last line
// if (poSplitPosn != null) {
// poSplitPosn.associate (mpoStream, UINT_MAX);
// }
// }
//
// else {
// DispLineWrapped poLine = oFinder.getLine();
//// Note: there can be blank space between lines or before the first line
//// (vertical justification). If the split point comes before the line,
//// we can use as is--it's in the blank space.
// UnitSpan oOffset (UnitSpan.PICA_PT_1K, 0);
// oOffset += poLine.getBMin();
//
// if (oOffset < oTarget) {
// oSplit = oOffset;
// }
//
// if (poSplitPosn != null) {
// if (poLine.getPositionCount() == 0) { // empty line
// poSplitPosn.associate (mpoStream, UINT_MAX);
// } else {
// poSplitPosn = poLine.getPosition (0);
// poSplitPosn.tighten (false); // before any leading attributes
// }
//
// if (pbEndsInNewLine != null) {
// TextPosnBase oNewLineTest (poSplitPosn);
// TextItemCode ePrev = oNewLineTest.prevUserPosnType();
//
// if (ePrev == TEXT_ITEM_PARA) {
// pbEndsInNewLine = true;
// } else if (ePrev == TEXT_ITEM_CHAR) {
// UniChar c = oNewLineTest.nextChar();
// int eData = TextCharProp.getCharProperty (c);
// int eBreak = TextCharProp.getBreakClass (eData);
// if ((eBreak == TextCharProp.BREAK_BK) || (eBreak == TextCharProp.BREAK_CR) || (eBreak == TextCharProp.BREAK_LF) || (eBreak == TextCharProp.BREAK_NL)) {
// pbEndsInNewLine = true;
// }
// }
// }
// }
// }
//
// return oSplit;
// }
/**
* Truncate underlying displayable stream to a given number of lines.
* @param nEndLine - First line not to be included in the result.
* @param pStream - Pointer to text stream object to receive the rich
* text being removed as a result of the truncation. If NULL, that text
* is simply discarded.
*/
// public void truncateLines (int nEndLine, TextStream pStream) {
// FrameLineFinder oFinder (mpoStream, nLine);
// if (! oFinder.processFrames()) {
// DispLineWrapped poLine = oFinder.getLine();
// TextPosnBase oResult;
// poLine.getCaretStartEnd (mpoStream, false, false, oResult);
// TextRange oRange (mpoStream, oResult.index());
//
// if (poOverflow != null) {
// if (mpoStream.attrPool() != null) {
// poOverflow.attrPool (mpoStream.attrPool());
// }
// if (mpoStream.fontService() != null) {
// poOverflow.fontService (mpoStream.fontService());
// }
// oRange.text (poOverflow);
// }
//
// TextPosnBase oDeleteStart (oRange.start());
// TextPosnBase oTestPrev (oDeleteStart);
// TextItemCode ePrev = oTestPrev.prevUserPosnType();
//
// if ((ePrev == TEXT_ITEM_CHAR) && (oTestPrev.nextChar() == '\n')) {
// oDeleteStart.prevUserPosn();
// } else if (ePrev == TEXT_ITEM_PARA) {
// oDeleteStart.prevUserPosn();
// }
//
// mpoStream.posnDelete (oDeleteStart, oRange.end().index() - oDeleteStart.index(), true);
// }
// }
/**
* Locate split point.
*
* Given a vertical split target, this method returns the index of the
* first line that is not completely contained within that target.
* @param oTarget - Vertical split target.
* @return Index of first line that can be split off.
*/
// public int lineSplit (UnitSpan oTarget) {
// TextFrameOffsetFinder oFinder (mpoStream, oTarget);
// oFinder.processFrames();
// return oFinder.getAbsLineIndex();
// }
/**
* Determine whether formatting is suppressed.
*
* An application that plans to make a number of changes to a text
* stream can turn off formatting (layout) of the display after each
* change, and restore it after the last change. This may improve
* performance.
* @return TRUE if formatting is currently suppressed; FALSE otherwise.
*/
public boolean suppressFormat () {
return mnSuppressFormat > 0;
}
/**
* Push request to suppress formatting.
*
* In a complex application, there may be many levels in the call stack
* that wish to suppress formatting. This method pushes a suppress
* format request onto a stack managed by the text display.
*/
public void pushSuppressFormat () {
mnSuppressFormat++;
}
/**
* Pop request to suppress formatting.
*
* In a complex application, there may be many levels in the call stack
* that wish to suppress formatting. This method pops a suppress format
* request from a stack managed by the text display. When the stack is
* empty, formatting is turned back on.
*/
public void popSuppressFormat () {
if (mnSuppressFormat > 0) {
if (mnSuppressFormat > 1) {
mnSuppressFormat--;
} else {
clearSuppressFormat();
}
}
}
/**
* Clear the suppress formatting request stack.
*
* This method pops all outstanding requests off the supress formatting
* stack and turns formatting back on.
*/
public void clearSuppressFormat () {
if (mnSuppressFormat == 0) {
return;
}
mnSuppressFormat = 0;
layout (true);
}
/**
* Query whether there is any font substitution in this display.
*
* This method exists to assist text layout caching. A text display
* with font substitution cannot be cached. As the display is built, it
* records a Boolean indicating whether any substitution takes place.
* Note that an incremental re-layout may clear the condition causing
* the font substitution; that will not be reflected in the value of
* this Boolean. However, in expected usage, this will not be an issue.
* @return TRUE if there has been any font substitution; FALSE if there
* is no substitution.
*/
public boolean hasFontSubstitution () {
return mbHasFontSubstitution;
}
/**
* Initialize the font substitution flag.
*
* This method allows the caller to pre-populate the font substitution
* flag, typically when the caller knows something that AXTE doesn't.
* @param bSubstitution - TRUE to indicate that font substitution has
* taken place; FALSE to indicate it hasn't.
*/
public void setFontSubstitution (boolean bSubstitution) {
mbHasFontSubstitution = bSubstitution;
}
public void updateSuspectLayout () {
boolean bUpdated = false;
for (int i = 0; i < mpoStream.getFrameCount(); i++) {
TextFrame poFrame = mpoStream.getFrame (i);
if ((poFrame != null) && (poFrame.getLayoutState() == TextFrame.LAYOUT_SUSPECT)) {
int nStartIndex = poFrame.getStart().index();
int nEndIndex;
int nNextFrame = i + 1;
if (nNextFrame >= mpoStream.getFrameCount()) {
nEndIndex = mpoStream.posnCount();
} else {
TextFrame poNextFrame = mpoStream.getFrame (nNextFrame, true);
TextPosnBase oEndPosn = new TextPosnBase (poNextFrame.getStart());
int nLines = poFrame.getLineCount();
if (nLines > 0) {
DispLineWrapped poLast = poFrame.getLine (nLines - 1);
if (poLast.getLastParaLine() >= DispLine.HARD_NEW_LINE) {
oEndPosn.prevUserPosn(); // suppress handling of line break char
}
}
nEndIndex = oEndPosn.index();
}
moChange.frame (mpoStream, nStartIndex, nEndIndex - nStartIndex);
layout (true, i, true, null);
bUpdated = true;
}
}
if (bUpdated) {
// DebugFrames();
}
}
public TextSparseStream stream () {
return mpoStream;
}
// TextGfxConnect textConnect () {
// return moConnect;
// }
// GFXConnect gfxConnect () {
// return moConnect.gfxConnect();
// }
// void gfxConnect (GFXConnect poNewGfxConnect) {
// moConnect.gfxConnect (poNewGfxConnect);
// }
// void hideEmbed () {
// }
// boolean pickEmbed (CoordPair oPickPoint, GFXEnv poGfxEnv) {
// return false;
// }
//----------------------------------------------------------------------
//
// UpdateSelected: Invalidate the display in a graphic
// environment when the selected range changes.
//
//----------------------------------------------------------------------
// void updateSelected (GFXEnv oGfxEnv, TextRange oOldSel, TextRange oNewSel, boolean bEraseBkgnd) {
// UpdateSelected (oOldSel, oNewSel, oGfxEnv, bEraseBkgnd);
// }
//----------------------------------------------------------------------
//
// UpdateSelected: Invalidate the display in a graphic
// environment (or all graphic environments) when a selected
// range changes.
//
//----------------------------------------------------------------------
// void updateSelected (TextRange poOldSel, TextRange poNewSel, GFXEnv poGfxEnv, boolean bEraseBkgnd) {
// TextStream poOldStream = null;
// TextStream poNewStream = null;
//
// if (poOldSel != null) {
// poOldStream = poOldSel.stream();
// }
// if (poNewSel != null) {
// poNewStream = poNewSel.stream();
// }
//
// boolean bOldLoose = (poOldStream == null);
// boolean bNewLoose = (poNewStream == null);
//
// if (bOldLoose && bNewLoose) {
// return;
// }
//
// TextRange oOldRange;
// TextRange oNewRange;
// TextRange poOld = poOldSel;
// TextRange poNew = poNewSel;
//
// if (bOldLoose) {
// poOld = null;
// } else if (bNewLoose) {
// poNew = null;
// }
//
// else if (poOldStream == poNewStream) {
// int nOldStart = poOldSel.start().index();
// int nOldEnd = poOldSel.end().index();
// int nNewStart = poNewSel.start().index();
// int nNewEnd = poNewSel.end().index();
//
// if (nOldStart < nNewStart) {
// if (nNewStart < nOldEnd) {
// oOldRange.associate (poOldStream, nOldStart, nNewStart);
// poOld = oOldRange;
// oNewRange.associate (poNewStream, nOldEnd, nNewEnd);
// poNew = oNewRange;
// }
// } else { // (nNewStart <= nOldStart)
// if (nOldStart < nNewEnd) {
// oNewRange.associate (poNewStream, nNewStart, nOldStart);
// poNew = oNewRange;
// oOldRange.associate (poOldStream, nNewEnd, nOldEnd);
// poOld = oOldRange;
// }
// }
// }
//
// if (poOld != null) {
// InvalidateSel (poOld, poGfxEnv, true);
// }
// if (poNew != null) {
// InvalidateSel (poNew, poGfxEnv, bEraseBkgnd);
// }
// }
//----------------------------------------------------------------------
//
// GetSelectionRectangles: Return all rectangles
// corresponding to a given range.
//
//----------------------------------------------------------------------
// boolean getSelectionRectangles (TextRange poRange, List oRectangles) {
// TextSelection oSelection;
// oSelection = poRange;
// oRectangles.setSize (0, false);
//
// TextFrameSelRect oFinder (mpoStream, oSelection, oRectangles);
// oFinder.processFrames();
// return oRectangles.size() > 0;
// }
// void invalidateArea (Rect oInvalid, boolean bEraseBkgnd, TextFrame poFrame) {
// if (poFrame == null) {
// moConnect.invalidateArea (oInvalid, bEraseBkgnd);
// } else {
// moConnect.invalidateContext (poFrame, oInvalid, bEraseBkgnd);
// }
// }
Rect runtimeExtent (boolean bExtended) { // TODO:
FrameDispInfo oInfoFinder = new FrameDispInfo (mpoStream, bExtended);
oInfoFinder.processFrames();
return oInfoFinder.getExtent();
}
public Rect runtimeExtent () {
return runtimeExtent (false);
}
Rect frame0Extent () {
if (mpoStream == null) {
return null;
}
TextFrame poFrame = mpoStream.forceFrame (0);
if (poFrame == null) {
return null;
}
return poFrame.getExtent();
}
void recomputeLineCount () {
mnLineCount = 0;
int nFrames = mpoStream.getFrameCount();
for (int i = 0; i < nFrames; i++) {
TextFrame poFrame = mpoStream.getFrame (i);
if (poFrame != null) {
mnLineCount += poFrame.getLineCount();
}
}
}
// TextDisplay (TextScroller poNewScroller, GFXEnv poEnv) {
// moConnect = poScroller;
// Initialize();
// mpoEnv = poEnv;
// }
// TextDisplay (GFXConnect poNewGfxConnect) {
// moConnect = poGfxConnect;
// Initialize();
// }
void connectStream (TextSparseStream poNewStream, boolean bSuppressLayout, TextAttr poDefaultAttr) {
cleanup();
mpoStream = poNewStream;
// moConnect.display (this);
mpoStream.textDisplaySet (this);
if (! bSuppressLayout) {
create (poDefaultAttr);
}
}
void connectStream (TextSparseStream poNewStream, boolean bSuppressLayout) {
connectStream (poNewStream, false, null);
}
void connectStream (TextSparseStream poNewStream) {
connectStream (poNewStream, false);
}
//----------------------------------------------------------------------
//
// Create: Initialize the display object.
//
//----------------------------------------------------------------------
void create (TextAttr poDefaultAttr) {
moChange.full();
layout (false, 0, false, poDefaultAttr);
// updateConnect();
}
//----------------------------------------------------------------------
//
// Update: Full regeneration of the display.
//
//----------------------------------------------------------------------
boolean update () {
if (mnIgnoreUpdates > 0) {
return true;
}
moChange.full();
return layout (true);
}
//----------------------------------------------------------------------
//
// UpdateInsert (with parameters): Partial update of the
// display, given insertion hint.
//
//----------------------------------------------------------------------
boolean updateInsert (TextStream poStream, int nIndex, int nChange) {
if (mnIgnoreUpdates > 0) {
return true;
}
moChange.insert (poStream, nIndex, nChange);
return layout (true);
}
//----------------------------------------------------------------------
//
// UpdateDelete (with parameters): Partial update of the
// display, given deleteion hint.
//
//----------------------------------------------------------------------
boolean updateDelete (TextStream poStream, int nIndex, int nChange) {
if (mnIgnoreUpdates > 0) {
return true;
}
moChange.delete (poStream, nIndex, nChange);
return layout (true);
}
//----------------------------------------------------------------------
//
// UpdateOther (with parameters): Partial update of the
// display, given any other hint.
//
//----------------------------------------------------------------------
boolean updateOther (TextStream poStream, int nIndex, int nCount) {
if (mnIgnoreUpdates > 0) {
return true;
}
moChange.other (poStream, nIndex, nCount);
return layout (true);
}
//----------------------------------------------------------------------
//
// UpdateToEnd: Stream has changed from this point on.
//
//----------------------------------------------------------------------
boolean updateToEnd (TextStream poStream, int nIndex) {
if (mnIgnoreUpdates > 0) {
return true;
}
moChange.toEnd (poStream, nIndex);
return layout (true);
}
//----------------------------------------------------------------------
//
// UpdateJustify: Only justification has changed.
//
//----------------------------------------------------------------------
void updateJustify () {
if (mnIgnoreUpdates > 0) {
return;
}
moChange.setJustify();
layout (true);
}
void detach (TextStream poStream) {
if (poStream != mpoStream) {
update();
}
poStream.textDisplaySet (null);
}
void ignoreUpdates (boolean bIgnore) {
if (bIgnore) {
mnIgnoreUpdates++;
} else {
assert (mnIgnoreUpdates > 0);
mnIgnoreUpdates--;
}
}
//----------------------------------------------------------------------
//
// CaretPos: Given a text position, determine the position
// and size of the caret. Return FALSE if the position
// cannot be located.
//
//----------------------------------------------------------------------
// boolean caretPos (TextPosnBase oPosn, Rect oCaret, boolean bAllowDangling) {
// TextFrameCaretRect oSearch (mpoStream, oPosn, bAllowDangling, false, oCaret);
// oSearch.processFrames();
// if (! oSearch.success()) {
// return false;
// }
// oCaret += CoordPair (UnitSpan.ZERO, oSearch.getOffset());
//
// return true;
// }
//----------------------------------------------------------------------
//
// CaretPos: given a stream and a coordinate pair, determine
// the nearest index in that stream corresponding to the
// point. Return FALSE if the stream cannot be located.
//
// Note: this function handles all cases, including checking
// for a poisition in an embedded field or a parent stream
// that includes a large embedded field.
//
//----------------------------------------------------------------------
// boolean caretPos (TextStream poStream, CoordPair oSearchPt, TextPosnBase oResult, boolean bAllowDescendents) {
// TextFrameCaretPosn oCaret (mpoStream, poStream, oSearchPt, bAllowDescendents, oResult);
// oCaret.processFrames();
// return oCaret.success();
// }
//----------------------------------------------------------------------
//
// FrameCaretPos: Given a text position, determine the frame
// and position and size of the caret relative to that frame.
// Return FALSE if the position cannot be located.
//
//----------------------------------------------------------------------
// boolean frameCaretPos (TextPosnBase oPosn, TextFrame poFrame, Rect oCaret, boolean bAllowDangling) {
// poFrame = null;
//
// TextFrameCaretRect oSearch (mpoStream, oPosn, bAllowDangling, false, oCaret);
// oSearch.processFrames();
// if (! oSearch.success()) {
// return false;
// }
// poFrame = oSearch.getFrame();
//
// return true;
// }
//----------------------------------------------------------------------
//
// FrameCaretPos: Given a frame, a stream and a coordinate
// pair, determine the nearest index in that stream
// corresponding to the point, within the given frame Return
// FALSE if the stream cannot be located.
//
// Note: this function handles all cases, including checking
// for a poisition in an embedded field or a parent stream
// that includes a large embedded field.
//
//----------------------------------------------------------------------
// boolean frameCaretPos (TextFrame poFrame, TextStream poStream, CoordPair oSearchPt, TextPosnBase oResult, boolean bAllowDescendents) {
// TextFrameCaretPosn oCaret (mpoStream, poStream, oSearchPt, bAllowDescendents, oResult);
// oCaret.lockFrame (poFrame);
// oCaret.processFrames();
// return oCaret.success();
// }
//----------------------------------------------------------------------
//
// CaretBaseline: Given a text position, return the "caret
// baseline point" for that position. See the definition of
// jtTextPosnBase for more information.
//
//----------------------------------------------------------------------
// CoordPair caretBaseline (TextPosnBase oPosn, boolean bAllowDangling) {
// Rect oCaret;
// TextFrameCaretRect oSearch (mpoStream, oPosn, bAllowDangling, true, oCaret);
// oSearch.processFrames();
// if (! oSearch.success()) {
// return CoordPair.zeroZero();
// }
//
//// TBD: doesn't seem to account for frame offset
// DispLineWrapped poLine = oSearch.getLine();
//
// CoordPair oResult;
// oResult.X ((oCaret.left() + oCaret.right()) / 2);
// oResult.Y (poLine.getBaselineOffset (true));
//
// ABXY.toXY (poLine.getXYOrigin(), poLine.frame().getLayoutOrientation(), oResult);
//
// return oResult;
// }
UnitSpan caretUp (TextPosnBase oPosn, UnitSpan poTarget, TextPosnBase oResult) {
return vertMove (oPosn, true, poTarget, oResult);
}
UnitSpan caretDown (TextPosnBase oPosn, UnitSpan poTarget, TextPosnBase oResult) {
return vertMove (oPosn, false, poTarget, oResult);
}
//----------------------------------------------------------------------
//
// CaretStartEnd: Move a position's caret to the start or end
// of its line.
//
//----------------------------------------------------------------------
boolean caretStartEnd (TextPosnBase oPosn, boolean bEnd, boolean bVisual, TextPosnBase oResult) {
FrameCaretStartEnd oCaret = new FrameCaretStartEnd (mpoStream, oPosn, bEnd, bVisual, oResult);
oCaret.processFrames();
return oCaret.success();
}
//----------------------------------------------------------------------
//
// CaretLeftRight: Move a position's caret to the left or
// right.
//
//----------------------------------------------------------------------
int caretLeftRight (TextPosnBase oPosn, boolean bRight, TextPosnBase oResult) {
FindCaretInfo findInfo = findCaretLine (oPosn);
if (findInfo == null) {
return '\0';
}
int nFrameIndex = findInfo.mFrameIndex;
int nLineIndex = findInfo.mLineIndex;
TextFrame poFrame = mpoStream.getFrame (nFrameIndex);
DispLineWrapped poLine = poFrame.getLine (nLineIndex);
int pcChar = poLine.getCaretLeftRight (oPosn, bRight, oResult);
if (pcChar != '\0') {
return pcChar;
}
TextPosnBase oOtherPosn = new TextPosnBase();
int eOtherCaret = DispLineWrapped.CARET_INVALID;
// TBD: do this in a paragraph basis; avoid infinite loop
if (bRight == isRTL()) { // left in LTR or right in RTL ...
for (; ; ) {
if (nLineIndex == 0) {
if (nFrameIndex == 0) {
break;
}
nFrameIndex--;
poFrame = mpoStream.forceFrame (nFrameIndex);
nLineIndex = poFrame.getLineCount();
}
if (nLineIndex > 0) {
nLineIndex--;
poLine = poFrame.getLine (nLineIndex);
eOtherCaret = poLine.getCaretStartEnd (oPosn.stream(), true, true, oOtherPosn);
if (eOtherCaret != DispLineWrapped.CARET_INVALID) {
// Obscure: If the main direction is RTL and the text is true BiDi,
// prevent the ambiguous position at the end (left) of the previous line
// from mapping to the same ambiguous position in the middle of the line
// before that on caret display.
if (isRTL()) {
oOtherPosn.affinity (TextPosnBase.AFFINITY_AFTER);
}
pcChar = poLine.getLastGlyphChar();
break;
}
}
}
}
else { // right in LTR or left in RTL ...
pcChar = poLine.getLastGlyphChar();
for (; ; ) {
nLineIndex++;
if (nLineIndex >= poFrame.getLineCount()) {
nFrameIndex++;
if (nFrameIndex >= mpoStream.getFrameCount()) {
break;
}
poFrame = mpoStream.forceFrame (nFrameIndex);
nLineIndex = 0;
}
poLine = poFrame.getLine (nLineIndex);
eOtherCaret = poLine.getCaretStartEnd (oPosn.stream(), false, true, oOtherPosn);
if (eOtherCaret != DispLineWrapped.CARET_INVALID) {
break;
}
}
}
if (eOtherCaret == DispLineWrapped.CARET_INVALID) {
return '\0';
}
oResult.copyFrom (oOtherPosn);
return pcChar;
}
void checkAXTELigature (TextPosnBase oPosn, boolean bForward) {
FindCaretInfo info = findCaretLine (oPosn);
if (info == null) {
return;
}
TextFrame poFrame = mpoStream.getFrame (info.mFrameIndex);
poFrame.getLine (info.mLineIndex).checkAXTELigature (oPosn, bForward);
}
//----------------------------------------------------------------------
//
// IsAtStart: Determine whether the position is at the start
// of the line.
//
//----------------------------------------------------------------------
boolean isAtStart (TextPosnBase oPosn, boolean bCheckFirstLineOnly) {
FindCaretInfo info = findCaretLine (oPosn);
if (info == null) {
return false;
}
if (bCheckFirstLineOnly && ((info.mFrameIndex > 0) || (info.mLineIndex > 0))) {
return false;
}
TextFrame poFrame = mpoStream.getFrame (info.mFrameIndex);
return poFrame.getLine (info.mLineIndex).isAtStart (oPosn);
}
// void scrollTo (TextPosnBase oPosn, GFXEnv oGfxEnv) {
// moConnect.scrollTo (oPosn, oGfxEnv);
// }
//----------------------------------------------------------------------
//
// Proprietary, for use by class TextEditor
//
//----------------------------------------------------------------------
// void onChange (boolean bExtentChanged) {
// moConnect.onChange (bExtentChanged);
// }
//----------------------------------------------------------------------
//
// Proprietary, for use by class TextScroller.
//
//----------------------------------------------------------------------
// void adjustObjects (GFXEnv poEnv, CoordPair oDisplacement) {
// for (int i = 0; i < moLines.size(); i++) {
// moLines[i].adjustObjects (poEnv, oDisplacement);
// }
// }
// UnitSpan subHeight (int nStart, int nEnd) {
// if (nEnd <= nStart) {
// return UnitSpan.ZERO;
// }
//
// int nFrames = mpoStream.getFrameCount();
// if (nFrames == 0) {
// return UnitSpan.ZERO;
// }
//
// DispLineWrapped poLine;
//
// FrameLineFinder oFindStart (mpoStream, nStart);
// if (oFindStart.processFrames()) {
// return UnitSpan.ZERO;
// }
// poLine = oFindStart.getFrame().getLine (oFindStart.getLineIndex());
//
// UnitSpan oStart (oFindStart.getOffset() + poLine.getBMin());
//
// FrameLineFinder oFindEnd (mpoStream, nEnd - 1);
// UnitSpan oEnd;
// if (oFindEnd.processFrames()) {
// oEnd = oFindEnd.getOffset(); // ran off end
// } else {
// poLine = oFindEnd.getFrame().getLine (oFindEnd.getLineIndex());
// oEnd = oFindEnd.getOffset() + poLine.getBMax();
// }
//
// if (mpoEnv != null) {
// oStart = mpoEnv.unitH (mpoEnv.devH (oStart));
// oEnd = mpoEnv.unitH (mpoEnv.devH (oEnd));
// }
//
// return oEnd - oStart;
// }
// UnitSpan lineMinY (int nIndex) {
// FrameLineFinder oFinder (mpoStream, nIndex);
// if (oFinder.processFrames()) {
// return UnitSpan.ZERO; // ran off the end
// }
// DispLineWrapped poLine = oFinder.getFrame().getLine (oFinder.getLineIndex());
// return oFinder.getOffset() + poLine.getBMin();
// }
TextContext getContext () {
return (mpoStream == null) ? null : mpoStream.forceContext();
}
GFXEnv getGFXEnv () {
return mpoEnv;
}
// GFXEnv getGfxEnv () {
// return mpoEnv;
// }
DispChange getChange () {
return moChange;
}
// TextFontMap getFontMap () {
// return getContext().getFontMap();
// }
DispMapSet getDisposableMaps () {
return getContext().getDisposableMaps();
}
public void releaseDisposableMaps (DispMapSet poMaps) {
getContext().releaseDisposableMaps (poMaps);
}
// AXTEWRSBase getWRS () {
// return getContext().getWRS();
// }
// TextGlyphArray getGlyphArray () {
// return getContext().getGlyphArray();
// }
void setLocale (TextAttr poAttr) {
int eDirection = TextAttr.DIRECTION_NEUTRAL;
String sLocale = "";
if (poAttr != null) {
eDirection = poAttr.paraDirection();
if (eDirection == TextAttr.DIRECTION_NEUTRAL) {
eDirection = poAttr.direction();
}
sLocale = poAttr.actualLocale();
}
moLocaleInfo = getContext().lookupLocale (sLocale);
moLocaleInfo = new LocaleInfo (moLocaleInfo); // TODO: could this be cached?
if (mpoStream.defaultDirection() != TextAttr.DIRECTION_NEUTRAL) {
eDirection = mpoStream.defaultDirection();
}
switch (eDirection) {
case TextAttr.DIRECTION_LTR:
moLocaleInfo.mbIsRTL = false;
break;
case TextAttr.DIRECTION_RTL:
moLocaleInfo.mbIsRTL = true;
break;
}
}
LocaleInfo getLocale (String sLocaleName) {
return getContext().lookupLocale (sLocaleName);
}
TextBreakFinder getBreakFinder () {
mpoBreakFinder = TextBreakFinder.recycle (moLocaleInfo.mpoLocale, mpoBreakFinder);
return mpoBreakFinder;
}
int getLayoutOrientation () {
return meLayoutOrientation;
}
boolean isRTL (TextAttr poAttr) {
boolean bRTL = moLocaleInfo.mbIsRTL;
if (poAttr != null) {
int eDirection = poAttr.direction();
if (eDirection == TextAttr.DIRECTION_LTR) {
bRTL = false;
} else if (eDirection == TextAttr.DIRECTION_RTL) {
bRTL = true;
} else {
LocaleInfo oLocaleInfo = getContext().lookupLocale (poAttr.actualLocale());
bRTL = oLocaleInfo.mbIsRTL;
}
}
return bRTL;
}
boolean isRTL () {
return isRTL (null);
}
boolean isIdeographic (TextAttr poAttr) {
boolean bIdeographic = moLocaleInfo.mbIsIdeographic;
if (poAttr != null) {
LocaleInfo oLocaleInfo = getContext().lookupLocale (poAttr.actualLocale());
bIdeographic = oLocaleInfo.mbIsIdeographic;
}
return bIdeographic;
}
boolean isIdeographic () {
return isIdeographic (null);
}
// boolean optycaJustify (TextAttr poAttr) {
// boolean bOptycaJustify = moLocaleInfo.mbOptycaJustify;
//
// if (poAttr != null) {
// LocaleInfo oLocaleInfo = GetContext().lookupLocale (poAttr.actualLocale());
// bOptycaJustify = oLocaleInfo.mbOptycaJustify;
// }
//
// return bOptycaJustify;
// }
int getDigits (TextAttr poAttr) {
if ((poAttr == null) || (poAttr.digits() == TextAttr.DIGITS_LOCALE)) {
return moLocaleInfo.meDigits;
} else {
return poAttr.digits();
}
}
boolean[] getBreakCandidates (int nCount, int nPreserve) {
return getContext().getBreakCandidates (nCount, nPreserve);
}
boolean[] getBreakCandidates (int nCount) {
return getContext().getBreakCandidates (nCount, 0);
}
int getLegacyLevel () {
return mpoStream.getLegacyLevel();
}
//----------------------------------------------------------------------
//
// FindCaretLine: given a stream and an index in that stream,
// determine the position and size of the caret, and the
// index of the line containing the caret. Return FALSE if
// the position cannot be located.
//
//----------------------------------------------------------------------
FindCaretInfo findCaretLine (TextPosnBase oPosn) {
FrameCaretRect oSearch = new FrameCaretRect (mpoStream, oPosn, false, true);
oSearch.processFrames();
if (! oSearch.success()) {
return null;
}
return new FindCaretInfo (oSearch.getFrameIndex(), oSearch.getLineIndex(), oSearch.getCaret());
}
//----------------------------------------------------------------------
//
// Layout: Format the text, building the completed line list.
//
// This code handles both the full format and the case when
// only a partial reformat is required. There is a
// substantial cost in reformatting a large, multi-line text
// block, so we try to avoid it on small changes.
//
// There are two phases to the format operation: layout,
// justification and display invalidation. Because much of
// the layout happens in the various TextDispLine...
// classes, most of the code here is for justification and
// invalidation.
//
//----------------------------------------------------------------------
private boolean layout (boolean bUpdate, int nFrameIndex, boolean bSuppressSuspectFrames, TextAttr poDefaultAttr) {
int i;
// Do not do anything if formatting is disabled.
if ((mnSuppressFormat > 0) || (mnIgnoreUpdates > 0)) {
return true;
}
// Treat initial call as full format even if caller didn't request it. // TODO: is this check necessary?
// if (lines() == 0) {
// bUpdate = false;
// moChange.full();
// }
// Horizontally growing object: we currently cannot optimize, as a size
// change may influence the layout of unchanged lines. TBD: this is far
// too restrictive; most cases could be treated like fixed-size blocks.
// Need to investigate.
if (mpoStream.getFrameCount() > 0) {
TextFrame poFrame = mpoStream.getFrame (0);
if ((poFrame != null) && (! poFrame.alignHPoint()) && (poFrame.minWidth() != poFrame.maxWidth())) {
moChange.full();
}
}
// Stream/attr initializations.
TextPosnBase oStartStream = new TextPosnBase (mpoStream);
TextAttr poAttr = oStartStream.attributePtr();
int eJustH = TextAttr.JUST_H_LEFT;
int eJustV = TextAttr.JUST_V_TOP;
meLayoutOrientation = TextAttr.ORIENTATION_HORIZONTAL;
if (poAttr != null) {
eJustH = poAttr.justifyH();
eJustV = poAttr.justifyV();
if (poAttr.layoutOrientationEnable()) {
meLayoutOrientation = poAttr.layoutOrientation();
}
if (poAttr.fontEnable() && poAttr.substituteFont()) {
setFontSubstitution (true);
}
}
setLocale (poAttr);
// Create important objects for the formatting operation.
FormatInfo oInfo = new FormatInfo (this, moTabs, bUpdate, eJustH, eJustV, nFrameIndex, poDefaultAttr);
// Clear the last line's last line flag because we may add new lines.
if (oInfo.isUpdate() && (oInfo.getChange().type() != DispChange.CHANGE_NONE)) {
mpoStream.updateLastLineFlag (false);
}
// The following if-block does the (possibly optimized) layout. Skip it
// if this is just a justification update. // and not all loaded layouts?
// The following loop does the actual layout of the lines, all or changed
// only. Each iteration creates one raw line, which in turn creates all
// the wrapped lines that correspond to it.
if ((oInfo.getChange().type() != DispChange.CHANGE_NONE) && (! oInfo.allInitialLayout())) { // not justify only?
boolean bNewPara = oInfo.isNewPara();
boolean bContinue = true;
do {
DispLineRaw oRawLine = new DispLineRaw (oInfo, bNewPara);
bContinue = oRawLine.fill();
bNewPara = oRawLine.isLastParaLine();
oRawLine.detach();
} while (bContinue);
}
// Restore the last line's last line flag.
if (oInfo.getChange().type() != DispChange.CHANGE_NONE) {
mpoStream.updateLastLineFlag (true);
}
oInfo.finish();
// Count the number of lines by iterating through the frames. If this is
// a justify-only operation, justify each frame.
mnLineCount = 0;
int nFrames = mpoStream.getFrameCount();
for (i = 0; i < nFrames; i++) {
TextFrame poFrame = mpoStream.getFrame (i);
if (poFrame != null) {
if (moChange.type() == DispChange.CHANGE_NONE) {
// poFrame.justify (oInfo); // TODO:
}
mnLineCount += poFrame.getLineCount();
}
}
if (oInfo.updateConnect()) {
// UpdateConnect();
}
// DebugFrames();
moChange.reset();
if (! bSuppressSuspectFrames) {
// UpdateSuspectLayout();
}
return oInfo.fits();
}
private boolean layout (boolean bUpdate) {
return layout (bUpdate, 0, true, null);
}
//----------------------------------------------------------------------
//
// UpdateConnect: Update our connect object (notably for
// scrolling information).
//
//----------------------------------------------------------------------
// void updateConnect () {
//// TBD: does this need to be extended to multi-frame environment?
// TextFrame poFrame = mpoStream.forceFrame (0);
// moConnect.onUpdate (poFrame.getLineCount(), poFrame.getExtent());
// }
//----------------------------------------------------------------------
//
// VertMove: Perform vertical move, up or down.
//
//----------------------------------------------------------------------
UnitSpan vertMove (TextPosnBase oPosn, boolean bUp, UnitSpan poTarget, TextPosnBase oResult) {
// First, find the line that contains this position's index.
FindCaretInfo info = findCaretLine (oPosn);
if (info == null) {
return null;
}
Rect oCaret = info.mCaret;
int nFrameIndex = info.mFrameIndex;
int nLineIndex = info.mLineIndex;
// The position has a horizontal "target" that we try to match as we move
// up or down, so that moving through a short line doesn't cause the
// horizontal caret position to change. If the target hasn't been set,
// set at as the horizontal midpoint of the caret rectangle. Note that
// the caller's target pointer is passed by reference, but we cache our
// own pointer on the stack. This is because if we set the caller's
// target too soon, it may get cleared as a result of calls we make here
// (notably forcing frames to load).
if (poTarget == null) {
poTarget = oCaret.left().add (oCaret.right());
poTarget = poTarget.divide (2);
}
TextFrame poFrame = mpoStream.getFrame (nFrameIndex);
boolean bFound = false;
if (bUp) {
// If moving up, iterate upward from the found line in the found frame.
// If all previous lines in the frame are exhausted, go to the previous
// frame.
while (! bFound) {
if (nLineIndex == 0) {
if (nFrameIndex == 0) {
break;
}
nFrameIndex--;
poFrame = mpoStream.forceFrame (nFrameIndex);
nLineIndex = poFrame.getLineCount();
}
if (nLineIndex > 0) {
nLineIndex--;
DispLineWrapped poLine = poFrame.getLine (nLineIndex);
if (poLine.getCaretPosn (oPosn.stream(), poTarget, oResult) != DispLine.CARET_INVALID) {
bFound = true;
}
}
}
}
else {
// If moving down, iterate downward from the found line in the found
// frame. If all subsequent lines in the frame are exhausted, go to the
// next frame.
nLineIndex++;
while (! bFound) {
if (nLineIndex >= poFrame.getLineCount()) {
nFrameIndex++;
if (nFrameIndex >= mpoStream.getFrameCount()) {
break;
}
poFrame = mpoStream.forceFrame (nFrameIndex);
nLineIndex = 0;
}
DispLineWrapped poLine = poFrame.getLine (nLineIndex);
if (poLine.getCaretPosn (oPosn.stream(), poTarget, oResult) != DispLine.CARET_INVALID) {
bFound = true;
}
nLineIndex++;
}
}
return poTarget;
}
void detachStream (TextStream poStream) {
if (poStream == null) {
return;
}
poStream.textDisplaySet (null);
Storage oFields = new Storage();
poStream.enumField (oFields);
for (int i = 0; i < oFields.size(); i++) {
detachStream (oFields.get(i));
}
}
//----------------------------------------------------------------------
//
// InvalidateSel: Invalidate the display by a given range.
//
//----------------------------------------------------------------------
// void invalidateSel (TextRange oRange, GFXEnv poGfxEnv, boolean bEraseBkgnd) {
// TextPosn oStart = oRange.start();
// TextPosn oEnd = oRange.end();
//
// if (oStart.index() == oEnd.index()) {
// return;
// }
//
// int nFrame;
// int nLine;
// CoordPair oOffset;
// if (! FindCaretLine (oStart, nFrame, nLine, null, oOffset)) {
// return;
// }
//
// int nFrameEnd;
// int nLineEnd;
// if (! FindCaretLine (oEnd, nFrameEnd, nLineEnd)) {
// return;
// }
//
// TextFrame poFrame = mpoStream.getFrame (nFrame);
// boolean bIsFirstLine = true;
//
// for (; ; ) {
// if (poFrame != null) {
// DispLineWrapped poLine = poFrame.getLine (nLine);
//
// Rect oInvalid;
// boolean bAnyInvalid;
//
// boolean bIsLastLine = (nLine == nLineEnd) && (nFrame == nFrameEnd);
// if ((! bIsFirstLine) && (! bIsLastLine)) {
// oInvalid.leftRight (poLine.getAMin(), poLine.getAMax (true));
// bAnyInvalid = true;
// } else {
// UnitSpan oLeft;
// UnitSpan oRight;
// bAnyInvalid = poLine.getInvalidationRect (oStart, oEnd, oLeft, oRight);
// oInvalid.leftRight (oLeft, oRight);
// }
//
// oInvalid.topBottom (poLine.getBMinExtended (true), poLine.getBMaxExtended (true));
// ABXY.toXY (poLine.getXYOrigin(), poLine.frame().getLayoutOrientation(), oInvalid);
// oInvalid += oOffset;
// moConnect.invalidateArea (poGfxEnv, oInvalid, bEraseBkgnd);
//
// if (bIsLastLine) {
// break;
// }
// }
//
// nLine++;
// if ((poFrame == null) || (nLine >= poFrame.getLineCount())) {
// nFrame++;
// if (nFrame >= mpoStream.getFrameCount()) {
// break;
// }
// poFrame = mpoStream.getFrame (nFrame);
// if (poFrame != null) {
// oOffset.Y (oOffset.Y() + poFrame.getBMax());
// }
// nLine = 0;
// }
// }
//
// UpdateConnect();
// }
void cleanup () {
detachStream (mpoStream);
mpoStream = null;
// mpoEnv = null;
mnSuppressFormat = 0;
mnIgnoreUpdates = 0;
mpoBreakFinder = null;
moTabs.setSize (0);
mnLineCount = 0;
mbHasFontSubstitution = false;
}
// void debugFrames () {
// if (AXTEFp != null) {
// int nFrames = mpoStream.getFrameCount();
// for (int i = 0; i < nFrames; i++) {
// fprintf (AXTEFp, "Frame %d:\\\\n", i);
// TextFrame poFrame = mpoStream.getFrame (i);
// if (poFrame != null) {
// poFrame.debugLines();
// }
// }
// }
// }
private static Rect computeClipRect (TextSparseStream poStream) {
return computeClipRect (poStream, TextDrawInfo.INVALID_DEFAULT_DECLARED_SIZE);
}
private static Rect computeClipRect (TextSparseStream poStream, int eInvalidDefault) {
Rect oExtent;
if ((eInvalidDefault == TextDrawInfo.INVALID_DEFAULT_RUNTIME_EXTENT) && (poStream.display() != null)) {
oExtent = poStream.display().runtimeExtent();
}
else {
// A bit of a hack.
TextFrame poFrame = poStream.forceFrame (0);
oExtent = poFrame.getExtent();
if (eInvalidDefault == TextDrawInfo.INVALID_DEFAULT_DECLARED_SIZE) {
UnitSpan oMaxWidth = poFrame.maxWidth();
if (oMaxWidth.value() >= 0) {
// oMaxWidth value of 0 is valid , Watson bug 1171970
oExtent = oExtent.leftRight (UnitSpan.ZERO, oMaxWidth);
}
UnitSpan oMaxHeight = poFrame.maxHeight();
if (oMaxHeight.value() > 0) {
oExtent = oExtent.topBottom (UnitSpan.ZERO, oMaxHeight);
}
}
}
return oExtent;
}
}