36 import javafx.geometry.Orientation;
37 import javafx.geometry.VPos;
38 import javafx.scene.AccessibleAttribute;
39 import javafx.scene.AccessibleRole;
40 import javafx.scene.Node;
41 import javafx.scene.layout.Pane;
42 import javafx.scene.shape.PathElement;
43 import javafx.css.StyleableDoubleProperty;
44 import javafx.css.StyleableObjectProperty;
45 import javafx.css.CssMetaData;
46 import javafx.css.converter.EnumConverter;
47 import javafx.css.converter.SizeConverter;
48 import com.sun.javafx.geom.BaseBounds;
49 import com.sun.javafx.geom.Point2D;
50 import com.sun.javafx.geom.RectBounds;
51 import com.sun.javafx.scene.text.GlyphList;
52 import com.sun.javafx.scene.text.TextLayout;
53 import com.sun.javafx.scene.text.TextLayoutFactory;
54 import com.sun.javafx.scene.text.TextSpan;
55 import com.sun.javafx.tk.Toolkit;
56 import javafx.css.Styleable;
57 import javafx.css.StyleableProperty;
58
59 /**
60 * TextFlow is special layout designed to lay out rich text.
61 * It can be used to layout several {@link Text} nodes in a single text flow.
62 * The TextFlow uses the text and the font of each {@link Text} node inside of it
63 * plus its own width and text alignment to determine the location for each child.
64 * A single {@link Text} node can span over several lines due to wrapping, and
65 * the visual location of {@link Text} node can differ from the logical location
66 * due to bidi reordering.
67 *
68 * <p>
69 * Any Node, other than Text, will be treated as an embedded object in the
70 * text layout. It will be inserted in the content using its preferred width,
71 * height, and baseline offset.
72 *
73 * <p>
74 * When a {@link Text} node is inside of a TextFlow, some of its properties are ignored.
75 * For example, the x and y properties of the {@link Text} node are ignored since
76 * the location of the node is determined by the parent. Likewise, the wrapping
366 return "\uFFFC";
367 }
368
369 @Override public Object getFont() {
370 return null;
371 }
372
373 @Override public RectBounds getBounds() {
374 return bounds;
375 }
376
377 public Node getNode() {
378 return node;
379 }
380 }
381
382 TextLayout getTextLayout() {
383 if (layout == null) {
384 TextLayoutFactory factory = Toolkit.getToolkit().getTextLayoutFactory();
385 layout = factory.createLayout();
386 needsContent = true;
387 }
388 if (needsContent) {
389 List<Node> children = getManagedChildren();
390 TextSpan[] spans = new TextSpan[children.size()];
391 for (int i = 0; i < spans.length; i++) {
392 Node node = children.get(i);
393 if (node instanceof Text) {
394 spans[i] = ((Text)node).getTextSpan();
395 } else {
396 /* Creating a text span every time forces text layout
397 * to run a full text analysis in the new content.
398 */
399 double baseline = node.getBaselineOffset();
400 if (baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
401 baseline = node.getLayoutBounds().getHeight();
402 }
403 double width = computeChildPrefAreaWidth(node, null);
404 double height = computeChildPrefAreaHeight(node, null);
405 spans[i] = new EmbeddedSpan(node, baseline, width, height);
466
467 public final DoubleProperty lineSpacingProperty() {
468 if (lineSpacing == null) {
469 lineSpacing =
470 new StyleableDoubleProperty(0) {
471 @Override public Object getBean() { return TextFlow.this; }
472 @Override public String getName() { return "lineSpacing"; }
473 @Override public CssMetaData<TextFlow, Number> getCssMetaData() {
474 return StyleableProperties.LINE_SPACING;
475 }
476 @Override public void invalidated() {
477 TextLayout layout = getTextLayout();
478 if (layout.setLineSpacing((float)get())) {
479 requestLayout();
480 }
481 }
482 };
483 }
484 return lineSpacing;
485 }
486
487 @Override public final double getBaselineOffset() {
488 Insets insets = getInsets();
489 double top = snapSpaceY(insets.getTop());
490 return top - getTextLayout().getBounds().getMinY();
491 }
492
493 /***************************************************************************
494 * *
495 * Stylesheet Handling *
496 * *
497 **************************************************************************/
498
499 /*
500 * Super-lazy instantiation pattern from Bill Pugh.
501 */
502 private static class StyleableProperties {
503
504 private static final
505 CssMetaData<TextFlow, TextAlignment> TEXT_ALIGNMENT =
506 new CssMetaData<TextFlow,TextAlignment>("-fx-text-alignment",
507 new EnumConverter<TextAlignment>(TextAlignment.class),
508 TextAlignment.LEFT) {
509
510 @Override public boolean isSettable(TextFlow node) {
511 return node.textAlignment == null || !node.textAlignment.isBound();
512 }
513
514 @Override public StyleableProperty<TextAlignment> getStyleableProperty(TextFlow node) {
515 return (StyleableProperty<TextAlignment>)node.textAlignmentProperty();
516 }
517 };
518
519 private static final
520 CssMetaData<TextFlow,Number> LINE_SPACING =
521 new CssMetaData<TextFlow,Number>("-fx-line-spacing",
522 SizeConverter.getInstance(), 0) {
523
524 @Override public boolean isSettable(TextFlow node) {
525 return node.lineSpacing == null || !node.lineSpacing.isBound();
526 }
527
528 @Override public StyleableProperty<Number> getStyleableProperty(TextFlow node) {
529 return (StyleableProperty<Number>)node.lineSpacingProperty();
530 }
531 };
532
533 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
534 static {
535 final List<CssMetaData<? extends Styleable, ?>> styleables =
536 new ArrayList<CssMetaData<? extends Styleable, ?>>(Pane.getClassCssMetaData());
537 styleables.add(TEXT_ALIGNMENT);
538 styleables.add(LINE_SPACING);
539 STYLEABLES = Collections.unmodifiableList(styleables);
540 }
541 }
542
543 /**
544 * @return The CssMetaData associated with this class, which may include the
545 * CssMetaData of its superclasses.
546 */
547 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
548 return StyleableProperties.STYLEABLES;
549 }
550
551 @Override
552 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
553 return getClassCssMetaData();
554 }
555
556 /* The methods in this section are copied from Region due to package visibility restriction */
557 private static double snapSpace(double value, boolean snapToPixel) {
558 return snapToPixel ? Math.round(value) : value;
559 }
560
|
36 import javafx.geometry.Orientation;
37 import javafx.geometry.VPos;
38 import javafx.scene.AccessibleAttribute;
39 import javafx.scene.AccessibleRole;
40 import javafx.scene.Node;
41 import javafx.scene.layout.Pane;
42 import javafx.scene.shape.PathElement;
43 import javafx.css.StyleableDoubleProperty;
44 import javafx.css.StyleableObjectProperty;
45 import javafx.css.CssMetaData;
46 import javafx.css.converter.EnumConverter;
47 import javafx.css.converter.SizeConverter;
48 import com.sun.javafx.geom.BaseBounds;
49 import com.sun.javafx.geom.Point2D;
50 import com.sun.javafx.geom.RectBounds;
51 import com.sun.javafx.scene.text.GlyphList;
52 import com.sun.javafx.scene.text.TextLayout;
53 import com.sun.javafx.scene.text.TextLayoutFactory;
54 import com.sun.javafx.scene.text.TextSpan;
55 import com.sun.javafx.tk.Toolkit;
56 import javafx.beans.property.IntegerProperty;
57 import javafx.beans.property.IntegerPropertyBase;
58 import javafx.css.Styleable;
59 import javafx.css.StyleableIntegerProperty;
60 import javafx.css.StyleableProperty;
61
62 /**
63 * TextFlow is special layout designed to lay out rich text.
64 * It can be used to layout several {@link Text} nodes in a single text flow.
65 * The TextFlow uses the text and the font of each {@link Text} node inside of it
66 * plus its own width and text alignment to determine the location for each child.
67 * A single {@link Text} node can span over several lines due to wrapping, and
68 * the visual location of {@link Text} node can differ from the logical location
69 * due to bidi reordering.
70 *
71 * <p>
72 * Any Node, other than Text, will be treated as an embedded object in the
73 * text layout. It will be inserted in the content using its preferred width,
74 * height, and baseline offset.
75 *
76 * <p>
77 * When a {@link Text} node is inside of a TextFlow, some of its properties are ignored.
78 * For example, the x and y properties of the {@link Text} node are ignored since
79 * the location of the node is determined by the parent. Likewise, the wrapping
369 return "\uFFFC";
370 }
371
372 @Override public Object getFont() {
373 return null;
374 }
375
376 @Override public RectBounds getBounds() {
377 return bounds;
378 }
379
380 public Node getNode() {
381 return node;
382 }
383 }
384
385 TextLayout getTextLayout() {
386 if (layout == null) {
387 TextLayoutFactory factory = Toolkit.getToolkit().getTextLayoutFactory();
388 layout = factory.createLayout();
389 layout.setTabSize(getTabSize());
390 needsContent = true;
391 }
392 if (needsContent) {
393 List<Node> children = getManagedChildren();
394 TextSpan[] spans = new TextSpan[children.size()];
395 for (int i = 0; i < spans.length; i++) {
396 Node node = children.get(i);
397 if (node instanceof Text) {
398 spans[i] = ((Text)node).getTextSpan();
399 } else {
400 /* Creating a text span every time forces text layout
401 * to run a full text analysis in the new content.
402 */
403 double baseline = node.getBaselineOffset();
404 if (baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
405 baseline = node.getLayoutBounds().getHeight();
406 }
407 double width = computeChildPrefAreaWidth(node, null);
408 double height = computeChildPrefAreaHeight(node, null);
409 spans[i] = new EmbeddedSpan(node, baseline, width, height);
470
471 public final DoubleProperty lineSpacingProperty() {
472 if (lineSpacing == null) {
473 lineSpacing =
474 new StyleableDoubleProperty(0) {
475 @Override public Object getBean() { return TextFlow.this; }
476 @Override public String getName() { return "lineSpacing"; }
477 @Override public CssMetaData<TextFlow, Number> getCssMetaData() {
478 return StyleableProperties.LINE_SPACING;
479 }
480 @Override public void invalidated() {
481 TextLayout layout = getTextLayout();
482 if (layout.setLineSpacing((float)get())) {
483 requestLayout();
484 }
485 }
486 };
487 }
488 return lineSpacing;
489 }
490 /**
491 * The size of a tab stop in spaces.
492 *
493 * @return the {@code tabSize} property
494 *
495 * @defaultValue {@code 8}
496 *
497 * @since 14
498 */
499 private IntegerProperty tabSize;
500
501 /**
502 * Gets the size of a tab stop in spaces.
503 * @return the size of a tab in spaces
504 * @since 14
505 */
506 public final int getTabSize() {
507 return tabSize == null ? TextLayout.DEFAULT_TAB_SIZE : tabSize.get();
508 }
509
510 /**
511 * Sets the size of a tab stop.
512 * @param spaces the size of a tab in spaces. Defaults to 8.
513 * Minimum is 1, lower values will be clamped to 1.
514 * @since 14
515 */
516 public final void setTabSize(int spaces) {
517 tabSizeProperty().set(spaces);
518 }
519
520 final IntegerProperty tabSizeProperty() {
521 if (tabSize == null) {
522 tabSize = new StyleableIntegerProperty(TextLayout.DEFAULT_TAB_SIZE) {
523 @Override public Object getBean() { return TextFlow.this; }
524 @Override public String getName() { return "tabSize"; }
525 @Override public CssMetaData getCssMetaData() {
526 return StyleableProperties.TAB_SIZE;
527 }
528 @Override public void set(int v) { super.set((v < 1) ? 1 : v); }
529 @Override protected void invalidated() {
530 TextLayout layout = getTextLayout();
531 if (layout.setTabSize(get())) {
532 requestLayout();
533 }
534 }
535 };
536 }
537 return tabSize;
538 }
539
540 @Override public final double getBaselineOffset() {
541 Insets insets = getInsets();
542 double top = snapSpaceY(insets.getTop());
543 return top - getTextLayout().getBounds().getMinY();
544 }
545
546 /***************************************************************************
547 * *
548 * Stylesheet Handling *
549 * *
550 **************************************************************************/
551
552 /*
553 * Super-lazy instantiation pattern from Bill Pugh.
554 */
555 private static class StyleableProperties {
556
557 private static final
558 CssMetaData<TextFlow, TextAlignment> TEXT_ALIGNMENT =
559 new CssMetaData<TextFlow,TextAlignment>("-fx-text-alignment",
560 new EnumConverter<TextAlignment>(TextAlignment.class),
561 TextAlignment.LEFT) {
562
563 @Override public boolean isSettable(TextFlow node) {
564 return node.textAlignment == null || !node.textAlignment.isBound();
565 }
566
567 @Override public StyleableProperty<TextAlignment> getStyleableProperty(TextFlow node) {
568 return (StyleableProperty<TextAlignment>)node.textAlignmentProperty();
569 }
570 };
571
572 private static final
573 CssMetaData<TextFlow,Number> LINE_SPACING =
574 new CssMetaData<TextFlow,Number>("-fx-line-spacing",
575 SizeConverter.getInstance(), 0) {
576
577 @Override public boolean isSettable(TextFlow node) {
578 return node.lineSpacing == null || !node.lineSpacing.isBound();
579 }
580
581 @Override public StyleableProperty<Number> getStyleableProperty(TextFlow node) {
582 return (StyleableProperty<Number>)node.lineSpacingProperty();
583 }
584 };
585
586 private static final CssMetaData<TextFlow,Number> TAB_SIZE =
587 new CssMetaData<TextFlow,Number>("-fx-tab-size",
588 SizeConverter.getInstance(), TextLayout.DEFAULT_TAB_SIZE) {
589
590 @Override
591 public boolean isSettable(TextFlow node) {
592 return node.tabSize == null || !node.tabSize.isBound();
593 }
594
595 @Override
596 public StyleableProperty<Number> getStyleableProperty(TextFlow node) {
597 return (StyleableProperty<Number>)node.tabSizeProperty();
598 }
599 };
600
601 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
602 static {
603 final List<CssMetaData<? extends Styleable, ?>> styleables =
604 new ArrayList<CssMetaData<? extends Styleable, ?>>(Pane.getClassCssMetaData());
605 styleables.add(TEXT_ALIGNMENT);
606 styleables.add(LINE_SPACING);
607 styleables.add(TAB_SIZE);
608 STYLEABLES = Collections.unmodifiableList(styleables);
609 }
610 }
611
612 /**
613 * @return The CssMetaData associated with this class, which may include the
614 * CssMetaData of its superclasses.
615 */
616 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
617 return StyleableProperties.STYLEABLES;
618 }
619
620 @Override
621 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
622 return getClassCssMetaData();
623 }
624
625 /* The methods in this section are copied from Region due to package visibility restriction */
626 private static double snapSpace(double value, boolean snapToPixel) {
627 return snapToPixel ? Math.round(value) : value;
628 }
629
|