From 14b183ce63fb5c44357cbeae4adcd179bc5ed1bb Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Wed, 3 Aug 2016 16:19:43 +0300 Subject: JRE-11 Support text rendering via DirectWrite API on Windows - initial version --- make/lib/Awt2dLibraries.gmk | 8 +- src/share/classes/sun/font/FileFontStrike.java | 93 +++++++-- src/windows/native/sun/font/lcdglyphDW.cpp | 258 +++++++++++++++++++++++++ 3 files changed, 342 insertions(+), 17 deletions(-) create mode 100644 src/windows/native/sun/font/lcdglyphDW.cpp diff --git a/make/lib/Awt2dLibraries.gmk b/make/lib/Awt2dLibraries.gmk index 34c3d38c3a..ff7af4605d 100644 --- a/make/lib/Awt2dLibraries.gmk +++ b/make/lib/Awt2dLibraries.gmk @@ -906,10 +906,12 @@ else ifeq ($(OPENJDK_TARGET_OS), macosx) LIBFONTMANAGER_EXCLUDE_FILES += X11FontScaler.c \ X11TextRenderer.c \ fontpath.c \ - lcdglyph.c + lcdglyph.c \ + lcdglyphDW.cpp else LIBFONTMANAGER_EXCLUDE_FILES += fontpath.c \ - lcdglyph.c + lcdglyph.c \ + lcdglyphDW.cpp endif BUILD_LIBFONTMANAGER_CFLAGS_COMMON := \ @@ -954,7 +956,7 @@ $(eval $(call SetupNativeCompilation,BUILD_LIBFONTMANAGER, \ LDFLAGS_SUFFIX_aix := -lawt -lawt_headless $(LIBM) $(LIBCXX) -ljava -ljvm,\ LDFLAGS_SUFFIX_macosx := -lawt $(LIBM) $(LIBCXX) -undefined dynamic_lookup \ -ljava -ljvm, \ - LDFLAGS_SUFFIX_windows := $(WIN_JAVA_LIB) advapi32.lib user32.lib gdi32.lib \ + LDFLAGS_SUFFIX_windows := $(WIN_JAVA_LIB) advapi32.lib user32.lib gdi32.lib Dwrite.lib \ $(WIN_AWT_LIB), \ VERSIONINFO_RESOURCE := $(JDK_TOPDIR)/src/windows/resource/version.rc, \ RC_FLAGS := $(RC_FLAGS) \ diff --git a/src/share/classes/sun/font/FileFontStrike.java b/src/share/classes/sun/font/FileFontStrike.java index 2d6a6df47d..8d0061de44 100644 --- a/src/share/classes/sun/font/FileFontStrike.java +++ b/src/share/classes/sun/font/FileFontStrike.java @@ -35,6 +35,8 @@ import java.awt.geom.GeneralPath; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.concurrent.ConcurrentHashMap; import static sun.awt.SunHints.*; @@ -115,11 +117,53 @@ public class FileFontStrike extends PhysicalStrike { /* Perform global initialisation needed for Windows native rasterizer */ private static native boolean initNative(); + private static native boolean isDirectWriteAvailable(); private static boolean isXPorLater = false; + + private static boolean useDirectWrite; + private static int dwMeasuringMode = 0; // 'natural' mode + private static int dwRenderingMode = -1; + private static float dwClearTypeLevel = -1; + private static float dwEnhancedContrast = -1; + private static float dwGamma = 1; // gamma correction will be applied when glyph image is blitted onto target surface, so disabling correction here by default + private static int dwPixelGeometry = -1; + static { if (FontUtilities.isWindows && !FontUtilities.useT2K && !GraphicsEnvironment.isHeadless()) { isXPorLater = initNative(); + if (isXPorLater) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + useDirectWrite = Boolean.getBoolean("directwrite.font.rendering") && isDirectWriteAvailable(); + if (useDirectWrite) { + String options = System.getProperty("directwrite.font.rendering.options"); + if (options != null) { + String[] parts = options.split(":"); + if (parts.length > 0 && parts[0].length() > 0) { + try { dwMeasuringMode = Integer.parseInt(parts[0]); } catch (NumberFormatException ignored) { } + } + if (parts.length > 1 && parts[1].length() > 0) { + try { dwRenderingMode = Integer.parseInt(parts[1]); } catch (NumberFormatException ignored) { } + } + if (parts.length > 2 && parts[2].length() > 0) { + try { dwClearTypeLevel = Float.parseFloat(parts[2]); } catch (NumberFormatException ignored) { } + } + if (parts.length > 3 && parts[3].length() > 0) { + try { dwEnhancedContrast = Float.parseFloat(parts[3]); } catch (NumberFormatException ignored) { } + } + if (parts.length > 4 && parts[4].length() > 0) { + try { dwGamma = Float.parseFloat(parts[4]); } catch (NumberFormatException ignored) { } + } + if (parts.length > 5 && parts[5].length() > 0) { + try { dwPixelGeometry = Integer.parseInt(parts[5]); } catch (NumberFormatException ignored) { } + } + } + } + return null; + } + }); + } } } @@ -340,29 +384,50 @@ public class FileFontStrike extends PhysicalStrike { boolean fracMetrics, int rotation); + private native long _getGlyphImageFromWindowsUsingDirectWrite(String family, + int style, + int size, + int glyphCode, + int measuringMode, + int renderingMode, + float clearTypeLevel, + float enhancedContrast, + float gamma, + int pixelGeometry); + 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, rotation); - 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. - */ - if (rotation == 0 || rotation == 2) { + long ptr = 0; + if (useDirectWrite && rotation == 0) { + ptr = _getGlyphImageFromWindowsUsingDirectWrite(family, style, size, glyphCode, + dwMeasuringMode, dwRenderingMode, dwClearTypeLevel, dwEnhancedContrast, dwGamma, dwPixelGeometry); + if (ptr == 0 && FontUtilities.isLogging()) { + FontUtilities.getLogger().warning("Failed to render glyph via DirectWrite: code=" + glyphCode + + ", fontFamily=" + family + ", style=" + style + ", size=" + size); + } + } + if (ptr == 0) { + ptr = _getGlyphImageFromWindows(family, style, size, glyphCode, + desc.fmHint == INTVAL_FRACTIONALMETRICS_ON, rotation); + if (ptr != 0 && (rotation == 0 || rotation == 2)) { + /* 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); } + if (ptr == 0) { + ptr = fileFont.getGlyphImage(pScalerContext, glyphCode); + } + return ptr; } /* Try the native strikes first, then try the fileFont strike */ diff --git a/src/windows/native/sun/font/lcdglyphDW.cpp b/src/windows/native/sun/font/lcdglyphDW.cpp new file mode 100644 index 0000000000..bfcb732885 --- /dev/null +++ b/src/windows/native/sun/font/lcdglyphDW.cpp @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2016 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "fontscalerdefs.h" + +extern "C" { + +#define FREE \ + if (target != NULL) { \ + target->Release(); \ + }\ + if (params != NULL) { \ + params->Release(); \ + }\ + if (defaultParams != NULL) { \ + defaultParams->Release(); \ + }\ + if (face != NULL) { \ + face->Release(); \ + }\ + if (font != NULL) { \ + font->Release(); \ + }\ + if (interop != NULL) { \ + interop->Release(); \ + }\ + if (factory != NULL) { \ + factory->Release(); \ + } + +#define FREE_AND_RETURN \ + FREE\ + return (jlong)0; + +JNIEXPORT jboolean JNICALL +Java_sun_font_FileFontStrike_isDirectWriteAvailable(JNIEnv *env, jclass unused) { + // This is an equivalent of IsWindows7OrGreater defined in VersionHelpers.h + + OSVERSIONINFOEXW osvi; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); + osvi.dwMajorVersion = 6; + osvi.dwMinorVersion = 1; + + DWORDLONG conditionMask = VerSetConditionMask(VerSetConditionMask(0, + VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL); + + BOOL result = VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION, conditionMask); + return result ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jlong JNICALL +Java_sun_font_FileFontStrike__1getGlyphImageFromWindowsUsingDirectWrite +(JNIEnv *env, jobject unused, jstring fontFamily, jint style, jint size, jint glyphCode, + jint measuringMode, jint renderingMode, jfloat clearTypeLevel, jfloat enhancedContrast, jfloat gamma, jint pixelGeometry) { + // variables cleared by FREE macro + IDWriteFactory* factory = NULL; + IDWriteGdiInterop* interop = NULL; + IDWriteFont* font = NULL; + IDWriteFontFace* face = NULL; + IDWriteRenderingParams* defaultParams = NULL; + IDWriteRenderingParams* params = NULL; + IDWriteBitmapRenderTarget* target = NULL; + + LOGFONTW lf; + memset(&lf, 0, sizeof(LOGFONTW)); + lf.lfWeight = (style & 1) ? FW_BOLD : FW_NORMAL; + lf.lfItalic = (style & 2) ? TRUE : FALSE; + + int nameLen = env->GetStringLength(fontFamily); + if (nameLen >= (sizeof(lf.lfFaceName) / sizeof(lf.lfFaceName[0]))) { + FREE_AND_RETURN + } + env->GetStringRegion(fontFamily, 0, nameLen, lf.lfFaceName); + lf.lfFaceName[nameLen] = '\0'; + + HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_ISOLATED, + __uuidof(IDWriteFactory), + reinterpret_cast(&factory)); + if (FAILED(hr)) { + FREE_AND_RETURN + } + hr = factory->GetGdiInterop(&interop); + if (FAILED(hr)) { + FREE_AND_RETURN + } + hr = interop->CreateFontFromLOGFONT(&lf, &font); + if (FAILED(hr)) { + FREE_AND_RETURN + } + hr = font->CreateFontFace(&face); + if (FAILED(hr)) { + FREE_AND_RETURN + } + hr = factory->CreateRenderingParams(&defaultParams); + if (FAILED(hr)) { + FREE_AND_RETURN + } + hr = factory->CreateCustomRenderingParams( + gamma > 0 && gamma <= 256 ? gamma : defaultParams->GetGamma(), + enhancedContrast >= 0 ? enhancedContrast : defaultParams->GetEnhancedContrast(), + clearTypeLevel >= 0 && clearTypeLevel <= 1 ? clearTypeLevel : defaultParams->GetClearTypeLevel(), + pixelGeometry >= 0 && pixelGeometry <= 2 ? (DWRITE_PIXEL_GEOMETRY)pixelGeometry : defaultParams->GetPixelGeometry(), + renderingMode >= 0 && renderingMode <= 6 ? (DWRITE_RENDERING_MODE)renderingMode : defaultParams->GetRenderingMode(), + ¶ms); + if (FAILED(hr)) { + FREE_AND_RETURN + } + + UINT16 indices[] = {(UINT16)glyphCode}; + FLOAT advances[] = {0}; + DWRITE_GLYPH_OFFSET offsets[] = {{0, 0}}; + DWRITE_GLYPH_RUN glyphRun; + glyphRun.fontFace = face; + glyphRun.fontEmSize = (FLOAT)size; + glyphRun.glyphCount = 1; + glyphRun.glyphIndices = indices; + glyphRun.glyphAdvances = advances; + glyphRun.glyphOffsets = offsets; + glyphRun.isSideways = FALSE; + glyphRun.bidiLevel = 0; + + DWRITE_FONT_METRICS fontMetrics; + face->GetMetrics(&fontMetrics); + FLOAT pxPerDU = ((FLOAT)size) / fontMetrics.designUnitsPerEm; + + DWRITE_GLYPH_METRICS metrics[1]; + hr = face->GetDesignGlyphMetrics(indices, 1, metrics, FALSE); + if (FAILED(hr)) { + FREE_AND_RETURN + } + + // trying to derive required bitmap size from glyph metrics (adding several spare pixels on each border) + // if that will fail, we'll perform a second attempt based on the output of DrawGlyphRun + int width = (int)((metrics[0].advanceWidth - metrics[0].leftSideBearing - metrics[0].rightSideBearing) * pxPerDU) + 10; + int height = (int)((metrics[0].advanceHeight - metrics[0].topSideBearing - metrics[0].bottomSideBearing) * pxPerDU) + 10; + int x = (int)(-metrics[0].leftSideBearing * pxPerDU) + 5; + int y = (int)((metrics[0].verticalOriginY - metrics[0].topSideBearing) * pxPerDU) + 5; + RECT bbRect; + + for (int attempt = 0; attempt < 2 && target == NULL; attempt++) { + hr = interop->CreateBitmapRenderTarget(NULL, width, height, &target); + if (FAILED(hr)) { + FREE_AND_RETURN + } + hr = target->DrawGlyphRun((FLOAT)x, + (FLOAT)y, + measuringMode >= 0 && measuringMode <= 2 ? (DWRITE_MEASURING_MODE)measuringMode : DWRITE_MEASURING_MODE_NATURAL, + &glyphRun, + params, + RGB(255,255,255), + &bbRect); + if (FAILED(hr) || bbRect.left > bbRect.right || bbRect.top > bbRect.bottom + || attempt > 0 && (bbRect.left < 0 || bbRect.top < 0 || bbRect.right > width || bbRect.bottom > height)) { + FREE_AND_RETURN + } + if (bbRect.left < 0 || bbRect.top < 0 || bbRect.right > width || bbRect.bottom > height) { + target->Release(); + target = NULL; + if (bbRect.right > width) width = bbRect.right; + if (bbRect.bottom > height) height = bbRect.bottom; + if (bbRect.left < 0) { + width -= bbRect.left; + x -= bbRect.left; + } + if (bbRect.top < 0) { + height -= bbRect.top; + y -= bbRect.top; + } + } + } + + HDC glyphDC = target->GetMemoryDC(); + HGDIOBJ glyphBitmap = GetCurrentObject(glyphDC, OBJ_BITMAP); + if (glyphBitmap == NULL) { + FREE_AND_RETURN + } + DIBSECTION dibSection; + if (GetObject(glyphBitmap, sizeof(DIBSECTION), &dibSection) == 0) { + FREE_AND_RETURN + } + + int glyphWidth = bbRect.right - bbRect.left; + int glyphHeight = bbRect.bottom - bbRect.top; + int glyphBytesWidth = glyphWidth * 3; + GlyphInfo* glyphInfo = (GlyphInfo*)SAFE_SIZE_STRUCT_ALLOC(malloc, sizeof(GlyphInfo), glyphBytesWidth, glyphHeight); + if (glyphInfo == NULL) { + FREE_AND_RETURN + } + glyphInfo->managed = UNMANAGED_GLYPH; + glyphInfo->cellInfo = NULL; + glyphInfo->image = (unsigned char*)glyphInfo+sizeof(GlyphInfo); + glyphInfo->rowBytes = glyphBytesWidth; + glyphInfo->width = glyphWidth; + glyphInfo->height = glyphHeight; + glyphInfo->advanceX = (float)((int)(metrics[0].advanceWidth * pxPerDU + 0.5)); + glyphInfo->advanceY = 0; + glyphInfo->topLeftX = (float)(bbRect.left - x); + glyphInfo->topLeftY = (float)(bbRect.top - y); + + int srcRowBytes = width * 4; + unsigned char* srcPtr = (unsigned char*) dibSection.dsBm.bmBits + srcRowBytes * bbRect.top; + unsigned char* destPtr = glyphInfo->image; + for (int y = 0; y < glyphHeight; y++) { + srcPtr += bbRect.left * 4; + for (int x = 0; x < glyphWidth; x++) { + // converting from BGRA to RGB + unsigned char b = *srcPtr++; + unsigned char g = *srcPtr++; + unsigned char r = *srcPtr++; + srcPtr++; + *destPtr++ = r; + *destPtr++ = g; + *destPtr++ = b; + } + srcPtr += (width - bbRect.right) * 4; + } + + FREE + return ptr_to_jlong(glyphInfo); +} + +} // extern "C" \ No newline at end of file -- cgit v1.2.3