/*
* Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
/*
* (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
* (C) Copyright IBM Corp. 1996 - 1998, All Rights Reserved
*
* The original version of this source code and documentation is
* copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
* of IBM. These materials are provided under terms of a License
* Agreement between Taligent and Sun. This technology is protected
* by multiple US and International patents.
*
* This notice and attribution to Taligent may not be removed.
* Taligent is a registered trademark of Taligent, Inc.
*
*/
package java.awt.font;
import java.awt.Font;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.AttributedString;
import java.text.Bidi;
import java.text.BreakIterator;
import java.text.CharacterIterator;
import java.awt.font.FontRenderContext;
import java.util.Hashtable;
import java.util.Map;
import sun.font.AttributeValues;
import sun.font.BidiUtils;
import sun.font.TextLineComponent;
import sun.font.TextLabelFactory;
import sun.font.FontResolver;
/**
* The TextMeasurer
class provides the primitive operations
* needed for line break: measuring up to a given advance, determining the
* advance of a range of characters, and generating a
* TextLayout
for a range of characters. It also provides
* methods for incremental editing of paragraphs.
*
* A TextMeasurer
object is constructed with an
* {@link java.text.AttributedCharacterIterator AttributedCharacterIterator}
* representing a single paragraph of text. The value returned by the
* {@link AttributedCharacterIterator#getBeginIndex() getBeginIndex}
* method of AttributedCharacterIterator
* defines the absolute index of the first character. The value
* returned by the
* {@link AttributedCharacterIterator#getEndIndex() getEndIndex}
* method of AttributedCharacterIterator
defines the index
* past the last character. These values define the range of indexes to
* use in calls to the TextMeasurer
. For example, calls to
* get the advance of a range of text or the line break of a range of text
* must use indexes between the beginning and end index values. Calls to
* {@link #insertChar(java.text.AttributedCharacterIterator, int) insertChar}
* and
* {@link #deleteChar(java.text.AttributedCharacterIterator, int) deleteChar}
* reset the TextMeasurer
to use the beginning index and end
* index of the AttributedCharacterIterator
passed in those calls.
*
* Most clients will use the more convenient LineBreakMeasurer
,
* which implements the standard line break policy (placing as many words
* as will fit on each line).
*
* @author John Raley
* @see LineBreakMeasurer
* @since 1.3
*/
public final class TextMeasurer implements Cloneable {
// Number of lines to format to.
private static float EST_LINES = (float) 2.1;
/*
static {
String s = System.getProperty("estLines");
if (s != null) {
try {
Float f = new Float(s);
EST_LINES = f.floatValue();
}
catch(NumberFormatException e) {
}
}
//System.out.println("EST_LINES="+EST_LINES);
}
*/
private FontRenderContext fFrc;
private int fStart;
// characters in source text
private char[] fChars;
// Bidi for this paragraph
private Bidi fBidi;
// Levels array for chars in this paragraph - needed to reorder
// trailing counterdirectional whitespace
private byte[] fLevels;
// line components in logical order
private TextLineComponent[] fComponents;
// index where components begin
private int fComponentStart;
// index where components end
private int fComponentLimit;
private boolean haveLayoutWindow;
// used to find valid starting points for line components
private BreakIterator fLineBreak = null;
private CharArrayIterator charIter = null;
int layoutCount = 0;
int layoutCharCount = 0;
// paragraph, with resolved fonts and styles
private StyledParagraph fParagraph;
// paragraph data - same across all layouts
private boolean fIsDirectionLTR;
private byte fBaseline;
private float[] fBaselineOffsets;
private float fJustifyRatio = 1;
/**
* Constructs a TextMeasurer
from the source text.
* The source text should be a single entire paragraph.
* @param text the source paragraph. Cannot be null.
* @param frc the information about a graphics device which is needed
* to measure the text correctly. Cannot be null.
*/
public TextMeasurer(AttributedCharacterIterator text, FontRenderContext frc) {
fFrc = frc;
initAll(text);
}
protected Object clone() {
TextMeasurer other;
try {
other = (TextMeasurer) super.clone();
}
catch(CloneNotSupportedException e) {
throw new Error();
}
if (fComponents != null) {
other.fComponents = fComponents.clone();
}
return other;
}
private void invalidateComponents() {
fComponentStart = fComponentLimit = fChars.length;
fComponents = null;
haveLayoutWindow = false;
}
/**
* Initialize state, including fChars array, direction, and
* fBidi.
*/
private void initAll(AttributedCharacterIterator text) {
fStart = text.getBeginIndex();
// extract chars
fChars = new char[text.getEndIndex() - fStart];
int n = 0;
for (char c = text.first();
c != CharacterIterator.DONE;
c = text.next())
{
fChars[n++] = c;
}
text.first();
fBidi = new Bidi(text);
if (fBidi.isLeftToRight()) {
fBidi = null;
}
text.first();
Map extends Attribute, ?> paragraphAttrs = text.getAttributes();
NumericShaper shaper = AttributeValues.getNumericShaping(paragraphAttrs);
if (shaper != null) {
shaper.shape(fChars, 0, fChars.length);
}
fParagraph = new StyledParagraph(text, fChars);
// set paragraph attributes
{
// If there's an embedded graphic at the start of the
// paragraph, look for the first non-graphic character
// and use it and its font to initialize the paragraph.
// If not, use the first graphic to initialize.
fJustifyRatio = AttributeValues.getJustification(paragraphAttrs);
boolean haveFont = TextLine.advanceToFirstFont(text);
if (haveFont) {
Font defaultFont = TextLine.getFontAtCurrentPos(text);
int charsStart = text.getIndex() - text.getBeginIndex();
LineMetrics lm = defaultFont.getLineMetrics(fChars, charsStart, charsStart+1, fFrc);
fBaseline = (byte) lm.getBaselineIndex();
fBaselineOffsets = lm.getBaselineOffsets();
}
else {
// hmmm what to do here? Just try to supply reasonable
// values I guess.
GraphicAttribute graphic = (GraphicAttribute)
paragraphAttrs.get(TextAttribute.CHAR_REPLACEMENT);
fBaseline = TextLayout.getBaselineFromGraphic(graphic);
Hashtablestart
and possible
* measuring up to maxAdvance
in graphical width.
*
* @param start the character index at which to start measuring.
* start
is an absolute index, not relative to the
* start of the paragraph
* @param maxAdvance the graphical width in which the line must fit
* @return the index after the last character that will fit
* on a line beginning at start
, which is not longer
* than maxAdvance
in graphical width
* @throws IllegalArgumentException if start
is
* less than the beginning of the paragraph.
*/
public int getLineBreakIndex(int start, float maxAdvance) {
int localStart = start - fStart;
if (!haveLayoutWindow ||
localStart < fComponentStart ||
localStart >= fComponentLimit) {
makeLayoutWindow(localStart);
}
return calcLineBreak(localStart, maxAdvance) + fStart;
}
/**
* Returns the graphical width of a line beginning at start
* and including characters up to limit
.
* start
and limit
are absolute indices,
* not relative to the start of the paragraph.
*
* @param start the character index at which to start measuring
* @param limit the character index at which to stop measuring
* @return the graphical width of a line beginning at start
* and including characters up to limit
* @throws IndexOutOfBoundsException if limit
is less
* than start
* @throws IllegalArgumentException if start
or
* limit
is not between the beginning of
* the paragraph and the end of the paragraph.
*/
public float getAdvanceBetween(int start, int limit) {
int localStart = start - fStart;
int localLimit = limit - fStart;
ensureComponents(localStart, localLimit);
TextLine line = makeTextLineOnRange(localStart, localLimit);
return line.getMetrics().advance;
// could cache line in case getLayout is called with same start, limit
}
/**
* Returns a TextLayout
on the given character range.
*
* @param start the index of the first character
* @param limit the index after the last character. Must be greater
* than start
* @return a TextLayout
for the characters beginning at
* start
up to (but not including) limit
* @throws IndexOutOfBoundsException if limit
is less
* than start
* @throws IllegalArgumentException if start
or
* limit
is not between the beginning of
* the paragraph and the end of the paragraph.
*/
public TextLayout getLayout(int start, int limit) {
int localStart = start - fStart;
int localLimit = limit - fStart;
ensureComponents(localStart, localLimit);
TextLine textLine = makeTextLineOnRange(localStart, localLimit);
if (localLimit < fChars.length) {
layoutCharCount += limit-start;
layoutCount++;
}
return new TextLayout(textLine,
fBaseline,
fBaselineOffsets,
fJustifyRatio);
}
private int formattedChars = 0;
private static boolean wantStats = false;/*"true".equals(System.getProperty("collectStats"));*/
private boolean collectStats = false;
private void printStats() {
System.out.println("formattedChars: " + formattedChars);
//formattedChars = 0;
collectStats = false;
}
/**
* Updates the TextMeasurer
after a single character has
* been inserted
* into the paragraph currently represented by this
* TextMeasurer
. After this call, this
* TextMeasurer
is equivalent to a new
* TextMeasurer
created from the text; however, it will
* usually be more efficient to update an existing
* TextMeasurer
than to create a new one from scratch.
*
* @param newParagraph the text of the paragraph after performing
* the insertion. Cannot be null.
* @param insertPos the position in the text where the character was
* inserted. Must not be less than the start of
* newParagraph
, and must be less than the end of
* newParagraph
.
* @throws IndexOutOfBoundsException if insertPos
is less
* than the start of newParagraph
or greater than
* or equal to the end of newParagraph
* @throws NullPointerException if newParagraph
is
* null
*/
public void insertChar(AttributedCharacterIterator newParagraph, int insertPos) {
if (collectStats) {
printStats();
}
if (wantStats) {
collectStats = true;
}
fStart = newParagraph.getBeginIndex();
int end = newParagraph.getEndIndex();
if (end - fStart != fChars.length+1) {
initAll(newParagraph);
}
char[] newChars = new char[end-fStart];
int newCharIndex = insertPos - fStart;
System.arraycopy(fChars, 0, newChars, 0, newCharIndex);
char newChar = newParagraph.setIndex(insertPos);
newChars[newCharIndex] = newChar;
System.arraycopy(fChars,
newCharIndex,
newChars,
newCharIndex+1,
end-insertPos-1);
fChars = newChars;
if (fBidi != null || Bidi.requiresBidi(newChars, newCharIndex, newCharIndex + 1) ||
newParagraph.getAttribute(TextAttribute.BIDI_EMBEDDING) != null) {
fBidi = new Bidi(newParagraph);
if (fBidi.isLeftToRight()) {
fBidi = null;
}
}
fParagraph = StyledParagraph.insertChar(newParagraph,
fChars,
insertPos,
fParagraph);
invalidateComponents();
}
/**
* Updates the TextMeasurer
after a single character has
* been deleted
* from the paragraph currently represented by this
* TextMeasurer
. After this call, this
* TextMeasurer
is equivalent to a new TextMeasurer
* created from the text; however, it will usually be more efficient
* to update an existing TextMeasurer
than to create a new one
* from scratch.
*
* @param newParagraph the text of the paragraph after performing
* the deletion. Cannot be null.
* @param deletePos the position in the text where the character was removed.
* Must not be less than
* the start of newParagraph
, and must not be greater than the
* end of newParagraph
.
* @throws IndexOutOfBoundsException if deletePos
is
* less than the start of newParagraph
or greater
* than the end of newParagraph
* @throws NullPointerException if newParagraph
is
* null
*/
public void deleteChar(AttributedCharacterIterator newParagraph, int deletePos) {
fStart = newParagraph.getBeginIndex();
int end = newParagraph.getEndIndex();
if (end - fStart != fChars.length-1) {
initAll(newParagraph);
}
char[] newChars = new char[end-fStart];
int changedIndex = deletePos-fStart;
System.arraycopy(fChars, 0, newChars, 0, deletePos-fStart);
System.arraycopy(fChars, changedIndex+1, newChars, changedIndex, end-deletePos);
fChars = newChars;
if (fBidi != null) {
fBidi = new Bidi(newParagraph);
if (fBidi.isLeftToRight()) {
fBidi = null;
}
}
fParagraph = StyledParagraph.deleteChar(newParagraph,
fChars,
deletePos,
fParagraph);
invalidateComponents();
}
/**
* NOTE: This method is only for LineBreakMeasurer's use. It is package-
* private because it returns internal data.
*/
char[] getChars() {
return fChars;
}
}