< prev index next >

modules/graphics/src/main/java/javafx/scene/text/Text.java

Print this page




  98 text.setTextAlignment(TextAlignment.JUSTIFY)
  99 text.setText("The quick brown fox jumps over the lazy dog");
 100 </PRE>
 101  * @since JavaFX 2.0
 102  */
 103 @DefaultProperty("text")
 104 public class Text extends Shape {
 105     static {
 106         TextHelper.setTextAccessor(new TextHelper.TextAccessor() {
 107             @Override
 108             public NGNode doCreatePeer(Node node) {
 109                 return ((Text) node).doCreatePeer();
 110             }
 111 
 112             @Override
 113             public void doUpdatePeer(Node node) {
 114                 ((Text) node).doUpdatePeer();
 115             }
 116 
 117             @Override





















 118             public com.sun.javafx.geom.Shape doConfigShape(Shape shape) {
 119                 return ((Text) shape).doConfigShape();
 120             }
 121         });
 122     }
 123 
 124     private TextLayout layout;
 125     private static final PathElement[] EMPTY_PATH_ELEMENT_ARRAY = new PathElement[0];
 126 
 127     {
 128         // To initialize the class helper at the begining each constructor of this class
 129         TextHelper.initHelper(this);
 130     }
 131 
 132     /**
 133      * Creates an empty instance of Text.
 134      */
 135     public Text() {
 136         setAccessibleRole(AccessibleRole.TEXT);
 137         InvalidationListener listener = observable -> checkSpan();


 190             int dir = rtl ? TextLayout.DIRECTION_RTL : TextLayout.DIRECTION_LTR;
 191             TextLayout layout = getTextLayout();
 192             if (layout.setDirection(dir)) {
 193                 needsTextLayout();
 194             }
 195         }
 196     }
 197 
 198     @Override
 199     public boolean usesMirroring() {
 200         return false;
 201     }
 202 
 203     private void needsFullTextLayout() {
 204         if (isSpan()) {
 205             /* Create new text span every time the font or text changes
 206              * so the text layout can see that the content has changed.
 207              */
 208             textSpan = null;
 209 
 210             /* Relies on impl_geomChanged() to request text flow to relayout */
 211         } else {
 212             TextLayout layout = getTextLayout();
 213             String string = getTextInternal();
 214             Object font = getFontInternal();
 215             layout.setContent(string, font);
 216         }
 217         needsTextLayout();
 218     }
 219 
 220     private void needsTextLayout() {
 221         textRuns = null;
 222         impl_geomChanged();
 223         NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS);
 224     }
 225 
 226     private TextSpan textSpan;
 227     TextSpan getTextSpan() {
 228         if (textSpan == null) {
 229             textSpan = new TextSpan() {
 230                 @Override public String getText() {
 231                     return getTextInternal();
 232                 }
 233                 @Override public Object getFont() {
 234                     return getFontInternal();
 235                 }
 236                 @Override public RectBounds getBounds() {
 237                     return null;
 238                 }
 239             };
 240         }
 241         return textSpan;
 242     }


 278             GlyphList run = runs[i];
 279             if (run.getTextSpan() == span) {
 280                 count++;
 281             }
 282         }
 283         textRuns = new GlyphList[count];
 284         count = 0;
 285         for (int i = 0; i < runs.length; i++) {
 286             GlyphList run = runs[i];
 287             if (run.getTextSpan() == span) {
 288                 textRuns[count++] = run;
 289             }
 290         }
 291         spanBoundsInvalid = true;
 292 
 293         /* Sometimes a property change in the text node will causes layout in
 294          * text flow. In this case all the dirty bits are already clear and no
 295          * extra work is necessary. Other times the layout is caused by changes
 296          * in the text flow object (wrapping width and text alignment for example).
 297          * In the second case the dirty bits must be set here using
 298          * impl_geomChanged() and NodeHelper.markDirty(). Note that impl_geomChanged()
 299          * causes another (undesired) layout request in the parent.
 300          * In general this is not a problem because shapes are not resizable and
 301          * region objects do not propagate layout changes to the parent.
 302          * This is a special case where a shape is resized by the parent during
 303          * layoutChildren(). See TextFlow#requestLayout() for information how
 304          * text flow deals with this situation.
 305          */
 306         impl_geomChanged();
 307         NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS);
 308     }
 309 
 310     BaseBounds getSpanBounds() {
 311         if (spanBoundsInvalid) {
 312             GlyphList[] runs = getRuns();
 313             if (runs.length != 0) {
 314                 float left = Float.POSITIVE_INFINITY;
 315                 float top = Float.POSITIVE_INFINITY;
 316                 float right = 0;
 317                 float bottom = 0;
 318                 for (int i = 0; i < runs.length; i++) {
 319                     GlyphList run = runs[i];
 320                     com.sun.javafx.geom.Point2D location = run.getLocation();
 321                     float width = run.getWidth();
 322                     float height = run.getLineBounds().getHeight();
 323                     left = Math.min(location.x, left);
 324                     top = Math.min(location.y, top);
 325                     right = Math.max(location.x + width, right);
 326                     bottom = Math.max(location.y + height, bottom);


 439      * Defines the X coordinate of text origin.
 440      *
 441      * @defaultValue 0
 442      */
 443     private DoubleProperty x;
 444 
 445     public final void setX(double value) {
 446         xProperty().set(value);
 447     }
 448 
 449     public final double getX() {
 450         return x == null ? 0.0 : x.get();
 451     }
 452 
 453     public final DoubleProperty xProperty() {
 454         if (x == null) {
 455             x = new DoublePropertyBase() {
 456                 @Override public Object getBean() { return Text.this; }
 457                 @Override public String getName() { return "x"; }
 458                 @Override public void invalidated() {
 459                     impl_geomChanged();
 460                 }
 461             };
 462         }
 463         return x;
 464     }
 465 
 466     /**
 467      * Defines the Y coordinate of text origin.
 468      *
 469      * @defaultValue 0
 470      */
 471     private DoubleProperty y;
 472 
 473     public final void setY(double value) {
 474         yProperty().set(value);
 475     }
 476 
 477     public final double getY() {
 478         return y == null ? 0.0 : y.get();
 479     }
 480 
 481     public final DoubleProperty yProperty() {
 482         if (y == null) {
 483             y = new DoublePropertyBase() {
 484                 @Override public Object getBean() { return Text.this; }
 485                 @Override public String getName() { return "y"; }
 486                 @Override public void invalidated() {
 487                     impl_geomChanged();
 488                 }
 489             };
 490         }
 491         return y;
 492     }
 493 
 494     /**
 495      * Defines the font of text.
 496      *
 497      * @defaultValue Font{}
 498      */
 499     private ObjectProperty<Font> font;
 500 
 501     public final void setFont(Font value) {
 502         fontProperty().set(value);
 503     }
 504 
 505     public final Font getFont() {
 506         return font == null ? Font.getDefault() : font.get();
 507     }


 576     }
 577 
 578     public final ObjectProperty<TextBoundsType> boundsTypeProperty() {
 579         if (boundsType == null) {
 580             boundsType =
 581                new StyleableObjectProperty<TextBoundsType>(DEFAULT_BOUNDS_TYPE) {
 582                    @Override public Object getBean() { return Text.this; }
 583                    @Override public String getName() { return "boundsType"; }
 584                    @Override public CssMetaData<Text,TextBoundsType> getCssMetaData() {
 585                        return StyleableProperties.BOUNDS_TYPE;
 586                    }
 587                    @Override public void invalidated() {
 588                        TextLayout layout = getTextLayout();
 589                        int type = 0;
 590                        if (boundsType.get() == TextBoundsType.LOGICAL_VERTICAL_CENTER) {
 591                            type |= TextLayout.BOUNDS_CENTER;
 592                        }
 593                        if (layout.setBoundsType(type)) {
 594                            needsTextLayout();
 595                        } else {
 596                            impl_geomChanged();
 597                        }
 598                    }
 599             };
 600         }
 601         return boundsType;
 602     }
 603 
 604     /**
 605      * Defines a width constraint for the text in user space coordinates,
 606      * e.g. pixels, not glyph or character count.
 607      * If the value is {@code > 0} text will be line wrapped as needed
 608      * to satisfy this constraint.
 609      *
 610      * @defaultValue 0
 611      */
 612     private DoubleProperty wrappingWidth;
 613 
 614     public final void setWrappingWidth(double value) {
 615         wrappingWidthProperty().set(value);
 616     }
 617 
 618     public final double getWrappingWidth() {
 619         return wrappingWidth == null ? 0 : wrappingWidth.get();
 620     }
 621 
 622     public final DoubleProperty wrappingWidthProperty() {
 623         if (wrappingWidth == null) {
 624             wrappingWidth = new DoublePropertyBase() {
 625                 @Override public Object getBean() { return Text.this; }
 626                 @Override public String getName() { return "wrappingWidth"; }
 627                 @Override public void invalidated() {
 628                     if (!isSpan()) {
 629                         TextLayout layout = getTextLayout();
 630                         if (layout.setWrapWidth((float)get())) {
 631                             needsTextLayout();
 632                         } else {
 633                             impl_geomChanged();
 634                         }
 635                     }
 636                 }
 637             };
 638         }
 639         return wrappingWidth;
 640     }
 641 
 642     public final void setUnderline(boolean value) {
 643         underlineProperty().set(value);
 644     }
 645 
 646     public final boolean isUnderline() {
 647         if (attributes == null || attributes.underline == null) {
 648             return DEFAULT_UNDERLINE;
 649         }
 650         return attributes.isUnderline();
 651     }
 652 
 653     /**


 759     }
 760 
 761     public final FontSmoothingType getFontSmoothingType() {
 762         return fontSmoothingType == null ?
 763             FontSmoothingType.GRAY : fontSmoothingType.get();
 764     }
 765 
 766     public final ObjectProperty<FontSmoothingType>
 767         fontSmoothingTypeProperty() {
 768         if (fontSmoothingType == null) {
 769             fontSmoothingType =
 770                 new StyleableObjectProperty<FontSmoothingType>
 771                                                (FontSmoothingType.GRAY) {
 772                 @Override public Object getBean() { return Text.this; }
 773                 @Override public String getName() { return "fontSmoothingType"; }
 774                 @Override public CssMetaData<Text,FontSmoothingType> getCssMetaData() {
 775                     return StyleableProperties.FONT_SMOOTHING_TYPE;
 776                 }
 777                 @Override public void invalidated() {
 778                     NodeHelper.markDirty(Text.this, DirtyBits.TEXT_ATTRS);
 779                     impl_geomChanged();
 780                 }
 781             };
 782         }
 783         return fontSmoothingType;
 784     }
 785 
 786     /**
 787      * @treatAsPrivate implementation detail
 788      * @deprecated This is an internal API that is not intended
 789      * for use and will be removed in the next version
 790      */
 791     @Deprecated
 792     @Override
 793     protected final void impl_geomChanged() {
 794         super.impl_geomChanged();
 795         if (attributes != null) {
 796             if (attributes.caretBinding != null) {
 797                 attributes.caretBinding.invalidate();
 798             }
 799             if (attributes.selectionBinding != null) {
 800                 attributes.selectionBinding.invalidate();
 801             }
 802         }
 803         NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
 804     }
 805 
 806     /**
 807      * Shape of selection in local coordinates.
 808      *
 809      * @since 9
 810      */
 811     public final PathElement[] getSelectionShape() {
 812         return selectionShapeProperty().get();
 813     }
 814 


1038             BaseBounds vBounds = getVisualBounds();
1039             float delta = vBounds.getMinY() - bounds.getMinY();
1040             switch (origin) {
1041             case TOP: return delta;
1042             case BASELINE: return -vBounds.getMinY() + delta;
1043             case CENTER: return vBounds.getHeight() / 2 + delta;
1044             case BOTTOM: return vBounds.getHeight() + delta;
1045             default: return 0;
1046             }
1047         } else {
1048             switch (origin) {
1049             case TOP: return 0;
1050             case BASELINE: return -bounds.getMinY();
1051             case CENTER: return bounds.getHeight() / 2;
1052             case BOTTOM: return bounds.getHeight();
1053             default: return 0;
1054             }
1055         }
1056     }
1057 
1058     /**
1059      * @treatAsPrivate implementation detail
1060      * @deprecated This is an internal API that is not intended
1061      * for use and will be removed in the next version
1062      */
1063     @Deprecated
1064     @Override
1065     protected final Bounds impl_computeLayoutBounds() {
1066         if (isSpan()) {
1067             BaseBounds bounds = getSpanBounds();
1068             double width = bounds.getWidth();
1069             double height = bounds.getHeight();
1070             return new BoundingBox(0, 0, width, height);
1071         }
1072 
1073         if (getBoundsType() == TextBoundsType.VISUAL) {
1074             /* In Node the layout bounds is computed based in the geom
1075              * bounds and in Shape the geom bounds is computed based
1076              * on the shape (generated here in #configShape()) */
1077             return super.impl_computeLayoutBounds();
1078         }
1079         BaseBounds bounds = getLogicalBounds();
1080         double x = bounds.getMinX() + getX();
1081         double y = bounds.getMinY() + getY() + getYAdjustment(bounds);
1082         double width = bounds.getWidth();
1083         double height = bounds.getHeight();
1084         double wrappingWidth = getWrappingWidth();
1085         if (wrappingWidth != 0) width = wrappingWidth;
1086         return new BoundingBox(x, y, width, height);
1087     }
1088 
1089     /**
1090      * @treatAsPrivate implementation detail
1091      * @deprecated This is an internal API that is not intended
1092      * for use and will be removed in the next version
1093      */
1094     @Deprecated
1095     @Override
1096     public final BaseBounds impl_computeGeomBounds(BaseBounds bounds,
1097                                                    BaseTransform tx) {
1098         if (isSpan()) {
1099             if (ShapeHelper.getMode(this) != NGShape.Mode.FILL && getStrokeType() != StrokeType.INSIDE) {
1100                 return super.impl_computeGeomBounds(bounds, tx);
1101             }
1102             TextLayout layout = getTextLayout();
1103             bounds = layout.getBounds(getTextSpan(), bounds);
1104             BaseBounds spanBounds = getSpanBounds();
1105             float minX = bounds.getMinX() - spanBounds.getMinX();
1106             float minY = bounds.getMinY() - spanBounds.getMinY();
1107             float maxX = minX + bounds.getWidth();
1108             float maxY = minY + bounds.getHeight();
1109             bounds = bounds.deriveWithNewBounds(minX, minY, 0, maxX, maxY, 0);
1110             return tx.transform(bounds, bounds);
1111         }
1112 
1113         if (getBoundsType() == TextBoundsType.VISUAL) {
1114             if (getTextInternal().length() == 0 || ShapeHelper.getMode(this) == NGShape.Mode.EMPTY) {
1115                 return bounds.makeEmpty();
1116             }
1117             if (ShapeHelper.getMode(this) == NGShape.Mode.FILL || getStrokeType() == StrokeType.INSIDE) {
1118                 /* Optimize for FILL and INNER STROKE: save the cost of shaping each glyph */
1119                 BaseBounds visualBounds = getVisualBounds();
1120                 float x = visualBounds.getMinX() + (float) getX();
1121                 float yadj = getYAdjustment(visualBounds);
1122                 float y = visualBounds.getMinY() + yadj + (float) getY();
1123                 bounds.deriveWithNewBounds(x, y, 0, x + visualBounds.getWidth(),
1124                         y + visualBounds.getHeight(), 0);
1125                 return tx.transform(bounds, bounds);
1126             } else {
1127                 /* Let the super class compute the bounds using shape */
1128                 return super.impl_computeGeomBounds(bounds, tx);
1129             }
1130         }
1131 
1132         BaseBounds textBounds = getLogicalBounds();
1133         float x = textBounds.getMinX() + (float)getX();
1134         float yadj = getYAdjustment(textBounds);
1135         float y = textBounds.getMinY() + yadj + (float)getY();
1136         float width = textBounds.getWidth();
1137         float height = textBounds.getHeight();
1138         float wrappingWidth = (float)getWrappingWidth();
1139         if (wrappingWidth > width) {
1140             width = wrappingWidth;
1141         } else {
1142             /* The following adjustment is necessary for the text bounds to be
1143              * relative to the same location as the mirrored bounds returned
1144              * by layout.getBounds().
1145              */
1146             if (wrappingWidth > 0) {
1147                 NodeOrientation orientation = getEffectiveNodeOrientation();
1148                 if (orientation == NodeOrientation.RIGHT_TO_LEFT) {
1149                     x -= width - wrappingWidth;
1150                 }
1151             }
1152         }
1153         textBounds = new RectBounds(x, y, x + width, y + height);
1154 
1155         /* handle stroked text */
1156         if (ShapeHelper.getMode(this) != NGShape.Mode.FILL && getStrokeType() != StrokeType.INSIDE) {
1157             bounds =
1158                 super.impl_computeGeomBounds(bounds,
1159                                              BaseTransform.IDENTITY_TRANSFORM);
1160         } else {
1161             TextLayout layout = getTextLayout();
1162             bounds = layout.getBounds(null, bounds);
1163             x = bounds.getMinX() + (float)getX();
1164             width = bounds.getWidth();
1165             bounds = bounds.deriveWithNewBounds(x, y, 0, x + width, y + height, 0);
1166         }
1167 
1168         bounds = bounds.deriveWithUnion(textBounds);
1169         return tx.transform(bounds, bounds);
1170     }
1171 
1172     /**
1173      * @treatAsPrivate implementation detail
1174      * @deprecated This is an internal API that is not intended
1175      * for use and will be removed in the next version
1176      */
1177     @Deprecated
1178     @Override
1179     protected final boolean impl_computeContains(double localX, double localY) {
1180         /* Used for spans, regular text uses bounds based picking */
1181         double x = localX + getSpanBounds().getMinX();
1182         double y = localY + getSpanBounds().getMinY();
1183         GlyphList[] runs = getRuns();
1184         if (runs.length != 0) {
1185             for (int i = 0; i < runs.length; i++) {
1186                 GlyphList run = runs[i];
1187                 com.sun.javafx.geom.Point2D location = run.getLocation();
1188                 float width = run.getWidth();
1189                 RectBounds lineBounds = run.getLineBounds();
1190                 float height = lineBounds.getHeight();
1191                 if (location.x <= x && x < location.x + width &&
1192                     location.y <= y && y < location.y + height) {
1193                         return true;
1194                 }
1195             }
1196         }
1197         return false;
1198     }
1199 


1481     private static final Color DEFAULT_SELECTION_FILL= Color.WHITE;
1482     private static final boolean DEFAULT_CARET_BIAS = true;
1483 
1484     private final class TextAttribute {
1485 
1486         private ObjectProperty<VPos> textOrigin;
1487 
1488         public final VPos getTextOrigin() {
1489             return textOrigin == null ? DEFAULT_TEXT_ORIGIN : textOrigin.get();
1490         }
1491 
1492         public final ObjectProperty<VPos> textOriginProperty() {
1493             if (textOrigin == null) {
1494                 textOrigin = new StyleableObjectProperty<VPos>(DEFAULT_TEXT_ORIGIN) {
1495                     @Override public Object getBean() { return Text.this; }
1496                     @Override public String getName() { return "textOrigin"; }
1497                     @Override public CssMetaData getCssMetaData() {
1498                         return StyleableProperties.TEXT_ORIGIN;
1499                     }
1500                     @Override public void invalidated() {
1501                         impl_geomChanged();
1502                     }
1503                 };
1504             }
1505             return textOrigin;
1506         }
1507 
1508         private BooleanProperty underline;
1509 
1510         public final boolean isUnderline() {
1511             return underline == null ? DEFAULT_UNDERLINE : underline.get();
1512         }
1513 
1514         public final BooleanProperty underlineProperty() {
1515             if (underline == null) {
1516                 underline = new StyleableBooleanProperty() {
1517                     @Override public Object getBean() { return Text.this; }
1518                     @Override public String getName() { return "underline"; }
1519                     @Override public CssMetaData getCssMetaData() {
1520                         return StyleableProperties.UNDERLINE;
1521                     }
1522                     @Override public void invalidated() {
1523                         NodeHelper.markDirty(Text.this, DirtyBits.TEXT_ATTRS);
1524                         if (getBoundsType() == TextBoundsType.VISUAL) {
1525                             impl_geomChanged();
1526                         }
1527                     }
1528                 };
1529             }
1530             return underline;
1531         }
1532 
1533         private BooleanProperty strikethrough;
1534 
1535         public final boolean isStrikethrough() {
1536             return strikethrough == null ? DEFAULT_STRIKETHROUGH : strikethrough.get();
1537         }
1538 
1539         public final BooleanProperty strikethroughProperty() {
1540             if (strikethrough == null) {
1541                 strikethrough = new StyleableBooleanProperty() {
1542                     @Override public Object getBean() { return Text.this; }
1543                     @Override public String getName() { return "strikethrough"; }
1544                     @Override public CssMetaData getCssMetaData() {
1545                         return StyleableProperties.STRIKETHROUGH;
1546                     }
1547                     @Override public void invalidated() {
1548                         NodeHelper.markDirty(Text.this, DirtyBits.TEXT_ATTRS);
1549                         if (getBoundsType() == TextBoundsType.VISUAL) {
1550                             impl_geomChanged();
1551                         }
1552                     }
1553                 };
1554             }
1555             return strikethrough;
1556         }
1557 
1558         private ObjectProperty<TextAlignment> textAlignment;
1559 
1560         public final TextAlignment getTextAlignment() {
1561             return textAlignment == null ? DEFAULT_TEXT_ALIGNMENT : textAlignment.get();
1562         }
1563 
1564         public final ObjectProperty<TextAlignment> textAlignmentProperty() {
1565             if (textAlignment == null) {
1566                 textAlignment =
1567                     new StyleableObjectProperty<TextAlignment>(DEFAULT_TEXT_ALIGNMENT) {
1568                     @Override public Object getBean() { return Text.this; }
1569                     @Override public String getName() { return "textAlignment"; }
1570                     @Override public CssMetaData getCssMetaData() {




  98 text.setTextAlignment(TextAlignment.JUSTIFY)
  99 text.setText("The quick brown fox jumps over the lazy dog");
 100 </PRE>
 101  * @since JavaFX 2.0
 102  */
 103 @DefaultProperty("text")
 104 public class Text extends Shape {
 105     static {
 106         TextHelper.setTextAccessor(new TextHelper.TextAccessor() {
 107             @Override
 108             public NGNode doCreatePeer(Node node) {
 109                 return ((Text) node).doCreatePeer();
 110             }
 111 
 112             @Override
 113             public void doUpdatePeer(Node node) {
 114                 ((Text) node).doUpdatePeer();
 115             }
 116 
 117             @Override
 118             public Bounds doComputeLayoutBounds(Node node) {
 119                 return ((Text) node).doComputeLayoutBounds();
 120             }
 121 
 122             @Override
 123             public BaseBounds doComputeGeomBounds(Node node,
 124             BaseBounds bounds, BaseTransform tx) {
 125                 return ((Text) node).doComputeGeomBounds(bounds, tx);
 126             }
 127 
 128             @Override
 129             public boolean doComputeContains(Node node, double localX, double localY) {
 130                 return ((Text) node).doComputeContains(localX, localY);
 131             }
 132 
 133             @Override
 134             public void doGeomChanged(Node node) {
 135                 ((Text) node).doGeomChanged();
 136             }
 137 
 138             @Override
 139             public com.sun.javafx.geom.Shape doConfigShape(Shape shape) {
 140                 return ((Text) shape).doConfigShape();
 141             }
 142         });
 143     }
 144 
 145     private TextLayout layout;
 146     private static final PathElement[] EMPTY_PATH_ELEMENT_ARRAY = new PathElement[0];
 147 
 148     {
 149         // To initialize the class helper at the begining each constructor of this class
 150         TextHelper.initHelper(this);
 151     }
 152 
 153     /**
 154      * Creates an empty instance of Text.
 155      */
 156     public Text() {
 157         setAccessibleRole(AccessibleRole.TEXT);
 158         InvalidationListener listener = observable -> checkSpan();


 211             int dir = rtl ? TextLayout.DIRECTION_RTL : TextLayout.DIRECTION_LTR;
 212             TextLayout layout = getTextLayout();
 213             if (layout.setDirection(dir)) {
 214                 needsTextLayout();
 215             }
 216         }
 217     }
 218 
 219     @Override
 220     public boolean usesMirroring() {
 221         return false;
 222     }
 223 
 224     private void needsFullTextLayout() {
 225         if (isSpan()) {
 226             /* Create new text span every time the font or text changes
 227              * so the text layout can see that the content has changed.
 228              */
 229             textSpan = null;
 230 
 231             /* Relies on NodeHelper.geomChanged(this) to request text flow to relayout */
 232         } else {
 233             TextLayout layout = getTextLayout();
 234             String string = getTextInternal();
 235             Object font = getFontInternal();
 236             layout.setContent(string, font);
 237         }
 238         needsTextLayout();
 239     }
 240 
 241     private void needsTextLayout() {
 242         textRuns = null;
 243         NodeHelper.geomChanged(this);
 244         NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS);
 245     }
 246 
 247     private TextSpan textSpan;
 248     TextSpan getTextSpan() {
 249         if (textSpan == null) {
 250             textSpan = new TextSpan() {
 251                 @Override public String getText() {
 252                     return getTextInternal();
 253                 }
 254                 @Override public Object getFont() {
 255                     return getFontInternal();
 256                 }
 257                 @Override public RectBounds getBounds() {
 258                     return null;
 259                 }
 260             };
 261         }
 262         return textSpan;
 263     }


 299             GlyphList run = runs[i];
 300             if (run.getTextSpan() == span) {
 301                 count++;
 302             }
 303         }
 304         textRuns = new GlyphList[count];
 305         count = 0;
 306         for (int i = 0; i < runs.length; i++) {
 307             GlyphList run = runs[i];
 308             if (run.getTextSpan() == span) {
 309                 textRuns[count++] = run;
 310             }
 311         }
 312         spanBoundsInvalid = true;
 313 
 314         /* Sometimes a property change in the text node will causes layout in
 315          * text flow. In this case all the dirty bits are already clear and no
 316          * extra work is necessary. Other times the layout is caused by changes
 317          * in the text flow object (wrapping width and text alignment for example).
 318          * In the second case the dirty bits must be set here using
 319          * NodeHelper.geomChanged(this) and NodeHelper.markDirty(). Note that NodeHelper.geomChanged(this)
 320          * causes another (undesired) layout request in the parent.
 321          * In general this is not a problem because shapes are not resizable and
 322          * region objects do not propagate layout changes to the parent.
 323          * This is a special case where a shape is resized by the parent during
 324          * layoutChildren(). See TextFlow#requestLayout() for information how
 325          * text flow deals with this situation.
 326          */
 327         NodeHelper.geomChanged(this);
 328         NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS);
 329     }
 330 
 331     BaseBounds getSpanBounds() {
 332         if (spanBoundsInvalid) {
 333             GlyphList[] runs = getRuns();
 334             if (runs.length != 0) {
 335                 float left = Float.POSITIVE_INFINITY;
 336                 float top = Float.POSITIVE_INFINITY;
 337                 float right = 0;
 338                 float bottom = 0;
 339                 for (int i = 0; i < runs.length; i++) {
 340                     GlyphList run = runs[i];
 341                     com.sun.javafx.geom.Point2D location = run.getLocation();
 342                     float width = run.getWidth();
 343                     float height = run.getLineBounds().getHeight();
 344                     left = Math.min(location.x, left);
 345                     top = Math.min(location.y, top);
 346                     right = Math.max(location.x + width, right);
 347                     bottom = Math.max(location.y + height, bottom);


 460      * Defines the X coordinate of text origin.
 461      *
 462      * @defaultValue 0
 463      */
 464     private DoubleProperty x;
 465 
 466     public final void setX(double value) {
 467         xProperty().set(value);
 468     }
 469 
 470     public final double getX() {
 471         return x == null ? 0.0 : x.get();
 472     }
 473 
 474     public final DoubleProperty xProperty() {
 475         if (x == null) {
 476             x = new DoublePropertyBase() {
 477                 @Override public Object getBean() { return Text.this; }
 478                 @Override public String getName() { return "x"; }
 479                 @Override public void invalidated() {
 480                     NodeHelper.geomChanged(Text.this);
 481                 }
 482             };
 483         }
 484         return x;
 485     }
 486 
 487     /**
 488      * Defines the Y coordinate of text origin.
 489      *
 490      * @defaultValue 0
 491      */
 492     private DoubleProperty y;
 493 
 494     public final void setY(double value) {
 495         yProperty().set(value);
 496     }
 497 
 498     public final double getY() {
 499         return y == null ? 0.0 : y.get();
 500     }
 501 
 502     public final DoubleProperty yProperty() {
 503         if (y == null) {
 504             y = new DoublePropertyBase() {
 505                 @Override public Object getBean() { return Text.this; }
 506                 @Override public String getName() { return "y"; }
 507                 @Override public void invalidated() {
 508                     NodeHelper.geomChanged(Text.this);
 509                 }
 510             };
 511         }
 512         return y;
 513     }
 514 
 515     /**
 516      * Defines the font of text.
 517      *
 518      * @defaultValue Font{}
 519      */
 520     private ObjectProperty<Font> font;
 521 
 522     public final void setFont(Font value) {
 523         fontProperty().set(value);
 524     }
 525 
 526     public final Font getFont() {
 527         return font == null ? Font.getDefault() : font.get();
 528     }


 597     }
 598 
 599     public final ObjectProperty<TextBoundsType> boundsTypeProperty() {
 600         if (boundsType == null) {
 601             boundsType =
 602                new StyleableObjectProperty<TextBoundsType>(DEFAULT_BOUNDS_TYPE) {
 603                    @Override public Object getBean() { return Text.this; }
 604                    @Override public String getName() { return "boundsType"; }
 605                    @Override public CssMetaData<Text,TextBoundsType> getCssMetaData() {
 606                        return StyleableProperties.BOUNDS_TYPE;
 607                    }
 608                    @Override public void invalidated() {
 609                        TextLayout layout = getTextLayout();
 610                        int type = 0;
 611                        if (boundsType.get() == TextBoundsType.LOGICAL_VERTICAL_CENTER) {
 612                            type |= TextLayout.BOUNDS_CENTER;
 613                        }
 614                        if (layout.setBoundsType(type)) {
 615                            needsTextLayout();
 616                        } else {
 617                            NodeHelper.geomChanged(Text.this);
 618                        }
 619                    }
 620             };
 621         }
 622         return boundsType;
 623     }
 624 
 625     /**
 626      * Defines a width constraint for the text in user space coordinates,
 627      * e.g. pixels, not glyph or character count.
 628      * If the value is {@code > 0} text will be line wrapped as needed
 629      * to satisfy this constraint.
 630      *
 631      * @defaultValue 0
 632      */
 633     private DoubleProperty wrappingWidth;
 634 
 635     public final void setWrappingWidth(double value) {
 636         wrappingWidthProperty().set(value);
 637     }
 638 
 639     public final double getWrappingWidth() {
 640         return wrappingWidth == null ? 0 : wrappingWidth.get();
 641     }
 642 
 643     public final DoubleProperty wrappingWidthProperty() {
 644         if (wrappingWidth == null) {
 645             wrappingWidth = new DoublePropertyBase() {
 646                 @Override public Object getBean() { return Text.this; }
 647                 @Override public String getName() { return "wrappingWidth"; }
 648                 @Override public void invalidated() {
 649                     if (!isSpan()) {
 650                         TextLayout layout = getTextLayout();
 651                         if (layout.setWrapWidth((float)get())) {
 652                             needsTextLayout();
 653                         } else {
 654                             NodeHelper.geomChanged(Text.this);
 655                         }
 656                     }
 657                 }
 658             };
 659         }
 660         return wrappingWidth;
 661     }
 662 
 663     public final void setUnderline(boolean value) {
 664         underlineProperty().set(value);
 665     }
 666 
 667     public final boolean isUnderline() {
 668         if (attributes == null || attributes.underline == null) {
 669             return DEFAULT_UNDERLINE;
 670         }
 671         return attributes.isUnderline();
 672     }
 673 
 674     /**


 780     }
 781 
 782     public final FontSmoothingType getFontSmoothingType() {
 783         return fontSmoothingType == null ?
 784             FontSmoothingType.GRAY : fontSmoothingType.get();
 785     }
 786 
 787     public final ObjectProperty<FontSmoothingType>
 788         fontSmoothingTypeProperty() {
 789         if (fontSmoothingType == null) {
 790             fontSmoothingType =
 791                 new StyleableObjectProperty<FontSmoothingType>
 792                                                (FontSmoothingType.GRAY) {
 793                 @Override public Object getBean() { return Text.this; }
 794                 @Override public String getName() { return "fontSmoothingType"; }
 795                 @Override public CssMetaData<Text,FontSmoothingType> getCssMetaData() {
 796                     return StyleableProperties.FONT_SMOOTHING_TYPE;
 797                 }
 798                 @Override public void invalidated() {
 799                     NodeHelper.markDirty(Text.this, DirtyBits.TEXT_ATTRS);
 800                     NodeHelper.geomChanged(Text.this);
 801                 }
 802             };
 803         }
 804         return fontSmoothingType;
 805     }
 806 
 807     /*
 808      * Note: This method MUST only be called via its accessor method.


 809      */
 810     private final void doGeomChanged() {



 811         if (attributes != null) {
 812             if (attributes.caretBinding != null) {
 813                 attributes.caretBinding.invalidate();
 814             }
 815             if (attributes.selectionBinding != null) {
 816                 attributes.selectionBinding.invalidate();
 817             }
 818         }
 819         NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
 820     }
 821 
 822     /**
 823      * Shape of selection in local coordinates.
 824      *
 825      * @since 9
 826      */
 827     public final PathElement[] getSelectionShape() {
 828         return selectionShapeProperty().get();
 829     }
 830 


1054             BaseBounds vBounds = getVisualBounds();
1055             float delta = vBounds.getMinY() - bounds.getMinY();
1056             switch (origin) {
1057             case TOP: return delta;
1058             case BASELINE: return -vBounds.getMinY() + delta;
1059             case CENTER: return vBounds.getHeight() / 2 + delta;
1060             case BOTTOM: return vBounds.getHeight() + delta;
1061             default: return 0;
1062             }
1063         } else {
1064             switch (origin) {
1065             case TOP: return 0;
1066             case BASELINE: return -bounds.getMinY();
1067             case CENTER: return bounds.getHeight() / 2;
1068             case BOTTOM: return bounds.getHeight();
1069             default: return 0;
1070             }
1071         }
1072     }
1073 
1074     private final Bounds doComputeLayoutBounds() {







1075         if (isSpan()) {
1076             BaseBounds bounds = getSpanBounds();
1077             double width = bounds.getWidth();
1078             double height = bounds.getHeight();
1079             return new BoundingBox(0, 0, width, height);
1080         }
1081 
1082         if (getBoundsType() == TextBoundsType.VISUAL) {
1083             /* In Node the layout bounds is computed based in the geom
1084              * bounds and in Shape the geom bounds is computed based
1085              * on the shape (generated here in #configShape()) */
1086             return TextHelper.superComputeLayoutBounds(this);
1087         }
1088         BaseBounds bounds = getLogicalBounds();
1089         double x = bounds.getMinX() + getX();
1090         double y = bounds.getMinY() + getY() + getYAdjustment(bounds);
1091         double width = bounds.getWidth();
1092         double height = bounds.getHeight();
1093         double wrappingWidth = getWrappingWidth();
1094         if (wrappingWidth != 0) width = wrappingWidth;
1095         return new BoundingBox(x, y, width, height);
1096     }
1097 
1098     /*
1099      * Note: This method MUST only be called via its accessor method.


1100      */
1101     private BaseBounds doComputeGeomBounds(BaseBounds bounds,


1102                                                    BaseTransform tx) {
1103         if (isSpan()) {
1104             if (ShapeHelper.getMode(this) != NGShape.Mode.FILL && getStrokeType() != StrokeType.INSIDE) {
1105                 return TextHelper.superComputeGeomBounds(this, bounds, tx);
1106             }
1107             TextLayout layout = getTextLayout();
1108             bounds = layout.getBounds(getTextSpan(), bounds);
1109             BaseBounds spanBounds = getSpanBounds();
1110             float minX = bounds.getMinX() - spanBounds.getMinX();
1111             float minY = bounds.getMinY() - spanBounds.getMinY();
1112             float maxX = minX + bounds.getWidth();
1113             float maxY = minY + bounds.getHeight();
1114             bounds = bounds.deriveWithNewBounds(minX, minY, 0, maxX, maxY, 0);
1115             return tx.transform(bounds, bounds);
1116         }
1117 
1118        if (getBoundsType() == TextBoundsType.VISUAL) {
1119             if (getTextInternal().length() == 0 || ShapeHelper.getMode(this) == NGShape.Mode.EMPTY) {
1120                 return bounds.makeEmpty();
1121             }
1122             if (ShapeHelper.getMode(this) == NGShape.Mode.FILL || getStrokeType() == StrokeType.INSIDE) {
1123                 /* Optimize for FILL and INNER STROKE: save the cost of shaping each glyph */
1124                 BaseBounds visualBounds = getVisualBounds();
1125                 float x = visualBounds.getMinX() + (float) getX();
1126                 float yadj = getYAdjustment(visualBounds);
1127                 float y = visualBounds.getMinY() + yadj + (float) getY();
1128                 bounds.deriveWithNewBounds(x, y, 0, x + visualBounds.getWidth(),
1129                         y + visualBounds.getHeight(), 0);
1130                 return tx.transform(bounds, bounds);
1131             } else {
1132                 /* Let the super class compute the bounds using shape */
1133                 return TextHelper.superComputeGeomBounds(this, bounds, tx);
1134             }
1135         }
1136 
1137         BaseBounds textBounds = getLogicalBounds();
1138         float x = textBounds.getMinX() + (float)getX();
1139         float yadj = getYAdjustment(textBounds);
1140         float y = textBounds.getMinY() + yadj + (float)getY();
1141         float width = textBounds.getWidth();
1142         float height = textBounds.getHeight();
1143         float wrappingWidth = (float)getWrappingWidth();
1144         if (wrappingWidth > width) {
1145             width = wrappingWidth;
1146         } else {
1147             /* The following adjustment is necessary for the text bounds to be
1148              * relative to the same location as the mirrored bounds returned
1149              * by layout.getBounds().
1150              */
1151             if (wrappingWidth > 0) {
1152                 NodeOrientation orientation = getEffectiveNodeOrientation();
1153                 if (orientation == NodeOrientation.RIGHT_TO_LEFT) {
1154                     x -= width - wrappingWidth;
1155                 }
1156             }
1157         }
1158         textBounds = new RectBounds(x, y, x + width, y + height);
1159 
1160         /* handle stroked text */
1161         if (ShapeHelper.getMode(this) != NGShape.Mode.FILL && getStrokeType() != StrokeType.INSIDE) {
1162             bounds = TextHelper.superComputeGeomBounds(this, bounds,

1163                     BaseTransform.IDENTITY_TRANSFORM);
1164         } else {
1165             TextLayout layout = getTextLayout();
1166             bounds = layout.getBounds(null, bounds);
1167             x = bounds.getMinX() + (float)getX();
1168             width = bounds.getWidth();
1169             bounds = bounds.deriveWithNewBounds(x, y, 0, x + width, y + height, 0);
1170         }
1171 
1172         bounds = bounds.deriveWithUnion(textBounds);
1173         return tx.transform(bounds, bounds);
1174     }
1175 
1176     /*
1177      * Note: This method MUST only be called via its accessor method.


1178      */
1179     private boolean doComputeContains(double localX, double localY) {


1180         /* Used for spans, regular text uses bounds based picking */
1181         double x = localX + getSpanBounds().getMinX();
1182         double y = localY + getSpanBounds().getMinY();
1183         GlyphList[] runs = getRuns();
1184         if (runs.length != 0) {
1185             for (int i = 0; i < runs.length; i++) {
1186                 GlyphList run = runs[i];
1187                 com.sun.javafx.geom.Point2D location = run.getLocation();
1188                 float width = run.getWidth();
1189                 RectBounds lineBounds = run.getLineBounds();
1190                 float height = lineBounds.getHeight();
1191                 if (location.x <= x && x < location.x + width &&
1192                     location.y <= y && y < location.y + height) {
1193                         return true;
1194                 }
1195             }
1196         }
1197         return false;
1198     }
1199 


1481     private static final Color DEFAULT_SELECTION_FILL= Color.WHITE;
1482     private static final boolean DEFAULT_CARET_BIAS = true;
1483 
1484     private final class TextAttribute {
1485 
1486         private ObjectProperty<VPos> textOrigin;
1487 
1488         public final VPos getTextOrigin() {
1489             return textOrigin == null ? DEFAULT_TEXT_ORIGIN : textOrigin.get();
1490         }
1491 
1492         public final ObjectProperty<VPos> textOriginProperty() {
1493             if (textOrigin == null) {
1494                 textOrigin = new StyleableObjectProperty<VPos>(DEFAULT_TEXT_ORIGIN) {
1495                     @Override public Object getBean() { return Text.this; }
1496                     @Override public String getName() { return "textOrigin"; }
1497                     @Override public CssMetaData getCssMetaData() {
1498                         return StyleableProperties.TEXT_ORIGIN;
1499                     }
1500                     @Override public void invalidated() {
1501                         NodeHelper.geomChanged(Text.this);
1502                     }
1503                 };
1504             }
1505             return textOrigin;
1506         }
1507 
1508         private BooleanProperty underline;
1509 
1510         public final boolean isUnderline() {
1511             return underline == null ? DEFAULT_UNDERLINE : underline.get();
1512         }
1513 
1514         public final BooleanProperty underlineProperty() {
1515             if (underline == null) {
1516                 underline = new StyleableBooleanProperty() {
1517                     @Override public Object getBean() { return Text.this; }
1518                     @Override public String getName() { return "underline"; }
1519                     @Override public CssMetaData getCssMetaData() {
1520                         return StyleableProperties.UNDERLINE;
1521                     }
1522                     @Override public void invalidated() {
1523                         NodeHelper.markDirty(Text.this, DirtyBits.TEXT_ATTRS);
1524                         if (getBoundsType() == TextBoundsType.VISUAL) {
1525                             NodeHelper.geomChanged(Text.this);
1526                         }
1527                     }
1528                 };
1529             }
1530             return underline;
1531         }
1532 
1533         private BooleanProperty strikethrough;
1534 
1535         public final boolean isStrikethrough() {
1536             return strikethrough == null ? DEFAULT_STRIKETHROUGH : strikethrough.get();
1537         }
1538 
1539         public final BooleanProperty strikethroughProperty() {
1540             if (strikethrough == null) {
1541                 strikethrough = new StyleableBooleanProperty() {
1542                     @Override public Object getBean() { return Text.this; }
1543                     @Override public String getName() { return "strikethrough"; }
1544                     @Override public CssMetaData getCssMetaData() {
1545                         return StyleableProperties.STRIKETHROUGH;
1546                     }
1547                     @Override public void invalidated() {
1548                         NodeHelper.markDirty(Text.this, DirtyBits.TEXT_ATTRS);
1549                         if (getBoundsType() == TextBoundsType.VISUAL) {
1550                             NodeHelper.geomChanged(Text.this);
1551                         }
1552                     }
1553                 };
1554             }
1555             return strikethrough;
1556         }
1557 
1558         private ObjectProperty<TextAlignment> textAlignment;
1559 
1560         public final TextAlignment getTextAlignment() {
1561             return textAlignment == null ? DEFAULT_TEXT_ALIGNMENT : textAlignment.get();
1562         }
1563 
1564         public final ObjectProperty<TextAlignment> textAlignmentProperty() {
1565             if (textAlignment == null) {
1566                 textAlignment =
1567                     new StyleableObjectProperty<TextAlignment>(DEFAULT_TEXT_ALIGNMENT) {
1568                     @Override public Object getBean() { return Text.this; }
1569                     @Override public String getName() { return "textAlignment"; }
1570                     @Override public CssMetaData getCssMetaData() {


< prev index next >