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