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

Print this page




 217      * Returns shape for the range of the text in local coordinates.
 218      *
 219      * @param start the beginning character index for the range
 220      * @param start the end character index (non-inclusive) for the range
 221      * @return an array of {@code PathElement} which can be used to create a {@code Shape}
 222      * @since 9
 223      */
 224     public final PathElement[] rangeShape(int start, int end) {
 225         return getRange(start, end, TextLayout.TYPE_TEXT);
 226     }
 227 
 228     @Override
 229     public boolean usesMirroring() {
 230         return false;
 231     }
 232 
 233     @Override protected void setWidth(double value) {
 234         if (value != getWidth()) {
 235             TextLayout layout = getTextLayout();
 236             Insets insets = getInsets();
 237             double left = snapSpace(insets.getLeft());
 238             double right = snapSpace(insets.getRight());
 239             double width = Math.max(1, value - left - right);
 240             layout.setWrapWidth((float)width);
 241             super.setWidth(value);
 242         }
 243     }
 244 
 245     @Override protected double computePrefWidth(double height) {
 246         TextLayout layout = getTextLayout();
 247         layout.setWrapWidth(0);
 248         double width = layout.getBounds().getWidth();
 249         Insets insets = getInsets();
 250         double left = snapSpace(insets.getLeft());
 251         double right = snapSpace(insets.getRight());
 252         double wrappingWidth = Math.max(1, getWidth() - left - right);
 253         layout.setWrapWidth((float)wrappingWidth);
 254         return left + width + right;
 255     }
 256 
 257     @Override protected double computePrefHeight(double width) {
 258         TextLayout layout = getTextLayout();
 259         Insets insets = getInsets();
 260         double left = snapSpace(insets.getLeft());
 261         double right = snapSpace(insets.getRight());
 262         if (width == USE_COMPUTED_SIZE) {
 263             layout.setWrapWidth(0);
 264         } else {
 265             double wrappingWidth = Math.max(1, width - left - right);
 266             layout.setWrapWidth((float)wrappingWidth);
 267         }
 268         double height = layout.getBounds().getHeight();
 269         double wrappingWidth = Math.max(1, getWidth() - left - right);
 270         layout.setWrapWidth((float)wrappingWidth);
 271         double top = snapSpace(insets.getTop());
 272         double bottom = snapSpace(insets.getBottom());
 273         return top + height + bottom;
 274     }
 275 
 276     @Override protected double computeMinHeight(double width) {
 277         return computePrefHeight(width);
 278     }
 279 
 280     @Override public void requestLayout() {
 281         /* The geometry of text nodes can be changed during layout children.
 282          * For that reason it has to call impl_geomChanged() causing
 283          * requestLayout() to happen during layoutChildren().
 284          * The inLayout flag prevents this call to cause any extra work.
 285          */
 286         if (inLayout) return;
 287 
 288         /*
 289         * There is no need to reset the text layout's content every time
 290         * requestLayout() is called. For example, the content needs
 291         * to be set when:
 292         *  children add or removed


 296         * The content does not need to set when:
 297         *  the width/height changes in the region
 298         *  the insets changes in the region
 299         *
 300         * Unfortunately, it is not possible to know what change invoked request
 301         * layout. The solution is to always reset the content in the text
 302         * layout and rely on it to preserve itself if the new content equals to
 303         * the old one. The cost to generate the new content is not avoid.
 304         */
 305         needsContent = true;
 306         super.requestLayout();
 307     }
 308 
 309     @Override public Orientation getContentBias() {
 310         return Orientation.HORIZONTAL;
 311     }
 312 
 313     @Override protected void layoutChildren() {
 314         inLayout = true;
 315         Insets insets = getInsets();
 316         double top = snapSpace(insets.getTop());
 317         double left = snapSpace(insets.getLeft());
 318 
 319         GlyphList[] runs = getTextLayout().getRuns();
 320         for (int j = 0; j < runs.length; j++) {
 321             GlyphList run = runs[j];
 322             TextSpan span = run.getTextSpan();
 323             if (span instanceof EmbeddedSpan) {
 324                 Node child = ((EmbeddedSpan)span).getNode();
 325                 Point2D location = run.getLocation();
 326                 double baselineOffset = -run.getLineBounds().getMinY();
 327 
 328                 layoutInArea(child, left + location.x, top + location.y,
 329                              run.getWidth(), run.getHeight(),
 330                              baselineOffset, null, true, true,
 331                              HPos.CENTER, VPos.BASELINE);
 332             }
 333         }
 334 
 335         List<Node> managed = getManagedChildren();
 336         for (Node node: managed) {
 337             if (node instanceof Text) {


 466             lineSpacing =
 467                 new StyleableDoubleProperty(0) {
 468                 @Override public Object getBean() { return TextFlow.this; }
 469                 @Override public String getName() { return "lineSpacing"; }
 470                 @Override public CssMetaData<TextFlow, Number> getCssMetaData() {
 471                     return StyleableProperties.LINE_SPACING;
 472                 }
 473                 @Override public void invalidated() {
 474                     TextLayout layout = getTextLayout();
 475                     if (layout.setLineSpacing((float)get())) {
 476                         requestLayout();
 477                     }
 478                 }
 479             };
 480         }
 481         return lineSpacing;
 482     }
 483 
 484     @Override public final double getBaselineOffset() {
 485         Insets insets = getInsets();
 486         double top = snapSpace(insets.getTop());
 487         return top - getTextLayout().getBounds().getMinY();
 488     }
 489 
 490    /***************************************************************************
 491     *                                                                         *
 492     *                            Stylesheet Handling                          *
 493     *                                                                         *
 494     **************************************************************************/
 495 
 496      /**
 497       * Super-lazy instantiation pattern from Bill Pugh.
 498       * @treatAsPrivate implementation detail
 499       */
 500      private static class StyleableProperties {
 501 
 502          private static final
 503              CssMetaData<TextFlow, TextAlignment> TEXT_ALIGNMENT =
 504                  new CssMetaData<TextFlow,TextAlignment>("-fx-text-alignment",
 505                  new EnumConverter<TextAlignment>(TextAlignment.class),
 506                  TextAlignment.LEFT) {


 557     }
 558 
 559     static double boundedSize(double min, double pref, double max) {
 560         double a = pref >= min ? pref : min;
 561         double b = min >= max ? min : max;
 562         return a <= b ? a : b;
 563     }
 564 
 565     double computeChildPrefAreaWidth(Node child, Insets margin) {
 566         return computeChildPrefAreaWidth(child, margin, -1);
 567     }
 568 
 569     double computeChildPrefAreaWidth(Node child, Insets margin, double height) {
 570         final boolean snap = isSnapToPixel();
 571         double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
 572         double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
 573         double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
 574         double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
 575         double alt = -1;
 576         if (child.getContentBias() == Orientation.VERTICAL) { // width depends on height
 577             alt = snapSize(boundedSize(
 578                     child.minHeight(-1), height != -1? height - top - bottom :
 579                            child.prefHeight(-1), child.maxHeight(-1)));
 580         }
 581         return left + snapSize(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
 582     }
 583 
 584     double computeChildPrefAreaHeight(Node child, Insets margin) {
 585         return computeChildPrefAreaHeight(child, margin, -1);
 586     }
 587 
 588     double computeChildPrefAreaHeight(Node child, Insets margin, double width) {
 589         final boolean snap = isSnapToPixel();
 590         double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
 591         double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
 592         double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
 593         double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
 594         double alt = -1;
 595         if (child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
 596             alt = snapSize(boundedSize(
 597                     child.minWidth(-1), width != -1? width - left - right :
 598                            child.prefWidth(-1), child.maxWidth(-1)));
 599         }
 600         return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
 601     }
 602     /* end of copied code */
 603 
 604     @Override
 605     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 606         switch (attribute) {
 607             case TEXT: {
 608                 String accText = getAccessibleText();
 609                 if (accText != null && !accText.isEmpty()) return accText;
 610 
 611                 StringBuilder title = new StringBuilder();
 612                 for (Node node: getChildren()) {
 613                     Object text = node.queryAccessibleAttribute(AccessibleAttribute.TEXT, parameters);
 614                     if (text != null) {
 615                         title.append(text.toString());
 616                     }
 617                 }
 618                 return title.toString();
 619             }
 620             default: return super.queryAccessibleAttribute(attribute, parameters);


 217      * Returns shape for the range of the text in local coordinates.
 218      *
 219      * @param start the beginning character index for the range
 220      * @param start the end character index (non-inclusive) for the range
 221      * @return an array of {@code PathElement} which can be used to create a {@code Shape}
 222      * @since 9
 223      */
 224     public final PathElement[] rangeShape(int start, int end) {
 225         return getRange(start, end, TextLayout.TYPE_TEXT);
 226     }
 227 
 228     @Override
 229     public boolean usesMirroring() {
 230         return false;
 231     }
 232 
 233     @Override protected void setWidth(double value) {
 234         if (value != getWidth()) {
 235             TextLayout layout = getTextLayout();
 236             Insets insets = getInsets();
 237             double left = snapSpaceX(insets.getLeft());
 238             double right = snapSpaceX(insets.getRight());
 239             double width = Math.max(1, value - left - right);
 240             layout.setWrapWidth((float)width);
 241             super.setWidth(value);
 242         }
 243     }
 244 
 245     @Override protected double computePrefWidth(double height) {
 246         TextLayout layout = getTextLayout();
 247         layout.setWrapWidth(0);
 248         double width = layout.getBounds().getWidth();
 249         Insets insets = getInsets();
 250         double left = snapSpaceX(insets.getLeft());
 251         double right = snapSpaceX(insets.getRight());
 252         double wrappingWidth = Math.max(1, getWidth() - left - right);
 253         layout.setWrapWidth((float)wrappingWidth);
 254         return left + width + right;
 255     }
 256 
 257     @Override protected double computePrefHeight(double width) {
 258         TextLayout layout = getTextLayout();
 259         Insets insets = getInsets();
 260         double left = snapSpaceX(insets.getLeft());
 261         double right = snapSpaceX(insets.getRight());
 262         if (width == USE_COMPUTED_SIZE) {
 263             layout.setWrapWidth(0);
 264         } else {
 265             double wrappingWidth = Math.max(1, width - left - right);
 266             layout.setWrapWidth((float)wrappingWidth);
 267         }
 268         double height = layout.getBounds().getHeight();
 269         double wrappingWidth = Math.max(1, getWidth() - left - right);
 270         layout.setWrapWidth((float)wrappingWidth);
 271         double top = snapSpaceY(insets.getTop());
 272         double bottom = snapSpaceY(insets.getBottom());
 273         return top + height + bottom;
 274     }
 275 
 276     @Override protected double computeMinHeight(double width) {
 277         return computePrefHeight(width);
 278     }
 279 
 280     @Override public void requestLayout() {
 281         /* The geometry of text nodes can be changed during layout children.
 282          * For that reason it has to call impl_geomChanged() causing
 283          * requestLayout() to happen during layoutChildren().
 284          * The inLayout flag prevents this call to cause any extra work.
 285          */
 286         if (inLayout) return;
 287 
 288         /*
 289         * There is no need to reset the text layout's content every time
 290         * requestLayout() is called. For example, the content needs
 291         * to be set when:
 292         *  children add or removed


 296         * The content does not need to set when:
 297         *  the width/height changes in the region
 298         *  the insets changes in the region
 299         *
 300         * Unfortunately, it is not possible to know what change invoked request
 301         * layout. The solution is to always reset the content in the text
 302         * layout and rely on it to preserve itself if the new content equals to
 303         * the old one. The cost to generate the new content is not avoid.
 304         */
 305         needsContent = true;
 306         super.requestLayout();
 307     }
 308 
 309     @Override public Orientation getContentBias() {
 310         return Orientation.HORIZONTAL;
 311     }
 312 
 313     @Override protected void layoutChildren() {
 314         inLayout = true;
 315         Insets insets = getInsets();
 316         double top = snapSpaceY(insets.getTop());
 317         double left = snapSpaceX(insets.getLeft());
 318 
 319         GlyphList[] runs = getTextLayout().getRuns();
 320         for (int j = 0; j < runs.length; j++) {
 321             GlyphList run = runs[j];
 322             TextSpan span = run.getTextSpan();
 323             if (span instanceof EmbeddedSpan) {
 324                 Node child = ((EmbeddedSpan)span).getNode();
 325                 Point2D location = run.getLocation();
 326                 double baselineOffset = -run.getLineBounds().getMinY();
 327 
 328                 layoutInArea(child, left + location.x, top + location.y,
 329                              run.getWidth(), run.getHeight(),
 330                              baselineOffset, null, true, true,
 331                              HPos.CENTER, VPos.BASELINE);
 332             }
 333         }
 334 
 335         List<Node> managed = getManagedChildren();
 336         for (Node node: managed) {
 337             if (node instanceof Text) {


 466             lineSpacing =
 467                 new StyleableDoubleProperty(0) {
 468                 @Override public Object getBean() { return TextFlow.this; }
 469                 @Override public String getName() { return "lineSpacing"; }
 470                 @Override public CssMetaData<TextFlow, Number> getCssMetaData() {
 471                     return StyleableProperties.LINE_SPACING;
 472                 }
 473                 @Override public void invalidated() {
 474                     TextLayout layout = getTextLayout();
 475                     if (layout.setLineSpacing((float)get())) {
 476                         requestLayout();
 477                     }
 478                 }
 479             };
 480         }
 481         return lineSpacing;
 482     }
 483 
 484     @Override public final double getBaselineOffset() {
 485         Insets insets = getInsets();
 486         double top = snapSpaceY(insets.getTop());
 487         return top - getTextLayout().getBounds().getMinY();
 488     }
 489 
 490    /***************************************************************************
 491     *                                                                         *
 492     *                            Stylesheet Handling                          *
 493     *                                                                         *
 494     **************************************************************************/
 495 
 496      /**
 497       * Super-lazy instantiation pattern from Bill Pugh.
 498       * @treatAsPrivate implementation detail
 499       */
 500      private static class StyleableProperties {
 501 
 502          private static final
 503              CssMetaData<TextFlow, TextAlignment> TEXT_ALIGNMENT =
 504                  new CssMetaData<TextFlow,TextAlignment>("-fx-text-alignment",
 505                  new EnumConverter<TextAlignment>(TextAlignment.class),
 506                  TextAlignment.LEFT) {


 557     }
 558 
 559     static double boundedSize(double min, double pref, double max) {
 560         double a = pref >= min ? pref : min;
 561         double b = min >= max ? min : max;
 562         return a <= b ? a : b;
 563     }
 564 
 565     double computeChildPrefAreaWidth(Node child, Insets margin) {
 566         return computeChildPrefAreaWidth(child, margin, -1);
 567     }
 568 
 569     double computeChildPrefAreaWidth(Node child, Insets margin, double height) {
 570         final boolean snap = isSnapToPixel();
 571         double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
 572         double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
 573         double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
 574         double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
 575         double alt = -1;
 576         if (child.getContentBias() == Orientation.VERTICAL) { // width depends on height
 577             alt = snapSizeY(boundedSize(
 578                     child.minHeight(-1), height != -1? height - top - bottom :
 579                            child.prefHeight(-1), child.maxHeight(-1)));
 580         }
 581         return left + snapSizeX(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
 582     }
 583 
 584     double computeChildPrefAreaHeight(Node child, Insets margin) {
 585         return computeChildPrefAreaHeight(child, margin, -1);
 586     }
 587 
 588     double computeChildPrefAreaHeight(Node child, Insets margin, double width) {
 589         final boolean snap = isSnapToPixel();
 590         double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
 591         double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
 592         double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
 593         double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
 594         double alt = -1;
 595         if (child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
 596             alt = snapSizeX(boundedSize(
 597                     child.minWidth(-1), width != -1? width - left - right :
 598                            child.prefWidth(-1), child.maxWidth(-1)));
 599         }
 600         return top + snapSizeY(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
 601     }
 602     /* end of copied code */
 603 
 604     @Override
 605     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 606         switch (attribute) {
 607             case TEXT: {
 608                 String accText = getAccessibleText();
 609                 if (accText != null && !accText.isEmpty()) return accText;
 610 
 611                 StringBuilder title = new StringBuilder();
 612                 for (Node node: getChildren()) {
 613                     Object text = node.queryAccessibleAttribute(AccessibleAttribute.TEXT, parameters);
 614                     if (text != null) {
 615                         title.append(text.toString());
 616                     }
 617                 }
 618                 return title.toString();
 619             }
 620             default: return super.queryAccessibleAttribute(attribute, parameters);