/* * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved * (C) Copyright IBM Corp. 1996-2003, All Rights Reserved * * The original version of this source code and documentation is * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary * of IBM. These materials are provided under terms of a License * Agreement between Taligent and Sun. This technology is protected * by multiple US and International patents. * * This notice and attribution to Taligent may not be removed. * Taligent is a registered trademark of Taligent, Inc. * */ package java.awt.font; import java.awt.Color;import java.awt.Font;import java.awt.Graphics2D;import java.awt.Rectangle;import java.awt.Shape;import java.awt.font.NumericShaper;import java.awt.font.TextLine.TextLineMetrics;import java.awt.geom.AffineTransform;import java.awt.geom.GeneralPath;import java.awt.geom.NoninvertibleTransformException;import java.awt.geom.Point2D;import java.awt.geom.Rectangle2D;import java.text.AttributedString;import java.text.AttributedCharacterIterator;import java.text.AttributedCharacterIterator.Attribute;import java.util.Map;import java.util.HashMap;import java.util.Hashtable;import sun.font.AttributeValues;import sun.font.CoreMetrics;import sun.font.Decoration;import sun.font.FontLineMetrics;import sun.font.FontResolver;import sun.font.GraphicComponent;import sun.font.LayoutPathImpl;import sun.text.CodePointIterator; /** * * <code>TextLayout</code> is an immutable graphical representation of styled * character data. * <p> * It provides the following capabilities: * <ul> * <li>implicit bidirectional analysis and reordering, * <li>cursor positioning and movement, including split cursors for * mixed directional text, * <li>highlighting, including both logical and visual highlighting * for mixed directional text, * <li>multiple baselines (roman, hanging, and centered), * <li>hit testing, * <li>justification, * <li>default font substitution, * <li>metric information such as ascent, descent, and advance, and * <li>rendering * </ul> * <p> * A <code>TextLayout</code> object can be rendered using * its <code>draw</code> method. * <p> * <code>TextLayout</code> can be constructed either directly or through * the use of a {@link LineBreakMeasurer}. When constructed directly, the * source text represents a single paragraph. <code>LineBreakMeasurer</code> * allows styled text to be broken into lines that fit within a particular * width. See the <code>LineBreakMeasurer</code> documentation for more * information. * <p> * <code>TextLayout</code> construction logically proceeds as follows: * <ul> * <li>paragraph attributes are extracted and examined, * <li>text is analyzed for bidirectional reordering, and reordering * information is computed if needed, * <li>text is segmented into style runs * <li>fonts are chosen for style runs, first by using a font if the * attribute {@link TextAttribute#FONT} is present, otherwise by computing * a default font using the attributes that have been defined * <li>if text is on multiple baselines, the runs or subruns are further * broken into subruns sharing a common baseline, * <li>glyphvectors are generated for each run using the chosen font, * <li>final bidirectional reordering is performed on the glyphvectors * </ul> * <p> * All graphical information returned from a <code>TextLayout</code> * object's methods is relative to the origin of the * <code>TextLayout</code>, which is the intersection of the * <code>TextLayout</code> object's baseline with its left edge. Also, * coordinates passed into a <code>TextLayout</code> object's methods * are assumed to be relative to the <code>TextLayout</code> object's * origin. Clients usually need to translate between a * <code>TextLayout</code> object's coordinate system and the coordinate * system in another object (such as a * {@link java.awt.Graphics Graphics} object). * <p> * <code>TextLayout</code> objects are constructed from styled text, * but they do not retain a reference to their source text. Thus, * changes in the text previously used to generate a <code>TextLayout</code> * do not affect the <code>TextLayout</code>. * <p> * Three methods on a <code>TextLayout</code> object * (<code>getNextRightHit</code>, <code>getNextLeftHit</code>, and * <code>hitTestChar</code>) return instances of {@link TextHitInfo}. * The offsets contained in these <code>TextHitInfo</code> objects * are relative to the start of the <code>TextLayout</code>, <b>not</b> * to the text used to create the <code>TextLayout</code>. Similarly, * <code>TextLayout</code> methods that accept <code>TextHitInfo</code> * instances as parameters expect the <code>TextHitInfo</code> object's * offsets to be relative to the <code>TextLayout</code>, not to any * underlying text storage model. * <p> * <strong>Examples</strong>:<p> * Constructing and drawing a <code>TextLayout</code> and its bounding * rectangle: * <blockquote><pre> * Graphics2D g = ...; * Point2D loc = ...; * Font font = Font.getFont("Helvetica-bold-italic"); * FontRenderContext frc = g.getFontRenderContext(); * TextLayout layout = new TextLayout("This is a string", font, frc); * layout.draw(g, (float)loc.getX(), (float)loc.getY()); * * Rectangle2D bounds = layout.getBounds(); * bounds.setRect(bounds.getX()+loc.getX(), * bounds.getY()+loc.getY(), * bounds.getWidth(), * bounds.getHeight()); * g.draw(bounds); * </pre> * </blockquote> * <p> * Hit-testing a <code>TextLayout</code> (determining which character is at * a particular graphical location): * <blockquote><pre> * Point2D click = ...; * TextHitInfo hit = layout.hitTestChar( * (float) (click.getX() - loc.getX()), * (float) (click.getY() - loc.getY())); * </pre> * </blockquote> * <p> * Responding to a right-arrow key press: * <blockquote><pre> * int insertionIndex = ...; * TextHitInfo next = layout.getNextRightHit(insertionIndex); * if (next != null) { * // translate graphics to origin of layout on screen * g.translate(loc.getX(), loc.getY()); * Shape[] carets = layout.getCaretShapes(next.getInsertionIndex()); * g.draw(carets[0]); * if (carets[1] != null) { * g.draw(carets[1]); * } * } * </pre></blockquote> * <p> * Drawing a selection range corresponding to a substring in the source text. * The selected area may not be visually contiguous: * <blockquote><pre> * // selStart, selLimit should be relative to the layout, * // not to the source text * * int selStart = ..., selLimit = ...; * Color selectionColor = ...; * Shape selection = layout.getLogicalHighlightShape(selStart, selLimit); * // selection may consist of disjoint areas * // graphics is assumed to be tranlated to origin of layout * g.setColor(selectionColor); * g.fill(selection); * </pre></blockquote> * <p> * Drawing a visually contiguous selection range. The selection range may * correspond to more than one substring in the source text. The ranges of * the corresponding source text substrings can be obtained with * <code>getLogicalRangesForVisualSelection()</code>: * <blockquote><pre> * TextHitInfo selStart = ..., selLimit = ...; * Shape selection = layout.getVisualHighlightShape(selStart, selLimit); * g.setColor(selectionColor); * g.fill(selection); * int[] ranges = getLogicalRangesForVisualSelection(selStart, selLimit); * // ranges[0], ranges[1] is the first selection range, * // ranges[2], ranges[3] is the second selection range, etc. * </pre></blockquote> * <p> * Note: Font rotations can cause text baselines to be rotated, and * multiple runs with different rotations can cause the baseline to * bend or zig-zag. In order to account for this (rare) possibility, * some APIs are specified to return metrics and take parameters 'in * baseline-relative coordinates' (e.g. ascent, advance), and others * are in 'in standard coordinates' (e.g. getBounds). Values in * baseline-relative coordinates map the 'x' coordinate to the * distance along the baseline, (positive x is forward along the * baseline), and the 'y' coordinate to a distance along the * perpendicular to the baseline at 'x' (postitive y is 90 degrees * clockwise from the baseline vector). Values in standard * coordinates are measured along the x and y axes, with 0,0 at the * origin of the TextLayout. Documentation for each relevant API * indicates what values are in what coordinate system. In general, * measurement-related APIs are in baseline-relative coordinates, * while display-related APIs are in standard coordinates. * * @see LineBreakMeasurer * @see TextAttribute * @see TextHitInfo * @see LayoutPath */public final class TextLayout implements Cloneable { private int characterCount; private boolean isVerticalLine = false; private byte baseline; private float[] baselineOffsets; // why have these ? private TextLine textLine; // cached values computed from GlyphSets and set info: // all are recomputed from scratch in buildCache() private TextLine.TextLineMetrics lineMetrics = null; private float visibleAdvance; private int hashCodeCache; /* * TextLayouts are supposedly immutable. If you mutate a TextLayout under * the covers (like the justification code does) you'll need to set this * back to false. Could be replaced with textLine != null <--> cacheIsValid. */ private boolean cacheIsValid = false; // This value is obtained from an attribute, and constrained to the // interval [0,1]. If 0, the layout cannot be justified. private float justifyRatio; // If a layout is produced by justification, then that layout // cannot be justified. To enforce this constraint the // justifyRatio of the justified layout is set to this value. private static final float ALREADY_JUSTIFIED = -53.9f; // dx and dy specify the distance between the TextLayout's origin // and the origin of the leftmost GlyphSet (TextLayoutComponent, // actually). They were used for hanging punctuation support, // which is no longer implemented. Currently they are both always 0, // and TextLayout is not guaranteed to work with non-zero dx, dy // values right now. They were left in as an aide and reminder to // anyone who implements hanging punctuation or other similar stuff. // They are static now so they don't take up space in TextLayout // instances. private static float dx; private static float dy; /* * Natural bounds is used internally. It is built on demand in * getNaturalBounds. */ private Rectangle2D naturalBounds = null; /* * boundsRect encloses all of the bits this TextLayout can draw. It * is build on demand in getBounds. */ private Rectangle2D boundsRect = null; /* * flag to supress/allow carets inside of ligatures when hit testing or * arrow-keying */ private boolean caretsInLigaturesAreAllowed = false; /** * Defines a policy for determining the strong caret location. * This class contains one method, <code>getStrongCaret</code>, which * is used to specify the policy that determines the strong caret in * dual-caret text. The strong caret is used to move the caret to the * left or right. Instances of this class can be passed to * <code>getCaretShapes</code>, <code>getNextLeftHit</code> and * <code>getNextRightHit</code> to customize strong caret * selection. * <p> * To specify alternate caret policies, subclass <code>CaretPolicy</code> * and override <code>getStrongCaret</code>. <code>getStrongCaret</code> * should inspect the two <code>TextHitInfo</code> arguments and choose * one of them as the strong caret. * <p> * Most clients do not need to use this class. */ public static class CaretPolicy { /** * Constructs a <code>CaretPolicy</code>. */ public CaretPolicy() { } /** * Chooses one of the specified <code>TextHitInfo</code> instances as * a strong caret in the specified <code>TextLayout</code>. * @param hit1 a valid hit in <code>layout</code> * @param hit2 a valid hit in <code>layout</code> * @param layout the <code>TextLayout</code> in which * <code>hit1</code> and <code>hit2</code> are used * @return <code>hit1</code> or <code>hit2</code> * (or an equivalent <code>TextHitInfo</code>), indicating the * strong caret. */ public TextHitInfo getStrongCaret(TextHitInfo hit1, TextHitInfo hit2, TextLayout layout) { // default implmentation just calls private method on layout return layout.getStrongHit(hit1, hit2); } } /** * This <code>CaretPolicy</code> is used when a policy is not specified * by the client. With this policy, a hit on a character whose direction * is the same as the line direction is stronger than a hit on a * counterdirectional character. If the characters' directions are * the same, a hit on the leading edge of a character is stronger * than a hit on the trailing edge of a character. */ public static final CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy(); /** * Constructs a <code>TextLayout</code> from a <code>String</code> * and a {@link Font}. All the text is styled using the specified * <code>Font</code>. * <p> * The <code>String</code> must specify a single paragraph of text, * because an entire paragraph is required for the bidirectional * algorithm. * @param string the text to display * @param font a <code>Font</code> used to style the text * @param frc contains information about a graphics device which is needed * to measure the text correctly. * Text measurements can vary slightly depending on the * device resolution, and attributes such as antialiasing. This * parameter does not specify a translation between the * <code>TextLayout</code> and user space. */ public TextLayout(String string, Font font, FontRenderContext frc) { if (font == null) { throw new IllegalArgumentException("Null font passed to TextLayout constructor."); } if (string == null) { throw new IllegalArgumentException("Null string passed to TextLayout constructor."); } if (string.length() == 0) { throw new IllegalArgumentException("Zero length string passed to TextLayout constructor."); } Map attributes = null; if (font.hasLayoutAttributes()) { attributes = font.getAttributes(); } char[] text = string.toCharArray(); if (sameBaselineUpTo(font, text, 0, text.length) == text.length) { fastInit(text, font, attributes, frc); } else { AttributedString as = attributes == null ? new AttributedString(string) : new AttributedString(string, attributes); as.addAttribute(TextAttribute.FONT, font); standardInit(as.getIterator(), text, frc); } } /** * Constructs a <code>TextLayout</code> from a <code>String</code> * and an attribute set. * <p> * All the text is styled using the provided attributes. * <p> * <code>string</code> must specify a single paragraph of text because an * entire paragraph is required for the bidirectional algorithm. * @param string the text to display * @param attributes the attributes used to style the text * @param frc contains information about a graphics device which is needed * to measure the text correctly. * Text measurements can vary slightly depending on the * device resolution, and attributes such as antialiasing. This * parameter does not specify a translation between the * <code>TextLayout</code> and user space. */ public TextLayout(String string, Map<? extends Attribute,?> attributes, FontRenderContext frc) { if (string == null) { throw new IllegalArgumentException("Null string passed to TextLayout constructor."); } if (attributes == null) { throw new IllegalArgumentException("Null map passed to TextLayout constructor."); } if (string.length() == 0) { throw new IllegalArgumentException("Zero length string passed to TextLayout constructor."); } char[] text = string.toCharArray(); Font font = singleFont(text, 0, text.length, attributes); if (font != null) { fastInit(text, font, attributes, frc); } else { AttributedString as = new AttributedString(string, attributes); standardInit(as.getIterator(), text, frc); } } /* * Determines a font for the attributes, and if a single font can render * all the text on one baseline, return it, otherwise null. If the * attributes specify a font, assume it can display all the text without * checking. * If the AttributeSet contains an embedded graphic, return null. */ private static Font singleFont(char[] text, int start, int limit, Map attributes) { if (attributes.get(TextAttribute.CHAR_REPLACEMENT) != null) { return null; } Font font = null; try { font = (Font)attributes.get(TextAttribute.FONT); } catch (ClassCastException e) { } if (font == null) { if (attributes.get(TextAttribute.FAMILY) != null) { font = Font.getFont(attributes); if (font.canDisplayUpTo(text, start, limit) != -1) { return null; } } else { FontResolver resolver = FontResolver.getInstance(); CodePointIterator iter = CodePointIterator.create(text, start, limit); int fontIndex = resolver.nextFontRunIndex(iter); if (iter.charIndex() == limit) { font = resolver.getFont(fontIndex, attributes); } } } if (sameBaselineUpTo(font, text, start, limit) != limit) { return null; } return font; } /** * Constructs a <code>TextLayout</code> from an iterator over styled text. * <p> * The iterator must specify a single paragraph of text because an * entire paragraph is required for the bidirectional * algorithm. * @param text the styled text to display * @param frc contains information about a graphics device which is needed * to measure the text correctly. * Text measurements can vary slightly depending on the * device resolution, and attributes such as antialiasing. This * parameter does not specify a translation between theLines 1–500 of 2753. Load more
/* * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved * (C) Copyright IBM Corp. 1996-2003, All Rights Reserved * * The original version of this source code and documentation is * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary * of IBM. These materials are provided under terms of a License * Agreement between Taligent and Sun. This technology is protected * by multiple US and International patents. * * This notice and attribution to Taligent may not be removed. * Taligent is a registered trademark of Taligent, Inc. * */ package java.awt.font; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Shape; import java.awt.font.NumericShaper; import java.awt.font.TextLine.TextLineMetrics; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.text.AttributedString; import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator.Attribute; import java.util.Map; import java.util.HashMap; import java.util.Hashtable; import sun.font.AttributeValues; import sun.font.CoreMetrics; import sun.font.Decoration; import sun.font.FontLineMetrics; import sun.font.FontResolver; import sun.font.GraphicComponent; import sun.font.LayoutPathImpl; import sun.text.CodePointIterator; /** * * <code>TextLayout</code> is an immutable graphical representation of styled * character data. * <p> * It provides the following capabilities: * <ul> * <li>implicit bidirectional analysis and reordering, * <li>cursor positioning and movement, including split cursors for * mixed directional text, * <li>highlighting, including both logical and visual highlighting * for mixed directional text, * <li>multiple baselines (roman, hanging, and centered), * <li>hit testing, * <li>justification, * <li>default font substitution, * <li>metric information such as ascent, descent, and advance, and * <li>rendering * </ul> * <p> * A <code>TextLayout</code> object can be rendered using * its <code>draw</code> method. * <p> * <code>TextLayout</code> can be constructed either directly or through * the use of a {@link LineBreakMeasurer}. When constructed directly, the * source text represents a single paragraph. <code>LineBreakMeasurer</code> * allows styled text to be broken into lines that fit within a particular * width. See the <code>LineBreakMeasurer</code> documentation for more * information. * <p> * <code>TextLayout</code> construction logically proceeds as follows: * <ul> * <li>paragraph attributes are extracted and examined, * <li>text is analyzed for bidirectional reordering, and reordering * information is computed if needed, * <li>text is segmented into style runs * <li>fonts are chosen for style runs, first by using a font if the * attribute {@link TextAttribute#FONT} is present, otherwise by computing * a default font using the attributes that have been defined * <li>if text is on multiple baselines, the runs or subruns are further * broken into subruns sharing a common baseline, * <li>glyphvectors are generated for each run using the chosen font, * <li>final bidirectional reordering is performed on the glyphvectors * </ul> * <p> * All graphical information returned from a <code>TextLayout</code> * object's methods is relative to the origin of the * <code>TextLayout</code>, which is the intersection of the * <code>TextLayout</code> object's baseline with its left edge. Also, * coordinates passed into a <code>TextLayout</code> object's methods * are assumed to be relative to the <code>TextLayout</code> object's * origin. Clients usually need to translate between a * <code>TextLayout</code> object's coordinate system and the coordinate * system in another object (such as a * {@link java.awt.Graphics Graphics} object). * <p> * <code>TextLayout</code> objects are constructed from styled text, * but they do not retain a reference to their source text. Thus, * changes in the text previously used to generate a <code>TextLayout</code> * do not affect the <code>TextLayout</code>. * <p> * Three methods on a <code>TextLayout</code> object * (<code>getNextRightHit</code>, <code>getNextLeftHit</code>, and * <code>hitTestChar</code>) return instances of {@link TextHitInfo}. * The offsets contained in these <code>TextHitInfo</code> objects * are relative to the start of the <code>TextLayout</code>, <b>not</b> * to the text used to create the <code>TextLayout</code>. Similarly, * <code>TextLayout</code> methods that accept <code>TextHitInfo</code> * instances as parameters expect the <code>TextHitInfo</code> object's * offsets to be relative to the <code>TextLayout</code>, not to any * underlying text storage model. * <p> * <strong>Examples</strong>:<p> * Constructing and drawing a <code>TextLayout</code> and its bounding * rectangle: * <blockquote><pre> * Graphics2D g = ...; * Point2D loc = ...; * Font font = Font.getFont("Helvetica-bold-italic"); * FontRenderContext frc = g.getFontRenderContext(); * TextLayout layout = new TextLayout("This is a string", font, frc); * layout.draw(g, (float)loc.getX(), (float)loc.getY()); * * Rectangle2D bounds = layout.getBounds(); * bounds.setRect(bounds.getX()+loc.getX(), * bounds.getY()+loc.getY(), * bounds.getWidth(), * bounds.getHeight()); * g.draw(bounds); * </pre> * </blockquote> * <p> * Hit-testing a <code>TextLayout</code> (determining which character is at * a particular graphical location): * <blockquote><pre> * Point2D click = ...; * TextHitInfo hit = layout.hitTestChar( * (float) (click.getX() - loc.getX()), * (float) (click.getY() - loc.getY())); * </pre> * </blockquote> * <p> * Responding to a right-arrow key press: * <blockquote><pre> * int insertionIndex = ...; * TextHitInfo next = layout.getNextRightHit(insertionIndex); * if (next != null) { * // translate graphics to origin of layout on screen * g.translate(loc.getX(), loc.getY()); * Shape[] carets = layout.getCaretShapes(next.getInsertionIndex()); * g.draw(carets[0]); * if (carets[1] != null) { * g.draw(carets[1]); * } * } * </pre></blockquote> * <p> * Drawing a selection range corresponding to a substring in the source text. * The selected area may not be visually contiguous: * <blockquote><pre> * // selStart, selLimit should be relative to the layout, * // not to the source text * * int selStart = ..., selLimit = ...; * Color selectionColor = ...; * Shape selection = layout.getLogicalHighlightShape(selStart, selLimit); * // selection may consist of disjoint areas * // graphics is assumed to be tranlated to origin of layout * g.setColor(selectionColor); * g.fill(selection); * </pre></blockquote> * <p> * Drawing a visually contiguous selection range. The selection range may * correspond to more than one substring in the source text. The ranges of * the corresponding source text substrings can be obtained with * <code>getLogicalRangesForVisualSelection()</code>: * <blockquote><pre> * TextHitInfo selStart = ..., selLimit = ...; * Shape selection = layout.getVisualHighlightShape(selStart, selLimit); * g.setColor(selectionColor); * g.fill(selection); * int[] ranges = getLogicalRangesForVisualSelection(selStart, selLimit); * // ranges[0], ranges[1] is the first selection range, * // ranges[2], ranges[3] is the second selection range, etc. * </pre></blockquote> * <p> * Note: Font rotations can cause text baselines to be rotated, and * multiple runs with different rotations can cause the baseline to * bend or zig-zag. In order to account for this (rare) possibility, * some APIs are specified to return metrics and take parameters 'in * baseline-relative coordinates' (e.g. ascent, advance), and others * are in 'in standard coordinates' (e.g. getBounds). Values in * baseline-relative coordinates map the 'x' coordinate to the * distance along the baseline, (positive x is forward along the * baseline), and the 'y' coordinate to a distance along the * perpendicular to the baseline at 'x' (postitive y is 90 degrees * clockwise from the baseline vector). Values in standard * coordinates are measured along the x and y axes, with 0,0 at the * origin of the TextLayout. Documentation for each relevant API * indicates what values are in what coordinate system. In general, * measurement-related APIs are in baseline-relative coordinates, * while display-related APIs are in standard coordinates. * * @see LineBreakMeasurer * @see TextAttribute * @see TextHitInfo * @see LayoutPath */ public final class TextLayout implements Cloneable { private int characterCount; private boolean isVerticalLine = false; private byte baseline; private float[] baselineOffsets; // why have these ? private TextLine textLine; // cached values computed from GlyphSets and set info: // all are recomputed from scratch in buildCache() private TextLine.TextLineMetrics lineMetrics = null; private float visibleAdvance; private int hashCodeCache; /* * TextLayouts are supposedly immutable. If you mutate a TextLayout under * the covers (like the justification code does) you'll need to set this * back to false. Could be replaced with textLine != null <--> cacheIsValid. */ private boolean cacheIsValid = false; // This value is obtained from an attribute, and constrained to the // interval [0,1]. If 0, the layout cannot be justified. private float justifyRatio; // If a layout is produced by justification, then that layout // cannot be justified. To enforce this constraint the // justifyRatio of the justified layout is set to this value. private static final float ALREADY_JUSTIFIED = -53.9f; // dx and dy specify the distance between the TextLayout's origin // and the origin of the leftmost GlyphSet (TextLayoutComponent, // actually). They were used for hanging punctuation support, // which is no longer implemented. Currently they are both always 0, // and TextLayout is not guaranteed to work with non-zero dx, dy // values right now. They were left in as an aide and reminder to // anyone who implements hanging punctuation or other similar stuff. // They are static now so they don't take up space in TextLayout // instances. private static float dx; private static float dy; /* * Natural bounds is used internally. It is built on demand in * getNaturalBounds. */ private Rectangle2D naturalBounds = null; /* * boundsRect encloses all of the bits this TextLayout can draw. It * is build on demand in getBounds. */ private Rectangle2D boundsRect = null; /* * flag to supress/allow carets inside of ligatures when hit testing or * arrow-keying */ private boolean caretsInLigaturesAreAllowed = false; /** * Defines a policy for determining the strong caret location. * This class contains one method, <code>getStrongCaret</code>, which * is used to specify the policy that determines the strong caret in * dual-caret text. The strong caret is used to move the caret to the * left or right. Instances of this class can be passed to * <code>getCaretShapes</code>, <code>getNextLeftHit</code> and * <code>getNextRightHit</code> to customize strong caret * selection. * <p> * To specify alternate caret policies, subclass <code>CaretPolicy</code> * and override <code>getStrongCaret</code>. <code>getStrongCaret</code> * should inspect the two <code>TextHitInfo</code> arguments and choose * one of them as the strong caret. * <p> * Most clients do not need to use this class. */ public static class CaretPolicy { /** * Constructs a <code>CaretPolicy</code>. */ public CaretPolicy() { } /** * Chooses one of the specified <code>TextHitInfo</code> instances as * a strong caret in the specified <code>TextLayout</code>. * @param hit1 a valid hit in <code>layout</code> * @param hit2 a valid hit in <code>layout</code> * @param layout the <code>TextLayout</code> in which * <code>hit1</code> and <code>hit2</code> are used * @return <code>hit1</code> or <code>hit2</code> * (or an equivalent <code>TextHitInfo</code>), indicating the * strong caret. */ public TextHitInfo getStrongCaret(TextHitInfo hit1, TextHitInfo hit2, TextLayout layout) { // default implmentation just calls private method on layout return layout.getStrongHit(hit1, hit2); } } /** * This <code>CaretPolicy</code> is used when a policy is not specified * by the client. With this policy, a hit on a character whose direction * is the same as the line direction is stronger than a hit on a * counterdirectional character. If the characters' directions are * the same, a hit on the leading edge of a character is stronger * than a hit on the trailing edge of a character. */ public static final CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy(); /** * Constructs a <code>TextLayout</code> from a <code>String</code> * and a {@link Font}. All the text is styled using the specified * <code>Font</code>. * <p> * The <code>String</code> must specify a single paragraph of text, * because an entire paragraph is required for the bidirectional * algorithm. * @param string the text to display * @param font a <code>Font</code> used to style the text * @param frc contains information about a graphics device which is needed * to measure the text correctly. * Text measurements can vary slightly depending on the * device resolution, and attributes such as antialiasing. This * parameter does not specify a translation between the * <code>TextLayout</code> and user space. */ public TextLayout(String string, Font font, FontRenderContext frc) { if (font == null) { throw new IllegalArgumentException("Null font passed to TextLayout constructor."); } if (string == null) { throw new IllegalArgumentException("Null string passed to TextLayout constructor."); } if (string.length() == 0) { throw new IllegalArgumentException("Zero length string passed to TextLayout constructor."); } Map attributes = null; if (font.hasLayoutAttributes()) { attributes = font.getAttributes(); } char[] text = string.toCharArray(); if (sameBaselineUpTo(font, text, 0, text.length) == text.length) { fastInit(text, font, attributes, frc); } else { AttributedString as = attributes == null ? new AttributedString(string) : new AttributedString(string, attributes); as.addAttribute(TextAttribute.FONT, font); standardInit(as.getIterator(), text, frc); } } /** * Constructs a <code>TextLayout</code> from a <code>String</code> * and an attribute set. * <p> * All the text is styled using the provided attributes. * <p> * <code>string</code> must specify a single paragraph of text because an * entire paragraph is required for the bidirectional algorithm. * @param string the text to display * @param attributes the attributes used to style the text * @param frc contains information about a graphics device which is needed * to measure the text correctly. * Text measurements can vary slightly depending on the * device resolution, and attributes such as antialiasing. This * parameter does not specify a translation between the * <code>TextLayout</code> and user space. */ public TextLayout(String string, Map<? extends Attribute,?> attributes, FontRenderContext frc) { if (string == null) { throw new IllegalArgumentException("Null string passed to TextLayout constructor."); } if (attributes == null) { throw new IllegalArgumentException("Null map passed to TextLayout constructor."); } if (string.length() == 0) { throw new IllegalArgumentException("Zero length string passed to TextLayout constructor."); } char[] text = string.toCharArray(); Font font = singleFont(text, 0, text.length, attributes); if (font != null) { fastInit(text, font, attributes, frc); } else { AttributedString as = new AttributedString(string, attributes); standardInit(as.getIterator(), text, frc); } } /* * Determines a font for the attributes, and if a single font can render * all the text on one baseline, return it, otherwise null. If the * attributes specify a font, assume it can display all the text without * checking. * If the AttributeSet contains an embedded graphic, return null. */ private static Font singleFont(char[] text, int start, int limit, Map attributes) { if (attributes.get(TextAttribute.CHAR_REPLACEMENT) != null) { return null; } Font font = null; try { font = (Font)attributes.get(TextAttribute.FONT); } catch (ClassCastException e) { } if (font == null) { if (attributes.get(TextAttribute.FAMILY) != null) { font = Font.getFont(attributes); if (font.canDisplayUpTo(text, start, limit) != -1) { return null; } } else { FontResolver resolver = FontResolver.getInstance(); CodePointIterator iter = CodePointIterator.create(text, start, limit); int fontIndex = resolver.nextFontRunIndex(iter); if (iter.charIndex() == limit) { font = resolver.getFont(fontIndex, attributes); } } } if (sameBaselineUpTo(font, text, start, limit) != limit) { return null; } return font; } /** * Constructs a <code>TextLayout</code> from an iterator over styled text. * <p> * The iterator must specify a single paragraph of text because an * entire paragraph is required for the bidirectional * algorithm. * @param text the styled text to display * @param frc contains information about a graphics device which is needed * to measure the text correctly. * Text measurements can vary slightly depending on the * device resolution, and attributes such as antialiasing. This * parameter does not specify a translation between the