changeset 1906:e732d95172cf 8.0-b66

Automated merge with ssh://jpgodine@jfxsrc.us.oracle.com//javafx/8.0/MASTER/jfx/rt
author jpgodine@JPGODINE-LAP.st-users.us.oracle.com
date Tue, 27 Nov 2012 10:45:21 -0800
parents 60400c9e4ce7 7a82d94e9019
children 12cce04e7847 54d464a6ade7 b4fe87e55747
files javafx-ui-common/src/javafx/scene/Node.java
diffstat 25 files changed, 2948 insertions(+), 758 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-fxml/nbproject/project.xml	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-fxml/nbproject/project.xml	Tue Nov 27 10:45:21 2012 -0800
@@ -1,132 +1,132 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://www.netbeans.org/ns/project/1">
-    <type>org.netbeans.modules.ant.freeform</type>
-    <configuration>
-        <general-data xmlns="http://www.netbeans.org/ns/freeform-project/1">
-            <!-- Do not use Project Properties customizer when editing this file manually. -->
-            <name>javafx-fxml</name>
-            <properties>
-                <property-file>../base.properties</property-file>
-                <property-file>project.properties</property-file>
-                <property-file>../common.properties</property-file>
-            </properties>
-            <folders>
-                <source-folder>
-                    <label>Source Packages</label>
-                    <type>java</type>
-                    <location>${src.dir}</location>
-                </source-folder>
-                <source-folder>
-                    <label>Test Packages</label>
-                    <type>java</type>
-                    <location>${test.dir}</location>
-                </source-folder>
-            </folders>
-            <ide-actions>
-                <action name="build">
-                    <target>jar</target>
-                </action>
-                <action name="clean">
-                    <target>clean</target>
-                </action>
-                <action name="test">
-                    <target>test</target>
-                </action>
-                <action name="rebuild">
-                    <target>clean</target>
-                    <target>jar</target>
-                </action>
-                <action name="run.single">
-                    <target>test-single</target>
-                    <context>
-                        <property>run.file</property>
-                        <folder>${test.dir}</folder>
-                        <pattern>\.java$</pattern>
-                        <format>relative-path</format>
-                        <arity>
-                            <one-file-only/>
-                        </arity>
-                    </context>
-                </action>
-                <action name="test.single">
-                    <target>test-single</target>
-                    <context>
-                        <property>run.file</property>
-                        <folder>${test.dir}</folder>
-                        <pattern>\.java$</pattern>
-                        <format>relative-path</format>
-                        <arity>
-                            <one-file-only/>
-                        </arity>
-                    </context>
-                </action>
-                <action name="debug.single">
-                    <script>nbproject/ide-file-targets.xml</script>
-                    <target>debug-selected-file-in-test</target>
-                    <context>
-                        <property>debug.class</property>
-                        <folder>${test.dir}</folder>
-                        <pattern>\.java$</pattern>
-                        <format>java-name</format>
-                        <arity>
-                            <one-file-only/>
-                        </arity>
-                    </context>
-                </action>
-            </ide-actions>
-            <export>
-                <type>jar</type>
-                <location>dist/javafx-fxml.jar</location>
-                <build-target>jar</build-target>
-            </export>
-            <export>
-                <type>folder</type>
-                <location>${build.test.classes.dir}</location>
-                <build-target>jar</build-target>
-            </export>
-            <view>
-                <items>
-                    <source-folder style="packages">
-                        <label>Source Packages</label>
-                        <location>${src.dir}</location>
-                    </source-folder>
-                    <source-folder style="packages">
-                        <label>Test Packages</label>
-                        <location>${test.dir}</location>
-                    </source-folder>
-                    <source-file>
-                        <location>build.xml</location>
-                    </source-file>
-                    <source-file>
-                        <location>project.properties</location>
-                    </source-file>
-                    <source-file>
-                        <location>nbproject/project.xml</location>
-                    </source-file>
-                </items>
-                <context-menu>
-                    <ide-action name="build"/>
-                    <ide-action name="rebuild"/>
-                    <ide-action name="clean"/>
-                    <ide-action name="test"/>
-                </context-menu>
-            </view>
-            <subprojects/>
-        </general-data>
-        <java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/3">
-            <compilation-unit>
-                <package-root>${src.dir}</package-root>
-                <classpath mode="compile">../javafx-logging/dist/javafx-logging.jar:../javafx-common/dist/javafx-common.jar:../javafx-beans/dist/javafx-beans.jar:../../rt/javafx-ui-common/dist/javafx-ui-common.jar:../../rt/javafx-ui-controls/dist/javafx-ui-controls.jar:../../rt/javafx-ui-charts/dist/javafx-ui-charts.jar:../javafx-ui-webnode/dist/javafx-ui-webnode.jar</classpath>
-                <built-to>dist/javafx-fxml.jar</built-to>
-                <source-level>1.6</source-level>
-            </compilation-unit>
-            <compilation-unit>
-                <package-root>${test.dir}</package-root>
-                <unit-tests/>
-                <classpath mode="compile">${javac.test.classpath}</classpath>
-                <built-to>${build.test.classes.dir}</built-to>
-                <source-level>1.6</source-level>
-            </compilation-unit>
-        </java-data>
-    </configuration>
-</project>
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.ant.freeform</type>
+    <configuration>
+        <general-data xmlns="http://www.netbeans.org/ns/freeform-project/1">
+            <!-- Do not use Project Properties customizer when editing this file manually. -->
+            <name>javafx-fxml</name>
+            <properties>
+                <property-file>../base.properties</property-file>
+                <property-file>project.properties</property-file>
+                <property-file>../common.properties</property-file>
+            </properties>
+            <folders>
+                <source-folder>
+                    <label>Source Packages</label>
+                    <type>java</type>
+                    <location>${src.dir}</location>
+                </source-folder>
+                <source-folder>
+                    <label>Test Packages</label>
+                    <type>java</type>
+                    <location>${test.dir}</location>
+                </source-folder>
+            </folders>
+            <ide-actions>
+                <action name="build">
+                    <target>jar</target>
+                </action>
+                <action name="clean">
+                    <target>clean</target>
+                </action>
+                <action name="test">
+                    <target>test</target>
+                </action>
+                <action name="rebuild">
+                    <target>clean</target>
+                    <target>jar</target>
+                </action>
+                <action name="run.single">
+                    <target>test-single</target>
+                    <context>
+                        <property>run.file</property>
+                        <folder>${test.dir}</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <one-file-only/>
+                        </arity>
+                    </context>
+                </action>
+                <action name="test.single">
+                    <target>test-single</target>
+                    <context>
+                        <property>run.file</property>
+                        <folder>${test.dir}</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <one-file-only/>
+                        </arity>
+                    </context>
+                </action>
+                <action name="debug.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>debug-selected-file-in-test</target>
+                    <context>
+                        <property>debug.class</property>
+                        <folder>${test.dir}</folder>
+                        <pattern>\.java$</pattern>
+                        <format>java-name</format>
+                        <arity>
+                            <one-file-only/>
+                        </arity>
+                    </context>
+                </action>
+            </ide-actions>
+            <export>
+                <type>jar</type>
+                <location>dist/javafx-fxml.jar</location>
+                <build-target>jar</build-target>
+            </export>
+            <export>
+                <type>folder</type>
+                <location>${build.test.classes.dir}</location>
+                <build-target>jar</build-target>
+            </export>
+            <view>
+                <items>
+                    <source-folder style="packages">
+                        <label>Source Packages</label>
+                        <location>${src.dir}</location>
+                    </source-folder>
+                    <source-folder style="packages">
+                        <label>Test Packages</label>
+                        <location>${test.dir}</location>
+                    </source-folder>
+                    <source-file>
+                        <location>build.xml</location>
+                    </source-file>
+                    <source-file>
+                        <location>project.properties</location>
+                    </source-file>
+                    <source-file>
+                        <location>nbproject/project.xml</location>
+                    </source-file>
+                </items>
+                <context-menu>
+                    <ide-action name="build"/>
+                    <ide-action name="rebuild"/>
+                    <ide-action name="clean"/>
+                    <ide-action name="test"/>
+                </context-menu>
+            </view>
+            <subprojects/>
+        </general-data>
+        <java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/3">
+            <compilation-unit>
+                <package-root>${src.dir}</package-root>
+                <classpath mode="compile">../../rt-closed/javafx-logging/dist/javafx-logging.jar;../../rt-closed/javafx-common/dist/javafx-common.jar;../../rt-closed/javafx-beans/dist/javafx-beans.jar;../javafx-ui-common/dist/javafx-ui-common.jar;../javafx-ui-controls/dist/javafx-ui-controls.jar;../javafx-ui-charts/dist/javafx-ui-charts.jar;../../rt-closed/javafx-ui-webnode/dist/javafx-ui-webnode.jar</classpath>
+                <built-to>dist/javafx-fxml.jar</built-to>
+                <source-level>1.6</source-level>
+            </compilation-unit>
+            <compilation-unit>
+                <package-root>${test.dir}</package-root>
+                <unit-tests/>
+                <classpath mode="compile">${javac.test.classpath}</classpath>
+                <built-to>${build.test.classes.dir}</built-to>
+                <source-level>1.6</source-level>
+            </compilation-unit>
+        </java-data>
+    </configuration>
+</project>
--- a/javafx-ui-common/src/com/sun/javafx/image/ByteToBytePixelConverter.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/com/sun/javafx/image/ByteToBytePixelConverter.java	Tue Nov 27 10:45:21 2012 -0800
@@ -30,14 +30,92 @@
 public interface ByteToBytePixelConverter
     extends PixelConverter<ByteBuffer, ByteBuffer>
 {
+    /**
+     * Copies a rectangular region of data from the source array to the
+     * destination array using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanbytes + x * srcbytesperpixel + srcoff;
+     *     int dstpos = y * dstscanbytes + x * dstbytesperpixel + dstoff;
+     *     for each j : 0 <= j < srcbytesperpixel {
+     *         load data from srcarr[srcpos + j];
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstbytesperpixel {
+     *         store data into dstarr[dstpos + k] = pixel data;
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcarr the byte array containing the source data
+     * @param srcoff the index in the array of the first source pixel data
+     * @param srcscanbytes number of array indices between rows of data in the source
+     * @param dstbuf the byte array containing the destination data
+     * @param dstoff the index in the array of the first destination pixel data
+     * @param dstscanbytes number of array indices between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(byte       srcarr[], int srcoff, int srcscanbytes,
                         byte       dstarr[], int dstoff, int dstscanbytes,
                         int w, int h);
 
+    /**
+     * Copies a rectangular region of data from the source buffer to the
+     * destination array using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanbytes + x * srcbytesperpixel + srcoff;
+     *     int dstpos = y * dstscanbytes + x * dstbytesperpixel + dstoff;
+     *     for each j : 0 <= j < srcbytesperpixel {
+     *         load data from srcbuf.get(srcpos + j);
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstbytesperpixel {
+     *         store data into dstarr[dstpos + k] = pixel data;
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the nio buffer containing the source data
+     * @param srcoff the absolute location in the buffer of the first source pixel data
+     * @param srcscanbytes number of buffer elements between rows of data in the source
+     * @param dstbuf the byte array containing the destination data
+     * @param dstoff the index in the array of the first destination pixel data
+     * @param dstscanbytes number of array indices between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(ByteBuffer srcbuf,   int srcoff, int srcscanbytes,
                         byte       dstarr[], int dstoff, int dstscanbytes,
                         int w, int h);
 
+    /**
+     * Copies a rectangular region of data from the source array to the
+     * destination buffer using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanbytes + x * srcbytesperpixel + srcoff;
+     *     int dstpos = y * dstscanbytes + x * dstbytesperpixel + dstoff;
+     *     for each j : 0 <= j < srcbytesperpixel {
+     *         load data from srcarr[srcpos + j];
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstbytesperpixel {
+     *         store data into dstbuf.put(dstpos + k, pixel data);
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcarr the byte array containing the source data
+     * @param srcoff the index in the array of the first source pixel data
+     * @param srcscanbytes number of array indices between rows of data in the source
+     * @param dstbuf the nio buffer containing the destination data
+     * @param dstoff the absolute location in the buffer of the first destination pixel data
+     * @param dstscanbytes number of buffer elements between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(byte       srcarr[], int srcoff, int srcscanbytes,
                         ByteBuffer dstbuf,   int dstoff, int dstscanbytes,
                         int w, int h);
--- a/javafx-ui-common/src/com/sun/javafx/image/ByteToIntPixelConverter.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/com/sun/javafx/image/ByteToIntPixelConverter.java	Tue Nov 27 10:45:21 2012 -0800
@@ -31,14 +31,92 @@
 public interface ByteToIntPixelConverter
     extends PixelConverter<ByteBuffer, IntBuffer>
 {
+    /**
+     * Copies a rectangular region of data from the source array to the
+     * destination array using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanbytes + x * srcbytesperpixel + srcoff;
+     *     int dstpos = y * dstscanints  + x * dstintsperpixel  + dstoff;
+     *     for each j : 0 <= j < srcbytesperpixel {
+     *         load data from srcarr[srcpos + j];
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstintsperpixel {
+     *         store data into dstarry[dstpos + k] = pixel data;
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the byte array containing the source data
+     * @param srcoff the index in the array of the first source pixel data
+     * @param srcscanbytes number of array indices between rows of data in the source
+     * @param dstbuf the int array containing the destination data
+     * @param dstoff the index in the array of the first destination pixel data
+     * @param dstscanints number of array indices between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(byte srcarr[], int srcoff, int srcscanbytes,
                         int  dstarr[], int dstoff, int dstscanints,
                         int w, int h);
 
+    /**
+     * Copies a rectangular region of data from the source buffer to the
+     * destination array using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanbytes + x * srcbytesperpixel + srcoff;
+     *     int dstpos = y * dstscanints  + x * dstintsperpixel  + dstoff;
+     *     for each j : 0 <= j < srcbytesperpixel {
+     *         load data from srcbuf.get(srcpos + j);
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstintsperpixel {
+     *         store data into dstarry[dstpos + k] = pixel data;
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the nio buffer containing the source data
+     * @param srcoff the absolute location in the buffer of the first source pixel data
+     * @param srcscanbytes number of buffer elements between rows of data in the source
+     * @param dstbuf the int array containing the destination data
+     * @param dstoff the index in the array of the first destination pixel data
+     * @param dstscanints number of array indices between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(ByteBuffer srcbuf,   int srcoff, int srcscanbytes,
                         int        dstarr[], int dstoff, int dstscanints,
                         int w, int h);
 
+    /**
+     * Copies a rectangular region of data from the source array to the
+     * destination buffer using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanbytes + x * srcbytesperpixel + srcoff;
+     *     int dstpos = y * dstscanints  + x * dstintsperpixel  + dstoff;
+     *     for each j : 0 <= j < srcbytesperpixel {
+     *         load data from srcarr[srcpos + j];
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstintsperpixel {
+     *         store data into dstbuf.put(dstpos + k, pixel data)
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the byte array containing the source data
+     * @param srcoff the index in the array of the first source pixel data
+     * @param srcscanbytes number of array indices between rows of data in the source
+     * @param dstbuf the nio buffer containing the destination data
+     * @param dstoff the absolute location in the buffer of the first destination pixel data
+     * @param dstscanints number of buffer elements between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(byte      srcarr[], int srcoff, int srcscanbytes,
                         IntBuffer dstbuf,   int dstoff, int dstscanints,
                         int w, int h);
--- a/javafx-ui-common/src/com/sun/javafx/image/IntToBytePixelConverter.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/com/sun/javafx/image/IntToBytePixelConverter.java	Tue Nov 27 10:45:21 2012 -0800
@@ -31,14 +31,92 @@
 public interface IntToBytePixelConverter
     extends PixelConverter<IntBuffer, ByteBuffer>
 {
+    /**
+     * Copies a rectangular region of data from the source array to the
+     * destination array using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanints  + x * srcintsperpixel  + srcoff;
+     *     int dstpos = y * dstscanbytes + x * dstbytesperpixel + dstoff;
+     *     for each j : 0 <= j < srcintsperpixel {
+     *         load data from srcarr[srcpos + j];
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstbytesperpixel {
+     *         store data into dstarr[dstpos + k] = pixel data;
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the int array containing the source data
+     * @param srcoff the index in the array of the first source pixel data
+     * @param srcscanints number of array indices between rows of data in the source
+     * @param dstbuf the byte array containing the destination data
+     * @param dstoff the index in the array of the first destination pixel data
+     * @param dstscanbytes number of array indices between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(int  srcarr[], int srcoff, int srcscanints,
                         byte dstarr[], int dstoff, int dstscanbytes,
                         int w, int h);
 
+    /**
+     * Copies a rectangular region of data from the source buffer to the
+     * destination array using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanints  + x * srcintsperpixel  + srcoff;
+     *     int dstpos = y * dstscanbytes + x * dstbytesperpixel + dstoff;
+     *     for each j : 0 <= j < srcintsperpixel {
+     *         load data from srcbuf.get(srcpos + j);
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstbytesperpixel {
+     *         store data into dstarr[dstpos + k] = pixel data;
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the nio buffer containing the source data
+     * @param srcoff the absolute location in the buffer of the first source pixel data
+     * @param srcscanints number of buffer elements between rows of data in the source
+     * @param dstbuf the byte array containing the destination data
+     * @param dstoff the index in the array of the first destination pixel data
+     * @param dstscanbytes number of array indices between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(IntBuffer srcbuf,   int srcoff, int srcscanints,
                         byte      dstarr[], int dstoff, int dstscanbytes,
                         int w, int h);
 
+    /**
+     * Copies a rectangular region of data from the source array to the
+     * destination buffer using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanints  + x * srcintsperpixel  + srcoff;
+     *     int dstpos = y * dstscanbytes + x * dstbytesperpixel + dstoff;
+     *     for each j : 0 <= j < srcintsperpixel {
+     *         load data from srcarr[srcpos + j];
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstbytesperpixel {
+     *         store data into dstbuf.put(dstpos + k, pixel data);
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the int array containing the source data
+     * @param srcoff the index in the array of the first source pixel data
+     * @param srcscanints number of array indices between rows of data in the source
+     * @param dstbuf the nio buffer containing the destination data
+     * @param dstoff the absolute location in the buffer of the first destination pixel data
+     * @param dstscanbytes number of buffer elements between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(int        srcarr[], int srcoff, int srcscanints,
                         ByteBuffer dstbuf,   int dstoff, int dstscanbytes,
                         int w, int h);
--- a/javafx-ui-common/src/com/sun/javafx/image/IntToIntPixelConverter.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/com/sun/javafx/image/IntToIntPixelConverter.java	Tue Nov 27 10:45:21 2012 -0800
@@ -30,14 +30,92 @@
 public interface IntToIntPixelConverter
     extends PixelConverter<IntBuffer, IntBuffer>
 {
+    /**
+     * Copies a rectangular region of data from the source array to the
+     * destination array using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanints + x * srcintsperpixel + srcoff;
+     *     int dstpos = y * dstscanints + x * dstintsperpixel + dstoff;
+     *     for each j : 0 <= j < srcintsperpixel {
+     *         load data from srcarr[srcpos + j];
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstintsperpixel {
+     *         store data into dstarr[dstpos + k] = pixel data;
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the int array containing the source data
+     * @param srcoff the index in the array of the first source pixel data
+     * @param srcscanints number of array indices between rows of data in the source
+     * @param dstbuf the int array containing the destination data
+     * @param dstoff the index in the array of the first destination pixel data
+     * @param dstscanints number of array indices between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(int srcarr[], int srcoff, int srcscanints,
                         int dstarr[], int dstoff, int dstscanints,
                         int w, int h);
 
+    /**
+     * Copies a rectangular region of data from the source buffer to the
+     * destination array using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanints + x * srcintsperpixel + srcoff;
+     *     int dstpos = y * dstscanints + x * dstintsperpixel + dstoff;
+     *     for each j : 0 <= j < srcintsperpixel {
+     *         load data from srcbuf.get(srcpos + j);
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstintsperpixel {
+     *         store data into dstarr[dstpos + k] = pixel data;
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the nio buffer containing the source data
+     * @param srcoff the absolute location in the buffer of the first source pixel data
+     * @param srcscanints number of buffer elements between rows of data in the source
+     * @param dstbuf the int array containing the destination data
+     * @param dstoff the index in the array of the first destination pixel data
+     * @param dstscanints number of array indices between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(IntBuffer srcbuf,   int srcoff, int srcscanints,
                         int       dstarr[], int dstoff, int dstscanints,
                         int w, int h);
 
+    /**
+     * Copies a rectangular region of data from the source array to the
+     * destination buffer using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanints + x * srcintsperpixel + srcoff;
+     *     int dstpos = y * dstscanints + x * dstintsperpixel + dstoff;
+     *     for each j : 0 <= j < srcintsperpixel {
+     *         load data from srcarr[srcpos + j];
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstintsperpixel {
+     *         store data into dstbuf.put(dstpos + k, pixel data);
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the int array containing the source data
+     * @param srcoff the index in the array of the first source pixel data
+     * @param srcscanints number of array indices between rows of data in the source
+     * @param dstbuf the nio buffer containing the destination data
+     * @param dstoff the absolute location in the buffer of the first destination pixel data
+     * @param dstscanints number of buffer elements between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
     public void convert(int       srcarr[], int srcoff, int srcscanints,
                         IntBuffer dstbuf,   int dstoff, int dstscanints,
                         int w, int h);
--- a/javafx-ui-common/src/com/sun/javafx/image/PixelConverter.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/com/sun/javafx/image/PixelConverter.java	Tue Nov 27 10:45:21 2012 -0800
@@ -28,8 +28,34 @@
 import java.nio.Buffer;
 
 public interface PixelConverter<T extends Buffer, U extends Buffer> {
-    public void convert(T srcbuf, int srcoff, int srcscanbytes,
-                        U dstbuf, int dstoff, int dstscanbytes,
+    /**
+     * Copies a rectangular region of data from the source buffer to the
+     * destination buffer using the following relationship:
+     * <pre>
+     * for each xy : 0 <= x,y < w,h {
+     *     int srcpos = y * srcscanelems + x * srcelemsperpixel + srcoff;
+     *     int dstpos = y * dstscanelems + x * dstelemsperpixel + dstoff;
+     *     for each j : 0 <= j < srcelemsperpixel {
+     *         load data from srcbuf.get(srcpos + j);
+     *     }
+     *     convert data to destination pixel format
+     *     for each k : 0 <= k < dstelemsperpixel {
+     *         store data into dstbuf.put(dstpos + k, pixel data);
+     *     }
+     * }
+     * </pre>
+     * 
+     * @param srcbuf the nio buffer containing the source data
+     * @param srcoff the absolute location in the buffer of the first source pixel data
+     * @param srcscanelems number of buffer elements between rows of data in the source
+     * @param dstbuf the nio buffer containing the destination data
+     * @param dstoff the absolute location in the buffer of the first destination pixel data
+     * @param dstscanelems number of buffer elements between rows of data in the destination
+     * @param w the number of pixels to process across before moving to the next row
+     * @param h the number of rows of pixels to process
+     */
+    public void convert(T srcbuf, int srcoff, int srcscanelems,
+                        U dstbuf, int dstoff, int dstscanelems,
                         int w, int h);
 
     public PixelGetter<T> getGetter();
--- a/javafx-ui-common/src/com/sun/javafx/image/impl/BaseByteToByteConverter.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/com/sun/javafx/image/impl/BaseByteToByteConverter.java	Tue Nov 27 10:45:21 2012 -0800
@@ -94,6 +94,8 @@
             h = 1;
         }
         if (srcbuf.hasArray() && dstbuf.hasArray()) {
+            srcoff += srcbuf.arrayOffset();
+            dstoff += dstbuf.arrayOffset();
             doConvert(srcbuf.array(), srcoff, srcscanbytes,
                       dstbuf.array(), dstoff, dstscanbytes,
                       w, h);
@@ -117,7 +119,9 @@
             h = 1;
         }
         if (srcbuf.hasArray()) {
-            doConvert(srcbuf.array(), srcoff, srcscanbytes,
+            byte srcarr[] = srcbuf.array();
+            srcoff += srcbuf.arrayOffset();
+            doConvert(srcarr, srcoff, srcscanbytes,
                       dstarr, dstoff, dstscanbytes,
                       w, h);
         } else {
@@ -141,8 +145,10 @@
             h = 1;
         }
         if (dstbuf.hasArray()) {
+            byte dstarr[] = dstbuf.array();
+            dstoff += dstbuf.arrayOffset();
             doConvert(srcarr, srcoff, srcscanbytes,
-                      dstbuf.array(), dstbuf.arrayOffset(), dstscanbytes,
+                      dstarr, dstoff, dstscanbytes,
                       w, h);
         } else {
             ByteBuffer srcbuf = ByteBuffer.wrap(srcarr);
--- a/javafx-ui-common/src/com/sun/javafx/image/impl/BaseByteToIntConverter.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/com/sun/javafx/image/impl/BaseByteToIntConverter.java	Tue Nov 27 10:45:21 2012 -0800
@@ -92,6 +92,8 @@
             h = 1;
         }
         if (srcbuf.hasArray() && dstbuf.hasArray()) {
+            srcoff += srcbuf.arrayOffset();
+            dstoff += dstbuf.arrayOffset();
             doConvert(srcbuf.array(), srcoff, srcscanbytes,
                       dstbuf.array(), dstoff, dstscanints,
                       w, h);
@@ -115,7 +117,9 @@
             h = 1;
         }
         if (srcbuf.hasArray()) {
-            doConvert(srcbuf.array(), srcoff, srcscanbytes,
+            byte srcarr[] = srcbuf.array();
+            srcoff += srcbuf.arrayOffset();
+            doConvert(srcarr, srcoff, srcscanbytes,
                       dstarr, dstoff, dstscanints,
                       w, h);
         } else {
@@ -139,8 +143,10 @@
             h = 1;
         }
         if (dstbuf.hasArray()) {
+            int dstarr[] = dstbuf.array();
+            dstoff += dstbuf.arrayOffset();
             doConvert(srcarr, srcoff, srcscanbytes,
-                      dstbuf.array(), dstoff, dstscanints,
+                      dstarr, dstoff, dstscanints,
                       w, h);
         } else {
             ByteBuffer srcbuf = ByteBuffer.wrap(srcarr);
--- a/javafx-ui-common/src/com/sun/javafx/image/impl/BaseIntToByteConverter.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/com/sun/javafx/image/impl/BaseIntToByteConverter.java	Tue Nov 27 10:45:21 2012 -0800
@@ -92,6 +92,8 @@
             h = 1;
         }
         if (srcbuf.hasArray() && dstbuf.hasArray()) {
+            srcoff += srcbuf.arrayOffset();
+            dstoff += dstbuf.arrayOffset();
             doConvert(srcbuf.array(), srcoff, srcscanints,
                       dstbuf.array(), dstoff, dstscanbytes,
                       w, h);
@@ -115,7 +117,9 @@
             h = 1;
         }
         if (srcbuf.hasArray()) {
-            doConvert(srcbuf.array(), srcoff, srcscanints,
+            int srcarr[] = srcbuf.array();
+            srcoff += srcbuf.arrayOffset();
+            doConvert(srcarr, srcoff, srcscanints,
                       dstarr, dstoff, dstscanbytes,
                       w, h);
         } else {
@@ -139,8 +143,10 @@
             h = 1;
         }
         if (dstbuf.hasArray()) {
+            byte dstarr[] = dstbuf.array();
+            dstoff += dstbuf.arrayOffset();
             doConvert(srcarr, srcoff, srcscanints,
-                      dstbuf.array(), dstoff, dstscanbytes,
+                      dstarr, dstoff, dstscanbytes,
                       w, h);
         } else {
             IntBuffer srcbuf = IntBuffer.wrap(srcarr);
--- a/javafx-ui-common/src/com/sun/javafx/image/impl/BaseIntToIntConverter.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/com/sun/javafx/image/impl/BaseIntToIntConverter.java	Tue Nov 27 10:45:21 2012 -0800
@@ -90,6 +90,8 @@
             h = 1;
         }
         if (srcbuf.hasArray() && dstbuf.hasArray()) {
+            srcoff += srcbuf.arrayOffset();
+            dstoff += dstbuf.arrayOffset();
             doConvert(srcbuf.array(), srcoff, srcscanints,
                       dstbuf.array(), dstoff, dstscanints,
                       w, h);
@@ -113,7 +115,9 @@
             h = 1;
         }
         if (srcbuf.hasArray()) {
-            doConvert(srcbuf.array(), srcoff, srcscanints,
+            int srcarr[] = srcbuf.array();
+            srcoff += srcbuf.arrayOffset();
+            doConvert(srcarr, srcoff, srcscanints,
                       dstarr, dstoff, dstscanints,
                       w, h);
         } else {
@@ -137,8 +141,10 @@
             h = 1;
         }
         if (dstbuf.hasArray()) {
+            int dstarr[] = dstbuf.array();
+            dstoff += dstbuf.arrayOffset();
             doConvert(srcarr, srcoff, srcscanints,
-                      dstbuf.array(), dstoff, dstscanints,
+                      dstarr, dstoff, dstscanints,
                       w, h);
         } else {
             IntBuffer srcbuf = IntBuffer.wrap(srcarr);
--- a/javafx-ui-common/src/com/sun/javafx/scene/transform/TransformUtils.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/com/sun/javafx/scene/transform/TransformUtils.java	Tue Nov 27 10:45:21 2012 -0800
@@ -26,7 +26,11 @@
 package com.sun.javafx.scene.transform;
 
 import com.sun.javafx.geom.transform.Affine3D;
+import javafx.geometry.Point2D;
+import javafx.geometry.Point3D;
+import javafx.scene.transform.NonInvertibleTransformException;
 import javafx.scene.transform.Transform;
+import javafx.scene.transform.Affine;
 
 /**
  * Internal utilities for transformations
@@ -34,7 +38,7 @@
 public class TransformUtils {
 
     /**
-     * Creates an immutable arbitrary Affine transformation.
+     * Creates an immutable arbitrary transformation.
      * This method is not intended for public use, users should use the Affine
      * class.
      */
@@ -49,7 +53,7 @@
     }
 
     /**
-     * Creates an immutable Affine transformation filled with current values
+     * Creates an immutable transformation filled with current values
      * from the given transformation.
      * This method is not intended for public use, users should use the Affine
      * class.
@@ -62,123 +66,789 @@
     }
 
     /**
-     * Immutable transform implementation.
+     * Creates an immutable arbitrary transformation.
+     * If the given instance is not null, it is reused.
+     * This method is not intended for public use, users should use the Affine
+     * class.
+     * @throws ClassCastException if the given transform to be reused
+     *                            is not instance of ImmutableTransform
      */
-    private static class ImmutableTransform extends Transform {
-        private final double mxx, mxy, mxz, tx;
-        private final double myx, myy, myz, ty;
-        private final double mzx, mzy, mzz, tz;
-
-        public ImmutableTransform(
+    public static Transform immutableTransform(Transform reuse,
                 double mxx, double mxy, double mxz, double tx,
                 double myx, double myy, double myz, double ty,
                 double mzx, double mzy, double mzz, double tz) {
-            this.mxx = mxx;
-            this.mxy = mxy;
-            this.mxz = mxz;
-            this.tx = tx;
-            this.myx = myx;
-            this.myy = myy;
-            this.myz = myz;
-            this.ty = ty;
-            this.mzx = mzx;
-            this.mzy = mzy;
-            this.mzz = mzz;
-            this.tz = tz;
+
+        if (reuse == null) {
+            return new ImmutableTransform(
+                mxx, mxy, mxz, tx,
+                myx, myy, myz, ty,
+                mzx, mzy, mzz, tz);
+        }
+
+        ((ImmutableTransform) reuse).setToTransform(
+                mxx, mxy, mxz, tx,
+                myx, myy, myz, ty,
+                mzx, mzy, mzz, tz);
+        return reuse;
+    }
+
+    /**
+     * Creates an immutable transformation filled with current values
+     * from the given transformation.
+     * If the given instance is not null, it is reused.
+     * This method is not intended for public use, users should use the Affine
+     * class.
+     * @throws ClassCastException if the given transform to be reused
+     *                            is not instance of ImmutableTransform
+     */
+    public static Transform immutableTransform(Transform reuse,
+                Transform t) {
+        return immutableTransform((ImmutableTransform) reuse,
+                t.getMxx(), t.getMxy(), t.getMxz(), t.getTx(),
+                t.getMyx(), t.getMyy(), t.getMyz(), t.getTy(),
+                t.getMzx(), t.getMzy(), t.getMzz(), t.getTz());
+    }
+
+    /**
+     * Creates an immutable transformation filled with concatenation
+     * of the given transformations.
+     * If the given instance is not null, it is reused.
+     * This method is not intended for public use, users should use the Affine
+     * class.
+     * @throws ClassCastException if one of the given transforms
+     *                            is not instance of ImmutableTransform
+     */
+    public static Transform immutableTransform(Transform reuse,
+            Transform left, Transform right) {
+
+        if (reuse == null) {
+            reuse = new ImmutableTransform();
+        }
+
+        ((ImmutableTransform) reuse).setToConcatenation(
+                (ImmutableTransform) left, ((ImmutableTransform) right));
+
+        return reuse;
+    }
+
+    /**
+     * Immutable transformation with performance optimizations based on Affine.
+     *
+     * From user's perspective, this transform is immutable. However, we can
+     * modify it internally. This allows for reusing instances that were
+     * not handed to users. The caller is responsible for not modifying
+     * user-visible instances.
+     *
+     * Note: can't override Transform's package private methods so they cannot
+     * be optimized. Currently not a big deal.
+     */
+    static class ImmutableTransform extends Transform {
+
+        private static final int APPLY_IDENTITY = 0;
+        private static final int APPLY_TRANSLATE = 1;
+        private static final int APPLY_SCALE = 2;
+        private static final int APPLY_SHEAR = 4;
+        private static final int APPLY_NON_3D = 0;
+        private static final int APPLY_3D_COMPLEX = 4;
+        private transient int state2d;
+        private transient int state3d;
+
+        private double xx;
+        private double xy;
+        private double xz;
+        private double yx;
+        private double yy;
+        private double yz;
+        private double zx;
+        private double zy;
+        private double zz;
+        private double xt;
+        private double yt;
+        private double zt;
+
+        public ImmutableTransform() {
+            xx = yy = zz = 1.0;
+        }
+
+        public ImmutableTransform(Transform transform) {
+            this(transform.getMxx(), transform.getMxy(), transform.getMxz(),
+                                                                 transform.getTx(),
+                 transform.getMyx(), transform.getMyy(), transform.getMyz(),
+                                                                 transform.getTy(),
+                 transform.getMzx(), transform.getMzy(), transform.getMzz(),
+                                                                 transform.getTz());
+        }
+
+        public ImmutableTransform(double mxx, double mxy, double mxz, double tx,
+                      double myx, double myy, double myz, double ty,
+                      double mzx, double mzy, double mzz, double tz) {
+            xx = mxx;
+            xy = mxy;
+            xz = mxz;
+            xt = tx;
+
+            yx = myx;
+            yy = myy;
+            yz = myz;
+            yt = ty;
+
+            zx = mzx;
+            zy = mzy;
+            zz = mzz;
+            zt = tz;
+
+            updateState();
+        }
+
+        // Beware: this is modifying immutable transform!
+        // It is private and it is there just for the purpose of reusing
+        // instances not given to users
+        private void setToTransform(double mxx, double mxy, double mxz, double tx,
+                                    double myx, double myy, double myz, double ty,
+                                    double mzx, double mzy, double mzz, double tz)
+        {
+            xx = mxx;
+            xy = mxy;
+            xz = mxz;
+            xt = tx;
+            yx = myx;
+            yy = myy;
+            yz = myz;
+            yt = ty;
+            zx = mzx;
+            zy = mzy;
+            zz = mzz;
+            zt = tz;
+            updateState();
+        }
+
+        // Beware: this is modifying immutable transform!
+        // It is private and it is there just for the purpose of reusing
+        // instances not given to users
+        private void setToConcatenation(ImmutableTransform left, ImmutableTransform right) {
+            if (left.state3d == APPLY_NON_3D && right.state3d == APPLY_NON_3D) {
+                xx = left.xx * right.xx + left.xy * right.yx;
+                xy = left.xx * right.xy + left.xy * right.yy;
+                xt = left.xx * right.xt + left.xy * right.yt + left.xt;
+                yx = left.yx * right.xx + left.yy * right.yx;
+                yy = left.yx * right.xy + left.yy * right.yy;
+                yt = left.yx * right.xt + left.yy * right.yt + left.yt;
+                updateState2D();
+            } else {
+                xx = left.xx * right.xx + left.xy * right.yx + left.xz * right.zx;
+                xy = left.xx * right.xy + left.xy * right.yy + left.xz * right.zy;
+                xz = left.xx * right.xz + left.xy * right.yz + left.xz * right.zz;
+                xt = left.xx * right.xt + left.xy * right.yt + left.xz * right.zt + left.xt;
+                yx = left.yx * right.xx + left.yy * right.yx + left.yz * right.zx;
+                yy = left.yx * right.xy + left.yy * right.yy + left.yz * right.zy;
+                yz = left.yx * right.xz + left.yy * right.yz + left.yz * right.zz;
+                yt = left.yx * right.xt + left.yy * right.yt + left.yz * right.zt + left.yt;
+                zx = left.zx * right.xx + left.zy * right.yx + left.zz * right.zx;
+                zy = left.zx * right.xy + left.zy * right.yy + left.zz * right.zy;
+                zz = left.zx * right.xz + left.zy * right.yz + left.zz * right.zz;
+                zt = left.zx * right.xt + left.zy * right.yt + left.zz * right.zt + left.zt;
+                updateState();
+            }
+            // could be further optimized using the states, but that would
+            // require a lot of code (see Affine and all its append* methods)
         }
 
         @Override
         public double getMxx() {
-            return mxx;
+            return xx;
         }
 
         @Override
         public double getMxy() {
-            return mxy;
+            return xy;
         }
 
         @Override
         public double getMxz() {
-            return mxz;
+            return xz;
         }
 
         @Override
         public double getTx() {
-            return tx;
+            return xt;
         }
 
         @Override
         public double getMyx() {
-            return myx;
+            return yx;
         }
 
         @Override
         public double getMyy() {
-            return myy;
+            return yy;
         }
 
         @Override
         public double getMyz() {
-            return myz;
+            return yz;
         }
 
         @Override
         public double getTy() {
-            return ty;
+            return yt;
         }
 
         @Override
         public double getMzx() {
-            return mzx;
+            return zx;
         }
 
         @Override
         public double getMzy() {
-            return mzy;
+            return zy;
         }
 
         @Override
         public double getMzz() {
-            return mzz;
+            return zz;
         }
 
         @Override
         public double getTz() {
-            return tz;
+            return zt;
+        }
+
+    /* *************************************************************************
+     *                                                                         *
+     *                           State getters                                 *
+     *                                                                         *
+     **************************************************************************/
+
+        @Override
+        public double determinant() {
+            switch(state3d) {
+                default:
+                    stateError();
+                    // cannot reach
+                case APPLY_NON_3D:
+                    switch (state2d) {
+                        default:
+                            stateError();
+                            // cannot reach
+                        case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
+                        case APPLY_SHEAR | APPLY_SCALE:
+                            return xx * yy - xy * yx;
+                        case APPLY_SHEAR | APPLY_TRANSLATE:
+                        case APPLY_SHEAR:
+                            return -(xy* yx);
+                        case APPLY_SCALE | APPLY_TRANSLATE:
+                        case APPLY_SCALE:
+                            return xx * yy;
+                        case APPLY_TRANSLATE:
+                        case APPLY_IDENTITY:
+                            return 1.0;
+                    }
+                case APPLY_TRANSLATE:
+                    return 1.0;
+                case APPLY_SCALE:
+                case APPLY_SCALE | APPLY_TRANSLATE:
+                    return xx * yy * zz;
+                case APPLY_3D_COMPLEX:
+                    return (xx* (yy * zz - zy * yz) +
+                            xy* (yz * zx - zz * yx) +
+                            xz* (yx * zy - zx * yy));
+            }
         }
 
         @Override
-        public void impl_apply(Affine3D t) {
-            t.concatenate(
-                    getMxx(), getMxy(), getMxz(), getTx(),
-                    getMyx(), getMyy(), getMyz(), getTy(),
-                    getMzx(), getMzy(), getMzz(), getTz());
+        public Transform createConcatenation(Transform transform) {
+            javafx.scene.transform.Affine a = new Affine(this);
+            a.append(transform);
+            return a;
+        }
+
+        @Override
+        public javafx.scene.transform.Affine createInverse() throws NonInvertibleTransformException {
+            javafx.scene.transform.Affine t = new Affine(this);
+            t.invert();
+            return t;
+        }
+
+        @Override
+        public Transform clone() {
+            return new ImmutableTransform(this);
+        }
+
+        /* *************************************************************************
+         *                                                                         *
+         *                     Transform, Inverse Transform                        *
+         *                                                                         *
+         **************************************************************************/
+
+        @Override
+        public Point2D transform(double x, double y) {
+            ensureCanTransform2DPoint();
+
+            switch (state2d) {
+                default:
+                    stateError();
+                    // cannot reach
+                case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
+                    return new Point2D(
+                        xx * x + xy * y + xt,
+                        yx * x + yy * y + yt);
+                case APPLY_SHEAR | APPLY_SCALE:
+                    return new Point2D(
+                        xx * x + xy * y,
+                        yx * x + yy * y);
+                case APPLY_SHEAR | APPLY_TRANSLATE:
+                    return new Point2D(
+                            xy * y + xt,
+                            yx * x + yt);
+                case APPLY_SHEAR:
+                    return new Point2D(xy * y, yx * x);
+                case APPLY_SCALE | APPLY_TRANSLATE:
+                    return new Point2D(
+                            xx * x + xt,
+                            yy * y + yt);
+                case APPLY_SCALE:
+                    return new Point2D(xx * x, yy * y);
+                case APPLY_TRANSLATE:
+                    return new Point2D(x + xt, y + yt);
+                case APPLY_IDENTITY:
+                    return new Point2D(x, y);
+            }
+        }
+
+        @Override
+        public Point3D transform(double x, double y, double z) {
+            switch (state3d) {
+                default:
+                    stateError();
+                    // cannot reach
+                case APPLY_NON_3D:
+                    switch (state2d) {
+                        default:
+                            stateError();
+                            // cannot reach
+                        case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
+                            return new Point3D(
+                                xx * x + xy * y + xt,
+                                yx * x + yy * y + yt, z);
+                        case APPLY_SHEAR | APPLY_SCALE:
+                            return new Point3D(
+                                xx * x + xy * y,
+                                yx * x + yy * y, z);
+                        case APPLY_SHEAR | APPLY_TRANSLATE:
+                            return new Point3D(
+                                    xy * y + xt, yx * x + yt,
+                                    z);
+                        case APPLY_SHEAR:
+                            return new Point3D(xy * y, yx * x, z);
+                        case APPLY_SCALE | APPLY_TRANSLATE:
+                            return new Point3D(
+                                    xx * x + xt, yy * y + yt,
+                                    z);
+                        case APPLY_SCALE:
+                            return new Point3D(xx * x, yy * y, z);
+                        case APPLY_TRANSLATE:
+                            return new Point3D(x + xt, y + yt, z);
+                        case APPLY_IDENTITY:
+                            return new Point3D(x, y, z);
+                    }
+                case APPLY_TRANSLATE:
+                    return new Point3D(x + xt, y + yt, z + zt);
+                case APPLY_SCALE:
+                    return new Point3D(xx * x, yy * y, zz * z);
+                case APPLY_SCALE | APPLY_TRANSLATE:
+                    return new Point3D(
+                            xx * x + xt,
+                            yy * y + yt,
+                            zz * z + zt);
+                case APPLY_3D_COMPLEX:
+                    return new Point3D(
+                        xx * x + xy * y + xz * z + xt,
+                        yx * x + yy * y + yz * z + yt,
+                        zx * x + zy * y + zz * z + zt);
+            }
+        }
+
+        @Override
+        public Point2D deltaTransform(double x, double y) {
+            ensureCanTransform2DPoint();
+
+            switch (state2d) {
+                default:
+                    stateError();
+                    // cannot reach
+                case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
+                case APPLY_SHEAR | APPLY_SCALE:
+                    return new Point2D(
+                        xx * x + xy * y,
+                        yx * x + yy * y);
+                case APPLY_SHEAR | APPLY_TRANSLATE:
+                case APPLY_SHEAR:
+                    return new Point2D(xy * y, yx * x);
+                case APPLY_SCALE | APPLY_TRANSLATE:
+                case APPLY_SCALE:
+                    return new Point2D(xx * x, yy * y);
+                case APPLY_TRANSLATE:
+                case APPLY_IDENTITY:
+                    return new Point2D(x, y);
+            }
+        }
+
+        @Override
+        public Point3D deltaTransform(double x, double y, double z) {
+            switch (state3d) {
+                default:
+                    stateError();
+                    // cannot reach
+                case APPLY_NON_3D:
+                    switch (state2d) {
+                        default:
+                            stateError();
+                            // cannot reach
+                        case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
+                        case APPLY_SHEAR | APPLY_SCALE:
+                            return new Point3D(
+                                xx * x + xy * y,
+                                yx * x + yy * y, z);
+                        case APPLY_SHEAR | APPLY_TRANSLATE:
+                        case APPLY_SHEAR:
+                            return new Point3D(xy * y, yx * x, z);
+                        case APPLY_SCALE | APPLY_TRANSLATE:
+                        case APPLY_SCALE:
+                            return new Point3D(xx * x, yy * y, z);
+                        case APPLY_TRANSLATE:
+                        case APPLY_IDENTITY:
+                            return new Point3D(x, y, z);
+                    }
+                case APPLY_TRANSLATE:
+                    return new Point3D(x, y, z);
+                case APPLY_SCALE:
+                case APPLY_SCALE | APPLY_TRANSLATE:
+                    return new Point3D(xx * x, yy * y, zz * z);
+                case APPLY_3D_COMPLEX:
+                    return new Point3D(
+                        xx * x + xy * y + xz * z,
+                        yx * x + yy * y + yz * z,
+                        zx * x + zy * y + zz * z);
+            }
+        }
+
+        @Override
+        public Point2D inverseTransform(double x, double y)
+                throws NonInvertibleTransformException {
+            ensureCanTransform2DPoint();
+
+            switch (state2d) {
+                default:
+                    return super.inverseTransform(x, y);
+                case APPLY_SHEAR | APPLY_TRANSLATE:
+                    if (xy == 0.0 || yx == 0.0) {
+                        throw new NonInvertibleTransformException("Determinant is 0");
+                    }
+                    return new Point2D(
+                            (1.0 / yx) * y - yt / yx,
+                            (1.0 / xy) * x - xt / xy);
+                case APPLY_SHEAR:
+                    if (xy == 0.0 || yx == 0.0) {
+                        throw new NonInvertibleTransformException("Determinant is 0");
+                    }
+                    return new Point2D((1.0 / yx) * y, (1.0 / xy) * x);
+                case APPLY_SCALE | APPLY_TRANSLATE:
+                    if (xx == 0.0 || yy == 0.0) {
+                        throw new NonInvertibleTransformException("Determinant is 0");
+                    }
+                    return new Point2D(
+                            (1.0 / xx) * x - xt / xx,
+                            (1.0 / yy) * y - yt / yy);
+                case APPLY_SCALE:
+                    if (xx == 0.0 || yy == 0.0) {
+                        throw new NonInvertibleTransformException("Determinant is 0");
+                    }
+                    return new Point2D((1.0 / xx) * x, (1.0 / yy) * y);
+                case APPLY_TRANSLATE:
+                    return new Point2D(x - xt, y - yt);
+                case APPLY_IDENTITY:
+                    return new Point2D(x, y);
+            }
+        }
+
+        @Override
+        public Point3D inverseTransform(double x, double y, double z)
+                throws NonInvertibleTransformException {
+            switch(state3d) {
+                default:
+                    stateError();
+                    // cannot reach
+                case APPLY_NON_3D:
+                    switch (state2d) {
+                        default:
+                            return super.inverseTransform(x, y, z);
+                        case APPLY_SHEAR | APPLY_TRANSLATE:
+                            if (xy == 0.0 || yx == 0.0) {
+                                throw new NonInvertibleTransformException(
+                                        "Determinant is 0");
+                            }
+                            return new Point3D(
+                                    (1.0 / yx) * y - yt / yx,
+                                    (1.0 / xy) * x - xt / xy, z);
+                        case APPLY_SHEAR:
+                            if (xy == 0.0 || yx == 0.0) {
+                                throw new NonInvertibleTransformException(
+                                        "Determinant is 0");
+                            }
+                            return new Point3D(
+                                    (1.0 / yx) * y,
+                                    (1.0 / xy) * x, z);
+                        case APPLY_SCALE | APPLY_TRANSLATE:
+                            if (xx == 0.0 || yy == 0.0) {
+                                throw new NonInvertibleTransformException(
+                                        "Determinant is 0");
+                            }
+                            return new Point3D(
+                                    (1.0 / xx) * x - xt / xx,
+                                    (1.0 / yy) * y - yt / yy, z);
+                        case APPLY_SCALE:
+                            if (xx == 0.0 || yy == 0.0) {
+                                throw new NonInvertibleTransformException(
+                                        "Determinant is 0");
+                            }
+                            return new Point3D((1.0 / xx) * x, (1.0 / yy) * y, z);
+                        case APPLY_TRANSLATE:
+                            return new Point3D(x - xt, y - yt, z);
+                        case APPLY_IDENTITY:
+                            return new Point3D(x, y, z);
+                    }
+                case APPLY_TRANSLATE:
+                    return new Point3D(x - xt, y - yt, z - zt);
+                case APPLY_SCALE:
+                    if (xx == 0.0 || yy == 0.0 || zz == 0.0) {
+                        throw new NonInvertibleTransformException("Determinant is 0");
+                    }
+                    return new Point3D(
+                            (1.0 / xx) * x,
+                            (1.0 / yy) * y,
+                            (1.0 / zz) * z);
+                case APPLY_SCALE | APPLY_TRANSLATE:
+                    if (xx == 0.0 || yy == 0.0 || zz == 0.0) {
+                        throw new NonInvertibleTransformException("Determinant is 0");
+                    }
+                    return new Point3D(
+                            (1.0 / xx) * x - xt / xx,
+                            (1.0 / yy) * y - yt / yy,
+                            (1.0 / zz) * z - zt / zz);
+                case APPLY_3D_COMPLEX:
+                    return super.inverseTransform(x, y, z);
+            }
+        }
+
+        @Override
+        public Point2D inverseDeltaTransform(double x, double y)
+                throws NonInvertibleTransformException {
+            ensureCanTransform2DPoint();
+
+            switch (state2d) {
+                default:
+                    return super.inverseDeltaTransform(x, y);
+                case APPLY_SHEAR | APPLY_TRANSLATE:
+                case APPLY_SHEAR:
+                    if (xy == 0.0 || yx == 0.0) {
+                        throw new NonInvertibleTransformException("Determinant is 0");
+                    }
+                    return new Point2D((1.0 / yx) * y, (1.0 / xy) * x);
+                case APPLY_SCALE | APPLY_TRANSLATE:
+                case APPLY_SCALE:
+                    if (xx == 0.0 || yy == 0.0) {
+                        throw new NonInvertibleTransformException("Determinant is 0");
+                    }
+                    return new Point2D((1.0 / xx) * x, (1.0 / yy) * y);
+                case APPLY_TRANSLATE:
+                case APPLY_IDENTITY:
+                    return new Point2D(x, y);
+            }
+        }
+
+        @Override
+        public Point3D inverseDeltaTransform(double x, double y, double z)
+                throws NonInvertibleTransformException {
+            switch(state3d) {
+                default:
+                    stateError();
+                    // cannot reach
+                case APPLY_NON_3D:
+                    switch (state2d) {
+                        default:
+                            return super.inverseDeltaTransform(x, y, z);
+                        case APPLY_SHEAR | APPLY_TRANSLATE:
+                        case APPLY_SHEAR:
+                            if (xy == 0.0 || yx == 0.0) {
+                                throw new NonInvertibleTransformException(
+                                        "Determinant is 0");
+                            }
+                            return new Point3D(
+                                    (1.0 / yx) * y,
+                                    (1.0 / xy) * x, z);
+                        case APPLY_SCALE | APPLY_TRANSLATE:
+                        case APPLY_SCALE:
+                            if (xx == 0.0 || yy == 0.0) {
+                                throw new NonInvertibleTransformException(
+                                        "Determinant is 0");
+                            }
+                            return new Point3D(
+                                    (1.0 / xx) * x,
+                                    (1.0 / yy) * y, z);
+                        case APPLY_TRANSLATE:
+                        case APPLY_IDENTITY:
+                            return new Point3D(x, y, z);
+                    }
+
+                case APPLY_TRANSLATE:
+                    return new Point3D(x, y, z);
+                case APPLY_SCALE | APPLY_TRANSLATE:
+                case APPLY_SCALE:
+                    if (xx == 0.0 || yy == 0.0 || zz == 0.0) {
+                        throw new NonInvertibleTransformException("Determinant is 0");
+                    }
+                    return new Point3D(
+                            (1.0 / xx) * x,
+                            (1.0 / yy) * y,
+                            (1.0 / zz) * z);
+                case APPLY_3D_COMPLEX:
+                    return super.inverseDeltaTransform(x, y, z);
+            }
+        }
+
+        /* *************************************************************************
+         *                                                                         *
+         *                               Other API                                 *
+         *                                                                         *
+         **************************************************************************/
+
+        @Override
+        public String toString() {
+           final StringBuilder sb = new StringBuilder("Transform [\n");
+
+            sb.append("\t").append(xx);
+            sb.append(", ").append(xy);
+            sb.append(", ").append(xz);
+            sb.append(", ").append(xt);
+            sb.append('\n');
+            sb.append("\t").append(yx);
+            sb.append(", ").append(yy);
+            sb.append(", ").append(yz);
+            sb.append(", ").append(yt);
+            sb.append('\n');
+            sb.append("\t").append(zx);
+            sb.append(", ").append(zy);
+            sb.append(", ").append(zz);
+            sb.append(", ").append(zt);
+
+            return sb.append("\n]").toString();
+        }
+
+        /* *************************************************************************
+         *                                                                         *
+         *                    Internal implementation stuff                        *
+         *                                                                         *
+         **************************************************************************/
+
+        private void updateState() {
+            updateState2D();
+
+            state3d = APPLY_NON_3D;
+
+            if (xz != 0.0 ||
+                yz != 0.0 ||
+                zx != 0.0 ||
+                zy != 0.0)
+            {
+                state3d = APPLY_3D_COMPLEX;
+            } else {
+                if ((state2d & APPLY_SHEAR) == 0) {
+                    if (zt != 0.0) {
+                        state3d |= APPLY_TRANSLATE;
+                    }
+                    if (zz != 1.0) {
+                        state3d |= APPLY_SCALE;
+                    }
+                    if (state3d != APPLY_NON_3D) {
+                        state3d |= (state2d & (APPLY_SCALE | APPLY_TRANSLATE));
+                    }
+                } else {
+                    if (zz != 1.0 || zt != 0.0) {
+                        state3d = APPLY_3D_COMPLEX;
+                    }
+                }
+            }
+        }
+
+        private void updateState2D() {
+            if (xy == 0.0 && yx == 0.0) {
+                if (xx == 1.0 && yy == 1.0) {
+                    if (xt == 0.0 && yt == 0.0) {
+                        state2d = APPLY_IDENTITY;
+                    } else {
+                        state2d = APPLY_TRANSLATE;
+                    }
+                } else {
+                    if (xt == 0.0 && yt == 0.0) {
+                        state2d = APPLY_SCALE;
+                    } else {
+                        state2d = (APPLY_SCALE | APPLY_TRANSLATE);
+                    }
+                }
+            } else {
+                if (xx == 0.0 && yy == 0.0) {
+                    if (xt == 0.0 && yt == 0.0) {
+                        state2d = APPLY_SHEAR;
+                    } else {
+                        state2d = (APPLY_SHEAR | APPLY_TRANSLATE);
+                    }
+                } else {
+                    if (xt == 0.0 && yt == 0.0) {
+                        state2d = (APPLY_SHEAR | APPLY_SCALE);
+                    } else {
+                        state2d = (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE);
+                    }
+                }
+            }
+        }
+
+        void ensureCanTransform2DPoint() throws IllegalStateException {
+            if (state3d != APPLY_NON_3D) {
+                throw new IllegalStateException("Cannot transform 2D point "
+                        + "with a 3D transform");
+            }
+        }
+
+        private static void stateError() {
+            throw new InternalError("missing case in a switch");
         }
 
         /**
-         * Returns a string representation of this transform.
-         * @return a string representation of this transform.
+         * @treatAsPrivate implementation detail
+         * @deprecated This is an internal API that is not intended for use and will be removed in the next version
          */
+        @Deprecated
         @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder("Transform [");
+        public void impl_apply(final Affine3D trans) {
+            trans.concatenate(xx, xy, xz, xt,
+                              yx, yy, yz, yt,
+                              zx, zy, zz, zt);
+        }
 
-            sb.append("mxx=").append(getMxx());
-            sb.append(", mxy=").append(getMxy());
-            sb.append(", mxz=").append(getMxz());
-            sb.append(", tx=").append(getTx());
+        /**
+         * Used only by tests to check the 2d matrix state
+         */
+        int getState2d() {
+            return state2d;
+        }
 
-            sb.append(", myx=").append(getMyx());
-            sb.append(", myy=").append(getMyy());
-            sb.append(", myz=").append(getMyz());
-            sb.append(", ty=").append(getTy());
+        /**
+         * Used only by tests to check the 3d matrix state
+         */
+        int getState3d() {
+            return state3d;
+        }
 
-            sb.append(", mzx=").append(getMzx());
-            sb.append(", mzy=").append(getMzy());
-            sb.append(", mzz=").append(getMzz());
-            sb.append(", tz=").append(getTz());
-
-            return sb.append("]").toString();
-        }
     }
 }
+
--- a/javafx-ui-common/src/javafx/animation/SequentialTransition.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/javafx/animation/SequentialTransition.java	Tue Nov 27 10:45:21 2012 -0800
@@ -399,12 +399,7 @@
         return false;
     }
 
-    /**
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
-     */
-    @Deprecated
-    @Override public void impl_playTo(long currentTicks, long cycleTicks) {
+    @Override void impl_playTo(long currentTicks, long cycleTicks) {
         impl_setCurrentTicks(currentTicks);
         final double frac = calculateFraction(currentTicks, cycleTicks);
         final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks));
@@ -590,12 +585,7 @@
         oldTicks = newTicks;
     }
 
-    /**
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
-     */
-    @Deprecated
-    @Override public void impl_jumpTo(long currentTicks, long cycleTicks) {
+    @Override void impl_jumpTo(long currentTicks, long cycleTicks) {
         impl_sync(false);
         final Status status = getStatus();
         final double frac = calculateFraction(currentTicks, cycleTicks);
--- a/javafx-ui-common/src/javafx/animation/Transition.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/javafx/animation/Transition.java	Tue Nov 27 10:45:21 2012 -0800
@@ -205,26 +205,14 @@
         }
     }
 
-    /**
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will
-     *             be removed in the next version
-     */
-    @Deprecated
     @Override
-    public void impl_playTo(long currentTicks, long cycleTicks) {
+    void impl_playTo(long currentTicks, long cycleTicks) {
         impl_setCurrentTicks(currentTicks);
         interpolate(calculateFraction(currentTicks, cycleTicks));
     }
 
-    /**
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will
-     *             be removed in the next version
-     */
-    @Deprecated
     @Override
-    public void impl_jumpTo(long currentTicks, long cycleTicks) {
+    void impl_jumpTo(long currentTicks, long cycleTicks) {
         if (getStatus() != Status.STOPPED) {
             interpolate(calculateFraction(currentTicks, cycleTicks));
         }
--- a/javafx-ui-common/src/javafx/scene/Node.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/javafx/scene/Node.java	Tue Nov 27 10:45:21 2012 -0800
@@ -140,6 +140,8 @@
 import com.sun.javafx.scene.traversal.Direction;
 import com.sun.javafx.sg.PGNode;
 import com.sun.javafx.tk.Toolkit;
+import javafx.scene.transform.Affine;
+import javafx.scene.transform.NonInvertibleTransformException;
 import javafx.geometry.NodeOrientation;
 
 /**
@@ -4545,15 +4547,25 @@
             if (localToParentTransform == null) {
                 localToParentTransform = new LazyTransformProperty() {
                     @Override
-                    protected Transform computeTransform() {
+                    protected Transform computeTransform(Transform reuse) {
                         updateLocalToParentTransform();
-                        return TransformUtils.immutableTransform(
+                        return TransformUtils.immutableTransform(reuse,
                                 localToParentTx.getMxx(), localToParentTx.getMxy(), localToParentTx.getMxz(), localToParentTx.getMxt(),
                                 localToParentTx.getMyx(), localToParentTx.getMyy(), localToParentTx.getMyz(), localToParentTx.getMyt(),
                                 localToParentTx.getMzx(), localToParentTx.getMzy(), localToParentTx.getMzz(), localToParentTx.getMzt());
                     }
 
                     @Override
+                    protected boolean validityKnown() {
+                        return true;
+                    }
+
+                    @Override
+                    protected int computeValidity() {
+                        return valid;
+                    }
+
+                    @Override
                     public Object getBean() {
                         return Node.this;
                     }
@@ -4585,18 +4597,18 @@
                     private List localToSceneListeners;
 
                     @Override
-                    protected Transform computeTransform() {
+                    protected Transform computeTransform(Transform reuse) {
                         updateLocalToParentTransform();
 
-                        Transform result = null;
                         Node parentNode = Node.this.getParent();
                         if (parentNode != null) {
-                            result = parentNode.getLocalToSceneTransform();
-                            result = result.createConcatenation(getLocalToParentTransform());
+                            return TransformUtils.immutableTransform(reuse,
+                                    ((LazyTransformProperty) parentNode.localToSceneTransformProperty()).getInternalValue(),
+                                    ((LazyTransformProperty) localToParentTransformProperty()).getInternalValue());
                         } else {
-                            result = getLocalToParentTransform();
+                            return TransformUtils.immutableTransform(reuse,
+                                    ((LazyTransformProperty) localToParentTransformProperty()).getInternalValue());
                         }
-                        return result;
                     }
 
                     @Override
@@ -4610,14 +4622,27 @@
                     }
 
                     @Override
-                    public Transform get() {
-                        Transform t = super.get();
-                        if (listenerReasons == 0) {
-                            // we don't get invalidation notifications
-                            // so we must expect it to be always invalid
-                            invalidate();
+                    protected boolean validityKnown() {
+                        return listenerReasons > 0;
+                    }
+
+                    @Override
+                    protected int computeValidity() {
+                        Node n = (Node) getBean();
+                        while (n != null) {
+                            int nValid = ((LazyTransformProperty)
+                                    n.localToSceneTransformProperty()).valid;
+
+                            if (nValid == VALID) {
+                                return VALID;
+                            } else if (nValid == INVALID) {
+                                return INVALID;
+                            }
+                            n = n.getParent();
                         }
-                        return t;
+
+                        // Everything up to the root is unknown, so there is no invalid parent
+                        return VALID;
                     }
 
                     @Override
@@ -7773,10 +7798,16 @@
 
     private static abstract class LazyTransformProperty
             extends ReadOnlyObjectProperty<Transform> {
+
+        protected static final int VALID = 0;
+        protected static final int INVALID = 1;
+        protected static final int VALIDITY_UNKNOWN = 2;
+        protected int valid = INVALID;
+
         private ExpressionHelper<Transform> helper;
-        private boolean valid;
 
         private Transform transform;
+        private boolean canReuse = false;
 
         @Override
         public void addListener(InvalidationListener listener) {
@@ -7798,24 +7829,34 @@
             helper = ExpressionHelper.removeListener(helper, listener);
         }
 
+        private Transform getInternalValue() {
+            if (valid == INVALID ||
+                    (valid == VALIDITY_UNKNOWN && computeValidity() == INVALID)) {
+                transform = computeTransform(canReuse ? transform : null);
+                canReuse = true;
+                valid = validityKnown() ? VALID : VALIDITY_UNKNOWN;
+            }
+
+            return transform;
+        }
+
         @Override
         public Transform get() {
-            if (!valid) {
-                transform = computeTransform();
-                valid = true;
-            }
-
+            transform = getInternalValue();
+            canReuse = false;
             return transform;
         }
 
         public void invalidate() {
-            if (valid) {
-                valid = false;
+            if (valid != INVALID) {
+                valid = INVALID;
                 ExpressionHelper.fireValueChangedEvent(helper);
             }
         }
 
-        protected abstract Transform computeTransform();
+        protected abstract boolean validityKnown();
+        protected abstract int computeValidity();
+        protected abstract Transform computeTransform(Transform reuse);
     }
 
     private static abstract class LazyBoundsProperty
--- a/javafx-ui-common/src/javafx/scene/text/Text.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/javafx/scene/text/Text.java	Tue Nov 27 10:45:21 2012 -0800
@@ -118,7 +118,7 @@
 public class Text extends Shape {
 
     private TextLayout layout;
-
+    
     /**
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended
@@ -177,15 +177,17 @@
     }
 
     private void checkSpan() {
-//        isSpan = isManaged() && getParent() instanceof TextFlow;
+        isSpan = isManaged() && getParent() instanceof TextFlow;
     }
 
     private void needsFullTextLayout() {
         if (isSpan()) {
-            /* If layout bounds is already invalid the node will not 
-             * notify the parent. See Node#impl_layoutBoundsChanged()
+            /* Create new text span every time the font or text changes
+             * so the text layout can see that the content has changed.
              */
-            getParent().requestLayout();
+            textSpan = null;
+
+            /* Relies on impl_geomChanged() to request text flow to relayout */
         } else {
             TextLayout layout = getTextLayout();
             String string = getTextInternal();
@@ -221,9 +223,9 @@
 
     private TextLayout getTextLayout() {
         if (isSpan()) {
-//            layout = null;
-//            TextFlow parent = (TextFlow)getParent();
-//            return parent.getTextLayout();
+            layout = null;
+            TextFlow parent = (TextFlow)getParent();
+            return parent.getTextLayout();
         }
         if (layout == null) {
             TextLayoutFactory factory = Toolkit.getToolkit().getTextLayoutFactory();
@@ -242,7 +244,21 @@
     private boolean spanBoundsInvalid = true;
 
     void layoutSpan(GlyphList[] runs) {
+        /* Sometimes a property change in the text node will causes layout in 
+         * text flow. In this case all the dirty bits are already clear and no 
+         * extra work is necessary. Other times the layout is caused by changes  
+         * in the text flow object (wrapping width and text alignment for example).
+         * In the second case the dirty bits must be set here using 
+         * needsTextLayout(). Note that needsTextLayout() uses impl_geomChanged() 
+         * which causes another (undesired) layout request in the parent.
+         * In general this is not a problem because shapes are not resizable and 
+         * region do not propagate layout changes to the parent.
+         * This is a special case where a shape is resized by the parent during
+         * layoutChildren().  See TextFlow#requestLayout() for information how 
+         * text flow deals with this situation.
+         */
         needsTextLayout();
+
         spanBoundsInvalid = true;
         int count = 0;
         TextSpan span = getTextSpan();
@@ -263,10 +279,6 @@
     }
 
     BaseBounds getSpanBounds() {
-        /* Called during the final stage of layouChildren() in the parent 
-         * but also called by other methods, should not access the runs array
-         * directly.
-         */
         if (spanBoundsInvalid) {
             GlyphList[] runs = getRuns();
             if (runs.length != 0) {
@@ -284,7 +296,7 @@
                     right = Math.max(location.x + width, right);
                     bottom = Math.max(location.y + height, bottom);
                 }
-                spanBounds = spanBounds.deriveWithNewBounds(left, top, 0, 
+                spanBounds = spanBounds.deriveWithNewBounds(left, top, 0,
                                                             right, bottom, 0);
             } else {
                 spanBounds = spanBounds.makeEmpty();
@@ -317,7 +329,7 @@
             filter = getTextSpan();
         } else {
             /* Relative to baseline (first line)
-             * This shape can be translate in the y axis according 
+             * This shape can be translate in the y axis according
              * to text origin, see impl_configShape().
              */
             type |= TextLayout.TYPE_BASELINE;
@@ -476,6 +488,17 @@
         return font;
     }
 
+    public final void setTextOrigin(VPos value) {
+        textOriginProperty().set(value);
+    }
+
+    public final VPos getTextOrigin() {
+        if (attributes == null || attributes.textOrigin == null) {
+            return DEFAULT_TEXT_ORIGIN;
+        }
+        return attributes.getTextOrigin();
+    }
+
     /**
      * Defines the origin of text coordinate system in local coordinates.
      * Note: in case multiple rows are rendered {@code VPos.BASELINE} and
@@ -484,30 +507,19 @@
      *
      * @defaultValue VPos.BASELINE
      */
-    private ObjectProperty<VPos> textOrigin;
-
-    public final void setTextOrigin(VPos value) {
-        textOriginProperty().set(value);
+    public final ObjectProperty<VPos> textOriginProperty() {
+        return getTextAttribute().textOriginProperty();
     }
 
-    public final VPos getTextOrigin() {
-        return textOrigin == null ? VPos.BASELINE : textOrigin.get();
+    public final void setBoundsType(TextBoundsType value) {
+        boundsTypeProperty().set(value);
     }
 
-    public final ObjectProperty<VPos> textOriginProperty() {
-        if (textOrigin == null) {
-            textOrigin = new StyleableObjectProperty<VPos>(VPos.BASELINE) {
-                @Override public Object getBean() { return Text.this; }
-                @Override public String getName() { return "textOrigin"; }
-                @Override public StyleableProperty getStyleableProperty() {
-                    return StyleableProperties.TEXT_ORIGIN;
-                }
-                @Override public void invalidated() {
-                    impl_geomChanged();
-                }
-            };
+    public final TextBoundsType getBoundsType() {
+        if (attributes == null || attributes.boundsType == null) {
+            return DEFAULT_BOUNDS_TYPE;
         }
-        return textOrigin;
+        return attributes.getBoundsType();
     }
 
     /**
@@ -518,27 +530,8 @@
      * @defaultValue TextBoundsType.LOGICAL
      * @since JavaFX 1.3
      */
-    private ObjectProperty<TextBoundsType> boundsType;
-
-    public final void setBoundsType(TextBoundsType value) {
-        boundsTypeProperty().set(value);
-    }
-
-    public final TextBoundsType getBoundsType() {
-        return boundsType == null ? TextBoundsType.LOGICAL : boundsType.get();
-    }
-
     public final ObjectProperty<TextBoundsType> boundsTypeProperty() {
-        if (boundsType == null) {
-            boundsType =
-               new SimpleObjectProperty<TextBoundsType>(this, "boundsType", 
-                   TextBoundsType.LOGICAL) {
-                   @Override public void invalidated() {
-                       impl_geomChanged();
-                   }
-            };
-        }
-        return boundsType;
+        return getTextAttribute().boundsTypeProperty();
     }
 
     /**
@@ -577,35 +570,35 @@
         return wrappingWidth;
     }
 
+    public final void setUnderline(boolean value) {
+        underlineProperty().set(value);
+    }
+
+    public final boolean isUnderline() {
+        if (attributes == null || attributes.underline == null) {
+            return DEFAULT_UNDERLINE;
+        }
+        return attributes.isUnderline();
+    }
+
     /**
      * Defines if each line of text should have a line below it.
      *
      * @defaultValue false
      */
-    private BooleanProperty underline;
-
-    public final void setUnderline(boolean value) {
-        underlineProperty().set(value);
+    public final BooleanProperty underlineProperty() {
+        return getTextAttribute().underlineProperty();
     }
 
-    public final boolean isUnderline() {
-        return underline == null ? false : underline.get();
+    public final void setStrikethrough(boolean value) {
+        strikethroughProperty().set(value);
     }
 
-    public final BooleanProperty underlineProperty() {
-        if (underline == null) {
-            underline = new StyleableBooleanProperty() {
-                @Override public Object getBean() { return Text.this; }
-                @Override public String getName() { return "underline"; }
-                @Override public StyleableProperty getStyleableProperty() {
-                    return StyleableProperties.UNDERLINE;
-                }
-                @Override public void invalidated() {
-                    impl_markDirty(DirtyBits.TEXT_ATTRS);
-                }
-            };
+    public final boolean isStrikethrough() {
+        if (attributes == null || attributes.strikethrough == null) {
+            return DEFAULT_STRIKETHROUGH;
         }
-        return underline;
+        return attributes.isStrikethrough();
     }
 
     /**
@@ -613,30 +606,19 @@
      *
      * @defaultValue false
      */
-    private BooleanProperty strikethrough;
-
-    public final void setStrikethrough(boolean value) {
-        strikethroughProperty().set(value);
+    public final BooleanProperty strikethroughProperty() {
+        return getTextAttribute().strikethroughProperty();
     }
 
-    public final boolean isStrikethrough() {
-        return strikethrough == null ? false : strikethrough.get();
+    public final void setTextAlignment(TextAlignment value) {
+        textAlignmentProperty().set(value);
     }
 
-    public final BooleanProperty strikethroughProperty() {
-        if (strikethrough == null) {
-            strikethrough = new StyleableBooleanProperty() {
-                @Override public Object getBean() { return Text.this; }
-                @Override public String getName() { return "strikethrough"; }
-                @Override public StyleableProperty getStyleableProperty() {
-                    return StyleableProperties.STRIKETHROUGH;
-                }
-                @Override public void invalidated() {
-                    impl_markDirty(DirtyBits.TEXT_ATTRS);
-                }
-            };
+    public final TextAlignment getTextAlignment() {
+        if (attributes == null || attributes.textAlignment == null) {
+            return DEFAULT_TEXT_ALIGNMENT;
         }
-        return strikethrough;
+        return attributes.getTextAlignment();
     }
 
     /**
@@ -649,36 +631,14 @@
      * has no effect.
      *
      * @defaultValue TextAlignment.LEFT
-     */
-    private ObjectProperty<TextAlignment> textAlignment;
-
-    public final void setTextAlignment(TextAlignment value) {
-        textAlignmentProperty().set(value);
+     */   
+    public final ObjectProperty<TextAlignment> textAlignmentProperty() {
+        return getTextAttribute().textAlignmentProperty();
     }
 
-    public final TextAlignment getTextAlignment() {
-        return textAlignment == null ? TextAlignment.LEFT : textAlignment.get();
-    }
-
-    public final ObjectProperty<TextAlignment> textAlignmentProperty() {
-        if (textAlignment == null) {
-            textAlignment =
-                new StyleableObjectProperty<TextAlignment>(TextAlignment.LEFT) {
-                @Override public Object getBean() { return Text.this; }
-                @Override public String getName() { return "textAlignment"; }
-                @Override public StyleableProperty getStyleableProperty() {
-                    return StyleableProperties.TEXT_ALIGNMENT;
-                }
-                @Override public void invalidated() {
-                    if (!isSpan()) {
-                        TextLayout layout = getTextLayout();
-                        layout.setAlignment(get().ordinal());
-                        needsTextLayout();
-                    }
-                }
-            };
-        }
-        return textAlignment;
+    @Override
+    public final double getBaselineOffset() {
+        return baselineOffsetProperty().get();
     }
 
     /**
@@ -688,29 +648,8 @@
      *
      * @since JavaFX 1.3
      */
-    private ReadOnlyDoubleWrapper baselineOffset;
-
-    @Override
-    public final double getBaselineOffset() {
-        return baselineOffsetProperty().get();
-    }
-
     public final ReadOnlyDoubleProperty baselineOffsetProperty() {
-        if (baselineOffset == null) {
-            baselineOffset = new ReadOnlyDoubleWrapper(this, "baselineOffset") {
-                {bind(new DoubleBinding() {
-                    {bind(fontProperty());}
-                    @Override protected double computeValue() {
-                        /* This method should never be used for spans.
-                         * If it is, it will still returns the ascent 
-                         * for the first line in the layout */
-                        BaseBounds bounds = getLogicalBounds();
-                        return -bounds.getMinY();
-                    }
-                });}
-            };
-        }
-        return baselineOffset.getReadOnlyProperty();
+        return getTextAttribute().baselineOffsetProperty();
     }
 
     /**
@@ -730,7 +669,7 @@
     }
 
     public final FontSmoothingType getFontSmoothingType() {
-        return fontSmoothingType == null ? 
+        return fontSmoothingType == null ?
             FontSmoothingType.GRAY : fontSmoothingType.get();
     }
 
@@ -781,22 +720,18 @@
     @Override
     protected final void impl_geomChanged() {
         super.impl_geomChanged();
-        if (impl_caretBinding != null) impl_caretBinding.invalidate();
-        if (impl_selectionBinding != null) impl_selectionBinding.invalidate();
+        if (attributes != null) {
+            if (attributes.impl_caretBinding != null) {
+                attributes.impl_caretBinding.invalidate();
+            }
+            if (attributes.impl_selectionBinding != null) {
+                attributes.impl_selectionBinding.invalidate();
+            }
+        }
         impl_markDirty(DirtyBits.NODE_GEOMETRY);
     }
 
     /**
-     * Shape of selection in local coordinates.
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended
-     * for use and will be removed in the next version
-     */
-    @Deprecated
-    private ObjectProperty<PathElement[]> impl_selectionShape;
-    private ObjectBinding<PathElement[]> impl_selectionBinding;
-
-    /**
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended
      * for use and will be removed in the next version
@@ -807,46 +742,28 @@
     }
 
     /**
+     * Shape of selection in local coordinates. 
+     * 
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended
      * for use and will be removed in the next version
      */
     @Deprecated
     public final ReadOnlyObjectProperty<PathElement[]> impl_selectionShapeProperty() {
-        if (impl_selectionShape == null) {
-            impl_selectionBinding = new ObjectBinding<PathElement[]>() {
-                {bind(impl_selectionStartProperty(), impl_selectionEndProperty());}
-                @Override protected PathElement[] computeValue() {
-                    int start = getImpl_selectionStart();
-                    int end = getImpl_selectionEnd();
-                    return getRange(start, end, TextLayout.TYPE_TEXT);
-                }
-          };
-          impl_selectionShape = new SimpleObjectProperty<PathElement[]>(this, "impl_selectionShape");
-          impl_selectionShape.bind(impl_selectionBinding);
-        }
-        return impl_selectionShape;
+        return getTextAttribute().impl_selectionShapeProperty();
     }
 
     /**
-     * Selection start index in the content.
-     * set to {@code -1} to unset selection.
-     *
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended
-     * for use and will be removed in the next version
-     */
-    @Deprecated
-    private IntegerProperty impl_selectionStart;
-
-    /**
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended
      * for use and will be removed in the next version
      */
     @Deprecated
     public final void setImpl_selectionStart(int value) {
-        if (value == -1 && impl_selectionStart == null) return;
+        if (value == -1 && 
+                (attributes == null || attributes.impl_selectionStart == null)) {
+            return;
+        }
         impl_selectionStartProperty().set(value);
     }
 
@@ -857,7 +774,23 @@
      */
     @Deprecated
     public final int getImpl_selectionStart() {
-        return impl_selectionStart == null ? -1 : impl_selectionStart.get();
+        if (attributes == null || attributes.impl_selectionStart == null) {
+            return DEFAULT_SELECTION_START;
+        }
+        return attributes.getImpl_selectionStart();
+    }
+
+    /**
+     * Selection start index in the content. 
+     * set to {@code -1} to unset selection.
+     *
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
+     */
+    @Deprecated
+    public final IntegerProperty impl_selectionStartProperty() {
+        return getTextAttribute().impl_selectionStartProperty();
     }
 
     /**
@@ -866,37 +799,11 @@
      * for use and will be removed in the next version
      */
     @Deprecated
-    public final IntegerProperty impl_selectionStartProperty() {
-        if (impl_selectionStart == null) {
-            impl_selectionStart = 
-                new SimpleIntegerProperty(this, "impl_selectionStart", -1) {
-                    @Override protected void invalidated() {
-                        impl_markDirty(DirtyBits.TEXT_SELECTION);
-                    }
-            };
+    public final void setImpl_selectionEnd(int value) {
+        if (value == -1 && 
+                (attributes == null || attributes.impl_selectionEnd == null)) {
+            return;
         }
-        return impl_selectionStart;
-    }
-
-    /**
-     * Selection end index in the content.
-     * set to {@code -1} to unset selection.
-     *
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended
-     * for use and will be removed in the next version
-     */
-    @Deprecated
-    private IntegerProperty impl_selectionEnd;
-
-    /**
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended
-     * for use and will be removed in the next version
-     */
-    @Deprecated
-    public final void setImpl_selectionEnd(int value) {
-        if (value == -1 && impl_selectionEnd == null) return;
         impl_selectionEndProperty().set(value);
     }
 
@@ -907,7 +814,23 @@
      */
     @Deprecated
     public final int getImpl_selectionEnd() {
-        return impl_selectionEnd == null ? -1 : impl_selectionEnd.get();
+        if (attributes == null || attributes.impl_selectionEnd == null) {
+            return DEFAULT_SELECTION_END;
+        }
+        return attributes.getImpl_selectionEnd();
+    }
+
+    /**
+     * Selection end index in the content. 
+     * set to {@code -1} to unset selection.
+     *
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
+     */
+    @Deprecated
+    public final IntegerProperty impl_selectionEndProperty() {
+        return getTextAttribute().impl_selectionEndProperty();
     }
 
     /**
@@ -916,49 +839,10 @@
      * for use and will be removed in the next version
      */
     @Deprecated
-    public final IntegerProperty impl_selectionEndProperty() {
-        if (impl_selectionEnd == null) {
-            impl_selectionEnd = 
-                new SimpleIntegerProperty(this, "impl_selectionEnd", -1) {
-                    @Override protected void invalidated() {
-                        impl_markDirty(DirtyBits.TEXT_SELECTION);
-                    }
-                };
-        }
-        return impl_selectionEnd;
+    public final ObjectProperty<Paint> impl_selectionFillProperty() {
+        return getTextAttribute().impl_selectionFillProperty();
     }
 
-    private ObjectProperty<Paint> selectionFill;
-
-    /**
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended
-     * for use and will be removed in the next version
-     */
-    @Deprecated
-    public final ObjectProperty<Paint> impl_selectionFillProperty() {
-        if (selectionFill == null) {
-            selectionFill = 
-                new SimpleObjectProperty<Paint>(this, "impl_selectionFill",
-                                                Color.WHITE) {
-                    @Override protected void invalidated() {
-                        impl_markDirty(DirtyBits.TEXT_SELECTION);
-                    }
-                };
-        }
-        return selectionFill;
-    }
-
-    /**
-     * Shape of caret in local coordinates.
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended
-     * for use and will be removed in the next version
-     */
-    @Deprecated
-    private ObjectProperty<PathElement[]> impl_caretShape;
-    private ObjectBinding<PathElement[]> impl_caretBinding;
-
     /**
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended
@@ -970,53 +854,28 @@
     }
 
     /**
+     * Shape of caret in local coordinates.
+     * 
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended
      * for use and will be removed in the next version
-     */
+    */
     @Deprecated
     public final ReadOnlyObjectProperty<PathElement[]> impl_caretShapeProperty() {
-        if (impl_caretShape == null) {
-            impl_caretBinding = new ObjectBinding<PathElement[]>() {
-                {bind(impl_caretPositionProperty(), impl_caretBiasProperty());}
-                @Override protected PathElement[] computeValue() {
-                    int pos = getImpl_caretPosition();
-                    int length = getTextInternal().length();
-                    if (0 <= pos && pos <= length) {
-                        boolean bias = isImpl_caretBias();
-                        float x = (float)getX();
-                        float y = (float)getY() - getYRendering();
-                        TextLayout layout = getTextLayout();
-                        return layout.getCaretShape(pos, bias, x, y);
-                    }
-                    return new PathElement[0];
-                }
-            };
-            impl_caretShape = new SimpleObjectProperty<PathElement[]>(this, "impl_caretShape");
-            impl_caretShape.bind(impl_caretBinding);
-        }
-        return impl_caretShape;
+        return getTextAttribute().impl_caretShapeProperty();
     }
 
     /**
-     * caret index in the content.
-     * set to {@code -1} to unset caret.
-     *
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended
-     * for use and will be removed in the next version
-     */
-    @Deprecated
-    private IntegerProperty impl_caretPosition;
-
-    /**
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended
      * for use and will be removed in the next version
      */
     @Deprecated
     public final void setImpl_caretPosition(int value) {
-        if (value == -1 && impl_caretPosition == null) return;
+        if (value == -1 && 
+                (attributes == null || attributes.impl_caretPosition == null)) {
+            return;
+        }
         impl_caretPositionProperty().set(value);
     }
 
@@ -1027,7 +886,23 @@
      */
     @Deprecated
     public final int getImpl_caretPosition() {
-        return impl_caretPosition == null ? -1 : impl_caretPosition.get();
+        if (attributes == null || attributes.impl_caretPosition == null) {
+            return DEFAULT_CARET_POSITION;
+        }
+        return attributes.getImpl_caretPosition();
+    }
+
+    /**
+     * caret index in the content. 
+     * set to {@code -1} to unset caret.
+     * 
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
+     */
+    @Deprecated
+    public final IntegerProperty impl_caretPositionProperty() {
+        return getTextAttribute().impl_caretPositionProperty();
     }
 
     /**
@@ -1036,12 +911,24 @@
      * for use and will be removed in the next version
      */
     @Deprecated
-    public final IntegerProperty impl_caretPositionProperty() {
-        if (impl_caretPosition == null) {
-            impl_caretPosition = 
-                new SimpleIntegerProperty(this, "impl_caretPosition", -1);
+    public final void setImpl_caretBias(boolean value) {
+        if (value && (attributes == null || attributes.impl_caretBias == null)) {
+            return;
         }
-        return impl_caretPosition;
+        impl_caretBiasProperty().set(value);
+    }
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
+     */
+    @Deprecated
+    public final boolean isImpl_caretBias() {
+        if (attributes == null || attributes.impl_caretBias == null) {
+            return DEFAULT_CARET_BIAS;
+        } 
+        return getTextAttribute().isImpl_caretBias();
     }
 
     /**
@@ -1053,41 +940,8 @@
      * for use and will be removed in the next version
      */
     @Deprecated
-    private BooleanProperty impl_caretBias;
-
-    /**
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended
-     * for use and will be removed in the next version
-     */
-    @Deprecated
-    public final void setImpl_caretBias(boolean value) {
-        if (value && impl_caretBias == null) return;
-        impl_caretBiasProperty().set(value);
-    }
-
-    /**
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended
-     * for use and will be removed in the next version
-     */
-    @Deprecated
-    public final boolean isImpl_caretBias() {
-        return impl_caretBias == null ? true : impl_caretBias.get();
-    }
-
-    /**
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended
-     * for use and will be removed in the next version
-     */
-    @Deprecated
     public final BooleanProperty impl_caretBiasProperty() {
-        if (impl_caretBias == null) {
-            impl_caretBias = 
-                new SimpleBooleanProperty(this, "impl_caretBias", true);
-        }
-        return impl_caretBias;
+        return getTextAttribute().impl_caretBiasProperty();
     }
 
     /**
@@ -1201,12 +1055,12 @@
             BaseBounds bounds = getSpanBounds();
             double width = bounds.getWidth();
             double height = bounds.getHeight();
-            return new BoundingBox(0, 0, width, height); 
+            return new BoundingBox(0, 0, width, height);
         }
 
         if (getBoundsType() == TextBoundsType.VISUAL) {
             /* In Node the layout bounds is computed based in the geom
-             * bounds and in Shape the geom bounds is computed based 
+             * bounds and in Shape the geom bounds is computed based
              * on the shape (generated here in #configShape()) */
             return super.impl_computeLayoutBounds();
         }
@@ -1217,7 +1071,7 @@
         double height = bounds.getHeight();
         double wrappingWidth = getWrappingWidth();
         if (wrappingWidth != 0) width = wrappingWidth;
-        return new BoundingBox(x, y, width, height); 
+        return new BoundingBox(x, y, width, height);
     }
 
     /**
@@ -1266,7 +1120,7 @@
 
         /* handle stroked text */
         if (impl_mode != Mode.FILL && getStrokeType() != StrokeType.INSIDE) {
-            bounds = 
+            bounds =
                 super.impl_computeGeomBounds(bounds,
                                              BaseTransform.IDENTITY_TRANSFORM);
         } else {
@@ -1289,7 +1143,7 @@
     @Deprecated
     @Override
     protected final boolean impl_computeContains(double localX, double localY) {
-        //TODO Presently only support bounds based picking. 
+        //TODO Presently only support bounds based picking.
         return true;
     }
 
@@ -1329,7 +1183,7 @@
       */
      private static class StyleableProperties {
 
-         private static final StyleableProperty<Text,Font> FONT = 
+         private static final StyleableProperty<Text,Font> FONT =
             new StyleableProperty.FONT<Text>("-fx-font", Font.getDefault()) {
 
             @Override
@@ -1343,13 +1197,15 @@
             }
          };
 
-         private static final StyleableProperty<Text,Boolean> UNDERLINE = 
+         private static final StyleableProperty<Text,Boolean> UNDERLINE =
             new StyleableProperty<Text,Boolean>("-fx-underline",
                  BooleanConverter.getInstance(), Boolean.FALSE) {
 
             @Override
             public boolean isSettable(Text node) {
-                return node.underline == null || !node.underline.isBound();
+                return node.attributes == null ||
+                       node.attributes.underline == null ||
+                      !node.attributes.underline.isBound();
             }
 
             @Override
@@ -1358,14 +1214,15 @@
             }
          };
 
-         private static final StyleableProperty<Text,Boolean> STRIKETHROUGH = 
+         private static final StyleableProperty<Text,Boolean> STRIKETHROUGH =
             new StyleableProperty<Text,Boolean>("-fx-strikethrough",
                  BooleanConverter.getInstance(), Boolean.FALSE) {
 
             @Override
             public boolean isSettable(Text node) {
-                return node.strikethrough == null ||
-                      !node.strikethrough.isBound();
+                return node.attributes == null ||
+                       node.attributes.strikethrough == null ||
+                      !node.attributes.strikethrough.isBound();
             }
 
             @Override
@@ -1382,8 +1239,9 @@
 
             @Override
             public boolean isSettable(Text node) {
-                return node.textAlignment == null ||
-                      !node.textAlignment.isBound();
+                return node.attributes == null ||
+                       node.attributes.textAlignment == null ||
+                      !node.attributes.textAlignment.isBound();
             }
 
             @Override
@@ -1392,14 +1250,16 @@
             }
          };
 
-         private static final StyleableProperty<Text,VPos> TEXT_ORIGIN = 
+         private static final StyleableProperty<Text,VPos> TEXT_ORIGIN =
                  new StyleableProperty<Text,VPos>("-fx-text-origin",
                  new EnumConverter<VPos>(VPos.class),
                  VPos.BASELINE) {
 
             @Override
             public boolean isSettable(Text node) {
-                return node.textOrigin == null || !node.textOrigin.isBound();
+                return node.attributes == null || 
+                       node.attributes.textOrigin == null || 
+                      !node.attributes.textOrigin.isBound();
             }
 
             @Override
@@ -1409,7 +1269,7 @@
          };
 
          private static final StyleableProperty<Text,FontSmoothingType>
-             FONT_SMOOTHING_TYPE = 
+             FONT_SMOOTHING_TYPE =
              new StyleableProperty<Text,FontSmoothingType>(
                  "-fx-font-smoothing-type",
                  new EnumConverter<FontSmoothingType>(FontSmoothingType.class),
@@ -1528,6 +1388,308 @@
         if( accText == null)
             accText = new AccessibleText(this);
         return (AccessibleProvider)accText ;
-    }    
+    }
     
+    /***************************************************************************
+     *                                                                         *
+     *                       Seldom Used Properties                            *
+     *                                                                         *
+     **************************************************************************/
+
+    private TextAttribute attributes;
+    
+    private TextAttribute getTextAttribute() {
+        if (attributes == null) {
+            attributes = new TextAttribute();
+        }
+        return attributes;
+    }
+    
+    private static final VPos DEFAULT_TEXT_ORIGIN = VPos.BASELINE;
+    private static final TextBoundsType DEFAULT_BOUNDS_TYPE = TextBoundsType.LOGICAL;
+    private static final boolean DEFAULT_UNDERLINE = false;
+    private static final boolean DEFAULT_STRIKETHROUGH = false;
+    private static final TextAlignment DEFAULT_TEXT_ALIGNMENT = TextAlignment.LEFT;
+    private static final int DEFAULT_CARET_POSITION = -1;
+    private static final int DEFAULT_SELECTION_START = -1;
+    private static final int DEFAULT_SELECTION_END = -1;
+    private static final Color DEFAULT_SELECTION_FILL= Color.WHITE;
+    private static final boolean DEFAULT_CARET_BIAS = true;
+    
+    private final class TextAttribute {
+
+        private ObjectProperty<VPos> textOrigin;
+
+        public final VPos getTextOrigin() {
+            return textOrigin == null ? DEFAULT_TEXT_ORIGIN : textOrigin.get();
+        }
+
+        public final ObjectProperty<VPos> textOriginProperty() {
+            if (textOrigin == null) {
+                textOrigin = new StyleableObjectProperty<VPos>(DEFAULT_TEXT_ORIGIN) {
+                    @Override public Object getBean() { return Text.this; }
+                    @Override public String getName() { return "textOrigin"; }
+                    @Override public StyleableProperty getStyleableProperty() {
+                        return StyleableProperties.TEXT_ORIGIN;
+                    }
+                    @Override public void invalidated() {
+                        impl_geomChanged();
+                    }
+                };
+            }
+            return textOrigin;
+        }
+        
+        private ObjectProperty<TextBoundsType> boundsType;
+
+        public final TextBoundsType getBoundsType() {
+            return boundsType == null ? DEFAULT_BOUNDS_TYPE : boundsType.get();
+        }
+
+        public final ObjectProperty<TextBoundsType> boundsTypeProperty() {
+            if (boundsType == null) {
+                boundsType =
+                   new SimpleObjectProperty<TextBoundsType>(Text.this, "boundsType", 
+                       DEFAULT_BOUNDS_TYPE) {
+                       @Override public void invalidated() {
+                           impl_geomChanged();
+                       }
+                };
+            }
+            return boundsType;
+        }
+        
+        private BooleanProperty underline;
+
+        public final boolean isUnderline() {
+            return underline == null ? DEFAULT_UNDERLINE : underline.get();
+        }
+
+        public final BooleanProperty underlineProperty() {
+            if (underline == null) {
+                underline = new StyleableBooleanProperty() {
+                    @Override public Object getBean() { return Text.this; }
+                    @Override public String getName() { return "underline"; }
+                    @Override public StyleableProperty getStyleableProperty() {
+                        return StyleableProperties.UNDERLINE;
+                    }
+                    @Override public void invalidated() {
+                        impl_markDirty(DirtyBits.TEXT_ATTRS);
+                    }
+                };
+            }
+            return underline;
+        }
+        
+        private BooleanProperty strikethrough;
+
+        public final boolean isStrikethrough() {
+            return strikethrough == null ? DEFAULT_STRIKETHROUGH : strikethrough.get();
+        }
+
+        public final BooleanProperty strikethroughProperty() {
+            if (strikethrough == null) {
+                strikethrough = new StyleableBooleanProperty() {
+                    @Override public Object getBean() { return Text.this; }
+                    @Override public String getName() { return "strikethrough"; }
+                    @Override public StyleableProperty getStyleableProperty() {
+                        return StyleableProperties.STRIKETHROUGH;
+                    }
+                    @Override public void invalidated() {
+                        impl_markDirty(DirtyBits.TEXT_ATTRS);
+                    }
+                };
+            }
+            return strikethrough;
+        }
+        
+        private ObjectProperty<TextAlignment> textAlignment;
+
+        public final TextAlignment getTextAlignment() {
+            return textAlignment == null ? DEFAULT_TEXT_ALIGNMENT : textAlignment.get();
+        }
+
+        public final ObjectProperty<TextAlignment> textAlignmentProperty() {
+            if (textAlignment == null) {
+                textAlignment =
+                    new StyleableObjectProperty<TextAlignment>(DEFAULT_TEXT_ALIGNMENT) {
+                    @Override public Object getBean() { return Text.this; }
+                    @Override public String getName() { return "textAlignment"; }
+                    @Override public StyleableProperty getStyleableProperty() {
+                        return StyleableProperties.TEXT_ALIGNMENT;
+                    }
+                    @Override public void invalidated() {
+                        if (!isSpan()) {
+                            TextLayout layout = getTextLayout();
+                            layout.setAlignment(get().ordinal());
+                            needsTextLayout();
+                        }
+                    }
+                };
+            }
+            return textAlignment;
+        }
+        
+        private ReadOnlyDoubleWrapper baselineOffset;
+
+        public final ReadOnlyDoubleProperty baselineOffsetProperty() {
+            if (baselineOffset == null) {
+                baselineOffset = new ReadOnlyDoubleWrapper(Text.this, "baselineOffset") {
+                    {bind(new DoubleBinding() {
+                        {bind(fontProperty());}
+                        @Override protected double computeValue() {
+                            /* This method should never be used for spans.
+                             * If it is, it will still returns the ascent 
+                             * for the first line in the layout */
+                            BaseBounds bounds = getLogicalBounds();
+                            return -bounds.getMinY();
+                        }
+                    });}
+                };
+            }
+            return baselineOffset.getReadOnlyProperty();
+        }
+
+        @Deprecated
+        private ObjectProperty<PathElement[]> impl_selectionShape;
+        private ObjectBinding<PathElement[]> impl_selectionBinding;
+
+        @Deprecated
+        public final ReadOnlyObjectProperty<PathElement[]> impl_selectionShapeProperty() {
+            if (impl_selectionShape == null) {
+                impl_selectionBinding = new ObjectBinding<PathElement[]>() {
+                    {bind(impl_selectionStartProperty(), impl_selectionEndProperty());}
+                    @Override protected PathElement[] computeValue() {
+                        int start = getImpl_selectionStart();
+                        int end = getImpl_selectionEnd();
+                        return getRange(start, end, TextLayout.TYPE_TEXT);
+                    }
+              };
+              impl_selectionShape = new SimpleObjectProperty<PathElement[]>(Text.this, "impl_selectionShape");
+              impl_selectionShape.bind(impl_selectionBinding);
+            }
+            return impl_selectionShape;
+        }
+
+        private ObjectProperty<Paint> selectionFill;
+
+        @Deprecated
+        public final ObjectProperty<Paint> impl_selectionFillProperty() {
+            if (selectionFill == null) {
+                selectionFill = 
+                    new SimpleObjectProperty<Paint>(Text.this, "impl_selectionFill",
+                                                    DEFAULT_SELECTION_FILL) {
+                        @Override protected void invalidated() {
+                            impl_markDirty(DirtyBits.TEXT_SELECTION);
+                        }
+                    };
+            }
+            return selectionFill;
+        }
+
+        @Deprecated
+        private IntegerProperty impl_selectionStart;
+
+        @Deprecated
+        public final int getImpl_selectionStart() {
+            return impl_selectionStart == null ? DEFAULT_SELECTION_START : impl_selectionStart.get();
+        }
+
+        @Deprecated
+        public final IntegerProperty impl_selectionStartProperty() {
+            if (impl_selectionStart == null) {
+                impl_selectionStart = 
+                    new SimpleIntegerProperty(Text.this, "impl_selectionStart", DEFAULT_SELECTION_START) {
+                        @Override protected void invalidated() {
+                            impl_markDirty(DirtyBits.TEXT_SELECTION);
+                        }
+                };
+            }
+            return impl_selectionStart;
+        }
+
+        @Deprecated
+        private IntegerProperty impl_selectionEnd;
+
+        @Deprecated
+        public final int getImpl_selectionEnd() {
+            return impl_selectionEnd == null ? DEFAULT_SELECTION_END : impl_selectionEnd.get();
+        }
+
+        @Deprecated
+        public final IntegerProperty impl_selectionEndProperty() {
+            if (impl_selectionEnd == null) {
+                impl_selectionEnd = 
+                    new SimpleIntegerProperty(Text.this, "impl_selectionEnd", DEFAULT_SELECTION_END) {
+                        @Override protected void invalidated() {
+                            impl_markDirty(DirtyBits.TEXT_SELECTION);
+                        }
+                    };
+            }
+            return impl_selectionEnd;
+        }
+
+        @Deprecated
+        private ObjectProperty<PathElement[]> impl_caretShape;
+        private ObjectBinding<PathElement[]> impl_caretBinding;
+
+        @Deprecated
+        public final ReadOnlyObjectProperty<PathElement[]> impl_caretShapeProperty() {
+            if (impl_caretShape == null) {
+                impl_caretBinding = new ObjectBinding<PathElement[]>() {
+                    {bind(impl_caretPositionProperty(), impl_caretBiasProperty());}
+                    @Override protected PathElement[] computeValue() {
+                        int pos = getImpl_caretPosition();
+                        int length = getTextInternal().length();
+                        if (0 <= pos && pos <= length) {
+                            boolean bias = isImpl_caretBias();
+                            float x = (float)getX();
+                            float y = (float)getY() - getYRendering();
+                            TextLayout layout = getTextLayout();
+                            return layout.getCaretShape(pos, bias, x, y);
+                        }
+                        return new PathElement[0];
+                    }
+                };
+                impl_caretShape = new SimpleObjectProperty<PathElement[]>(Text.this, "impl_caretShape");
+                impl_caretShape.bind(impl_caretBinding);
+            }
+            return impl_caretShape;
+        }
+        
+        @Deprecated
+        private IntegerProperty impl_caretPosition;
+
+        @Deprecated
+        public final int getImpl_caretPosition() {
+            return impl_caretPosition == null ? DEFAULT_CARET_POSITION : impl_caretPosition.get();
+        }
+
+        @Deprecated
+        public final IntegerProperty impl_caretPositionProperty() {
+            if (impl_caretPosition == null) {
+                impl_caretPosition =
+                        new SimpleIntegerProperty(Text.this, "impl_caretPosition", DEFAULT_CARET_POSITION);
+            }
+            return impl_caretPosition;
+        }
+        
+        @Deprecated
+        private BooleanProperty impl_caretBias;
+
+        @Deprecated
+        public final boolean isImpl_caretBias() {
+            return impl_caretBias == null ? DEFAULT_CARET_BIAS : impl_caretBias.get();
+        }
+
+        @Deprecated
+        public final BooleanProperty impl_caretBiasProperty() {
+            if (impl_caretBias == null) {
+                impl_caretBias =
+                        new SimpleBooleanProperty(Text.this, "impl_caretBias", DEFAULT_CARET_BIAS);
+            }
+            return impl_caretBias;
+        }
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/src/javafx/scene/text/TextFlow.java	Tue Nov 27 10:45:21 2012 -0800
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javafx.scene.text;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.value.WritableValue;
+import javafx.geometry.HPos;
+import javafx.geometry.Insets;
+import javafx.geometry.Orientation;
+import javafx.geometry.VPos;
+import javafx.scene.Node;
+import javafx.scene.layout.Pane;
+
+import com.sun.javafx.css.StyleableObjectProperty;
+import com.sun.javafx.css.StyleableProperty;
+import com.sun.javafx.css.converters.EnumConverter;
+import com.sun.javafx.geom.BaseBounds;
+import com.sun.javafx.geom.Point2D;
+import com.sun.javafx.geom.RectBounds;
+import com.sun.javafx.scene.text.GlyphList;
+import com.sun.javafx.scene.text.TextLayout;
+import com.sun.javafx.scene.text.TextLayoutFactory;
+import com.sun.javafx.scene.text.TextSpan;
+import com.sun.javafx.tk.Toolkit;
+
+/**
+ * TextFlow is special layout designed to lay out rich text.
+ * It can be used to layout several {@link Text} nodes in a single text flow.
+ * The TextFlow uses the text and the font of each {@link Text} node inside of it
+ * plus it own width and text alignment to determine the location for each child.
+ * A single {@link Text} node can span over several lines due to wrapping and
+ * the visual location of {@link Text} node can differ from the logical location
+ * due to bidi reordering.
+ * 
+ * <p>
+ * Any other Node, rather than Text, will be treated as embedded object in the 
+ * text layout. It will be inserted in the content using its preferred width,
+ * height, and baseline offset.
+ * 
+ * <p>
+ * When a {@link Text} node is inside of a TextFlow some its properties are ignored.
+ * For example, the x and y properties of the {@link Text} node are ignored since
+ * the location of the node is determined by the parent. Likewise, the wrapping
+ * width in the {@link Text} node is ignored since the width used for wrapping
+ * is the TextFlow's width.
+ * 
+ * <p>
+ * The wrapping width of the layout is determined by the region's current width.
+ * It can be specified by the application by setting the textflow's preferred
+ * width. If no wrapping is desired, the application can either set the preferred
+ * with to Double.MAX_VALUE or Region.USE_COMPUTED_SIZE.
+ *
+ * <p>
+ * Paragraphs are separated by {@code '\n'} present in any Text child.
+ *
+ * <p>
+ * Example of a TextFlow:
+ * <pre><code>
+ *     Text text1 = new Text("Big italic red text");
+ *     text1.setFill(Color.RED);
+ *     text1.setFont(Font.font("Helvetica", FontPosture.ITALIC, 40));
+ *     Text text2 = new Text(" little bold blue text");
+ *     text2.setFill(Color.BLUE);
+ *     text2.setFont(Font.font("Helvetica", FontWeight.BOLD, 10));
+ *     TextFlow textFlow = new TextFlow(text1, text2);
+ * </code></pre>
+ *
+ * <p>
+ * TextFlow lays out each managed child regardless of the child's visible property value;
+ * unmanaged children are ignored for all layout calculations.</p>
+ *
+ * <p>
+ * TextFlow may be styled with backgrounds and borders using CSS.  See
+ * {@link javafx.scene.layout.Region Region} superclass for details.</p>
+ *
+ * <h4>Resizable Range</h4>
+ *
+ * A textflow's parent will resize the textflow within the textflow's range
+ * during layout. By default the textflow computes this range based on its content
+ * as outlined in the tables below.
+ * <p>
+ * <table border="1">
+ * <tr><td></td><th>width</th><th>height</th></tr>
+ * <tr><th>minimum</th>
+ * <td>left/right insets</td>
+ * <td>top/bottom insets</td></tr>
+ * <tr><th>preferred</th>
+ * <td>left/right insets plus the width of the text content</td>
+ * <td>top/bottom insets plus the height of the text content</td></tr>
+ * <tr><th>maximum</th>
+ * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr>
+ * </table>
+ * <p>
+ * A textflow's unbounded maximum width and height are an indication to the parent that
+ * it may be resized beyond its preferred size to fill whatever space is assigned to it.
+ * <p>
+ * TextFlow provides properties for setting the size range directly.  These
+ * properties default to the sentinel value Region.USE_COMPUTED_SIZE, however the
+ * application may set them to other values as needed:
+ * <pre><code>
+ *     <b>textflow.setMaxWidth(500);</b>
+ * </code></pre>
+ * Applications may restore the computed values by setting these properties back
+ * to Region.USE_COMPUTED_SIZE.
+ * <p>
+ * TextFlow does not clip its content by default, so it is possible that childrens'
+ * bounds may extend outside its own bounds if a child's pref size is larger than
+ * the space textflow has to allocate for it.</p>
+ *
+ * @since 8.0
+ */
+public class TextFlow extends Pane {
+
+    private TextLayout layout;
+    private boolean needsContent;
+    private boolean inLayout;
+
+    /**
+     * Creates an empty TextFlow layout.
+     */
+    public TextFlow() {
+        super();
+    }
+
+    /**
+     * Creates a TextFlow layout with the given children.
+     * 
+     * @param children children.
+     */
+    public TextFlow(Node... children) {
+        this();
+        getChildren().addAll(children);
+    }
+
+    @Override protected void setWidth(double value) {
+        if (value != getWidth()) {
+            TextLayout layout = getTextLayout();
+            Insets insets = getInsets();
+            double left = snapSpace(insets.getLeft());
+            double right = snapSpace(insets.getRight());
+            double width = Math.max(1, value - left - right);
+            layout.setWrapWidth((float)width);
+            super.setWidth(value);
+        }
+    }
+
+    @Override protected double computePrefWidth(double height) {
+        TextLayout layout = getTextLayout();
+        layout.setWrapWidth(0);
+        double width = layout.getBounds().getWidth();
+        Insets insets = getInsets();
+        double left = snapSpace(insets.getLeft());
+        double right = snapSpace(insets.getRight());
+        double wrappingWidth = Math.max(1, getWidth() - left - right);
+        layout.setWrapWidth((float)wrappingWidth);
+        return left + width + right;
+    }
+
+    @Override protected double computePrefHeight(double width) {
+        TextLayout layout = getTextLayout();
+        Insets insets = getInsets();
+        double left = snapSpace(insets.getLeft());
+        double right = snapSpace(insets.getRight());
+        if (width == USE_COMPUTED_SIZE) {
+            layout.setWrapWidth(0);
+        } else {
+            double wrappingWidth = Math.max(1, width - left - right);
+            layout.setWrapWidth((float)wrappingWidth);
+        }
+        double height = layout.getBounds().getHeight();
+        double wrappingWidth = Math.max(1, getWidth() - left - right);
+        layout.setWrapWidth((float)wrappingWidth);
+        double top = snapSpace(insets.getTop());
+        double bottom = snapSpace(insets.getBottom());
+        return top + height + bottom;
+    }
+
+    @Override public void requestLayout() {
+        /* The geometry of text nodes can be changed during layout children.
+         * For that reason it has to call impl_geomChanged() causing
+         * requestLayout() to happen during layoutChildren().
+         * The inLayout flag prevents this call to cause any extra work.
+         */
+        if (inLayout) return;
+
+        /*
+        * There is no need to reset the text layout's content every time
+        * requestLayout() is called. For example, the content needs
+        * to be set when:
+        *  children add or removed
+        *  children managed state changes
+        *  children geomChanged (width/height of embedded node)
+        *  children content changes (text/font of text node)
+        * The content does not need to set when:
+        *  the width/height changes in the region
+        *  the insets changes in the region
+        * 
+        * Unfortunately, it is not possible to know what change invoked request
+        * layout. The solution is to always reset the content in the text
+        * layout and rely on it to preserve itself if the new content equals to
+        * the old one. The cost to generate the new content is not avoid.
+        */
+        needsContent = true;
+        super.requestLayout();
+    }
+
+    @Override public Orientation getContentBias() {
+        return Orientation.HORIZONTAL;
+    }
+
+    @Override protected void layoutChildren() {
+        inLayout = true;
+        Insets insets = getInsets();
+        double top = snapSpace(insets.getTop());
+        double left = snapSpace(insets.getLeft());
+
+        GlyphList[] runs = getTextLayout().getRuns();
+        for (int j = 0; j < runs.length; j++) {
+            GlyphList run = runs[j];
+            TextSpan span = run.getTextSpan();
+            if (span instanceof EmbeddedSpan) {
+                Node child = ((EmbeddedSpan)span).getNode();
+                Point2D location = run.getLocation();
+                double baselineOffset = -run.getLineBounds().getMinY();
+
+                layoutInArea(child, left + location.x, top + location.y,
+                             run.getWidth(), run.getHeight(),
+                             baselineOffset, null, true, true,
+                             HPos.CENTER, VPos.BASELINE);
+            }
+        }
+
+        List<Node> managed = getManagedChildren();
+        for (Node node: managed) {
+            if (node instanceof Text) {
+                Text text = (Text)node;
+                text.layoutSpan(runs);
+                BaseBounds spanBounds = text.getSpanBounds();
+                text.relocate(left + spanBounds.getMinX(),
+                              top + spanBounds.getMinY());
+            }
+        }
+        inLayout = false;
+    }
+
+    private static class EmbeddedSpan implements TextSpan {
+        RectBounds bounds;
+        Node node;
+        public EmbeddedSpan(Node node, double baseline, double width, double height) {
+            this.node = node;
+            bounds = new RectBounds(0, (float)-baseline,
+                                    (float)width, (float)(height - baseline));
+        }
+
+        @Override public String getText() {
+            return "\uFFFC";
+        }
+
+        @Override public Object getFont() {
+            return null;
+        }
+
+        @Override public RectBounds getBounds() {
+            return bounds;
+        }
+
+        public Node getNode() {
+            return node;
+        }
+    }
+
+    TextLayout getTextLayout() {
+        if (layout == null) {
+            TextLayoutFactory factory = Toolkit.getToolkit().getTextLayoutFactory();
+            layout = factory.createLayout();
+            needsContent = true;
+        }
+        if (needsContent) {
+            List<Node> children = getManagedChildren();
+            TextSpan[] spans = new TextSpan[children.size()];
+            for (int i = 0; i < spans.length; i++) {
+                Node node = children.get(i);
+                if (node instanceof Text) {
+                    spans[i] = ((Text)node).getTextSpan();
+                } else {
+                    /* Creating a text span every time forces text layout
+                     * to run a full text analysis in the new content.
+                     */
+                    double baseline = node.getBaselineOffset();
+                    double width = computeChildPrefAreaWidth(node, null);
+                    double height = computeChildPrefAreaHeight(node, null);
+                    spans[i] = new EmbeddedSpan(node, baseline, width, height);
+                }
+            }
+            layout.setContent(spans);
+            needsContent = false;
+        }
+        return layout;
+    }
+
+    /**
+     * Defines horizontal text alignment.
+     *
+     * @defaultValue TextAlignment.LEFT
+     */
+    private ObjectProperty<TextAlignment> textAlignment;
+
+    public final void setTextAlignment(TextAlignment value) {
+        textAlignmentProperty().set(value);
+    }
+
+    public final TextAlignment getTextAlignment() {
+        return textAlignment == null ? TextAlignment.LEFT : textAlignment.get();
+    }
+
+    public final ObjectProperty<TextAlignment> textAlignmentProperty() {
+        if (textAlignment == null) {
+            textAlignment =
+                new StyleableObjectProperty<TextAlignment>(TextAlignment.LEFT) {
+                @Override public Object getBean() { return TextFlow.this; }
+                @Override public String getName() { return "textAlignment"; }
+                @Override public StyleableProperty getStyleableProperty() {
+                    return StyleableProperties.TEXT_ALIGNMENT;
+                }
+                @Override public void invalidated() {
+                    TextAlignment align = get();
+                    if (align == null) align = TextAlignment.LEFT;
+                    TextLayout layout = getTextLayout();
+                    layout.setAlignment(align.ordinal());
+                    requestLayout();
+                }
+            };
+        }
+        return textAlignment;
+    }
+
+    @Override public final double getBaselineOffset() {
+        Insets insets = getInsets();
+        double top = snapSpace(insets.getTop());
+        return top - getTextLayout().getBounds().getMinY();
+    }
+
+   /***************************************************************************
+    *                                                                         *
+    *                            Stylesheet Handling                          *
+    *                                                                         *
+    **************************************************************************/
+
+     /**
+      * Super-lazy instantiation pattern from Bill Pugh.
+      * @treatAsPrivate implementation detail
+      */
+     private static class StyleableProperties {
+         
+         private static final
+             StyleableProperty<TextFlow, TextAlignment> TEXT_ALIGNMENT =
+                 new StyleableProperty<TextFlow,TextAlignment>("-fx-text-alignment",
+                 new EnumConverter<TextAlignment>(TextAlignment.class),
+                 TextAlignment.LEFT) {
+
+            @Override
+            public boolean isSettable(TextFlow node) {
+                return node.textAlignment == null ||
+                      !node.textAlignment.isBound();
+            }
+
+            @Override
+            public WritableValue<TextAlignment> getWritableValue(TextFlow node) {
+                return node.textAlignmentProperty();
+            }
+         };
+
+         private static final List<StyleableProperty> STYLEABLES;
+         static {
+            final List<StyleableProperty> styleables =
+                new ArrayList<StyleableProperty>(Pane.impl_CSS_STYLEABLES());
+            Collections.addAll(styleables, TEXT_ALIGNMENT);
+            STYLEABLES = Collections.unmodifiableList(styleables);
+         }
+    }
+
+    /**
+     * Super-lazy instantiation pattern from Bill Pugh.
+     * StyleableProperties is referenced  no earlier
+     * (and therefore loaded no earlier by the class loader) than
+     * the moment that  impl_CSS_STYLEABLES() is called.
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
+     */
+    @Deprecated
+    public static List<StyleableProperty> impl_CSS_STYLEABLES() {
+        return TextFlow.StyleableProperties.STYLEABLES;
+    }
+
+    /**
+     * RT-19263
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
+     */
+    @Deprecated
+    public List<StyleableProperty> impl_getStyleableProperties() {
+        return impl_CSS_STYLEABLES();
+    }
+
+    /* The methods in this section are copied from Region due to package visibility restriction */
+    private static double snapSpace(double value, boolean snapToPixel) {
+        return snapToPixel ? Math.round(value) : value;
+    }
+
+    static double boundedSize(double min, double pref, double max) {
+        double a = pref >= min ? pref : min;
+        double b = min >= max ? min : max;
+        return a <= b ? a : b;
+    }
+
+    double computeChildPrefAreaWidth(Node child, Insets margin) {
+        return computeChildPrefAreaWidth(child, margin, -1);
+    }
+
+    double computeChildPrefAreaWidth(Node child, Insets margin, double height) {
+        final boolean snap = isSnapToPixel();
+        double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
+        double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
+        double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
+        double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
+        double alt = -1;
+        if (child.getContentBias() == Orientation.VERTICAL) { // width depends on height
+            alt = snapSize(boundedSize(
+                    child.minHeight(-1), height != -1? height - top - bottom :
+                           child.prefHeight(-1), child.maxHeight(-1)));
+        }
+        return left + snapSize(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
+    }
+
+    double computeChildPrefAreaHeight(Node child, Insets margin) {
+        return computeChildPrefAreaHeight(child, margin, -1);
+    }
+
+    double computeChildPrefAreaHeight(Node child, Insets margin, double width) {
+        final boolean snap = isSnapToPixel();
+        double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
+        double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
+        double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
+        double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
+        double alt = -1;
+        if (child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
+            alt = snapSize(boundedSize(
+                    child.minWidth(-1), width != -1? width - left - right :
+                           child.prefWidth(-1), child.maxWidth(-1)));
+        }
+        return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
+    }
+    /* end of copied code */
+
+}
--- a/javafx-ui-common/src/javafx/scene/transform/Transform.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/javafx/scene/transform/Transform.java	Tue Nov 27 10:45:21 2012 -0800
@@ -1236,19 +1236,16 @@
      * @since JavaFX 8.0
      */
     public Bounds transform(Bounds bounds) {
-        final Point3D base = new Point3D(
+        final Point3D base = transform(
                 bounds.getMinX(),
                 bounds.getMinY(),
                 bounds.getMinZ());
-        final Point3D size = new Point3D(
+        final Point3D size = deltaTransform(
                 bounds.getWidth(),
                 bounds.getHeight(),
                 bounds.getDepth());
-
-        final Point3D tbase = transform(base);
-        final Point3D tsize = deltaTransform(size);
-        return new BoundingBox(tbase.getX(), tbase.getY(), tbase.getZ(),
-                tsize.getX(), tsize.getY(), tsize.getZ());
+        return new BoundingBox(base.getX(), base.getY(), base.getZ(),
+                size.getX(), size.getY(), size.getZ());
     }
 
     /**
@@ -1536,19 +1533,17 @@
      */
     public Bounds inverseTransform(Bounds bounds)
             throws NonInvertibleTransformException {
-        Point3D base = new Point3D(
+
+        Point3D base = inverseTransform(
                 bounds.getMinX(),
                 bounds.getMinY(),
                 bounds.getMinZ());
-        Point3D size = new Point3D(
+        Point3D size = inverseDeltaTransform(
                 bounds.getWidth(),
                 bounds.getHeight(),
                 bounds.getDepth());
-
-        Point3D tbase = inverseTransform(base);
-        Point3D tsize = inverseDeltaTransform(size);
-        return new BoundingBox(tbase.getX(), tbase.getY(), tbase.getZ(),
-                tsize.getX(), tsize.getY(), tsize.getZ());
+        return new BoundingBox(base.getX(), base.getY(), base.getZ(),
+                size.getX(), size.getY(), size.getZ());
     }
 
     /**
--- a/javafx-ui-common/src/javafx/scene/transform/TransformChangedEvent.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/src/javafx/scene/transform/TransformChangedEvent.java	Tue Nov 27 10:45:21 2012 -0800
@@ -33,7 +33,7 @@
 /**
  * This event is fired on a transform when any of its properties changes.
  */
-public class TransformChangedEvent extends Event {
+public final class TransformChangedEvent extends Event {
 
     private static final long serialVersionUID = 20121107L;
     
--- a/javafx-ui-common/test/unit/com/sun/javafx/image/ConverterTest.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/test/unit/com/sun/javafx/image/ConverterTest.java	Tue Nov 27 10:45:21 2012 -0800
@@ -45,6 +45,42 @@
 /**
  */
 public class ConverterTest {
+    static ByteBuffer heapByteBuffer(int off, int len) {
+        ByteBuffer bbuf = ByteBuffer.allocate(off + len);
+        if (off > 0) {
+            bbuf.position(off);
+            bbuf = bbuf.slice();
+        }
+        return bbuf;
+    }
+
+    static ByteBuffer directByteBuffer(int off, int len) {
+        ByteBuffer bbuf = ByteBuffer.allocateDirect(off + len);
+        if (off > 0) {
+            bbuf.position(off);
+            bbuf = bbuf.slice();
+        }
+        return bbuf;
+    }
+
+    static IntBuffer heapIntBuffer(int off, int len) {
+        IntBuffer ibuf = IntBuffer.allocate(off + len);
+        if (off > 0) {
+            ibuf.position(off);
+            ibuf = ibuf.slice();
+        }
+        return ibuf;
+    }
+
+    static IntBuffer directIntBuffer(int off, int len) {
+        IntBuffer ibuf = ByteBuffer.allocateDirect((off + len) * 4).asIntBuffer();
+        if (off > 0) {
+            ibuf.position(off);
+            ibuf = ibuf.slice();
+        }
+        return ibuf;
+    }
+
     static Color derive(Color c, double opacity) {
         return new Color(c.getRed(), c.getGreen(), c.getBlue(), c.getOpacity() * opacity);
     }
@@ -501,7 +537,13 @@
 
     @Test
     public void testByteAccessors() {
-        ByteBuffer bbuf = ByteBuffer.allocate(8);
+        testByteAccessors(heapByteBuffer(0, 8));
+        testByteAccessors(heapByteBuffer(1, 8));
+        testByteAccessors(directByteBuffer(0, 8));
+        testByteAccessors(directByteBuffer(1, 8));
+    }
+
+    private void testByteAccessors(ByteBuffer bbuf) {
         byte barr[] = new byte[8];
         for (ByteFormat bfmt : ByteFormats) {
             BytePixelGetter getter = bfmt.getGetter();
@@ -524,7 +566,13 @@
 
     @Test
     public void testIntAccessors() {
-        IntBuffer ibuf = IntBuffer.allocate(2);
+        testIntAccessors(heapIntBuffer(0, 2));
+        testIntAccessors(heapIntBuffer(1, 2));
+        testIntAccessors(directIntBuffer(0, 2));
+        testIntAccessors(directIntBuffer(1, 2));
+    }
+    
+    private void testIntAccessors(IntBuffer ibuf) {
         int iarr[] = new int[2];
         for (IntFormat ifmt : IntFormats) {
             IntPixelGetter getter = ifmt.getGetter();
@@ -575,11 +623,16 @@
 
     @Test
     public void testB2BConverterTypes() {
-        ByteBuffer srchbuf = ByteBuffer.allocate(4 * TestColors.length);
-        ByteBuffer srcdbuf = ByteBuffer.allocateDirect(4 * TestColors.length);
+        testB2BConverterTypes(0);
+        testB2BConverterTypes(1);
+    }
+    
+    private void testB2BConverterTypes(int off) {
+        ByteBuffer srchbuf = heapByteBuffer(off, 4 * TestColors.length);
+        ByteBuffer srcdbuf = directByteBuffer(off, 4 * TestColors.length);
         byte srcarr[] = new byte[4 * TestColors.length];
-        ByteBuffer dsthbuf = ByteBuffer.allocate(4 * TestColors.length);
-        ByteBuffer dstdbuf = ByteBuffer.allocateDirect(4 * TestColors.length);
+        ByteBuffer dsthbuf = heapByteBuffer(off, 4 * TestColors.length);
+        ByteBuffer dstdbuf = directByteBuffer(off, 4 * TestColors.length);
         byte dstarr[] = new byte[4 * TestColors.length];
         for (ByteFormat bfmtgetter : ByteFormats) {
             BytePixelGetter bpg = bfmtgetter.getGetter();
@@ -649,11 +702,16 @@
 
     @Test
     public void testB2IConverterTypes() {
-        ByteBuffer srchbuf = ByteBuffer.allocate(4 * TestColors.length);
-        ByteBuffer srcdbuf = ByteBuffer.allocateDirect(4 * TestColors.length);
+        testB2IConverterTypes(0);
+        testB2IConverterTypes(1);
+    }
+
+    private void testB2IConverterTypes(int off) {
+        ByteBuffer srchbuf = heapByteBuffer(off, 4 * TestColors.length);
+        ByteBuffer srcdbuf = directByteBuffer(off, 4 * TestColors.length);
         byte srcarr[] = new byte[4 * TestColors.length];
-        IntBuffer dsthbuf = IntBuffer.allocate(TestColors.length);
-        IntBuffer dstdbuf = IntBuffer.allocate(TestColors.length);
+        IntBuffer dsthbuf = heapIntBuffer(off, TestColors.length);
+        IntBuffer dstdbuf = directIntBuffer(off,TestColors.length);
         int dstarr[] = new int[TestColors.length];
         for (ByteFormat bfmtgetter : ByteFormats) {
             BytePixelGetter bpg = bfmtgetter.getGetter();
@@ -722,11 +780,16 @@
 
     @Test
     public void testI2BConverterTypes() {
-        IntBuffer srchbuf = IntBuffer.allocate(TestColors.length);
-        IntBuffer srcdbuf = IntBuffer.allocate(TestColors.length);
+        testI2BConverterTypes(0);
+        testI2BConverterTypes(1);
+    }
+
+    private void testI2BConverterTypes(int off) {
+        IntBuffer srchbuf = heapIntBuffer(off, TestColors.length);
+        IntBuffer srcdbuf = directIntBuffer(off, TestColors.length);
         int srcarr[] = new int[TestColors.length];
-        ByteBuffer dsthbuf = ByteBuffer.allocate(4 * TestColors.length);
-        ByteBuffer dstdbuf = ByteBuffer.allocateDirect(4 * TestColors.length);
+        ByteBuffer dsthbuf = heapByteBuffer(off, 4 * TestColors.length);
+        ByteBuffer dstdbuf = directByteBuffer(off, 4 * TestColors.length);
         byte dstarr[] = new byte[4 * TestColors.length];
         for (IntFormat ifmtgetter : IntFormats) {
             IntPixelGetter ipg = ifmtgetter.getGetter();
@@ -795,11 +858,16 @@
 
     @Test
     public void testI2IConverterTypes() {
-        IntBuffer srchbuf = IntBuffer.allocate(TestColors.length);
-        IntBuffer srcdbuf = IntBuffer.allocate(TestColors.length);
+        testI2IConverterTypes(0);
+        testI2IConverterTypes(1);
+    }
+
+    private void testI2IConverterTypes(int off) {
+        IntBuffer srchbuf = heapIntBuffer(off, TestColors.length);
+        IntBuffer srcdbuf = directIntBuffer(off, TestColors.length);
         int srcarr[] = new int[TestColors.length];
-        IntBuffer dsthbuf = IntBuffer.allocate(TestColors.length);
-        IntBuffer dstdbuf = IntBuffer.allocate(TestColors.length);
+        IntBuffer dsthbuf = heapIntBuffer(off, TestColors.length);
+        IntBuffer dstdbuf = directIntBuffer(off, TestColors.length);
         int dstarr[] = new int[TestColors.length];
         for (IntFormat ifmtgetter : IntFormats) {
             IntPixelGetter ipg = ifmtgetter.getGetter();
--- a/javafx-ui-common/test/unit/com/sun/javafx/scene/transform/TransformUtilsTest.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/test/unit/com/sun/javafx/scene/transform/TransformUtilsTest.java	Tue Nov 27 10:45:21 2012 -0800
@@ -27,6 +27,8 @@
 import com.sun.javafx.test.TransformHelper;
 import javafx.scene.transform.Transform;
 import com.sun.javafx.geom.transform.Affine3D;
+import javafx.scene.transform.TransformOperationsTest;
+import javafx.scene.transform.Translate;
 import static org.junit.Assert.*;
 
 import org.junit.Test;
@@ -90,4 +92,136 @@
         assertFalse(s.isEmpty());
     }
 
+    @Test public void testImmutableTransformState() {
+        int counter = 0;
+        for (Object o : TransformOperationsTest.getParams()) {
+            Object[] arr = (Object[]) o;
+            if (arr[0] instanceof TransformUtils.ImmutableTransform) {
+                TransformUtils.ImmutableTransform t =
+                        (TransformUtils.ImmutableTransform) arr[0];
+
+                TransformHelper.assertStateOk("Checking state of transform #" +
+                        (counter++) + " of TransformOperationsTest", t,
+                        t.getState3d(), t.getState2d());
+            }
+        }
+    }
+
+    @Test public void testReusedImmutableTransform() {
+        int counter = 0;
+        for (Object o : TransformOperationsTest.getParams()) {
+            Object[] arr = (Object[]) o;
+            if (arr[0] instanceof TransformUtils.ImmutableTransform) {
+
+                Transform t = (Transform) arr[0];
+
+
+                // reusing
+                Transform reuse = TransformUtils.immutableTransform(
+                        new Translate(10, 20));
+
+                Transform returned = TransformUtils.immutableTransform(reuse, t);
+
+                assertSame("Checking reusing immutable transform to values of #"
+                        + counter + " of TransformOperationsTest", reuse, returned);
+
+                TransformHelper.assertStateOk(
+                        "Checking reusing immutable transform to values of #"
+                        + counter + " of TransformOperationsTest",
+                        (TransformUtils.ImmutableTransform) returned,
+                        ((TransformUtils.ImmutableTransform) returned).getState3d(),
+                        ((TransformUtils.ImmutableTransform) returned).getState2d());
+
+                TransformHelper.assertMatrix("Checking reusing immutable "
+                        + "transform to values of #" + counter
+                        + " of TransformOperationsTest", returned, t);
+
+                // creating new
+                Transform returned2 = TransformUtils.immutableTransform(null, t);
+
+                assertNotSame("Checking reusing immutable transform to values of #"
+                        + counter + " of TransformOperationsTest", returned2, t);
+
+                TransformHelper.assertStateOk(
+                        "Checking reusing immutable transform to values of #"
+                        + counter + " of TransformOperationsTest",
+                        (TransformUtils.ImmutableTransform) returned2,
+                        ((TransformUtils.ImmutableTransform) returned2).getState3d(),
+                        ((TransformUtils.ImmutableTransform) returned2).getState2d());
+
+                TransformHelper.assertMatrix("Checking reusing immutable "
+                        + "transform to values of #" + counter
+                        + " of TransformOperationsTest", returned2, t);
+
+                counter++;
+            }
+        }
+    }
+
+    @Test public void testConcatenatedImmutableTransform() {
+        int outer = 0;
+        for (Object o : TransformOperationsTest.getParams()) {
+            int inner = 0;
+            Object[] arr = (Object[]) o;
+            if (arr[0] instanceof TransformUtils.ImmutableTransform) {
+                TransformUtils.ImmutableTransform t1 =
+                        (TransformUtils.ImmutableTransform) arr[0];
+
+                for (Object o2 : TransformOperationsTest.getParams()) {
+                    Object[] arr2 = (Object[]) o2;
+                    if (arr2[0] instanceof TransformUtils.ImmutableTransform) {
+                        TransformUtils.ImmutableTransform t2 =
+                                (TransformUtils.ImmutableTransform) arr2[0];
+
+                        // reusing
+                        Transform clone = t1.clone();
+                        Transform conc = TransformUtils.immutableTransform(
+                                clone, t1, t2);
+
+                        assertSame("Checking state of concatenation of "
+                                + "transform #" + outer + " and #" + inner +
+                                " of TransformOperationsTest", clone, conc);
+                        TransformHelper.assertStateOk(
+                                "Checking state of concatenation of "
+                                + "transform #" + outer + " and #" + inner +
+                                " of TransformOperationsTest",
+                                (TransformUtils.ImmutableTransform) conc,
+                                ((TransformUtils.ImmutableTransform) conc).getState3d(),
+                                ((TransformUtils.ImmutableTransform) conc).getState2d());
+                        TransformHelper.assertMatrix(
+                                "Checking state of concatenation of "
+                                + "transform #" + outer + " and #" + inner +
+                                " of TransformOperationsTest", conc,
+                                TransformHelper.concatenate(t1, t2));
+
+                        // creating new
+                        Transform conc2 = TransformUtils.immutableTransform(
+                                null, t1, t2);
+
+                        assertNotSame("Checking state of concatenation of "
+                                + "transform #" + outer + " and #" + inner +
+                                " of TransformOperationsTest", conc2, t1);
+                        assertNotSame("Checking state of concatenation of "
+                                + "transform #" + outer + " and #" + inner +
+                                " of TransformOperationsTest", conc2, t2);
+                        TransformHelper.assertStateOk(
+                                "Checking state of concatenation of "
+                                + "transform #" + outer + " and #" + inner +
+                                " of TransformOperationsTest",
+                                (TransformUtils.ImmutableTransform) conc2,
+                                ((TransformUtils.ImmutableTransform) conc2).getState3d(),
+                                ((TransformUtils.ImmutableTransform) conc2).getState2d());
+                        TransformHelper.assertMatrix(
+                                "Checking state of concatenation of "
+                                + "transform #" + outer + " and #" + inner +
+                                " of TransformOperationsTest", conc2,
+                                TransformHelper.concatenate(t1, t2));
+                        inner++;
+                    }
+                }
+            }
+            outer++;
+        }
+    }
+
 }
--- a/javafx-ui-common/test/unit/com/sun/javafx/test/TransformHelper.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/test/unit/com/sun/javafx/test/TransformHelper.java	Tue Nov 27 10:45:21 2012 -0800
@@ -42,7 +42,7 @@
 /**
  * Helper class for transform tests.
  */
-public class TransformHelper {
+public final class TransformHelper {
 
     /**
      * Asserts the {@code matrix} equals to the specified expected values
@@ -457,4 +457,235 @@
 
         return true;
     }
+
+    private static enum State2D {
+        IDENTITY(0),
+        TRANSLATE(1),
+        SCALE(2),
+        SC_TR(3),
+        SHEAR(4),
+        SH_TR(5),
+        SH_SC(6),
+        SH_SC_TR(7);
+
+        private int value;
+
+        private State2D(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return value;
+        }
+    }
+
+    private static enum State3D {
+        NON_3D(0),
+        TRANSLATE(1),
+        SCALE(2),
+        SC_TR(3),
+        COMPLEX(4);
+
+        private int value;
+
+        private State3D(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return value;
+        }
+    }
+
+    private static State2D getExpectedState2D(Transform t) {
+        if (t.getMxy() == 0.0 && t.getMyx() == 0.0) {
+            if (t.getMxx() == 1.0 && t.getMyy() == 1.0) {
+                if (t.getTx() == 0.0 && t.getTy() == 0.0) {
+                    return State2D.IDENTITY;
+                } else {
+                    return State2D.TRANSLATE;
+                }
+            } else {
+                if (t.getTx() == 0.0 && t.getTy() == 0.0) {
+                    return State2D.SCALE;
+                } else {
+                    return State2D.SC_TR;
+                }
+            }
+        } else {
+            if (t.getMxx() == 0.0 && t.getMyy() == 0.0) {
+                if (t.getTx() == 0.0 && t.getTy() == 0.0) {
+                    return State2D.SHEAR;
+                } else {
+                    return State2D.SH_TR;
+                }
+            } else {
+                if (t.getTx() == 0.0 && t.getTy() == 0.0) {
+                    return State2D.SH_SC;
+                } else {
+                    return State2D.SH_SC_TR;
+                }
+            }
+        }
+    }
+
+    private static State3D getExpectedState3D(Transform t) {
+        if (t.getMxz() == 0.0 && t.getMyz() == 0.0 &&
+                t.getMzx() == 0.0 && t.getMzy() == 0.0 &&
+                t.getMzz() == 1.0 && t.getTz() == 0.0) {
+            return State3D.NON_3D;
+        }
+
+        if (t.getMxy() != 0.0 || t.getMxz() != 0.0 ||
+                t.getMyx() != 0.0 || t.getMyz() != 0.0 ||
+                t.getMzx() != 0.0 || t.getMzy() != 0.0) {
+            return State3D.COMPLEX;
+        }
+
+        if ((t.getMxx() != 1.0 || t.getMyy() != 1.0 || t.getMzz() != 1.0) &&
+                (t.getTx() != 0.0 || t.getTy() != 0.0 || t.getTz() != 0.0)) {
+            return State3D.SC_TR;
+        }
+
+        if (t.getMxx() != 1.0 || t.getMyy() != 1.0 || t.getMzz() != 1.0) {
+            return State3D.SCALE;
+        }
+        if (t.getTx() != 0.0 || t.getTy() != 0.0 || t.getTz() != 0.0) {
+            return State3D.TRANSLATE;
+        }
+        return null;
+    }
+
+    public static void assertStateOk(Transform t, int state3d, int state2d) {
+        TransformHelper.State3D expectedState3D = TransformHelper.getExpectedState3D(t);
+        assertEquals(expectedState3D.getValue(), state3d);
+        if (expectedState3D == TransformHelper.State3D.NON_3D) {
+            assertEquals(TransformHelper.getExpectedState2D(t).getValue(), state2d);
+        }
+    }
+
+    public static void assertStateOk(String message, Transform t, int state3d, int state2d) {
+        TransformHelper.State3D expectedState3D = TransformHelper.getExpectedState3D(t);
+        assertEquals(message, expectedState3D.getValue(), state3d);
+        if (expectedState3D == TransformHelper.State3D.NON_3D) {
+            assertEquals(message, TransformHelper.getExpectedState2D(t).getValue(), state2d);
+        }
+    }
+
+    /**
+     * Convenient factory for 2D immutable transforms.
+     */
+    public static Transform immutableTransform(
+            double mxx, double mxy, double tx,
+            double myx, double myy, double ty) {
+        return TransformUtils.immutableTransform(
+                mxx, mxy, 0.0, tx,
+                myx, myy, 0.0, ty,
+                0.0, 0.0, 1.0, 0.0);
+    }
+
+    /**
+     * Creates a raw transformation to test the Transform class.
+     */
+    public static Transform rawTransform(
+                double mxx, double mxy, double mxz, double tx,
+                double myx, double myy, double myz, double ty,
+                double mzx, double mzy, double mzz, double tz) {
+        return new RawTransform(
+                mxx, mxy, mxz, tx,
+                myx, myy, myz, ty,
+                mzx, mzy, mzz, tz);
+    }
+
+    private static class RawTransform extends Transform {
+        private final double mxx, mxy, mxz, tx;
+        private final double myx, myy, myz, ty;
+        private final double mzx, mzy, mzz, tz;
+
+        public RawTransform(
+                double mxx, double mxy, double mxz, double tx,
+                double myx, double myy, double myz, double ty,
+                double mzx, double mzy, double mzz, double tz) {
+            this.mxx = mxx;
+            this.mxy = mxy;
+            this.mxz = mxz;
+            this.tx = tx;
+            this.myx = myx;
+            this.myy = myy;
+            this.myz = myz;
+            this.ty = ty;
+            this.mzx = mzx;
+            this.mzy = mzy;
+            this.mzz = mzz;
+            this.tz = tz;
+        }
+
+        @Override
+        public double getMxx() {
+            return mxx;
+        }
+
+        @Override
+        public double getMxy() {
+            return mxy;
+        }
+
+        @Override
+        public double getMxz() {
+            return mxz;
+        }
+
+        @Override
+        public double getTx() {
+            return tx;
+        }
+
+        @Override
+        public double getMyx() {
+            return myx;
+        }
+
+        @Override
+        public double getMyy() {
+            return myy;
+        }
+
+        @Override
+        public double getMyz() {
+            return myz;
+        }
+
+        @Override
+        public double getTy() {
+            return ty;
+        }
+
+        @Override
+        public double getMzx() {
+            return mzx;
+        }
+
+        @Override
+        public double getMzy() {
+            return mzy;
+        }
+
+        @Override
+        public double getMzz() {
+            return mzz;
+        }
+
+        @Override
+        public double getTz() {
+            return tz;
+        }
+
+        @Override
+        public void impl_apply(Affine3D t) {
+            t.concatenate(
+                    getMxx(), getMxy(), getMxz(), getTx(),
+                    getMyx(), getMyy(), getMyz(), getTy(),
+                    getMzx(), getMzy(), getMzz(), getTz());
+        }
+    }
 }
--- a/javafx-ui-common/test/unit/javafx/scene/Node_LocalToParentTransform_Test.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/test/unit/javafx/scene/Node_LocalToParentTransform_Test.java	Tue Nov 27 10:45:21 2012 -0800
@@ -25,6 +25,7 @@
 package javafx.scene;
 
 import com.sun.javafx.test.TransformHelper;
+import javafx.scene.transform.Transform;
 import javafx.scene.transform.Translate;
 import javafx.scene.shape.Rectangle;
 import javafx.beans.Observable;
@@ -104,4 +105,29 @@
         n.setTranslateY(20);
         assertTrue(notified);
     }
+
+    @Test
+    public void shouldNotBeReusedWhenReferenceGivenToUser() {
+        final Node n = new Rectangle(20, 20);
+
+        n.setTranslateX(200);
+        Transform t1 = n.getLocalToParentTransform();
+        TransformHelper.assertMatrix(t1,
+                1, 0, 0, 200,
+                0, 1, 0, 0,
+                0, 0, 1, 0);
+
+        n.setTranslateX(300);
+        Transform t2 = n.getLocalToParentTransform();
+        TransformHelper.assertMatrix(t2,
+                1, 0, 0, 300,
+                0, 1, 0, 0,
+                0, 0, 1, 0);
+
+        assertFalse(t1 == t2);
+        TransformHelper.assertMatrix(t1,
+                1, 0, 0, 200,
+                0, 1, 0, 0,
+                0, 0, 1, 0);
+    }
 }
--- a/javafx-ui-common/test/unit/javafx/scene/Node_LocalToSceneTransform_Test.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/test/unit/javafx/scene/Node_LocalToSceneTransform_Test.java	Tue Nov 27 10:45:21 2012 -0800
@@ -348,4 +348,31 @@
                 0, 1, 0,  0,
                 0, 0, 1,  0);
     }
+
+    @Test
+    public void shouldNotBeReusedWhenReferenceGivenToUser() {
+
+        final Node n = new Rectangle(20, 20);
+        final Group g = new Group(n);
+
+        g.setTranslateX(200);
+        Transform t1 = n.getLocalToSceneTransform();
+        TransformHelper.assertMatrix(t1,
+                1, 0, 0, 200,
+                0, 1, 0, 0,
+                0, 0, 1, 0);
+
+        g.setTranslateX(300);
+        Transform t2 = n.getLocalToSceneTransform();
+        TransformHelper.assertMatrix(t2,
+                1, 0, 0, 300,
+                0, 1, 0, 0,
+                0, 0, 1, 0);
+
+        assertFalse(t1 == t2);
+        TransformHelper.assertMatrix(t1,
+                1, 0, 0, 200,
+                0, 1, 0, 0,
+                0, 0, 1, 0);
+    }
 }
--- a/javafx-ui-common/test/unit/javafx/scene/transform/AffineOperationsTest.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/test/unit/javafx/scene/transform/AffineOperationsTest.java	Tue Nov 27 10:45:21 2012 -0800
@@ -183,105 +183,6 @@
     private int listener2Counter;
     private double memMyx, memTy;
 
-    private static enum State2D {
-        IDENTITY(0),
-        TRANSLATE(1),
-        SCALE(2),
-        SC_TR(3),
-        SHEAR(4),
-        SH_TR(5),
-        SH_SC(6),
-        SH_SC_TR(7);
-
-        private int value;
-
-        private State2D(int value) {
-            this.value = value;
-        }
-
-        public int getValue() {
-            return value;
-        }
-    }
-
-    private static enum State3D {
-        NON_3D(0),
-        TRANSLATE(1),
-        SCALE(2),
-        SC_TR(3),
-        COMPLEX(4);
-
-        private int value;
-
-        private State3D(int value) {
-            this.value = value;
-        }
-
-        public int getValue() {
-            return value;
-        }
-    }
-
-    private State2D getExpectedState2D(Affine a) {
-        if (a.getMxy() == 0.0 && a.getMyx() == 0.0) {
-            if (a.getMxx() == 1.0 && a.getMyy() == 1.0) {
-                if (a.getTx() == 0.0 && a.getTy() == 0.0) {
-                    return State2D.IDENTITY;
-                } else {
-                    return State2D.TRANSLATE;
-                }
-            } else {
-                if (a.getTx() == 0.0 && a.getTy() == 0.0) {
-                    return State2D.SCALE;
-                } else {
-                    return State2D.SC_TR;
-                }
-            }
-        } else {
-            if (a.getMxx() == 0.0 && a.getMyy() == 0.0) {
-                if (a.getTx() == 0.0 && a.getTy() == 0.0) {
-                    return State2D.SHEAR;
-                } else {
-                    return State2D.SH_TR;
-                }
-            } else {
-                if (a.getTx() == 0.0 && a.getTy() == 0.0) {
-                    return State2D.SH_SC;
-                } else {
-                    return State2D.SH_SC_TR;
-                }
-            }
-        }
-    }
-
-    private State3D getExpectedState3D(Affine a) {
-        if (a.getMxz() == 0.0 && a.getMyz() == 0.0 &&
-                a.getMzx() == 0.0 && a.getMzy() == 0.0 &&
-                a.getMzz() == 1.0 && a.getTz() == 0.0) {
-            return State3D.NON_3D;
-        }
-
-        if (a.getMxy() != 0.0 || a.getMxz() != 0.0 ||
-                a.getMyx() != 0.0 || a.getMyz() != 0.0 ||
-                a.getMzx() != 0.0 || a.getMzy() != 0.0) {
-            return State3D.COMPLEX;
-        }
-
-        if ((a.getMxx() != 1.0 || a.getMyy() != 1.0 || a.getMzz() != 1.0) &&
-                (a.getTx() != 0.0 || a.getTy() != 0.0 || a.getTz() != 0.0)) {
-            return State3D.SC_TR;
-        }
-
-        if (a.getMxx() != 1.0 || a.getMyy() != 1.0 || a.getMzz() != 1.0) {
-            return State3D.SCALE;
-        }
-        if (a.getTx() != 0.0 || a.getTy() != 0.0 || a.getTz() != 0.0) {
-            return State3D.TRANSLATE;
-        }
-        return null;
-    }
-
-
     private void assertAffineOk(Transform expected, Affine a) {
         TransformHelper.assertMatrix(a, expected);
         assertStateOk(a);
@@ -293,19 +194,11 @@
     }
 
     private void assertStateOk(Affine a) {
-        State3D expectedState3D = getExpectedState3D(a);
-        assertEquals(expectedState3D.getValue(), a.getState3d());
-        if (expectedState3D == State3D.NON_3D) {
-            assertEquals(getExpectedState2D(a).getValue(), a.getState2d());
-        }
+        TransformHelper.assertStateOk(a, a.getState3d(), a.getState2d());
     }
 
     private void assertStateOk(String message, Affine a) {
-        State3D expectedState3D = getExpectedState3D(a);
-        assertEquals(message, expectedState3D.getValue(), a.getState3d());
-        if (expectedState3D == State3D.NON_3D) {
-            assertEquals(message, getExpectedState2D(a).getValue(), a.getState2d());
-        }
+        TransformHelper.assertStateOk(message, a, a.getState3d(), a.getState2d());
     }
 
     private void testOperationIsAtomic(Affine a,
--- a/javafx-ui-common/test/unit/javafx/scene/transform/TransformOperationsTest.java	Tue Nov 27 13:01:20 2012 -0500
+++ b/javafx-ui-common/test/unit/javafx/scene/transform/TransformOperationsTest.java	Tue Nov 27 10:45:21 2012 -0800
@@ -88,8 +88,8 @@
     private static final Affine affine_3d_translate_only = new Affine(0, 0, 0, 10,
                                                                        0, 0, 0, 20,
                                                                        0, 0, 0, 30);
-    private static final Affine affine_3d_complex = new Affine( 2,  3,  4,  5,
-                                                          6,  7,  8,  9,
+    private static final Affine affine_3d_complex = new Affine( 7,  3,  4,  5,
+                                                          6,  7,  5,  9,
                                                          10, 11, 12, 13);
     private static final Affine affine_3d_complex_noninvertible =
                                                      new Affine( 2,  3,  4,  5,
@@ -172,26 +172,106 @@
     private static final Rotate rotate3dNoPivot = new Rotate(97.5, new Point3D(66, 77, 88));
     private static final Rotate rotate2dPivot3d = new Rotate(97.5, 125, 126, 127, Rotate.Z_AXIS);
     private static final Rotate noRotate = new Rotate(0, Rotate.Y_AXIS);
-    private static final Transform immutable_arbitrary_nonInvertible =
-            TransformUtils.immutableTransform( 5,  6,  7,  8,
-                                              10, 11, 12, 13,
-                                              15, 16, 17, 18);
-    private static final Transform immutable_arbitrary =
-            TransformUtils.immutableTransform( 5,  6, 13,  8,
-                                              10,  4, 12, 13,
-                                              15, 16, 26, 18);
+    private static final Transform immutable_identity =
+            TransformHelper.immutableTransform(1, 0, 0, 0, 1, 0);
+    private static final Transform immutable_translate_only =
+            TransformHelper.immutableTransform(0, 0, 2, 0, 0, 3);
+    private static final Transform immutable_translate =
+            TransformHelper.immutableTransform(1, 0, 2, 0, 1, 3);
+    private static final Transform immutable_scale =
+            TransformHelper.immutableTransform(4, 0, 0, 0, 5, 0);
+    private static final Transform immutable_sc_tr =
+            TransformHelper.immutableTransform(6, 0, 8, 0, 7, 9);
+    private static final Transform immutable_shear =
+            TransformHelper.immutableTransform( 0, 10, 0, 11,  0, 0);
+    private static final Transform immutable_sh_tr =
+            TransformHelper.immutableTransform( 0, 12, 14, 13,  0, 15);
+    private static final Transform immutable_sh_sc_simple =
+            TransformHelper.immutableTransform( 1, 18, 0, 19,  1, 0);
+    private static final Transform immutable_sh_sc =
+            TransformHelper.immutableTransform(16, 18, 0, 19, 17, 0);
+    private static final Transform immutable_sh_sc_tr =
+            TransformHelper.immutableTransform(20, 21, 22, 23, 24, 25);
+    private static final Transform immutable_3d_tr =
+            TransformUtils.immutableTransform(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 30);
+    private static final Transform immutable_3d_sc =
+            TransformUtils.immutableTransform(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 0);
+    private static final Transform immutable_3d_sc_tr =
+            TransformUtils.immutableTransform(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 30);
+    private static final Transform immutable_3d_sc2_tr3 =
+            TransformUtils.immutableTransform(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 30);
+    private static final Transform immutable_3d_sc3_tr2 =
+            TransformUtils.immutableTransform(1, 0, 0, 25, 0, 1, 0, 0, 0, 0, 3, 0);
+    private static final Transform immutable_3d_withShear =
+            TransformUtils.immutableTransform(1, 5, 0, 0, 0, 1, 0, 0, 0, 0, 3, 30);
+    private static final Transform immutable_3d_only3d =
+            TransformUtils.immutableTransform(1, 0, 20, 0, 0, 1, 30, 0, 11, 12, 13, 0);
+    private static final Transform immutable_3d_translate_only =
+            TransformUtils.immutableTransform(0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30);
+    private static final Transform immutable_3d_complex =
+            TransformUtils.immutableTransform(7, 3, 4, 5, 5, 7, 8, 9, 10, 11, 12, 13);
+    private static final Transform immutable_3d_complex_noninvertible =
+            TransformUtils.immutableTransform(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
     private static final Transform immutable_empty =
-            TransformUtils.immutableTransform(0, 0, 0, 0,
-                                              0, 0, 0, 0,
-                                              0, 0, 0, 0);
+            TransformUtils.immutableTransform(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
     private static final Transform immutable_emptyZ =
-            TransformUtils.immutableTransform(1, 0, 0, 0,
-                                              0, 1, 0, 0,
-                                              0, 0, 0, 0);
+            TransformUtils.immutableTransform(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0);
     private static final Transform immutable_emptyXY =
-            TransformUtils.immutableTransform(0, 0, 0, 0,
-                                              0, 0, 0, 0,
-                                              0, 0, 1, 0);
+            TransformUtils.immutableTransform(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
+    private static final Transform immutable_nonInv_translate_x =
+            TransformHelper.immutableTransform(0, 0, 2, 0, 0, 0);
+    private static final Transform immutable_nonInv_translate_y =
+            TransformHelper.immutableTransform(0, 0, 0, 0, 0, 4);
+    private static final Transform immutable_nonInv_translate_z =
+            TransformUtils.immutableTransform(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4);
+    private static final Transform immutable_nonInv_scale_x =
+            TransformHelper.immutableTransform(2, 0, 0, 0, 0, 0);
+    private static final Transform immutable_nonInv_scale_y =
+            TransformHelper.immutableTransform(0, 0, 0, 0, 2, 0);
+    private static final Transform immutable_nonInv_scale_xy =
+            TransformUtils.immutableTransform(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0);
+    private static final Transform immutable_nonInv_scale_z =
+            TransformUtils.immutableTransform(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0);
+    private static final Transform immutable_nonInv_shear_x =
+            TransformHelper.immutableTransform(0, 3, 0, 0, 0, 0);
+    private static final Transform immutable_nonInv_shear_y =
+            TransformHelper.immutableTransform(0, 0, 0, 3, 0, 0);
+    private static final Transform immutable_nonInv_sh_tr_x =
+            TransformHelper.immutableTransform(0, 3, 4, 0, 0, 0);
+    private static final Transform immutable_nonInv_sh_tr_y =
+            TransformHelper.immutableTransform(0, 0, 0, 3, 0, 4);
+    private static final Transform immutable_nonInv_sh_sc_tr =
+            TransformHelper.immutableTransform(0, 0, 0, 2, 3, 4);
+    private static final Transform immutable_nonInv_sh_sc =
+            TransformHelper.immutableTransform(0, 0, 0, 2, 3, 0);
+    private static final Transform immutable_nonInv_sh_tr =
+            TransformHelper.immutableTransform(0, 0, 0, 2, 0, 5);
+    private static final Transform immutable_nonInv_sc_tr =
+            TransformHelper.immutableTransform(0, 0, 0, 0, 6, 5);
+    private static final Transform immutable_nonInv_sc_tr_x =
+            TransformHelper.immutableTransform(2, 0, 4, 0, 0, 0);
+    private static final Transform immutable_nonInv_sc_tr_y =
+            TransformHelper.immutableTransform(0, 0, 0, 0, 2, 7);
+    private static final Transform raw_arbitrary_nonInvertible =
+            TransformHelper.rawTransform( 5,  6,  7,  8,
+                                         10, 11, 12, 13,
+                                         15, 16, 17, 18);
+    private static final Transform raw_arbitrary =
+            TransformHelper.rawTransform( 5,  6, 13,  8,
+                                         10,  4, 12, 13,
+                                         15, 16, 26, 18);
+    private static final Transform raw_empty =
+            TransformHelper.rawTransform(0, 0, 0, 0,
+                                         0, 0, 0, 0,
+                                         0, 0, 0, 0);
+    private static final Transform raw_emptyZ =
+            TransformHelper.rawTransform(1, 0, 0, 0,
+                                         0, 1, 0, 0,
+                                         0, 0, 0, 0);
+    private static final Transform raw_emptyXY =
+            TransformHelper.rawTransform(0, 0, 0, 0,
+                                         0, 0, 0, 0,
+                                         0, 0, 1, 0);
 
     private boolean listenerCalled;
     private int eventCounter;
@@ -271,11 +351,51 @@
             { rotate3dNoPivot, false, Rotate.class },           // 68
             { rotate2dPivot3d, true, Rotate.class },            // 69
             { noRotate, true, Rotate.class },                   // 70
-            { immutable_arbitrary, false, Affine.class },       // 71
-            { immutable_arbitrary_nonInvertible, false, null }, // 72
-            { immutable_empty, false, null },                   // 73
-            { immutable_emptyZ, false, null },                  // 74
-            { immutable_emptyXY, true, null },                  // 75
+            { immutable_identity, true, Affine.class },         // 71
+            { immutable_translate, true, Affine.class },        // 72
+            { immutable_translate_only, true, Affine.class },   // 73
+            { immutable_scale, true, Affine.class },            // 74
+            { immutable_sc_tr, true, Affine.class },            // 75
+            { immutable_shear, true, Affine.class },            // 76
+            { immutable_sh_tr, true, Affine.class },            // 77
+            { immutable_sh_sc_simple, true, Affine.class },     // 78
+            { immutable_sh_sc, true, Affine.class },            // 79
+            { immutable_sh_sc_tr, true, Affine.class },         // 80
+            { immutable_3d_tr, false, Affine.class },           // 81
+            { immutable_3d_sc, false, Affine.class },           // 82
+            { immutable_3d_sc_tr, false, Affine.class },        // 83
+            { immutable_3d_sc2_tr3, false, Affine.class },      // 84
+            { immutable_3d_sc3_tr2, false, Affine.class },      // 85
+            { immutable_3d_withShear, false, Affine.class },    // 86
+            { immutable_3d_only3d, false, Affine.class },       // 87
+            { immutable_3d_translate_only, false, null },       // 88
+            { immutable_3d_complex, false, Affine.class },      // 89
+            { immutable_3d_complex_noninvertible, false, null },// 90
+            { immutable_empty, false, null },                   // 91
+            { immutable_emptyZ, false, null },                  // 92
+            { immutable_emptyXY, true, null },                  // 93
+            { immutable_nonInv_translate_x, true, null },       // 94
+            { immutable_nonInv_translate_y, true, null },       // 95
+            { immutable_nonInv_translate_z, false, null },      // 96
+            { immutable_nonInv_scale_x, true, null },           // 97
+            { immutable_nonInv_scale_y, true, null },           // 98
+            { immutable_nonInv_scale_xy, false, null },         // 99
+            { immutable_nonInv_scale_z, false, null },          //100
+            { immutable_nonInv_shear_x, true, null },           //101
+            { immutable_nonInv_shear_y, true, null },           //102
+            { immutable_nonInv_sh_tr_x, true, null },           //103
+            { immutable_nonInv_sh_tr_y, true, null },           //104
+            { immutable_nonInv_sh_sc_tr, true, null },          //105
+            { immutable_nonInv_sh_sc, true, null },             //106
+            { immutable_nonInv_sh_tr, true, null },             //107
+            { immutable_nonInv_sc_tr, true, null },             //108
+            { immutable_nonInv_sc_tr_x, true, null },           //109
+            { immutable_nonInv_sc_tr_y, true, null },           //110
+            { raw_arbitrary, false, Affine.class },             //111
+            { raw_arbitrary_nonInvertible, false, null },       //112
+            { raw_empty, false, null },                         //113
+            { raw_emptyZ, false, null },                        //114
+            { raw_emptyXY, true, null },                        //115
         });
     }