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