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

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

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;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy