changeset 5519:c0c85cc23907

RT-14187: Support glyph rasterisation to sub-pixel precision Reviewed-by: Phil Race
author Felipe Heidrich <felipe.heidrich@oracle.com>
date Thu, 24 Oct 2013 11:38:07 -0700
parents bc90b41e06c1
children 659cc7864404
files modules/graphics/src/main/java/com/sun/javafx/font/CompositeStrike.java modules/graphics/src/main/java/com/sun/javafx/font/FontStrike.java modules/graphics/src/main/java/com/sun/javafx/font/Glyph.java modules/graphics/src/main/java/com/sun/javafx/font/PrismFontFactory.java modules/graphics/src/main/java/com/sun/javafx/font/PrismFontStrike.java modules/graphics/src/main/java/com/sun/javafx/font/coretext/CTFontStrike.java modules/graphics/src/main/java/com/sun/javafx/font/coretext/CTGlyph.java modules/graphics/src/main/java/com/sun/javafx/font/directwrite/DWFontStrike.java modules/graphics/src/main/java/com/sun/javafx/font/directwrite/DWGlyph.java modules/graphics/src/main/java/com/sun/javafx/font/pango/FTFontStrike.java modules/graphics/src/main/java/com/sun/javafx/font/pango/FTGlyph.java modules/graphics/src/main/java/com/sun/prism/impl/GlyphCache.java modules/graphics/src/main/java/com/sun/prism/impl/ps/BaseShaderGraphics.java modules/graphics/src/main/java/com/sun/prism/sw/SWGraphics.java
diffstat 14 files changed, 256 insertions(+), 151 deletions(-) [+]
line wrap: on
line diff
--- a/modules/graphics/src/main/java/com/sun/javafx/font/CompositeStrike.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/CompositeStrike.java	Thu Oct 24 11:38:07 2013 -0700
@@ -29,6 +29,7 @@
 import com.sun.javafx.geom.transform.Affine2D;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.geom.Path2D;
+import com.sun.javafx.geom.Point2D;
 import com.sun.javafx.geom.Shape;
 
 public class CompositeStrike implements FontStrike {
@@ -125,10 +126,6 @@
         }
     }
 
-    public boolean isSubPixelGlyph() {
-        return getStrikeSlot(0).isSubPixelGlyph();
-    }
-
     public FontResource getFontResource() {
         return fontResource;
     }
@@ -178,6 +175,11 @@
         return fontResource.getAdvance(glyphCode, size);
     }
 
+    @Override
+    public int getQuantizedPosition(Point2D point) {
+        return getStrikeSlot(0).getQuantizedPosition(point);
+    }
+
     public Shape getOutline(GlyphList gl, BaseTransform transform) {
 
         Path2D result = new Path2D();
--- a/modules/graphics/src/main/java/com/sun/javafx/font/FontStrike.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/FontStrike.java	Thu Oct 24 11:38:07 2013 -0700
@@ -26,6 +26,7 @@
 package com.sun.javafx.font;
 
 import com.sun.javafx.scene.text.GlyphList;
+import com.sun.javafx.geom.Point2D;
 import com.sun.javafx.geom.Shape;
 import com.sun.javafx.geom.transform.BaseTransform;
 
@@ -34,7 +35,15 @@
     public float getSize();
     public BaseTransform getTransform();
     public boolean drawAsShapes();
-    public boolean isSubPixelGlyph();
+
+    /**
+     * Modifies the point argument to the quantized position suitable for the
+     * underlying glyph rasterizer.
+     * The return value is the sub pixel index which should be passed to
+     * {@link Glyph#getPixelData(int)} in order to obtain the correct glyph mask
+     * for the given point.
+     */
+    public int getQuantizedPosition(Point2D point);
     public Metrics getMetrics();
     public Glyph getGlyph(char symbol);
     public Glyph getGlyph(int glyphCode);
--- a/modules/graphics/src/main/java/com/sun/javafx/font/Glyph.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/Glyph.java	Thu Oct 24 11:38:07 2013 -0700
@@ -36,12 +36,22 @@
     public Shape getShape();
     /* The rest are in device space */
     public byte[] getPixelData();
-    public byte[] getPixelData(float x, float y);
+
+    /**
+     * Returns the glyph mask at the subpixel position specified by subPixel.
+     *
+     * @see FontStrike#getQuantizedPosition(com.sun.javafx.geom.Point2D)
+     */
+    public byte[] getPixelData(int subPixel);
     public float getPixelXAdvance();
     public float getPixelYAdvance();
+    public boolean isLCDGlyph();
+
+    /* These 4 methods should only be called after either getPixelData() or
+     * getPixelData(int subPixel) is invoked. This ensures the returned value 
+     * is correct for the requested subpixel position. */
     public int getWidth();
     public int getHeight();
     public int getOriginX();
     public int getOriginY();
-    public boolean isLCDGlyph();
 }
--- a/modules/graphics/src/main/java/com/sun/javafx/font/PrismFontFactory.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/PrismFontFactory.java	Thu Oct 24 11:38:07 2013 -0700
@@ -55,7 +55,11 @@
     public static final boolean isEmbedded;
     public static int cacheLayoutSize = 0x10000;
     static boolean useNativeRasterizer;
-    private static boolean subPixelEnabled;
+    private static int subPixelMode;
+    public static final int SUB_PIXEL_ON = 1;
+    public static final int SUB_PIXEL_Y = 2;
+    public static final int SUB_PIXEL_NATIVE = 4;
+
     private static boolean lcdEnabled;
     private static float lcdContrast = -1;
     private static String jreFontDir;
@@ -106,8 +110,16 @@
                                     + s + "'");
                         }
                     }
-                    s = System.getProperty("prism.subpixeltext");
-                    subPixelEnabled = s == null || s.equalsIgnoreCase("true");
+                    s = System.getProperty("prism.subpixeltext", "on");
+                    if (s.indexOf("on") != -1 || s.indexOf("true") != -1) {
+                        subPixelMode = SUB_PIXEL_ON;
+                    }
+                    if (s.indexOf("native") != -1) {
+                        subPixelMode |= SUB_PIXEL_NATIVE | SUB_PIXEL_ON;
+                    }
+                    if (s.indexOf("vertical") != -1) {
+                        subPixelMode |= SUB_PIXEL_Y | SUB_PIXEL_NATIVE | SUB_PIXEL_ON;
+                    }
 
                     useNativeRasterizer = isMacOSX || isWindows;
                     String defPrismText = useNativeRasterizer ? "native" : "t2k";
@@ -163,6 +175,16 @@
         }
         if (debugFonts) {
             System.err.println("Loading FontFactory " + factoryClass);
+            if (subPixelMode != 0) {
+                String s = "Subpixel: enabled";
+                if ((subPixelMode & SUB_PIXEL_Y) != 0) {
+                    s += ", vertical";
+                }
+                if ((subPixelMode & SUB_PIXEL_NATIVE) != 0) {
+                    s += ", native";
+                }
+                System.err.println(s);
+            }
         }
         theFontFactory = getFontFactory(factoryClass);
         if (theFontFactory == null) {
@@ -1309,8 +1331,8 @@
         }
     }
 
-    public final boolean isSubPixelEnabled() {
-        return subPixelEnabled;
+    public final int getSubPixelMode() {
+        return subPixelMode;
     }
 
     public boolean isLCDTextSupported() {
--- a/modules/graphics/src/main/java/com/sun/javafx/font/PrismFontStrike.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/PrismFontStrike.java	Thu Oct 24 11:38:07 2013 -0700
@@ -29,6 +29,7 @@
 import java.util.Map;
 
 import com.sun.javafx.geom.Path2D;
+import com.sun.javafx.geom.Point2D;
 import com.sun.javafx.geom.Shape;
 import com.sun.javafx.geom.transform.Affine2D;
 import com.sun.javafx.geom.transform.BaseTransform;
@@ -134,6 +135,19 @@
         return transform;
     }
 
+    @Override
+    public int getQuantizedPosition(Point2D point) {
+        if (aaMode == FontResource.AA_GREYSCALE) {
+            /* No subpixel position */
+            point.x = (float)Math.round(point.x);
+        } else {
+            /* Prism can produce 3 subpixel positions in the shader */
+            point.x = (float)Math.round(3.0 * point.x)/ 3.0f;
+        }
+        point.y = (float)Math.round(point.y);
+        return 0;
+    }
+
     /**
      * Access to individual character advances are frequently needed for layout
      * understand that advance may vary for single glyph if ligatures or kerning
--- a/modules/graphics/src/main/java/com/sun/javafx/font/coretext/CTFontStrike.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/coretext/CTFontStrike.java	Thu Oct 24 11:38:07 2013 -0700
@@ -32,16 +32,26 @@
 import com.sun.javafx.font.PrismFontFactory;
 import com.sun.javafx.font.PrismFontStrike;
 import com.sun.javafx.geom.Path2D;
+import com.sun.javafx.geom.Point2D;
 import com.sun.javafx.geom.transform.BaseTransform;
 
 class CTFontStrike extends PrismFontStrike<CTFontFile> {
 
     private long fontRef;
     CGAffineTransform matrix;
-    boolean subPixel = false;
+    /* CoreText uses different precision for subpixel text according
+     * to the font size. By observation, font sizes smaller than 12
+     * have 4 subpixel positions. Between 12 and 17 it decreases to 3.
+     * Between 18 and 33 it is only 2. Above 33 it rounds all positions
+     * to integral values.
+     */
+    static final float SUBPIXEL4_SIZE = 12;
+    static final float SUBPIXEL3_SIZE = 18;
+    static final float SUBPIXEL2_SIZE = 34;
     private static final boolean SUBPIXEL;
     static {
-        SUBPIXEL = PrismFontFactory.getFontFactory().isSubPixelEnabled();
+        int mode = PrismFontFactory.getFontFactory().getSubPixelMode();
+        SUBPIXEL =  (mode & PrismFontFactory.SUB_PIXEL_ON) != 0;
     }
 
     CTFontStrike(CTFontFile fontResource, float size,
@@ -77,25 +87,6 @@
                 System.err.println("Failed to create CTFont for " + this);
             }
         }
-
-        /* CoreText uses different precision for subpixel text according
-         * to the font size. By observation, font sizes smaller than 12
-         * have 4 subpixel positions. Between 12 and 17 it decreases to 3.
-         * Between 18 and 33 it is only 2. Above 33 it rounds all positions
-         * to integral values.
-         */
-        if (SUBPIXEL && matrix == null) {
-            if (getAAMode() == FontResource.AA_LCD) {
-                /* Prism support 3 subpixel positions for LCD text and it is better
-                 * to use Prism when possible to save texture space in the glyph
-                 * cache.
-                 */
-                subPixel = getSize() < 12;
-            } else {
-                /* Prism has no subpixel support for grayscale text. */
-                subPixel = getSize() < 18;
-            }
-        }
     }
 
     long getFontRef() {
@@ -111,8 +102,68 @@
         return new CTGlyph(this, glyphCode, drawShapes);
     }
 
-    @Override public boolean isSubPixelGlyph() {
-        return subPixel;
+    @Override
+    public int getQuantizedPosition(Point2D point) {
+        if (SUBPIXEL && matrix == null) {
+            /* Prism only produces 3 position, so for sizes smaller than 12 use
+             * Coretext for LCD and grayscale text
+             */
+            if (getSize() < SUBPIXEL4_SIZE) {
+                float subPixelX = point.x;
+                point.x = (int) point.x;
+                subPixelX -= point.x;
+                point.y = (float) Math.round(point.y);
+                if (subPixelX >= 0.75f) return 3;
+                if (subPixelX >= 0.50f) return 2;
+                if (subPixelX >= 0.25f) return 1;
+                return 0;
+            }
+            if (getAAMode() == FontResource.AA_GREYSCALE) {
+                if (getSize() < SUBPIXEL3_SIZE) {
+                    float subPixelX = point.x;
+                    point.x = (int) point.x;
+                    subPixelX -= point.x;
+                    point.y = (float) Math.round(point.y);
+                    if (subPixelX >= 0.66f) return 2;
+                    if (subPixelX >= 0.33f) return 1;
+                    return 0;
+                }
+                if (getSize() < SUBPIXEL2_SIZE) {
+                    float subPixelX = point.x;
+                    point.x = (int) point.x;
+                    subPixelX -= point.x;
+                    point.y = (float) Math.round(point.y);
+                    if (subPixelX >= 0.5f) return 1;
+                }
+                return 0;
+            }
+        }
+        return super.getQuantizedPosition(point);
+    }
+
+    float getSubPixelPosition(int index) {
+        if (index == 0) return 0;
+        float size = getSize();
+        if (size < SUBPIXEL4_SIZE) {
+            if (index == 3) return 0.75f;
+            if (index == 2) return 0.50f;
+            if (index == 1) return 0.25f;
+            return 0;
+        }
+        if (getAAMode() == FontResource.AA_LCD) return 0;
+        if (size < SUBPIXEL3_SIZE) {
+            if (index == 2) return 0.66f;
+            if (index == 1) return 0.33f;
+            return 0;
+        }
+        if (size < SUBPIXEL2_SIZE) {
+            if (index == 1) return 0.50f;
+        }
+        return 0;
+    }
+
+    boolean isSubPixelGlyph() {
+        return SUBPIXEL && matrix == null;
     }
 
     @Override protected Path2D createGlyphOutline(int glyphCode) {
--- a/modules/graphics/src/main/java/com/sun/javafx/font/coretext/CTGlyph.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/coretext/CTGlyph.java	Thu Oct 24 11:38:07 2013 -0700
@@ -143,8 +143,7 @@
         return cachedContextRef;
     }
 
-    private synchronized byte[] getImage(double x, double y, int w, int h,
-                                         double subPixelX, double subPixelY) {
+    private synchronized byte[] getImage(double x, double y, int w, int h, int subPixel) {
 
         if (w == 0 || h == 0) return new byte[0];
 
@@ -168,12 +167,8 @@
         if (matrix != null) {
             OS.CGContextTranslateCTM(context, -x, -y);
         } else {
-            drawX = x;
+            drawX = x - strike.getSubPixelPosition(subPixel);
             drawY = y;
-            if (strike.isSubPixelGlyph()) {
-                drawX -= subPixelX;
-                drawX -= subPixelY;
-            }
         }
 
         /* Draw the text with black */
@@ -198,13 +193,13 @@
     }
 
     @Override public byte[] getPixelData() {
-        return getPixelData(0, 0);
+        return getPixelData(0);
     }
 
-    @Override public byte[] getPixelData(float x, float y) {
+    @Override public byte[] getPixelData(int subPixel) {
         checkBounds();
         return getImage(bounds.origin.x, bounds.origin.y,
-                        (int)bounds.size.width, (int)bounds.size.height, x, y);
+                        (int)bounds.size.width, (int)bounds.size.height, subPixel);
     }
 
     @Override public float getAdvance() {
--- a/modules/graphics/src/main/java/com/sun/javafx/font/directwrite/DWFontStrike.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/directwrite/DWFontStrike.java	Thu Oct 24 11:38:07 2013 -0700
@@ -26,19 +26,26 @@
 package com.sun.javafx.font.directwrite;
 
 import com.sun.javafx.font.DisposerRecord;
+import com.sun.javafx.font.FontResource;
 import com.sun.javafx.font.FontStrikeDesc;
 import com.sun.javafx.font.Glyph;
 import com.sun.javafx.font.PrismFontFactory;
 import com.sun.javafx.font.PrismFontStrike;
 import com.sun.javafx.geom.Path2D;
+import com.sun.javafx.geom.Point2D;
 import com.sun.javafx.geom.RectBounds;
 import com.sun.javafx.geom.transform.BaseTransform;
 
 class DWFontStrike extends PrismFontStrike<DWFontFile> {
     DWRITE_MATRIX matrix;
-    static final boolean SUBPIXEL;
+    static final boolean SUBPIXEL_ON;
+    static final boolean SUBPIXEL_Y;
+    static final boolean SUBPIXEL_NATIVE;
     static {
-        SUBPIXEL = PrismFontFactory.getFontFactory().isSubPixelEnabled();
+        int mode = PrismFontFactory.getFontFactory().getSubPixelMode();
+        SUBPIXEL_ON = (mode & PrismFontFactory.SUB_PIXEL_ON) != 0;
+        SUBPIXEL_Y = (mode & PrismFontFactory.SUB_PIXEL_Y) != 0;
+        SUBPIXEL_NATIVE = (mode & PrismFontFactory.SUB_PIXEL_NATIVE) != 0;
     }
 
     DWFontStrike(DWFontFile fontResource, float size, BaseTransform tx,
@@ -70,13 +77,38 @@
     }
 
     @Override
-    public boolean isSubPixelGlyph() {
-        /* Disable subpixel for DirectWrite until better support for it
-         * is implemented on Prism. DirectWrite support 3 subpixel positions
-         * for LCD and Gray text. Prism currently expects it to support 4.
-         */
-//        return SUBPIXEL && matrix == null;
-        return false;
+    public int getQuantizedPosition(Point2D point) {
+        if (SUBPIXEL_ON && (matrix == null || SUBPIXEL_NATIVE)) {
+            /* Using DirectWrite to produce subpixel glyph masks for grayscale
+             * text and (by default) let Prism produce subpixel glyphs for LCD
+             * using shaders (thus, saving texture and memory).
+             */
+            if (getAAMode() == FontResource.AA_GREYSCALE || SUBPIXEL_NATIVE) {
+                float subPixel = point.x;
+                point.x = (int)point.x;
+                subPixel -= point.x;
+                int index = 0;
+                if (subPixel >= 0.66f) {
+                    index = 2;
+                } else if (subPixel >= 0.33f) {
+                    index = 1;
+                }
+                if (SUBPIXEL_Y) {
+                    subPixel = point.y;
+                    point.y = (int)point.y;
+                    subPixel -= point.y;
+                    if (subPixel >= 0.66f) {
+                        index += 6;
+                    } else if (subPixel >= 0.33f) {
+                        index += 3;
+                    }
+                } else {
+                    point.y = (float)Math.round(point.y);
+                }
+                return index;
+            }
+        }
+        return super.getQuantizedPosition(point);
     }
 
     IDWriteFontFace getFontFace() {
--- a/modules/graphics/src/main/java/com/sun/javafx/font/directwrite/DWGlyph.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/directwrite/DWGlyph.java	Thu Oct 24 11:38:07 2013 -0700
@@ -39,7 +39,8 @@
     private float pixelXAdvance, pixelYAdvance;
     private RECT rect;
     private boolean drawShapes;
-    private byte[] pixelData;
+    private byte[][] pixelData;
+    private RECT[] rects;
 
     private static final boolean CACHE_TARGET = true;
     private static IWICBitmap cachedBitmap;
@@ -55,6 +56,9 @@
     DWGlyph(DWFontStrike strike, int glyphCode, boolean drawShapes) {
         this.strike = strike;
         this.drawShapes = drawShapes;
+        int size = DWFontStrike.SUBPIXEL_Y ? 9 : 3;
+        this.pixelData = new byte[size][];
+        this.rects = new RECT[size];
 
         IDWriteFontFace face = strike.getFontFace();
         run = new DWRITE_GLYPH_RUN();
@@ -111,6 +115,12 @@
         }
         if (rect == null) {
             rect = new RECT();
+        } else {
+            /* Increase the RECT */
+            rect.left--;
+            rect.top--;
+            rect.right++;
+            rect.bottom++;
         }
     }
 
@@ -146,11 +156,6 @@
             return new byte[0];
         }
 
-        /* Increase the RECT */
-        rect.left--;
-        rect.top--;
-        rect.right++;
-        rect.bottom++;
         float glyphX = rect.left;
         float glyphY = rect.top;
         int w = rect.right - rect.left;
@@ -174,7 +179,7 @@
         if (matrix != null) {
             transform = new D2D1_MATRIX_3X2_F(matrix.m11, matrix.m12,
                                               matrix.m21, matrix.m22,
-                                              -glyphX, -glyphY);
+                                              -glyphX + subPixelX, -glyphY + subPixelY);
             glyphX = glyphY = 0;
         } else {
             transform = D2D2_MATRIX_IDENTITY;
@@ -254,7 +259,9 @@
     IDWriteGlyphRunAnalysis createAnalysis(float x, float y) {
         if (run.fontFace == 0) return null;
         IDWriteFactory factory = DWFactory.getDWriteFactory();
-        int renderingMode = OS.DWRITE_RENDERING_MODE_NATURAL;
+        int renderingMode = DWFontStrike.SUBPIXEL_Y ?
+                            OS.DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC :
+                            OS.DWRITE_RENDERING_MODE_NATURAL;
         int measuringMode = OS.DWRITE_MEASURING_MODE_NATURAL;
         DWRITE_MATRIX matrix = strike.matrix; /* can be null */
         float dpi = 1;  /* Assumes WICBitmap has 96 dpi */
@@ -319,15 +326,35 @@
 
     @Override
     public byte[] getPixelData() {
-        return getPixelData(0, 0);
+        return getPixelData(0);
     }
 
     @Override
-    public byte[] getPixelData(float x, float y) {
-        if (pixelData == null) {
-            pixelData = isLCDGlyph() ? getLCDMask(x, y) : getD2DMask(x, y, false);
+    public byte[] getPixelData(int subPixel) {
+        byte[] data = pixelData[subPixel];
+        /* Caching all possible masks has an important performance impact on the
+         * software pipeline (as it doesn't have a glyph cache).
+         * Note: The same cache is not implemented on CTGlyph.
+         */
+        if (data == null) {
+            float x = 0, y = 0;
+            int index = subPixel;
+            if (index >= 6) {
+                index -= 6;
+                y = 0.66f;
+            } else if (index >= 3) {
+                index -= 3;
+                y = 0.33f;
+            }
+            if (index == 1) x = 0.33f;
+            if (index == 2) x = 0.66f;
+            pixelData[subPixel] = data = isLCDGlyph() ? getLCDMask(x, y) :
+                                                        getD2DMask(x, y, false);
+            rects[subPixel] = rect;
+        } else {
+            rect = rects[subPixel];
         }
-        return pixelData;
+        return data;
     }
 
     @Override
--- a/modules/graphics/src/main/java/com/sun/javafx/font/pango/FTFontStrike.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/pango/FTFontStrike.java	Thu Oct 24 11:38:07 2013 -0700
@@ -62,11 +62,6 @@
     }
 
     @Override
-    public boolean isSubPixelGlyph() {
-        return false;
-    }
-
-    @Override
     protected DisposerRecord createDisposer(FontStrikeDesc desc) {
         return null;
     }
--- a/modules/graphics/src/main/java/com/sun/javafx/font/pango/FTGlyph.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/pango/FTGlyph.java	Thu Oct 24 11:38:07 2013 -0700
@@ -82,7 +82,7 @@
     }
 
     @Override
-    public byte[] getPixelData(float x, float y) {
+    public byte[] getPixelData(int subPixel) {
         init();
         return buffer;
     }
--- a/modules/graphics/src/main/java/com/sun/prism/impl/GlyphCache.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/prism/impl/GlyphCache.java	Thu Oct 24 11:38:07 2013 -0700
@@ -74,10 +74,7 @@
 
     // Because of SEGSHIFT the 5 high bit in the key to glyphDataMap are unused
     // Using them for subpixel
-    private static final int SUBPIXEL_NONE = 0;
-    private static final int SUBPIXEL_ONEQUARTER    = 0x08000000; // bit 27
-    private static final int SUBPIXEL_ONEHALF       = 0x10000000; // bit 28
-    private static final int SUBPIXEL_THREEQUARTERS = 0x18000000; // bit 27+28
+    private static final int SUBPIXEL_SHIFT = 27;
 
     private RectanglePacker packer;
 
@@ -136,8 +133,6 @@
         Color currentColor = null;
         Point2D pt = new Point2D();
 
-        boolean subPixel = strike.isSubPixelGlyph();
-        float subPixelX = 0;
         for (int gi = 0; gi < len; gi++) {
             int gc = gl.getGlyphCode(gi) & CompositeGlyphMapper.GLYPHMASK;
 
@@ -148,21 +143,13 @@
                 continue;
             }
             pt.setLocation(x + gl.getPosX(gi), y + gl.getPosY(gi));
-            if (subPixel) {
-                subPixelX = pt.x;
-                pt.x = (int)pt.x;
-                subPixelX -= pt.x;
-            }
-            GlyphData data = getCachedGlyph(gl, gi, subPixelX, 0);
+            int subPixel = strike.getQuantizedPosition(pt);
+            GlyphData data = getCachedGlyph(gc, subPixel);
             if (data != null) {
                 if (clip != null) {
                     // Always check clipping using user space.
                     if (x + gl.getPosX(gi) > clip.getMaxX()) break;
-                    if (x + gl.getPosX(gi + 1) < clip.getMinX()) {
-                        pt.x += data.getXAdvance();
-                        pt.y += data.getYAdvance();
-                        continue;
-                    }
+                    if (x + gl.getPosX(gi + 1) < clip.getMinX()) continue;
                 }
                 /* Will not render selected text for complex
                  * paints such as gradient.
@@ -250,24 +237,10 @@
         packer.clear();
     }
 
-    private GlyphData getCachedGlyph(GlyphList gl, int gi, float x, float y) {
-        int glyphCode = gl.getGlyphCode(gi);
+    private GlyphData getCachedGlyph(int glyphCode, int subPixel) {
         int segIndex = glyphCode >> SEGSHIFT;
         int subIndex = glyphCode % SEGSIZE;
-        if (x != 0) {
-            if (x < 0.25) {
-                x = 0;
-            } else if (x < 0.50) {
-                x = 0.25f;
-                segIndex |= SUBPIXEL_ONEQUARTER;
-            } else if (x < 0.75) {
-                x = 0.50f;
-                segIndex |= SUBPIXEL_ONEHALF;
-            } else {
-                x = 0.75f;
-                segIndex |= SUBPIXEL_THREEQUARTERS;
-            }
-        }
+        segIndex |= (subPixel << SUBPIXEL_SHIFT);
         GlyphData[] segment = glyphDataMap.get(segIndex);
         if (segment != null) {
             if (segment[subIndex] != null) {
@@ -282,18 +255,18 @@
         GlyphData data = null;
         Glyph glyph = strike.getGlyph(glyphCode);
         if (glyph != null) {
-            if (glyph.getWidth() == 0 || glyph.getHeight() == 0) {
+            byte[] glyphImage = glyph.getPixelData(subPixel);
+            if (glyphImage == null || glyphImage.length == 0) {
                 data = new GlyphData(0, 0, 0,
                                      glyph.getPixelXAdvance(),
                                      glyph.getPixelYAdvance(),
                                      null);
             } else {
                 // Rasterize the glyph
-                // TODO: needs more work for fractional metrics support (RT-27423)
                 // NOTE : if the MaskData can be stored back directly
                 // in the glyph, even as an opaque type, it should save
                 // repeated work next time the glyph is used.
-                MaskData maskData = MaskData.create(glyph.getPixelData(x ,y),
+                MaskData maskData = MaskData.create(glyphImage,
                                                     glyph.getOriginX(),
                                                     glyph.getOriginY(),
                                                     glyph.getWidth(),
--- a/modules/graphics/src/main/java/com/sun/prism/impl/ps/BaseShaderGraphics.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/prism/impl/ps/BaseShaderGraphics.java	Thu Oct 24 11:38:07 2013 -0700
@@ -42,7 +42,6 @@
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
 import com.sun.javafx.scene.text.GlyphList;
-import com.sun.javafx.sg.prism.NGCamera;
 import com.sun.javafx.sg.prism.NGLightBase;
 import com.sun.prism.BasicStroke;
 import com.sun.prism.CompositeMode;
@@ -2049,7 +2048,7 @@
 
         //Since we currently cannot support LCD text on transparant surfaces, we
         //verify that we are drawing to an opaque surface.
-        if (strike.getAAMode() == FontResource.AA_LCD && lcdSupported) {
+        if (strike.getAAMode() == FontResource.AA_LCD) {
             if (nodeBounds == null) {
                 // If drawString is called directly without using
                 // setNodeBounds then we must determine the bounds of the str,
@@ -2097,27 +2096,19 @@
             float unitXCoord = 1.0f/((float)cacheTex.getPhysicalWidth());
             shader.setConstant("gamma", gamma, invgamma, unitXCoord);
             setCompositeMode(blendMode); // Restore composite mode
-            if (isSimpleTranslate) {
-                // Applying this rounding allows for smoother text animation,
-                // when animating simple translated text.
-                if (!strike.isSubPixelGlyph()) {
-                    p2d.x = (float)Math.round(3.0 * p2d.x)/ 3.0f;
-                }
-                p2d.y = (float)Math.round(p2d.y);
-            }
         } else {
-            if (isSimpleTranslate) {
-                // Asking glyph textures to be rendered at non-integral
-                // locations produces very poor text. This doesn't solve
-                // the problem for scaled (etc) cases, but addresses a
-                // common case.
-                if (!strike.isSubPixelGlyph()) {
-                    p2d.x = (float)Math.round(p2d.x);
-                }
-                p2d.y = (float)Math.round(p2d.y);
-            }
             context.validatePaintOp(this, IDENT, cacheTex, bx, by, bw, bh);
         }
+        if (isSimpleTranslate) {
+            // Applying this rounding allows for smoother text animation,
+            // when animating simple translated text.
+            // Asking glyph textures to be rendered at non-integral
+            // locations produces very poor text. This doesn't solve
+            // the problem for scaled (etc) cases, but addresses a
+            // common case.
+            p2d.y = Math.round(p2d.y);
+            p2d.x = Math.round(p2d.x);
+        }
         glyphCache.render(context, gl, p2d.x, p2d.y, selectStart, selectEnd,
                           selectColor, textColor, xform, clip);
     }
--- a/modules/graphics/src/main/java/com/sun/prism/sw/SWGraphics.java	Thu Oct 24 12:40:55 2013 -0400
+++ b/modules/graphics/src/main/java/com/sun/prism/sw/SWGraphics.java	Thu Oct 24 11:38:07 2013 -0700
@@ -607,13 +607,13 @@
 
         final Glyph g = strike.getGlyph(gl.getGlyphCode(idx));
         if (drawAsMasks) {
-            final float posX = (float)(x + tx.getMxt() + gl.getPosX(idx));
-            final float posY = (float)(y + tx.getMyt() + gl.getPosY(idx));
-            final float subPosX = getSubPos(posX);
-            final byte pixelData[] = g.getPixelData(subPosX, 0);
+            final Point2D pt = new Point2D((float)(x + tx.getMxt() + gl.getPosX(idx)),
+                                           (float)(y + tx.getMyt() + gl.getPosY(idx)));
+            int subPixel = strike.getQuantizedPosition(pt);
+            final byte pixelData[] = g.getPixelData(subPixel);
             if (pixelData != null) {
-                final int intPosX = g.getOriginX() + (int)posX;
-                final int intPosY = g.getOriginY() + (int)posY;
+                final int intPosX = g.getOriginX() + (int)pt.x;
+                final int intPosY = g.getOriginY() + (int)pt.y;
                 if (g.isLCDGlyph()) {
                     this.pr.fillLCDAlphaMask(pixelData, intPosX, intPosY,
                             g.getWidth(), g.getHeight(),
@@ -634,22 +634,6 @@
         }
     }
 
-    private float getSubPos(float value) {
-        float v = value - ((int)value);
-        if (v != 0f) {
-            if (v < 0.25f) {
-                v = 0;
-            } else if (v < 0.50f) {
-                v = 0.25f;
-            } else if (v < 0.75f) {
-                v = 0.50f;
-            } else {
-                v = 0.75f;
-            }
-        }
-        return v;
-    }
-
     public void drawTexture(Texture tex, float x, float y, float w, float h) {
         if (PrismSettings.debug) {
             System.out.printf("+ drawTexture1, x: %f, y: %f, w: %f, h: %f\n", x, y, w, h);