Method from java.awt.font.TextLayout Detail: |
protected Object clone() {
/*
* !!! I think this is safe. Once created, nothing mutates the
* glyphvectors or arrays. But we need to make sure.
* {jbr} actually, that's not quite true. The justification code
* mutates after cloning. It doesn't actually change the glyphvectors
* (that's impossible) but it replaces them with justified sets. This
* is a problem for GlyphIterator creation, since new GlyphIterators
* are created by cloning a prototype. If the prototype has outdated
* glyphvectors, so will the new ones. A partial solution is to set the
* prototypical GlyphIterator to null when the glyphvectors change. If
* you forget this one time, you're hosed.
*/
try {
return super.clone();
}
catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
Creates a copy of this TextLayout . |
public void draw(Graphics2D g2,
float x,
float y) {
if (g2 == null) {
throw new IllegalArgumentException("Null Graphics2D passed to TextLayout.draw()");
}
textLine.draw(g2, x - dx, y - dy);
}
Renders this TextLayout at the specified location in
the specified Graphics2D context.
The origin of the layout is placed at x, y. Rendering may touch
any point within getBounds() of this position. This
leaves the g2 unchanged. Text is rendered along the
baseline path. |
public boolean equals(Object obj) {
return (obj instanceof TextLayout) && equals((TextLayout)obj);
}
Returns true if the specified Object is a
TextLayout object and if the specified Object
equals this TextLayout . |
public boolean equals(TextLayout rhs) {
if (rhs == null) {
return false;
}
if (rhs == this) {
return true;
}
ensureCache();
return textLine.equals(rhs.textLine);
}
Returns true if the two layouts are equal.
Two layouts are equal if they contain equal glyphvectors in the same order. |
public float getAdvance() {
ensureCache();
return lineMetrics.advance;
}
Returns the advance of this TextLayout .
The advance is the distance from the origin to the advance of the
rightmost (bottommost) character. This is in baseline-relative
coordinates. |
public float getAscent() {
ensureCache();
return lineMetrics.ascent;
}
Returns the ascent of this TextLayout .
The ascent is the distance from the top (right) of the
TextLayout to the baseline. It is always either
positive or zero. The ascent is sufficient to
accomodate superscripted text and is the maximum of the sum of the
ascent, offset, and baseline of each glyph. The ascent is
the maximum ascent from the baseline of all the text in the
TextLayout. It is in baseline-relative coordinates. |
public byte getBaseline() {
return baseline;
}
Returns the baseline for this TextLayout .
The baseline is one of the values defined in Font ,
which are roman, centered and hanging. Ascent and descent are
relative to this baseline. The baselineOffsets
are also relative to this baseline. |
static byte getBaselineFromGraphic(GraphicAttribute graphic) {
byte alignment = (byte) graphic.getAlignment();
if (alignment == GraphicAttribute.BOTTOM_ALIGNMENT ||
alignment == GraphicAttribute.TOP_ALIGNMENT) {
return (byte)GraphicAttribute.ROMAN_BASELINE;
}
else {
return alignment;
}
}
|
public float[] getBaselineOffsets() {
float[] offsets = new float[baselineOffsets.length];
System.arraycopy(baselineOffsets, 0, offsets, 0, offsets.length);
return offsets;
}
Returns the offsets array for the baselines used for this
TextLayout .
The array is indexed by one of the values defined in
Font , which are roman, centered and hanging. The
values are relative to this TextLayout object's
baseline, so that getBaselineOffsets[getBaseline()] == 0 .
Offsets are added to the position of the TextLayout
object's baseline to get the position for the new baseline. |
public Shape getBlackBoxBounds(int firstEndpoint,
int secondEndpoint) {
ensureCache();
if (firstEndpoint > secondEndpoint) {
int t = firstEndpoint;
firstEndpoint = secondEndpoint;
secondEndpoint = t;
}
if (firstEndpoint < 0 || secondEndpoint > characterCount) {
throw new IllegalArgumentException("Invalid range passed to TextLayout.getBlackBoxBounds()");
}
/*
* return an area that consists of the bounding boxes of all the
* characters from firstEndpoint to limit
*/
GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO);
if (firstEndpoint < characterCount) {
for (int logIndex = firstEndpoint;
logIndex < secondEndpoint;
logIndex++) {
Rectangle2D r = textLine.getCharBounds(logIndex);
if (!r.isEmpty()) {
result.append(r, false);
}
}
}
if (dx != 0 || dy != 0) {
AffineTransform tx = AffineTransform.getTranslateInstance(dx, dy);
result = (GeneralPath)tx.createTransformedShape(result);
}
LayoutPathImpl lp = textLine.getLayoutPath();
if (lp != null) {
result = (GeneralPath)lp.mapShape(result);
}
//return new Highlight(result, false);
return result;
}
Returns the black box bounds of the characters in the specified range.
The black box bounds is an area consisting of the union of the bounding
boxes of all the glyphs corresponding to the characters between start
and limit. This area can be disjoint. |
public Rectangle2D getBounds() {
ensureCache();
if (boundsRect == null) {
Rectangle2D vb = textLine.getVisualBounds();
if (dx != 0 || dy != 0) {
vb.setRect(vb.getX() - dx,
vb.getY() - dy,
vb.getWidth(),
vb.getHeight());
}
boundsRect = vb;
}
Rectangle2D bounds = new Rectangle2D.Float();
bounds.setRect(boundsRect);
return bounds;
}
Returns the bounds of this TextLayout .
The bounds are in standard coordinates.
Due to rasterization effects, this bounds might not enclose all of the
pixels rendered by the TextLayout.
It might not coincide exactly with the ascent, descent,
origin or advance of the TextLayout . |
public float[] getCaretInfo(TextHitInfo hit) {
return getCaretInfo(hit, getNaturalBounds());
}
Returns information about the caret corresponding to hit .
This method is a convenience overload of getCaretInfo and
uses the natural bounds of this TextLayout . |
public float[] getCaretInfo(TextHitInfo hit,
Rectangle2D bounds) {
ensureCache();
checkTextHit(hit);
return getCaretInfoTestInternal(hit, bounds);
}
Returns information about the caret corresponding to hit .
The first element of the array is the intersection of the caret with
the baseline, as a distance along the baseline. The second element
of the array is the inverse slope (run/rise) of the caret, measured
with respect to the baseline at that point.
This method is meant for informational use. To display carets, it
is better to use getCaretShapes . |
public Shape getCaretShape(TextHitInfo hit) {
return getCaretShape(hit, getNaturalBounds());
}
Returns a Shape representing the caret at the specified
hit inside the natural bounds of this TextLayout . |
public Shape getCaretShape(TextHitInfo hit,
Rectangle2D bounds) {
ensureCache();
checkTextHit(hit);
if (bounds == null) {
throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCaret()");
}
return pathToShape(getCaretPath(hit, bounds), false, textLine.getLayoutPath());
}
Returns a Shape representing the caret at the specified
hit inside the specified bounds. |
public Shape[] getCaretShapes(int offset) {
// {sfb} parameter checking is done in overloaded version
return getCaretShapes(offset, getNaturalBounds(), DEFAULT_CARET_POLICY);
}
Returns two paths corresponding to the strong and weak caret.
This method is a convenience overload of getCaretShapes
that uses the default caret policy and this TextLayout
object's natural bounds. |
public Shape[] getCaretShapes(int offset,
Rectangle2D bounds) {
// {sfb} parameter checking is done in overloaded version
return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
}
Returns two paths corresponding to the strong and weak caret.
This method is a convenience overload of getCaretShapes
that uses the default caret policy. |
public Shape[] getCaretShapes(int offset,
Rectangle2D bounds,
CaretPolicy policy) {
ensureCache();
if (offset < 0 || offset > characterCount) {
throw new IllegalArgumentException("Offset out of bounds in TextLayout.getCaretShapes()");
}
if (bounds == null) {
throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCaretShapes()");
}
if (policy == null) {
throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getCaretShapes()");
}
Shape[] result = new Shape[2];
TextHitInfo hit = TextHitInfo.afterOffset(offset);
int hitCaret = hitToCaret(hit);
LayoutPathImpl lp = textLine.getLayoutPath();
Shape hitShape = pathToShape(getCaretPath(hit, bounds), false, lp);
TextHitInfo otherHit = hit.getOtherHit();
int otherCaret = hitToCaret(otherHit);
if (hitCaret == otherCaret) {
result[0] = hitShape;
}
else { // more than one caret
Shape otherShape = pathToShape(getCaretPath(otherHit, bounds), false, lp);
TextHitInfo strongHit = policy.getStrongCaret(hit, otherHit, this);
boolean hitIsStrong = strongHit.equals(hit);
if (hitIsStrong) {// then other is weak
result[0] = hitShape;
result[1] = otherShape;
}
else {
result[0] = otherShape;
result[1] = hitShape;
}
}
return result;
}
Returns two paths corresponding to the strong and weak caret. |
public int getCharacterCount() {
return characterCount;
}
Returns the number of characters represented by this
TextLayout . |
public byte getCharacterLevel(int index) {
// hmm, allow indices at endpoints? For now, yes.
if (index < -1 || index > characterCount) {
throw new IllegalArgumentException("Index is out of range in getCharacterLevel.");
}
ensureCache();
if (index == -1 || index == characterCount) {
return (byte) (textLine.isDirectionLTR()? 0 : 1);
}
return textLine.getCharLevel(index);
}
Returns the level of the character at index .
Indices -1 and characterCount are assigned the base
level of this TextLayout . |
public float getDescent() {
ensureCache();
return lineMetrics.descent;
}
Returns the descent of this TextLayout .
The descent is the distance from the baseline to the bottom (left) of
the TextLayout . It is always either positive or zero.
The descent is sufficient to accomodate subscripted text and is the
maximum of the sum of the descent, offset, and baseline of each glyph.
This is the maximum descent from the baseline of all the text in
the TextLayout. It is in baseline-relative coordinates. |
public TextLayout getJustifiedLayout(float justificationWidth) {
if (justificationWidth < = 0) {
throw new IllegalArgumentException("justificationWidth < = 0 passed to TextLayout.getJustifiedLayout()");
}
if (justifyRatio == ALREADY_JUSTIFIED) {
throw new Error("Can't justify again.");
}
ensureCache(); // make sure textLine is not null
// default justification range to exclude trailing logical whitespace
int limit = characterCount;
while (limit > 0 && textLine.isCharWhitespace(limit-1)) {
--limit;
}
TextLine newLine = textLine.getJustifiedLine(justificationWidth, justifyRatio, 0, limit);
if (newLine != null) {
return new TextLayout(newLine, baseline, baselineOffsets, ALREADY_JUSTIFIED);
}
return this;
}
Creates a copy of this TextLayout justified to the
specified width.
If this TextLayout has already been justified, an
exception is thrown. If this TextLayout object's
justification ratio is zero, a TextLayout identical
to this TextLayout is returned. |
public LayoutPath getLayoutPath() {
return textLine.getLayoutPath();
}
Return the LayoutPath, or null if the layout path is the
default path (x maps to advance, y maps to offset). |
public float getLeading() {
ensureCache();
return lineMetrics.leading;
}
Returns the leading of the TextLayout .
The leading is the suggested interline spacing for this
TextLayout . This is in baseline-relative
coordinates.
The leading is computed from the leading, descent, and baseline
of all glyphvectors in the TextLayout . The algorithm
is roughly as follows:
maxD = 0;
maxDL = 0;
for (GlyphVector g in all glyphvectors) {
maxD = max(maxD, g.getDescent() + offsets[g.getBaseline()]);
maxDL = max(maxDL, g.getDescent() + g.getLeading() +
offsets[g.getBaseline()]);
}
return maxDL - maxD;
|
public Shape getLogicalHighlightShape(int firstEndpoint,
int secondEndpoint) {
return getLogicalHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
}
Returns a Shape enclosing the logical selection in the
specified range, extended to the natural bounds of this
TextLayout . This method is a convenience overload of
getLogicalHighlightShape that uses the natural bounds of
this TextLayout . |
public Shape getLogicalHighlightShape(int firstEndpoint,
int secondEndpoint,
Rectangle2D bounds) {
if (bounds == null) {
throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getLogicalHighlightShape()");
}
ensureCache();
if (firstEndpoint > secondEndpoint) {
int t = firstEndpoint;
firstEndpoint = secondEndpoint;
secondEndpoint = t;
}
if(firstEndpoint < 0 || secondEndpoint > characterCount) {
throw new IllegalArgumentException("Range is invalid in TextLayout.getLogicalHighlightShape()");
}
GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
int[] carets = new int[10]; // would this ever not handle all cases?
int count = 0;
if (firstEndpoint < secondEndpoint) {
int logIndex = firstEndpoint;
do {
carets[count++] = hitToCaret(TextHitInfo.leading(logIndex));
boolean ltr = textLine.isCharLTR(logIndex);
do {
logIndex++;
} while (logIndex < secondEndpoint && textLine.isCharLTR(logIndex) == ltr);
int hitCh = logIndex;
carets[count++] = hitToCaret(TextHitInfo.trailing(hitCh - 1));
if (count == carets.length) {
int[] temp = new int[carets.length + 10];
System.arraycopy(carets, 0, temp, 0, count);
carets = temp;
}
} while (logIndex < secondEndpoint);
}
else {
count = 2;
carets[0] = carets[1] = hitToCaret(TextHitInfo.leading(firstEndpoint));
}
// now create paths for pairs of carets
for (int i = 0; i < count; i += 2) {
result.append(caretBoundingShape(carets[i], carets[i+1], bounds),
false);
}
if (firstEndpoint != secondEndpoint) {
if ((textLine.isDirectionLTR() && firstEndpoint == 0) || (!textLine.isDirectionLTR() &&
secondEndpoint == characterCount)) {
GeneralPath ls = leftShape(bounds);
if (!ls.getBounds().isEmpty()) {
result.append(ls, false);
}
}
if ((textLine.isDirectionLTR() && secondEndpoint == characterCount) ||
(!textLine.isDirectionLTR() && firstEndpoint == 0)) {
GeneralPath rs = rightShape(bounds);
if (!rs.getBounds().isEmpty()) {
result.append(rs, false);
}
}
}
LayoutPathImpl lp = textLine.getLayoutPath();
if (lp != null) {
result = (GeneralPath)lp.mapShape(result); // dlf cast safe?
}
return result;
}
Returns a Shape enclosing the logical selection in the
specified range, extended to the specified bounds .
If the selection range includes the first logical character, the
selection is extended to the portion of bounds before
the start of this TextLayout . If the range includes
the last logical character, the selection is extended to the portion
of bounds after the end of this TextLayout .
The height (width on vertical lines) of the selection is always
extended to bounds .
The selection can be discontiguous on lines with mixed-direction text.
Only those characters in the logical range between start and limit
appear selected. For example, consider the text 'ABCdef' where capital
letters indicate right-to-left text, rendered on a right-to-left line,
with a logical selection from 0 to 4 ('ABCd'). The text appears as
follows, with bold standing in for the selection, and underlining for
the extension:
defCBA
The selection is discontiguous because the selected characters are
visually discontiguous. Also note that since the range includes the
first logical character (A), the selection is extended to the portion
of the bounds before the start of the layout, which in
this case (a right-to-left line) is the right portion of the
bounds . |
public int[] getLogicalRangesForVisualSelection(TextHitInfo firstEndpoint,
TextHitInfo secondEndpoint) {
ensureCache();
checkTextHit(firstEndpoint);
checkTextHit(secondEndpoint);
// !!! probably want to optimize for all LTR text
boolean[] included = new boolean[characterCount];
int startIndex = hitToCaret(firstEndpoint);
int limitIndex = hitToCaret(secondEndpoint);
if (startIndex > limitIndex) {
int t = startIndex;
startIndex = limitIndex;
limitIndex = t;
}
/*
* now we have the visual indexes of the glyphs at the start and limit
* of the selection range walk through runs marking characters that
* were included in the visual range there is probably a more efficient
* way to do this, but this ought to work, so hey
*/
if (startIndex < limitIndex) {
int visIndex = startIndex;
while (visIndex < limitIndex) {
included[textLine.visualToLogical(visIndex)] = true;
++visIndex;
}
}
/*
* count how many runs we have, ought to be one or two, but perhaps
* things are especially weird
*/
int count = 0;
boolean inrun = false;
for (int i = 0; i < characterCount; i++) {
if (included[i] != inrun) {
inrun = !inrun;
if (inrun) {
count++;
}
}
}
int[] ranges = new int[count * 2];
count = 0;
inrun = false;
for (int i = 0; i < characterCount; i++) {
if (included[i] != inrun) {
ranges[count++] = i;
inrun = !inrun;
}
}
if (inrun) {
ranges[count++] = characterCount;
}
return ranges;
}
Returns the logical ranges of text corresponding to a visual selection. |
public TextHitInfo getNextLeftHit(TextHitInfo hit) {
ensureCache();
checkTextHit(hit);
int caret = hitToCaret(hit);
if (caret == 0) {
return null;
}
do {
--caret;
} while(!caretIsValid(caret));
return caretToHit(caret);
}
Returns the hit for the next caret to the left (top); if no such
hit, returns null .
If the hit character index is out of bounds, an
IllegalArgumentException is thrown. |
public TextHitInfo getNextLeftHit(int offset) {
return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
}
Returns the hit for the next caret to the left (top); if no
such hit, returns null . The hit is to the left of
the strong caret at the specified offset, as determined by the
default policy.
The returned hit is the stronger of the two possible
hits, as determined by the default policy. |
public TextHitInfo getNextLeftHit(int offset,
CaretPolicy policy) {
if (policy == null) {
throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getNextLeftHit()");
}
if (offset < 0 || offset > characterCount) {
throw new IllegalArgumentException("Offset out of bounds in TextLayout.getNextLeftHit()");
}
TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
TextHitInfo hit2 = hit1.getOtherHit();
TextHitInfo nextHit = getNextLeftHit(policy.getStrongCaret(hit1, hit2, this));
if (nextHit != null) {
TextHitInfo otherHit = getVisualOtherHit(nextHit);
return policy.getStrongCaret(otherHit, nextHit, this);
}
else {
return null;
}
}
Returns the hit for the next caret to the left (top); if no
such hit, returns null . The hit is to the left of
the strong caret at the specified offset, as determined by the
specified policy.
The returned hit is the stronger of the two possible
hits, as determined by the specified policy. |
public TextHitInfo getNextRightHit(TextHitInfo hit) {
ensureCache();
checkTextHit(hit);
int caret = hitToCaret(hit);
if (caret == characterCount) {
return null;
}
do {
++caret;
} while (!caretIsValid(caret));
return caretToHit(caret);
}
Returns the hit for the next caret to the right (bottom); if there
is no such hit, returns null .
If the hit character index is out of bounds, an
IllegalArgumentException is thrown. |
public TextHitInfo getNextRightHit(int offset) {
return getNextRightHit(offset, DEFAULT_CARET_POLICY);
}
Returns the hit for the next caret to the right (bottom); if no
such hit, returns null . The hit is to the right of
the strong caret at the specified offset, as determined by the
default policy.
The returned hit is the stronger of the two possible
hits, as determined by the default policy. |
public TextHitInfo getNextRightHit(int offset,
CaretPolicy policy) {
if (offset < 0 || offset > characterCount) {
throw new IllegalArgumentException("Offset out of bounds in TextLayout.getNextRightHit()");
}
if (policy == null) {
throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getNextRightHit()");
}
TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
TextHitInfo hit2 = hit1.getOtherHit();
TextHitInfo nextHit = getNextRightHit(policy.getStrongCaret(hit1, hit2, this));
if (nextHit != null) {
TextHitInfo otherHit = getVisualOtherHit(nextHit);
return policy.getStrongCaret(otherHit, nextHit, this);
}
else {
return null;
}
}
Returns the hit for the next caret to the right (bottom); if no
such hit, returns null . The hit is to the right of
the strong caret at the specified offset, as determined by the
specified policy.
The returned hit is the stronger of the two possible
hits, as determined by the specified policy. |
public Shape getOutline(AffineTransform tx) {
ensureCache();
Shape result = textLine.getOutline(tx);
LayoutPathImpl lp = textLine.getLayoutPath();
if (lp != null) {
result = lp.mapShape(result);
}
return result;
}
Returns a Shape representing the outline of this
TextLayout . |
public Rectangle getPixelBounds(FontRenderContext frc,
float x,
float y) {
return textLine.getPixelBounds(frc, x, y);
}
Returns the pixel bounds of this TextLayout when
rendered in a graphics with the given
FontRenderContext at the given location. The
graphics render context need not be the same as the
FontRenderContext used to create this
TextLayout , and can be null. If it is null, the
FontRenderContext of this TextLayout
is used. |
TextLine getTextLineForTesting() {
return textLine;
}
Package-only method for testing ONLY. Please don't abuse. |
public float getVisibleAdvance() {
ensureCache();
return visibleAdvance;
}
Returns the advance of this TextLayout , minus trailing
whitespace. This is in baseline-relative coordinates. |
public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
TextHitInfo secondEndpoint) {
return getVisualHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
}
Returns a Shape enclosing the visual selection in the
specified range, extended to the bounds. This method is a
convenience overload of getVisualHighlightShape that
uses the natural bounds of this TextLayout . |
public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
TextHitInfo secondEndpoint,
Rectangle2D bounds) {
ensureCache();
checkTextHit(firstEndpoint);
checkTextHit(secondEndpoint);
if(bounds == null) {
throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getVisualHighlightShape()");
}
GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
int firstCaret = hitToCaret(firstEndpoint);
int secondCaret = hitToCaret(secondEndpoint);
result.append(caretBoundingShape(firstCaret, secondCaret, bounds),
false);
if (firstCaret == 0 || secondCaret == 0) {
GeneralPath ls = leftShape(bounds);
if (!ls.getBounds().isEmpty())
result.append(ls, false);
}
if (firstCaret == characterCount || secondCaret == characterCount) {
GeneralPath rs = rightShape(bounds);
if (!rs.getBounds().isEmpty()) {
result.append(rs, false);
}
}
LayoutPathImpl lp = textLine.getLayoutPath();
if (lp != null) {
result = (GeneralPath)lp.mapShape(result); // dlf cast safe?
}
return result;
}
Returns a path enclosing the visual selection in the specified range,
extended to bounds .
If the selection includes the leftmost (topmost) position, the selection
is extended to the left (top) of bounds . If the
selection includes the rightmost (bottommost) position, the selection
is extended to the right (bottom) of the bounds. The height
(width on vertical lines) of the selection is always extended to
bounds .
Although the selection is always contiguous, the logically selected
text can be discontiguous on lines with mixed-direction text. The
logical ranges of text selected can be retrieved using
getLogicalRangesForVisualSelection . For example,
consider the text 'ABCdef' where capital letters indicate
right-to-left text, rendered on a right-to-left line, with a visual
selection from 0L (the leading edge of 'A') to 3T (the trailing edge
of 'd'). The text appears as follows, with bold underlined areas
representing the selection:
defCBA
The logical selection ranges are 0-3, 4-6 (ABC, ef) because the
visually contiguous text is logically discontiguous. Also note that
since the rightmost position on the layout (to the right of 'A') is
selected, the selection is extended to the right of the bounds. |
public TextHitInfo getVisualOtherHit(TextHitInfo hit) {
ensureCache();
checkTextHit(hit);
int hitCharIndex = hit.getCharIndex();
int charIndex;
boolean leading;
if (hitCharIndex == -1 || hitCharIndex == characterCount) {
int visIndex;
if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
visIndex = 0;
}
else {
visIndex = characterCount-1;
}
charIndex = textLine.visualToLogical(visIndex);
if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
// at left end
leading = textLine.isCharLTR(charIndex);
}
else {
// at right end
leading = !textLine.isCharLTR(charIndex);
}
}
else {
int visIndex = textLine.logicalToVisual(hitCharIndex);
boolean movedToRight;
if (textLine.isCharLTR(hitCharIndex) == hit.isLeadingEdge()) {
--visIndex;
movedToRight = false;
}
else {
++visIndex;
movedToRight = true;
}
if (visIndex > -1 && visIndex < characterCount) {
charIndex = textLine.visualToLogical(visIndex);
leading = movedToRight == textLine.isCharLTR(charIndex);
}
else {
charIndex =
(movedToRight == textLine.isDirectionLTR())? characterCount : -1;
leading = charIndex == characterCount;
}
}
return leading? TextHitInfo.leading(charIndex) :
TextHitInfo.trailing(charIndex);
}
Returns the hit on the opposite side of the specified hit's caret. |
protected void handleJustify(float justificationWidth) {
// never called
}
Justify this layout. Overridden by subclassers to control justification
(if there were subclassers, that is...)
The layout will only justify if the paragraph attributes (from the
source text, possibly defaulted by the layout attributes) indicate a
non-zero justification ratio. The text will be justified to the
indicated width. The current implementation also adjusts hanging
punctuation and trailing whitespace to overhang the justification width.
Once justified, the layout may not be rejustified.
Some code may rely on immutablity of layouts. Subclassers should not
call this directly, but instead should call getJustifiedLayout, which
will call this method on a clone of this layout, preserving
the original. |
public int hashCode() {
if (hashCodeCache == 0) {
ensureCache();
hashCodeCache = textLine.hashCode();
}
return hashCodeCache;
}
Returns the hash code of this TextLayout . |
public TextHitInfo hitTestChar(float x,
float y) {
return hitTestChar(x, y, getNaturalBounds());
}
Returns a TextHitInfo corresponding to the
specified point. This method is a convenience overload of
hitTestChar that uses the natural bounds of this
TextLayout . |
public TextHitInfo hitTestChar(float x,
float y,
Rectangle2D bounds) {
// check boundary conditions
LayoutPathImpl lp = textLine.getLayoutPath();
boolean prev = false;
if (lp != null) {
Point2D.Float pt = new Point2D.Float(x, y);
prev = lp.pointToPath(pt, pt);
x = pt.x;
y = pt.y;
}
if (isVertical()) {
if (y < bounds.getMinY()) {
return TextHitInfo.leading(0);
} else if (y >= bounds.getMaxY()) {
return TextHitInfo.trailing(characterCount-1);
}
} else {
if (x < bounds.getMinX()) {
return isLeftToRight() ? TextHitInfo.leading(0) : TextHitInfo.trailing(characterCount-1);
} else if (x >= bounds.getMaxX()) {
return isLeftToRight() ? TextHitInfo.trailing(characterCount-1) : TextHitInfo.leading(0);
}
}
// revised hit test
// the original seems too complex and fails miserably with italic offsets
// the natural tendency is to move towards the character you want to hit
// so we'll just measure distance to the center of each character's visual
// bounds, pick the closest one, then see which side of the character's
// center line (italic) the point is on.
// this tends to make it easier to hit narrow characters, which can be a
// bit odd if you're visually over an adjacent wide character. this makes
// a difference with bidi, so perhaps i need to revisit this yet again.
double distance = Double.MAX_VALUE;
int index = 0;
int trail = -1;
CoreMetrics lcm = null;
float icx = 0, icy = 0, ia = 0, cy = 0, dya = 0, ydsq = 0;
for (int i = 0; i < characterCount; ++i) {
if (!textLine.caretAtOffsetIsValid(i)) {
continue;
}
if (trail == -1) {
trail = i;
}
CoreMetrics cm = textLine.getCoreMetricsAt(i);
if (cm != lcm) {
lcm = cm;
// just work around baseline mess for now
if (cm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
cy = -(textLine.getMetrics().ascent - cm.ascent) + cm.ssOffset;
} else if (cm.baselineIndex == GraphicAttribute.BOTTOM_ALIGNMENT) {
cy = textLine.getMetrics().descent - cm.descent + cm.ssOffset;
} else {
cy = cm.effectiveBaselineOffset(baselineOffsets) + cm.ssOffset;
}
float dy = (cm.descent - cm.ascent) / 2 - cy;
dya = dy * cm.italicAngle;
cy += dy;
ydsq = (cy - y)*(cy - y);
}
float cx = textLine.getCharXPosition(i);
float ca = textLine.getCharAdvance(i);
float dx = ca / 2;
cx += dx - dya;
// proximity in x (along baseline) is two times as important as proximity in y
double nd = Math.sqrt(4*(cx - x)*(cx - x) + ydsq);
if (nd < distance) {
distance = nd;
index = i;
trail = -1;
icx = cx; icy = cy; ia = cm.italicAngle;
}
}
boolean left = x < icx - (y - icy) * ia;
boolean leading = textLine.isCharLTR(index) == left;
if (trail == -1) {
trail = characterCount;
}
TextHitInfo result = leading ? TextHitInfo.leading(index) :
TextHitInfo.trailing(trail-1);
return result;
}
Returns a TextHitInfo corresponding to the
specified point.
Coordinates outside the bounds of the TextLayout
map to hits on the leading edge of the first logical character,
or the trailing edge of the last logical character, as appropriate,
regardless of the position of that character in the line. Only the
direction along the baseline is used to make this evaluation. |
public void hitToPoint(TextHitInfo hit,
Point2D point) {
if (hit == null || point == null) {
throw new NullPointerException((hit == null ? "hit" : "point") +
" can't be null");
}
ensureCache();
checkTextHit(hit);
float adv = 0;
float off = 0;
int ix = hit.getCharIndex();
boolean leading = hit.isLeadingEdge();
boolean ltr;
if (ix == -1 || ix == textLine.characterCount()) {
ltr = textLine.isDirectionLTR();
adv = (ltr == (ix == -1)) ? 0 : lineMetrics.advance;
} else {
ltr = textLine.isCharLTR(ix);
adv = textLine.getCharLinePosition(ix, leading);
off = textLine.getCharYPosition(ix);
}
point.setLocation(adv, off);
LayoutPath lp = textLine.getLayoutPath();
if (lp != null) {
lp.pathToPoint(point, ltr != leading, point);
}
}
Convert a hit to a point in standard coordinates. The point is
on the baseline of the character at the leading or trailing
edge of the character, as appropriate. If the path is
broken at the side of the character represented by the hit, the
point will be adjacent to the character. |
public boolean isLeftToRight() {
return textLine.isDirectionLTR();
}
Returns true if this TextLayout has
a left-to-right base direction or false if it has
a right-to-left base direction. The TextLayout
has a base direction of either left-to-right (LTR) or
right-to-left (RTL). The base direction is independent of the
actual direction of text on the line, which may be either LTR,
RTL, or mixed. Left-to-right layouts by default should position
flush left. If the layout is on a tabbed line, the
tabs run left to right, so that logically successive layouts position
left to right. The opposite is true for RTL layouts. By default they
should position flush left, and tabs run right-to-left. |
public boolean isVertical() {
return isVerticalLine;
}
Returns true if this TextLayout is vertical. |
public String toString() {
ensureCache();
return textLine.toString();
}
Returns debugging information for this TextLayout . |