JRE-11 Support text rendering via DirectWrite API on Windows

port commits 7b180f8d, cccbcab4 from JBR 9

port from JBR 11 to JBR 15 (cherry picked from commit 030f15834c)

cherry picked from commit b16ee45915
This commit is contained in:
Dmitry Batrak
2018-12-24 14:40:15 +03:00
committed by alexey.ushakov@jetbrains.com
parent 73e30c2761
commit 6c074f75f7
3 changed files with 392 additions and 19 deletions

View File

@@ -500,10 +500,12 @@ else ifeq ($(call isTargetOs, macosx), true)
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
LIBFONTMANAGER_CFLAGS += $(X_CFLAGS) -DLE_STANDALONE -DHEADLESS

View File

@@ -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,68 @@ 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;
// DirectWrite rendering options' values can be found in MSDN documentation
// for IDWriteBitmapRenderTarget::DrawGlyphRun method and its parameters
// (https://msdn.microsoft.com/en-us/library/windows/desktop/dd368167(v=vs.85).aspx)
// Measuring mode doesn't seem to impact glyph rendering directly,
// but values other that 0 ('natural' measuring mode) seem to limit possible other options' values.
private static int dwMeasuringMode = 0;
// Only 'natural' and 'natural symmetric' rendering modes seem to use requested gamma value,
// so only they can be used to produce valid glyph images.
// 'Natural' mode is said to look better for smaller font sizes.
private static int dwRenderingMode = 4;
// 'Full' ClearType
private static float dwClearTypeLevel = 1;
// Disabling enhanced contrast - it doesn't seem to be doing anything for white-on-black glyphs being generated.
private static float dwEnhancedContrast = 0;
// gamma correction will be applied when glyph image is blitted onto target surface, so for a cached glyph image
// we don't need any gamma correction
private static float dwGamma = 1;
// use monitor-default pixel geometry
private static int dwPixelGeometry = -1;
static {
if (FontUtilities.isWindows && !FontUtilities.useJDKScaler &&
!GraphicsEnvironment.isHeadless()) {
isXPorLater = initNative();
if (isXPorLater) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void 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;
}
});
}
}
}
@@ -305,37 +364,57 @@ public class FileFontStrike extends PhysicalStrike {
int rotation,
int fontDataSize);
private native long _getGlyphImageFromWindowsUsingDirectWrite(String family,
int style,
int size,
int glyphCode,
int rotation,
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,
((TrueTypeFont)fileFont).fontDataSize);
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) {
ptr = _getGlyphImageFromWindowsUsingDirectWrite(family, style, size, glyphCode, rotation,
dwMeasuringMode, dwRenderingMode, dwClearTypeLevel, dwEnhancedContrast, dwGamma, dwPixelGeometry);
if (ptr == 0 && FontUtilities.isLogging()) {
FontUtilities.logWarning("Failed to render glyph via DirectWrite: code=" + glyphCode
+ ", fontFamily=" + family + ", style=" + style + ", size=" + size + ", rotation=" + rotation);
}
}
if (ptr == 0) {
ptr = _getGlyphImageFromWindows(family, style, size, glyphCode,
desc.fmHint == INTVAL_FRACTIONALMETRICS_ON, rotation,
((TrueTypeFont)fileFont).fontDataSize);
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 {
}
if (ptr == 0) {
if (FontUtilities.isLogging()) {
FontUtilities.logWarning("Failed to render glyph using GDI: code=" + glyphCode
+ ", fontFamily=" + family + ", style=" + style
+ ", size=" + size);
}
return fileFont.getGlyphImage(pScalerContext, glyphCode);
ptr = fileFont.getGlyphImage(pScalerContext, glyphCode);
}
return ptr;
}
/* Try the native strikes first, then try the fileFont strike */

View File

@@ -0,0 +1,292 @@
/*
* 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 <stdio.h>
#include <malloc.h>
#include <math.h>
#include <windows.h>
#include <winuser.h>
#include <Dwrite.h>
#include <jni.h>
#include <jni_util.h>
#include <jlong_md.h>
#include <jdk_util.h>
#include <sizecalc.h>
#include <sun_font_FileFontStrike.h>
#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;
typedef HRESULT (WINAPI *DWriteCreateFactoryType)(DWRITE_FACTORY_TYPE, REFIID, IUnknown**);
static BOOL checkedForDirectWriteAvailability = FALSE;
static DWriteCreateFactoryType fDWriteCreateFactory = NULL;
JNIEXPORT jboolean JNICALL
Java_sun_font_FileFontStrike_isDirectWriteAvailable(JNIEnv *env, jclass unused) {
if (!checkedForDirectWriteAvailability) {
checkedForDirectWriteAvailability = TRUE;
HMODULE hDwrite = JDK_LoadSystemLibrary("Dwrite.dll");
if (hDwrite) {
fDWriteCreateFactory = (DWriteCreateFactoryType)GetProcAddress(hDwrite, "DWriteCreateFactory");
}
}
return fDWriteCreateFactory != NULL ? 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 rotation,
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';
if (fDWriteCreateFactory == NULL) {
FREE_AND_RETURN
}
HRESULT hr = (*fDWriteCreateFactory)(DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&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(),
&params);
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;
float advance = (float)((int)(metrics[0].advanceWidth * pxPerDU + 0.5));
RECT bbRect;
DWRITE_MATRIX mx;
switch (rotation) {
case 0: mx.m11 = mx.m22 = 1; mx.m12 = mx.m21 = mx.dx = mx.dy = 0; break;
case 1: mx.m11 = mx.m22 = 0; mx.m12 = -1; mx.m21 = 1; mx.dx = 0; mx.dy = (float)width; break;
case 2: mx.m11 = mx.m22 = -1; mx.m12 = mx.m21 = 0; mx.dx = (float)width; mx.dy = (float)height; break;
case 3: mx.m11 = mx.m22 = 0; mx.m12 = 1; mx.m21 = -1; mx.dx = (float)height; mx.dy = 0;
}
if (rotation == 1 || rotation == 3) {
int tmp = width;
width = height;
height = tmp;
}
float xTransformed = mx.m11 * x - mx.m12 * y + mx.dx;
float yTransformed = -mx.m21 * x + mx.m22 * y + mx.dy;
for (int attempt = 0; attempt < 2 && target == NULL; attempt++) {
hr = interop->CreateBitmapRenderTarget(NULL, width, height, &target);
if (FAILED(hr)) {
FREE_AND_RETURN
}
hr = target->SetCurrentTransform(&mx);
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;
switch (rotation) {
case 0: x -= bbRect.left; break;
case 1: y -= bbRect.left; break;
case 2: x += bbRect.left; break;
case 3: y += bbRect.left;
}
}
if (bbRect.top < 0) {
height -= bbRect.top;
switch (rotation) {
case 0: y -= bbRect.top; break;
case 1: x += bbRect.top; break;
case 2: y += bbRect.top; break;
case 3: x -= 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 = rotation == 0 ? advance : rotation == 2 ? -advance : 0;
glyphInfo->advanceY = rotation == 3 ? advance : rotation == 1 ? -advance : 0;
glyphInfo->topLeftX = bbRect.left - xTransformed;
glyphInfo->topLeftY = bbRect.top - yTransformed;
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"