6656651: Windows Look and Feel LCD glyph images have some differences from native applications.
Reviewed-by: igor, tdv
--- a/jdk/make/sun/font/FILES_c.gmk Mon Apr 28 15:57:46 2008 -0700
+++ b/jdk/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/jdk/make/sun/font/Makefile Mon Apr 28 15:57:46 2008 -0700
+++ b/jdk/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/jdk/src/share/classes/sun/font/FileFontStrike.java Mon Apr 28 15:57:46 2008 -0700
+++ b/jdk/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/jdk/src/share/classes/sun/font/FontManager.java Mon Apr 28 15:57:46 2008 -0700
+++ b/jdk/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/jdk/src/share/classes/sun/font/TrueTypeFont.java Mon Apr 28 15:57:46 2008 -0700
+++ b/jdk/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/jdk/src/windows/classes/sun/awt/Win32GraphicsEnvironment.java Mon Apr 28 15:57:46 2008 -0700
+++ b/jdk/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/jdk/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/jdk/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);
+ }
+ }
+}