blob: cc6f0ba3af61dbf8539bb01b529e0ab80bbf6e0e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Oleg V. Khaschansky
* @version $Revision$
*/
package java.awt.font;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.geom.GeneralPath;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.Map;
import org.apache.harmony.awt.gl.font.BasicMetrics;
import org.apache.harmony.awt.gl.font.CaretManager;
import org.apache.harmony.awt.gl.font.TextMetricsCalculator;
import org.apache.harmony.awt.gl.font.TextRunBreaker;
import org.apache.harmony.awt.internal.nls.Messages;
/**
* The TextLayout class defines the graphical representation of character data.
* This class provides method for obtaining information about cursor positioning
* and movement, split cursors for text with different directions, logical and
* visual highlighting, multiple baselines, hits, justification, ascent,
* descent, and advance, and rendering. A TextLayout object can be rendered
* using Graphics context.
*
* @since Android 1.0
*/
public final class TextLayout implements Cloneable {
/**
* The CaretPolicy class provides a policy for obtaining the caret location.
* The single getStrongCaret method specifies the policy.
*/
public static class CaretPolicy {
/**
* Instantiates a new CaretPolicy.
*/
public CaretPolicy() {
// Nothing to do
}
/**
* Returns whichever of the two specified TextHitInfo objects has the
* stronger caret (higher character level) in the specified TextLayout.
*
* @param hit1
* the first TextHitInfo of the specified TextLayout.
* @param hit2
* the second TextHitInfo of the specified TextLayout.
* @param layout
* the TextLayout.
* @return the TextHitInfo with the stronger caret.
*/
public TextHitInfo getStrongCaret(TextHitInfo hit1, TextHitInfo hit2, TextLayout layout) {
// Stronger hit is the one with greater level.
// If the level is same, leading edge is stronger.
int level1 = layout.getCharacterLevel(hit1.getCharIndex());
int level2 = layout.getCharacterLevel(hit2.getCharIndex());
if (level1 == level2) {
return (hit2.isLeadingEdge() && (!hit1.isLeadingEdge())) ? hit2 : hit1;
}
return level1 > level2 ? hit1 : hit2;
}
}
/**
* The Constant DEFAULT_CARET_POLICY indicates the default caret policy.
*/
public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
/**
* The breaker.
*/
private TextRunBreaker breaker;
/**
* The metrics valid.
*/
private boolean metricsValid = false;
/**
* The tmc.
*/
private TextMetricsCalculator tmc;
/**
* The metrics.
*/
private BasicMetrics metrics;
/**
* The caret manager.
*/
private CaretManager caretManager;
/**
* The justification width.
*/
float justificationWidth = -1;
/**
* Instantiates a new TextLayout object from the specified string and Font.
*
* @param string
* the string to be displayed.
* @param font
* the font of the text.
* @param frc
* the FontRenderContext object for obtaining information about a
* graphics device.
*/
public TextLayout(String string, Font font, FontRenderContext frc) {
if (string == null) {
// awt.01='{0}' parameter is null
throw new IllegalArgumentException(Messages.getString("awt.01", "string")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (font == null) {
// awt.01='{0}' parameter is null
throw new IllegalArgumentException(Messages.getString("awt.01", "font")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (string.length() == 0) {
// awt.02='{0}' parameter has zero length
throw new IllegalArgumentException(Messages.getString("awt.02", "string")); //$NON-NLS-1$ //$NON-NLS-2$
}
AttributedString as = new AttributedString(string);
as.addAttribute(TextAttribute.FONT, font);
this.breaker = new TextRunBreaker(as.getIterator(), frc);
caretManager = new CaretManager(breaker);
}
/**
* Instantiates a new TextLayout from the specified text and a map of
* attributes.
*
* @param string
* the string to be displayed.
* @param attributes
* the attributes to be used for obtaining the text style.
* @param frc
* the FontRenderContext object for obtaining information about a
* graphics device.
*/
public TextLayout(String string,
Map<? extends java.text.AttributedCharacterIterator.Attribute, ?> attributes,
FontRenderContext frc) {
if (string == null) {
// awt.01='{0}' parameter is null
throw new IllegalArgumentException(Messages.getString("awt.01", "string")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (attributes == null) {
// awt.01='{0}' parameter is null
throw new IllegalArgumentException(Messages.getString("awt.01", "attributes")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (string.length() == 0) {
// awt.02='{0}' parameter has zero length
throw new IllegalArgumentException(Messages.getString("awt.02", "string")); //$NON-NLS-1$ //$NON-NLS-2$
}
AttributedString as = new AttributedString(string);
as.addAttributes(attributes, 0, string.length());
this.breaker = new TextRunBreaker(as.getIterator(), frc);
caretManager = new CaretManager(breaker);
}
/**
* Instantiates a new TextLayout from the AttributedCharacterIterator.
*
* @param text
* the AttributedCharacterIterator.
* @param frc
* the FontRenderContext object for obtaining information about a
* graphics device.
*/
public TextLayout(AttributedCharacterIterator text, FontRenderContext frc) {
if (text == null) {
// awt.03='{0}' iterator parameter is null
throw new IllegalArgumentException(Messages.getString("awt.03", "text")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (text.getBeginIndex() == text.getEndIndex()) {
// awt.04='{0}' iterator parameter has zero length
throw new IllegalArgumentException(Messages.getString("awt.04", "text")); //$NON-NLS-1$ //$NON-NLS-2$
}
this.breaker = new TextRunBreaker(text, frc);
caretManager = new CaretManager(breaker);
}
/**
* Instantiates a new text layout.
*
* @param breaker
* the breaker.
*/
TextLayout(TextRunBreaker breaker) {
this.breaker = breaker;
caretManager = new CaretManager(this.breaker);
}
/**
* Returns a hash code of this TextLayout object.
*
* @return a hash code of this TextLayout object.
*/
@Override
public int hashCode() {
return breaker.hashCode();
}
/**
* Returns a copy of this object.
*
* @return a copy of this object.
*/
@Override
protected Object clone() {
TextLayout res = new TextLayout((TextRunBreaker)breaker.clone());
if (justificationWidth >= 0) {
res.handleJustify(justificationWidth);
}
return res;
}
/**
* Compares this TextLayout object to the specified TextLayout object.
*
* @param layout
* the TextLayout object to be compared.
* @return true, if this TextLayout object is equal to the specified
* TextLayout object, false otherwise.
*/
public boolean equals(TextLayout layout) {
if (layout == null) {
return false;
}
return this.breaker.equals(layout.breaker);
}
/**
* Compares this TextLayout object to the specified Object.
*
* @param obj
* the Object to be compared.
* @return true, if this TextLayout object is equal to the specified Object,
* false otherwise.
*/
@Override
public boolean equals(Object obj) {
return obj instanceof TextLayout ? equals((TextLayout)obj) : false;
}
/**
* Gets the string representation for this TextLayout.
*
* @return the string representation for this TextLayout.
*/
@Override
public String toString() { // what for?
return super.toString();
}
/**
* Draws this TextLayout at the specified location with the specified
* Graphics2D context.
*
* @param g2d
* the Graphics2D object which renders this TextLayout.
* @param x
* the X coordinate of the TextLayout origin.
* @param y
* the Y coordinate of the TextLayout origin.
*/
public void draw(Graphics2D g2d, float x, float y) {
updateMetrics();
breaker.drawSegments(g2d, x, y);
}
/**
* Update metrics.
*/
private void updateMetrics() {
if (!metricsValid) {
breaker.createAllSegments();
tmc = new TextMetricsCalculator(breaker);
metrics = tmc.createMetrics();
metricsValid = true;
}
}
/**
* Gets the advance of this TextLayout object.
*
* @return the advance of this TextLayout object.
*/
public float getAdvance() {
updateMetrics();
return metrics.getAdvance();
}
/**
* Gets the ascent of this TextLayout object.
*
* @return the ascent of this TextLayout object.
*/
public float getAscent() {
updateMetrics();
return metrics.getAscent();
}
/**
* Gets the baseline of this TextLayout object.
*
* @return the baseline of this TextLayout object.
*/
public byte getBaseline() {
updateMetrics();
return (byte)metrics.getBaseLineIndex();
}
/**
* Gets the float array of offsets for the baselines which are used in this
* TextLayout.
*
* @return the float array of offsets for the baselines which are used in
* this TextLayout.
*/
public float[] getBaselineOffsets() {
updateMetrics();
return tmc.getBaselineOffsets();
}
/**
* Gets the black box bounds of the characters in the specified area. The
* black box bounds is an Shape which contains all bounding boxes of all the
* glyphs of the characters between firstEndpoint and secondEndpoint
* parameters values.
*
* @param firstEndpoint
* the first point of the area.
* @param secondEndpoint
* the second point of the area.
* @return the Shape which contains black box bounds.
*/
public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) {
updateMetrics();
if (firstEndpoint < secondEndpoint) {
return breaker.getBlackBoxBounds(firstEndpoint, secondEndpoint);
}
return breaker.getBlackBoxBounds(secondEndpoint, firstEndpoint);
}
/**
* Gets the bounds of this TextLayout.
*
* @return the bounds of this TextLayout.
*/
public Rectangle2D getBounds() {
updateMetrics();
return breaker.getVisualBounds();
}
/**
* Gets information about the caret of the specified TextHitInfo.
*
* @param hitInfo
* the TextHitInfo.
* @return the information about the caret of the specified TextHitInfo.
*/
public float[] getCaretInfo(TextHitInfo hitInfo) {
updateMetrics();
return caretManager.getCaretInfo(hitInfo);
}
/**
* Gets information about the caret of the specified TextHitInfo of a
* character in this TextLayout.
*
* @param hitInfo
* the TextHitInfo of a character in this TextLayout.
* @param bounds
* the bounds to which the caret info is constructed.
* @return the caret of the specified TextHitInfo.
*/
public float[] getCaretInfo(TextHitInfo hitInfo, Rectangle2D bounds) {
updateMetrics();
return caretManager.getCaretInfo(hitInfo);
}
/**
* Gets a Shape which represents the caret of the specified TextHitInfo in
* the bounds of this TextLayout.
*
* @param hitInfo
* the TextHitInfo.
* @param bounds
* the bounds to which the caret info is constructed.
* @return the Shape which represents the caret.
*/
public Shape getCaretShape(TextHitInfo hitInfo, Rectangle2D bounds) {
updateMetrics();
return caretManager.getCaretShape(hitInfo, this);
}
/**
* Gets a Shape which represents the caret of the specified TextHitInfo in
* the bounds of this TextLayout.
*
* @param hitInfo
* the TextHitInfo.
* @return the Shape which represents the caret.
*/
public Shape getCaretShape(TextHitInfo hitInfo) {
updateMetrics();
return caretManager.getCaretShape(hitInfo, this);
}
/**
* Gets two Shapes for the strong and weak carets with default caret policy
* and null bounds: the first element is the strong caret, the second is the
* weak caret or null.
*
* @param offset
* an offset in the TextLayout.
* @return an array of two Shapes corresponded to the strong and weak
* carets.
*/
public Shape[] getCaretShapes(int offset) {
return getCaretShapes(offset, null, TextLayout.DEFAULT_CARET_POLICY);
}
/**
* Gets two Shapes for the strong and weak carets with the default caret
* policy: the first element is the strong caret, the second is the weak
* caret or null.
*
* @param offset
* an offset in the TextLayout.
* @param bounds
* the bounds to which to extend the carets.
* @return an array of two Shapes corresponded to the strong and weak
* carets.
*/
public Shape[] getCaretShapes(int offset, Rectangle2D bounds) {
return getCaretShapes(offset, bounds, TextLayout.DEFAULT_CARET_POLICY);
}
/**
* Gets two Shapes for the strong and weak carets: the first element is the
* strong caret, the second is the weak caret or null.
*
* @param offset
* an offset in the TextLayout.
* @param bounds
* the bounds to which to extend the carets.
* @param policy
* the specified CaretPolicy.
* @return an array of two Shapes corresponded to the strong and weak
* carets.
*/
public Shape[] getCaretShapes(int offset, Rectangle2D bounds, TextLayout.CaretPolicy policy) {
if (offset < 0 || offset > breaker.getCharCount()) {
// awt.195=Offset is out of bounds
throw new IllegalArgumentException(Messages.getString("awt.195")); //$NON-NLS-1$
}
updateMetrics();
return caretManager.getCaretShapes(offset, bounds, policy, this);
}
/**
* Gets the number of characters in this TextLayout.
*
* @return the number of characters in this TextLayout.
*/
public int getCharacterCount() {
return breaker.getCharCount();
}
/**
* Gets the level of the character with the specified index.
*
* @param index
* the specified index of the character.
* @return the level of the character.
*/
public byte getCharacterLevel(int index) {
if (index == -1 || index == getCharacterCount()) {
return (byte)breaker.getBaseLevel();
}
return breaker.getLevel(index);
}
/**
* Gets the descent of this TextLayout.
*
* @return the descent of this TextLayout.
*/
public float getDescent() {
updateMetrics();
return metrics.getDescent();
}
/**
* Gets the TextLayout wich is justified with the specified width related to
* this TextLayout.
*
* @param justificationWidth
* the width which is used for justification.
* @return a TextLayout justified to the specified width.
* @throws Error
* the error occures if this TextLayout has been already
* justified.
*/
public TextLayout getJustifiedLayout(float justificationWidth) throws Error {
float justification = breaker.getJustification();
if (justification < 0) {
// awt.196=Justification impossible, layout already justified
throw new Error(Messages.getString("awt.196")); //$NON-NLS-1$
} else if (justification == 0) {
return this;
}
TextLayout justifiedLayout = new TextLayout((TextRunBreaker)breaker.clone());
justifiedLayout.handleJustify(justificationWidth);
return justifiedLayout;
}
/**
* Gets the leading of this TextLayout.
*
* @return the leading of this TextLayout.
*/
public float getLeading() {
updateMetrics();
return metrics.getLeading();
}
/**
* Gets a Shape representing the logical selection betweeen the specified
* endpoints and extended to the natural bounds of this TextLayout.
*
* @param firstEndpoint
* the first selected endpoint within the area of characters
* @param secondEndpoint
* the second selected endpoint within the area of characters
* @return a Shape represented the logical selection betweeen the specified
* endpoints.
*/
public Shape getLogicalHighlightShape(int firstEndpoint, int secondEndpoint) {
updateMetrics();
return getLogicalHighlightShape(firstEndpoint, secondEndpoint, breaker.getLogicalBounds());
}
/**
* Gets a Shape representing the logical selection betweeen the specified
* endpoints and extended to the specified bounds of this TextLayout.
*
* @param firstEndpoint
* the first selected endpoint within the area of characters
* @param secondEndpoint
* the second selected endpoint within the area of characters
* @param bounds
* the specified bounds of this TextLayout.
* @return a Shape represented the logical selection betweeen the specified
* endpoints.
*/
public Shape getLogicalHighlightShape(int firstEndpoint, int secondEndpoint, Rectangle2D bounds) {
updateMetrics();
if (firstEndpoint > secondEndpoint) {
if (secondEndpoint < 0 || firstEndpoint > breaker.getCharCount()) {
// awt.197=Endpoints are out of range
throw new IllegalArgumentException(Messages.getString("awt.197")); //$NON-NLS-1$
}
return caretManager.getLogicalHighlightShape(secondEndpoint, firstEndpoint, bounds,
this);
}
if (firstEndpoint < 0 || secondEndpoint > breaker.getCharCount()) {
// awt.197=Endpoints are out of range
throw new IllegalArgumentException(Messages.getString("awt.197")); //$NON-NLS-1$
}
return caretManager.getLogicalHighlightShape(firstEndpoint, secondEndpoint, bounds, this);
}
/**
* Gets the logical ranges of text which corresponds to a visual selection.
*
* @param hit1
* the first endpoint of the visual range.
* @param hit2
* the second endpoint of the visual range.
* @return the logical ranges of text which corresponds to a visual
* selection.
*/
public int[] getLogicalRangesForVisualSelection(TextHitInfo hit1, TextHitInfo hit2) {
return caretManager.getLogicalRangesForVisualSelection(hit1, hit2);
}
/**
* Gets the TextHitInfo for the next caret to the left (or up at the end of
* the line) of the specified offset.
*
* @param offset
* the offset in this TextLayout.
* @return the TextHitInfo for the next caret to the left (or up at the end
* of the line) of the specified hit, or null if there is no hit.
*/
public TextHitInfo getNextLeftHit(int offset) {
return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
}
/**
* Gets the TextHitInfo for the next caret to the left (or up at the end of
* the line) of the specified hit.
*
* @param hitInfo
* the initial hit.
* @return the TextHitInfo for the next caret to the left (or up at the end
* of the line) of the specified hit, or null if there is no hit.
*/
public TextHitInfo getNextLeftHit(TextHitInfo hitInfo) {
breaker.createAllSegments();
return caretManager.getNextLeftHit(hitInfo);
}
/**
* Gets the TextHitInfo for the next caret to the left (or up at the end of
* the line) of the specified offset, given the specified caret policy.
*
* @param offset
* the offset in this TextLayout.
* @param policy
* the policy to be used for obtaining the strong caret.
* @return the TextHitInfo for the next caret to the left of the specified
* offset, or null if there is no hit.
*/
public TextHitInfo getNextLeftHit(int offset, TextLayout.CaretPolicy policy) {
if (offset < 0 || offset > breaker.getCharCount()) {
// awt.195=Offset is out of bounds
throw new IllegalArgumentException(Messages.getString("awt.195")); //$NON-NLS-1$
}
TextHitInfo hit = TextHitInfo.afterOffset(offset);
TextHitInfo strongHit = policy.getStrongCaret(hit, hit.getOtherHit(), this);
TextHitInfo nextLeftHit = getNextLeftHit(strongHit);
if (nextLeftHit != null) {
return policy.getStrongCaret(getVisualOtherHit(nextLeftHit), nextLeftHit, this);
}
return null;
}
/**
* Gets the TextHitInfo for the next caret to the right (or down at the end
* of the line) of the specified hit.
*
* @param hitInfo
* the initial hit.
* @return the TextHitInfo for the next caret to the right (or down at the
* end of the line) of the specified hit, or null if there is no
* hit.
*/
public TextHitInfo getNextRightHit(TextHitInfo hitInfo) {
breaker.createAllSegments();
return caretManager.getNextRightHit(hitInfo);
}
/**
* Gets the TextHitInfo for the next caret to the right (or down at the end
* of the line) of the specified offset.
*
* @param offset
* the offset in this TextLayout.
* @return the TextHitInfo for the next caret to the right of the specified
* offset, or null if there is no hit.
*/
public TextHitInfo getNextRightHit(int offset) {
return getNextRightHit(offset, DEFAULT_CARET_POLICY);
}
/**
* Gets the TextHitInfo for the next caret to the right (or down at the end
* of the line) of the specified offset, given the specified caret policy.
*
* @param offset
* the offset in this TextLayout.
* @param policy
* the policy to be used for obtaining the strong caret.
* @return the TextHitInfo for the next caret to the right of the specified
* offset, or null if there is no hit.
*/
public TextHitInfo getNextRightHit(int offset, TextLayout.CaretPolicy policy) {
if (offset < 0 || offset > breaker.getCharCount()) {
// awt.195=Offset is out of bounds
throw new IllegalArgumentException(Messages.getString("awt.195")); //$NON-NLS-1$
}
TextHitInfo hit = TextHitInfo.afterOffset(offset);
TextHitInfo strongHit = policy.getStrongCaret(hit, hit.getOtherHit(), this);
TextHitInfo nextRightHit = getNextRightHit(strongHit);
if (nextRightHit != null) {
return policy.getStrongCaret(getVisualOtherHit(nextRightHit), nextRightHit, this);
}
return null;
}
/**
* Gets the outline of this TextLayout as a Shape.
*
* @param xform
* the AffineTransform to be used to transform the outline before
* returning it, or null if no transformation is desired.
* @return the outline of this TextLayout as a Shape.
*/
public Shape getOutline(AffineTransform xform) {
breaker.createAllSegments();
GeneralPath outline = breaker.getOutline();
if (outline != null && xform != null) {
outline.transform(xform);
}
return outline;
}
/**
* Gets the visible advance of this TextLayout which is defined as diffence
* between leading (advance) and trailing whitespace.
*
* @return the visible advance of this TextLayout.
*/
public float getVisibleAdvance() {
updateMetrics();
// Trailing whitespace _SHOULD_ be reordered (Unicode spec) to
// base direction, so it is also trailing
// in logical representation. We use this fact.
int lastNonWhitespace = breaker.getLastNonWhitespace();
if (lastNonWhitespace < 0) {
return 0;
} else if (lastNonWhitespace == getCharacterCount() - 1) {
return getAdvance();
} else if (justificationWidth >= 0) { // Layout is justified
return justificationWidth;
} else {
breaker.pushSegments(breaker.getACI().getBeginIndex(), lastNonWhitespace
+ breaker.getACI().getBeginIndex() + 1);
breaker.createAllSegments();
float visAdvance = tmc.createMetrics().getAdvance();
breaker.popSegments();
return visAdvance;
}
}
/**
* Gets a Shape which corresponds to the highlighted (selected) area based
* on two hit locations within the text and extends to the bounds.
*
* @param hit1
* the first text hit location.
* @param hit2
* the second text hit location.
* @param bounds
* the rectangle that the highlighted area should be extended or
* restricted to.
* @return a Shape which corresponds to the highlighted (selected) area.
*/
public Shape getVisualHighlightShape(TextHitInfo hit1, TextHitInfo hit2, Rectangle2D bounds) {
return caretManager.getVisualHighlightShape(hit1, hit2, bounds, this);
}
/**
* Gets a Shape which corresponds to the highlighted (selected) area based
* on two hit locations within the text.
*
* @param hit1
* the first text hit location.
* @param hit2
* the second text hit location.
* @return a Shape which corresponds to the highlighted (selected) area.
*/
public Shape getVisualHighlightShape(TextHitInfo hit1, TextHitInfo hit2) {
breaker.createAllSegments();
return caretManager.getVisualHighlightShape(hit1, hit2, breaker.getLogicalBounds(), this);
}
/**
* Gets the TextHitInfo for a hit on the opposite side of the specified
* hit's caret.
*
* @param hitInfo
* the specified TextHitInfo.
* @return the TextHitInfo for a hit on the opposite side of the specified
* hit's caret.
*/
public TextHitInfo getVisualOtherHit(TextHitInfo hitInfo) {
return caretManager.getVisualOtherHit(hitInfo);
}
/**
* Justifies the text; this method should be overridden by subclasses.
*
* @param justificationWidth
* the width for justification.
*/
protected void handleJustify(float justificationWidth) {
float justification = breaker.getJustification();
if (justification < 0) {
// awt.196=Justification impossible, layout already justified
throw new IllegalStateException(Messages.getString("awt.196")); //$NON-NLS-1$
} else if (justification == 0) {
return;
}
float gap = (justificationWidth - getVisibleAdvance()) * justification;
breaker.justify(gap);
this.justificationWidth = justificationWidth;
// Correct metrics
tmc = new TextMetricsCalculator(breaker);
tmc.correctAdvance(metrics);
}
/**
* Returns a TextHitInfo object that gives information on which division
* point (between two characters) is corresponds to a hit (such as a mouse
* click) at the specified coordinates.
*
* @param x
* the X coordinate in this TextLayout.
* @param y
* the Y coordinate in this TextLayout. TextHitInfo object
* corresponding to the given coordinates within the text.
* @return the information about the character at the specified position.
*/
public TextHitInfo hitTestChar(float x, float y) {
return hitTestChar(x, y, getBounds());
}
/**
* Returns a TextHitInfo object that gives information on which division
* point (between two characters) is corresponds to a hit (such as a mouse
* click) at the specified coordinates within the specified text rectangle.
*
* @param x
* the X coordinate in this TextLayout.
* @param y
* the Y coordinate in this TextLayout.
* @param bounds
* the bounds of the text area. TextHitInfo object corresponding
* to the given coordinates within the text.
* @return the information about the character at the specified position.
*/
public TextHitInfo hitTestChar(float x, float y, Rectangle2D bounds) {
if (x > bounds.getMaxX()) {
return breaker.isLTR() ? TextHitInfo.trailing(breaker.getCharCount() - 1) : TextHitInfo
.leading(0);
}
if (x < bounds.getMinX()) {
return breaker.isLTR() ? TextHitInfo.leading(0) : TextHitInfo.trailing(breaker
.getCharCount() - 1);
}
return breaker.hitTest(x, y);
}
/**
* Returns true if this TextLayout has a "left to right" direction.
*
* @return true if this TextLayout has a "left to right" direction, false if
* this TextLayout has a "right to left" direction.
*/
public boolean isLeftToRight() {
return breaker.isLTR();
}
/**
* Returns true if this TextLayout is vertical, false otherwise.
*
* @return true if this TextLayout is vertical, false if horizontal.
*/
public boolean isVertical() {
return false;
}
}