TextLayoutpublic final class TextLayout extends Object implements CloneableTextLayout is an immutable graphical representation of styled
character data.
It provides the following capabilities:
- implicit bidirectional analysis and reordering,
- cursor positioning and movement, including split cursors for
mixed directional text,
- highlighting, including both logical and visual highlighting
for mixed directional text,
- multiple baselines (roman, hanging, and centered),
- hit testing,
- justification,
- default font substitution,
- metric information such as ascent, descent, and advance, and
- rendering
A TextLayout object can be rendered using
its draw method.
TextLayout can be constructed either directly or through
the use of a {@link LineBreakMeasurer}. When constructed directly, the
source text represents a single paragraph. LineBreakMeasurer
allows styled text to be broken into lines that fit within a particular
width. See the LineBreakMeasurer documentation for more
information.
TextLayout construction logically proceeds as follows:
- paragraph attributes are extracted and examined,
- text is analyzed for bidirectional reordering, and reordering
information is computed if needed,
- text is segmented into style runs
- fonts are chosen for style runs, first by using a font if the
attribute {@link TextAttribute#FONT} is present, otherwise by computing
a default font using the attributes that have been defined
- if text is on multiple baselines, the runs or subruns are further
broken into subruns sharing a common baseline,
- glyphvectors are generated for each run using the chosen font,
- final bidirectional reordering is performed on the glyphvectors
All graphical information returned from a TextLayout
object's methods is relative to the origin of the
TextLayout , which is the intersection of the
TextLayout object's baseline with its left edge. Also,
coordinates passed into a TextLayout object's methods
are assumed to be relative to the TextLayout object's
origin. Clients usually need to translate between a
TextLayout object's coordinate system and the coordinate
system in another object (such as a
{@link java.awt.Graphics Graphics} object).
TextLayout objects are constructed from styled text,
but they do not retain a reference to their source text. Thus,
changes in the text previously used to generate a TextLayout
do not affect the TextLayout .
Three methods on a TextLayout object
(getNextRightHit , getNextLeftHit , and
hitTestChar ) return instances of {@link TextHitInfo}.
The offsets contained in these TextHitInfo objects
are relative to the start of the TextLayout , not
to the text used to create the TextLayout . Similarly,
TextLayout methods that accept TextHitInfo
instances as parameters expect the TextHitInfo object's
offsets to be relative to the TextLayout , not to any
underlying text storage model.
Examples:
Constructing and drawing a TextLayout and its bounding
rectangle:
Graphics2D g = ...;
Point2D loc = ...;
Font font = Font.getFont("Helvetica-bold-italic");
FontRenderContext frc = g.getFontRenderContext();
TextLayout layout = new TextLayout("This is a string", font, frc);
layout.draw(g, (float)loc.getX(), (float)loc.getY());
Rectangle2D bounds = layout.getBounds();
bounds.setRect(bounds.getX()+loc.getX(),
bounds.getY()+loc.getY(),
bounds.getWidth(),
bounds.getHeight());
g.draw(bounds);
Hit-testing a TextLayout (determining which character is at
a particular graphical location):
Point2D click = ...;
TextHitInfo hit = layout.hitTestChar(
(float) (click.getX() - loc.getX()),
(float) (click.getY() - loc.getY()));
Responding to a right-arrow key press:
int insertionIndex = ...;
TextHitInfo next = layout.getNextRightHit(insertionIndex);
if (next != null) {
// translate graphics to origin of layout on screen
g.translate(loc.getX(), loc.getY());
Shape[] carets = layout.getCaretShapes(next.getInsertionIndex());
g.draw(carets[0]);
if (carets[1] != null) {
g.draw(carets[1]);
}
}
Drawing a selection range corresponding to a substring in the source text.
The selected area may not be visually contiguous:
// selStart, selLimit should be relative to the layout,
// not to the source text
int selStart = ..., selLimit = ...;
Color selectionColor = ...;
Shape selection = layout.getLogicalHighlightShape(selStart, selLimit);
// selection may consist of disjoint areas
// graphics is assumed to be tranlated to origin of layout
g.setColor(selectionColor);
g.fill(selection);
Drawing a visually contiguous selection range. The selection range may
correspond to more than one substring in the source text. The ranges of
the corresponding source text substrings can be obtained with
getLogicalRangesForVisualSelection() :
TextHitInfo selStart = ..., selLimit = ...;
Shape selection = layout.getVisualHighlightShape(selStart, selLimit);
g.setColor(selectionColor);
g.fill(selection);
int[] ranges = getLogicalRangesForVisualSelection(selStart, selLimit);
// ranges[0], ranges[1] is the first selection range,
// ranges[2], ranges[3] is the second selection range, etc.
|
Fields Summary |
---|
private int | characterCount | private boolean | isVerticalLine | private byte | baseline | private float[] | baselineOffsets | private TextLine | textLine | private java.awt.font.TextLine.TextLineMetrics | lineMetrics | private float | visibleAdvance | private int | hashCodeCache | private OptInfo | optInfo | private boolean | cacheIsValid | private float | justifyRatio | private static final float | ALREADY_JUSTIFIED | private static float | dx | private static float | dy | private Rectangle2D | naturalBounds | private Rectangle2D | boundsRect | private boolean | caretsInLigaturesAreAllowed | public static final CaretPolicy | DEFAULT_CARET_POLICYThis CaretPolicy is used when a policy is not specified
by the client. With this policy, a hit on a character whose direction
is the same as the line direction is stronger than a hit on a
counterdirectional character. If the characters' directions are
the same, a hit on the leading edge of a character is stronger
than a hit on the trailing edge of a character. |
Constructors Summary |
---|
public TextLayout(String string, Font font, FontRenderContext frc)Constructs a TextLayout from a String
and a {@link Font}. All the text is styled using the specified
Font .
The String must specify a single paragraph of text,
because an entire paragraph is required for the bidirectional
algorithm.
if (font == null) {
throw new IllegalArgumentException("Null font passed to TextLayout constructor.");
}
if (string == null) {
throw new IllegalArgumentException("Null string passed to TextLayout constructor.");
}
if (string.length() == 0) {
throw new IllegalArgumentException("Zero length string passed to TextLayout constructor.");
}
char[] text = string.toCharArray();
if (sameBaselineUpTo(font, text, 0, text.length) == text.length) {
fastInit(text, font, null, frc);
} else {
AttributedString as = new AttributedString(string);
as.addAttribute(TextAttribute.FONT, font);
standardInit(as.getIterator(), text, frc);
}
| public TextLayout(String string, Map attributes, FontRenderContext frc)Constructs a TextLayout from a String
and an attribute set.
All the text is styled using the provided attributes.
string must specify a single paragraph of text because an
entire paragraph is required for the bidirectional algorithm.
if (string == null) {
throw new IllegalArgumentException("Null string passed to TextLayout constructor.");
}
if (attributes == null) {
throw new IllegalArgumentException("Null map passed to TextLayout constructor.");
}
if (string.length() == 0) {
throw new IllegalArgumentException("Zero length string passed to TextLayout constructor.");
}
char[] text = string.toCharArray();
Font font = singleFont(text, 0, text.length, attributes);
if (font != null) {
fastInit(text, font, attributes, frc);
} else {
AttributedString as = new AttributedString(string, attributes);
standardInit(as.getIterator(), text, frc);
}
| public TextLayout(AttributedCharacterIterator text, FontRenderContext frc)Constructs a TextLayout from an iterator over styled text.
The iterator must specify a single paragraph of text because an
entire paragraph is required for the bidirectional
algorithm.
if (text == null) {
throw new IllegalArgumentException("Null iterator passed to TextLayout constructor.");
}
int start = text.getBeginIndex();
int limit = text.getEndIndex();
if (start == limit) {
throw new IllegalArgumentException("Zero length iterator passed to TextLayout constructor.");
}
int len = limit - start;
text.first();
char[] chars = new char[len];
int n = 0;
for (char c = text.first(); c != text.DONE; c = text.next()) {
chars[n++] = c;
}
text.first();
if (text.getRunLimit() == limit) {
Map attributes = text.getAttributes();
Font font = singleFont(chars, 0, len, attributes);
if (font != null) {
fastInit(chars, font, attributes, frc);
return;
}
}
standardInit(text, chars, frc);
| TextLayout(TextLine textLine, byte baseline, float[] baselineOffsets, float justifyRatio)Creates a TextLayout from a {@link TextLine} and
some paragraph data. This method is used by {@link TextMeasurer}.
this.characterCount = textLine.characterCount();
this.baseline = baseline;
this.baselineOffsets = baselineOffsets;
this.textLine = textLine;
this.justifyRatio = justifyRatio;
|
Methods Summary |
---|
private java.awt.geom.GeneralPath | boundingShape(double[] path0, double[] path1)
// Really, we want the path to be a convex hull around all of the
// points in path0 and path1. But we can get by with less than
// that. We do need to prevent the two segments which
// join path0 to path1 from crossing each other. So, if we
// traverse path0 from top to bottom, we'll traverse path1 from
// bottom to top (and vice versa).
GeneralPath result = pathToShape(path0, false);
boolean sameDirection;
if (isVerticalLine) {
sameDirection = (path0[1] > path0[path0.length-1]) ==
(path1[1] > path1[path1.length-1]);
}
else {
sameDirection = (path0[0] > path0[path0.length-2]) ==
(path1[0] > path1[path1.length-2]);
}
int start;
int limit;
int increment;
if (sameDirection) {
start = path1.length-2;
limit = -2;
increment = -2;
}
else {
start = 0;
limit = path1.length;
increment = 2;
}
for (int i = start; i != limit; i += increment) {
result.lineTo((float)path1[i], (float)path1[i+1]);
}
result.closePath();
return result;
| private void | buildCache()
if (textLine == null) {
initTextLine();
}
lineMetrics = textLine.getMetrics();
// compute visibleAdvance
if (textLine.isDirectionLTR()) {
int lastNonSpace = characterCount-1;
while (lastNonSpace != -1) {
int logIndex = textLine.visualToLogical(lastNonSpace);
if (!textLine.isCharSpace(logIndex)) {
break;
}
else {
--lastNonSpace;
}
}
if (lastNonSpace == characterCount-1) {
visibleAdvance = lineMetrics.advance;
}
else if (lastNonSpace == -1) {
visibleAdvance = 0;
}
else {
int logIndex = textLine.visualToLogical(lastNonSpace);
visibleAdvance = textLine.getCharLinePosition(logIndex)
+ textLine.getCharAdvance(logIndex);
}
}
else {
int leftmostNonSpace = 0;
while (leftmostNonSpace != characterCount) {
int logIndex = textLine.visualToLogical(leftmostNonSpace);
if (!textLine.isCharSpace(logIndex)) {
break;
}
else {
++leftmostNonSpace;
}
}
if (leftmostNonSpace == characterCount) {
visibleAdvance = 0;
}
else if (leftmostNonSpace == 0) {
visibleAdvance = lineMetrics.advance;
}
else {
int logIndex = textLine.visualToLogical(leftmostNonSpace);
float pos = textLine.getCharLinePosition(logIndex);
visibleAdvance = lineMetrics.advance - pos;
}
}
// naturalBounds, boundsRect will be generated on demand
naturalBounds = null;
boundsRect = null;
// hashCode will be regenerated on demand
hashCodeCache = 0;
cacheIsValid = true;
| private java.awt.geom.GeneralPath | caretBoundingShape(int caret0, int caret1, java.awt.geom.Rectangle2D bounds)
if (caret0 > caret1) {
int temp = caret0;
caret0 = caret1;
caret1 = temp;
}
return boundingShape(getCaretPath(caret0, bounds, true),
getCaretPath(caret1, bounds, true));
| private boolean | caretIsValid(int caret)
if (caret == characterCount || caret == 0) {
return true;
}
int offset = textLine.visualToLogical(caret);
if (!textLine.isCharLTR(offset)) {
offset = textLine.visualToLogical(caret-1);
if (textLine.isCharLTR(offset)) {
return true;
}
}
// At this point, the leading edge of the character
// at offset is at the given caret.
return textLine.caretAtOffsetIsValid(offset);
| private java.awt.font.TextHitInfo | caretToHit(int caret)Given a caret index, return a hit whose caret is at the index.
The hit is NOT guaranteed to be strong!!!
if (caret == 0 || caret == characterCount) {
if ((caret == characterCount) == textLine.isDirectionLTR()) {
return TextHitInfo.leading(characterCount);
}
else {
return TextHitInfo.trailing(-1);
}
}
else {
int charIndex = textLine.visualToLogical(caret);
boolean leading = textLine.isCharLTR(charIndex);
return leading? TextHitInfo.leading(charIndex)
: TextHitInfo.trailing(charIndex);
}
| private float | caretToPointDistance(float[] caretInfo, float x, float y)Returns the distance from the point (x, y) to the caret along
the line direction defined in caretInfo . Distance is
negative if the point is to the left of the caret on a horizontal
line, or above the caret on a vertical line.
Utility for use by hitTestChar.
// distanceOffBaseline is negative if you're 'above' baseline
float lineDistance = isVerticalLine? y : x;
float distanceOffBaseline = isVerticalLine? -x : y;
return lineDistance - caretInfo[0] +
(distanceOffBaseline*caretInfo[1]);
| private void | checkTextHit(java.awt.font.TextHitInfo hit)
if (hit == null) {
throw new IllegalArgumentException("TextHitInfo is null.");
}
if (hit.getInsertionIndex() < 0 ||
hit.getInsertionIndex() > characterCount) {
throw new IllegalArgumentException("TextHitInfo is out of range");
}
| protected java.lang.Object | clone()Creates a copy of this TextLayout .
/*
* !!! 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();
}
| public void | draw(java.awt.Graphics2D g2, float x, float y)Renders this TextLayout at the specified location in
the specified {@link java.awt.Graphics2D 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.
if (g2 == null) {
throw new IllegalArgumentException("Null Graphics2D passed to TextLayout.draw()");
}
if (optInfo != null) {
if (optInfo.draw(g2, x, y)) { // might fail to draw because of frc change
return;
}
// replace with TextLine and fall through
initTextLine();
}
textLine.draw(g2, x - dx, y - dy);
| private void | ensureCache()
if (!cacheIsValid) {
buildCache();
}
| public boolean | equals(java.lang.Object obj)Returns true if the specified Object is a
TextLayout object and if the specified Object
equals this TextLayout .
return (obj instanceof TextLayout) && equals((TextLayout)obj);
| public boolean | equals(java.awt.font.TextLayout rhs)Returns true if the two layouts are equal.
Two layouts are equal if they contain equal glyphvectors in the same order.
if (rhs == null) {
return false;
}
if (rhs == this) {
return true;
}
ensureCache();
return textLine.equals(rhs.textLine);
| private void | fastInit(char[] chars, java.awt.Font font, java.util.Map attrs, java.awt.font.FontRenderContext frc)
// Object vf = attrs.get(TextAttribute.ORIENTATION);
// isVerticalLine = TextAttribute.ORIENTATION_VERTICAL.equals(vf);
isVerticalLine = false;
LineMetrics lm = font.getLineMetrics(chars, 0, chars.length, frc);
CoreMetrics cm = CoreMetrics.get(lm);
byte glyphBaseline = (byte) cm.baselineIndex;
if (attrs == null) {
baseline = glyphBaseline;
baselineOffsets = cm.baselineOffsets;
justifyRatio = 1.0f;
} else {
paragraphInit(glyphBaseline, cm, attrs, chars);
}
characterCount = chars.length;
optInfo = OptInfo.create(frc, chars, font, cm, attrs);
if (optInfo == null) {
textLine = TextLine.fastCreateTextLine(frc, chars, font, cm, attrs);
}
| public float | getAdvance()Returns the advance of this TextLayout .
The advance is the distance from the origin to the advance of the
rightmost (bottommost) character measuring in the line direction.
if (optInfo != null) {
try {
return optInfo.getAdvance();
}
catch (Error e) {
// cache was flushed under optInfo
}
}
ensureCache();
return lineMetrics.advance;
| public float | getAscent()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.
if (optInfo != null) {
return optInfo.getCoreMetrics().ascent;
}
ensureCache();
return lineMetrics.ascent;
| public byte | getBaseline()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.
return baseline;
| static byte | getBaselineFromGraphic(java.awt.font.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()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.
float[] offsets = new float[baselineOffsets.length];
System.arraycopy(baselineOffsets, 0, offsets, 0, offsets.length);
return offsets;
| public java.awt.Shape | getBlackBoxBounds(int firstEndpoint, int secondEndpoint)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 path may be disjoint.
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 translate = new AffineTransform();
translate.setToTranslation(dx, dy);
result = (GeneralPath) result.createTransformedShape(translate);
}
//return new Highlight(result, false);
return result;
| public java.awt.geom.Rectangle2D | getBounds()Returns the bounds of this TextLayout .
The bounds contains all of the pixels the TextLayout
can draw. It might not coincide exactly with the ascent, descent,
origin or advance of the TextLayout .
if (optInfo != null) {
return optInfo.getVisualBounds();
}
ensureCache();
if (boundsRect == null) {
Rectangle2D lineBounds = textLine.getBounds();
if (dx != 0 || dy != 0) {
lineBounds.setRect(lineBounds.getX() - dx,
lineBounds.getY() - dy,
lineBounds.getWidth(),
lineBounds.getHeight());
}
boundsRect = lineBounds;
}
Rectangle2D bounds = new Rectangle2D.Float();
bounds.setRect(boundsRect);
return bounds;
| private float[] | getCaretInfo(int caret, java.awt.geom.Rectangle2D bounds, float[] info)
float top1X, top2X;
float bottom1X, bottom2X;
if (caret == 0 || caret == characterCount) {
float pos;
int logIndex;
if (caret == characterCount) {
logIndex = textLine.visualToLogical(characterCount-1);
pos = textLine.getCharLinePosition(logIndex)
+ textLine.getCharAdvance(logIndex);
}
else {
logIndex = textLine.visualToLogical(caret);
pos = textLine.getCharLinePosition(logIndex);
}
float angle = textLine.getCharAngle(logIndex);
float shift = textLine.getCharShift(logIndex);
pos += angle * shift;
top1X = top2X = pos + angle*textLine.getCharAscent(logIndex);
bottom1X = bottom2X = pos - angle*textLine.getCharDescent(logIndex);
}
else {
{
int logIndex = textLine.visualToLogical(caret-1);
float angle1 = textLine.getCharAngle(logIndex);
float pos1 = textLine.getCharLinePosition(logIndex)
+ textLine.getCharAdvance(logIndex);
if (angle1 != 0) {
pos1 += angle1 * textLine.getCharShift(logIndex);
top1X = pos1 + angle1*textLine.getCharAscent(logIndex);
bottom1X = pos1 - angle1*textLine.getCharDescent(logIndex);
}
else {
top1X = bottom1X = pos1;
}
}
{
int logIndex = textLine.visualToLogical(caret);
float angle2 = textLine.getCharAngle(logIndex);
float pos2 = textLine.getCharLinePosition(logIndex);
if (angle2 != 0) {
pos2 += angle2*textLine.getCharShift(logIndex);
top2X = pos2 + angle2*textLine.getCharAscent(logIndex);
bottom2X = pos2 - angle2*textLine.getCharDescent(logIndex);
}
else {
top2X = bottom2X = pos2;
}
}
}
float topX = (top1X + top2X) / 2;
float bottomX = (bottom1X + bottom2X) / 2;
if (info == null) {
info = new float[2];
}
if (isVerticalLine) {
info[1] = (float) ((topX - bottomX) / bounds.getWidth());
info[0] = (float) (topX + (info[1]*bounds.getX()));
}
else {
info[1] = (float) ((topX - bottomX) / bounds.getHeight());
info[0] = (float) (bottomX + (info[1]*bounds.getMaxY()));
}
return info;
| public float[] | getCaretInfo(java.awt.font.TextHitInfo hit, java.awt.geom.Rectangle2D bounds)Returns information about the caret corresponding to hit .
The first element of the array is the intersection of the caret with
the baseline. The second element of the array is the inverse slope
(run/rise) of the caret.
This method is meant for informational use. To display carets, it
is better to use getCaretShapes .
ensureCache();
checkTextHit(hit);
return getCaretInfoTestInternal(hit, bounds);
| public float[] | getCaretInfo(java.awt.font.TextHitInfo hit)Returns information about the caret corresponding to hit .
This method is a convenience overload of getCaretInfo and
uses the natural bounds of this TextLayout .
return getCaretInfo(hit, getNaturalBounds());
| private float[] | getCaretInfoTestInternal(java.awt.font.TextHitInfo hit, java.awt.geom.Rectangle2D bounds)
ensureCache();
checkTextHit(hit);
float[] info = new float[6];
// get old data first
getCaretInfo(hitToCaret(hit), bounds, info);
// then add our new data
double iangle, ixbase, p1x, p1y, p2x, p2y;
int charix = hit.getCharIndex();
boolean lead = hit.isLeadingEdge();
boolean ltr = textLine.isDirectionLTR();
boolean horiz = !isVertical();
if (charix == -1 || charix == characterCount) {
// !!! note: want non-shifted, baseline ascent and descent here!
// TextLine should return appropriate line metrics object for these values
TextLineMetrics m = textLine.getMetrics();
boolean low = ltr == (charix == -1);
iangle = 0;
if (horiz) {
p1x = p2x = low ? 0 : m.advance;
p1y = -m.ascent;
p2y = m.descent;
} else {
p1y = p2y = low ? 0 : m.advance;
p1x = m.descent;
p2x = m.ascent;
}
} else {
CoreMetrics thiscm = textLine.getCoreMetricsAt(charix);
iangle = thiscm.italicAngle;
ixbase = textLine.getCharLinePosition(charix, lead);
if (thiscm.baselineIndex < 0) {
// this is a graphic, no italics, use entire line height for caret
TextLineMetrics m = textLine.getMetrics();
if (horiz) {
p1x = p2x = ixbase;
if (thiscm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
p1y = -m.ascent;
p2y = p1y + thiscm.height;
} else {
p2y = m.descent;
p1y = p2y - thiscm.height;
}
} else {
p1y = p2y = ixbase;
p1x = m.descent;
p2x = m.ascent;
// !!! top/bottom adjustment not implemented for vertical
}
} else {
float bo = baselineOffsets[thiscm.baselineIndex];
if (horiz) {
ixbase += iangle * thiscm.ssOffset;
p1x = ixbase + iangle * thiscm.ascent;
p2x = ixbase - iangle * thiscm.descent;
p1y = bo - thiscm.ascent;
p2y = bo + thiscm.descent;
} else {
ixbase -= iangle * thiscm.ssOffset;
p1y = ixbase + iangle * thiscm.ascent;
p2y = ixbase - iangle * thiscm.descent;
p1x = bo + thiscm.ascent;
p2x = bo + thiscm.descent;
}
}
}
info[2] = (float)p1x;
info[3] = (float)p1y;
info[4] = (float)p2x;
info[5] = (float)p2y;
return info;
| private double[] | getCaretPath(java.awt.font.TextHitInfo hit, java.awt.geom.Rectangle2D bounds)
float[] info = getCaretInfo(hit, bounds);
return new double[] { info[2], info[3], info[4], info[5] };
| private double[] | getCaretPath(int caret, java.awt.geom.Rectangle2D bounds, boolean clipToBounds)Return an array of four floats corresponding the endpoints of the caret
x0, y0, x1, y1.
This creates a line along the slope of the caret intersecting the
baseline at the caret
position, and extending from ascent above the baseline to descent below
it.
float[] info = getCaretInfo(caret, bounds, null);
double pos = info[0];
double slope = info[1];
double x0, y0, x1, y1;
double x2 = -3141.59, y2 = -2.7; // values are there to make compiler happy
double left = bounds.getX();
double right = left + bounds.getWidth();
double top = bounds.getY();
double bottom = top + bounds.getHeight();
boolean threePoints = false;
if (isVerticalLine) {
if (slope >= 0) {
x0 = left;
x1 = right;
}
else {
x1 = left;
x0 = right;
}
y0 = pos + x0 * slope;
y1 = pos + x1 * slope;
// y0 <= y1, always
if (clipToBounds) {
if (y0 < top) {
if (slope <= 0 || y1 <= top) {
y0 = y1 = top;
}
else {
threePoints = true;
y0 = top;
y2 = top;
x2 = x1 + (top-y1)/slope;
if (y1 > bottom) {
y1 = bottom;
}
}
}
else if (y1 > bottom) {
if (slope >= 0 || y0 >= bottom) {
y0 = y1 = bottom;
}
else {
threePoints = true;
y1 = bottom;
y2 = bottom;
x2 = x0 + (bottom-x1)/slope;
}
}
}
}
else {
if (slope >= 0) {
y0 = bottom;
y1 = top;
}
else {
y1 = bottom;
y0 = top;
}
x0 = pos - y0 * slope;
x1 = pos - y1 * slope;
// x0 <= x1, always
if (clipToBounds) {
if (x0 < left) {
if (slope <= 0 || x1 <= left) {
x0 = x1 = left;
}
else {
threePoints = true;
x0 = left;
x2 = left;
y2 = y1 - (left-x1)/slope;
if (x1 > right) {
x1 = right;
}
}
}
else if (x1 > right) {
if (slope >= 0 || x0 >= right) {
x0 = x1 = right;
}
else {
threePoints = true;
x1 = right;
x2 = right;
y2 = y0 - (right-x0)/slope;
}
}
}
}
return threePoints?
new double[] { x0, y0, x2, y2, x1, y1 } :
new double[] { x0, y0, x1, y1 };
| public java.awt.Shape | getCaretShape(java.awt.font.TextHitInfo hit, java.awt.geom.Rectangle2D bounds)Returns a {@link Shape} representing the caret at the specified
hit inside the specified bounds.
ensureCache();
checkTextHit(hit);
if (bounds == null) {
throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCaret()");
}
// int hitCaret = hitToCaret(hit);
// GeneralPath hitShape =
// pathToShape(getCaretPath(hitCaret, bounds, false), false);
return pathToShape(getCaretPath(hit, bounds), false);
//return new Highlight(hitShape, true);
// return hitShape;
| public java.awt.Shape | getCaretShape(java.awt.font.TextHitInfo hit)Returns a Shape representing the caret at the specified
hit inside the natural bounds of this TextLayout .
return getCaretShape(hit, getNaturalBounds());
| public java.awt.Shape[] | getCaretShapes(int offset, java.awt.geom.Rectangle2D bounds, java.awt.font.TextLayout$CaretPolicy policy)Returns two paths corresponding to the strong and weak caret.
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);
// Shape hitShape =
// pathToShape(getCaretPath(hitCaret, bounds, false), false);
Shape hitShape = pathToShape(getCaretPath(hit, bounds), false);
TextHitInfo otherHit = hit.getOtherHit();
int otherCaret = hitToCaret(otherHit);
if (hitCaret == otherCaret) {
result[0] = hitShape;
}
else { // more than one caret
// Shape otherShape =
// pathToShape(getCaretPath(otherCaret, bounds, false), false);
Shape otherShape = pathToShape(getCaretPath(otherHit, bounds), false);
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;
| public java.awt.Shape[] | getCaretShapes(int offset, java.awt.geom.Rectangle2D bounds)Returns two paths corresponding to the strong and weak caret.
This method is a convenience overload of getCaretShapes
that uses the default caret policy.
// {sfb} parameter checking is done in overloaded version
return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
| public java.awt.Shape[] | getCaretShapes(int offset)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.
// {sfb} parameter checking is done in overloaded version
return getCaretShapes(offset, getNaturalBounds(), DEFAULT_CARET_POLICY);
| public int | getCharacterCount()Returns the number of characters represented by this
TextLayout .
return characterCount;
| public byte | getCharacterLevel(int index)Returns the level of the character at index .
Indices -1 and characterCount are assigned the base
level of this TextLayout .
// hmm, allow indices at endpoints? For now, yes.
if (index < -1 || index > characterCount) {
throw new IllegalArgumentException("Index is out of range in getCharacterLevel.");
}
if (optInfo != null) {
return 0;
}
ensureCache();
if (index == -1 || index == characterCount) {
return (byte) (textLine.isDirectionLTR()? 0 : 1);
}
return textLine.getCharLevel(index);
| public float | getDescent()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.
if (optInfo != null) {
return optInfo.getCoreMetrics().descent;
}
ensureCache();
return lineMetrics.descent;
| public java.awt.font.TextLayout | getJustifiedLayout(float justificationWidth)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.
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;
| public float | getLeading()Returns the leading of the TextLayout .
The leading is the suggested interline spacing for this
TextLayout .
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;
if (optInfo != null) {
return optInfo.getCoreMetrics().leading;
}
ensureCache();
return lineMetrics.leading;
| public java.awt.Shape | getLogicalHighlightShape(int firstEndpoint, int secondEndpoint, java.awt.geom.Rectangle2D bounds)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 .
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)) {
result.append(leftShape(bounds), false);
}
if ((textLine.isDirectionLTR() && secondEndpoint == characterCount) ||
(!textLine.isDirectionLTR() && firstEndpoint == 0)) {
result.append(rightShape(bounds), false);
}
}
return result;
| public java.awt.Shape | getLogicalHighlightShape(int firstEndpoint, int secondEndpoint)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 .
return getLogicalHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
| public int[] | getLogicalRangesForVisualSelection(java.awt.font.TextHitInfo firstEndpoint, java.awt.font.TextHitInfo secondEndpoint)Returns the logical ranges of text corresponding to a visual selection.
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;
| private java.awt.geom.Rectangle2D | getNaturalBounds()The 'natural bounds' encloses all the carets the layout can draw.
ensureCache();
if (naturalBounds == null) {
naturalBounds = textLine.getItalicBounds();
}
return naturalBounds;
| public java.awt.font.TextHitInfo | getNextLeftHit(java.awt.font.TextHitInfo hit)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.
ensureCache();
checkTextHit(hit);
int caret = hitToCaret(hit);
if (caret == 0) {
return null;
}
do {
--caret;
} while(!caretIsValid(caret));
return caretToHit(caret);
| public java.awt.font.TextHitInfo | getNextLeftHit(int offset, java.awt.font.TextLayout$CaretPolicy 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
specified policy.
The returned hit is the stronger of the two possible
hits, as determined by the specified 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;
}
| public java.awt.font.TextHitInfo | getNextLeftHit(int offset)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.
return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
| public java.awt.font.TextHitInfo | getNextRightHit(java.awt.font.TextHitInfo hit)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
{@link IllegalArgumentException} is thrown.
ensureCache();
checkTextHit(hit);
int caret = hitToCaret(hit);
if (caret == characterCount) {
return null;
}
do {
++caret;
} while (!caretIsValid(caret));
return caretToHit(caret);
| public java.awt.font.TextHitInfo | getNextRightHit(int offset, java.awt.font.TextLayout$CaretPolicy 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
specified policy.
The returned hit is the stronger of the two possible
hits, as determined by the specified 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;
}
| public java.awt.font.TextHitInfo | getNextRightHit(int offset)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.
return getNextRightHit(offset, DEFAULT_CARET_POLICY);
| public java.awt.Shape | getOutline(java.awt.geom.AffineTransform tx)Returns a Shape representing the outline of this
TextLayout .
ensureCache();
return textLine.getOutline(tx);
| private final java.awt.font.TextHitInfo | getStrongHit(java.awt.font.TextHitInfo hit1, java.awt.font.TextHitInfo hit2)Return the "stronger" of the TextHitInfos. The TextHitInfos
should be logical or visual counterparts. They are not
checked for validity.
// right now we're using the following rule for strong hits:
// A hit on a character with a lower level
// is stronger than one on a character with a higher level.
// If this rule ties, the hit on the leading edge of a character wins.
// If THIS rule ties, hit1 wins. Both rules shouldn't tie, unless the
// infos aren't counterparts of some sort.
byte hit1Level = getCharacterLevel(hit1.getCharIndex());
byte hit2Level = getCharacterLevel(hit2.getCharIndex());
if (hit1Level == hit2Level) {
if (hit2.isLeadingEdge() && !hit1.isLeadingEdge()) {
return hit2;
}
else {
return hit1;
}
}
else {
return (hit1Level < hit2Level)? hit1 : hit2;
}
| java.awt.font.TextLine | getTextLineForTesting()Package-only method for testing ONLY. Please don't abuse.
return textLine;
| public float | getVisibleAdvance()Returns the advance of this TextLayout , minus trailing
whitespace.
ensureCache();
return visibleAdvance;
| public java.awt.Shape | getVisualHighlightShape(java.awt.font.TextHitInfo firstEndpoint, java.awt.font.TextHitInfo secondEndpoint, java.awt.geom.Rectangle2D bounds)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.
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) {
result.append(leftShape(bounds), false);
}
if (firstCaret == characterCount || secondCaret == characterCount) {
result.append(rightShape(bounds), false);
}
//return new Highlight(result, false);
return result;
| public java.awt.Shape | getVisualHighlightShape(java.awt.font.TextHitInfo firstEndpoint, java.awt.font.TextHitInfo secondEndpoint)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 .
return getVisualHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
| public java.awt.font.TextHitInfo | getVisualOtherHit(java.awt.font.TextHitInfo hit)Returns the hit on the opposite side of the specified hit's caret.
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);
| protected void | handleJustify(float justificationWidth)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.
// never called
| public int | hashCode()Returns the hash code of this TextLayout .
if (hashCodeCache == 0) {
ensureCache();
hashCodeCache = textLine.hashCode();
}
return hashCodeCache;
| public java.awt.font.TextHitInfo | hitTestChar(float x, float y, java.awt.geom.Rectangle2D bounds)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.
// check boundary conditions
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;
| public java.awt.font.TextHitInfo | hitTestChar(float x, float y)Returns a TextHitInfo corresponding to the
specified point. This method is a convenience overload of
hitTestChar that uses the natural bounds of this
TextLayout .
return hitTestChar(x, y, getNaturalBounds());
| private int | hitToCaret(java.awt.font.TextHitInfo hit)Returns a caret index corresponding to hit .
Carets are numbered from left to right (top to bottom) starting from
zero. This always places carets next to the character hit, on the
indicated side of the character.
int hitIndex = hit.getCharIndex();
if (hitIndex < 0) {
return textLine.isDirectionLTR() ? 0 : characterCount;
} else if (hitIndex >= characterCount) {
return textLine.isDirectionLTR() ? characterCount : 0;
}
int visIndex = textLine.logicalToVisual(hitIndex);
if (hit.isLeadingEdge() != textLine.isCharLTR(hitIndex)) {
++visIndex;
}
return visIndex;
| private void | initTextLine()
textLine = optInfo.createTextLine();
optInfo = null;
| public boolean | isLeftToRight()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.
return (optInfo != null) || textLine.isDirectionLTR();
| public boolean | isVertical()Returns true if this TextLayout is vertical.
return isVerticalLine;
| private java.awt.geom.GeneralPath | leftShape(java.awt.geom.Rectangle2D bounds)
double[] path0;
if (isVerticalLine) {
path0 = new double[] { bounds.getX(), bounds.getY(),
bounds.getX() + bounds.getWidth(),
bounds.getY() };
} else {
path0 = new double[] { bounds.getX(),
bounds.getY() + bounds.getHeight(),
bounds.getX(), bounds.getY() };
}
double[] path1 = getCaretPath(0, bounds, true);
return boundingShape(path0, path1);
| private void | paragraphInit(byte aBaseline, sun.font.CoreMetrics lm, java.util.Map paragraphAttrs, char[] text)Initialize the paragraph-specific data.
baseline = aBaseline;
// normalize to current baseline
baselineOffsets = TextLine.getNormalizedOffsets(lm.baselineOffsets, baseline);
justifyRatio = TextLine.getJustifyRatio(paragraphAttrs);
if (paragraphAttrs != null) {
Object o = paragraphAttrs.get(TextAttribute.NUMERIC_SHAPING);
if (o != null) {
try {
NumericShaper shaper = (NumericShaper)o;
shaper.shape(text, 0, text.length);
}
catch (ClassCastException e) {
}
}
}
| private static java.awt.geom.GeneralPath | pathToShape(double[] path, boolean close)
GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD, path.length);
result.moveTo((float)path[0], (float)path[1]);
for (int i = 2; i < path.length; i += 2) {
result.lineTo((float)path[i], (float)path[i+1]);
}
if (close) {
result.closePath();
}
return result;
| private java.awt.geom.GeneralPath | rightShape(java.awt.geom.Rectangle2D bounds)
double[] path1;
if (isVerticalLine) {
path1 = new double[] {
bounds.getX(),
bounds.getY() + bounds.getHeight(),
bounds.getX() + bounds.getWidth(),
bounds.getY() + bounds.getHeight()
};
} else {
path1 = new double[] {
bounds.getX() + bounds.getWidth(),
bounds.getY() + bounds.getHeight(),
bounds.getX() + bounds.getWidth(),
bounds.getY()
};
}
double[] path0 = getCaretPath(characterCount, bounds, true);
return boundingShape(path0, path1);
| private static int | sameBaselineUpTo(java.awt.Font font, char[] text, int start, int limit)Return the index of the first character with a different baseline from the
character at start, or limit if all characters between start and limit have
the same baseline.
// current implementation doesn't support multiple baselines
return limit;
/*
byte bl = font.getBaselineFor(text[start++]);
while (start < limit && font.getBaselineFor(text[start]) == bl) {
++start;
}
return start;
*/
| private static java.awt.Font | singleFont(char[] text, int start, int limit, java.util.Map attributes)
if (attributes.get(TextAttribute.CHAR_REPLACEMENT) != null) {
return null;
}
Font font = (Font)attributes.get(TextAttribute.FONT);
if (font == null) {
if (attributes.get(TextAttribute.FAMILY) != null) {
font = Font.getFont(attributes);
if (font.canDisplayUpTo(text, start, limit) != -1) {
return null;
}
} else {
FontResolver resolver = FontResolver.getInstance();
CodePointIterator iter = CodePointIterator.create(text, start, limit);
int fontIndex = resolver.nextFontRunIndex(iter);
if (iter.charIndex() == limit) {
font = resolver.getFont(fontIndex, attributes);
}
}
}
if (sameBaselineUpTo(font, text, start, limit) != limit) {
return null;
}
return font;
| private void | standardInit(java.text.AttributedCharacterIterator text, char[] chars, java.awt.font.FontRenderContext frc)
characterCount = chars.length;
// 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.
Map paragraphAttrs = text.getAttributes();
boolean haveFont = TextLine.advanceToFirstFont(text);
if (haveFont) {
Font defaultFont = TextLine.getFontAtCurrentPos(text);
int charsStart = text.getIndex() - text.getBeginIndex();
LineMetrics lm = defaultFont.getLineMetrics(chars, charsStart, charsStart+1, frc);
CoreMetrics cm = CoreMetrics.get(lm);
paragraphInit((byte)cm.baselineIndex, cm, paragraphAttrs, chars);
}
else {
// hmmm what to do here? Just try to supply reasonable
// values I guess.
GraphicAttribute graphic = (GraphicAttribute)
paragraphAttrs.get(TextAttribute.CHAR_REPLACEMENT);
byte defaultBaseline = getBaselineFromGraphic(graphic);
CoreMetrics cm = GraphicComponent.createCoreMetrics(graphic);
paragraphInit(defaultBaseline, cm, paragraphAttrs, chars);
}
}
textLine = TextLine.standardCreateTextLine(frc, text, chars, baselineOffsets);
| public java.lang.String | toString()Returns debugging information for this TextLayout .
ensureCache();
return textLine.toString();
|
|