com.adobe.xfa.text.DispLineRaw Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
The newest version!
package com.adobe.xfa.text;
import com.adobe.fontengine.font.FontLoadingException;
import com.adobe.fontengine.font.InvalidFontException;
import com.adobe.fontengine.font.UnsupportedFontException;
import com.adobe.fontengine.inlineformatting.BasicFormatter;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.IntegerHolder;
import com.adobe.xfa.ut.UnitSpan;
class DispLineRaw extends DispLine {
static final int NEW_LINE_CONTINUE = 0;
static final int NEW_LINE_STOP = 1;
static final int NEW_LINE_RETRY = 2;
static final int GLYPH_ACCUMULATE = 0;
static final int GLYPH_ALREADY_HANDLED = 1;
static final int GLYPH_BREAK_NOW = 2;
static final int GLYPH_BREAK_NEXT = 3;
static final int GLYPH_BREAK_WORD = 4;
static final int GLYPH_BREAK_TAB = 5;
private FormatInfo moFormatInfo;
DispLineRaw (FormatInfo oFormatInfo, boolean bIsFirstParaLine) {
super (oFormatInfo.getFrame());
moFormatInfo = oFormatInfo;
setFirstLineInStream (oFormatInfo.isFirstLine());
setFirstParaLine (bIsFirstParaLine);
setVerticalOrientation (frame().getLayoutOrientation() != TextAttr.ORIENTATION_HORIZONTAL);
}
// TODO: is this still required?
// DispLineRaw (DispMapSet poMaps, FormatInfo oFormatInfo) {
// super (poMaps);
// moFormatInfo = oFormatInfo;
// }
boolean fill () {
boolean bRTL = false;
if (moFormatInfo.isNewPara()) { // starting new para ...
int eDirection = TextAttr.DIRECTION_NEUTRAL;
TextAttr poAttr = moFormatInfo.getAttr();
if (poAttr != null)
eDirection = poAttr.paraDirection();
switch (eDirection) {
case TextAttr.DIRECTION_LTR:
bRTL = false;
break;
case TextAttr.DIRECTION_RTL:
bRTL = true;
break;
default:
switch (display().stream().defaultDirection()) {
case TextAttr.DIRECTION_LTR:
bRTL = false;
break;
case TextAttr.DIRECTION_RTL:
bRTL = true;
break;
default:
bRTL = display().isRTL (poAttr);
break;
}
}
moFormatInfo.setRTL (bRTL);
} else { // after forced break in same para ...
bRTL = moFormatInfo.isRTL(); // ... use RTL from start of para
}
setRTL (bRTL);
int nTabStart = moFormatInfo.getTabSize();
boolean bContinue = extractRawLine();
format();
// createGlyphs();
bContinue = wrap (nTabStart, bContinue);
return bContinue;
}
void fill (DispLineRaw poSource, int nSourceIndex, int nSourceLength, TextAttr poAttr, String sContent, boolean bIsPrefix) {
clear();
initialize (poSource.frame());
setHasBIDI (poSource.hasBIDI());
setOptycaMapping (poSource.getGlyphLocMap().size() > 0);
preAllocChars (sContent.length()); // TODO:
//DispPosn oPosition = poSource.getMappedPosition (nSourceIndex);
//int nStreamIndex = charToStreamIndex (oPosition, nSourceIndex, 0);
//TextPosnBase oStart = new TextPosnBase (oPosition.pp().stream(), nStreamIndex);
/* TODO:
int i;
RawProcessor oProcessor (moFormatInfo, this, oStart, poAttr);
for (i = 0; i < sContent.length(); ) {
UniChar c = sContent.uniChar (i);
oProcessor.processChar (c, TextCharProp.getCharProperty (c));
}
oProcessor.finish (false, false);
*/
clearPositionMap();
CharMapper oMapper = new CharMapper (this, poSource, nSourceIndex, nSourceLength, bIsPrefix);
oMapper.map();
format();
// createGlyphs();
}
FormatInfo getFormatInfo () {
return moFormatInfo;
}
int newLine (WrapInfo oInfo, TabControl poTabControl, int eLineBreak) {
// Obtain space information for the created line
oInfo.moLineDesc.mnInternalSpaces = oInfo.moLine.getInternalSpaces();
oInfo.moLineDesc.mnTrailingSpaces = oInfo.moLine.getTrailingSpaces()
+ oInfo.moLine.getTrailingGlue();
// For correct WRServices handling of the line, internal tabs need to be
// represented by embedded object characters, while trailing tabs must be
// represented by spaces. Run through the trailing characters and
// convert any tab embedded object characters to spaces.
// TBD: this probably messes up if the line is reprocessed in a new frame
int nLineMax = oInfo.moLine.getStartCharIndex() + oInfo.moLine.getCharLength();
int nTrail = nLineMax - oInfo.moLineDesc.mnTrailingSpaces;
for (; nTrail < nLineMax; nTrail++) {
DispTab poTab = tabAt (nTrail);
if (poTab != null) {
setChar (nTrail, ' ', TextCharProp.defaultSpace);
poTab.setTrailing (true);
}
}
// Commit the new line.
int eNewLine = newLine (oInfo.mbFirstLine,
oInfo.moLine.getStartCharIndex(),
oInfo.moLine.getCharLength(),
oInfo.moLineDesc,
oInfo.meLineBreak,
eLineBreak);
// New line didn't fit and new frame has different geometry: we will have
// to reprocess the entire line in the new frame.
switch (eNewLine) {
case NEW_LINE_RETRY:
oInfo.meLineBreak = LINE_BREAK_NORMAL;
if (poTabControl != null) {
poTabControl.restartLine (oInfo);
}
break;
// Stop due to frame full or end of incremental update
case NEW_LINE_STOP:
return NEW_LINE_STOP;
// Normal case: start the line at the beginning of the offending word.
default:
oInfo.mbFirstLine = false; // only if the line is successfully committed
oInfo.moLine.reset (oInfo.moWord);
if (poTabControl != null) {
oInfo.mnNextTab = poTabControl.getTabIndex();
}
oInfo.meLineBreak = eLineBreak;
break;
}
// Reset for next time.
determineLineLimits (oInfo, false);
oInfo.moLineDesc.mnInternalSpaces = 0;
oInfo.moLineDesc.mnTrailingSpaces = 0;
return eNewLine;
}
int newLine (WrapInfo oInfo, TabControl poTabControl) {
return newLine (oInfo, poTabControl, LINE_BREAK_NORMAL);
}
private boolean extractRawLine () {
// Extract the raw line's content in two passes. Pass one simply counts
// the number of characters required, so that the array can be
// pre-allocated, rather than reallocated incrementally.
PosnStack oTempStack = new PosnStack (moFormatInfo.posnStack());
RawCounter oCounter = new RawCounter (moFormatInfo, oTempStack);
oCounter.run();
preAllocChars (oCounter.getCount());
// Pass two does the heavy lifting. This will populate all the raw
// line's structures.
RawBuilder oBuilder = new RawBuilder (moFormatInfo, this);
oBuilder.run();
oBuilder.finish();
return oBuilder.canContinue();
}
private void format () {
int i;
// The presence of RTL text forces char/glyph mapping up front so that
// BIDI levels can be made available to AFE formatting.
MappingManager mappingManager = null;
if (hasBIDI()) {
mappingManager = display().getContext().getMappingManager();
mappingManager.analyze (this);
}
moFormatInfo.setMappingManager (mappingManager);
// Obtain an AFE run and initially populate it with the content of this
// line. This is the vehicle for describing rich text to AFE.
AFERun afeRun = moFormatInfo.getAFERun();
afeRun.load (this);
// This loop iterates through the content, one character at a time,
// looking for AXTE display run breaks. Each AXTE display run
// (identified by its start and limit indexes) is one unit to pass to the
// AFE formatter.
BasicFormatter formatter = BasicFormatter.getFormatterInstance();
int limit = getCharCount();
boolean allowKerning = false;
try
{
int start = 0;
i = 0;
while (i < limit) {
AFEElement element = afeRun.getElement (i);
if (element.getRunBreak()) {
if (start < i) {
int end = i;
if (afeRun.getElement(start).getEmbed() == null) { // AFE discards tabs, which are represented as embedded objects
end = formatter.preFormat (afeRun, start, end);
end = formatter.format (afeRun, start, end, allowKerning);
}
limit += end - i;
start = end;
i = end;
}
allowKerning = element.allowKerning();
}
i++;
}
if (start < limit) {
if (afeRun.getElement(start).getEmbed() == null) { // AFE discards tabs, which are represented as embedded objects
limit = formatter.preFormat (afeRun, start, limit);
limit = formatter.format (afeRun, start, limit, allowKerning);
}
}
} catch (FontLoadingException e) {
assert (false);
} catch (InvalidFontException e) {
assert (false);
} catch (UnsupportedFontException e) {
assert (false);
} catch (Exception e) {
assert (false);
}
// System.out.println (afeRun.toString());
// During formatting, AFE makes calls to AFE run callbacks that may
// affect the character/glyph mapping. If a mapping manager hasn't
// already been created and this line contains complex LTR mappings,
// mapping analysis will have to occur now. Note that if there was RTL
// text, analysis occured before the formatting loop and doesn't need to
// be repeated.
DispMapSet maps = getMaps();
boolean mapRequired = afeRun.isMapRequired();
maps.preAllocGlyphs (limit, mapRequired);
if (mapRequired) {
if (mappingManager == null) {
mappingManager = display().getContext().getMappingManager();
mappingManager.analyze (this);
moFormatInfo.setMappingManager (mappingManager);
}
}
// Step through the final set of elements in the AFE run and generate an
// AXTE glyph object for each one.
GlyphMaker glyphMaker = new GlyphMaker (this);
for (i = 0; i < limit; i++) {
AFEElement afeElement = afeRun.getElement (i);
int charIndex = afeElement.getMapIndex();
glyphMaker.addGlyph (afeElement, charIndex);
if (mapRequired) {
GlyphLoc glyphLoc = new GlyphLoc (i, charIndex, afeElement.getMapLength());
maps.moGlyphLocMap.add (glyphLoc);
}
}
glyphMaker.applyWidths();
}
private boolean wrap (int nTabStart, boolean bContinue) {
// Once the raw line's content has been generated, this method performs
// line-breaking, generating as many "wrapped" lines as required.
WrapInfo oInfo = new WrapInfo (moFormatInfo, this, nTabStart);
// Establish initial attributes.
TextAttr poAttr = null;
if (getCharCount() == 0) {
poAttr = getLastAttr();
} else {
int nRunMapIndex = getRunMap().findItem (0);
assert (getRunMap().isValidMapIndex (nRunMapIndex));
poAttr = getRun(nRunMapIndex).getAttr();
}
// Determine justification and margins. Note the inversion of the
// concepts of left and right when dealing with RTL text.
oInfo.moLineDesc.meJustifyH = TextAttr.JUST_H_LEFT;
oInfo.moLineDesc.mnInternalSpaces = 0;
oInfo.moLineDesc.mnTrailingSpaces = 0;
if (poAttr != null) {
if (poAttr.justifyHEnable()) {
oInfo.moLineDesc.meJustifyH = poAttr.justifyH();
}
if (poAttr.marginLEnable()) {
oInfo.moLineDesc.moMarginL = Units.toFloat (poAttr.marginL().getLength());
}
if (poAttr.marginREnable()) {
oInfo.moLineDesc.moMarginR = Units.toFloat (poAttr.marginR().getLength());
}
float oSpecial = Units.toFloat (poAttr.special().getLength());
if (oSpecial >= 0.0f)
oInfo.moSpecialFirst = oSpecial;
else
oInfo.moSpecialRest = -oSpecial;
}
determineLineLimits (oInfo, true);
// Generate wrapped lines
boolean bWordWrap = (! oInfo.moFormat.getFrame().unlimitedWidth())
&& (! oInfo.moFormat.getFrame().suppressWordWrap());
// If this is an empty line (empty text object or text object ends in
// new-line or paragraph mark), we create a single empty wrapped line.
if (getCharCount() == 0) {
for (int nTry = 0; nTry < 2; nTry++) {
int eNewLine = newLine (oInfo, null);
if (eNewLine != NEW_LINE_RETRY) {
if (eNewLine == NEW_LINE_STOP)
bContinue = false;
break;
}
}
}
// Comb cells require special processing for wrapping.
else if (getCombCells() > 0) {
int nCharFirst = 0;
// TBD: this duplicates part of the wrapped line comb cell determination.
// Need to factor out common code for a consistent implementation.
if (bWordWrap) {
int nCell = 0;
int nGlyph = 0;
int nCharNext = 0;
for (;;) {
if (nCell >= getCombCells()) {
newLine (oInfo.mbFirstLine, // TBD: worry about retry?
nCharFirst,
nCharNext - nCharFirst,
oInfo.moLineDesc);
nCell = 0;
}
GlyphLoc oGlyphLoc = getOrderedGlyphLoc (nGlyph);
if (nCell == 0)
nCharFirst = oGlyphLoc.getMapIndex();
nGlyph = nextGlyphBaseAccentIndex (nGlyph);
if (nGlyph >= getGlyphCount())
break;
nCharNext = getOrderedGlyphLoc(nGlyph).getMapIndex();
nCell++;
}
}
newLine (oInfo.mbFirstLine,
nCharFirst,
getCharCount() - nCharFirst,
oInfo.moLineDesc);
}
// Normal case: this line has content to go through regular wrapping.
else {
// Establish the word-wrap tolerance. If legacy positioning, allow
// simply for the difference between (old) unit spans and (new) floating
// point (i.e., pt/1000), because we're using the old truncated metrics.
// If new positioning, use a slightly larger threshold, so that legacy
// forms can be updated without introducing a lot of wrapping problems.
double nBreakTolerance = legacyPositioning() ? 0.001 : 0.5;
// Establish break candidates. If the raw line supports word-wrapping,
// build a Boolean array of candidates. Each element in the array is
// TRUE if a break is allowed before the corresponding _character_ and
// FALSE otherwise.
boolean[] pbBreakCandidates = null;
if (bWordWrap) {
pbBreakCandidates = display().getBreakCandidates (getCharCount());
TextBreakFinder poBreakFinder = display().getBreakFinder();
poBreakFinder.setBreakCandidates (getCharArray(),
getBreakArray(),
pbBreakCandidates,
legacyPositioning());
}
// Other initializations for line-breaking. Most significant is the
// creation of a tab control object which not only manages tabs but also
// keeps track of the accumulated line width.
boolean bForceBreak = false;
TabControl oTabControl = new TabControl (this, moFormatInfo, nTabStart, bWordWrap, oInfo.moLineWidth);
HyphenControl oHyphenControl = new HyphenControl (this, oInfo, oTabControl, poAttr);
boolean bBreakLoop = false;
// Iterate over each _glyph_ in the line. At this point there is not yet
// any RTL reordering in the glyphs. If there is a direction change in
// the line, we will continue to accumulate characters in logical order
// as long as space remains.
for (int nGlyph = 0; (nGlyph < getGlyphCount()) && (! bBreakLoop); ) {
Glyph oGlyph = getGlyph (nGlyph);
int nCharIndex = getGlyphLocCharIndex (nGlyph);
int c = getChar (nCharIndex); // first one if ligature
int eBreak = getBreakClass (nCharIndex);
// Watson 1245432: The presence of Unicode direction control characters
// at the end of a line can wreak havoc with alignment. Treat them as
// spaces to avoid this.
if ((c >= 0x202A) && (c <= 0x202E)) {
eBreak = TextCharProp.BREAK_SP;
}
// If we can or must break on this character, commit the previously
// accumulated word to the current line, before looking at the character.
if (bWordWrap) {
if (bForceBreak || ((nCharIndex > 0) && pbBreakCandidates[nCharIndex]))
oInfo.moLine.commitWord (oInfo.moWord);
}
bForceBreak = false;
// Process this glyph for tab information and determine the new
// accumulated line width. This returns a rough-cut specification of how
// to handle the glyph. Refine with hyphenation information.
int eProcess = oTabControl.processGlyph (oGlyph, nCharIndex, c);
eProcess = oHyphenControl.testHyphenation (eProcess, nGlyph);
// Refine the glyph processing specification if designated as
// "accumulate". The multi-branch if statement checks from least to most
// severe cases.
float oNewWidth = oTabControl.getLineWidth();
if (eProcess == GLYPH_ACCUMULATE) {
// Accumulate the glyph if it fits or falls into one of the other cases
// that allow accumulation (not word wrapping, trailing space or last
// character in raw line and fits with slight tolerance adjustment.
if ((! bWordWrap)
|| (oNewWidth <= oInfo.moLineWidth)
|| (eBreak == TextCharProp.BREAK_SP)
|| ((nGlyph+1 >= getVisualCharCount())
&& (oNewWidth - oInfo.moLineWidth <= nBreakTolerance))) {
eProcess = GLYPH_ACCUMULATE; // (redundant)
}
// Something already accumulated in the line before the current word:
// this is the normal word break case--push the current word to the next
// line.
else if (! oInfo.moLine.isEmpty()) {
eProcess = GLYPH_BREAK_WORD;
}
// Line is empty, but word is not: emergency break in the middle of the
// word, pushing this glyph to the next line and keeping the preceding
// word text within the bounds.
else if (! oInfo.moWord.isEmpty()) {
eProcess = GLYPH_BREAK_NOW;
}
// First glyph in line doesn't fit: keep it on this line to avoid
// infinitely pushing it to the next line.
else {
eProcess = GLYPH_BREAK_NEXT;
}
}
// Actual glyph processing, based on above determination.
switch (eProcess) {
// Accumulate: Each new glyph gets appended to the current word.
case GLYPH_ACCUMULATE:
oInfo.moWord.accumulate (oGlyph, nGlyph, eBreak); // accumulate in current word
nGlyph++;
break;
// Already handled: Must have been dealt with by the tab control. Still
// need to accumulate glyph information.
case GLYPH_ALREADY_HANDLED:
oInfo.moWord.accumulate (oGlyph, nGlyph, eBreak); // for consistency
nGlyph++;
break;
// Immediate break: This glyph doesn't fit, and is part of the only
// "word" accumulated on the line. Must break in the middle of the word.
case GLYPH_BREAK_NOW:
nGlyph = oHyphenControl.hyphenate (eProcess,
eBreak,
pbBreakCandidates,
oGlyph,
nGlyph);
if (nGlyph < 0) {
bBreakLoop = true;
}
break;
// This is the first "real" glyph on the line and it doesn't fit (very
// narrow line): Accumulate it in the current word and force a break on
// the next iteration (before the next glyph).
case GLYPH_BREAK_NEXT:
oInfo.moWord.accumulate (oGlyph, nGlyph, eBreak); // accumulate for next iteration
bForceBreak = true; // force break before next glyph
nGlyph++; // on to next glyph
break;
// Normal word break: There is already at least one word accumulated in
// the line and this glyph causes the current (different word) not to
// fit. Accumulate the glyph in the pending word; force a line (without
// the pending word) and restart accumulation at the start of the pending
// word. Note that if the entire line doesn't fit in the current frame,
// NewLine() actually backs us up to the start of the line to try it in a
// new frame.
case GLYPH_BREAK_WORD:
nGlyph = oHyphenControl.hyphenate (eProcess,
eBreak,
pbBreakCandidates,
oGlyph,
nGlyph);
if (nGlyph < 0) {
bBreakLoop = true;
}
break;
// Tabbing off the end: The tab gets added to the current line and then
// we complete that line. The next character--even if a space--will
// start the next line. Note that Gretzky treated tabbing off the end as
// the last paragraph line for spread justification determination.
case GLYPH_BREAK_TAB:
oInfo.moWord.accumulate (oGlyph, nGlyph, eBreak); // accumulate tab
oInfo.moLine.commitWord (oInfo.moWord); // keep it on this line
int eNewLine = newLine (oInfo, oTabControl);
if (eNewLine == NEW_LINE_RETRY) {
nGlyph = oInfo.moLine.getStartGlyphIndex(); // restart this line
} else { // line was committed
oInfo.moFormat.getLastChanged().setLastParaLine (TAB_TO_END);
if (eNewLine == NEW_LINE_STOP) {
bBreakLoop = true;
} else {
oTabControl.startLine();
}
}
break;
}
// If this is the last glyph in the raw line and we haven't stopped
// prematurely (block full or incremental update), try to commit the
// final wrapped line. That may force a retry if it has to go into a new
// frame with different geometry.
if ((! bBreakLoop)
&& (nGlyph == getGlyphCount())) // nGlyph already incremented
{
oTabControl.commitTab();
oInfo.moLine.commitWord (oInfo.moWord);
switch (newLine (oInfo, oTabControl)) {
case NEW_LINE_RETRY:
nGlyph = oInfo.moLine.getStartGlyphIndex(); // restart this line
break;
case NEW_LINE_STOP:
bBreakLoop = true;
break;
}
}
}
if (bBreakLoop)
bContinue = false;
}
return bContinue;
}
private int newLine (boolean bFirstLine, int nStartIndex, int nLength, LineDesc oLineDesc, int eLineBreakStart, int eLineBreakEnd) {
// Adds another wrapped line and sets up for the next
// Line initializations
DispLineWrapped poLine = frame().allocateWrappedLine (oLineDesc);
poLine.setRTL (isRTL());
poLine.setHasBIDI (hasBIDI());
// Set first/last flags as appropriate
if (bFirstLine) {
if (isFirstParaLine()) {
poLine.setFirstParaLine (true);
}
if (isFirstLineInStream()) {
poLine.setFirstLineInStream (true);
}
}
if ((nStartIndex + nLength) >= getCharCount()) {
if (getLastParaLine() != INTERNAL_LINE) {
poLine.setLastParaLine (getLastParaLine());
}
}
// This builds the wrapped line
poLine.fill (this, nStartIndex, nLength);
// Note that committing a line may change the frame, so set it for this raw line.
int eCommit = moFormatInfo.commitLine (poLine);
setFrame (moFormatInfo.getFrame());
switch (eCommit) {
// Couldn't commit because the frame is full and there is no frame to
// take the overflow.
case FormatInfo.COMMIT_FULL:
moFormatInfo.setFits (false);
return NEW_LINE_STOP;
// Couldn't commit because the frame is full and the next frame has
// different width limits--retry in the context of the new frame.
case FormatInfo.COMMIT_RETRY:
return NEW_LINE_RETRY;
}
poLine.setStartBreak (eLineBreakStart);
poLine.setEndBreak (eLineBreakEnd);
// TBD: Some of these returns may have changed as a result of moving to
// NewLineEnum. May have been incorrect before.
if (! moFormatInfo.isUpdate()) {
return NEW_LINE_CONTINUE;
}
int nNextLineStart = nStartIndex + nLength;
if (nNextLineStart >= getCharCount()) {
return NEW_LINE_CONTINUE;
}
// Incremental update completion test.
DispPosn oNextMapPosn = getMappedPosition (nNextLineStart);
TextPosnBase oNextPosn = new TextPosnBase (oNextMapPosn.pp().stream(), charToStreamIndex (oNextMapPosn, nNextLineStart, 0));
if (moFormatInfo.canStopNow (oNextPosn)) {
return NEW_LINE_STOP;
}
return NEW_LINE_CONTINUE;
}
private int newLine (boolean bFirstLine, int nStartIndex, int nLength, LineDesc oLineDesc) {
return newLine (bFirstLine, nStartIndex, nLength, oLineDesc, LINE_BREAK_NORMAL, LINE_BREAK_NORMAL);
}
private void determineLineLimits (WrapInfo oInfo, boolean bFirst) {
oInfo.moMaxWidth = 0.0f;
oInfo.moLineWidth = 0.0f;
if (frame().isOrientationVertical()) {
if (! oInfo.moFormat.getFrame().unlimitedHeight()) {
oInfo.moMaxWidth = Units.toFloat (oInfo.moFormat.getFrame().maxHeight());
}
} else {
if (! oInfo.moFormat.getFrame().unlimitedWidth()) {
oInfo.moMaxWidth = Units.toFloat (oInfo.moFormat.getFrame().maxWidth());
}
}
if (bFirst && isFirstParaLine()) {
oInfo.moLineDesc.moSpecial = oInfo.moSpecialFirst;
} else {
oInfo.moLineDesc.moSpecial = oInfo.moSpecialRest;
}
if (oInfo.moMaxWidth > 0.0f) {
oInfo.moMaxWidth -= oInfo.moLineDesc.moMarginL;
oInfo.moMaxWidth -= oInfo.moLineDesc.moMarginR;
oInfo.moLineWidth = oInfo.moMaxWidth;
oInfo.moLineWidth -= oInfo.moLineDesc.moSpecial;
}
}
//----------------------------------------------------------------------
//
// Class MeasuredText - Helper class represents a
// sequence of glyphs whose start and end are known. Used in
// line breaking.
//
//----------------------------------------------------------------------
private static class MeasuredText {
private DispLineRaw mpoLine;
private boolean mbStarted;
private int mnStartGlyphIndex;
private int mnStartCharIndex;
private int mnEndCharIndex;
private float moStartOffset;
private float moCurrentWidth;
private int mnInternalSpaces;
private int mnTrailingSpaces;
private int mnTrailingGlue;
MeasuredText (DispLineRaw poLine) {
mpoLine = poLine;
}
boolean isEmpty () {
return ! mbStarted;
}
int getStartGlyphIndex () {
return mnStartGlyphIndex;
}
int getStartCharIndex () {
return mnStartCharIndex;
}
int getCharLength () {
return mnEndCharIndex - mnStartCharIndex;
}
float getStartOffset () {
return moStartOffset;
}
float getCurrentWidth () {
return mbStarted ? moCurrentWidth : 0.0f;
}
int getInternalSpaces () {
return mnInternalSpaces;
}
int getTrailingSpaces () {
return mnTrailingSpaces;
}
int getTrailingGlue () {
return mnTrailingGlue;
}
void accumulate (Glyph oGlyph, int nGlyphIndex, int eBreak) {
GlyphLoc oGlyphLoc = mpoLine.getGlyphLoc (nGlyphIndex);
if (! mbStarted) {
moStartOffset = oGlyph.getDrawX (mpoLine);
moCurrentWidth = 0.0f;
mnStartGlyphIndex = nGlyphIndex;
mnStartCharIndex = oGlyphLoc.getMapIndex();
mnInternalSpaces = 0;
mnTrailingSpaces = 0;
mnTrailingGlue = 0;
mbStarted = true;
}
mnEndCharIndex = oGlyphLoc.getMapIndex() + oGlyphLoc.getMapLength();
moCurrentWidth += oGlyph.getOriginalNextX() - oGlyph.getOriginalX();
switch (eBreak) {
case TextCharProp.BREAK_SP:
mnTrailingSpaces++;
break;
case TextCharProp.BREAK_GL:
mnTrailingGlue++;
break;
default:
mnInternalSpaces += mnTrailingSpaces + mnTrailingGlue;
mnTrailingSpaces = 0;
mnTrailingGlue = 0;
}
}
void commitWord (MeasuredText oWord) {
if (oWord.mbStarted) {
if (! mbStarted) {
copyFrom (oWord);
}
else {
mnEndCharIndex = oWord.mnEndCharIndex;
// If the word is all spaces, just accumulate more trailing spaces. This
// can happen with Unicode direction control characters at line end.
if ((oWord.mnTrailingSpaces + oWord.mnTrailingGlue) == oWord.getCharLength()) {
mnTrailingSpaces += oWord.mnTrailingSpaces;
mnTrailingGlue += oWord.mnTrailingGlue;
}
// Otherwise, the line's trailing spaces are now internal spaces and the
// new word's trailing spaces become the line's trailing spaces.
else {
mnInternalSpaces += mnTrailingSpaces + mnTrailingGlue + oWord.mnInternalSpaces;
mnTrailingSpaces = oWord.mnTrailingSpaces;
mnTrailingGlue = oWord.mnTrailingGlue;
}
moCurrentWidth += oWord.moCurrentWidth;
}
oWord.mbStarted = false;
}
}
void restart () {
mbStarted = false;
}
void reset (MeasuredText oWord) {
mbStarted = false;
mnStartGlyphIndex = oWord.mnStartGlyphIndex;
mnStartCharIndex = oWord.mnStartCharIndex;
moStartOffset = oWord.moStartOffset;
moCurrentWidth = oWord.moCurrentWidth;
mnInternalSpaces = 0;
mnTrailingSpaces = 0;
mnTrailingGlue = 0;
oWord.mbStarted = false;
}
void copyFrom (MeasuredText source) {
mpoLine = source.mpoLine;
mbStarted = source.mbStarted;
mnStartGlyphIndex = source.mnStartGlyphIndex;
mnStartCharIndex = source.mnStartCharIndex;
mnEndCharIndex = source.mnEndCharIndex;
moStartOffset = source.moStartOffset;
moCurrentWidth = source.moCurrentWidth;
mnInternalSpaces = source.mnInternalSpaces;
mnTrailingSpaces = source.mnTrailingSpaces;
mnTrailingGlue = source.mnTrailingGlue;
}
}
//----------------------------------------------------------------------
//
// WrapInfo - Used in line breaking
//
//----------------------------------------------------------------------
private static class WrapInfo {
FormatInfo moFormat;
MeasuredText moLine;
MeasuredText moWord;
LineDesc moLineDesc;
float moMaxWidth;
float moLineWidth;
float moSpecialFirst;
float moSpecialRest;
int mnNextTab;
boolean mbFirstLine;
int meLineBreak;
WrapInfo (FormatInfo oFormatInfo, DispLineRaw poLine, int nNextTab) {
moFormat = oFormatInfo;
moLine = new MeasuredText (poLine);
moWord = new MeasuredText (poLine);
moLineDesc = new LineDesc();
mnNextTab = nNextTab;
mbFirstLine = true;
meLineBreak = DispLine.LINE_BREAK_NORMAL;
}
}
//----------------------------------------------------------------------
//
// Class TabControl - Manages tabs when performing
// word wrapping
//
//----------------------------------------------------------------------
private static class TabControl {
private static final int RTL_DECIMAL_OFF = 0;
private static final int RTL_DECIMAL_WHOLE_PART = 1;
private static final int RTL_DECIMAL_FRACTION_PART = 2;
private DispLineRaw mpoLine;
private FormatInfo moFormatInfo;
private int mnTabIndex;
private boolean mbWordWrap;
private float moMaxWidth;
private DispTab mpoCurrentDispTab;
private TextTab moCurrentTabStop;
private float moAccumulatedLineWidth;
private float moCurrentTabOffset;
private float moCurrentTabTextWidth;
private float moTrailingSpaceWidth;
private int meRTLDecimalState;
private float moRTLDigitWidth;
TabControl (DispLineRaw poLine, FormatInfo oFormatInfo, int nTabStart, boolean bWordWrap, float oMaxWidth) {
mpoLine = poLine;
moFormatInfo = oFormatInfo;
mnTabIndex = nTabStart;
mbWordWrap = bWordWrap;
moMaxWidth = oMaxWidth;
}
int getTabIndex () {
return mnTabIndex;
}
float getLineWidth () {
float oResult = moAccumulatedLineWidth;
if ((mpoCurrentDispTab != null) && (moCurrentTabStop.tabType() == TextTab.TYPE_CENTRE)) {
oResult += moCurrentTabTextWidth / 2;
}
return oResult;
}
void setMaxWidth (float oMaxWidth) {
moMaxWidth = oMaxWidth;
}
int processGlyph (Glyph oGlyph, int nCharIndex, int c) {
int eReturn = GLYPH_ACCUMULATE;
// New glyph represents a non-tab character...
if (c != '\t') {
float oGlyphWidth = oGlyph.getOriginalNextX() - oGlyph.getOriginalX();
// If there is no tab in progress, simply accumulate the character's
// width.
if (mpoCurrentDispTab == null) {
moAccumulatedLineWidth += oGlyphWidth;
}
// Otherwise there is a tab in progress. Note that left tabs are never
// "in progress"--when we encounter one we deal with it then and there.
// The layout of other tab types depends on the text that follows; hence
// the concept of a tab in progress.
else {
switch (moCurrentTabStop.tabType()) {
// Centre: Can accumulate characters until the width of characters after
// the tab exceeds half the space before the tab.
case TextTab.TYPE_CENTRE:
if (mpoLine.getBreakClass (nCharIndex) == TextCharProp.BREAK_SP) {
moTrailingSpaceWidth += oGlyphWidth;
} else {
commitTrailingSpace();
moCurrentTabTextWidth += oGlyphWidth;
if ((moCurrentTabTextWidth / 2) > moCurrentTabOffset)
cancelTab();
}
break;
// Right: Can accumulate characters until the width of characters after
// the tab exceeds the space before the tab.
case TextTab.TYPE_ALIGN_BEFORE:
if (mpoLine.getBreakClass (nCharIndex) == TextCharProp.BREAK_SP) {
moTrailingSpaceWidth += oGlyphWidth;
} else {
commitTrailingSpace();
moCurrentTabTextWidth += oGlyphWidth;
if (moCurrentTabTextWidth > moCurrentTabOffset)
cancelTab();
}
break;
// Decimal: Complicated. In LTR text, we treat it as a right tab until
// we see the decimal point, when we're done. In RTL text, we will try
// to fit the text that comes after the decimal point (in logical order)
// before the decimal point (recall that text is filled from the right by
// default in RTL text).
case TextTab.TYPE_DECIMAL:
TextAttr poAttr = mpoLine.getMappedAttr (nCharIndex);
boolean bAccumulateGlyph = false;
// Decimal character: If accumulating the RTL whole part (before the
// decimal), switch mode to accumulating fractional part. Otherwise,
// we're done with the tab.
if ((poAttr != null) && (c == poAttr.radixChar())) {
if (meRTLDecimalState == RTL_DECIMAL_WHOLE_PART) {
bAccumulateGlyph = true;
meRTLDecimalState = RTL_DECIMAL_FRACTION_PART;
} else { // not RTL or have seen decimal
commitTrailingSpace();
commitTab();
moAccumulatedLineWidth += oGlyphWidth;
}
}
// Any other character (except tab): processing depends on RTL decimal
// state. For RTL text, the intent is to treat all digits immediately
// before the decimal (in logical order) as coming "after" the tab (in
// RTL order). If we see a mixture of digits and other characters, only
// the last contiguous set is going to come after the tab. Any digits
// that come after the decimal (in logical order) will apper before the
// tab (in RTL order). Once we hit a non-digit, were done. For LTR
// text, we simply accumulate glyphs until we hit the decimal point.
else {
switch (meRTLDecimalState) {
case RTL_DECIMAL_WHOLE_PART:
if (mpoLine.getBreakClass (nCharIndex) == TextCharProp.BREAK_NU) {
moRTLDigitWidth += oGlyphWidth;
} else {
moCurrentTabTextWidth += moRTLDigitWidth;
moRTLDigitWidth = 0.0f;
bAccumulateGlyph = true;
}
break;
case RTL_DECIMAL_FRACTION_PART:
if (mpoLine.getBreakClass (nCharIndex) == TextCharProp.BREAK_NU) {
bAccumulateGlyph = true;
} else {
commitTab();
}
break;
default: // case RTL_DECIMAL_OFF:
bAccumulateGlyph = true;
break;
}
}
if (bAccumulateGlyph) {
if (mpoLine.getBreakClass (nCharIndex) == TextCharProp.BREAK_SP) {
moTrailingSpaceWidth += oGlyphWidth;
} else {
commitTrailingSpace();
moCurrentTabTextWidth += oGlyphWidth;
if (moCurrentTabTextWidth > moCurrentTabOffset) {
cancelTab();
}
}
}
break;
}
}
}
// New glyph represents a tab character...
else {
// Commit any previous tab.
commitTab();
// We need to use the text attributes to find the tab based on the
// current accumulated width.
assert (mnTabIndex < moFormatInfo.getTabSize());
DispTab poDispTab = moFormatInfo.getTab (mnTabIndex);
mnTabIndex++;
TextTab oTabStop = new TextTab();
TextAttr poAttr = mpoLine.getMappedAttr (nCharIndex);
boolean bFillTab = false;
if (poAttr != null) {
// Note: for consistency with old implementation, don't check
// poAttr->TabsEnable(); just use the default tab set returned.
oTabStop = poAttr.tabs().next (Units.toUnitSpan (moAccumulatedLineWidth));
if (oTabStop.tabStop().value() > 0) {
UnitSpan oSpaceAfter = Units.toUnitSpan(moMaxWidth).subtract (oTabStop.tabStop());
if ((! mbWordWrap)
|| (! mpoLine.legacyPositioning())
|| (oSpaceAfter.value() > 0)) {
mpoCurrentDispTab = poDispTab;
if (mbWordWrap && (oSpaceAfter.value() < 0)) {
poDispTab.setFillWidth (moMaxWidth - moAccumulatedLineWidth);
}
}
}
if (poAttr.leaderContentEnable() && poAttr.leaderPatternEnable()) {
int eLeader = poAttr.leaderPattern();
if ((eLeader == TextAttr.LEADER_PATTERN_DOTS)
|| (eLeader == TextAttr.LEADER_PATTERN_USE_CONTENT)) {
bFillTab = true;
}
}
}
// If RTL text, need to swap the notions of left and right for tab
// accumulation. Subsequent code is direction-agnostic for left and
// right tabs. In other words, left tabs align text toward from the
// final edge and right tabs align text back toward the initial edge.
oTabStop.tabType (TextTab.resolveType (oTabStop.tabType(), mpoLine.isRTL(), true));
meRTLDecimalState = RTL_DECIMAL_OFF;
// Could not find a tab stop: Treat as tab to next line. Note that this
// is a forced new line in compatibility mode, but will allow trailing
// spaces (and tabs) to accumulate on the end of the current line if not.
if (mpoCurrentDispTab == null) {
if (mpoLine.legacyPositioning()) {
eReturn = GLYPH_BREAK_TAB;
bFillTab = false;
} else if (mbWordWrap) {
poDispTab.setWidth (moMaxWidth - moAccumulatedLineWidth);
moAccumulatedLineWidth = moMaxWidth;
} else {
eReturn = GLYPH_BREAK_NEXT;
bFillTab = false;
}
mpoLine.setChar (nCharIndex, ' ', TextCharProp.defaultSpace); // treat as trailing space
}
// Left tab: Can handle in place by making the tab character occupy the
// space from the current width to the tab stop.
else if (oTabStop.tabType() == TextTab.TYPE_ALIGN_AFTER) {
float width = Units.toFloat (oTabStop.tabStop());
float oTabWidth = width;
oTabWidth -= moAccumulatedLineWidth;
poDispTab.setWidth (oTabWidth);
moAccumulatedLineWidth = width;
mpoCurrentDispTab = null;
mpoLine.setChar (nCharIndex, Pkg.EMBED_OBJ_CHAR, TextCharProp.defaultSpace);
eReturn = GLYPH_ALREADY_HANDLED;
}
// All other tab types: Tab space is dependent on the characters that
// follow, so we can only initialize for now.
else { // centre, right, decimal
moCurrentTabStop = oTabStop;
float oTabOffset = Units.toFloat (oTabStop.tabStop());
moCurrentTabOffset = oTabOffset - moAccumulatedLineWidth;
moAccumulatedLineWidth = oTabOffset;
mpoLine.setChar (nCharIndex, Pkg.EMBED_OBJ_CHAR, TextCharProp.defaultSpace);
if (mpoLine.isRTL() && (oTabStop.tabType() == TextTab.TYPE_DECIMAL)) {
meRTLDecimalState = RTL_DECIMAL_WHOLE_PART;
moRTLDigitWidth = 0.0f;
}
eReturn = GLYPH_ALREADY_HANDLED;
}
if (bFillTab) {
TextDisplay poDisplay = mpoLine.display();
assert (poDisplay != null);
poDispTab.fill (poDisplay.getContext(), poAttr, poDisplay.getGFXEnv());
}
}
return eReturn;
}
void startLine () {
moAccumulatedLineWidth = 0.0f;
moCurrentTabOffset = 0.0f;
moCurrentTabTextWidth = 0.0f;
moTrailingSpaceWidth = 0.0f;
}
void restartLine (WrapInfo oInfo) {
startLine();
mnTabIndex = oInfo.mnNextTab;
}
void backUp (float oNewWidth) {
moAccumulatedLineWidth = oNewWidth;
}
void cancelTab () {
moAccumulatedLineWidth -= moCurrentTabOffset; // back to start of tab
moAccumulatedLineWidth += moCurrentTabTextWidth; // include any text after the tab
moCurrentTabOffset = 0.0f;
moCurrentTabTextWidth = 0.0f;
moTrailingSpaceWidth = 0.0f;
mpoCurrentDispTab = null;
}
void commitTab () {
if (mpoCurrentDispTab != null) {
float oTabWidth = 0.0f;
switch (moCurrentTabStop.tabType()) {
case TextTab.TYPE_CENTRE:
moCurrentTabTextWidth /= 2;
oTabWidth = moCurrentTabOffset - moCurrentTabTextWidth;
moAccumulatedLineWidth += moCurrentTabTextWidth + moTrailingSpaceWidth;
break;
case TextTab.TYPE_ALIGN_BEFORE:
case TextTab.TYPE_DECIMAL:
oTabWidth = moCurrentTabOffset - moCurrentTabTextWidth;
moAccumulatedLineWidth += moRTLDigitWidth;
if (meRTLDecimalState != RTL_DECIMAL_FRACTION_PART) {
moAccumulatedLineWidth += moTrailingSpaceWidth;
}
break;
default:
assert (false);
}
mpoCurrentDispTab.setWidth (oTabWidth);
}
moCurrentTabOffset = 0.0f;
moCurrentTabTextWidth = 0.0f;
moTrailingSpaceWidth = 0.0f;
mpoCurrentDispTab = null;
}
void commitTrailingSpace () {
moCurrentTabTextWidth += moTrailingSpaceWidth;
moTrailingSpaceWidth = 0.0f;
}
}
//----------------------------------------------------------------------
//
// Class HyphenControl - Handles optional
// hyphenation when a word doesn't fit during wrapping.
//
//----------------------------------------------------------------------
private static class HyphenControl {
@FindBugsSuppress(code="UwF") // this field is never set - this implementation looks incomplete
private HyphenData mpoData;
private DispLineRaw mpoLine;
private WrapInfo moWrapInfo;
private TabControl moTabControl;
HyphenControl (DispLineRaw poLine, WrapInfo oWrapInfo, TabControl oTabControl, TextAttr poAttr) {
mpoLine = poLine;
moWrapInfo = oWrapInfo;
moTabControl = oTabControl;
mpoData = null;
if ((poAttr != null)
&& (poAttr.hyphLevelEnable())
&& (poAttr.hyphLevel() != TextAttr.HYPHEN_OFF)) {
// mpoData = new HyphenData (poAttr); // TODO: implement hyphenation
}
}
int testHyphenation (int eProcess, int nGlyphIndex) {
int eReturn = eProcess;
if ((mpoData != null) && (mpoData.meState != HyphenData.STATE_OFF)) {
int nCharIndex = mpoLine.getGlyphLocCharIndex (nGlyphIndex);
if (mpoData.meState == HyphenData.STATE_PREFIX) {
if (nCharIndex == mpoData.mnSuffixStart) {
eReturn = GLYPH_BREAK_NOW;
}
} else {
if (nCharIndex >= (mpoData.mnSuffixStart + mpoData.mnSuffixLength)) {
endHyphenation();
}
}
}
return eReturn;
}
int hyphenate (int eProcess, int eBreak, boolean[] pbBreakCandidates, Glyph oGlyph, int nGlyph) {
// Called during word-wrapping on every line break, even if hyphenation
// is disabled. Responsible for determining whether hyphenation can be
// applied and whether there are any candidates that satisfy the current
// line end conditions.
// If hyphenation is allowed and either the current word has content or
// we are processing a hyphenation prefix, do hyphenation testing. This
// may or may not initiate a hyphenation.
IntegerHolder eLineBreak = new IntegerHolder(DispLine.LINE_BREAK_NORMAL);
int nReturnGlyph = -1;
if ((mpoData != null)
&& ((! moWrapInfo.moWord.isEmpty()) || (mpoData.meState == HyphenData.STATE_PREFIX))) {
nReturnGlyph = doHyphenation (eProcess, eBreak, pbBreakCandidates, oGlyph, nGlyph, eLineBreak);
}
// If hyphenation wasn't attempted or wasn't successful, perform normal
// line end processing.
if (nReturnGlyph < 0) {
nReturnGlyph = nGlyph;
if (eProcess == GLYPH_BREAK_NOW) {
moWrapInfo.moLine.commitWord (moWrapInfo.moWord); // new line w/o this glyph
if (eLineBreak.value == DispLine.LINE_BREAK_NORMAL) {
eLineBreak.value = DispLine.LINE_BREAK_FORCED;
}
}
moWrapInfo.moWord.accumulate (oGlyph, nGlyph, eBreak); // for next line
switch (mpoLine.newLine (moWrapInfo, moTabControl, eLineBreak.value)) {
case DispLineRaw.NEW_LINE_RETRY:
nReturnGlyph = moWrapInfo.moLine.getStartGlyphIndex(); // restart this line
break;
case DispLineRaw.NEW_LINE_STOP:
nReturnGlyph = -1;
break;
default:
if (eProcess == GLYPH_BREAK_WORD) {
nReturnGlyph = moWrapInfo.moLine.getStartGlyphIndex(); // start of offending word
}
moTabControl.startLine();
break;
}
}
return nReturnGlyph;
}
private int doHyphenation (int eProcess, int eBreak, boolean[] pbBreakCandidates, Glyph oGlyph, int nGlyph, IntegerHolder eLineBreak) {
// Initialize or continue with hyphenation. Called when a word doesn't
// fit, the prefix has just been processed, or the suffix doesn't fit.
int nCharIndex = mpoLine.getGlyphLocCharIndex (nGlyph);
HyphExtents oHyphExtents = new HyphExtents();
String sPrefixPrefix = "";
switch (mpoData.meState) {
// No hyphenation in progress, but word doesn't fit: Determine the start
// and length of the word in line characters. Then, generate a hyphen
// word object (in a member variable). This contains all possible
// candidates.
// No hyphenation in progress, but word doesn't fit: Determine the start
// and length of the word in line characters. Then, generate a hyphen
// word object (in a member variable). This contains all possible
// candidates.
case HyphenData.STATE_OFF:
if (! determineWordExtents (pbBreakCandidates, nCharIndex, oHyphExtents, eLineBreak)) {
return -1;
}
mpoData.moHyphExtents.copyFrom (oHyphExtents);
// sPrefixPrefix = String (mpoLine.getCharArray() + oHyphExtents.mnHyphStart, oHyphExtents.mnWordStart - oHyphExtents.mnHyphStart);
if (! getHyphenation (oHyphExtents.mnWordStart, oHyphExtents.mnWordLength)) {
return -1;
}
break;
// End of prefix: Position the hyphenation object at its (first) child.
// This would be the complete suffix. Return for normal line-end
// processing.
case HyphenData.STATE_PREFIX:
// mpoData.mpoHyphenation.toChild();
mpoData.meState = HyphenData.STATE_SUFFIX;
eLineBreak.value = DispLine.LINE_BREAK_HYPHEN;
return -1;
// Suffix doesn't fit: Set up to replace the suffix with any possible
// sub-hyphenation.
case HyphenData.STATE_SUFFIX:
oHyphExtents.mnHyphStart = mpoData.mnSuffixStart;
oHyphExtents.mnWordStart = mpoData.mnSuffixStart;
oHyphExtents.mnWordLength = mpoData.mnSuffixLength;
break;
default:
assert (false);
}
// AXTELangHyphenation poHyphenation = mpoData.mpoHyphenation; // for simpler refs
// Initializations for candidate testing. Each candidate has two parts:
// prefix (part before the hyphen break) and suffix (part after). For a
// word with multiple hyphenation points, we may be forced to split the
// former suffix in the same way. In order to be a valid candidate, the
// prefix and any hyphen character(s) must fit on the current line.
// Prefix and suffix will be created as separate raw line instances for
// the purpose of measurement.
//float oWidthAvailable = moWrapInfo.moMaxWidth - moWrapInfo.moLine.getCurrentWidth();
TextAttr poAttr = mpoLine.getMappedAttr (nCharIndex);
//DispMapSet oPrefixMapSet = new DispMapSet();
DispLineRaw oPrefixLine = new DispLineRaw (moWrapInfo.moFormat, false);
String sPrefix = "";
String sPrefixHyphen = "";
int nPrefixMapLength = 0;
String sSuffix = "";
String sSuffixHyphen = "";
int nSuffixMapLength = 0;
getHyphenChars (sPrefixHyphen, sSuffixHyphen);
// Iterate through the candidates. These are returned ordered by level
// (preferred first) and character length (longer first). The first
// candidate that fits will be used. For each candidate, fill the prefix
// line with the prefix text and hyphenation character(s) and then see if
// it fits. It is sufficient to find a prefix that fits. If the suffix
// doesn't fit, that will be detected later and cause a sub-hyphenation.
// boolean bEmergency = false;
// if (eProcess == GLYPH_BREAK_NOW) {
// bEmergency = true;
// }
//boolean bCandidate = false;
// boolean bCandidate = (poHyphenation.getDepth() == 0) ? poHyphenation.toChild (bEmergency) : poHyphenation.toSibling (bEmergency);
boolean bFound = false;
// while (bCandidate) {
// sPrefix = poHyphenation.getText();
// if (poHyphenation.toChild()) {
// sSuffix = poHyphenation.getText();
// nSuffixMapLength = sSuffix.uniLength();
// poHyphenation.toParent();
//
// if (nSuffixMapLength < oHyphExtents.mnWordLength) {
// nPrefixMapLength = oHyphExtents.mnWordLength - nSuffixMapLength;
// oPrefixLine.fill (mpoLine, oHyphExtents.mnHyphStart, nPrefixMapLength + sPrefixPrefix.uniLength(), poAttr, sPrefixPrefix + sPrefix + sPrefixHyphen, true);
// if (oPrefixLine.getWidth() <= oWidthAvailable) {
// bFound = true;
// break;
// }
// }
// }
//
// bCandidate = poHyphenation.toSibling (bEmergency);
// }
if (! bFound) {
endHyphenation();
return -1;
}
// If the prefix was embellished with leading punctuation for width
// testing, reformat the prefix without that punctuation for insertion
// into the raw line.
if (sPrefixPrefix.length() > 0) {
oPrefixLine.fill (mpoLine, oHyphExtents.mnWordStart, nPrefixMapLength, poAttr, sPrefix + sPrefixHyphen, true);
}
// Generate the suffix line only once a prefix has been found.
//DispMapSet oSuffixMapSet = new DispMapSet();
DispLineRaw oSuffixLine = new DispLineRaw (moWrapInfo.moFormat, false);
oSuffixLine.fill (mpoLine, oHyphExtents.mnWordStart + nPrefixMapLength, nSuffixMapLength, poAttr, sSuffixHyphen + sSuffix, false);
// Adjust the caller's break candidate array to include the hyphenated
// word with an eligible break after the prefix and hyphen character(s).
int nNewWordLength = oPrefixLine.getCharCount() + oSuffixLine.getCharCount();
resizeBreakCandidates (oHyphExtents.mnWordStart, oHyphExtents.mnWordLength, nNewWordLength, pbBreakCandidates);
pbBreakCandidates[oHyphExtents.mnWordStart + oPrefixLine.getCharCount()] = true;
mpoData.mnSuffixStart = oHyphExtents.mnWordStart + oPrefixLine.getCharCount();
mpoData.mnSuffixLength = oSuffixLine.getCharCount();
mpoData.meState = HyphenData.STATE_PREFIX;
// Edit the caller's raw line, removing the old word and inserting the
// hyphenation (prefix, suffix and any hyphenation characters).
mpoLine.removeContent (oHyphExtents.mnWordStart, oHyphExtents.mnWordLength);
mpoLine.insertContent (oPrefixLine, oHyphExtents.mnWordStart);
mpoLine.insertContent (oSuffixLine, mpoData.mnSuffixStart);
// Back up to the start of the hyphenated word to resume the word-wrap
// algorithm.
nGlyph = moWrapInfo.moWord.getStartGlyphIndex();
moTabControl.backUp (moWrapInfo.moLine.getCurrentWidth());
moWrapInfo.moWord.restart();
return nGlyph;
}
private boolean determineWordExtents (boolean[] pbBreakCandidates, int nCharIndex, HyphExtents oHyphExtents, IntegerHolder eLineBreak) {
eLineBreak.value = DispLine.LINE_BREAK_NORMAL;
if (moWrapInfo.moWord.isEmpty()) {
return false;
}
int nIndex;
// Scan forward from the start of the "augmented word" (including
// abutting punctuation) to find the next line break candidate. This
// delimits the word plus any leading and trailing punctuation. If this
// scan runs off the end of the raw line, it is the last word in the
// paragraph. In such a case, it can be hyphenated only if the word is
// too large to fit on a line by itself.
oHyphExtents.mnHyphStart = moWrapInfo.moWord.getStartCharIndex();
int nEnd = oHyphExtents.mnHyphStart + 1;
for (; nEnd < mpoLine.getCharCount(); nEnd++) {
if (pbBreakCandidates[nEnd]) {
break;
}
}
if (nEnd >= mpoLine.getCharCount()) { // ran off end
int nGlyphIndex = moWrapInfo.moWord.getStartGlyphIndex();
Glyph oGlyph = mpoLine.getGlyph (nGlyphIndex);
float oWordSize = mpoLine.getWidth() - mpoLine.getTrailingWidth() - oGlyph.getOriginalX();
if (oWordSize < moWrapInfo.moMaxWidth) { // can fit by itself
eLineBreak.value = DispLine.LINE_BREAK_HYPHEN_SUPPRESS;
return false;
}
}
// Use a word break iterator to scan forward for the real start of the
// word (minus any punctuation). The iterator will indicate breaks
// between words and spaces, words and punctuation, and so on. If the
// character immediately after the break is a letter, it is the start of
// our word.
TextCharPropDataIterator oDataIterator = new TextCharPropDataIterator (mpoLine.getBreakArray(), oHyphExtents.mnHyphStart);
TextBreakIterator poBreakIterator = TextBreakIterator.createWordInstance (oDataIterator);
boolean bFound = false;
for (nIndex = poBreakIterator.first(); nIndex != TextBreakIterator.DONE; nIndex = poBreakIterator.next()) {
int nTestIndex = oHyphExtents.mnHyphStart + nIndex;
if (mpoLine.getWordClass (nTestIndex) == TextCharProp.WORD_ALetter) {
bFound = true;
oHyphExtents.mnWordStart = nTestIndex;
break;
}
if ((nIndex > 0) && pbBreakCandidates[nTestIndex]) {
break;
}
}
if (! bFound) {
return false;
}
// Simply find the next break once the word has been detected. That
// delimits its end. If we run off the end, there is no word to
// hyphenate.
nIndex = poBreakIterator.next();
if (nIndex == TextBreakIterator.DONE) {
return false;
}
oHyphExtents.mnWordLength = oHyphExtents.mnHyphStart + nIndex - oHyphExtents.mnWordStart;
if (oHyphExtents.mnWordLength <= 1) {
return false;
}
// Don't allow stream changes in the middle of a word. This could mess
// up subsequent mapping code. Fortunately, it's an extremely unlikely
// scenario.
int nLimit = oHyphExtents.mnWordStart + oHyphExtents.mnWordLength;
TextStream poStream = null;
for (nIndex = oHyphExtents.mnWordStart; nIndex < nLimit; nIndex++) {
DispPosn oPosition = mpoLine.getMappedPosition (nIndex);
TextStream poTestStream = oPosition.pp().stream();
if (poTestStream != poStream) {
if (poStream != null) {
return false;
}
poStream = poTestStream;
}
}
return true;
}
private boolean getHyphenation (int nWordStart, int nWordLength) {
/*
String sWord (mpoLine.getCharArray() + nWordStart, nWordLength);
if (mpoData.mpoAXTELang == null) {
mpoData.mpoAXTELang = mpoLine.display().getContext().forceAXTELang();
}
if (mpoData.mpoLILOManager == null) {
mpoData.mpoLILOManager = mpoData.mpoAXTELang.createLILOManager();
if (mpoData.mpoLILOManager == null) {
return null;
}
}
if (mpoData.mpoProvider == null) {
mpoData.mpoProvider = mpoData.mpoLILOManager.getDefaultProvider (mpoData.msLocale, SERVICE_HYPHENATION);
if (mpoData.mpoProvider == null) {
return null;
}
}
if (mpoData.mpoService == null) {
mpoData.mpoService = mpoData.mpoLILOManager.createService (mpoData.mpoProvider, mpoData.msLocale);
if (mpoData.mpoService == null) {
return null;
}
}
mpoData.mpoHyphenation = mpoData.mpoService.hyphenate (sWord, mpoData.moHyphenParms);
return mpoData.mpoHyphenation != null;
*/
return false;
}
// TBD: proper implementation
private void getHyphenChars (String sPrefixChars, String sSuffixChars) {
/*
sPrefixChars = Literal ("-");
sSuffixChars.empty();
*/
}
private void resizeBreakCandidates (int nWordStart, int nOldWordLength, int nNewWordLength, boolean[] pbBreakCandidates) {
int nOldCount = mpoLine.getCharCount();
int nNewCount = nOldCount + nNewWordLength - nOldWordLength;
if (nNewWordLength != nOldWordLength) {
int nOldWordEnd = nWordStart + nOldWordLength;
if (nNewWordLength < nOldWordLength) {
int nShift = nOldWordLength - nNewWordLength;
int nSrc = nOldWordEnd;
while (nSrc < nOldCount) {
pbBreakCandidates[nSrc - nShift] = pbBreakCandidates[nSrc];
nSrc++;
}
} else {
pbBreakCandidates = mpoLine.display().getBreakCandidates (nNewCount, nOldCount);
int nSrc = nOldCount;
int nDst = nNewCount;
while (nSrc > nOldWordEnd) {
pbBreakCandidates[--nDst] = pbBreakCandidates[--nSrc];
}
}
}
for (int i = 0; i < nNewWordLength; i++) {
pbBreakCandidates[nWordStart + i] = false;
}
}
private void endHyphenation () {
// mpoData.mpoHyphenation.release();
mpoData.meState = HyphenData.STATE_OFF;
}
private static class HyphExtents {
int mnHyphStart;
int mnWordStart;
int mnWordLength;
void copyFrom (HyphExtents source) {
mnHyphStart = source.mnHyphStart;
mnWordStart = source.mnWordStart;
mnWordLength = source.mnWordLength;
}
}
private static class HyphenData {
static final int STATE_OFF = 0;
static final int STATE_PREFIX = 1;
static final int STATE_SUFFIX = 2;
// AXTELangHyphenParms moHyphenParms;
@FindBugsSuppress(code="UrF") // field is never read - incomplete implementation?
String msLocale;
// AXTELang mpoAXTELang;
// AXTELangLILOManager mpoLILOManager;
// AXTELangLILOProvider mpoProvider;
// AXTELangLILOService mpoService;
// AXTELangHyphenation mpoHyphenation;
int meState;
HyphExtents moHyphExtents = new HyphExtents();
int mnSuffixStart;
int mnSuffixLength;
HyphenData (TextAttr poAttr) {
meState = STATE_OFF;
// moHyphenParms.meLevel = poAttr.hyphLevel();
// if (poAttr.hyphMinWordEnable()) {
// moHyphenParms.mnMinWord = poAttr.hyphMinWord();
// }
// if (poAttr.hyphMinPrefixEnable()) {
// moHyphenParms.mnMinPrefix = poAttr.hyphMinPrefix();
// }
// if (poAttr.hyphMinSuffixEnable()) {
// moHyphenParms.mnMinSuffix = poAttr.hyphMinSuffix();
// }
// if (poAttr.hyphSuppressNamesEnable()) {
// moHyphenParms.mbSuppressNames = poAttr.hyphSuppressNames();
// }
// if (poAttr.hyphSuppressAcronymsEnable()) {
// moHyphenParms.mbSuppressAcronyms = poAttr.hyphSuppressAcronyms();
// }
msLocale = poAttr.actualLocale();
}
}
}
//----------------------------------------------------------------------
//
// Class CharMapper - Used in hyphenation, this
// class generates the character map for a line representing
// a portion of the replacement word.
//
//----------------------------------------------------------------------
private static class CharMapper {
private final DispLineRaw mpoDest;
private final int mnDestLength;
private final DispLineRaw mpoSource;
private final int mnSourceStart;
private final int mnSourceLength;
private final boolean mbIsPrefix;
private boolean mbExactMappings;
private int mnCopyLength;
private int mnDestIndex;
private int mnDestExtra;
private int mnSourceIndex;
private int mnSourceExtra;
private TextStream mpoStream;
private int mnStreamIndex;
private int mnStreamLength;
private int mnMapIndex;
private int mnMapLength;
CharMapper (DispLineRaw poDest, DispLineRaw poSource, int nSourceStart, int nSourceLength, boolean bIsPrefix) {
mpoDest = poDest;
mnDestLength = poDest.getCharCount();
mpoSource = poSource;
mnSourceStart = nSourceStart;
mnSourceLength = nSourceLength;
mbIsPrefix = bIsPrefix;
assert (mnDestLength > 0);
assert (mnSourceLength > 0);
// The mapping process is driven by the lines' characters. These
// obviously may not correspond 1:1 to glyphs. Moreover, if AXTE
// ligatures are involved, they may not correspond 1:1 to text stream
// characters.
// The general algorithm is to map source (pre-hyphenation) characters
// 1:1 as much as possible to destination (prefix or suffix) characters.
// Variable mnCopyLength specifies the number of 1:1 mappings possible.
// Any mismatch is handled on a single character, either in the source
// (mnSourceExtra=1 if there are more destination characters) or in the
// destination (mnDestExtra=1 if there are more source characters). That
// special handling happens at the end of the destination line if it is a
// prefix and at its start if it is a suffix.
if (mnDestLength < mnSourceLength) {
mnCopyLength = mnDestLength - 1;
mnDestExtra = 1;
mnSourceExtra = mnSourceLength - mnDestLength + 1;
} else if (mnDestLength > mnSourceLength) {
mnCopyLength = mnSourceLength - 1;
mnDestExtra = mnDestLength - mnSourceLength + 1;
mnSourceExtra = 1;
} else {
mnCopyLength = mnDestLength;
mnDestExtra = 0;
mnSourceExtra = 0;
}
}
void map () {
int nCopy = initialize();
for (int i = 0; i < nCopy; i++) {
copyMappings (1, 1);
}
finish();
}
private int initialize () {
mnDestIndex = 0;
mnSourceIndex = mnSourceStart;
DispPosn oPosition = mpoSource.getMappedPosition (mnSourceIndex);
mpoStream = oPosition.pp().stream();
mnStreamIndex = DispLine.charToStreamIndex (oPosition, mnSourceIndex, 0);
mnStreamLength = 0;
mnMapIndex = 0;
mnMapLength = 0;
mpoDest.clearPositionMap();
if (! mbIsPrefix) {
if (! mbExactMappings) {
copyMappings (mnDestExtra, mnSourceExtra); // start suffix with mismatch
}
}
return mnCopyLength;
}
private void finish () {
if (mbIsPrefix) {
if (! mbExactMappings) {
copyMappings (mnDestExtra, mnSourceExtra); // end prefix with mismatch
}
}
flushMappings();
}
private void copyMappings (int nDestLength, int nSourceLength) {
// This method generates the actual text stream character mappings for
// the destination line. Even if the source to destination mapping is
// 1:1 for this call, the source character may map to several text stream
// characters.
int i;
// Get the stream index corresponding to the current source line
// character index.
DispPosn oPosition = mpoSource.getMappedPosition (mnSourceIndex);
int nStreamIndex = DispLine.charToStreamIndex (oPosition, mnSourceIndex, 0);
// If this doesn't represent a contiguous stream:line 1:1 character
// mapping, flush any accumulated mapping information to the destination
// line.
if ((mpoStream != oPosition.pp().stream())
|| (nStreamIndex != (mnStreamIndex + mnStreamLength))) {
flushMappings();
mpoStream = oPosition.pp().stream();
mnStreamIndex = nStreamIndex;
}
// This line iterates once for each source character in this call. It
// counts the number of stream characters corresponding to the source
// line character(s).
int nStreamCount = 0;
for (i = 0; i < nSourceLength; i++) {
DispPosn poPosition;
poPosition = (i == 0) ? oPosition : mpoSource.getMappedPosition (mnSourceIndex + i);
switch (DispLine.getPositionType (poPosition)) {
case DispLine.POSN_TYPE_RUN:
nStreamCount++;
break;
case DispLine.POSN_TYPE_LIGATURE:
nStreamCount += poPosition.getStreamCount();
break;
default:
assert (false);
nStreamCount++;
break;
}
}
// More stream characters than destination line characters: Map all but
// the last destination line character 1:1 to stream characters. Map the
// last destination character to the remaining stream characters
// (ligature).
if (nDestLength < nStreamCount) {
assert (nDestLength > 0);
int n1To1 = nDestLength - 1;
mnStreamLength += n1To1;
mnMapLength += n1To1;
flushMappings();
mnStreamLength = nStreamCount - n1To1;
mnMapLength = 1;
flushMappings();
}
// More destination line characters than stream line characters: Map all
// but the last stream character 1:1 to destination characters. Map the
// last stream character to the remaining destination characters
// (multiple).
else if (nDestLength > nStreamCount) {
assert (nSourceLength > 0);
int n1To1 = nSourceLength - 1;
mnStreamLength += n1To1;
mnMapLength += n1To1;
flushMappings();
mnStreamLength = 1;
mnMapLength = nDestLength - n1To1;
flushMappings();
}
// Equal number of characters: treat all as mapping 1:1.
else {
mnStreamLength += nDestLength;
mnMapLength += nDestLength;
}
mnDestIndex += nDestLength;
mnSourceIndex += nSourceLength;
}
private void flushMappings () {
if (mnMapLength == 0) {
return;
}
DispPosn oDispPosn = new DispPosn (mpoStream, mnStreamIndex);
if (mnStreamLength != mnMapLength) {
oDispPosn.setStreamCount (mnStreamLength);
}
mpoDest.add (oDispPosn, mnMapIndex, mnMapLength);
mnStreamIndex += mnStreamLength;
mnStreamLength = 0;
mnMapIndex += mnMapLength;
mnMapLength = 0;
}
}
}