History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/java/awt/font/TextLayout.java (1 of 18352 files). Show all

Unified · Split

java/pkg/java/awt/font/TextLayout.java+2753−0
@@ -0,0 +1,2753 @@
1+/*
2+ * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
3+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+ *
5+ * This code is free software; you can redistribute it and/or modify it
6+ * under the terms of the GNU General Public License version 2 only, as
7+ * published by the Free Software Foundation. Oracle designates this
8+ * particular file as subject to the "Classpath" exception as provided
9+ * by Oracle in the LICENSE file that accompanied this code.
10+ *
11+ * This code is distributed in the hope that it will be useful, but WITHOUT
12+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+ * version 2 for more details (a copy is included in the LICENSE file that
15+ * accompanied this code).
16+ *
17+ * You should have received a copy of the GNU General Public License version
18+ * 2 along with this work; if not, write to the Free Software Foundation,
19+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+ *
21+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+ * or visit www.oracle.com if you need additional information or have any
23+ * questions.
24+ */
25+
26+/*
27+ * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
28+ * (C) Copyright IBM Corp. 1996-2003, All Rights Reserved
29+ *
30+ * The original version of this source code and documentation is
31+ * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
32+ * of IBM. These materials are provided under terms of a License
33+ * Agreement between Taligent and Sun. This technology is protected
34+ * by multiple US and International patents.
35+ *
36+ * This notice and attribution to Taligent may not be removed.
37+ * Taligent is a registered trademark of Taligent, Inc.
38+ *
39+ */
40+
41+package java.awt.font;
42+
43+import java.awt.Color;
44+import java.awt.Font;
45+import java.awt.Graphics2D;
46+import java.awt.Rectangle;
47+import java.awt.Shape;
48+import java.awt.font.NumericShaper;
49+import java.awt.font.TextLine.TextLineMetrics;
50+import java.awt.geom.AffineTransform;
51+import java.awt.geom.GeneralPath;
52+import java.awt.geom.NoninvertibleTransformException;
53+import java.awt.geom.Point2D;
54+import java.awt.geom.Rectangle2D;
55+import java.text.AttributedString;
56+import java.text.AttributedCharacterIterator;
57+import java.text.AttributedCharacterIterator.Attribute;
58+import java.util.Map;
59+import java.util.HashMap;
60+import java.util.Hashtable;
61+import sun.font.AttributeValues;
62+import sun.font.CoreMetrics;
63+import sun.font.Decoration;
64+import sun.font.FontLineMetrics;
65+import sun.font.FontResolver;
66+import sun.font.GraphicComponent;
67+import sun.font.LayoutPathImpl;
68+import sun.text.CodePointIterator;
69+
70+/**
71+ *
72+ * <code>TextLayout</code> is an immutable graphical representation of styled
73+ * character data.
74+ * <p>
75+ * It provides the following capabilities:
76+ * <ul>
77+ * <li>implicit bidirectional analysis and reordering,
78+ * <li>cursor positioning and movement, including split cursors for
79+ * mixed directional text,
80+ * <li>highlighting, including both logical and visual highlighting
81+ * for mixed directional text,
82+ * <li>multiple baselines (roman, hanging, and centered),
83+ * <li>hit testing,
84+ * <li>justification,
85+ * <li>default font substitution,
86+ * <li>metric information such as ascent, descent, and advance, and
87+ * <li>rendering
88+ * </ul>
89+ * <p>
90+ * A <code>TextLayout</code> object can be rendered using
91+ * its <code>draw</code> method.
92+ * <p>
93+ * <code>TextLayout</code> can be constructed either directly or through
94+ * the use of a {@link LineBreakMeasurer}. When constructed directly, the
95+ * source text represents a single paragraph. <code>LineBreakMeasurer</code>
96+ * allows styled text to be broken into lines that fit within a particular
97+ * width. See the <code>LineBreakMeasurer</code> documentation for more
98+ * information.
99+ * <p>
100+ * <code>TextLayout</code> construction logically proceeds as follows:
101+ * <ul>
102+ * <li>paragraph attributes are extracted and examined,
103+ * <li>text is analyzed for bidirectional reordering, and reordering
104+ * information is computed if needed,
105+ * <li>text is segmented into style runs
106+ * <li>fonts are chosen for style runs, first by using a font if the
107+ * attribute {@link TextAttribute#FONT} is present, otherwise by computing
108+ * a default font using the attributes that have been defined
109+ * <li>if text is on multiple baselines, the runs or subruns are further
110+ * broken into subruns sharing a common baseline,
111+ * <li>glyphvectors are generated for each run using the chosen font,
112+ * <li>final bidirectional reordering is performed on the glyphvectors
113+ * </ul>
114+ * <p>
115+ * All graphical information returned from a <code>TextLayout</code>
116+ * object's methods is relative to the origin of the
117+ * <code>TextLayout</code>, which is the intersection of the
118+ * <code>TextLayout</code> object's baseline with its left edge. Also,
119+ * coordinates passed into a <code>TextLayout</code> object's methods
120+ * are assumed to be relative to the <code>TextLayout</code> object's
121+ * origin. Clients usually need to translate between a
122+ * <code>TextLayout</code> object's coordinate system and the coordinate
123+ * system in another object (such as a
124+ * {@link java.awt.Graphics Graphics} object).
125+ * <p>
126+ * <code>TextLayout</code> objects are constructed from styled text,
127+ * but they do not retain a reference to their source text. Thus,
128+ * changes in the text previously used to generate a <code>TextLayout</code>
129+ * do not affect the <code>TextLayout</code>.
130+ * <p>
131+ * Three methods on a <code>TextLayout</code> object
132+ * (<code>getNextRightHit</code>, <code>getNextLeftHit</code>, and
133+ * <code>hitTestChar</code>) return instances of {@link TextHitInfo}.
134+ * The offsets contained in these <code>TextHitInfo</code> objects
135+ * are relative to the start of the <code>TextLayout</code>, <b>not</b>
136+ * to the text used to create the <code>TextLayout</code>. Similarly,
137+ * <code>TextLayout</code> methods that accept <code>TextHitInfo</code>
138+ * instances as parameters expect the <code>TextHitInfo</code> object's
139+ * offsets to be relative to the <code>TextLayout</code>, not to any
140+ * underlying text storage model.
141+ * <p>
142+ * <strong>Examples</strong>:<p>
143+ * Constructing and drawing a <code>TextLayout</code> and its bounding
144+ * rectangle:
145+ * <blockquote><pre>
146+ * Graphics2D g = ...;
147+ * Point2D loc = ...;
148+ * Font font = Font.getFont("Helvetica-bold-italic");
149+ * FontRenderContext frc = g.getFontRenderContext();
150+ * TextLayout layout = new TextLayout("This is a string", font, frc);
151+ * layout.draw(g, (float)loc.getX(), (float)loc.getY());
152+ *
153+ * Rectangle2D bounds = layout.getBounds();
154+ * bounds.setRect(bounds.getX()+loc.getX(),
155+ * bounds.getY()+loc.getY(),
156+ * bounds.getWidth(),
157+ * bounds.getHeight());
158+ * g.draw(bounds);
159+ * </pre>
160+ * </blockquote>
161+ * <p>
162+ * Hit-testing a <code>TextLayout</code> (determining which character is at
163+ * a particular graphical location):
164+ * <blockquote><pre>
165+ * Point2D click = ...;
166+ * TextHitInfo hit = layout.hitTestChar(
167+ * (float) (click.getX() - loc.getX()),
168+ * (float) (click.getY() - loc.getY()));
169+ * </pre>
170+ * </blockquote>
171+ * <p>
172+ * Responding to a right-arrow key press:
173+ * <blockquote><pre>
174+ * int insertionIndex = ...;
175+ * TextHitInfo next = layout.getNextRightHit(insertionIndex);
176+ * if (next != null) {
177+ * // translate graphics to origin of layout on screen
178+ * g.translate(loc.getX(), loc.getY());
179+ * Shape[] carets = layout.getCaretShapes(next.getInsertionIndex());
180+ * g.draw(carets[0]);
181+ * if (carets[1] != null) {
182+ * g.draw(carets[1]);
183+ * }
184+ * }
185+ * </pre></blockquote>
186+ * <p>
187+ * Drawing a selection range corresponding to a substring in the source text.
188+ * The selected area may not be visually contiguous:
189+ * <blockquote><pre>
190+ * // selStart, selLimit should be relative to the layout,
191+ * // not to the source text
192+ *
193+ * int selStart = ..., selLimit = ...;
194+ * Color selectionColor = ...;
195+ * Shape selection = layout.getLogicalHighlightShape(selStart, selLimit);
196+ * // selection may consist of disjoint areas
197+ * // graphics is assumed to be tranlated to origin of layout
198+ * g.setColor(selectionColor);
199+ * g.fill(selection);
200+ * </pre></blockquote>
201+ * <p>
202+ * Drawing a visually contiguous selection range. The selection range may
203+ * correspond to more than one substring in the source text. The ranges of
204+ * the corresponding source text substrings can be obtained with
205+ * <code>getLogicalRangesForVisualSelection()</code>:
206+ * <blockquote><pre>
207+ * TextHitInfo selStart = ..., selLimit = ...;
208+ * Shape selection = layout.getVisualHighlightShape(selStart, selLimit);
209+ * g.setColor(selectionColor);
210+ * g.fill(selection);
211+ * int[] ranges = getLogicalRangesForVisualSelection(selStart, selLimit);
212+ * // ranges[0], ranges[1] is the first selection range,
213+ * // ranges[2], ranges[3] is the second selection range, etc.
214+ * </pre></blockquote>
215+ * <p>
216+ * Note: Font rotations can cause text baselines to be rotated, and
217+ * multiple runs with different rotations can cause the baseline to
218+ * bend or zig-zag. In order to account for this (rare) possibility,
219+ * some APIs are specified to return metrics and take parameters 'in
220+ * baseline-relative coordinates' (e.g. ascent, advance), and others
221+ * are in 'in standard coordinates' (e.g. getBounds). Values in
222+ * baseline-relative coordinates map the 'x' coordinate to the
223+ * distance along the baseline, (positive x is forward along the
224+ * baseline), and the 'y' coordinate to a distance along the
225+ * perpendicular to the baseline at 'x' (postitive y is 90 degrees
226+ * clockwise from the baseline vector). Values in standard
227+ * coordinates are measured along the x and y axes, with 0,0 at the
228+ * origin of the TextLayout. Documentation for each relevant API
229+ * indicates what values are in what coordinate system. In general,
230+ * measurement-related APIs are in baseline-relative coordinates,
231+ * while display-related APIs are in standard coordinates.
232+ *
233+ * @see LineBreakMeasurer
234+ * @see TextAttribute
235+ * @see TextHitInfo
236+ * @see LayoutPath
237+ */
238+public final class TextLayout implements Cloneable {
239+
240+ private int characterCount;
241+ private boolean isVerticalLine = false;
242+ private byte baseline;
243+ private float[] baselineOffsets; // why have these ?
244+ private TextLine textLine;
245+
246+ // cached values computed from GlyphSets and set info:
247+ // all are recomputed from scratch in buildCache()
248+ private TextLine.TextLineMetrics lineMetrics = null;
249+ private float visibleAdvance;
250+ private int hashCodeCache;
251+
252+ /*
253+ * TextLayouts are supposedly immutable. If you mutate a TextLayout under
254+ * the covers (like the justification code does) you'll need to set this
255+ * back to false. Could be replaced with textLine != null <--> cacheIsValid.
256+ */
257+ private boolean cacheIsValid = false;
258+
259+
260+ // This value is obtained from an attribute, and constrained to the
261+ // interval [0,1]. If 0, the layout cannot be justified.
262+ private float justifyRatio;
263+
264+ // If a layout is produced by justification, then that layout
265+ // cannot be justified. To enforce this constraint the
266+ // justifyRatio of the justified layout is set to this value.
267+ private static final float ALREADY_JUSTIFIED = -53.9f;
268+
269+ // dx and dy specify the distance between the TextLayout's origin
270+ // and the origin of the leftmost GlyphSet (TextLayoutComponent,
271+ // actually). They were used for hanging punctuation support,
272+ // which is no longer implemented. Currently they are both always 0,
273+ // and TextLayout is not guaranteed to work with non-zero dx, dy
274+ // values right now. They were left in as an aide and reminder to
275+ // anyone who implements hanging punctuation or other similar stuff.
276+ // They are static now so they don't take up space in TextLayout
277+ // instances.
278+ private static float dx;
279+ private static float dy;
280+
281+ /*
282+ * Natural bounds is used internally. It is built on demand in
283+ * getNaturalBounds.
284+ */
285+ private Rectangle2D naturalBounds = null;
286+
287+ /*
288+ * boundsRect encloses all of the bits this TextLayout can draw. It
289+ * is build on demand in getBounds.
290+ */
291+ private Rectangle2D boundsRect = null;
292+
293+ /*
294+ * flag to supress/allow carets inside of ligatures when hit testing or
295+ * arrow-keying
296+ */
297+ private boolean caretsInLigaturesAreAllowed = false;
298+
299+ /**
300+ * Defines a policy for determining the strong caret location.
301+ * This class contains one method, <code>getStrongCaret</code>, which
302+ * is used to specify the policy that determines the strong caret in
303+ * dual-caret text. The strong caret is used to move the caret to the
304+ * left or right. Instances of this class can be passed to
305+ * <code>getCaretShapes</code>, <code>getNextLeftHit</code> and
306+ * <code>getNextRightHit</code> to customize strong caret
307+ * selection.
308+ * <p>
309+ * To specify alternate caret policies, subclass <code>CaretPolicy</code>
310+ * and override <code>getStrongCaret</code>. <code>getStrongCaret</code>
311+ * should inspect the two <code>TextHitInfo</code> arguments and choose
312+ * one of them as the strong caret.
313+ * <p>
314+ * Most clients do not need to use this class.
315+ */
316+ public static class CaretPolicy {
317+
318+ /**
319+ * Constructs a <code>CaretPolicy</code>.
320+ */
321+ public CaretPolicy() {
322+ }
323+
324+ /**
325+ * Chooses one of the specified <code>TextHitInfo</code> instances as
326+ * a strong caret in the specified <code>TextLayout</code>.
327+ * @param hit1 a valid hit in <code>layout</code>
328+ * @param hit2 a valid hit in <code>layout</code>
329+ * @param layout the <code>TextLayout</code> in which
330+ * <code>hit1</code> and <code>hit2</code> are used
331+ * @return <code>hit1</code> or <code>hit2</code>
332+ * (or an equivalent <code>TextHitInfo</code>), indicating the
333+ * strong caret.
334+ */
335+ public TextHitInfo getStrongCaret(TextHitInfo hit1,
336+ TextHitInfo hit2,
337+ TextLayout layout) {
338+
339+ // default implmentation just calls private method on layout
340+ return layout.getStrongHit(hit1, hit2);
341+ }
342+ }
343+
344+ /**
345+ * This <code>CaretPolicy</code> is used when a policy is not specified
346+ * by the client. With this policy, a hit on a character whose direction
347+ * is the same as the line direction is stronger than a hit on a
348+ * counterdirectional character. If the characters' directions are
349+ * the same, a hit on the leading edge of a character is stronger
350+ * than a hit on the trailing edge of a character.
351+ */
352+ public static final CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
353+
354+ /**
355+ * Constructs a <code>TextLayout</code> from a <code>String</code>
356+ * and a {@link Font}. All the text is styled using the specified
357+ * <code>Font</code>.
358+ * <p>
359+ * The <code>String</code> must specify a single paragraph of text,
360+ * because an entire paragraph is required for the bidirectional
361+ * algorithm.
362+ * @param string the text to display
363+ * @param font a <code>Font</code> used to style the text
364+ * @param frc contains information about a graphics device which is needed
365+ * to measure the text correctly.
366+ * Text measurements can vary slightly depending on the
367+ * device resolution, and attributes such as antialiasing. This
368+ * parameter does not specify a translation between the
369+ * <code>TextLayout</code> and user space.
370+ */
371+ public TextLayout(String string, Font font, FontRenderContext frc) {
372+
373+ if (font == null) {
374+ throw new IllegalArgumentException("Null font passed to TextLayout constructor.");
375+ }
376+
377+ if (string == null) {
378+ throw new IllegalArgumentException("Null string passed to TextLayout constructor.");
379+ }
380+
381+ if (string.length() == 0) {
382+ throw new IllegalArgumentException("Zero length string passed to TextLayout constructor.");
383+ }
384+
385+ Map attributes = null;
386+ if (font.hasLayoutAttributes()) {
387+ attributes = font.getAttributes();
388+ }
389+
390+ char[] text = string.toCharArray();
391+ if (sameBaselineUpTo(font, text, 0, text.length) == text.length) {
392+ fastInit(text, font, attributes, frc);
393+ } else {
394+ AttributedString as = attributes == null
395+ ? new AttributedString(string)
396+ : new AttributedString(string, attributes);
397+ as.addAttribute(TextAttribute.FONT, font);
398+ standardInit(as.getIterator(), text, frc);
399+ }
400+ }
401+
402+ /**
403+ * Constructs a <code>TextLayout</code> from a <code>String</code>
404+ * and an attribute set.
405+ * <p>
406+ * All the text is styled using the provided attributes.
407+ * <p>
408+ * <code>string</code> must specify a single paragraph of text because an
409+ * entire paragraph is required for the bidirectional algorithm.
410+ * @param string the text to display
411+ * @param attributes the attributes used to style the text
412+ * @param frc contains information about a graphics device which is needed
413+ * to measure the text correctly.
414+ * Text measurements can vary slightly depending on the
415+ * device resolution, and attributes such as antialiasing. This
416+ * parameter does not specify a translation between the
417+ * <code>TextLayout</code> and user space.
418+ */
419+ public TextLayout(String string, Map<? extends Attribute,?> attributes,
420+ FontRenderContext frc)
421+ {
422+ if (string == null) {
423+ throw new IllegalArgumentException("Null string passed to TextLayout constructor.");
424+ }
425+
426+ if (attributes == null) {
427+ throw new IllegalArgumentException("Null map passed to TextLayout constructor.");
428+ }
429+
430+ if (string.length() == 0) {
431+ throw new IllegalArgumentException("Zero length string passed to TextLayout constructor.");
432+ }
433+
434+ char[] text = string.toCharArray();
435+ Font font = singleFont(text, 0, text.length, attributes);
436+ if (font != null) {
437+ fastInit(text, font, attributes, frc);
438+ } else {
439+ AttributedString as = new AttributedString(string, attributes);
440+ standardInit(as.getIterator(), text, frc);
441+ }
442+ }
443+
444+ /*
445+ * Determines a font for the attributes, and if a single font can render
446+ * all the text on one baseline, return it, otherwise null. If the
447+ * attributes specify a font, assume it can display all the text without
448+ * checking.
449+ * If the AttributeSet contains an embedded graphic, return null.
450+ */
451+ private static Font singleFont(char[] text,
452+ int start,
453+ int limit,
454+ Map attributes) {
455+
456+ if (attributes.get(TextAttribute.CHAR_REPLACEMENT) != null) {
457+ return null;
458+ }
459+
460+ Font font = null;
461+ try {
462+ font = (Font)attributes.get(TextAttribute.FONT);
463+ }
464+ catch (ClassCastException e) {
465+ }
466+ if (font == null) {
467+ if (attributes.get(TextAttribute.FAMILY) != null) {
468+ font = Font.getFont(attributes);
469+ if (font.canDisplayUpTo(text, start, limit) != -1) {
470+ return null;
471+ }
472+ } else {
473+ FontResolver resolver = FontResolver.getInstance();
474+ CodePointIterator iter = CodePointIterator.create(text, start, limit);
475+ int fontIndex = resolver.nextFontRunIndex(iter);
476+ if (iter.charIndex() == limit) {
477+ font = resolver.getFont(fontIndex, attributes);
478+ }
479+ }
480+ }
481+
482+ if (sameBaselineUpTo(font, text, start, limit) != limit) {
483+ return null;
484+ }
485+
486+ return font;
487+ }
488+
489+ /**
490+ * Constructs a <code>TextLayout</code> from an iterator over styled text.
491+ * <p>
492+ * The iterator must specify a single paragraph of text because an
493+ * entire paragraph is required for the bidirectional
494+ * algorithm.
495+ * @param text the styled text to display
496+ * @param frc contains information about a graphics device which is needed
497+ * to measure the text correctly.
498+ * Text measurements can vary slightly depending on the
499+ * device resolution, and attributes such as antialiasing. This
500+ * parameter does not specify a translation between the
501+ * <code>TextLayout</code> and user space.
502+ */
503+ public TextLayout(AttributedCharacterIterator text, FontRenderContext frc) {
504+
505+ if (text == null) {
506+ throw new IllegalArgumentException("Null iterator passed to TextLayout constructor.");
507+ }
508+
509+ int start = text.getBeginIndex();
510+ int limit = text.getEndIndex();
511+ if (start == limit) {
512+ throw new IllegalArgumentException("Zero length iterator passed to TextLayout constructor.");
513+ }
514+
515+ int len = limit - start;
516+ text.first();
517+ char[] chars = new char[len];
518+ int n = 0;
519+ for (char c = text.first(); c != text.DONE; c = text.next()) {
520+ chars[n++] = c;
521+ }
522+
523+ text.first();
524+ if (text.getRunLimit() == limit) {
525+
526+ Map attributes = text.getAttributes();
527+ Font font = singleFont(chars, 0, len, attributes);
528+ if (font != null) {
529+ fastInit(chars, font, attributes, frc);
530+ return;
531+ }
532+ }
533+
534+ standardInit(text, chars, frc);
535+ }
536+
537+ /**
538+ * Creates a <code>TextLayout</code> from a {@link TextLine} and
539+ * some paragraph data. This method is used by {@link TextMeasurer}.
540+ * @param textLine the line measurement attributes to apply to the
541+ * the resulting <code>TextLayout</code>
542+ * @param baseline the baseline of the text
543+ * @param baselineOffsets the baseline offsets for this
544+ * <code>TextLayout</code>. This should already be normalized to
545+ * <code>baseline</code>
546+ * @param justifyRatio <code>0</code> if the <code>TextLayout</code>
547+ * cannot be justified; <code>1</code> otherwise.
548+ */
549+ TextLayout(TextLine textLine,
550+ byte baseline,
551+ float[] baselineOffsets,
552+ float justifyRatio) {
553+
554+ this.characterCount = textLine.characterCount();
555+ this.baseline = baseline;
556+ this.baselineOffsets = baselineOffsets;
557+ this.textLine = textLine;
558+ this.justifyRatio = justifyRatio;
559+ }
560+
561+ /**
562+ * Initialize the paragraph-specific data.
563+ */
564+ private void paragraphInit(byte aBaseline, CoreMetrics lm, Map paragraphAttrs, char[] text) {
565+
566+ baseline = aBaseline;
567+
568+ // normalize to current baseline
569+ baselineOffsets = TextLine.getNormalizedOffsets(lm.baselineOffsets, baseline);
570+
571+ justifyRatio = AttributeValues.getJustification(paragraphAttrs);
572+ NumericShaper shaper = AttributeValues.getNumericShaping(paragraphAttrs);
573+ if (shaper != null) {
574+ shaper.shape(text, 0, text.length);
575+ }
576+ }
577+
578+ /*
579+ * the fast init generates a single glyph set. This requires:
580+ * all one style
581+ * all renderable by one font (ie no embedded graphics)
582+ * all on one baseline
583+ */
584+ private void fastInit(char[] chars, Font font, Map attrs, FontRenderContext frc) {
585+ // Object vf = attrs.get(TextAttribute.ORIENTATION);
586+ // isVerticalLine = TextAttribute.ORIENTATION_VERTICAL.equals(vf);
587+ isVerticalLine = false;
588+
589+ LineMetrics lm = font.getLineMetrics(chars, 0, chars.length, frc);
590+ CoreMetrics cm = CoreMetrics.get(lm);
591+ byte glyphBaseline = (byte) cm.baselineIndex;
592+
593+ if (attrs == null) {
594+ baseline = glyphBaseline;
595+ baselineOffsets = cm.baselineOffsets;
596+ justifyRatio = 1.0f;
597+ } else {
598+ paragraphInit(glyphBaseline, cm, attrs, chars);
599+ }
600+
601+ characterCount = chars.length;
602+
603+ textLine = TextLine.fastCreateTextLine(frc, chars, font, cm, attrs);
604+ }
605+
606+ /*
607+ * the standard init generates multiple glyph sets based on style,
608+ * renderable, and baseline runs.
609+ * @param chars the text in the iterator, extracted into a char array
610+ */
611+ private void standardInit(AttributedCharacterIterator text, char[] chars, FontRenderContext frc) {
612+
613+ characterCount = chars.length;
614+
615+ // set paragraph attributes
616+ {
617+ // If there's an embedded graphic at the start of the
618+ // paragraph, look for the first non-graphic character
619+ // and use it and its font to initialize the paragraph.
620+ // If not, use the first graphic to initialize.
621+
622+ Map paragraphAttrs = text.getAttributes();
623+
624+ boolean haveFont = TextLine.advanceToFirstFont(text);
625+
626+ if (haveFont) {
627+ Font defaultFont = TextLine.getFontAtCurrentPos(text);
628+ int charsStart = text.getIndex() - text.getBeginIndex();
629+ LineMetrics lm = defaultFont.getLineMetrics(chars, charsStart, charsStart+1, frc);
630+ CoreMetrics cm = CoreMetrics.get(lm);
631+ paragraphInit((byte)cm.baselineIndex, cm, paragraphAttrs, chars);
632+ }
633+ else {
634+ // hmmm what to do here? Just try to supply reasonable
635+ // values I guess.
636+
637+ GraphicAttribute graphic = (GraphicAttribute)
638+ paragraphAttrs.get(TextAttribute.CHAR_REPLACEMENT);
639+ byte defaultBaseline = getBaselineFromGraphic(graphic);
640+ CoreMetrics cm = GraphicComponent.createCoreMetrics(graphic);
641+ paragraphInit(defaultBaseline, cm, paragraphAttrs, chars);
642+ }
643+ }
644+
645+ textLine = TextLine.standardCreateTextLine(frc, text, chars, baselineOffsets);
646+ }
647+
648+ /*
649+ * A utility to rebuild the ascent/descent/leading/advance cache.
650+ * You'll need to call this if you clone and mutate (like justification,
651+ * editing methods do)
652+ */
653+ private void ensureCache() {
654+ if (!cacheIsValid) {
655+ buildCache();
656+ }
657+ }
658+
659+ private void buildCache() {
660+ lineMetrics = textLine.getMetrics();
661+
662+ // compute visibleAdvance
663+ if (textLine.isDirectionLTR()) {
664+
665+ int lastNonSpace = characterCount-1;
666+ while (lastNonSpace != -1) {
667+ int logIndex = textLine.visualToLogical(lastNonSpace);
668+ if (!textLine.isCharSpace(logIndex)) {
669+ break;
670+ }
671+ else {
672+ --lastNonSpace;
673+ }
674+ }
675+ if (lastNonSpace == characterCount-1) {
676+ visibleAdvance = lineMetrics.advance;
677+ }
678+ else if (lastNonSpace == -1) {
679+ visibleAdvance = 0;
680+ }
681+ else {
682+ int logIndex = textLine.visualToLogical(lastNonSpace);
683+ visibleAdvance = textLine.getCharLinePosition(logIndex)
684+ + textLine.getCharAdvance(logIndex);
685+ }
686+ }
687+ else {
688+
689+ int leftmostNonSpace = 0;
690+ while (leftmostNonSpace != characterCount) {
691+ int logIndex = textLine.visualToLogical(leftmostNonSpace);
692+ if (!textLine.isCharSpace(logIndex)) {
693+ break;
694+ }
695+ else {
696+ ++leftmostNonSpace;
697+ }
698+ }
699+ if (leftmostNonSpace == characterCount) {
700+ visibleAdvance = 0;
701+ }
702+ else if (leftmostNonSpace == 0) {
703+ visibleAdvance = lineMetrics.advance;
704+ }
705+ else {
706+ int logIndex = textLine.visualToLogical(leftmostNonSpace);
707+ float pos = textLine.getCharLinePosition(logIndex);
708+ visibleAdvance = lineMetrics.advance - pos;
709+ }
710+ }
711+
712+ // naturalBounds, boundsRect will be generated on demand
713+ naturalBounds = null;
714+ boundsRect = null;
715+
716+ // hashCode will be regenerated on demand
717+ hashCodeCache = 0;
718+
719+ cacheIsValid = true;
720+ }
721+
722+ /**
723+ * The 'natural bounds' encloses all the carets the layout can draw.
724+ *
725+ */
726+ private Rectangle2D getNaturalBounds() {
727+ ensureCache();
728+
729+ if (naturalBounds == null) {
730+ naturalBounds = textLine.getItalicBounds();
731+ }
732+
733+ return naturalBounds;
734+ }
735+
736+ /**
737+ * Creates a copy of this <code>TextLayout</code>.
738+ */
739+ protected Object clone() {
740+ /*
741+ * !!! I think this is safe. Once created, nothing mutates the
742+ * glyphvectors or arrays. But we need to make sure.
743+ * {jbr} actually, that's not quite true. The justification code
744+ * mutates after cloning. It doesn't actually change the glyphvectors
745+ * (that's impossible) but it replaces them with justified sets. This
746+ * is a problem for GlyphIterator creation, since new GlyphIterators
747+ * are created by cloning a prototype. If the prototype has outdated
748+ * glyphvectors, so will the new ones. A partial solution is to set the
749+ * prototypical GlyphIterator to null when the glyphvectors change. If
750+ * you forget this one time, you're hosed.
751+ */
752+ try {
753+ return super.clone();
754+ }
755+ catch (CloneNotSupportedException e) {
756+ throw new InternalError();
757+ }
758+ }
759+
760+ /*
761+ * Utility to throw an expection if an invalid TextHitInfo is passed
762+ * as a parameter. Avoids code duplication.
763+ */
764+ private void checkTextHit(TextHitInfo hit) {
765+ if (hit == null) {
766+ throw new IllegalArgumentException("TextHitInfo is null.");
767+ }
768+
769+ if (hit.getInsertionIndex() < 0 ||
770+ hit.getInsertionIndex() > characterCount) {
771+ throw new IllegalArgumentException("TextHitInfo is out of range");
772+ }
773+ }
774+
775+ /**
776+ * Creates a copy of this <code>TextLayout</code> justified to the
777+ * specified width.
778+ * <p>
779+ * If this <code>TextLayout</code> has already been justified, an
780+ * exception is thrown. If this <code>TextLayout</code> object's
781+ * justification ratio is zero, a <code>TextLayout</code> identical
782+ * to this <code>TextLayout</code> is returned.
783+ * @param justificationWidth the width to use when justifying the line.
784+ * For best results, it should not be too different from the current
785+ * advance of the line.
786+ * @return a <code>TextLayout</code> justified to the specified width.
787+ * @exception Error if this layout has already been justified, an Error is
788+ * thrown.
789+ */
790+ public TextLayout getJustifiedLayout(float justificationWidth) {
791+
792+ if (justificationWidth <= 0) {
793+ throw new IllegalArgumentException("justificationWidth <= 0 passed to TextLayout.getJustifiedLayout()");
794+ }
795+
796+ if (justifyRatio == ALREADY_JUSTIFIED) {
797+ throw new Error("Can't justify again.");
798+ }
799+
800+ ensureCache(); // make sure textLine is not null
801+
802+ // default justification range to exclude trailing logical whitespace
803+ int limit = characterCount;
804+ while (limit > 0 && textLine.isCharWhitespace(limit-1)) {
805+ --limit;
806+ }
807+
808+ TextLine newLine = textLine.getJustifiedLine(justificationWidth, justifyRatio, 0, limit);
809+ if (newLine != null) {
810+ return new TextLayout(newLine, baseline, baselineOffsets, ALREADY_JUSTIFIED);
811+ }
812+
813+ return this;
814+ }
815+
816+ /**
817+ * Justify this layout. Overridden by subclassers to control justification
818+ * (if there were subclassers, that is...)
819+ *
820+ * The layout will only justify if the paragraph attributes (from the
821+ * source text, possibly defaulted by the layout attributes) indicate a
822+ * non-zero justification ratio. The text will be justified to the
823+ * indicated width. The current implementation also adjusts hanging
824+ * punctuation and trailing whitespace to overhang the justification width.
825+ * Once justified, the layout may not be rejustified.
826+ * <p>
827+ * Some code may rely on immutablity of layouts. Subclassers should not
828+ * call this directly, but instead should call getJustifiedLayout, which
829+ * will call this method on a clone of this layout, preserving
830+ * the original.
831+ *
832+ * @param justificationWidth the width to use when justifying the line.
833+ * For best results, it should not be too different from the current
834+ * advance of the line.
835+ * @see #getJustifiedLayout(float)
836+ */
837+ protected void handleJustify(float justificationWidth) {
838+ // never called
839+ }
840+
841+
842+ /**
843+ * Returns the baseline for this <code>TextLayout</code>.
844+ * The baseline is one of the values defined in <code>Font</code>,
845+ * which are roman, centered and hanging. Ascent and descent are
846+ * relative to this baseline. The <code>baselineOffsets</code>
847+ * are also relative to this baseline.
848+ * @return the baseline of this <code>TextLayout</code>.
849+ * @see #getBaselineOffsets()
850+ * @see Font
851+ */
852+ public byte getBaseline() {
853+ return baseline;
854+ }
855+
856+ /**
857+ * Returns the offsets array for the baselines used for this
858+ * <code>TextLayout</code>.
859+ * <p>
860+ * The array is indexed by one of the values defined in
861+ * <code>Font</code>, which are roman, centered and hanging. The
862+ * values are relative to this <code>TextLayout</code> object's
863+ * baseline, so that <code>getBaselineOffsets[getBaseline()] == 0</code>.
864+ * Offsets are added to the position of the <code>TextLayout</code>
865+ * object's baseline to get the position for the new baseline.
866+ * @return the offsets array containing the baselines used for this
867+ * <code>TextLayout</code>.
868+ * @see #getBaseline()
869+ * @see Font
870+ */
871+ public float[] getBaselineOffsets() {
872+ float[] offsets = new float[baselineOffsets.length];
873+ System.arraycopy(baselineOffsets, 0, offsets, 0, offsets.length);
874+ return offsets;
875+ }
876+
877+ /**
878+ * Returns the advance of this <code>TextLayout</code>.
879+ * The advance is the distance from the origin to the advance of the
880+ * rightmost (bottommost) character. This is in baseline-relative
881+ * coordinates.
882+ * @return the advance of this <code>TextLayout</code>.
883+ */
884+ public float getAdvance() {
885+ ensureCache();
886+ return lineMetrics.advance;
887+ }
888+
889+ /**
890+ * Returns the advance of this <code>TextLayout</code>, minus trailing
891+ * whitespace. This is in baseline-relative coordinates.
892+ * @return the advance of this <code>TextLayout</code> without the
893+ * trailing whitespace.
894+ * @see #getAdvance()
895+ */
896+ public float getVisibleAdvance() {
897+ ensureCache();
898+ return visibleAdvance;
899+ }
900+
901+ /**
902+ * Returns the ascent of this <code>TextLayout</code>.
903+ * The ascent is the distance from the top (right) of the
904+ * <code>TextLayout</code> to the baseline. It is always either
905+ * positive or zero. The ascent is sufficient to
906+ * accomodate superscripted text and is the maximum of the sum of the
907+ * ascent, offset, and baseline of each glyph. The ascent is
908+ * the maximum ascent from the baseline of all the text in the
909+ * TextLayout. It is in baseline-relative coordinates.
910+ * @return the ascent of this <code>TextLayout</code>.
911+ */
912+ public float getAscent() {
913+ ensureCache();
914+ return lineMetrics.ascent;
915+ }
916+
917+ /**
918+ * Returns the descent of this <code>TextLayout</code>.
919+ * The descent is the distance from the baseline to the bottom (left) of
920+ * the <code>TextLayout</code>. It is always either positive or zero.
921+ * The descent is sufficient to accomodate subscripted text and is the
922+ * maximum of the sum of the descent, offset, and baseline of each glyph.
923+ * This is the maximum descent from the baseline of all the text in
924+ * the TextLayout. It is in baseline-relative coordinates.
925+ * @return the descent of this <code>TextLayout</code>.
926+ */
927+ public float getDescent() {
928+ ensureCache();
929+ return lineMetrics.descent;
930+ }
931+
932+ /**
933+ * Returns the leading of the <code>TextLayout</code>.
934+ * The leading is the suggested interline spacing for this
935+ * <code>TextLayout</code>. This is in baseline-relative
936+ * coordinates.
937+ * <p>
938+ * The leading is computed from the leading, descent, and baseline
939+ * of all glyphvectors in the <code>TextLayout</code>. The algorithm
940+ * is roughly as follows:
941+ * <blockquote><pre>
942+ * maxD = 0;
943+ * maxDL = 0;
944+ * for (GlyphVector g in all glyphvectors) {
945+ * maxD = max(maxD, g.getDescent() + offsets[g.getBaseline()]);
946+ * maxDL = max(maxDL, g.getDescent() + g.getLeading() +
947+ * offsets[g.getBaseline()]);
948+ * }
949+ * return maxDL - maxD;
950+ * </pre></blockquote>
951+ * @return the leading of this <code>TextLayout</code>.
952+ */
953+ public float getLeading() {
954+ ensureCache();
955+ return lineMetrics.leading;
956+ }
957+
958+ /**
959+ * Returns the bounds of this <code>TextLayout</code>.
960+ * The bounds are in standard coordinates.
961+ * <p>Due to rasterization effects, this bounds might not enclose all of the
962+ * pixels rendered by the TextLayout.</p>
963+ * It might not coincide exactly with the ascent, descent,
964+ * origin or advance of the <code>TextLayout</code>.
965+ * @return a {@link Rectangle2D} that is the bounds of this
966+ * <code>TextLayout</code>.
967+ */
968+ public Rectangle2D getBounds() {
969+ ensureCache();
970+
971+ if (boundsRect == null) {
972+ Rectangle2D vb = textLine.getVisualBounds();
973+ if (dx != 0 || dy != 0) {
974+ vb.setRect(vb.getX() - dx,
975+ vb.getY() - dy,
976+ vb.getWidth(),
977+ vb.getHeight());
978+ }
979+ boundsRect = vb;
980+ }
981+
982+ Rectangle2D bounds = new Rectangle2D.Float();
983+ bounds.setRect(boundsRect);
984+
985+ return bounds;
986+ }
987+
988+ /**
989+ * Returns the pixel bounds of this <code>TextLayout</code> when
990+ * rendered in a graphics with the given
991+ * <code>FontRenderContext</code> at the given location. The
992+ * graphics render context need not be the same as the
993+ * <code>FontRenderContext</code> used to create this
994+ * <code>TextLayout</code>, and can be null. If it is null, the
995+ * <code>FontRenderContext</code> of this <code>TextLayout</code>
996+ * is used.
997+ * @param frc the <code>FontRenderContext</code> of the <code>Graphics</code>.
998+ * @param x the x-coordinate at which to render this <code>TextLayout</code>.
999+ * @param y the y-coordinate at which to render this <code>TextLayout</code>.
1000+ * @return a <code>Rectangle</code> bounding the pixels that would be affected.
1001+ * @see GlyphVector#getPixelBounds
1002+ * @since 1.6
1003+ */
1004+ public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) {
1005+ return textLine.getPixelBounds(frc, x, y);
1006+ }
1007+
1008+ /**
1009+ * Returns <code>true</code> if this <code>TextLayout</code> has
1010+ * a left-to-right base direction or <code>false</code> if it has
1011+ * a right-to-left base direction. The <code>TextLayout</code>
1012+ * has a base direction of either left-to-right (LTR) or
1013+ * right-to-left (RTL). The base direction is independent of the
1014+ * actual direction of text on the line, which may be either LTR,
1015+ * RTL, or mixed. Left-to-right layouts by default should position
1016+ * flush left. If the layout is on a tabbed line, the
1017+ * tabs run left to right, so that logically successive layouts position
1018+ * left to right. The opposite is true for RTL layouts. By default they
1019+ * should position flush left, and tabs run right-to-left.
1020+ * @return <code>true</code> if the base direction of this
1021+ * <code>TextLayout</code> is left-to-right; <code>false</code>
1022+ * otherwise.
1023+ */
1024+ public boolean isLeftToRight() {
1025+ return textLine.isDirectionLTR();
1026+ }
1027+
1028+ /**
1029+ * Returns <code>true</code> if this <code>TextLayout</code> is vertical.
1030+ * @return <code>true</code> if this <code>TextLayout</code> is vertical;
1031+ * <code>false</code> otherwise.
1032+ */
1033+ public boolean isVertical() {
1034+ return isVerticalLine;
1035+ }
1036+
1037+ /**
1038+ * Returns the number of characters represented by this
1039+ * <code>TextLayout</code>.
1040+ * @return the number of characters in this <code>TextLayout</code>.
1041+ */
1042+ public int getCharacterCount() {
1043+ return characterCount;
1044+ }
1045+
1046+ /*
1047+ * carets and hit testing
1048+ *
1049+ * Positions on a text line are represented by instances of TextHitInfo.
1050+ * Any TextHitInfo with characterOffset between 0 and characterCount-1,
1051+ * inclusive, represents a valid position on the line. Additionally,
1052+ * [-1, trailing] and [characterCount, leading] are valid positions, and
1053+ * represent positions at the logical start and end of the line,
1054+ * respectively.
1055+ *
1056+ * The characterOffsets in TextHitInfo's used and returned by TextLayout
1057+ * are relative to the beginning of the text layout, not necessarily to
1058+ * the beginning of the text storage the client is using.
1059+ *
1060+ *
1061+ * Every valid TextHitInfo has either one or two carets associated with it.
1062+ * A caret is a visual location in the TextLayout indicating where text at
1063+ * the TextHitInfo will be displayed on screen. If a TextHitInfo
1064+ * represents a location on a directional boundary, then there are two
1065+ * possible visible positions for newly inserted text. Consider the
1066+ * following example, in which capital letters indicate right-to-left text,
1067+ * and the overall line direction is left-to-right:
1068+ *
1069+ * Text Storage: [ a, b, C, D, E, f ]
1070+ * Display: a b E D C f
1071+ *
1072+ * The text hit info (1, t) represents the trailing side of 'b'. If 'q',
1073+ * a left-to-right character is inserted into the text storage at this
1074+ * location, it will be displayed between the 'b' and the 'E':
1075+ *
1076+ * Text Storage: [ a, b, q, C, D, E, f ]
1077+ * Display: a b q E D C f
1078+ *
1079+ * However, if a 'W', which is right-to-left, is inserted into the storage
1080+ * after 'b', the storage and display will be:
1081+ *
1082+ * Text Storage: [ a, b, W, C, D, E, f ]
1083+ * Display: a b E D C W f
1084+ *
1085+ * So, for the original text storage, two carets should be displayed for
1086+ * location (1, t): one visually between 'b' and 'E' and one visually
1087+ * between 'C' and 'f'.
1088+ *
1089+ *
1090+ * When two carets are displayed for a TextHitInfo, one caret is the
1091+ * 'strong' caret and the other is the 'weak' caret. The strong caret
1092+ * indicates where an inserted character will be displayed when that
1093+ * character's direction is the same as the direction of the TextLayout.
1094+ * The weak caret shows where an character inserted character will be
1095+ * displayed when the character's direction is opposite that of the
1096+ * TextLayout.
1097+ *
1098+ *
1099+ * Clients should not be overly concerned with the details of correct
1100+ * caret display. TextLayout.getCaretShapes(TextHitInfo) will return an
1101+ * array of two paths representing where carets should be displayed.
1102+ * The first path in the array is the strong caret; the second element,
1103+ * if non-null, is the weak caret. If the second element is null,
1104+ * then there is no weak caret for the given TextHitInfo.
1105+ *
1106+ *
1107+ * Since text can be visually reordered, logically consecutive
1108+ * TextHitInfo's may not be visually consecutive. One implication of this
1109+ * is that a client cannot tell from inspecting a TextHitInfo whether the
1110+ * hit represents the first (or last) caret in the layout. Clients
1111+ * can call getVisualOtherHit(); if the visual companion is
1112+ * (-1, TRAILING) or (characterCount, LEADING), then the hit is at the
1113+ * first (last) caret position in the layout.
1114+ */
1115+
1116+ private float[] getCaretInfo(int caret,
1117+ Rectangle2D bounds,
1118+ float[] info) {
1119+
1120+ float top1X, top2X;
1121+ float bottom1X, bottom2X;
1122+
1123+ if (caret == 0 || caret == characterCount) {
1124+
1125+ float pos;
1126+ int logIndex;
1127+ if (caret == characterCount) {
1128+ logIndex = textLine.visualToLogical(characterCount-1);
1129+ pos = textLine.getCharLinePosition(logIndex)
1130+ + textLine.getCharAdvance(logIndex);
1131+ }
1132+ else {
1133+ logIndex = textLine.visualToLogical(caret);
1134+ pos = textLine.getCharLinePosition(logIndex);
1135+ }
1136+ float angle = textLine.getCharAngle(logIndex);
1137+ float shift = textLine.getCharShift(logIndex);
1138+ pos += angle * shift;
1139+ top1X = top2X = pos + angle*textLine.getCharAscent(logIndex);
1140+ bottom1X = bottom2X = pos - angle*textLine.getCharDescent(logIndex);
1141+ }
1142+ else {
1143+
1144+ {
1145+ int logIndex = textLine.visualToLogical(caret-1);
1146+ float angle1 = textLine.getCharAngle(logIndex);
1147+ float pos1 = textLine.getCharLinePosition(logIndex)
1148+ + textLine.getCharAdvance(logIndex);
1149+ if (angle1 != 0) {
1150+ pos1 += angle1 * textLine.getCharShift(logIndex);
1151+ top1X = pos1 + angle1*textLine.getCharAscent(logIndex);
1152+ bottom1X = pos1 - angle1*textLine.getCharDescent(logIndex);
1153+ }
1154+ else {
1155+ top1X = bottom1X = pos1;
1156+ }
1157+ }
1158+ {
1159+ int logIndex = textLine.visualToLogical(caret);
1160+ float angle2 = textLine.getCharAngle(logIndex);
1161+ float pos2 = textLine.getCharLinePosition(logIndex);
1162+ if (angle2 != 0) {
1163+ pos2 += angle2*textLine.getCharShift(logIndex);
1164+ top2X = pos2 + angle2*textLine.getCharAscent(logIndex);
1165+ bottom2X = pos2 - angle2*textLine.getCharDescent(logIndex);
1166+ }
1167+ else {
1168+ top2X = bottom2X = pos2;
1169+ }
1170+ }
1171+ }
1172+
1173+ float topX = (top1X + top2X) / 2;
1174+ float bottomX = (bottom1X + bottom2X) / 2;
1175+
1176+ if (info == null) {
1177+ info = new float[2];
1178+ }
1179+
1180+ if (isVerticalLine) {
1181+ info[1] = (float) ((topX - bottomX) / bounds.getWidth());
1182+ info[0] = (float) (topX + (info[1]*bounds.getX()));
1183+ }
1184+ else {
1185+ info[1] = (float) ((topX - bottomX) / bounds.getHeight());
1186+ info[0] = (float) (bottomX + (info[1]*bounds.getMaxY()));
1187+ }
1188+
1189+ return info;
1190+ }
1191+
1192+ /**
1193+ * Returns information about the caret corresponding to <code>hit</code>.
1194+ * The first element of the array is the intersection of the caret with
1195+ * the baseline, as a distance along the baseline. The second element
1196+ * of the array is the inverse slope (run/rise) of the caret, measured
1197+ * with respect to the baseline at that point.
1198+ * <p>
1199+ * This method is meant for informational use. To display carets, it
1200+ * is better to use <code>getCaretShapes</code>.
1201+ * @param hit a hit on a character in this <code>TextLayout</code>
1202+ * @param bounds the bounds to which the caret info is constructed.
1203+ * The bounds is in baseline-relative coordinates.
1204+ * @return a two-element array containing the position and slope of
1205+ * the caret. The returned caret info is in baseline-relative coordinates.
1206+ * @see #getCaretShapes(int, Rectangle2D, TextLayout.CaretPolicy)
1207+ * @see Font#getItalicAngle
1208+ */
1209+ public float[] getCaretInfo(TextHitInfo hit, Rectangle2D bounds) {
1210+ ensureCache();
1211+ checkTextHit(hit);
1212+
1213+ return getCaretInfoTestInternal(hit, bounds);
1214+ }
1215+
1216+ // this version provides extra info in the float array
1217+ // the first two values are as above
1218+ // the next four values are the endpoints of the caret, as computed
1219+ // using the hit character's offset (baseline + ssoffset) and
1220+ // natural ascent and descent.
1221+ // these values are trimmed to the bounds where required to fit,
1222+ // but otherwise independent of it.
1223+ private float[] getCaretInfoTestInternal(TextHitInfo hit, Rectangle2D bounds) {
1224+ ensureCache();
1225+ checkTextHit(hit);
1226+
1227+ float[] info = new float[6];
1228+
1229+ // get old data first
1230+ getCaretInfo(hitToCaret(hit), bounds, info);
1231+
1232+ // then add our new data
1233+ double iangle, ixbase, p1x, p1y, p2x, p2y;
1234+
1235+ int charix = hit.getCharIndex();
1236+ boolean lead = hit.isLeadingEdge();
1237+ boolean ltr = textLine.isDirectionLTR();
1238+ boolean horiz = !isVertical();
1239+
1240+ if (charix == -1 || charix == characterCount) {
1241+ // !!! note: want non-shifted, baseline ascent and descent here!
1242+ // TextLine should return appropriate line metrics object for these values
1243+ TextLineMetrics m = textLine.getMetrics();
1244+ boolean low = ltr == (charix == -1);
1245+ iangle = 0;
1246+ if (horiz) {
1247+ p1x = p2x = low ? 0 : m.advance;
1248+ p1y = -m.ascent;
1249+ p2y = m.descent;
1250+ } else {
1251+ p1y = p2y = low ? 0 : m.advance;
1252+ p1x = m.descent;
1253+ p2x = m.ascent;
1254+ }
1255+ } else {
1256+ CoreMetrics thiscm = textLine.getCoreMetricsAt(charix);
1257+ iangle = thiscm.italicAngle;
1258+ ixbase = textLine.getCharLinePosition(charix, lead);
1259+ if (thiscm.baselineIndex < 0) {
1260+ // this is a graphic, no italics, use entire line height for caret
1261+ TextLineMetrics m = textLine.getMetrics();
1262+ if (horiz) {
1263+ p1x = p2x = ixbase;
1264+ if (thiscm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
1265+ p1y = -m.ascent;
1266+ p2y = p1y + thiscm.height;
1267+ } else {
1268+ p2y = m.descent;
1269+ p1y = p2y - thiscm.height;
1270+ }
1271+ } else {
1272+ p1y = p2y = ixbase;
1273+ p1x = m.descent;
1274+ p2x = m.ascent;
1275+ // !!! top/bottom adjustment not implemented for vertical
1276+ }
1277+ } else {
1278+ float bo = baselineOffsets[thiscm.baselineIndex];
1279+ if (horiz) {
1280+ ixbase += iangle * thiscm.ssOffset;
1281+ p1x = ixbase + iangle * thiscm.ascent;
1282+ p2x = ixbase - iangle * thiscm.descent;
1283+ p1y = bo - thiscm.ascent;
1284+ p2y = bo + thiscm.descent;
1285+ } else {
1286+ ixbase -= iangle * thiscm.ssOffset;
1287+ p1y = ixbase + iangle * thiscm.ascent;
1288+ p2y = ixbase - iangle * thiscm.descent;
1289+ p1x = bo + thiscm.ascent;
1290+ p2x = bo + thiscm.descent;
1291+ }
1292+ }
1293+ }
1294+
1295+ info[2] = (float)p1x;
1296+ info[3] = (float)p1y;
1297+ info[4] = (float)p2x;
1298+ info[5] = (float)p2y;
1299+
1300+ return info;
1301+ }
1302+
1303+ /**
1304+ * Returns information about the caret corresponding to <code>hit</code>.
1305+ * This method is a convenience overload of <code>getCaretInfo</code> and
1306+ * uses the natural bounds of this <code>TextLayout</code>.
1307+ * @param hit a hit on a character in this <code>TextLayout</code>
1308+ * @return the information about a caret corresponding to a hit. The
1309+ * returned caret info is in baseline-relative coordinates.
1310+ */
1311+ public float[] getCaretInfo(TextHitInfo hit) {
1312+
1313+ return getCaretInfo(hit, getNaturalBounds());
1314+ }
1315+
1316+ /**
1317+ * Returns a caret index corresponding to <code>hit</code>.
1318+ * Carets are numbered from left to right (top to bottom) starting from
1319+ * zero. This always places carets next to the character hit, on the
1320+ * indicated side of the character.
1321+ * @param hit a hit on a character in this <code>TextLayout</code>
1322+ * @return a caret index corresponding to the specified hit.
1323+ */
1324+ private int hitToCaret(TextHitInfo hit) {
1325+
1326+ int hitIndex = hit.getCharIndex();
1327+
1328+ if (hitIndex < 0) {
1329+ return textLine.isDirectionLTR() ? 0 : characterCount;
1330+ } else if (hitIndex >= characterCount) {
1331+ return textLine.isDirectionLTR() ? characterCount : 0;
1332+ }
1333+
1334+ int visIndex = textLine.logicalToVisual(hitIndex);
1335+
1336+ if (hit.isLeadingEdge() != textLine.isCharLTR(hitIndex)) {
1337+ ++visIndex;
1338+ }
1339+
1340+ return visIndex;
1341+ }
1342+
1343+ /**
1344+ * Given a caret index, return a hit whose caret is at the index.
1345+ * The hit is NOT guaranteed to be strong!!!
1346+ *
1347+ * @param caret a caret index.
1348+ * @return a hit on this layout whose strong caret is at the requested
1349+ * index.
1350+ */
1351+ private TextHitInfo caretToHit(int caret) {
1352+
1353+ if (caret == 0 || caret == characterCount) {
1354+
1355+ if ((caret == characterCount) == textLine.isDirectionLTR()) {
1356+ return TextHitInfo.leading(characterCount);
1357+ }
1358+ else {
1359+ return TextHitInfo.trailing(-1);
1360+ }
1361+ }
1362+ else {
1363+
1364+ int charIndex = textLine.visualToLogical(caret);
1365+ boolean leading = textLine.isCharLTR(charIndex);
1366+
1367+ return leading? TextHitInfo.leading(charIndex)
1368+ : TextHitInfo.trailing(charIndex);
1369+ }
1370+ }
1371+
1372+ private boolean caretIsValid(int caret) {
1373+
1374+ if (caret == characterCount || caret == 0) {
1375+ return true;
1376+ }
1377+
1378+ int offset = textLine.visualToLogical(caret);
1379+
1380+ if (!textLine.isCharLTR(offset)) {
1381+ offset = textLine.visualToLogical(caret-1);
1382+ if (textLine.isCharLTR(offset)) {
1383+ return true;
1384+ }
1385+ }
1386+
1387+ // At this point, the leading edge of the character
1388+ // at offset is at the given caret.
1389+
1390+ return textLine.caretAtOffsetIsValid(offset);
1391+ }
1392+
1393+ /**
1394+ * Returns the hit for the next caret to the right (bottom); if there
1395+ * is no such hit, returns <code>null</code>.
1396+ * If the hit character index is out of bounds, an
1397+ * {@link IllegalArgumentException} is thrown.
1398+ * @param hit a hit on a character in this layout
1399+ * @return a hit whose caret appears at the next position to the
1400+ * right (bottom) of the caret of the provided hit or <code>null</code>.
1401+ */
1402+ public TextHitInfo getNextRightHit(TextHitInfo hit) {
1403+ ensureCache();
1404+ checkTextHit(hit);
1405+
1406+ int caret = hitToCaret(hit);
1407+
1408+ if (caret == characterCount) {
1409+ return null;
1410+ }
1411+
1412+ do {
1413+ ++caret;
1414+ } while (!caretIsValid(caret));
1415+
1416+ return caretToHit(caret);
1417+ }
1418+
1419+ /**
1420+ * Returns the hit for the next caret to the right (bottom); if no
1421+ * such hit, returns <code>null</code>. The hit is to the right of
1422+ * the strong caret at the specified offset, as determined by the
1423+ * specified policy.
1424+ * The returned hit is the stronger of the two possible
1425+ * hits, as determined by the specified policy.
1426+ * @param offset an insertion offset in this <code>TextLayout</code>.
1427+ * Cannot be less than 0 or greater than this <code>TextLayout</code>
1428+ * object's character count.
1429+ * @param policy the policy used to select the strong caret
1430+ * @return a hit whose caret appears at the next position to the
1431+ * right (bottom) of the caret of the provided hit, or <code>null</code>.
1432+ */
1433+ public TextHitInfo getNextRightHit(int offset, CaretPolicy policy) {
1434+
1435+ if (offset < 0 || offset > characterCount) {
1436+ throw new IllegalArgumentException("Offset out of bounds in TextLayout.getNextRightHit()");
1437+ }
1438+
1439+ if (policy == null) {
1440+ throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getNextRightHit()");
1441+ }
1442+
1443+ TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
1444+ TextHitInfo hit2 = hit1.getOtherHit();
1445+
1446+ TextHitInfo nextHit = getNextRightHit(policy.getStrongCaret(hit1, hit2, this));
1447+
1448+ if (nextHit != null) {
1449+ TextHitInfo otherHit = getVisualOtherHit(nextHit);
1450+ return policy.getStrongCaret(otherHit, nextHit, this);
1451+ }
1452+ else {
1453+ return null;
1454+ }
1455+ }
1456+
1457+ /**
1458+ * Returns the hit for the next caret to the right (bottom); if no
1459+ * such hit, returns <code>null</code>. The hit is to the right of
1460+ * the strong caret at the specified offset, as determined by the
1461+ * default policy.
1462+ * The returned hit is the stronger of the two possible
1463+ * hits, as determined by the default policy.
1464+ * @param offset an insertion offset in this <code>TextLayout</code>.
1465+ * Cannot be less than 0 or greater than the <code>TextLayout</code>
1466+ * object's character count.
1467+ * @return a hit whose caret appears at the next position to the
1468+ * right (bottom) of the caret of the provided hit, or <code>null</code>.
1469+ */
1470+ public TextHitInfo getNextRightHit(int offset) {
1471+
1472+ return getNextRightHit(offset, DEFAULT_CARET_POLICY);
1473+ }
1474+
1475+ /**
1476+ * Returns the hit for the next caret to the left (top); if no such
1477+ * hit, returns <code>null</code>.
1478+ * If the hit character index is out of bounds, an
1479+ * <code>IllegalArgumentException</code> is thrown.
1480+ * @param hit a hit on a character in this <code>TextLayout</code>.
1481+ * @return a hit whose caret appears at the next position to the
1482+ * left (top) of the caret of the provided hit, or <code>null</code>.
1483+ */
1484+ public TextHitInfo getNextLeftHit(TextHitInfo hit) {
1485+ ensureCache();
1486+ checkTextHit(hit);
1487+
1488+ int caret = hitToCaret(hit);
1489+
1490+ if (caret == 0) {
1491+ return null;
1492+ }
1493+
1494+ do {
1495+ --caret;
1496+ } while(!caretIsValid(caret));
1497+
1498+ return caretToHit(caret);
1499+ }
1500+
1501+ /**
1502+ * Returns the hit for the next caret to the left (top); if no
1503+ * such hit, returns <code>null</code>. The hit is to the left of
1504+ * the strong caret at the specified offset, as determined by the
1505+ * specified policy.
1506+ * The returned hit is the stronger of the two possible
1507+ * hits, as determined by the specified policy.
1508+ * @param offset an insertion offset in this <code>TextLayout</code>.
1509+ * Cannot be less than 0 or greater than this <code>TextLayout</code>
1510+ * object's character count.
1511+ * @param policy the policy used to select the strong caret
1512+ * @return a hit whose caret appears at the next position to the
1513+ * left (top) of the caret of the provided hit, or <code>null</code>.
1514+ */
1515+ public TextHitInfo getNextLeftHit(int offset, CaretPolicy policy) {
1516+
1517+ if (policy == null) {
1518+ throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getNextLeftHit()");
1519+ }
1520+
1521+ if (offset < 0 || offset > characterCount) {
1522+ throw new IllegalArgumentException("Offset out of bounds in TextLayout.getNextLeftHit()");
1523+ }
1524+
1525+ TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
1526+ TextHitInfo hit2 = hit1.getOtherHit();
1527+
1528+ TextHitInfo nextHit = getNextLeftHit(policy.getStrongCaret(hit1, hit2, this));
1529+
1530+ if (nextHit != null) {
1531+ TextHitInfo otherHit = getVisualOtherHit(nextHit);
1532+ return policy.getStrongCaret(otherHit, nextHit, this);
1533+ }
1534+ else {
1535+ return null;
1536+ }
1537+ }
1538+
1539+ /**
1540+ * Returns the hit for the next caret to the left (top); if no
1541+ * such hit, returns <code>null</code>. The hit is to the left of
1542+ * the strong caret at the specified offset, as determined by the
1543+ * default policy.
1544+ * The returned hit is the stronger of the two possible
1545+ * hits, as determined by the default policy.
1546+ * @param offset an insertion offset in this <code>TextLayout</code>.
1547+ * Cannot be less than 0 or greater than this <code>TextLayout</code>
1548+ * object's character count.
1549+ * @return a hit whose caret appears at the next position to the
1550+ * left (top) of the caret of the provided hit, or <code>null</code>.
1551+ */
1552+ public TextHitInfo getNextLeftHit(int offset) {
1553+
1554+ return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
1555+ }
1556+
1557+ /**
1558+ * Returns the hit on the opposite side of the specified hit's caret.
1559+ * @param hit the specified hit
1560+ * @return a hit that is on the opposite side of the specified hit's
1561+ * caret.
1562+ */
1563+ public TextHitInfo getVisualOtherHit(TextHitInfo hit) {
1564+
1565+ ensureCache();
1566+ checkTextHit(hit);
1567+
1568+ int hitCharIndex = hit.getCharIndex();
1569+
1570+ int charIndex;
1571+ boolean leading;
1572+
1573+ if (hitCharIndex == -1 || hitCharIndex == characterCount) {
1574+
1575+ int visIndex;
1576+ if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
1577+ visIndex = 0;
1578+ }
1579+ else {
1580+ visIndex = characterCount-1;
1581+ }
1582+
1583+ charIndex = textLine.visualToLogical(visIndex);
1584+
1585+ if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
1586+ // at left end
1587+ leading = textLine.isCharLTR(charIndex);
1588+ }
1589+ else {
1590+ // at right end
1591+ leading = !textLine.isCharLTR(charIndex);
1592+ }
1593+ }
1594+ else {
1595+
1596+ int visIndex = textLine.logicalToVisual(hitCharIndex);
1597+
1598+ boolean movedToRight;
1599+ if (textLine.isCharLTR(hitCharIndex) == hit.isLeadingEdge()) {
1600+ --visIndex;
1601+ movedToRight = false;
1602+ }
1603+ else {
1604+ ++visIndex;
1605+ movedToRight = true;
1606+ }
1607+
1608+ if (visIndex > -1 && visIndex < characterCount) {
1609+ charIndex = textLine.visualToLogical(visIndex);
1610+ leading = movedToRight == textLine.isCharLTR(charIndex);
1611+ }
1612+ else {
1613+ charIndex =
1614+ (movedToRight == textLine.isDirectionLTR())? characterCount : -1;
1615+ leading = charIndex == characterCount;
1616+ }
1617+ }
1618+
1619+ return leading? TextHitInfo.leading(charIndex) :
1620+ TextHitInfo.trailing(charIndex);
1621+ }
1622+
1623+ private double[] getCaretPath(TextHitInfo hit, Rectangle2D bounds) {
1624+ float[] info = getCaretInfo(hit, bounds);
1625+ return new double[] { info[2], info[3], info[4], info[5] };
1626+ }
1627+
1628+ /**
1629+ * Return an array of four floats corresponding the endpoints of the caret
1630+ * x0, y0, x1, y1.
1631+ *
1632+ * This creates a line along the slope of the caret intersecting the
1633+ * baseline at the caret
1634+ * position, and extending from ascent above the baseline to descent below
1635+ * it.
1636+ */
1637+ private double[] getCaretPath(int caret, Rectangle2D bounds,
1638+ boolean clipToBounds) {
1639+
1640+ float[] info = getCaretInfo(caret, bounds, null);
1641+
1642+ double pos = info[0];
1643+ double slope = info[1];
1644+
1645+ double x0, y0, x1, y1;
1646+ double x2 = -3141.59, y2 = -2.7; // values are there to make compiler happy
1647+
1648+ double left = bounds.getX();
1649+ double right = left + bounds.getWidth();
1650+ double top = bounds.getY();
1651+ double bottom = top + bounds.getHeight();
1652+
1653+ boolean threePoints = false;
1654+
1655+ if (isVerticalLine) {
1656+
1657+ if (slope >= 0) {
1658+ x0 = left;
1659+ x1 = right;
1660+ }
1661+ else {
1662+ x1 = left;
1663+ x0 = right;
1664+ }
1665+
1666+ y0 = pos + x0 * slope;
1667+ y1 = pos + x1 * slope;
1668+
1669+ // y0 <= y1, always
1670+
1671+ if (clipToBounds) {
1672+ if (y0 < top) {
1673+ if (slope <= 0 || y1 <= top) {
1674+ y0 = y1 = top;
1675+ }
1676+ else {
1677+ threePoints = true;
1678+ y0 = top;
1679+ y2 = top;
1680+ x2 = x1 + (top-y1)/slope;
1681+ if (y1 > bottom) {
1682+ y1 = bottom;
1683+ }
1684+ }
1685+ }
1686+ else if (y1 > bottom) {
1687+ if (slope >= 0 || y0 >= bottom) {
1688+ y0 = y1 = bottom;
1689+ }
1690+ else {
1691+ threePoints = true;
1692+ y1 = bottom;
1693+ y2 = bottom;
1694+ x2 = x0 + (bottom-x1)/slope;
1695+ }
1696+ }
1697+ }
1698+
1699+ }
1700+ else {
1701+
1702+ if (slope >= 0) {
1703+ y0 = bottom;
1704+ y1 = top;
1705+ }
1706+ else {
1707+ y1 = bottom;
1708+ y0 = top;
1709+ }
1710+
1711+ x0 = pos - y0 * slope;
1712+ x1 = pos - y1 * slope;
1713+
1714+ // x0 <= x1, always
1715+
1716+ if (clipToBounds) {
1717+ if (x0 < left) {
1718+ if (slope <= 0 || x1 <= left) {
1719+ x0 = x1 = left;
1720+ }
1721+ else {
1722+ threePoints = true;
1723+ x0 = left;
1724+ x2 = left;
1725+ y2 = y1 - (left-x1)/slope;
1726+ if (x1 > right) {
1727+ x1 = right;
1728+ }
1729+ }
1730+ }
1731+ else if (x1 > right) {
1732+ if (slope >= 0 || x0 >= right) {
1733+ x0 = x1 = right;
1734+ }
1735+ else {
1736+ threePoints = true;
1737+ x1 = right;
1738+ x2 = right;
1739+ y2 = y0 - (right-x0)/slope;
1740+ }
1741+ }
1742+ }
1743+ }
1744+
1745+ return threePoints?
1746+ new double[] { x0, y0, x2, y2, x1, y1 } :
1747+ new double[] { x0, y0, x1, y1 };
1748+ }
1749+
1750+
1751+ private static GeneralPath pathToShape(double[] path, boolean close, LayoutPathImpl lp) {
1752+ GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD, path.length);
1753+ result.moveTo((float)path[0], (float)path[1]);
1754+ for (int i = 2; i < path.length; i += 2) {
1755+ result.lineTo((float)path[i], (float)path[i+1]);
1756+ }
1757+ if (close) {
1758+ result.closePath();
1759+ }
1760+
1761+ if (lp != null) {
1762+ result = (GeneralPath)lp.mapShape(result);
1763+ }
1764+ return result;
1765+ }
1766+
1767+ /**
1768+ * Returns a {@link Shape} representing the caret at the specified
1769+ * hit inside the specified bounds.
1770+ * @param hit the hit at which to generate the caret
1771+ * @param bounds the bounds of the <code>TextLayout</code> to use
1772+ * in generating the caret. The bounds is in baseline-relative
1773+ * coordinates.
1774+ * @return a <code>Shape</code> representing the caret. The returned
1775+ * shape is in standard coordinates.
1776+ */
1777+ public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds) {
1778+ ensureCache();
1779+ checkTextHit(hit);
1780+
1781+ if (bounds == null) {
1782+ throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCaret()");
1783+ }
1784+
1785+ return pathToShape(getCaretPath(hit, bounds), false, textLine.getLayoutPath());
1786+ }
1787+
1788+ /**
1789+ * Returns a <code>Shape</code> representing the caret at the specified
1790+ * hit inside the natural bounds of this <code>TextLayout</code>.
1791+ * @param hit the hit at which to generate the caret
1792+ * @return a <code>Shape</code> representing the caret. The returned
1793+ * shape is in standard coordinates.
1794+ */
1795+ public Shape getCaretShape(TextHitInfo hit) {
1796+
1797+ return getCaretShape(hit, getNaturalBounds());
1798+ }
1799+
1800+ /**
1801+ * Return the "stronger" of the TextHitInfos. The TextHitInfos
1802+ * should be logical or visual counterparts. They are not
1803+ * checked for validity.
1804+ */
1805+ private final TextHitInfo getStrongHit(TextHitInfo hit1, TextHitInfo hit2) {
1806+
1807+ // right now we're using the following rule for strong hits:
1808+ // A hit on a character with a lower level
1809+ // is stronger than one on a character with a higher level.
1810+ // If this rule ties, the hit on the leading edge of a character wins.
1811+ // If THIS rule ties, hit1 wins. Both rules shouldn't tie, unless the
1812+ // infos aren't counterparts of some sort.
1813+
1814+ byte hit1Level = getCharacterLevel(hit1.getCharIndex());
1815+ byte hit2Level = getCharacterLevel(hit2.getCharIndex());
1816+
1817+ if (hit1Level == hit2Level) {
1818+ if (hit2.isLeadingEdge() && !hit1.isLeadingEdge()) {
1819+ return hit2;
1820+ }
1821+ else {
1822+ return hit1;
1823+ }
1824+ }
1825+ else {
1826+ return (hit1Level < hit2Level)? hit1 : hit2;
1827+ }
1828+ }
1829+
1830+ /**
1831+ * Returns the level of the character at <code>index</code>.
1832+ * Indices -1 and <code>characterCount</code> are assigned the base
1833+ * level of this <code>TextLayout</code>.
1834+ * @param index the index of the character from which to get the level
1835+ * @return the level of the character at the specified index.
1836+ */
1837+ public byte getCharacterLevel(int index) {
1838+
1839+ // hmm, allow indices at endpoints? For now, yes.
1840+ if (index < -1 || index > characterCount) {
1841+ throw new IllegalArgumentException("Index is out of range in getCharacterLevel.");
1842+ }
1843+
1844+ ensureCache();
1845+ if (index == -1 || index == characterCount) {
1846+ return (byte) (textLine.isDirectionLTR()? 0 : 1);
1847+ }
1848+
1849+ return textLine.getCharLevel(index);
1850+ }
1851+
1852+ /**
1853+ * Returns two paths corresponding to the strong and weak caret.
1854+ * @param offset an offset in this <code>TextLayout</code>
1855+ * @param bounds the bounds to which to extend the carets. The
1856+ * bounds is in baseline-relative coordinates.
1857+ * @param policy the specified <code>CaretPolicy</code>
1858+ * @return an array of two paths. Element zero is the strong
1859+ * caret. If there are two carets, element one is the weak caret,
1860+ * otherwise it is <code>null</code>. The returned shapes
1861+ * are in standard coordinates.
1862+ */
1863+ public Shape[] getCaretShapes(int offset, Rectangle2D bounds, CaretPolicy policy) {
1864+
1865+ ensureCache();
1866+
1867+ if (offset < 0 || offset > characterCount) {
1868+ throw new IllegalArgumentException("Offset out of bounds in TextLayout.getCaretShapes()");
1869+ }
1870+
1871+ if (bounds == null) {
1872+ throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCaretShapes()");
1873+ }
1874+
1875+ if (policy == null) {
1876+ throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getCaretShapes()");
1877+ }
1878+
1879+ Shape[] result = new Shape[2];
1880+
1881+ TextHitInfo hit = TextHitInfo.afterOffset(offset);
1882+
1883+ int hitCaret = hitToCaret(hit);
1884+
1885+ LayoutPathImpl lp = textLine.getLayoutPath();
1886+ Shape hitShape = pathToShape(getCaretPath(hit, bounds), false, lp);
1887+ TextHitInfo otherHit = hit.getOtherHit();
1888+ int otherCaret = hitToCaret(otherHit);
1889+
1890+ if (hitCaret == otherCaret) {
1891+ result[0] = hitShape;
1892+ }
1893+ else { // more than one caret
1894+ Shape otherShape = pathToShape(getCaretPath(otherHit, bounds), false, lp);
1895+
1896+ TextHitInfo strongHit = policy.getStrongCaret(hit, otherHit, this);
1897+ boolean hitIsStrong = strongHit.equals(hit);
1898+
1899+ if (hitIsStrong) {// then other is weak
1900+ result[0] = hitShape;
1901+ result[1] = otherShape;
1902+ }
1903+ else {
1904+ result[0] = otherShape;
1905+ result[1] = hitShape;
1906+ }
1907+ }
1908+
1909+ return result;
1910+ }
1911+
1912+ /**
1913+ * Returns two paths corresponding to the strong and weak caret.
1914+ * This method is a convenience overload of <code>getCaretShapes</code>
1915+ * that uses the default caret policy.
1916+ * @param offset an offset in this <code>TextLayout</code>
1917+ * @param bounds the bounds to which to extend the carets. This is
1918+ * in baseline-relative coordinates.
1919+ * @return two paths corresponding to the strong and weak caret as
1920+ * defined by the <code>DEFAULT_CARET_POLICY</code>. These are
1921+ * in standard coordinates.
1922+ */
1923+ public Shape[] getCaretShapes(int offset, Rectangle2D bounds) {
1924+ // {sfb} parameter checking is done in overloaded version
1925+ return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
1926+ }
1927+
1928+ /**
1929+ * Returns two paths corresponding to the strong and weak caret.
1930+ * This method is a convenience overload of <code>getCaretShapes</code>
1931+ * that uses the default caret policy and this <code>TextLayout</code>
1932+ * object's natural bounds.
1933+ * @param offset an offset in this <code>TextLayout</code>
1934+ * @return two paths corresponding to the strong and weak caret as
1935+ * defined by the <code>DEFAULT_CARET_POLICY</code>. These are
1936+ * in standard coordinates.
1937+ */
1938+ public Shape[] getCaretShapes(int offset) {
1939+ // {sfb} parameter checking is done in overloaded version
1940+ return getCaretShapes(offset, getNaturalBounds(), DEFAULT_CARET_POLICY);
1941+ }
1942+
1943+ // A utility to return a path enclosing the given path
1944+ // Path0 must be left or top of path1
1945+ // {jbr} no assumptions about size of path0, path1 anymore.
1946+ private GeneralPath boundingShape(double[] path0, double[] path1) {
1947+
1948+ // Really, we want the path to be a convex hull around all of the
1949+ // points in path0 and path1. But we can get by with less than
1950+ // that. We do need to prevent the two segments which
1951+ // join path0 to path1 from crossing each other. So, if we
1952+ // traverse path0 from top to bottom, we'll traverse path1 from
1953+ // bottom to top (and vice versa).
1954+
1955+ GeneralPath result = pathToShape(path0, false, null);
1956+
1957+ boolean sameDirection;
1958+
1959+ if (isVerticalLine) {
1960+ sameDirection = (path0[1] > path0[path0.length-1]) ==
1961+ (path1[1] > path1[path1.length-1]);
1962+ }
1963+ else {
1964+ sameDirection = (path0[0] > path0[path0.length-2]) ==
1965+ (path1[0] > path1[path1.length-2]);
1966+ }
1967+
1968+ int start;
1969+ int limit;
1970+ int increment;
1971+
1972+ if (sameDirection) {
1973+ start = path1.length-2;
1974+ limit = -2;
1975+ increment = -2;
1976+ }
1977+ else {
1978+ start = 0;
1979+ limit = path1.length;
1980+ increment = 2;
1981+ }
1982+
1983+ for (int i = start; i != limit; i += increment) {
1984+ result.lineTo((float)path1[i], (float)path1[i+1]);
1985+ }
1986+
1987+ result.closePath();
1988+
1989+ return result;
1990+ }
1991+
1992+ // A utility to convert a pair of carets into a bounding path
1993+ // {jbr} Shape is never outside of bounds.
1994+ private GeneralPath caretBoundingShape(int caret0,
1995+ int caret1,
1996+ Rectangle2D bounds) {
1997+
1998+ if (caret0 > caret1) {
1999+ int temp = caret0;
2000+ caret0 = caret1;
2001+ caret1 = temp;
2002+ }
2003+
2004+ return boundingShape(getCaretPath(caret0, bounds, true),
2005+ getCaretPath(caret1, bounds, true));
2006+ }
2007+
2008+ /*
2009+ * A utility to return the path bounding the area to the left (top) of the
2010+ * layout.
2011+ * Shape is never outside of bounds.
2012+ */
2013+ private GeneralPath leftShape(Rectangle2D bounds) {
2014+
2015+ double[] path0;
2016+ if (isVerticalLine) {
2017+ path0 = new double[] { bounds.getX(), bounds.getY(),
2018+ bounds.getX() + bounds.getWidth(),
2019+ bounds.getY() };
2020+ } else {
2021+ path0 = new double[] { bounds.getX(),
2022+ bounds.getY() + bounds.getHeight(),
2023+ bounds.getX(), bounds.getY() };
2024+ }
2025+
2026+ double[] path1 = getCaretPath(0, bounds, true);
2027+
2028+ return boundingShape(path0, path1);
2029+ }
2030+
2031+ /*
2032+ * A utility to return the path bounding the area to the right (bottom) of
2033+ * the layout.
2034+ */
2035+ private GeneralPath rightShape(Rectangle2D bounds) {
2036+ double[] path1;
2037+ if (isVerticalLine) {
2038+ path1 = new double[] {
2039+ bounds.getX(),
2040+ bounds.getY() + bounds.getHeight(),
2041+ bounds.getX() + bounds.getWidth(),
2042+ bounds.getY() + bounds.getHeight()
2043+ };
2044+ } else {
2045+ path1 = new double[] {
2046+ bounds.getX() + bounds.getWidth(),
2047+ bounds.getY() + bounds.getHeight(),
2048+ bounds.getX() + bounds.getWidth(),
2049+ bounds.getY()
2050+ };
2051+ }
2052+
2053+ double[] path0 = getCaretPath(characterCount, bounds, true);
2054+
2055+ return boundingShape(path0, path1);
2056+ }
2057+
2058+ /**
2059+ * Returns the logical ranges of text corresponding to a visual selection.
2060+ * @param firstEndpoint an endpoint of the visual range
2061+ * @param secondEndpoint the other endpoint of the visual range.
2062+ * This endpoint can be less than <code>firstEndpoint</code>.
2063+ * @return an array of integers representing start/limit pairs for the
2064+ * selected ranges.
2065+ * @see #getVisualHighlightShape(TextHitInfo, TextHitInfo, Rectangle2D)
2066+ */
2067+ public int[] getLogicalRangesForVisualSelection(TextHitInfo firstEndpoint,
2068+ TextHitInfo secondEndpoint) {
2069+ ensureCache();
2070+
2071+ checkTextHit(firstEndpoint);
2072+ checkTextHit(secondEndpoint);
2073+
2074+ // !!! probably want to optimize for all LTR text
2075+
2076+ boolean[] included = new boolean[characterCount];
2077+
2078+ int startIndex = hitToCaret(firstEndpoint);
2079+ int limitIndex = hitToCaret(secondEndpoint);
2080+
2081+ if (startIndex > limitIndex) {
2082+ int t = startIndex;
2083+ startIndex = limitIndex;
2084+ limitIndex = t;
2085+ }
2086+
2087+ /*
2088+ * now we have the visual indexes of the glyphs at the start and limit
2089+ * of the selection range walk through runs marking characters that
2090+ * were included in the visual range there is probably a more efficient
2091+ * way to do this, but this ought to work, so hey
2092+ */
2093+
2094+ if (startIndex < limitIndex) {
2095+ int visIndex = startIndex;
2096+ while (visIndex < limitIndex) {
2097+ included[textLine.visualToLogical(visIndex)] = true;
2098+ ++visIndex;
2099+ }
2100+ }
2101+
2102+ /*
2103+ * count how many runs we have, ought to be one or two, but perhaps
2104+ * things are especially weird
2105+ */
2106+ int count = 0;
2107+ boolean inrun = false;
2108+ for (int i = 0; i < characterCount; i++) {
2109+ if (included[i] != inrun) {
2110+ inrun = !inrun;
2111+ if (inrun) {
2112+ count++;
2113+ }
2114+ }
2115+ }
2116+
2117+ int[] ranges = new int[count * 2];
2118+ count = 0;
2119+ inrun = false;
2120+ for (int i = 0; i < characterCount; i++) {
2121+ if (included[i] != inrun) {
2122+ ranges[count++] = i;
2123+ inrun = !inrun;
2124+ }
2125+ }
2126+ if (inrun) {
2127+ ranges[count++] = characterCount;
2128+ }
2129+
2130+ return ranges;
2131+ }
2132+
2133+ /**
2134+ * Returns a path enclosing the visual selection in the specified range,
2135+ * extended to <code>bounds</code>.
2136+ * <p>
2137+ * If the selection includes the leftmost (topmost) position, the selection
2138+ * is extended to the left (top) of <code>bounds</code>. If the
2139+ * selection includes the rightmost (bottommost) position, the selection
2140+ * is extended to the right (bottom) of the bounds. The height
2141+ * (width on vertical lines) of the selection is always extended to
2142+ * <code>bounds</code>.
2143+ * <p>
2144+ * Although the selection is always contiguous, the logically selected
2145+ * text can be discontiguous on lines with mixed-direction text. The
2146+ * logical ranges of text selected can be retrieved using
2147+ * <code>getLogicalRangesForVisualSelection</code>. For example,
2148+ * consider the text 'ABCdef' where capital letters indicate
2149+ * right-to-left text, rendered on a right-to-left line, with a visual
2150+ * selection from 0L (the leading edge of 'A') to 3T (the trailing edge
2151+ * of 'd'). The text appears as follows, with bold underlined areas
2152+ * representing the selection:
2153+ * <br><pre>
2154+ * d<u><b>efCBA </b></u>
2155+ * </pre>
2156+ * The logical selection ranges are 0-3, 4-6 (ABC, ef) because the
2157+ * visually contiguous text is logically discontiguous. Also note that
2158+ * since the rightmost position on the layout (to the right of 'A') is
2159+ * selected, the selection is extended to the right of the bounds.
2160+ * @param firstEndpoint one end of the visual selection
2161+ * @param secondEndpoint the other end of the visual selection
2162+ * @param bounds the bounding rectangle to which to extend the selection.
2163+ * This is in baseline-relative coordinates.
2164+ * @return a <code>Shape</code> enclosing the selection. This is in
2165+ * standard coordinates.
2166+ * @see #getLogicalRangesForVisualSelection(TextHitInfo, TextHitInfo)
2167+ * @see #getLogicalHighlightShape(int, int, Rectangle2D)
2168+ */
2169+ public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
2170+ TextHitInfo secondEndpoint,
2171+ Rectangle2D bounds)
2172+ {
2173+ ensureCache();
2174+
2175+ checkTextHit(firstEndpoint);
2176+ checkTextHit(secondEndpoint);
2177+
2178+ if(bounds == null) {
2179+ throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getVisualHighlightShape()");
2180+ }
2181+
2182+ GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
2183+
2184+ int firstCaret = hitToCaret(firstEndpoint);
2185+ int secondCaret = hitToCaret(secondEndpoint);
2186+
2187+ result.append(caretBoundingShape(firstCaret, secondCaret, bounds),
2188+ false);
2189+
2190+ if (firstCaret == 0 || secondCaret == 0) {
2191+ GeneralPath ls = leftShape(bounds);
2192+ if (!ls.getBounds().isEmpty())
2193+ result.append(ls, false);
2194+ }
2195+
2196+ if (firstCaret == characterCount || secondCaret == characterCount) {
2197+ GeneralPath rs = rightShape(bounds);
2198+ if (!rs.getBounds().isEmpty()) {
2199+ result.append(rs, false);
2200+ }
2201+ }
2202+
2203+ LayoutPathImpl lp = textLine.getLayoutPath();
2204+ if (lp != null) {
2205+ result = (GeneralPath)lp.mapShape(result); // dlf cast safe?
2206+ }
2207+
2208+ return result;
2209+ }
2210+
2211+ /**
2212+ * Returns a <code>Shape</code> enclosing the visual selection in the
2213+ * specified range, extended to the bounds. This method is a
2214+ * convenience overload of <code>getVisualHighlightShape</code> that
2215+ * uses the natural bounds of this <code>TextLayout</code>.
2216+ * @param firstEndpoint one end of the visual selection
2217+ * @param secondEndpoint the other end of the visual selection
2218+ * @return a <code>Shape</code> enclosing the selection. This is
2219+ * in standard coordinates.
2220+ */
2221+ public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
2222+ TextHitInfo secondEndpoint) {
2223+ return getVisualHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
2224+ }
2225+
2226+ /**
2227+ * Returns a <code>Shape</code> enclosing the logical selection in the
2228+ * specified range, extended to the specified <code>bounds</code>.
2229+ * <p>
2230+ * If the selection range includes the first logical character, the
2231+ * selection is extended to the portion of <code>bounds</code> before
2232+ * the start of this <code>TextLayout</code>. If the range includes
2233+ * the last logical character, the selection is extended to the portion
2234+ * of <code>bounds</code> after the end of this <code>TextLayout</code>.
2235+ * The height (width on vertical lines) of the selection is always
2236+ * extended to <code>bounds</code>.
2237+ * <p>
2238+ * The selection can be discontiguous on lines with mixed-direction text.
2239+ * Only those characters in the logical range between start and limit
2240+ * appear selected. For example, consider the text 'ABCdef' where capital
2241+ * letters indicate right-to-left text, rendered on a right-to-left line,
2242+ * with a logical selection from 0 to 4 ('ABCd'). The text appears as
2243+ * follows, with bold standing in for the selection, and underlining for
2244+ * the extension:
2245+ * <br><pre>
2246+ * <u><b>d</b></u>ef<u><b>CBA </b></u>
2247+ * </pre>
2248+ * The selection is discontiguous because the selected characters are
2249+ * visually discontiguous. Also note that since the range includes the
2250+ * first logical character (A), the selection is extended to the portion
2251+ * of the <code>bounds</code> before the start of the layout, which in
2252+ * this case (a right-to-left line) is the right portion of the
2253+ * <code>bounds</code>.
2254+ * @param firstEndpoint an endpoint in the range of characters to select
2255+ * @param secondEndpoint the other endpoint of the range of characters
2256+ * to select. Can be less than <code>firstEndpoint</code>. The range
2257+ * includes the character at min(firstEndpoint, secondEndpoint), but
2258+ * excludes max(firstEndpoint, secondEndpoint).
2259+ * @param bounds the bounding rectangle to which to extend the selection.
2260+ * This is in baseline-relative coordinates.
2261+ * @return an area enclosing the selection. This is in standard
2262+ * coordinates.
2263+ * @see #getVisualHighlightShape(TextHitInfo, TextHitInfo, Rectangle2D)
2264+ */
2265+ public Shape getLogicalHighlightShape(int firstEndpoint,
2266+ int secondEndpoint,
2267+ Rectangle2D bounds) {
2268+ if (bounds == null) {
2269+ throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getLogicalHighlightShape()");
2270+ }
2271+
2272+ ensureCache();
2273+
2274+ if (firstEndpoint > secondEndpoint) {
2275+ int t = firstEndpoint;
2276+ firstEndpoint = secondEndpoint;
2277+ secondEndpoint = t;
2278+ }
2279+
2280+ if(firstEndpoint < 0 || secondEndpoint > characterCount) {
2281+ throw new IllegalArgumentException("Range is invalid in TextLayout.getLogicalHighlightShape()");
2282+ }
2283+
2284+ GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
2285+
2286+ int[] carets = new int[10]; // would this ever not handle all cases?
2287+ int count = 0;
2288+
2289+ if (firstEndpoint < secondEndpoint) {
2290+ int logIndex = firstEndpoint;
2291+ do {
2292+ carets[count++] = hitToCaret(TextHitInfo.leading(logIndex));
2293+ boolean ltr = textLine.isCharLTR(logIndex);
2294+
2295+ do {
2296+ logIndex++;
2297+ } while (logIndex < secondEndpoint && textLine.isCharLTR(logIndex) == ltr);
2298+
2299+ int hitCh = logIndex;
2300+ carets[count++] = hitToCaret(TextHitInfo.trailing(hitCh - 1));
2301+
2302+ if (count == carets.length) {
2303+ int[] temp = new int[carets.length + 10];
2304+ System.arraycopy(carets, 0, temp, 0, count);
2305+ carets = temp;
2306+ }
2307+ } while (logIndex < secondEndpoint);
2308+ }
2309+ else {
2310+ count = 2;
2311+ carets[0] = carets[1] = hitToCaret(TextHitInfo.leading(firstEndpoint));
2312+ }
2313+
2314+ // now create paths for pairs of carets
2315+
2316+ for (int i = 0; i < count; i += 2) {
2317+ result.append(caretBoundingShape(carets[i], carets[i+1], bounds),
2318+ false);
2319+ }
2320+
2321+ if (firstEndpoint != secondEndpoint) {
2322+ if ((textLine.isDirectionLTR() && firstEndpoint == 0) || (!textLine.isDirectionLTR() &&
2323+ secondEndpoint == characterCount)) {
2324+ GeneralPath ls = leftShape(bounds);
2325+ if (!ls.getBounds().isEmpty()) {
2326+ result.append(ls, false);
2327+ }
2328+ }
2329+
2330+ if ((textLine.isDirectionLTR() && secondEndpoint == characterCount) ||
2331+ (!textLine.isDirectionLTR() && firstEndpoint == 0)) {
2332+
2333+ GeneralPath rs = rightShape(bounds);
2334+ if (!rs.getBounds().isEmpty()) {
2335+ result.append(rs, false);
2336+ }
2337+ }
2338+ }
2339+
2340+ LayoutPathImpl lp = textLine.getLayoutPath();
2341+ if (lp != null) {
2342+ result = (GeneralPath)lp.mapShape(result); // dlf cast safe?
2343+ }
2344+ return result;
2345+ }
2346+
2347+ /**
2348+ * Returns a <code>Shape</code> enclosing the logical selection in the
2349+ * specified range, extended to the natural bounds of this
2350+ * <code>TextLayout</code>. This method is a convenience overload of
2351+ * <code>getLogicalHighlightShape</code> that uses the natural bounds of
2352+ * this <code>TextLayout</code>.
2353+ * @param firstEndpoint an endpoint in the range of characters to select
2354+ * @param secondEndpoint the other endpoint of the range of characters
2355+ * to select. Can be less than <code>firstEndpoint</code>. The range
2356+ * includes the character at min(firstEndpoint, secondEndpoint), but
2357+ * excludes max(firstEndpoint, secondEndpoint).
2358+ * @return a <code>Shape</code> enclosing the selection. This is in
2359+ * standard coordinates.
2360+ */
2361+ public Shape getLogicalHighlightShape(int firstEndpoint, int secondEndpoint) {
2362+
2363+ return getLogicalHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
2364+ }
2365+
2366+ /**
2367+ * Returns the black box bounds of the characters in the specified range.
2368+ * The black box bounds is an area consisting of the union of the bounding
2369+ * boxes of all the glyphs corresponding to the characters between start
2370+ * and limit. This area can be disjoint.
2371+ * @param firstEndpoint one end of the character range
2372+ * @param secondEndpoint the other end of the character range. Can be
2373+ * less than <code>firstEndpoint</code>.
2374+ * @return a <code>Shape</code> enclosing the black box bounds. This is
2375+ * in standard coordinates.
2376+ */
2377+ public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) {
2378+ ensureCache();
2379+
2380+ if (firstEndpoint > secondEndpoint) {
2381+ int t = firstEndpoint;
2382+ firstEndpoint = secondEndpoint;
2383+ secondEndpoint = t;
2384+ }
2385+
2386+ if (firstEndpoint < 0 || secondEndpoint > characterCount) {
2387+ throw new IllegalArgumentException("Invalid range passed to TextLayout.getBlackBoxBounds()");
2388+ }
2389+
2390+ /*
2391+ * return an area that consists of the bounding boxes of all the
2392+ * characters from firstEndpoint to limit
2393+ */
2394+
2395+ GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO);
2396+
2397+ if (firstEndpoint < characterCount) {
2398+ for (int logIndex = firstEndpoint;
2399+ logIndex < secondEndpoint;
2400+ logIndex++) {
2401+
2402+ Rectangle2D r = textLine.getCharBounds(logIndex);
2403+ if (!r.isEmpty()) {
2404+ result.append(r, false);
2405+ }
2406+ }
2407+ }
2408+
2409+ if (dx != 0 || dy != 0) {
2410+ AffineTransform tx = AffineTransform.getTranslateInstance(dx, dy);
2411+ result = (GeneralPath)tx.createTransformedShape(result);
2412+ }
2413+ LayoutPathImpl lp = textLine.getLayoutPath();
2414+ if (lp != null) {
2415+ result = (GeneralPath)lp.mapShape(result);
2416+ }
2417+
2418+ //return new Highlight(result, false);
2419+ return result;
2420+ }
2421+
2422+ /**
2423+ * Returns the distance from the point (x,&nbsp;y) to the caret along
2424+ * the line direction defined in <code>caretInfo</code>. Distance is
2425+ * negative if the point is to the left of the caret on a horizontal
2426+ * line, or above the caret on a vertical line.
2427+ * Utility for use by hitTestChar.
2428+ */
2429+ private float caretToPointDistance(float[] caretInfo, float x, float y) {
2430+ // distanceOffBaseline is negative if you're 'above' baseline
2431+
2432+ float lineDistance = isVerticalLine? y : x;
2433+ float distanceOffBaseline = isVerticalLine? -x : y;
2434+
2435+ return lineDistance - caretInfo[0] +
2436+ (distanceOffBaseline*caretInfo[1]);
2437+ }
2438+
2439+ /**
2440+ * Returns a <code>TextHitInfo</code> corresponding to the
2441+ * specified point.
2442+ * Coordinates outside the bounds of the <code>TextLayout</code>
2443+ * map to hits on the leading edge of the first logical character,
2444+ * or the trailing edge of the last logical character, as appropriate,
2445+ * regardless of the position of that character in the line. Only the
2446+ * direction along the baseline is used to make this evaluation.
2447+ * @param x the x offset from the origin of this
2448+ * <code>TextLayout</code>. This is in standard coordinates.
2449+ * @param y the y offset from the origin of this
2450+ * <code>TextLayout</code>. This is in standard coordinates.
2451+ * @param bounds the bounds of the <code>TextLayout</code>. This
2452+ * is in baseline-relative coordinates.
2453+ * @return a hit describing the character and edge (leading or trailing)
2454+ * under the specified point.
2455+ */
2456+ public TextHitInfo hitTestChar(float x, float y, Rectangle2D bounds) {
2457+ // check boundary conditions
2458+
2459+ LayoutPathImpl lp = textLine.getLayoutPath();
2460+ boolean prev = false;
2461+ if (lp != null) {
2462+ Point2D.Float pt = new Point2D.Float(x, y);
2463+ prev = lp.pointToPath(pt, pt);
2464+ x = pt.x;
2465+ y = pt.y;
2466+ }
2467+
2468+ if (isVertical()) {
2469+ if (y < bounds.getMinY()) {
2470+ return TextHitInfo.leading(0);
2471+ } else if (y >= bounds.getMaxY()) {
2472+ return TextHitInfo.trailing(characterCount-1);
2473+ }
2474+ } else {
2475+ if (x < bounds.getMinX()) {
2476+ return isLeftToRight() ? TextHitInfo.leading(0) : TextHitInfo.trailing(characterCount-1);
2477+ } else if (x >= bounds.getMaxX()) {
2478+ return isLeftToRight() ? TextHitInfo.trailing(characterCount-1) : TextHitInfo.leading(0);
2479+ }
2480+ }
2481+
2482+ // revised hit test
2483+ // the original seems too complex and fails miserably with italic offsets
2484+ // the natural tendency is to move towards the character you want to hit
2485+ // so we'll just measure distance to the center of each character's visual
2486+ // bounds, pick the closest one, then see which side of the character's
2487+ // center line (italic) the point is on.
2488+ // this tends to make it easier to hit narrow characters, which can be a
2489+ // bit odd if you're visually over an adjacent wide character. this makes
2490+ // a difference with bidi, so perhaps i need to revisit this yet again.
2491+
2492+ double distance = Double.MAX_VALUE;
2493+ int index = 0;
2494+ int trail = -1;
2495+ CoreMetrics lcm = null;
2496+ float icx = 0, icy = 0, ia = 0, cy = 0, dya = 0, ydsq = 0;
2497+
2498+ for (int i = 0; i < characterCount; ++i) {
2499+ if (!textLine.caretAtOffsetIsValid(i)) {
2500+ continue;
2501+ }
2502+ if (trail == -1) {
2503+ trail = i;
2504+ }
2505+ CoreMetrics cm = textLine.getCoreMetricsAt(i);
2506+ if (cm != lcm) {
2507+ lcm = cm;
2508+ // just work around baseline mess for now
2509+ if (cm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
2510+ cy = -(textLine.getMetrics().ascent - cm.ascent) + cm.ssOffset;
2511+ } else if (cm.baselineIndex == GraphicAttribute.BOTTOM_ALIGNMENT) {
2512+ cy = textLine.getMetrics().descent - cm.descent + cm.ssOffset;
2513+ } else {
2514+ cy = cm.effectiveBaselineOffset(baselineOffsets) + cm.ssOffset;
2515+ }
2516+ float dy = (cm.descent - cm.ascent) / 2 - cy;
2517+ dya = dy * cm.italicAngle;
2518+ cy += dy;
2519+ ydsq = (cy - y)*(cy - y);
2520+ }
2521+ float cx = textLine.getCharXPosition(i);
2522+ float ca = textLine.getCharAdvance(i);
2523+ float dx = ca / 2;
2524+ cx += dx - dya;
2525+
2526+ // proximity in x (along baseline) is two times as important as proximity in y
2527+ double nd = Math.sqrt(4*(cx - x)*(cx - x) + ydsq);
2528+ if (nd < distance) {
2529+ distance = nd;
2530+ index = i;
2531+ trail = -1;
2532+ icx = cx; icy = cy; ia = cm.italicAngle;
2533+ }
2534+ }
2535+ boolean left = x < icx - (y - icy) * ia;
2536+ boolean leading = textLine.isCharLTR(index) == left;
2537+ if (trail == -1) {
2538+ trail = characterCount;
2539+ }
2540+ TextHitInfo result = leading ? TextHitInfo.leading(index) :
2541+ TextHitInfo.trailing(trail-1);
2542+ return result;
2543+ }
2544+
2545+ /**
2546+ * Returns a <code>TextHitInfo</code> corresponding to the
2547+ * specified point. This method is a convenience overload of
2548+ * <code>hitTestChar</code> that uses the natural bounds of this
2549+ * <code>TextLayout</code>.
2550+ * @param x the x offset from the origin of this
2551+ * <code>TextLayout</code>. This is in standard coordinates.
2552+ * @param y the y offset from the origin of this
2553+ * <code>TextLayout</code>. This is in standard coordinates.
2554+ * @return a hit describing the character and edge (leading or trailing)
2555+ * under the specified point.
2556+ */
2557+ public TextHitInfo hitTestChar(float x, float y) {
2558+
2559+ return hitTestChar(x, y, getNaturalBounds());
2560+ }
2561+
2562+ /**
2563+ * Returns the hash code of this <code>TextLayout</code>.
2564+ * @return the hash code of this <code>TextLayout</code>.
2565+ */
2566+ public int hashCode() {
2567+ if (hashCodeCache == 0) {
2568+ ensureCache();
2569+ hashCodeCache = textLine.hashCode();
2570+ }
2571+ return hashCodeCache;
2572+ }
2573+
2574+ /**
2575+ * Returns <code>true</code> if the specified <code>Object</code> is a
2576+ * <code>TextLayout</code> object and if the specified <code>Object</code>
2577+ * equals this <code>TextLayout</code>.
2578+ * @param obj an <code>Object</code> to test for equality
2579+ * @return <code>true</code> if the specified <code>Object</code>
2580+ * equals this <code>TextLayout</code>; <code>false</code>
2581+ * otherwise.
2582+ */
2583+ public boolean equals(Object obj) {
2584+ return (obj instanceof TextLayout) && equals((TextLayout)obj);
2585+ }
2586+
2587+ /**
2588+ * Returns <code>true</code> if the two layouts are equal.
2589+ * Two layouts are equal if they contain equal glyphvectors in the same order.
2590+ * @param rhs the <code>TextLayout</code> to compare to this
2591+ * <code>TextLayout</code>
2592+ * @return <code>true</code> if the specified <code>TextLayout</code>
2593+ * equals this <code>TextLayout</code>.
2594+ *
2595+ */
2596+ public boolean equals(TextLayout rhs) {
2597+
2598+ if (rhs == null) {
2599+ return false;
2600+ }
2601+ if (rhs == this) {
2602+ return true;
2603+ }
2604+
2605+ ensureCache();
2606+ return textLine.equals(rhs.textLine);
2607+ }
2608+
2609+ /**
2610+ * Returns debugging information for this <code>TextLayout</code>.
2611+ * @return the <code>textLine</code> of this <code>TextLayout</code>
2612+ * as a <code>String</code>.
2613+ */
2614+ public String toString() {
2615+ ensureCache();
2616+ return textLine.toString();
2617+ }
2618+
2619+ /**
2620+ * Renders this <code>TextLayout</code> at the specified location in
2621+ * the specified {@link java.awt.Graphics2D Graphics2D} context.
2622+ * The origin of the layout is placed at x,&nbsp;y. Rendering may touch
2623+ * any point within <code>getBounds()</code> of this position. This
2624+ * leaves the <code>g2</code> unchanged. Text is rendered along the
2625+ * baseline path.
2626+ * @param g2 the <code>Graphics2D</code> context into which to render
2627+ * the layout
2628+ * @param x the X coordinate of the origin of this <code>TextLayout</code>
2629+ * @param y the Y coordinate of the origin of this <code>TextLayout</code>
2630+ * @see #getBounds()
2631+ */
2632+ public void draw(Graphics2D g2, float x, float y) {
2633+
2634+ if (g2 == null) {
2635+ throw new IllegalArgumentException("Null Graphics2D passed to TextLayout.draw()");
2636+ }
2637+
2638+ textLine.draw(g2, x - dx, y - dy);
2639+ }
2640+
2641+ /**
2642+ * Package-only method for testing ONLY. Please don't abuse.
2643+ */
2644+ TextLine getTextLineForTesting() {
2645+
2646+ return textLine;
2647+ }
2648+
2649+ /**
2650+ *
2651+ * Return the index of the first character with a different baseline from the
2652+ * character at start, or limit if all characters between start and limit have
2653+ * the same baseline.
2654+ */
2655+ private static int sameBaselineUpTo(Font font, char[] text,
2656+ int start, int limit) {
2657+ // current implementation doesn't support multiple baselines
2658+ return limit;
2659+ /*
2660+ byte bl = font.getBaselineFor(text[start++]);
2661+ while (start < limit && font.getBaselineFor(text[start]) == bl) {
2662+ ++start;
2663+ }
2664+ return start;
2665+ */
2666+ }
2667+
2668+ static byte getBaselineFromGraphic(GraphicAttribute graphic) {
2669+
2670+ byte alignment = (byte) graphic.getAlignment();
2671+
2672+ if (alignment == GraphicAttribute.BOTTOM_ALIGNMENT ||
2673+ alignment == GraphicAttribute.TOP_ALIGNMENT) {
2674+
2675+ return (byte)GraphicAttribute.ROMAN_BASELINE;
2676+ }
2677+ else {
2678+ return alignment;
2679+ }
2680+ }
2681+
2682+ /**
2683+ * Returns a <code>Shape</code> representing the outline of this
2684+ * <code>TextLayout</code>.
2685+ * @param tx an optional {@link AffineTransform} to apply to the
2686+ * outline of this <code>TextLayout</code>.
2687+ * @return a <code>Shape</code> that is the outline of this
2688+ * <code>TextLayout</code>. This is in standard coordinates.
2689+ */
2690+ public Shape getOutline(AffineTransform tx) {
2691+ ensureCache();
2692+ Shape result = textLine.getOutline(tx);
2693+ LayoutPathImpl lp = textLine.getLayoutPath();
2694+ if (lp != null) {
2695+ result = lp.mapShape(result);
2696+ }
2697+ return result;
2698+ }
2699+
2700+ /**
2701+ * Return the LayoutPath, or null if the layout path is the
2702+ * default path (x maps to advance, y maps to offset).
2703+ * @return the layout path
2704+ * @since 1.6
2705+ */
2706+ public LayoutPath getLayoutPath() {
2707+ return textLine.getLayoutPath();
2708+ }
2709+
2710+ /**
2711+ * Convert a hit to a point in standard coordinates. The point is
2712+ * on the baseline of the character at the leading or trailing
2713+ * edge of the character, as appropriate. If the path is
2714+ * broken at the side of the character represented by the hit, the
2715+ * point will be adjacent to the character.
2716+ * @param hit the hit to check. This must be a valid hit on
2717+ * the TextLayout.
2718+ * @param point the returned point. The point is in standard
2719+ * coordinates.
2720+ * @throws IllegalArgumentException if the hit is not valid for the
2721+ * TextLayout.
2722+ * @throws NullPointerException if hit or point is null.
2723+ * @since 1.6
2724+ */
2725+ public void hitToPoint(TextHitInfo hit, Point2D point) {
2726+ if (hit == null || point == null) {
2727+ throw new NullPointerException((hit == null ? "hit" : "point") +
2728+ " can't be null");
2729+ }
2730+ ensureCache();
2731+ checkTextHit(hit);
2732+
2733+ float adv = 0;
2734+ float off = 0;
2735+
2736+ int ix = hit.getCharIndex();
2737+ boolean leading = hit.isLeadingEdge();
2738+ boolean ltr;
2739+ if (ix == -1 || ix == textLine.characterCount()) {
2740+ ltr = textLine.isDirectionLTR();
2741+ adv = (ltr == (ix == -1)) ? 0 : lineMetrics.advance;
2742+ } else {
2743+ ltr = textLine.isCharLTR(ix);
2744+ adv = textLine.getCharLinePosition(ix, leading);
2745+ off = textLine.getCharYPosition(ix);
2746+ }
2747+ point.setLocation(adv, off);
2748+ LayoutPath lp = textLine.getLayoutPath();
2749+ if (lp != null) {
2750+ lp.pathToPoint(point, ltr != leading, point);
2751+ }
2752+ }
2753+}