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