blob: e80afd091e0e923f2b72ef35de9a8f2b8a6ec973 [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.
*/
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 cooresponding to the given coordinates
* within the text.
*/
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 cooresponding to the given coordinates
* within the text.
*/
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;
}
}