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