changeset 301:55e6548451df

6656651: Windows Look and Feel LCD glyph images have some differences from native applications. Reviewed-by: igor, tdv
author prr
date Wed, 30 Apr 2008 13:10:39 -0700
parents d7accc312aec
children fb61ff1cc5fd
files make/sun/font/FILES_c.gmk make/sun/font/Makefile src/share/classes/sun/font/FileFontStrike.java src/share/classes/sun/font/FontManager.java src/share/classes/sun/font/TrueTypeFont.java src/windows/classes/sun/awt/Win32GraphicsEnvironment.java src/windows/native/sun/font/lcdglyph.c test/java/awt/Graphics2D/DrawString/ScaledLCDTextMetrics.java
diffstat 8 files changed, 718 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/make/sun/font/FILES_c.gmk	Mon Apr 28 15:57:46 2008 -0700
+++ b/make/sun/font/FILES_c.gmk	Wed Apr 30 13:10:39 2008 -0700
@@ -113,7 +113,9 @@
 
 
 ifeq ($(PLATFORM),windows)
-FILES_c_platform = fontpath.c
+FILES_c_platform = fontpath.c \
+                   lcdglyph.c
+
 FILES_cpp_platform = D3DTextRenderer.cpp
 else
 FILES_c_platform = X11FontScaler.c \
--- a/make/sun/font/Makefile	Mon Apr 28 15:57:46 2008 -0700
+++ b/make/sun/font/Makefile	Wed Apr 30 13:10:39 2008 -0700
@@ -63,6 +63,7 @@
     java/awt/Font.java \
     java/text/Bidi.java \
     sun/font/FileFont.java \
+    sun/font/FileFontStrike.java \
     sun/font/FontManager.java \
     sun/font/GlyphList.java \
     sun/font/NativeFont.java \
--- a/src/share/classes/sun/font/FileFontStrike.java	Mon Apr 28 15:57:46 2008 -0700
+++ b/src/share/classes/sun/font/FileFontStrike.java	Wed Apr 30 13:10:39 2008 -0700
@@ -27,6 +27,7 @@
 
 import java.lang.ref.SoftReference;
 import java.awt.Font;
+import java.awt.GraphicsEnvironment;
 import java.awt.Rectangle;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.GeneralPath;
@@ -105,6 +106,19 @@
     boolean useNatives;
     NativeStrike[] nativeStrikes;
 
+    /* Used only for communication to native layer */
+    private int intPtSize;
+
+    /* Perform global initialisation needed for Windows native rasterizer */
+    private static native boolean initNative();
+    private static boolean isXPorLater = false;
+    static {
+        if (FontManager.isWindows && !FontManager.useT2K &&
+            !GraphicsEnvironment.isHeadless()) {
+            isXPorLater = initNative();
+        }
+    }
+
     FileFontStrike(FileFont fileFont, FontStrikeDesc desc) {
         super(fileFont, desc);
         this.fileFont = fileFont;
@@ -165,7 +179,7 @@
          * should not segment unless there's another reason to do so.
          */
         float ptSize = (float)matrix[3]; // interpreted only when meaningful.
-        int iSize = (int)ptSize;
+        int iSize = intPtSize = (int)ptSize;
         boolean isSimpleTx = (at.getType() & complexTX) == 0;
         segmentedCache =
             (numGlyphs > SEGSIZE << 3) ||
@@ -189,8 +203,26 @@
             FontManager.deRegisterBadFont(fileFont);
             return;
         }
-
-        if (fileFont.checkUseNatives() && desc.aaHint==0 && !algoStyle) {
+        /* First, see if native code should be used to create the glyph.
+         * GDI will return the integer metrics, not fractional metrics, which
+         * may be requested for this strike, so we would require here that :
+         * desc.fmHint != INTVAL_FRACTIONALMETRICS_ON
+         * except that the advance returned by GDI is always overwritten by
+         * the JDK rasteriser supplied one (see getGlyphImageFromWindows()).
+         */
+        if (FontManager.isWindows && isXPorLater &&
+            !FontManager.useT2K &&
+            !GraphicsEnvironment.isHeadless() &&
+            !fileFont.useJavaRasterizer &&
+            (desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB ||
+             desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR) &&
+            (matrix[1] == 0.0 && matrix[2] == 0.0 &&
+             matrix[0] == matrix[3] &&
+             matrix[0] >= 3.0 && matrix[0] <= 100.0) &&
+            !((TrueTypeFont)fileFont).useEmbeddedBitmapsForSize(intPtSize)) {
+            useNatives = true;
+        }
+        else if (fileFont.checkUseNatives() && desc.aaHint==0 && !algoStyle) {
             /* Check its a simple scale of a pt size in the range
              * where native bitmaps typically exist (6-36 pts) */
             if (matrix[1] == 0.0 && matrix[2] == 0.0 &&
@@ -208,7 +240,16 @@
                 }
             }
         }
-
+        if (FontManager.logging && FontManager.isWindows) {
+            FontManager.logger.info
+                ("Strike for " + fileFont + " at size = " + intPtSize +
+                 " use natives = " + useNatives +
+                 " useJavaRasteriser = " + fileFont.useJavaRasterizer +
+                 " AAHint = " + desc.aaHint +
+                 " Has Embedded bitmaps = " +
+                 ((TrueTypeFont)fileFont).
+                 useEmbeddedBitmapsForSize(intPtSize));
+        }
         this.disposer = new FontStrikeDisposer(fileFont, desc, pScalerContext);
 
         /* Always get the image and the advance together for smaller sizes
@@ -255,8 +296,50 @@
         return fileFont.getNumGlyphs();
     }
 
+    long getGlyphImageFromNative(int glyphCode) {
+        if (FontManager.isWindows) {
+            return getGlyphImageFromWindows(glyphCode);
+        } else {
+            return getGlyphImageFromX11(glyphCode);
+        }
+    }
+
+    /* There's no global state conflicts, so this method is not
+     * presently synchronized.
+     */
+    private native long _getGlyphImageFromWindows(String family,
+                                                  int style,
+                                                  int size,
+                                                  int glyphCode,
+                                                  boolean fracMetrics);
+
+    long getGlyphImageFromWindows(int glyphCode) {
+        String family = fileFont.getFamilyName(null);
+        int style = desc.style & Font.BOLD | desc.style & Font.ITALIC
+            | fileFont.getStyle();
+        int size = intPtSize;
+        long ptr = _getGlyphImageFromWindows
+            (family, style, size, glyphCode,
+             desc.fmHint == INTVAL_FRACTIONALMETRICS_ON);
+        if (ptr != 0) {
+            /* Get the advance from the JDK rasterizer. This is mostly
+             * necessary for the fractional metrics case, but there are
+             * also some very small number (<0.25%) of marginal cases where
+             * there is some rounding difference between windows and JDK.
+             * After these are resolved, we can restrict this extra
+             * work to the FM case.
+             */
+            float advance = getGlyphAdvance(glyphCode, false);
+            StrikeCache.unsafe.putFloat(ptr + StrikeCache.xAdvanceOffset,
+                                        advance);
+            return ptr;
+        } else {
+            return fileFont.getGlyphImage(pScalerContext, glyphCode);
+        }
+    }
+
     /* Try the native strikes first, then try the fileFont strike */
-    long getGlyphImageFromNative(int glyphCode) {
+    long getGlyphImageFromX11(int glyphCode) {
         long glyphPtr;
         char charCode = fileFont.glyphToCharMap[glyphCode];
         for (int i=0;i<nativeStrikes.length;i++) {
@@ -276,13 +359,19 @@
         if (glyphCode >= INVISIBLE_GLYPHS) {
             return StrikeCache.invisibleGlyphPtr;
         }
-        long glyphPtr;
+        long glyphPtr = 0L;
         if ((glyphPtr = getCachedGlyphPtr(glyphCode)) != 0L) {
             return glyphPtr;
         } else {
             if (useNatives) {
                 glyphPtr = getGlyphImageFromNative(glyphCode);
-            } else {
+                if (glyphPtr == 0L && FontManager.logging) {
+                    FontManager.logger.info
+                        ("Strike for " + fileFont +
+                         " at size = " + intPtSize +
+                         " couldn't get native glyph for code = " + glyphCode);
+                 }
+            } if (glyphPtr == 0L) {
                 glyphPtr = fileFont.getGlyphImage(pScalerContext,
                                                   glyphCode);
             }
@@ -300,10 +389,10 @@
             } else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) {
                 continue;
             } else {
-                long glyphPtr;
+                long glyphPtr = 0L;
                 if (useNatives) {
                     glyphPtr = getGlyphImageFromNative(glyphCode);
-                } else {
+                } if (glyphPtr == 0L) {
                     glyphPtr = fileFont.getGlyphImage(pScalerContext,
                                                       glyphCode);
                 }
@@ -332,10 +421,11 @@
             } else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) {
                 continue;
             } else {
-                long glyphPtr;
+                long glyphPtr = 0L;
                 if (useNatives) {
                     glyphPtr = getGlyphImageFromNative(glyphCode);
-                } else {
+                }
+                if (glyphPtr == 0L) {
                     glyphPtr = fileFont.getGlyphImage(pScalerContext,
                                                       glyphCode);
                 }
@@ -459,11 +549,16 @@
         }
     }
 
+    float getGlyphAdvance(int glyphCode) {
+        return getGlyphAdvance(glyphCode, true);
+    }
+
     /* Metrics info is always retrieved. If the GlyphInfo address is non-zero
      * then metrics info there is valid and can just be copied.
-     * This is in user space coordinates.
+     * This is in user space coordinates unless getUserAdv == false.
+     * Device space advance should not be propagated out of this class.
      */
-    float getGlyphAdvance(int glyphCode) {
+    private float getGlyphAdvance(int glyphCode, boolean getUserAdv) {
         float advance;
 
         if (glyphCode >= INVISIBLE_GLYPHS) {
@@ -485,11 +580,11 @@
             }
         }
 
-        if (invertDevTx != null) {
+        if (invertDevTx != null || !getUserAdv) {
             /* If there is a device transform need x & y advance to
              * transform back into user space.
              */
-            advance = getGlyphMetrics(glyphCode).x;
+            advance = getGlyphMetrics(glyphCode, getUserAdv).x;
         } else {
             long glyphPtr;
             if (getImageWithAdvance) {
@@ -625,6 +720,10 @@
     }
 
     Point2D.Float getGlyphMetrics(int glyphCode) {
+        return getGlyphMetrics(glyphCode, true);
+    }
+
+    private Point2D.Float getGlyphMetrics(int glyphCode, boolean getUserAdv) {
         Point2D.Float metrics = new Point2D.Float();
 
         // !!! or do we force sgv user glyphs?
@@ -632,7 +731,7 @@
             return metrics;
         }
         long glyphPtr;
-        if (getImageWithAdvance) {
+        if (getImageWithAdvance && getUserAdv) {
             /* A heuristic optimisation says that for most cases its
              * worthwhile retrieving the image at the same time as the
              * metrics. So here we get the image data even if its not
@@ -649,9 +748,9 @@
             metrics.y = StrikeCache.unsafe.getFloat
                 (glyphPtr + StrikeCache.yAdvanceOffset);
             /* advance is currently in device space, need to convert back
-             * into user space.
+             * into user space, unless getUserAdv == false.
              * This must not include the translation component. */
-            if (invertDevTx != null) {
+            if (invertDevTx != null && getUserAdv) {
                 invertDevTx.deltaTransform(metrics, metrics);
             }
         } else {
@@ -680,9 +779,9 @@
             if (value == null) {
                 fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics);
                 /* advance is currently in device space, need to convert back
-                 * into user space.
+                 * into user space, unless getUserAdv == false.
                  */
-                if (invertDevTx != null) {
+                if (invertDevTx != null && getUserAdv) {
                     invertDevTx.deltaTransform(metrics, metrics);
                 }
                 value = new Point2D.Float(metrics.x, metrics.y);
--- a/src/share/classes/sun/font/FontManager.java	Mon Apr 28 15:57:46 2008 -0700
+++ b/src/share/classes/sun/font/FontManager.java	Wed Apr 30 13:10:39 2008 -0700
@@ -244,9 +244,11 @@
                osName = System.getProperty("os.name", "unknownOS");
                isSolaris = osName.startsWith("SunOS");
 
+               String t2kStr = System.getProperty("sun.java2d.font.scaler");
+               if (t2kStr != null) {
+                   useT2K = "t2k".equals(t2kStr);
+               }
                if (isSolaris) {
-                   String t2kStr= System.getProperty("sun.java2d.font.scaler");
-                   useT2K = "t2k".equals(t2kStr);
                    String version = System.getProperty("os.version", "unk");
                    isSolaris8 = version.equals("5.8");
                    isSolaris9 = version.equals("5.9");
--- a/src/share/classes/sun/font/TrueTypeFont.java	Mon Apr 28 15:57:46 2008 -0700
+++ b/src/share/classes/sun/font/TrueTypeFont.java	Wed Apr 30 13:10:39 2008 -0700
@@ -893,6 +893,31 @@
         return null;
     }
 
+    /* Used to determine if this size has embedded bitmaps, which
+     * for CJK fonts should be used in preference to LCD glyphs.
+     */
+    boolean useEmbeddedBitmapsForSize(int ptSize) {
+        if (!supportsCJK) {
+            return false;
+        }
+        if (getDirectoryEntry(EBLCTag) == null) {
+            return false;
+        }
+        ByteBuffer eblcTable = getTableBuffer(EBLCTag);
+        int numSizes = eblcTable.getInt(4);
+        /* The bitmapSizeTable's start at offset of 8.
+         * Each bitmapSizeTable entry is 48 bytes.
+         * The offset of ppemY in the entry is 45.
+         */
+        for (int i=0;i<numSizes;i++) {
+            int ppemY = eblcTable.get(8+(i*48)+45) &0xff;
+            if (ppemY == ptSize) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public String getFullName() {
         return fullName;
     }
--- a/src/windows/classes/sun/awt/Win32GraphicsEnvironment.java	Mon Apr 28 15:57:46 2008 -0700
+++ b/src/windows/classes/sun/awt/Win32GraphicsEnvironment.java	Wed Apr 30 13:10:39 2008 -0700
@@ -260,6 +260,7 @@
         try {
             while (!found && parser.hasMoreTokens()) {
                 String newPath = parser.nextToken();
+                boolean ujr = newPath.equals(jreFontDirName);
                 File theFile = new File(newPath, fontFileName);
                 if (theFile.canRead()) {
                     found = true;
@@ -267,11 +268,11 @@
                     if (defer) {
                         FontManager.registerDeferredFont(fontFileName, path,
                                                          nativeNames,
-                                                         fontFormat, true,
+                                                         fontFormat, ujr,
                                                          fontRank);
                     } else {
                         FontManager.registerFontFile(path, nativeNames,
-                                                     fontFormat, true,
+                                                     fontFormat, ujr,
                                                      fontRank);
                     }
                     break;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/windows/native/sun/font/lcdglyph.c	Wed Apr 30 13:10:39 2008 -0700
@@ -0,0 +1,481 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  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.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * The function here is used to get a GDI rasterized LCD glyph and place it
+ * into the JDK glyph cache. The benefit is rendering fidelity for the
+ * most common cases, with no impact on the 2D rendering pipelines.
+ *
+ * Requires that the font and graphics are unrotated, and the scale is
+ * a simple one, and the font is a TT font registered with windows.
+ * Those conditions are established by the calling code.
+ *
+ * This code
+ * - Receives the family name, style, and size of the font
+ * and creates a Font object.
+ * - Create a surface from which we can get a DC : must be 16 bit or more.
+ * Ideally we'd be able to specify the depth of this, but in practice we
+ * have to accept it will be the same as the default screen.
+ * - Selects the GDI font on to the device
+ * - Uses GetGlyphOutline to estimate the bounds.
+ * - Creates a DIB on to which to blit the image.
+ * - Creates a GlyphInfo structure and copies the GDI glyph and offsets
+ * into the glyph which is returned.
+ */
+
+#include <stdio.h>
+#include <malloc.h>
+#include <math.h>
+#include <windows.h>
+#include <winuser.h>
+
+#include <jni.h>
+#include <jni_util.h>
+#include <jlong_md.h>
+#include <sun_font_FileFontStrike.h>
+
+#include "fontscalerdefs.h"
+
+/* Some of these are also defined in awtmsg.h but I don't want a dependency
+ * on that here. They are needed here - and in awtmsg.h - until we
+ * move up our build to define WIN32_WINNT >= 0x501 (ie XP), since MS
+ * headers will not define them otherwise.
+ */
+#ifndef SPI_GETFONTSMOOTHINGTYPE
+#define SPI_GETFONTSMOOTHINGTYPE        0x200A
+#endif //SPI_GETFONTSMOOTHINGTYPE
+
+#ifndef SPI_GETFONTSMOOTHINGCONTRAST
+#define SPI_GETFONTSMOOTHINGCONTRAST    0x200C
+#endif //SPI_GETFONTSMOOTHINGCONTRAST
+
+#ifndef SPI_GETFONTSMOOTHINGORIENTATION
+#define SPI_GETFONTSMOOTHINGORIENTATION    0x2012
+#endif //SPI_GETFONTSMOOTHINGORIENTATION
+
+#ifndef FE_FONTSMOOTHINGORIENTATIONBGR
+#define FE_FONTSMOOTHINGORIENTATIONBGR 0x0000
+#endif //FE_FONTSMOOTHINGORIENTATIONBGR
+
+#ifndef FE_FONTSMOOTHINGORIENTATIONRGB
+#define FE_FONTSMOOTHINGORIENTATIONRGB 0x0001
+#endif //FE_FONTSMOOTHINGORIENTATIONRGB
+
+#define MIN_GAMMA 100
+#define MAX_GAMMA 220
+#define LCDLUTCOUNT (MAX_GAMMA-MIN_GAMMA+1)
+
+static unsigned char* igLUTable[LCDLUTCOUNT];
+
+static unsigned char* getIGTable(int gamma) {
+    int i, index;
+    double ig;
+    char *igTable;
+
+    if (gamma < MIN_GAMMA) {
+        gamma = MIN_GAMMA;
+    } else if (gamma > MAX_GAMMA) {
+        gamma = MAX_GAMMA;
+    }
+
+    index = gamma - MIN_GAMMA;
+
+    if (igLUTable[index] != NULL) {
+        return igLUTable[index];
+    }
+    igTable = (unsigned char*)malloc(256);
+    if (igTable == NULL) {
+      return NULL;
+    }
+    igTable[0] = 0;
+    igTable[255] = 255;
+    ig = ((double)gamma)/100.0;
+
+    for (i=1;i<255;i++) {
+        igTable[i] = (unsigned char)(pow(((double)i)/255.0, ig)*255);
+    }
+    igLUTable[index] = igTable;
+    return igTable;
+}
+
+
+JNIEXPORT jboolean JNICALL
+    Java_sun_font_FileFontStrike_initNative(JNIEnv *env, jclass unused) {
+
+    DWORD osVersion = GetVersion();
+    DWORD majorVersion = (DWORD)(LOBYTE(LOWORD(osVersion)));
+    DWORD minorVersion = (DWORD)(HIBYTE(LOWORD(osVersion)));
+
+    /* Need at least XP which is 5.1 */
+    if (majorVersion < 5 || (majorVersion == 5 && minorVersion < 1)) {
+        return JNI_FALSE;
+    }
+
+    memset(igLUTable, 0,  LCDLUTCOUNT);
+
+    return JNI_TRUE;
+}
+
+#ifndef CLEARTYPE_QUALITY
+#define CLEARTYPE_QUALITY 5
+#endif
+
+#ifndef CLEARTYPE_NATURAL_QUALITY
+#define CLEARTYPE_NATURAL_QUALITY 6
+#endif
+
+#define FREE_AND_RETURN \
+    if (hDesktopDC != 0 && hWnd != 0) { \
+       ReleaseDC(hWnd, hDesktopDC); \
+    }\
+    if (hMemoryDC != 0) { \
+        DeleteObject(hMemoryDC); \
+    } \
+    if (hBitmap != 0) { \
+        DeleteObject(hBitmap); \
+    } \
+    if (dibImage != NULL) { \
+        free(dibImage); \
+    } \
+    if (glyphInfo != NULL) { \
+        free(glyphInfo); \
+    } \
+    return (jlong)0;
+/* end define */
+
+JNIEXPORT jlong JNICALL
+Java_sun_font_FileFontStrike__1getGlyphImageFromWindows
+(JNIEnv *env, jobject unused,
+ jstring fontFamily, jint style, jint size, jint glyphCode, jboolean fm) {
+
+    GLYPHMETRICS glyphMetrics;
+    LOGFONTW lf;
+    BITMAPINFO bmi;
+    TEXTMETRIC textMetric;
+    RECT rect;
+    int bytesWidth, dibBytesWidth, extra, imageSize, dibImageSize;
+    unsigned char* dibImage = NULL, *rowPtr, *pixelPtr, *dibPixPtr, *dibRowPtr;
+    unsigned char r,g,b;
+    unsigned char* igTable;
+    GlyphInfo* glyphInfo = NULL;
+    int nameLen;
+    LPWSTR name;
+    HFONT oldFont, hFont;
+    MAT2 mat2;
+
+    unsigned short width;
+    unsigned short height;
+    short advanceX;
+    short advanceY;
+    int topLeftX;
+    int topLeftY;
+    int err;
+    int bmWidth, bmHeight;
+    int x, y;
+    HBITMAP hBitmap = NULL, hOrigBM;
+    int gamma, orient;
+
+    HWND hWnd = NULL;
+    HDC hDesktopDC = NULL;
+    HDC hMemoryDC = NULL;
+
+    hWnd = GetDesktopWindow();
+    hDesktopDC = GetWindowDC(hWnd);
+    if (hDesktopDC == NULL) {
+        return (jlong)0;
+    }
+    if (GetDeviceCaps(hDesktopDC, BITSPIXEL) < 15) {
+        FREE_AND_RETURN;
+    }
+
+    hMemoryDC = CreateCompatibleDC(hDesktopDC);
+    if (hMemoryDC == NULL || fontFamily == NULL) {
+        FREE_AND_RETURN;
+    }
+    err = SetMapMode(hMemoryDC, MM_TEXT);
+    if (err == 0) {
+        FREE_AND_RETURN;
+    }
+
+    memset(&lf, 0, sizeof(LOGFONTW));
+    lf.lfHeight = -size;
+    lf.lfWeight = (style & 1) ? FW_BOLD : FW_NORMAL;
+    lf.lfItalic = (style & 2) ? 0xff : 0;
+    lf.lfCharSet = DEFAULT_CHARSET;
+    lf.lfQuality = CLEARTYPE_QUALITY;
+    lf.lfOutPrecision = OUT_TT_PRECIS;
+    lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+    lf.lfPitchAndFamily = DEFAULT_PITCH;
+
+    nameLen = (*env)->GetStringLength(env, fontFamily);
+    name = (LPWSTR)alloca((nameLen+1)*2);
+    if (name == NULL) {
+       FREE_AND_RETURN;
+    }
+    (*env)->GetStringRegion(env, fontFamily, 0, nameLen, name);
+    name[nameLen] = '\0';
+
+    if (nameLen < (sizeof(lf.lfFaceName) / sizeof(lf.lfFaceName[0]))) {
+        wcscpy(lf.lfFaceName, name);
+    } else {
+        FREE_AND_RETURN;
+    }
+
+    hFont = CreateFontIndirectW(&lf);
+    if (hFont == NULL) {
+        FREE_AND_RETURN;
+    }
+    oldFont = SelectObject(hMemoryDC, hFont);
+
+    memset(&textMetric, 0, sizeof(TEXTMETRIC));
+    err = GetTextMetrics(hMemoryDC, &textMetric);
+    if (err == 0) {
+        FREE_AND_RETURN;
+    }
+    memset(&glyphMetrics, 0, sizeof(GLYPHMETRICS));
+    memset(&mat2, 0, sizeof(MAT2));
+    mat2.eM11.value = 1; mat2.eM22.value = 1;
+    err = GetGlyphOutline(hMemoryDC, glyphCode,
+                          GGO_METRICS|GGO_GLYPH_INDEX,
+                          &glyphMetrics,
+                          0, NULL, &mat2);
+    if (err == GDI_ERROR) {
+        /* Probably no such glyph - ie the font wasn't the one we expected. */
+        FREE_AND_RETURN;
+    }
+
+    width  = (unsigned short)glyphMetrics.gmBlackBoxX;
+    height = (unsigned short)glyphMetrics.gmBlackBoxY;
+
+    /* Don't handle "invisible" glyphs in this code */
+    if (width <= 0 || height == 0) {
+       FREE_AND_RETURN;
+    }
+
+    advanceX = glyphMetrics.gmCellIncX;
+    advanceY = glyphMetrics.gmCellIncY;
+    topLeftX = glyphMetrics.gmptGlyphOrigin.x;
+    topLeftY = glyphMetrics.gmptGlyphOrigin.y;
+
+    /* GetGlyphOutline pre-dates cleartype and I'm not sure that it will
+     * account for all pixels touched by the rendering. Need to widen,
+     * and also adjust by one the x position at which it is rendered.
+     * The extra pixels of width are used as follows :
+     * One extra pixel at the left and the right will be needed to absorb
+     * the pixels that will be touched by filtering by GDI to compensate
+     * for colour fringing.
+     * However there seem to be some cases where GDI renders two extra
+     * pixels to the right, so we add one additional pixel to the right,
+     * and in the code that copies this to the image cache we test for
+     * the (rare) cases when this is touched, and if its not reduce the
+     * stated image width for the blitting loops.
+     * For fractional metrics :
+     * One extra pixel at each end to account for sub-pixel positioning used
+     * when fractional metrics is on in LCD mode.
+     * The pixel at the left is needed so the blitting loop can index into
+     * that a byte at a time to more accurately position the glyph.
+     * The pixel at the right is needed so that when such indexing happens,
+     * the blitting still can use the same width.
+     * Consequently the width that is specified for the glyph is one less
+     * than that of the actual image.
+     * Note that in the FM case as a consequence we need to adjust the
+     * position at which GDI renders, and the declared width of the glyph
+     * See the if (fm) {} cases in the code.
+     * For the non-FM case, we not only save 3 bytes per row, but this
+     * prevents apparent glyph overlapping which affects the rendering
+     * performance of accelerated pipelines since it adds additional
+     * read-back requirements.
+     */
+    width+=3;
+    if (fm) {
+        width+=1;
+    }
+    /* DIB scanline must end on a DWORD boundary. We specify 3 bytes per pixel,
+     * so must round up as needed to a multiple of 4 bytes.
+     */
+    dibBytesWidth = bytesWidth = width*3;
+    extra = dibBytesWidth % 4;
+    if (extra != 0) {
+        dibBytesWidth += (4-extra);
+    }
+    /* The glyph cache image must be a multiple of 3 bytes wide. */
+    extra = bytesWidth % 3;
+    if (extra != 0) {
+        bytesWidth += (3-extra);
+    }
+    bmWidth = width;
+    bmHeight = height;
+
+    /* Must use desktop DC to create a bitmap of that depth */
+    hBitmap = CreateCompatibleBitmap(hDesktopDC, bmWidth, bmHeight);
+    if (hBitmap == NULL) {
+        FREE_AND_RETURN;
+    }
+    hOrigBM = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
+
+    /* Fill in black */
+    rect.left = 0;
+    rect.top = 0;
+    rect.right = bmWidth;
+    rect.bottom = bmHeight;
+    FillRect(hMemoryDC, (LPRECT)&rect, GetStockObject(BLACK_BRUSH));
+
+    /* Set text color to white, background to black. */
+    SetBkColor(hMemoryDC, RGB(0,0,0));
+    SetTextColor(hMemoryDC, RGB(255,255,255));
+
+    /* adjust rendering position */
+    x = -topLeftX+1;
+    if (fm) {
+        x += 1;
+    }
+    y = topLeftY - textMetric.tmAscent;
+    err = ExtTextOutW(hMemoryDC, x, y, ETO_GLYPH_INDEX|ETO_OPAQUE,
+                (LPRECT)&rect, (LPCWSTR)&glyphCode, 1, NULL);
+    if (err == 0) {
+        FREE_AND_RETURN;
+    }
+
+    /* Now get the image into a DIB.
+     * MS docs for GetDIBits says the compatible bitmap must not be
+     * selected into a DC, so restore the original first.
+     */
+    SelectObject(hMemoryDC, hOrigBM);
+    SelectObject(hMemoryDC, oldFont);
+    DeleteObject(hFont);
+
+    memset(&bmi, 0, sizeof(BITMAPINFO));
+    bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
+    bmi.bmiHeader.biWidth = width;
+    bmi.bmiHeader.biHeight = -height;
+    bmi.bmiHeader.biPlanes = 1;
+    bmi.bmiHeader.biBitCount = 24;
+    bmi.bmiHeader.biCompression = BI_RGB;
+
+    dibImageSize = dibBytesWidth*height;
+    dibImage = malloc(dibImageSize);
+    if (dibImage == NULL) {
+        FREE_AND_RETURN;
+    }
+    memset(dibImage, 0, dibImageSize);
+
+    err = GetDIBits(hMemoryDC, hBitmap, 0, height, dibImage,
+                    &bmi, DIB_RGB_COLORS);
+
+    if (err == 0) {        /* GetDIBits failed. */
+        FREE_AND_RETURN;
+    }
+
+    err = SystemParametersInfo(SPI_GETFONTSMOOTHINGORIENTATION, 0, &orient, 0);
+    if (err == 0) {
+        FREE_AND_RETURN;
+    }
+    err = SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &gamma, 0);
+    if (err == 0) {
+        FREE_AND_RETURN;
+    }
+    igTable = getIGTable(gamma/10);
+    if (igTable == NULL) {
+        FREE_AND_RETURN;
+    }
+
+    /* Now copy glyph image into a GlyphInfo structure and return it.
+     * NB the xadvance calculated here may be overwritten by the caller.
+     * 1 is subtracted from the bitmap width to get the glyph width, since
+     * that extra "1" was added as padding, so the sub-pixel positioning of
+     * fractional metrics could index into it.
+     */
+    imageSize = bytesWidth*height;
+    glyphInfo = (GlyphInfo*)malloc(sizeof(GlyphInfo)+imageSize);
+    if (malloc == NULL) {
+        FREE_AND_RETURN;
+    }
+    glyphInfo->cellInfo = NULL;
+    glyphInfo->rowBytes = bytesWidth;
+    glyphInfo->width = width;
+    if (fm) {
+        glyphInfo->width -= 1; // must subtract 1
+    }
+    glyphInfo->height = height;
+    glyphInfo->advanceX = advanceX;
+    glyphInfo->advanceY = advanceY;
+    glyphInfo->topLeftX = (float)(topLeftX-1);
+    if (fm) {
+        glyphInfo->topLeftX -= 1;
+    }
+    glyphInfo->topLeftY = (float)-topLeftY;
+    glyphInfo->image = (unsigned char*)glyphInfo+sizeof(GlyphInfo);
+    memset(glyphInfo->image, 0, imageSize);
+
+    /* DIB 24bpp data is always stored in BGR order, but we usually
+     * need this in RGB, so we can't just memcpy and need to swap B and R.
+     * Also need to apply inverse gamma adjustment here.
+     * We re-use the variable "extra" to see if the last pixel is touched
+     * at all. If its not we can reduce the glyph image width. This comes
+     * into play in some cases where GDI touches more pixels than accounted
+     * for by increasing width by two pixels over the B&W image. Whilst
+     * the bytes are in the cache, it doesn't affect rendering performance
+     * of the hardware pipelines.
+     */
+    extra = 0;
+    if (fm) {
+        extra = 1; // always need it.
+    }
+    dibRowPtr = dibImage;
+    rowPtr = glyphInfo->image;
+    for (y=0;y<height;y++) {
+        pixelPtr = rowPtr;
+        dibPixPtr = dibRowPtr;
+        for (x=0;x<width;x++) {
+            if (orient == FE_FONTSMOOTHINGORIENTATIONRGB) {
+                b = *dibPixPtr++;
+                g = *dibPixPtr++;
+                r = *dibPixPtr++;
+            } else {
+                r = *dibPixPtr++;
+                g = *dibPixPtr++;
+                b = *dibPixPtr++;
+            }
+            *pixelPtr++ = igTable[r];
+            *pixelPtr++ = igTable[g];
+            *pixelPtr++ = igTable[b];
+            if (!fm && (x==(width-1)) && (r|g|b)) {
+                extra = 1;
+            }
+        }
+        dibRowPtr += dibBytesWidth;
+        rowPtr  += bytesWidth;
+    }
+    if (!extra) {
+        glyphInfo->width -= 1;
+    }
+
+    free(dibImage);
+    ReleaseDC(hWnd, hDesktopDC);
+    DeleteObject(hMemoryDC);
+    DeleteObject(hBitmap);
+
+    return ptr_to_jlong(glyphInfo);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/awt/Graphics2D/DrawString/ScaledLCDTextMetrics.java	Wed Apr 30 13:10:39 2008 -0700
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/**
+ * @test
+ * @bug 6685312
+ * @summary Check advance of LCD text on a scaled graphics.
+ */
+
+import javax.swing.*;
+import java.awt.*;
+import static java.awt.RenderingHints.*;
+
+public class ScaledLCDTextMetrics extends Component {
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.add("Center", new ScaledLCDTextMetrics());
+        f.pack();
+        f.setVisible(true);
+    }
+
+    public Dimension getPreferredSize() {
+      return new Dimension(200,100);
+    }
+    public void paint(Graphics g) {
+       Graphics2D g2 = (Graphics2D)g;
+
+       Font f = new Font("Tahoma", Font.PLAIN, 11);
+       g.setFont(f);
+       g.setColor(Color.white);
+       g.fillRect(0,0,400,300);
+       g.setColor(Color.black);
+       g2.setRenderingHint(KEY_TEXT_ANTIALIASING,VALUE_TEXT_ANTIALIAS_LCD_HRGB);
+       String text = "ABCDEFGHIJKLI";
+
+       FontMetrics fm1 = g2.getFontMetrics();
+       int adv1 = fm1.stringWidth(text);
+       g.drawString(text, 5, 20);
+
+       g2.scale(2,2);
+
+       FontMetrics fm2 = g2.getFontMetrics();
+       int adv2 = fm2.stringWidth(text);
+       g.drawString(text, 5, 40);
+
+       double frac = Math.abs(adv1/(double)adv2);
+
+       System.out.println("scalex1: " + adv1);
+       System.out.println("scalex2: " + adv2);
+       System.out.println("Fraction : "+ frac);
+
+       // adv1 will not be exactly the same as adv2, but should differ
+       // only by a fraction.
+
+       if (frac < 0.8 || frac > 1.2) {
+           throw new RuntimeException("Metrics differ " +
+           "Adv1="+adv1+" Adv2="+adv2+" Fraction="+frac);
+       }
+    }
+}