| /* |
| * 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; |
| } |
| } |