1 /* 2 * Copyright (c) 2010, 2016, 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 package javafx.scene.text; 27 28 import javafx.css.converter.BooleanConverter; 29 import javafx.css.converter.EnumConverter; 30 import javafx.css.converter.SizeConverter; 31 import com.sun.javafx.geom.BaseBounds; 32 import com.sun.javafx.geom.Path2D; 33 import com.sun.javafx.geom.RectBounds; 34 import com.sun.javafx.geom.TransformedShape; 35 import com.sun.javafx.geom.transform.BaseTransform; 36 import com.sun.javafx.scene.DirtyBits; 37 import com.sun.javafx.scene.text.GlyphList; 38 import com.sun.javafx.scene.text.TextLayout; 39 import com.sun.javafx.scene.text.TextLayoutFactory; 40 import com.sun.javafx.scene.text.TextLine; 41 import com.sun.javafx.scene.text.TextSpan; 42 import com.sun.javafx.sg.prism.NGNode; 43 import com.sun.javafx.sg.prism.NGShape; 44 import com.sun.javafx.sg.prism.NGText; 45 import com.sun.javafx.text.FontHelper; 46 import com.sun.javafx.tk.Toolkit; 47 import javafx.beans.DefaultProperty; 48 import javafx.beans.InvalidationListener; 49 import javafx.beans.binding.DoubleBinding; 50 import javafx.beans.binding.ObjectBinding; 51 import javafx.beans.property.*; 52 import javafx.css.*; 53 import javafx.geometry.*; 54 import javafx.scene.AccessibleAttribute; 55 import javafx.scene.AccessibleRole; 56 import javafx.scene.paint.Color; 57 import javafx.scene.paint.Paint; 58 import javafx.scene.shape.LineTo; 59 import javafx.scene.shape.MoveTo; 60 import javafx.scene.shape.PathElement; 61 import javafx.scene.shape.Shape; 62 import javafx.scene.shape.StrokeType; 63 import java.util.ArrayList; 64 import java.util.Collections; 65 import java.util.List; 66 67 /** 68 * The {@code Text} class defines a node that displays a text. 69 * 70 * Paragraphs are separated by {@code '\n'} and the text is wrapped on 71 * paragraph boundaries. 72 * 73 <PRE> 74 import javafx.scene.text.*; 75 76 Text t = new Text(10, 50, "This is a test"); 77 t.setFont(new Font(20)); 78 </PRE> 79 * 80 <PRE> 81 import javafx.scene.text.*; 82 83 Text t = new Text(); 84 text.setFont(new Font(20)); 85 text.setText("First row\nSecond row"); 86 </PRE> 87 * 88 <PRE> 89 import javafx.scene.text.*; 90 91 Text t = new Text(); 92 text.setFont(new Font(20)); 93 text.setWrappingWidth(200); 94 text.setTextAlignment(TextAlignment.JUSTIFY) 95 text.setText("The quick brown fox jumps over the lazy dog"); 96 </PRE> 97 * @since JavaFX 2.0 98 */ 99 @DefaultProperty("text") 100 public class Text extends Shape { 101 102 private TextLayout layout; 103 private static final PathElement[] EMPTY_PATH_ELEMENT_ARRAY = new PathElement[0]; 104 105 /** 106 * @treatAsPrivate implementation detail 107 * @deprecated This is an internal API that is not intended 108 * for use and will be removed in the next version 109 */ 110 @Deprecated 111 @Override 112 protected final NGNode impl_createPeer() { 113 return new NGText(); 114 } 115 116 /** 117 * Creates an empty instance of Text. 118 */ 119 public Text() { 120 setAccessibleRole(AccessibleRole.TEXT); 121 InvalidationListener listener = observable -> checkSpan(); 122 parentProperty().addListener(listener); 123 managedProperty().addListener(listener); 124 effectiveNodeOrientationProperty().addListener(observable -> checkOrientation()); 125 setPickOnBounds(true); 126 } 127 128 /** 129 * Creates an instance of Text containing the given string. 130 * @param text text to be contained in the instance 131 */ 132 public Text(String text) { 133 this(); 134 setText(text); 135 } 136 137 /** 138 * Creates an instance of Text on the given coordinates containing the 139 * given string. 140 * @param x the horizontal position of the text 141 * @param y the vertical position of the text 142 * @param text text to be contained in the instance 143 */ 144 public Text(double x, double y, String text) { 145 this(text); 146 setX(x); 147 setY(y); 148 } 149 150 private boolean isSpan; 151 private boolean isSpan() { 152 return isSpan; 153 } 154 155 private void checkSpan() { 156 isSpan = isManaged() && getParent() instanceof TextFlow; 157 if (isSpan() && !pickOnBoundsProperty().isBound()) { 158 /* Documented behavior. See class description for TextFlow */ 159 setPickOnBounds(false); 160 } 161 } 162 163 private void checkOrientation() { 164 if (!isSpan()) { 165 NodeOrientation orientation = getEffectiveNodeOrientation(); 166 boolean rtl = orientation == NodeOrientation.RIGHT_TO_LEFT; 167 int dir = rtl ? TextLayout.DIRECTION_RTL : TextLayout.DIRECTION_LTR; 168 TextLayout layout = getTextLayout(); 169 if (layout.setDirection(dir)) { 170 needsTextLayout(); 171 } 172 } 173 } 174 175 @Override 176 public boolean usesMirroring() { 177 return false; 178 } 179 180 private void needsFullTextLayout() { 181 if (isSpan()) { 182 /* Create new text span every time the font or text changes 183 * so the text layout can see that the content has changed. 184 */ 185 textSpan = null; 186 187 /* Relies on impl_geomChanged() to request text flow to relayout */ 188 } else { 189 TextLayout layout = getTextLayout(); 190 String string = getTextInternal(); 191 Object font = getFontInternal(); 192 layout.setContent(string, font); 193 } 194 needsTextLayout(); 195 } 196 197 private void needsTextLayout() { 198 textRuns = null; 199 impl_geomChanged(); 200 impl_markDirty(DirtyBits.NODE_CONTENTS); 201 } 202 203 private TextSpan textSpan; 204 TextSpan getTextSpan() { 205 if (textSpan == null) { 206 textSpan = new TextSpan() { 207 @Override public String getText() { 208 return getTextInternal(); 209 } 210 @Override public Object getFont() { 211 return getFontInternal(); 212 } 213 @Override public RectBounds getBounds() { 214 return null; 215 } 216 }; 217 } 218 return textSpan; 219 } 220 221 private TextLayout getTextLayout() { 222 if (isSpan()) { 223 layout = null; 224 TextFlow parent = (TextFlow)getParent(); 225 return parent.getTextLayout(); 226 } 227 if (layout == null) { 228 TextLayoutFactory factory = Toolkit.getToolkit().getTextLayoutFactory(); 229 layout = factory.createLayout(); 230 String string = getTextInternal(); 231 Object font = getFontInternal(); 232 TextAlignment alignment = getTextAlignment(); 233 if (alignment == null) alignment = DEFAULT_TEXT_ALIGNMENT; 234 layout.setContent(string, font); 235 layout.setAlignment(alignment.ordinal()); 236 layout.setLineSpacing((float)getLineSpacing()); 237 layout.setWrapWidth((float)getWrappingWidth()); 238 if (getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { 239 layout.setDirection(TextLayout.DIRECTION_RTL); 240 } else { 241 layout.setDirection(TextLayout.DIRECTION_LTR); 242 } 243 } 244 return layout; 245 } 246 247 private GlyphList[] textRuns = null; 248 private BaseBounds spanBounds = new RectBounds(); /* relative to the textlayout */ 249 private boolean spanBoundsInvalid = true; 250 251 void layoutSpan(GlyphList[] runs) { 252 TextSpan span = getTextSpan(); 253 int count = 0; 254 for (int i = 0; i < runs.length; i++) { 255 GlyphList run = runs[i]; 256 if (run.getTextSpan() == span) { 257 count++; 258 } 259 } 260 textRuns = new GlyphList[count]; 261 count = 0; 262 for (int i = 0; i < runs.length; i++) { 263 GlyphList run = runs[i]; 264 if (run.getTextSpan() == span) { 265 textRuns[count++] = run; 266 } 267 } 268 spanBoundsInvalid = true; 269 270 /* Sometimes a property change in the text node will causes layout in 271 * text flow. In this case all the dirty bits are already clear and no 272 * extra work is necessary. Other times the layout is caused by changes 273 * in the text flow object (wrapping width and text alignment for example). 274 * In the second case the dirty bits must be set here using 275 * impl_geomChanged() and impl_markDirty(). Note that impl_geomChanged() 276 * causes another (undesired) layout request in the parent. 277 * In general this is not a problem because shapes are not resizable and 278 * region objects do not propagate layout changes to the parent. 279 * This is a special case where a shape is resized by the parent during 280 * layoutChildren(). See TextFlow#requestLayout() for information how 281 * text flow deals with this situation. 282 */ 283 impl_geomChanged(); 284 impl_markDirty(DirtyBits.NODE_CONTENTS); 285 } 286 287 BaseBounds getSpanBounds() { 288 if (spanBoundsInvalid) { 289 GlyphList[] runs = getRuns(); 290 if (runs.length != 0) { 291 float left = Float.POSITIVE_INFINITY; 292 float top = Float.POSITIVE_INFINITY; 293 float right = 0; 294 float bottom = 0; 295 for (int i = 0; i < runs.length; i++) { 296 GlyphList run = runs[i]; 297 com.sun.javafx.geom.Point2D location = run.getLocation(); 298 float width = run.getWidth(); 299 float height = run.getLineBounds().getHeight(); 300 left = Math.min(location.x, left); 301 top = Math.min(location.y, top); 302 right = Math.max(location.x + width, right); 303 bottom = Math.max(location.y + height, bottom); 304 } 305 spanBounds = spanBounds.deriveWithNewBounds(left, top, 0, 306 right, bottom, 0); 307 } else { 308 spanBounds = spanBounds.makeEmpty(); 309 } 310 spanBoundsInvalid = false; 311 } 312 return spanBounds; 313 } 314 315 private GlyphList[] getRuns() { 316 if (textRuns != null) return textRuns; 317 if (isSpan()) { 318 /* List of run is initialized when the TextFlow layout the children */ 319 getParent().layout(); 320 } else { 321 TextLayout layout = getTextLayout(); 322 textRuns = layout.getRuns(); 323 } 324 return textRuns; 325 } 326 327 private com.sun.javafx.geom.Shape getShape() { 328 TextLayout layout = getTextLayout(); 329 /* TextLayout has the text shape cached */ 330 int type = TextLayout.TYPE_TEXT; 331 if (isStrikethrough()) type |= TextLayout.TYPE_STRIKETHROUGH; 332 if (isUnderline()) type |= TextLayout.TYPE_UNDERLINE; 333 334 TextSpan filter = null; 335 if (isSpan()) { 336 /* Spans are always relative to the top */ 337 type |= TextLayout.TYPE_TOP; 338 filter = getTextSpan(); 339 } else { 340 /* Relative to baseline (first line) 341 * This shape can be translate in the y axis according 342 * to text origin, see impl_configShape(). 343 */ 344 type |= TextLayout.TYPE_BASELINE; 345 } 346 return layout.getShape(type, filter); 347 } 348 349 private BaseBounds getVisualBounds() { 350 if (impl_mode == NGShape.Mode.FILL || getStrokeType() == StrokeType.INSIDE) { 351 int type = TextLayout.TYPE_TEXT; 352 if (isStrikethrough()) type |= TextLayout.TYPE_STRIKETHROUGH; 353 if (isUnderline()) type |= TextLayout.TYPE_UNDERLINE; 354 return getTextLayout().getVisualBounds(type); 355 } else { 356 return getShape().getBounds(); 357 } 358 } 359 360 private BaseBounds getLogicalBounds() { 361 TextLayout layout = getTextLayout(); 362 /* TextLayout has the bounds cached */ 363 return layout.getBounds(); 364 } 365 366 /** 367 * Defines text string that is to be displayed. 368 * 369 * @defaultValue empty string 370 */ 371 private StringProperty text; 372 373 public final void setText(String value) { 374 if (value == null) value = ""; 375 textProperty().set(value); 376 } 377 378 public final String getText() { 379 return text == null ? "" : text.get(); 380 } 381 382 private String getTextInternal() { 383 // this might return null in case of bound property 384 String localText = getText(); 385 return localText == null ? "" : localText; 386 } 387 388 public final StringProperty textProperty() { 389 if (text == null) { 390 text = new StringPropertyBase("") { 391 @Override public Object getBean() { return Text.this; } 392 @Override public String getName() { return "text"; } 393 @Override public void invalidated() { 394 needsFullTextLayout(); 395 setSelectionStart(-1); 396 setSelectionEnd(-1); 397 setCaretPosition(-1); 398 setCaretBias(true); 399 400 // MH: Functionality copied from store() method, 401 // which was removed. 402 // Wonder what should happen if text is bound 403 // and becomes null? 404 final String value = get(); 405 if ((value == null) && !isBound()) { 406 set(""); 407 } 408 notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT); 409 } 410 }; 411 } 412 return text; 413 } 414 415 /** 416 * Defines the X coordinate of text origin. 417 * 418 * @defaultValue 0 419 */ 420 private DoubleProperty x; 421 422 public final void setX(double value) { 423 xProperty().set(value); 424 } 425 426 public final double getX() { 427 return x == null ? 0.0 : x.get(); 428 } 429 430 public final DoubleProperty xProperty() { 431 if (x == null) { 432 x = new DoublePropertyBase() { 433 @Override public Object getBean() { return Text.this; } 434 @Override public String getName() { return "x"; } 435 @Override public void invalidated() { 436 impl_geomChanged(); 437 } 438 }; 439 } 440 return x; 441 } 442 443 /** 444 * Defines the Y coordinate of text origin. 445 * 446 * @defaultValue 0 447 */ 448 private DoubleProperty y; 449 450 public final void setY(double value) { 451 yProperty().set(value); 452 } 453 454 public final double getY() { 455 return y == null ? 0.0 : y.get(); 456 } 457 458 public final DoubleProperty yProperty() { 459 if (y == null) { 460 y = new DoublePropertyBase() { 461 @Override public Object getBean() { return Text.this; } 462 @Override public String getName() { return "y"; } 463 @Override public void invalidated() { 464 impl_geomChanged(); 465 } 466 }; 467 } 468 return y; 469 } 470 471 /** 472 * Defines the font of text. 473 * 474 * @defaultValue Font{} 475 */ 476 private ObjectProperty<Font> font; 477 478 public final void setFont(Font value) { 479 fontProperty().set(value); 480 } 481 482 public final Font getFont() { 483 return font == null ? Font.getDefault() : font.get(); 484 } 485 486 /** 487 * Internally used safe version of getFont which never returns null. 488 * 489 * @return the font 490 */ 491 private Object getFontInternal() { 492 Font font = getFont(); 493 if (font == null) font = Font.getDefault(); 494 return FontHelper.getNativeFont(font); 495 } 496 497 public final ObjectProperty<Font> fontProperty() { 498 if (font == null) { 499 font = new StyleableObjectProperty<Font>(Font.getDefault()) { 500 @Override public Object getBean() { return Text.this; } 501 @Override public String getName() { return "font"; } 502 @Override public CssMetaData<Text,Font> getCssMetaData() { 503 return StyleableProperties.FONT; 504 } 505 @Override public void invalidated() { 506 needsFullTextLayout(); 507 impl_markDirty(DirtyBits.TEXT_FONT); 508 } 509 }; 510 } 511 return font; 512 } 513 514 public final void setTextOrigin(VPos value) { 515 textOriginProperty().set(value); 516 } 517 518 public final VPos getTextOrigin() { 519 if (attributes == null || attributes.textOrigin == null) { 520 return DEFAULT_TEXT_ORIGIN; 521 } 522 return attributes.getTextOrigin(); 523 } 524 525 /** 526 * Defines the origin of text coordinate system in local coordinates. 527 * Note: in case multiple rows are rendered {@code VPos.BASELINE} and 528 * {@code VPos.TOP} define the origin of the top row while 529 * {@code VPos.BOTTOM} defines the origin of the bottom row. 530 * 531 * @defaultValue VPos.BASELINE 532 */ 533 public final ObjectProperty<VPos> textOriginProperty() { 534 return getTextAttribute().textOriginProperty(); 535 } 536 537 /** 538 * Determines how the bounds of the text node are calculated. 539 * Logical bounds is a more appropriate default for text than 540 * the visual bounds. See {@code TextBoundsType} for more information. 541 * 542 * @defaultValue TextBoundsType.LOGICAL 543 */ 544 private ObjectProperty<TextBoundsType> boundsType; 545 546 public final void setBoundsType(TextBoundsType value) { 547 boundsTypeProperty().set(value); 548 } 549 550 public final TextBoundsType getBoundsType() { 551 return boundsType == null ? 552 DEFAULT_BOUNDS_TYPE : boundsTypeProperty().get(); 553 } 554 555 public final ObjectProperty<TextBoundsType> boundsTypeProperty() { 556 if (boundsType == null) { 557 boundsType = 558 new StyleableObjectProperty<TextBoundsType>(DEFAULT_BOUNDS_TYPE) { 559 @Override public Object getBean() { return Text.this; } 560 @Override public String getName() { return "boundsType"; } 561 @Override public CssMetaData<Text,TextBoundsType> getCssMetaData() { 562 return StyleableProperties.BOUNDS_TYPE; 563 } 564 @Override public void invalidated() { 565 TextLayout layout = getTextLayout(); 566 int type = 0; 567 if (boundsType.get() == TextBoundsType.LOGICAL_VERTICAL_CENTER) { 568 type |= TextLayout.BOUNDS_CENTER; 569 } 570 if (layout.setBoundsType(type)) { 571 needsTextLayout(); 572 } else { 573 impl_geomChanged(); 574 } 575 } 576 }; 577 } 578 return boundsType; 579 } 580 581 /** 582 * Defines a width constraint for the text in user space coordinates, 583 * e.g. pixels, not glyph or character count. 584 * If the value is {@code > 0} text will be line wrapped as needed 585 * to satisfy this constraint. 586 * 587 * @defaultValue 0 588 */ 589 private DoubleProperty wrappingWidth; 590 591 public final void setWrappingWidth(double value) { 592 wrappingWidthProperty().set(value); 593 } 594 595 public final double getWrappingWidth() { 596 return wrappingWidth == null ? 0 : wrappingWidth.get(); 597 } 598 599 public final DoubleProperty wrappingWidthProperty() { 600 if (wrappingWidth == null) { 601 wrappingWidth = new DoublePropertyBase() { 602 @Override public Object getBean() { return Text.this; } 603 @Override public String getName() { return "wrappingWidth"; } 604 @Override public void invalidated() { 605 if (!isSpan()) { 606 TextLayout layout = getTextLayout(); 607 if (layout.setWrapWidth((float)get())) { 608 needsTextLayout(); 609 } else { 610 impl_geomChanged(); 611 } 612 } 613 } 614 }; 615 } 616 return wrappingWidth; 617 } 618 619 public final void setUnderline(boolean value) { 620 underlineProperty().set(value); 621 } 622 623 public final boolean isUnderline() { 624 if (attributes == null || attributes.underline == null) { 625 return DEFAULT_UNDERLINE; 626 } 627 return attributes.isUnderline(); 628 } 629 630 /** 631 * Defines if each line of text should have a line below it. 632 * 633 * @defaultValue false 634 */ 635 public final BooleanProperty underlineProperty() { 636 return getTextAttribute().underlineProperty(); 637 } 638 639 public final void setStrikethrough(boolean value) { 640 strikethroughProperty().set(value); 641 } 642 643 public final boolean isStrikethrough() { 644 if (attributes == null || attributes.strikethrough == null) { 645 return DEFAULT_STRIKETHROUGH; 646 } 647 return attributes.isStrikethrough(); 648 } 649 650 /** 651 * Defines if each line of text should have a line through it. 652 * 653 * @defaultValue false 654 */ 655 public final BooleanProperty strikethroughProperty() { 656 return getTextAttribute().strikethroughProperty(); 657 } 658 659 public final void setTextAlignment(TextAlignment value) { 660 textAlignmentProperty().set(value); 661 } 662 663 public final TextAlignment getTextAlignment() { 664 if (attributes == null || attributes.textAlignment == null) { 665 return DEFAULT_TEXT_ALIGNMENT; 666 } 667 return attributes.getTextAlignment(); 668 } 669 670 /** 671 * Defines horizontal text alignment in the bounding box. 672 * 673 * The width of the bounding box is defined by the widest row. 674 * 675 * Note: In the case of a single line of text, where the width of the 676 * node is determined by the width of the text, the alignment setting 677 * has no effect. 678 * 679 * @defaultValue TextAlignment.LEFT 680 */ 681 public final ObjectProperty<TextAlignment> textAlignmentProperty() { 682 return getTextAttribute().textAlignmentProperty(); 683 } 684 685 public final void setLineSpacing(double spacing) { 686 lineSpacingProperty().set(spacing); 687 } 688 689 public final double getLineSpacing() { 690 if (attributes == null || attributes.lineSpacing == null) { 691 return DEFAULT_LINE_SPACING; 692 } 693 return attributes.getLineSpacing(); 694 } 695 696 /** 697 * Defines the vertical space in pixel between lines. 698 * 699 * @defaultValue 0 700 * 701 * @since JavaFX 8.0 702 */ 703 public final DoubleProperty lineSpacingProperty() { 704 return getTextAttribute().lineSpacingProperty(); 705 } 706 707 @Override 708 public final double getBaselineOffset() { 709 return baselineOffsetProperty().get(); 710 } 711 712 /** 713 * The 'alphabetic' (or roman) baseline offset from the Text node's 714 * layoutBounds.minY location. 715 * The value typically corresponds to the max ascent of the font. 716 */ 717 public final ReadOnlyDoubleProperty baselineOffsetProperty() { 718 return getTextAttribute().baselineOffsetProperty(); 719 } 720 721 /** 722 * Specifies a requested font smoothing type : gray or LCD. 723 * 724 * The width of the bounding box is defined by the widest row. 725 * 726 * Note: LCD mode doesn't apply in numerous cases, such as various 727 * compositing modes, where effects are applied and very large glyphs. 728 * 729 * @defaultValue FontSmoothingType.GRAY 730 * @since JavaFX 2.1 731 */ 732 private ObjectProperty<FontSmoothingType> fontSmoothingType; 733 734 public final void setFontSmoothingType(FontSmoothingType value) { 735 fontSmoothingTypeProperty().set(value); 736 } 737 738 public final FontSmoothingType getFontSmoothingType() { 739 return fontSmoothingType == null ? 740 FontSmoothingType.GRAY : fontSmoothingType.get(); 741 } 742 743 public final ObjectProperty<FontSmoothingType> 744 fontSmoothingTypeProperty() { 745 if (fontSmoothingType == null) { 746 fontSmoothingType = 747 new StyleableObjectProperty<FontSmoothingType> 748 (FontSmoothingType.GRAY) { 749 @Override public Object getBean() { return Text.this; } 750 @Override public String getName() { return "fontSmoothingType"; } 751 @Override public CssMetaData<Text,FontSmoothingType> getCssMetaData() { 752 return StyleableProperties.FONT_SMOOTHING_TYPE; 753 } 754 @Override public void invalidated() { 755 impl_markDirty(DirtyBits.TEXT_ATTRS); 756 impl_geomChanged(); 757 } 758 }; 759 } 760 return fontSmoothingType; 761 } 762 763 /** 764 * @treatAsPrivate implementation detail 765 * @deprecated This is an internal API that is not intended 766 * for use and will be removed in the next version 767 */ 768 @Deprecated 769 @Override 770 protected final void impl_geomChanged() { 771 super.impl_geomChanged(); 772 if (attributes != null) { 773 if (attributes.caretBinding != null) { 774 attributes.caretBinding.invalidate(); 775 } 776 if (attributes.selectionBinding != null) { 777 attributes.selectionBinding.invalidate(); 778 } 779 } 780 impl_markDirty(DirtyBits.NODE_GEOMETRY); 781 } 782 783 /** 784 * Shape of selection in local coordinates. 785 * 786 * @since 9 787 */ 788 public final PathElement[] getSelectionShape() { 789 return selectionShapeProperty().get(); 790 } 791 792 public final ReadOnlyObjectProperty<PathElement[]> selectionShapeProperty() { 793 return getTextAttribute().impl_selectionShapeProperty(); 794 } 795 796 /** 797 * Selection start index in the content. 798 * Set to {@code -1} to unset selection. 799 * 800 * @since 9 801 */ 802 public final void setSelectionStart(int value) { 803 if (value == -1 && 804 (attributes == null || attributes.impl_selectionStart == null)) { 805 return; 806 } 807 selectionStartProperty().set(value); 808 } 809 810 public final int getSelectionStart() { 811 if (attributes == null || attributes.impl_selectionStart == null) { 812 return DEFAULT_SELECTION_START; 813 } 814 return attributes.getImpl_selectionStart(); 815 } 816 817 public final IntegerProperty selectionStartProperty() { 818 return getTextAttribute().impl_selectionStartProperty(); 819 } 820 821 /** 822 * Selection end index in the content. 823 * Set to {@code -1} to unset selection. 824 * 825 * @since 9 826 */ 827 public final void setSelectionEnd(int value) { 828 if (value == -1 && 829 (attributes == null || attributes.impl_selectionEnd == null)) { 830 return; 831 } 832 selectionEndProperty().set(value); 833 } 834 835 public final int getSelectionEnd() { 836 if (attributes == null || attributes.impl_selectionEnd == null) { 837 return DEFAULT_SELECTION_END; 838 } 839 return attributes.getImpl_selectionEnd(); 840 } 841 842 public final IntegerProperty selectionEndProperty() { 843 return getTextAttribute().impl_selectionEndProperty(); 844 } 845 846 /** 847 * The fill color of selected text. 848 * 849 * @since 9 850 */ 851 public final ObjectProperty<Paint> selectionFillProperty() { 852 return getTextAttribute().impl_selectionFillProperty(); 853 } 854 855 public final void setSelectionFill(Paint paint) { 856 selectionFillProperty().set(paint); 857 } 858 public final Paint getSelectionFill() { 859 return selectionFillProperty().get(); 860 } 861 862 /** 863 * Shape of caret in local coordinates. 864 * 865 * @since 9 866 */ 867 public final PathElement[] getCaretShape() { 868 return caretShapeProperty().get(); 869 } 870 871 public final ReadOnlyObjectProperty<PathElement[]> caretShapeProperty() { 872 return getTextAttribute().impl_caretShapeProperty(); 873 } 874 875 /** 876 * Caret index in the content. 877 * Set to {@code -1} to unset caret. 878 * 879 * @since 9 880 */ 881 public final void setCaretPosition(int value) { 882 if (value == -1 && 883 (attributes == null || attributes.impl_caretPosition == null)) { 884 return; 885 } 886 caretPositionProperty().set(value); 887 } 888 889 public final int getCaretPosition() { 890 if (attributes == null || attributes.impl_caretPosition == null) { 891 return DEFAULT_CARET_POSITION; 892 } 893 return attributes.getImpl_caretPosition(); 894 } 895 896 public final IntegerProperty caretPositionProperty() { 897 return getTextAttribute().impl_caretPositionProperty(); 898 } 899 900 /** 901 * caret bias in the content. {@code true} means a bias towards the leading character edge. 902 * (true=leading/false=trailing) 903 * 904 * @since 9 905 */ 906 public final void setCaretBias(boolean value) { 907 if (value && (attributes == null || attributes.impl_caretBias == null)) { 908 return; 909 } 910 caretBiasProperty().set(value); 911 } 912 913 public final boolean isCaretBias() { 914 if (attributes == null || attributes.impl_caretBias == null) { 915 return DEFAULT_CARET_BIAS; 916 } 917 return getTextAttribute().isImpl_caretBias(); 918 } 919 920 public final BooleanProperty caretBiasProperty() { 921 return getTextAttribute().impl_caretBiasProperty(); 922 } 923 924 /** 925 * Maps local point to index in the content. 926 * 927 * @param point the specified point to be tested 928 * @return a {@code HitInfo} representing the character index found 929 * @since 9 930 */ 931 public final HitInfo hitTest(Point2D point) { 932 if (point == null) return null; 933 TextLayout layout = getTextLayout(); 934 double x = point.getX() - getX(); 935 double y = point.getY() - getY() + getYRendering(); 936 TextLayout.Hit layoutHit = layout.getHitInfo((float)x, (float)y); 937 return new HitInfo(layoutHit.getCharIndex(), layoutHit.getInsertionIndex(), 938 layoutHit.isLeading(), getText()); 939 } 940 941 private PathElement[] getRange(int start, int end, int type) { 942 int length = getTextInternal().length(); 943 if (0 <= start && start < end && end <= length) { 944 TextLayout layout = getTextLayout(); 945 float x = (float)getX(); 946 float y = (float)getY() - getYRendering(); 947 return layout.getRange(start, end, type, x, y); 948 } 949 return EMPTY_PATH_ELEMENT_ARRAY; 950 } 951 952 /** 953 * Returns shape for the caret at given index and bias. 954 * 955 * @param charIndex the character index for the caret 956 * @param caretBias whether the caret is biased on the leading edge of the character 957 * @return an array of {@code PathElement} which can be used to create a {@code Shape} 958 * @since 9 959 */ 960 public final PathElement[] caretShape(int charIndex, boolean caretBias) { 961 if (0 <= charIndex && charIndex <= getTextInternal().length()) { 962 float x = (float)getX(); 963 float y = (float)getY() - getYRendering(); 964 return getTextLayout().getCaretShape(charIndex, caretBias, x, y); 965 } else { 966 return null; 967 } 968 } 969 970 /** 971 * Returns shape for the range of the text in local coordinates. 972 * 973 * @param start the beginning character index for the range 974 * @param start the end character index (non-inclusive) for the range 975 * @return an array of {@code PathElement} which can be used to create a {@code Shape} 976 * @since 9 977 */ 978 public final PathElement[] rangeShape(int start, int end) { 979 return getRange(start, end, TextLayout.TYPE_TEXT); 980 } 981 982 /** 983 * Returns shape for the underline in local coordinates. 984 * 985 * @param start the beginning character index for the range 986 * @param start the end character index (non-inclusive) for the range 987 * @return an array of {@code PathElement} which can be used to create a {@code Shape} 988 * @since 9 989 */ 990 public final PathElement[] underlineShape(int start, int end) { 991 return getRange(start, end, TextLayout.TYPE_UNDERLINE); 992 } 993 994 /** 995 * Shows/Hides on-screen keyboard if available (mobile platform) 996 * 997 * @treatAsPrivate implementation detail 998 * @deprecated This is an internal API that is not intended 999 * for use and will be removed in the next version 1000 */ 1001 @Deprecated 1002 public final void impl_displaySoftwareKeyboard(boolean display) { 1003 } 1004 1005 private float getYAdjustment(BaseBounds bounds) { 1006 VPos origin = getTextOrigin(); 1007 if (origin == null) origin = DEFAULT_TEXT_ORIGIN; 1008 switch (origin) { 1009 case TOP: return -bounds.getMinY(); 1010 case BASELINE: return 0; 1011 case CENTER: return -bounds.getMinY() - bounds.getHeight() / 2; 1012 case BOTTOM: return -bounds.getMinY() - bounds.getHeight(); 1013 default: return 0; 1014 } 1015 } 1016 1017 private float getYRendering() { 1018 if (isSpan()) return 0; 1019 1020 /* Always logical for rendering */ 1021 BaseBounds bounds = getLogicalBounds(); 1022 1023 VPos origin = getTextOrigin(); 1024 if (origin == null) origin = DEFAULT_TEXT_ORIGIN; 1025 if (getBoundsType() == TextBoundsType.VISUAL) { 1026 BaseBounds vBounds = getVisualBounds(); 1027 float delta = vBounds.getMinY() - bounds.getMinY(); 1028 switch (origin) { 1029 case TOP: return delta; 1030 case BASELINE: return -vBounds.getMinY() + delta; 1031 case CENTER: return vBounds.getHeight() / 2 + delta; 1032 case BOTTOM: return vBounds.getHeight() + delta; 1033 default: return 0; 1034 } 1035 } else { 1036 switch (origin) { 1037 case TOP: return 0; 1038 case BASELINE: return -bounds.getMinY(); 1039 case CENTER: return bounds.getHeight() / 2; 1040 case BOTTOM: return bounds.getHeight(); 1041 default: return 0; 1042 } 1043 } 1044 } 1045 1046 /** 1047 * @treatAsPrivate implementation detail 1048 * @deprecated This is an internal API that is not intended 1049 * for use and will be removed in the next version 1050 */ 1051 @Deprecated 1052 @Override 1053 protected final Bounds impl_computeLayoutBounds() { 1054 if (isSpan()) { 1055 BaseBounds bounds = getSpanBounds(); 1056 double width = bounds.getWidth(); 1057 double height = bounds.getHeight(); 1058 return new BoundingBox(0, 0, width, height); 1059 } 1060 1061 if (getBoundsType() == TextBoundsType.VISUAL) { 1062 /* In Node the layout bounds is computed based in the geom 1063 * bounds and in Shape the geom bounds is computed based 1064 * on the shape (generated here in #configShape()) */ 1065 return super.impl_computeLayoutBounds(); 1066 } 1067 BaseBounds bounds = getLogicalBounds(); 1068 double x = bounds.getMinX() + getX(); 1069 double y = bounds.getMinY() + getY() + getYAdjustment(bounds); 1070 double width = bounds.getWidth(); 1071 double height = bounds.getHeight(); 1072 double wrappingWidth = getWrappingWidth(); 1073 if (wrappingWidth != 0) width = wrappingWidth; 1074 return new BoundingBox(x, y, width, height); 1075 } 1076 1077 /** 1078 * @treatAsPrivate implementation detail 1079 * @deprecated This is an internal API that is not intended 1080 * for use and will be removed in the next version 1081 */ 1082 @Deprecated 1083 @Override 1084 public final BaseBounds impl_computeGeomBounds(BaseBounds bounds, 1085 BaseTransform tx) { 1086 if (isSpan()) { 1087 if (impl_mode != NGShape.Mode.FILL && getStrokeType() != StrokeType.INSIDE) { 1088 return super.impl_computeGeomBounds(bounds, tx); 1089 } 1090 TextLayout layout = getTextLayout(); 1091 bounds = layout.getBounds(getTextSpan(), bounds); 1092 BaseBounds spanBounds = getSpanBounds(); 1093 float minX = bounds.getMinX() - spanBounds.getMinX(); 1094 float minY = bounds.getMinY() - spanBounds.getMinY(); 1095 float maxX = minX + bounds.getWidth(); 1096 float maxY = minY + bounds.getHeight(); 1097 bounds = bounds.deriveWithNewBounds(minX, minY, 0, maxX, maxY, 0); 1098 return tx.transform(bounds, bounds); 1099 } 1100 1101 if (getBoundsType() == TextBoundsType.VISUAL) { 1102 if (getTextInternal().length() == 0 || impl_mode == NGShape.Mode.EMPTY) { 1103 return bounds.makeEmpty(); 1104 } 1105 if (impl_mode == NGShape.Mode.FILL || getStrokeType() == StrokeType.INSIDE) { 1106 /* Optimize for FILL and INNER STROKE: save the cost of shaping each glyph */ 1107 BaseBounds visualBounds = getVisualBounds(); 1108 float x = visualBounds.getMinX() + (float) getX(); 1109 float yadj = getYAdjustment(visualBounds); 1110 float y = visualBounds.getMinY() + yadj + (float) getY(); 1111 bounds.deriveWithNewBounds(x, y, 0, x + visualBounds.getWidth(), 1112 y + visualBounds.getHeight(), 0); 1113 return tx.transform(bounds, bounds); 1114 } else { 1115 /* Let the super class compute the bounds using shape */ 1116 return super.impl_computeGeomBounds(bounds, tx); 1117 } 1118 } 1119 1120 BaseBounds textBounds = getLogicalBounds(); 1121 float x = textBounds.getMinX() + (float)getX(); 1122 float yadj = getYAdjustment(textBounds); 1123 float y = textBounds.getMinY() + yadj + (float)getY(); 1124 float width = textBounds.getWidth(); 1125 float height = textBounds.getHeight(); 1126 float wrappingWidth = (float)getWrappingWidth(); 1127 if (wrappingWidth > width) { 1128 width = wrappingWidth; 1129 } else { 1130 /* The following adjustment is necessary for the text bounds to be 1131 * relative to the same location as the mirrored bounds returned 1132 * by layout.getBounds(). 1133 */ 1134 if (wrappingWidth > 0) { 1135 NodeOrientation orientation = getEffectiveNodeOrientation(); 1136 if (orientation == NodeOrientation.RIGHT_TO_LEFT) { 1137 x -= width - wrappingWidth; 1138 } 1139 } 1140 } 1141 textBounds = new RectBounds(x, y, x + width, y + height); 1142 1143 /* handle stroked text */ 1144 if (impl_mode != NGShape.Mode.FILL && getStrokeType() != StrokeType.INSIDE) { 1145 bounds = 1146 super.impl_computeGeomBounds(bounds, 1147 BaseTransform.IDENTITY_TRANSFORM); 1148 } else { 1149 TextLayout layout = getTextLayout(); 1150 bounds = layout.getBounds(null, bounds); 1151 x = bounds.getMinX() + (float)getX(); 1152 width = bounds.getWidth(); 1153 bounds = bounds.deriveWithNewBounds(x, y, 0, x + width, y + height, 0); 1154 } 1155 1156 bounds = bounds.deriveWithUnion(textBounds); 1157 return tx.transform(bounds, bounds); 1158 } 1159 1160 /** 1161 * @treatAsPrivate implementation detail 1162 * @deprecated This is an internal API that is not intended 1163 * for use and will be removed in the next version 1164 */ 1165 @Deprecated 1166 @Override 1167 protected final boolean impl_computeContains(double localX, double localY) { 1168 /* Used for spans, regular text uses bounds based picking */ 1169 double x = localX + getSpanBounds().getMinX(); 1170 double y = localY + getSpanBounds().getMinY(); 1171 GlyphList[] runs = getRuns(); 1172 if (runs.length != 0) { 1173 for (int i = 0; i < runs.length; i++) { 1174 GlyphList run = runs[i]; 1175 com.sun.javafx.geom.Point2D location = run.getLocation(); 1176 float width = run.getWidth(); 1177 RectBounds lineBounds = run.getLineBounds(); 1178 float height = lineBounds.getHeight(); 1179 if (location.x <= x && x < location.x + width && 1180 location.y <= y && y < location.y + height) { 1181 return true; 1182 } 1183 } 1184 } 1185 return false; 1186 } 1187 1188 /** 1189 * @treatAsPrivate implementation detail 1190 * @deprecated This is an internal API that is not intended 1191 * for use and will be removed in the next version 1192 */ 1193 @Deprecated 1194 @Override 1195 public final com.sun.javafx.geom.Shape impl_configShape() { 1196 if (impl_mode == NGShape.Mode.EMPTY || getTextInternal().length() == 0) { 1197 return new Path2D(); 1198 } 1199 com.sun.javafx.geom.Shape shape = getShape(); 1200 float x, y; 1201 if (isSpan()) { 1202 BaseBounds bounds = getSpanBounds(); 1203 x = -bounds.getMinX(); 1204 y = -bounds.getMinY(); 1205 } else { 1206 x = (float)getX(); 1207 y = getYAdjustment(getVisualBounds()) + (float)getY(); 1208 } 1209 return TransformedShape.translatedShape(shape, x, y); 1210 } 1211 1212 /*************************************************************************** 1213 * * 1214 * Stylesheet Handling * 1215 * * 1216 **************************************************************************/ 1217 1218 /** 1219 * Super-lazy instantiation pattern from Bill Pugh. 1220 * @treatAsPrivate implementation detail 1221 */ 1222 private static class StyleableProperties { 1223 1224 private static final CssMetaData<Text,Font> FONT = 1225 new FontCssMetaData<Text>("-fx-font", Font.getDefault()) { 1226 1227 @Override 1228 public boolean isSettable(Text node) { 1229 return node.font == null || !node.font.isBound(); 1230 } 1231 1232 @Override 1233 public StyleableProperty<Font> getStyleableProperty(Text node) { 1234 return (StyleableProperty<Font>)node.fontProperty(); 1235 } 1236 }; 1237 1238 private static final CssMetaData<Text,Boolean> UNDERLINE = 1239 new CssMetaData<Text,Boolean>("-fx-underline", 1240 BooleanConverter.getInstance(), Boolean.FALSE) { 1241 1242 @Override 1243 public boolean isSettable(Text node) { 1244 return node.attributes == null || 1245 node.attributes.underline == null || 1246 !node.attributes.underline.isBound(); 1247 } 1248 1249 @Override 1250 public StyleableProperty<Boolean> getStyleableProperty(Text node) { 1251 return (StyleableProperty<Boolean>)node.underlineProperty(); 1252 } 1253 }; 1254 1255 private static final CssMetaData<Text,Boolean> STRIKETHROUGH = 1256 new CssMetaData<Text,Boolean>("-fx-strikethrough", 1257 BooleanConverter.getInstance(), Boolean.FALSE) { 1258 1259 @Override 1260 public boolean isSettable(Text node) { 1261 return node.attributes == null || 1262 node.attributes.strikethrough == null || 1263 !node.attributes.strikethrough.isBound(); 1264 } 1265 1266 @Override 1267 public StyleableProperty<Boolean> getStyleableProperty(Text node) { 1268 return (StyleableProperty<Boolean>)node.strikethroughProperty(); 1269 } 1270 }; 1271 1272 private static final 1273 CssMetaData<Text,TextAlignment> TEXT_ALIGNMENT = 1274 new CssMetaData<Text,TextAlignment>("-fx-text-alignment", 1275 new EnumConverter<TextAlignment>(TextAlignment.class), 1276 TextAlignment.LEFT) { 1277 1278 @Override 1279 public boolean isSettable(Text node) { 1280 return node.attributes == null || 1281 node.attributes.textAlignment == null || 1282 !node.attributes.textAlignment.isBound(); 1283 } 1284 1285 @Override 1286 public StyleableProperty<TextAlignment> getStyleableProperty(Text node) { 1287 return (StyleableProperty<TextAlignment>)node.textAlignmentProperty(); 1288 } 1289 }; 1290 1291 private static final CssMetaData<Text,VPos> TEXT_ORIGIN = 1292 new CssMetaData<Text,VPos>("-fx-text-origin", 1293 new EnumConverter<VPos>(VPos.class), 1294 VPos.BASELINE) { 1295 1296 @Override 1297 public boolean isSettable(Text node) { 1298 return node.attributes == null || 1299 node.attributes.textOrigin == null || 1300 !node.attributes.textOrigin.isBound(); 1301 } 1302 1303 @Override 1304 public StyleableProperty<VPos> getStyleableProperty(Text node) { 1305 return (StyleableProperty<VPos>)node.textOriginProperty(); 1306 } 1307 }; 1308 1309 private static final CssMetaData<Text,FontSmoothingType> 1310 FONT_SMOOTHING_TYPE = 1311 new CssMetaData<Text,FontSmoothingType>( 1312 "-fx-font-smoothing-type", 1313 new EnumConverter<FontSmoothingType>(FontSmoothingType.class), 1314 FontSmoothingType.GRAY) { 1315 1316 @Override 1317 public boolean isSettable(Text node) { 1318 return node.fontSmoothingType == null || 1319 !node.fontSmoothingType.isBound(); 1320 } 1321 1322 @Override 1323 public StyleableProperty<FontSmoothingType> 1324 getStyleableProperty(Text node) { 1325 1326 return (StyleableProperty<FontSmoothingType>)node.fontSmoothingTypeProperty(); 1327 } 1328 }; 1329 1330 private static final 1331 CssMetaData<Text,Number> LINE_SPACING = 1332 new CssMetaData<Text,Number>("-fx-line-spacing", 1333 SizeConverter.getInstance(), 0) { 1334 1335 @Override 1336 public boolean isSettable(Text node) { 1337 return node.attributes == null || 1338 node.attributes.lineSpacing == null || 1339 !node.attributes.lineSpacing.isBound(); 1340 } 1341 1342 @Override 1343 public StyleableProperty<Number> getStyleableProperty(Text node) { 1344 return (StyleableProperty<Number>)node.lineSpacingProperty(); 1345 } 1346 }; 1347 1348 private static final CssMetaData<Text, TextBoundsType> 1349 BOUNDS_TYPE = 1350 new CssMetaData<Text,TextBoundsType>( 1351 "-fx-bounds-type", 1352 new EnumConverter<TextBoundsType>(TextBoundsType.class), 1353 DEFAULT_BOUNDS_TYPE) { 1354 1355 @Override 1356 public boolean isSettable(Text node) { 1357 return node.boundsType == null || !node.boundsType.isBound(); 1358 } 1359 1360 @Override 1361 public StyleableProperty<TextBoundsType> getStyleableProperty(Text node) { 1362 return (StyleableProperty<TextBoundsType>)node.boundsTypeProperty(); 1363 } 1364 }; 1365 1366 private final static List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 1367 static { 1368 final List<CssMetaData<? extends Styleable, ?>> styleables = 1369 new ArrayList<CssMetaData<? extends Styleable, ?>>(Shape.getClassCssMetaData()); 1370 styleables.add(FONT); 1371 styleables.add(UNDERLINE); 1372 styleables.add(STRIKETHROUGH); 1373 styleables.add(TEXT_ALIGNMENT); 1374 styleables.add(TEXT_ORIGIN); 1375 styleables.add(FONT_SMOOTHING_TYPE); 1376 styleables.add(LINE_SPACING); 1377 styleables.add(BOUNDS_TYPE); 1378 STYLEABLES = Collections.unmodifiableList(styleables); 1379 } 1380 } 1381 1382 /** 1383 * @return The CssMetaData associated with this class, which may include the 1384 * CssMetaData of its super classes. 1385 * @since JavaFX 8.0 1386 */ 1387 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 1388 return StyleableProperties.STYLEABLES; 1389 } 1390 1391 /** 1392 * {@inheritDoc} 1393 * 1394 * @since JavaFX 8.0 1395 */ 1396 1397 1398 @Override 1399 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 1400 return getClassCssMetaData(); 1401 } 1402 1403 @SuppressWarnings("deprecation") 1404 private void updatePGText() { 1405 final NGText peer = impl_getPeer(); 1406 if (impl_isDirty(DirtyBits.TEXT_ATTRS)) { 1407 peer.setUnderline(isUnderline()); 1408 peer.setStrikethrough(isStrikethrough()); 1409 FontSmoothingType smoothing = getFontSmoothingType(); 1410 if (smoothing == null) smoothing = FontSmoothingType.GRAY; 1411 peer.setFontSmoothingType(smoothing.ordinal()); 1412 } 1413 if (impl_isDirty(DirtyBits.TEXT_FONT)) { 1414 peer.setFont(getFontInternal()); 1415 } 1416 if (impl_isDirty(DirtyBits.NODE_CONTENTS)) { 1417 peer.setGlyphs(getRuns()); 1418 } 1419 if (impl_isDirty(DirtyBits.NODE_GEOMETRY)) { 1420 if (isSpan()) { 1421 BaseBounds spanBounds = getSpanBounds(); 1422 peer.setLayoutLocation(spanBounds.getMinX(), spanBounds.getMinY()); 1423 } else { 1424 float x = (float)getX(); 1425 float y = (float)getY(); 1426 float yadj = getYRendering(); 1427 peer.setLayoutLocation(-x, yadj - y); 1428 } 1429 } 1430 if (impl_isDirty(DirtyBits.TEXT_SELECTION)) { 1431 Object fillObj = null; 1432 int start = getSelectionStart(); 1433 int end = getSelectionEnd(); 1434 int length = getTextInternal().length(); 1435 if (0 <= start && start < end && end <= length) { 1436 Paint fill = selectionFillProperty().get(); 1437 fillObj = fill != null ? Toolkit.getPaintAccessor().getPlatformPaint(fill) : null; 1438 } 1439 peer.setSelection(start, end, fillObj); 1440 } 1441 } 1442 1443 /** 1444 * @treatAsPrivate implementation detail 1445 * @deprecated This is an internal API that is not intended 1446 * for use and will be removed in the next version 1447 */ 1448 @Deprecated 1449 @Override 1450 public final void impl_updatePeer() { 1451 super.impl_updatePeer(); 1452 updatePGText(); 1453 } 1454 1455 /*************************************************************************** 1456 * * 1457 * Seldom Used Properties * 1458 * * 1459 **************************************************************************/ 1460 1461 private TextAttribute attributes; 1462 1463 private TextAttribute getTextAttribute() { 1464 if (attributes == null) { 1465 attributes = new TextAttribute(); 1466 } 1467 return attributes; 1468 } 1469 1470 private static final VPos DEFAULT_TEXT_ORIGIN = VPos.BASELINE; 1471 private static final TextBoundsType DEFAULT_BOUNDS_TYPE = TextBoundsType.LOGICAL; 1472 private static final boolean DEFAULT_UNDERLINE = false; 1473 private static final boolean DEFAULT_STRIKETHROUGH = false; 1474 private static final TextAlignment DEFAULT_TEXT_ALIGNMENT = TextAlignment.LEFT; 1475 private static final double DEFAULT_LINE_SPACING = 0; 1476 private static final int DEFAULT_CARET_POSITION = -1; 1477 private static final int DEFAULT_SELECTION_START = -1; 1478 private static final int DEFAULT_SELECTION_END = -1; 1479 private static final Color DEFAULT_SELECTION_FILL= Color.WHITE; 1480 private static final boolean DEFAULT_CARET_BIAS = true; 1481 1482 private final class TextAttribute { 1483 1484 private ObjectProperty<VPos> textOrigin; 1485 1486 public final VPos getTextOrigin() { 1487 return textOrigin == null ? DEFAULT_TEXT_ORIGIN : textOrigin.get(); 1488 } 1489 1490 public final ObjectProperty<VPos> textOriginProperty() { 1491 if (textOrigin == null) { 1492 textOrigin = new StyleableObjectProperty<VPos>(DEFAULT_TEXT_ORIGIN) { 1493 @Override public Object getBean() { return Text.this; } 1494 @Override public String getName() { return "textOrigin"; } 1495 @Override public CssMetaData getCssMetaData() { 1496 return StyleableProperties.TEXT_ORIGIN; 1497 } 1498 @Override public void invalidated() { 1499 impl_geomChanged(); 1500 } 1501 }; 1502 } 1503 return textOrigin; 1504 } 1505 1506 private BooleanProperty underline; 1507 1508 public final boolean isUnderline() { 1509 return underline == null ? DEFAULT_UNDERLINE : underline.get(); 1510 } 1511 1512 public final BooleanProperty underlineProperty() { 1513 if (underline == null) { 1514 underline = new StyleableBooleanProperty() { 1515 @Override public Object getBean() { return Text.this; } 1516 @Override public String getName() { return "underline"; } 1517 @Override public CssMetaData getCssMetaData() { 1518 return StyleableProperties.UNDERLINE; 1519 } 1520 @Override public void invalidated() { 1521 impl_markDirty(DirtyBits.TEXT_ATTRS); 1522 if (getBoundsType() == TextBoundsType.VISUAL) { 1523 impl_geomChanged(); 1524 } 1525 } 1526 }; 1527 } 1528 return underline; 1529 } 1530 1531 private BooleanProperty strikethrough; 1532 1533 public final boolean isStrikethrough() { 1534 return strikethrough == null ? DEFAULT_STRIKETHROUGH : strikethrough.get(); 1535 } 1536 1537 public final BooleanProperty strikethroughProperty() { 1538 if (strikethrough == null) { 1539 strikethrough = new StyleableBooleanProperty() { 1540 @Override public Object getBean() { return Text.this; } 1541 @Override public String getName() { return "strikethrough"; } 1542 @Override public CssMetaData getCssMetaData() { 1543 return StyleableProperties.STRIKETHROUGH; 1544 } 1545 @Override public void invalidated() { 1546 impl_markDirty(DirtyBits.TEXT_ATTRS); 1547 if (getBoundsType() == TextBoundsType.VISUAL) { 1548 impl_geomChanged(); 1549 } 1550 } 1551 }; 1552 } 1553 return strikethrough; 1554 } 1555 1556 private ObjectProperty<TextAlignment> textAlignment; 1557 1558 public final TextAlignment getTextAlignment() { 1559 return textAlignment == null ? DEFAULT_TEXT_ALIGNMENT : textAlignment.get(); 1560 } 1561 1562 public final ObjectProperty<TextAlignment> textAlignmentProperty() { 1563 if (textAlignment == null) { 1564 textAlignment = 1565 new StyleableObjectProperty<TextAlignment>(DEFAULT_TEXT_ALIGNMENT) { 1566 @Override public Object getBean() { return Text.this; } 1567 @Override public String getName() { return "textAlignment"; } 1568 @Override public CssMetaData getCssMetaData() { 1569 return StyleableProperties.TEXT_ALIGNMENT; 1570 } 1571 @Override public void invalidated() { 1572 if (!isSpan()) { 1573 TextAlignment alignment = get(); 1574 if (alignment == null) { 1575 alignment = DEFAULT_TEXT_ALIGNMENT; 1576 } 1577 TextLayout layout = getTextLayout(); 1578 if (layout.setAlignment(alignment.ordinal())) { 1579 needsTextLayout(); 1580 } 1581 } 1582 } 1583 }; 1584 } 1585 return textAlignment; 1586 } 1587 1588 private DoubleProperty lineSpacing; 1589 1590 public final double getLineSpacing() { 1591 return lineSpacing == null ? DEFAULT_LINE_SPACING : lineSpacing.get(); 1592 } 1593 1594 public final DoubleProperty lineSpacingProperty() { 1595 if (lineSpacing == null) { 1596 lineSpacing = 1597 new StyleableDoubleProperty(DEFAULT_LINE_SPACING) { 1598 @Override public Object getBean() { return Text.this; } 1599 @Override public String getName() { return "lineSpacing"; } 1600 @Override public CssMetaData getCssMetaData() { 1601 return StyleableProperties.LINE_SPACING; 1602 } 1603 @Override public void invalidated() { 1604 if (!isSpan()) { 1605 TextLayout layout = getTextLayout(); 1606 if (layout.setLineSpacing((float)get())) { 1607 needsTextLayout(); 1608 } 1609 } 1610 } 1611 }; 1612 } 1613 return lineSpacing; 1614 } 1615 1616 private ReadOnlyDoubleWrapper baselineOffset; 1617 1618 public final ReadOnlyDoubleProperty baselineOffsetProperty() { 1619 if (baselineOffset == null) { 1620 baselineOffset = new ReadOnlyDoubleWrapper(Text.this, "baselineOffset") { 1621 {bind(new DoubleBinding() { 1622 {bind(fontProperty());} 1623 @Override protected double computeValue() { 1624 /* This method should never be used for spans. 1625 * If it is, it will still returns the ascent 1626 * for the first line in the layout */ 1627 BaseBounds bounds = getLogicalBounds(); 1628 return -bounds.getMinY(); 1629 } 1630 });} 1631 }; 1632 } 1633 return baselineOffset.getReadOnlyProperty(); 1634 } 1635 1636 @Deprecated 1637 private ObjectProperty<PathElement[]> impl_selectionShape; 1638 private ObjectBinding<PathElement[]> selectionBinding; 1639 1640 @Deprecated 1641 public final ReadOnlyObjectProperty<PathElement[]> impl_selectionShapeProperty() { 1642 if (impl_selectionShape == null) { 1643 selectionBinding = new ObjectBinding<PathElement[]>() { 1644 {bind(impl_selectionStartProperty(), impl_selectionEndProperty());} 1645 @Override protected PathElement[] computeValue() { 1646 int start = getSelectionStart(); 1647 int end = getSelectionEnd(); 1648 return getRange(start, end, TextLayout.TYPE_TEXT); 1649 } 1650 }; 1651 impl_selectionShape = new SimpleObjectProperty<PathElement[]>(Text.this, "impl_selectionShape"); 1652 impl_selectionShape.bind(selectionBinding); 1653 } 1654 return impl_selectionShape; 1655 } 1656 1657 private ObjectProperty<Paint> selectionFill; 1658 1659 @Deprecated 1660 public final ObjectProperty<Paint> impl_selectionFillProperty() { 1661 if (selectionFill == null) { 1662 selectionFill = 1663 new ObjectPropertyBase<Paint>(DEFAULT_SELECTION_FILL) { 1664 @Override public Object getBean() { return Text.this; } 1665 @Override public String getName() { return "impl_selectionFill"; } 1666 @Override protected void invalidated() { 1667 impl_markDirty(DirtyBits.TEXT_SELECTION); 1668 } 1669 }; 1670 } 1671 return selectionFill; 1672 } 1673 1674 @Deprecated 1675 private IntegerProperty impl_selectionStart; 1676 1677 @Deprecated 1678 public final int getImpl_selectionStart() { 1679 return impl_selectionStart == null ? DEFAULT_SELECTION_START : impl_selectionStart.get(); 1680 } 1681 1682 @Deprecated 1683 public final IntegerProperty impl_selectionStartProperty() { 1684 if (impl_selectionStart == null) { 1685 impl_selectionStart = 1686 new IntegerPropertyBase(DEFAULT_SELECTION_START) { 1687 @Override public Object getBean() { return Text.this; } 1688 @Override public String getName() { return "impl_selectionStart"; } 1689 @Override protected void invalidated() { 1690 impl_markDirty(DirtyBits.TEXT_SELECTION); 1691 notifyAccessibleAttributeChanged(AccessibleAttribute.SELECTION_START); 1692 } 1693 }; 1694 } 1695 return impl_selectionStart; 1696 } 1697 1698 @Deprecated 1699 private IntegerProperty impl_selectionEnd; 1700 1701 @Deprecated 1702 public final int getImpl_selectionEnd() { 1703 return impl_selectionEnd == null ? DEFAULT_SELECTION_END : impl_selectionEnd.get(); 1704 } 1705 1706 @Deprecated 1707 public final IntegerProperty impl_selectionEndProperty() { 1708 if (impl_selectionEnd == null) { 1709 impl_selectionEnd = 1710 new IntegerPropertyBase(DEFAULT_SELECTION_END) { 1711 @Override public Object getBean() { return Text.this; } 1712 @Override public String getName() { return "impl_selectionEnd"; } 1713 @Override protected void invalidated() { 1714 impl_markDirty(DirtyBits.TEXT_SELECTION); 1715 notifyAccessibleAttributeChanged(AccessibleAttribute.SELECTION_END); 1716 } 1717 }; 1718 } 1719 return impl_selectionEnd; 1720 } 1721 1722 private ObjectProperty<PathElement[]> impl_caretShape; 1723 private ObjectBinding<PathElement[]> caretBinding; 1724 1725 @Deprecated 1726 public final ReadOnlyObjectProperty<PathElement[]> impl_caretShapeProperty() { 1727 if (impl_caretShape == null) { 1728 caretBinding = new ObjectBinding<PathElement[]>() { 1729 {bind(impl_caretPositionProperty(), impl_caretBiasProperty());} 1730 @Override protected PathElement[] computeValue() { 1731 int pos = getImpl_caretPosition(); 1732 int length = getTextInternal().length(); 1733 if (0 <= pos && pos <= length) { 1734 boolean bias = isImpl_caretBias(); 1735 float x = (float)getX(); 1736 float y = (float)getY() - getYRendering(); 1737 TextLayout layout = getTextLayout(); 1738 return layout.getCaretShape(pos, bias, x, y); 1739 } 1740 return EMPTY_PATH_ELEMENT_ARRAY; 1741 } 1742 }; 1743 impl_caretShape = new SimpleObjectProperty<PathElement[]>(Text.this, "impl_caretShape"); 1744 impl_caretShape.bind(caretBinding); 1745 } 1746 return impl_caretShape; 1747 } 1748 1749 @Deprecated 1750 private IntegerProperty impl_caretPosition; 1751 1752 @Deprecated 1753 public final int getImpl_caretPosition() { 1754 return impl_caretPosition == null ? DEFAULT_CARET_POSITION : impl_caretPosition.get(); 1755 } 1756 1757 @Deprecated 1758 public final IntegerProperty impl_caretPositionProperty() { 1759 if (impl_caretPosition == null) { 1760 impl_caretPosition = 1761 new IntegerPropertyBase(DEFAULT_CARET_POSITION) { 1762 @Override public Object getBean() { return Text.this; } 1763 @Override public String getName() { return "impl_caretPosition"; } 1764 @Override protected void invalidated() { 1765 notifyAccessibleAttributeChanged(AccessibleAttribute.SELECTION_END); 1766 } 1767 }; 1768 } 1769 return impl_caretPosition; 1770 } 1771 1772 @Deprecated 1773 private BooleanProperty impl_caretBias; 1774 1775 @Deprecated 1776 public final boolean isImpl_caretBias() { 1777 return impl_caretBias == null ? DEFAULT_CARET_BIAS : impl_caretBias.get(); 1778 } 1779 1780 @Deprecated 1781 public final BooleanProperty impl_caretBiasProperty() { 1782 if (impl_caretBias == null) { 1783 impl_caretBias = 1784 new SimpleBooleanProperty(Text.this, "impl_caretBias", DEFAULT_CARET_BIAS); 1785 } 1786 return impl_caretBias; 1787 } 1788 } 1789 1790 /** 1791 * Returns a string representation of this {@code Text} object. 1792 * @return a string representation of this {@code Text} object. 1793 */ 1794 @Override 1795 public String toString() { 1796 final StringBuilder sb = new StringBuilder("Text["); 1797 1798 String id = getId(); 1799 if (id != null) { 1800 sb.append("id=").append(id).append(", "); 1801 } 1802 1803 sb.append("text=\"").append(getText()).append("\""); 1804 sb.append(", x=").append(getX()); 1805 sb.append(", y=").append(getY()); 1806 sb.append(", alignment=").append(getTextAlignment()); 1807 sb.append(", origin=").append(getTextOrigin()); 1808 sb.append(", boundsType=").append(getBoundsType()); 1809 1810 double spacing = getLineSpacing(); 1811 if (spacing != DEFAULT_LINE_SPACING) { 1812 sb.append(", lineSpacing=").append(spacing); 1813 } 1814 1815 double wrap = getWrappingWidth(); 1816 if (wrap != 0) { 1817 sb.append(", wrappingWidth=").append(wrap); 1818 } 1819 1820 sb.append(", font=").append(getFont()); 1821 sb.append(", fontSmoothingType=").append(getFontSmoothingType()); 1822 1823 if (isStrikethrough()) { 1824 sb.append(", strikethrough"); 1825 } 1826 if (isUnderline()) { 1827 sb.append(", underline"); 1828 } 1829 1830 sb.append(", fill=").append(getFill()); 1831 1832 Paint stroke = getStroke(); 1833 if (stroke != null) { 1834 sb.append(", stroke=").append(stroke); 1835 sb.append(", strokeWidth=").append(getStrokeWidth()); 1836 } 1837 1838 return sb.append("]").toString(); 1839 } 1840 1841 @Override 1842 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { 1843 switch (attribute) { 1844 case TEXT: { 1845 String accText = getAccessibleText(); 1846 if (accText != null && !accText.isEmpty()) return accText; 1847 return getText(); 1848 } 1849 case FONT: return getFont(); 1850 case CARET_OFFSET: { 1851 int sel = getCaretPosition(); 1852 if (sel >= 0) return sel; 1853 return getText().length(); 1854 } 1855 case SELECTION_START: { 1856 int sel = getSelectionStart(); 1857 if (sel >= 0) return sel; 1858 sel = getCaretPosition(); 1859 if (sel >= 0) return sel; 1860 return getText().length(); 1861 } 1862 case SELECTION_END: { 1863 int sel = getSelectionEnd(); 1864 if (sel >= 0) return sel; 1865 sel = getCaretPosition(); 1866 if (sel >= 0) return sel; 1867 return getText().length(); 1868 } 1869 case LINE_FOR_OFFSET: { 1870 int offset = (Integer)parameters[0]; 1871 if (offset > getTextInternal().length()) return null; 1872 TextLine[] lines = getTextLayout().getLines(); 1873 int lineIndex = 0; 1874 for (int i = 1; i < lines.length; i++) { 1875 TextLine line = lines[i]; 1876 if (line.getStart() > offset) break; 1877 lineIndex++; 1878 } 1879 return lineIndex; 1880 } 1881 case LINE_START: { 1882 int lineIndex = (Integer)parameters[0]; 1883 TextLine[] lines = getTextLayout().getLines(); 1884 if (0 <= lineIndex && lineIndex < lines.length) { 1885 TextLine line = lines[lineIndex]; 1886 return line.getStart(); 1887 } 1888 return null; 1889 } 1890 case LINE_END: { 1891 int lineIndex = (Integer)parameters[0]; 1892 TextLine[] lines = getTextLayout().getLines(); 1893 if (0 <= lineIndex && lineIndex < lines.length) { 1894 TextLine line = lines[lineIndex]; 1895 return line.getStart() + line.getLength(); 1896 } 1897 return null; 1898 } 1899 case OFFSET_AT_POINT: { 1900 Point2D point = (Point2D)parameters[0]; 1901 point = screenToLocal(point); 1902 return hitTest(point).getCharIndex(); 1903 } 1904 case BOUNDS_FOR_RANGE: { 1905 int start = (Integer)parameters[0]; 1906 int end = (Integer)parameters[1]; 1907 PathElement[] elements = rangeShape(start, end + 1); 1908 /* Each bounds is defined by a MoveTo (top-left) followed by 1909 * 4 LineTo (to top-right, bottom-right, bottom-left, back to top-left). 1910 */ 1911 Bounds[] bounds = new Bounds[elements.length / 5]; 1912 int index = 0; 1913 for (int i = 0; i < bounds.length; i++) { 1914 MoveTo topLeft = (MoveTo)elements[index]; 1915 LineTo topRight = (LineTo)elements[index+1]; 1916 LineTo bottomRight = (LineTo)elements[index+2]; 1917 BoundingBox b = new BoundingBox(topLeft.getX(), topLeft.getY(), 1918 topRight.getX() - topLeft.getX(), 1919 bottomRight.getY() - topRight.getY()); 1920 bounds[i] = localToScreen(b); 1921 index += 5; 1922 } 1923 return bounds; 1924 } 1925 default: return super.queryAccessibleAttribute(attribute, parameters); 1926 } 1927 } 1928 }