diff --git a/src/java.desktop/macosx/classes/sun/font/CCharToGlyphMapper.java b/src/java.desktop/macosx/classes/sun/font/CCharToGlyphMapper.java index 5fa60f1b7ec29..aac3eff5af411 100644 --- a/src/java.desktop/macosx/classes/sun/font/CCharToGlyphMapper.java +++ b/src/java.desktop/macosx/classes/sun/font/CCharToGlyphMapper.java @@ -77,7 +77,8 @@ public synchronized boolean charsToGlyphsNS(int count, if (code < FontUtilities.MIN_LAYOUT_CHARCODE) { continue; - } else if (FontUtilities.isComplexCharCode(code)) { + } else if (FontUtilities.isComplexCharCode(code) || + CharToGlyphMapper.isVariationSelector(code)) { return true; } else if (code >= 0x10000) { i += 1; // Empty glyph slot after surrogate @@ -88,6 +89,21 @@ public synchronized boolean charsToGlyphsNS(int count, return false; } + public synchronized int charToVariationGlyph(int unicode, int variationSelector) { + if (variationSelector == 0) { + return charToGlyph(unicode); + } + final char[] unicodeArray = new char[4]; + final int[] glyphArray = new int[4]; + + int size = Character.toChars(unicode, unicodeArray, 0); + size += Character.toChars(variationSelector, unicodeArray, size); + + nativeCharsToGlyphs(fFont.getNativeFontPtr(), size, unicodeArray, glyphArray); + + return glyphArray[0]; + } + public synchronized int charToGlyph(char unicode) { final int glyph = cache.get(unicode); if (glyph != 0) return glyph; @@ -105,10 +121,8 @@ public synchronized int charToGlyph(int unicode) { if (unicode >= 0x10000) { int[] glyphs = new int[2]; char[] surrogates = new char[2]; - int base = unicode - 0x10000; - surrogates[0] = (char)((base >>> 10) + HI_SURROGATE_START); - surrogates[1] = (char)((base % 0x400) + LO_SURROGATE_START); - charsToGlyphs(2, surrogates, glyphs); + int size = Character.toChars(unicode, surrogates, 0); + charsToGlyphs(size, surrogates, glyphs); return glyphs[0]; } else { return charToGlyph((char)unicode); diff --git a/src/java.desktop/macosx/classes/sun/font/CCompositeGlyphMapper.java b/src/java.desktop/macosx/classes/sun/font/CCompositeGlyphMapper.java index 1897c7af64203..33ff15b5a7d2e 100644 --- a/src/java.desktop/macosx/classes/sun/font/CCompositeGlyphMapper.java +++ b/src/java.desktop/macosx/classes/sun/font/CCompositeGlyphMapper.java @@ -51,16 +51,34 @@ public boolean canDisplay(char ch) { return glyph != missingGlyph; } - private int convertToGlyph(int unicode) { + @Override + public int charToVariationGlyph(int unicode, int variationSelector) { + if (variationSelector == 0) { + return charToGlyph(unicode); + } else { + int glyph = convertToGlyph(unicode, variationSelector); + if (glyph == missingGlyph) glyph = charToGlyph(unicode); + return glyph; + } + } + + @Override + protected int convertToGlyph(int unicode) { + int glyph = convertToGlyph(unicode, 0); + return glyph; + } + + @Override + protected int convertToGlyph(int unicode, int variationSelector) { for (int slot = 0; slot < font.numSlots; slot++) { CharToGlyphMapper mapper = getSlotMapper(slot); - int glyphCode = mapper.charToGlyph(unicode); + int glyphCode = mapper.charToVariationGlyph(unicode, variationSelector); // The CFont Mappers will return a negative code // for fonts that will fill the glyph from fallbacks // - cascading font in OSX-speak. But we need to be // know here that only the codes > 0 are really present. if (glyphCode > 0) { - glyphCode = compositeGlyphCode(slot, glyphCode); + glyphCode = font.compositeGlyphCode(slot, glyphCode); return glyphCode; } } @@ -87,69 +105,4 @@ public int charToGlyph(int unicode) { public int charToGlyph(char unicode) { return convertToGlyph(unicode); } - - public boolean charsToGlyphsNS(int count, char[] unicodes, int[] glyphs) { - - for (int i=0; i= HI_SURROGATE_START && - code <= HI_SURROGATE_END && i < count - 1) { - char low = unicodes[i + 1]; - - if (low >= LO_SURROGATE_START && - low <= LO_SURROGATE_END) { - code = (code - HI_SURROGATE_START) * - 0x400 + low - LO_SURROGATE_START + 0x10000; - glyphs[i + 1] = INVISIBLE_GLYPH_ID; - } - } - - glyphs[i] = convertToGlyph(code); - - if (code < FontUtilities.MIN_LAYOUT_CHARCODE) { - continue; - } - else if (FontUtilities.isComplexCharCode(code)) { - return true; - } - else if (code >= 0x10000) { - i += 1; // Empty glyph slot after surrogate - continue; - } - } - - return false; - } - - public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) { - for (int i=0; i= HI_SURROGATE_START && - code <= HI_SURROGATE_END && i < count - 1) { - char low = unicodes[i + 1]; - - if (low >= LO_SURROGATE_START && - low <= LO_SURROGATE_END) { - code = (code - HI_SURROGATE_START) * - 0x400 + low - LO_SURROGATE_START + 0x10000; - - glyphs[i] = convertToGlyph(code); - i += 1; // Empty glyph slot after surrogate - glyphs[i] = INVISIBLE_GLYPH_ID; - continue; - } - } - - glyphs[i] = convertToGlyph(code); - } - } - - public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) { - for (int i=0; i 0) { + int slotMask = font2D + .getSlotInfoForGlyph(gv.getGlyphCode(0)) + .getSlotMask(); + for (int i = 0; i < length; i++) { + int glyphCode = gv.getGlyphCode(i); + if ((glyphCode & slotMask) != 0) { + return true; + } } } return false; } - private Font getSlotFont(Font font, int slot) { - Font2D f2d = FontUtilities.getFont2D(font); - if (f2d instanceof CFont) { - CompositeFont cf = ((CFont)f2d).getCompositeFont2D(); - PhysicalFont pf = cf.getSlotFont(slot); - Font f = new Font(pf.getFontName(null), - font.getStyle(), font.getSize()); - return f; - } - return null; - } - - private GlyphVector getGlyphVectorWithRange(final Font font, final GlyphVector gV, int start, int count) { + private GlyphVector getGlyphVectorWithRange(final Font font, final GlyphVector gV, int start, int count, int slotShift) { int[] glyphs = new int[count]; for (int i = 0; i < count; i++) { - glyphs[i] = gV.getGlyphCode(start+i) & CompositeGlyphMapper.GLYPHMASK; + glyphs[i] = gV.getGlyphCode(start+i) >>> slotShift; } // Positions should be null to recalculate by native methods, // if GV was segmented. @@ -119,11 +113,11 @@ private GlyphVector getGlyphVectorWithRange(final Font font, final GlyphVector g return sgv; } - private int getLengthOfSameSlot(final GlyphVector gV, final int targetSlot, final int start, final int length) { + private int getLengthOfSameSlot(final GlyphVector gV, final int targetSlot, final int slotMask, + final int start, final int length) { int count = 1; for (; start + count < length; count++) { - int slot = (gV.getGlyphCode(start + count) & - CompositeGlyphMapper.SLOTMASK) >> 24; + int slot = gV.getGlyphCode(start + count) & slotMask; if (targetSlot != slot) { break; } @@ -143,25 +137,33 @@ private void drawGlyphVectorImpl(final SunGraphics2D sg2d, final GlyphVector gV, public void drawGlyphVector(final SunGraphics2D sg2d, final GlyphVector gV, final float x, final float y) { final Font prevFont = sg2d.getFont(); - sg2d.setFont(gV.getFont()); + Font gvFont = gV.getFont(); + Font2D f2d = FontUtilities.getFont2D(gvFont); + if (f2d instanceof FontSubstitution fs) { + f2d = fs.getCompositeFont2D(); + } - if (hasSlotData(gV)) { + if (hasSlotData(gV, f2d)) { final int length = gV.getNumGlyphs(); float[] positions = gV.getGlyphPositions(0, length, null); int start = 0; while (start < length) { - int slot = (gV.getGlyphCode(start) & - CompositeGlyphMapper.SLOTMASK) >> 24; - sg2d.setFont(getSlotFont(gV.getFont(), slot)); - int count = getLengthOfSameSlot(gV, slot, start, length); + int glyphCode = gV.getGlyphCode(start); + Font2D.SlotInfo slotInfo = f2d.getSlotInfoForGlyph(glyphCode); + int slotMask = slotInfo.getSlotMask(); + int slot = glyphCode & slotMask; + sg2d.setFont(new Font(slotInfo.font.getFontName(null), + gvFont.getStyle(), gvFont.getSize())); + int count = getLengthOfSameSlot(gV, slot, slotMask, start, length); GlyphVector rangeGV = getGlyphVectorWithRange(sg2d.getFont(), - gV, start, count); + gV, start, count, slotInfo.slotShift); drawGlyphVectorImpl(sg2d, rangeGV, x + positions[start * 2], y + positions[start * 2 + 1]); start += count; } } else { + sg2d.setFont(gvFont); drawGlyphVectorImpl(sg2d, gV, x, y); } sg2d.setFont(prevFont); diff --git a/src/java.desktop/macosx/native/libawt_lwawt/font/AWTStrike.m b/src/java.desktop/macosx/native/libawt_lwawt/font/AWTStrike.m index 1e2ba23d9aa40..48b661eeebc71 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/font/AWTStrike.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/font/AWTStrike.m @@ -205,20 +205,11 @@ + (AWTStrike *) awtStrikeForFont:(AWTFont *)awtFont JNI_COCOA_EXIT(env); } -/* - * Class: sun_font_CStrike - * Method: getNativeGlyphOutline - * Signature: (JJIDD)Ljava/awt/geom/GeneralPath; - */ -JNIEXPORT jobject JNICALL -Java_sun_font_CStrike_getNativeGlyphOutline - (JNIEnv *env, jclass clazz, - jlong awtStrikePtr, jint glyphCode, jdouble xPos, jdouble yPos) +jobject getGlyphOutline(JNIEnv *env, AWTStrike *awtStrike, + CTFontRef font, CGGlyph glyph, + jdouble xPos, jdouble yPos) { jobject generalPath = NULL; - -JNI_COCOA_ENTER(env); - AWTPathRef path = NULL; jfloatArray pointCoords = NULL; jbyteArray pointTypes = NULL; @@ -228,21 +219,11 @@ + (AWTStrike *) awtStrikeForFont:(AWTFont *)awtFont AWT_FONT_CLEANUP_SETUP; - AWTStrike *awtStrike = (AWTStrike *)jlong_to_ptr(awtStrikePtr); - AWTFont *awtfont = awtStrike->fAWTFont; - -AWT_FONT_CLEANUP_CHECK(awtfont); - // inverting the shear order and sign to compensate for the flipped coordinate system CGAffineTransform tx = awtStrike->fTx; tx.tx += xPos; tx.ty += yPos; - // get the right font and glyph for this "Java GlyphCode" - - CGGlyph glyph; - const CTFontRef font = CTS_CopyCTFallbackFontAndGlyphForJavaGlyphCode(awtfont, glyphCode, &glyph); - // get the advance of this glyph CGSize advance; CTFontGetAdvancesForGlyphs(font, kCTFontDefaultOrientation, &glyph, &advance, 1); @@ -255,7 +236,6 @@ + (AWTStrike *) awtStrikeForFont:(AWTFont *)awtFont tx = awtStrike->fTx; tx = CGAffineTransformConcat(tx, sInverseTX); AWTGetGlyphOutline(&glyph, (NSFont *)font, &advance, &tx, 0, 1, &path); - CFRelease(font); pointCoords = (*env)->NewFloatArray(env, path->fNumberOfDataElements); AWT_FONT_CLEANUP_CHECK(pointCoords); @@ -289,10 +269,213 @@ + (AWTStrike *) awtStrikeForFont:(AWTFont *)awtFont } AWT_FONT_CLEANUP_FINISH; -JNI_COCOA_EXIT(env); return generalPath; } +/* + * Class: sun_font_CStrike + * Method: getNativeGlyphOutline + * Signature: (JJIDD)Ljava/awt/geom/GeneralPath; + */ +JNIEXPORT jobject JNICALL +Java_sun_font_CStrike_getNativeGlyphOutline + (JNIEnv *env, jclass clazz, + jlong awtStrikePtr, jint glyphCode, jdouble xPos, jdouble yPos) { + jobject generalPath = NULL; + + JNI_COCOA_ENTER(env); + AWT_FONT_CLEANUP_SETUP; + + AWTStrike *awtStrike = (AWTStrike *)jlong_to_ptr(awtStrikePtr); + AWTFont *awtfont = awtStrike->fAWTFont; + AWT_FONT_CLEANUP_CHECK(awtfont); + + // get the right font and glyph for this "Java GlyphCode" + CGGlyph glyph; + const CTFontRef font = CTS_CopyCTFallbackFontAndGlyphForJavaGlyphCode(awtfont, glyphCode, &glyph); + + generalPath = getGlyphOutline(env, awtStrike, font, glyph, xPos, yPos); + + cleanup: + CFRelease(font); + + AWT_FONT_CLEANUP_FINISH; + JNI_COCOA_EXIT(env); + + return generalPath; +} + +// OpenType data is Big-Endian +#define GET_BE_INT32(data, i) CFSwapInt32BigToHost(*(const UInt32*) ((const UInt8*) (data) + (i))) +#define GET_BE_INT16(data, i) CFSwapInt16BigToHost(*(const UInt16*) ((const UInt8*) (data) + (i))) + +static bool addBitmapRenderData(JNIEnv *env, AWTStrike *awtStrike, + CTFontRef font, CGGlyph glyph, + jdouble xPos, jdouble yPos, jobject result) { + bool success = false; + + DECLARE_CLASS_RETURN(jc_GlyphRenderData, "sun/font/GlyphRenderData", false); + DECLARE_METHOD_RETURN(GlyphRenderDataAddBitmap, jc_GlyphRenderData, "addBitmap", "(DDDDDDIIII[I)V", false); + + AWT_FONT_CLEANUP_SETUP; + + CTFontDescriptorRef descriptor = NULL; + CGFontRef cgFont = CTFontCopyGraphicsFont(font, &descriptor); + + CFDataRef sbixTable = CGFontCopyTableForTag(cgFont, kCTFontTableSbix); + if (sbixTable == NULL) { + goto cleanup; + } + + // Parse sbix table + CFIndex sbixSize = CFDataGetLength(sbixTable); + if (sbixSize < 8) { + goto cleanup; // Corrupted table + } + const UInt8* sbix = CFDataGetBytePtr(sbixTable); + UInt32 numStrikes = GET_BE_INT32(sbix, 4); + if (8 + 4 * numStrikes > sbixSize) { + goto cleanup; // Corrupted table + } + // Find last strike which has data for our glyph + // Last is usually the biggest + const UInt8* glyphData = NULL; + UInt32 size; + UInt16 ppem, ppi; + for (int i = numStrikes - 1; i >= 0; i--) { + const UInt8* strike = sbix + GET_BE_INT32(sbix, 8 + 4 * i); + if (strike + 12 + 4 * glyph > sbix + sbixSize) { + goto cleanup; // Corrupted table + } + UInt32 offset = GET_BE_INT32(strike, 4 + 4 * glyph); + size = GET_BE_INT32(strike, 8 + 4 * glyph) - offset; + if (size > 0) { + ppem = GET_BE_INT16(strike, 0); + ppi = GET_BE_INT16(strike, 2); + glyphData = strike + offset; + break; + } + } + if (glyphData == NULL) { + goto cleanup; + } + if (glyphData + 4 > sbix + sbixSize) { + goto cleanup; // Corrupted table + } + + // Read glyph data + FourCharCode graphicType = GET_BE_INT32(glyphData, 4); + glyphData += 8; + size -= 8; + if (glyphData + size > sbix + sbixSize) { + goto cleanup; // Corrupted table + } + + // Decode glyph image + CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, glyphData, size, NULL); + CGImageRef image = NULL; + if (graphicType == 'jpg ') { + image = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, false, kCGRenderingIntentDefault); + } else if (graphicType == 'png ') { + image = CGImageCreateWithPNGDataProvider(dataProvider, NULL, false, kCGRenderingIntentDefault); + } + CGDataProviderRelease(dataProvider); + + if (image != NULL) { + CGBitmapInfo info = CGImageGetBitmapInfo(image); + size_t bits = CGImageGetBitsPerPixel(image); + jint colorModel = -1; + if (info & (kCGImageAlphaPremultipliedLast | kCGImageAlphaLast)) { + colorModel = 0; // RGBA + } else if (info & (kCGImageAlphaPremultipliedFirst | kCGImageAlphaFirst)) { + colorModel = 1; // ARGB + } + if (colorModel != -1 && (info & kCGBitmapFloatComponents) == 0 && bits == 32) { + size_t width = CGImageGetWidth(image); + size_t height = CGImageGetHeight(image); + size_t pitch = CGImageGetBytesPerRow(image) / 4; + dataProvider = CGImageGetDataProvider(image); + CFDataRef data = CGDataProviderCopyData(dataProvider); + + jbyteArray array = (*env)->NewIntArray(env, pitch * height); + (*env)->SetIntArrayRegion(env, array, 0, pitch * height, (const jint*) CFDataGetBytePtr(data)); + CFRelease(data); + + double pointSize = 72.0 * ppem / ppi; + double scale = 1.0 / pointSize; + font = CTFontCreateWithGraphicsFont(cgFont, pointSize, NULL, descriptor); + CGRect bbox = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationDefault, &glyph, NULL, 1); + CFRelease(font); + double tx = bbox.origin.x + xPos * pointSize / awtStrike->fSize; + double ty = -bbox.origin.y - (double) height + yPos * pointSize / awtStrike->fSize; + + jdouble m00 = awtStrike->fTx.a * scale, m10 = awtStrike->fTx.b * scale; + jdouble m01 = -awtStrike->fTx.c * scale, m11 = -awtStrike->fTx.d * scale; + jdouble m02 = m00 * tx + m01 * ty, m12 = m10 * tx + m11 * ty; + + (*env)->CallVoidMethod(env, result, GlyphRenderDataAddBitmap, + m00, m10, m01, m11, m02, m12, + width, height, pitch, 0, array); + success = true; + } + CGImageRelease(image); + } + + // Cleanup + cleanup: + if (sbixTable) { + CFRelease(sbixTable); + } + if (cgFont) { + CFRelease(cgFont); + } + if (descriptor) { + CFRelease(descriptor); + } + + AWT_FONT_CLEANUP_FINISH; + return success; +} + +/* + * Class: sun_font_CStrike + * Method: getNativeGlyphRenderData + * Signature: (JIDDLsun/font/GlyphRenderData;)V + */ +JNIEXPORT void JNICALL +Java_sun_font_CStrike_getNativeGlyphRenderData + (JNIEnv *env, jclass clazz, + jlong awtStrikePtr, jint glyphCode, jdouble xPos, jdouble yPos, jobject result) +{ + JNI_COCOA_ENTER(env); + + DECLARE_CLASS(jc_GlyphRenderData, "sun/font/GlyphRenderData"); + DECLARE_FIELD(GlyphRenderDataOutline, jc_GlyphRenderData, "outline", "Ljava/awt/geom/GeneralPath;") + + AWT_FONT_CLEANUP_SETUP; + + AWTStrike *awtStrike = (AWTStrike *)jlong_to_ptr(awtStrikePtr); + AWTFont *awtfont = awtStrike->fAWTFont; + AWT_FONT_CLEANUP_CHECK(awtfont); + + // get the right font and glyph for this "Java GlyphCode" + CGGlyph glyph; + const CTFontRef font = CTS_CopyCTFallbackFontAndGlyphForJavaGlyphCode(awtfont, glyphCode, &glyph); + + if (!addBitmapRenderData(env, awtStrike, font, glyph, xPos, yPos, result)) { + jobject gp = getGlyphOutline(env, awtStrike, font, glyph, xPos, yPos); + if (gp != NULL) { + (*env)->SetObjectField(env, result, GlyphRenderDataOutline, gp); + } + } + + cleanup: + CFRelease(font); + + AWT_FONT_CLEANUP_FINISH; + JNI_COCOA_EXIT(env); +} + /* * Class: sun_font_CStrike * Method: getGlyphImagePtrsNative diff --git a/src/java.desktop/macosx/native/libawt_lwawt/font/CGGlyphImages.m b/src/java.desktop/macosx/native/libawt_lwawt/font/CGGlyphImages.m index f6e8fe6af552e..0a43bb550f1de 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/font/CGGlyphImages.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/font/CGGlyphImages.m @@ -585,6 +585,7 @@ @implementation CGGI_GlyphCanvas glyphInfo->height = height; glyphInfo->rowBytes = width * pixelSize; glyphInfo->cellInfo = NULL; + glyphInfo->format = (UInt8) pixelSize; #ifdef USE_IMAGE_ALIGNED_MEMORY glyphInfo->image = image; diff --git a/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.h b/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.h index cbd3dc5923b2d..e8a692f8b301b 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.h +++ b/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.h @@ -35,6 +35,10 @@ #define HI_SURROGATE_END 0xDBFF #define LO_SURROGATE_START 0xDC00 #define LO_SURROGATE_END 0xDFFF +#define VS_START 0xFE00 +#define VS_END 0xFE0F +#define VSS_START 0xE0100 +#define VSS_END 0xE01FF /* * Transform Unicode characters into glyphs. diff --git a/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.m b/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.m index 5663b3084ac7c..38895b80a48d4 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.m @@ -88,6 +88,19 @@ CFRelease(ctStateDict); // GC } +int NextUnicode(const UniChar unicodes[], UnicodeScalarValue *codePoint, const size_t index, const size_t limit) { + if (index >= limit) { + return 0; + } + UniChar unicode = unicodes[index]; + UniChar nextUnicode = (index+1) < limit ? unicodes[index+1] : 0; + bool surrogatePair = unicode >= HI_SURROGATE_START && unicode <= HI_SURROGATE_END + && nextUnicode >= LO_SURROGATE_START && nextUnicode <= LO_SURROGATE_END; + *codePoint = surrogatePair ? (((int)(unicode - HI_SURROGATE_START)) << 10) + + nextUnicode - LO_SURROGATE_START + 0x10000 : unicode; + return surrogatePair ? 2 : 1; +} + /* * Transform Unicode characters into glyphs. * @@ -101,36 +114,41 @@ { CTFontGetGlyphsForCharacters((CTFontRef)font->fFont, unicodes, glyphs, count); - size_t i; - for (i = 0; i < count; i++) { - UniChar unicode = unicodes[i]; - UniChar nextUnicode = (i+1) < count ? unicodes[i+1] : 0; - bool surrogatePair = unicode >= HI_SURROGATE_START && unicode <= HI_SURROGATE_END - && nextUnicode >= LO_SURROGATE_START && nextUnicode <= LO_SURROGATE_END; + size_t i, size; + for (i = 0; i < count; i += size) { + UnicodeScalarValue codePoint, variationCodePoint; + int codePointSize = size = NextUnicode(unicodes, &codePoint, i, count); + if (size == 0) { + break; + } + + int variationSize = NextUnicode(unicodes, &variationCodePoint, i + size , count); + bool hasVariationSelector = variationSize > 0 && + ((variationCodePoint >= VSS_START && variationCodePoint <= VSS_END) || + (variationCodePoint >= VS_START && variationCodePoint <= VS_END)); + if (hasVariationSelector) { + size += variationSize; + } CGGlyph glyph = glyphs[i]; - if (glyph > 0) { + if (glyph > 0 && (!hasVariationSelector || glyphs[i + codePointSize] > 0)) { glyphsAsInts[i] = glyph; - if (surrogatePair) i++; continue; } const CTFontRef fallback = JRSFontCreateFallbackFontForCharacters((CTFontRef)font->fFont, &unicodes[i], - surrogatePair ? 2 : 1); + size); if (fallback) { - CTFontGetGlyphsForCharacters(fallback, &unicodes[i], &glyphs[i], surrogatePair ? 2 : 1); + CTFontGetGlyphsForCharacters(fallback, &unicodes[i], &glyphs[i], size); glyph = glyphs[i]; CFRelease(fallback); } if (glyph > 0) { - int codePoint = surrogatePair ? (((int)(unicode - HI_SURROGATE_START)) << 10) - + nextUnicode - LO_SURROGATE_START + 0x10000 : unicode; glyphsAsInts[i] = -codePoint; // set the glyph code to the negative unicode value } else { glyphsAsInts[i] = 0; // CoreText couldn't find a glyph for this character either } - if (surrogatePair) i++; } } diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTextRenderer.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTextRenderer.m index a52d32a5dea97..e1a7bee4dd831 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTextRenderer.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTextRenderer.m @@ -713,7 +713,7 @@ static void DisableColorGlyphPainting(MTLContext *mtlc) { J2dTraceLn2(J2D_TRACE_INFO, "Glyph width = %d height = %d", ginfo->width, ginfo->height); J2dTraceLn1(J2D_TRACE_INFO, "rowBytes = %d", ginfo->rowBytes); - if (ginfo->rowBytes == ginfo->width) { + if (ginfo->format == sun_font_StrikeCache_PIXEL_FORMAT_GREYSCALE) { // grayscale or monochrome glyph data if (ginfo->width <= MTLTR_CACHE_CELL_WIDTH && ginfo->height <= MTLTR_CACHE_CELL_HEIGHT) @@ -724,7 +724,7 @@ static void DisableColorGlyphPainting(MTLContext *mtlc) { J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawGlyphList Grayscale no cache"); ok = MTLTR_DrawGrayscaleGlyphNoCache(mtlc, ginfo, x, y, dstOps); } - } else if (ginfo->rowBytes == ginfo->width * 4) { + } else if (ginfo->format == sun_font_StrikeCache_PIXEL_FORMAT_BGRA) { J2dTraceLn(J2D_TRACE_INFO, "MTLTR_DrawGlyphList color glyph no cache"); ok = MTLTR_DrawColorGlyphNoCache(mtlc, ginfo, x, y, dstOps); flushBeforeLCD = JNI_FALSE; diff --git a/src/java.desktop/share/classes/sun/font/CMap.java b/src/java.desktop/share/classes/sun/font/CMap.java index e025b64d9ea4e..57d8b99c9f5d1 100644 --- a/src/java.desktop/share/classes/sun/font/CMap.java +++ b/src/java.desktop/share/classes/sun/font/CMap.java @@ -1064,7 +1064,7 @@ char getGlyph(int charCode) { public static final NullCMapClass theNullCmap = new NullCMapClass(); - final int getControlCodeGlyph(int charCode, boolean noSurrogates) { + static int getControlCodeGlyph(int charCode, boolean noSurrogates) { if (charCode < 0x0010) { switch (charCode) { case 0x0009: @@ -1077,7 +1077,7 @@ final int getControlCodeGlyph(int charCode, boolean noSurrogates) { return -1; } - final char getFormatCharGlyph(int charCode) { + static char getFormatCharGlyph(int charCode) { if (charCode >= 0x200c) { if ((charCode <= 0x200f) || (charCode >= 0x2028 && charCode <= 0x202e) || @@ -1144,15 +1144,17 @@ static class UVS { } } - static final int VS_NOGLYPH = 0; - private int getGlyph(int charCode, int variationSelector) { - int targetSelector = -1; + private int findVariationSelectorIndex(int variationSelector) { for (int i = 0; i < numSelectors; i++) { if (selector[i] == variationSelector) { - targetSelector = i; - break; + return i; } } + return -1; + } + + static final int VS_NOGLYPH = 0; + private int getGlyph(int charCode, int targetSelector) { if (targetSelector == -1) { return VS_NOGLYPH; } @@ -1168,15 +1170,19 @@ private int getGlyph(int charCode, int variationSelector) { } char getVariationGlyph(int charCode, int variationSelector) { + if (variationSelector == 0) { + return getGlyph(charCode); + } char glyph = 0; - if (uvs == null) { - glyph = getGlyph(charCode); - } else { - int result = uvs.getGlyph(charCode, variationSelector); - if (result > 0) { - glyph = (char)(result & 0xFFFF); - } else { - glyph = getGlyph(charCode); + if (uvs != null) { + int targetSelector = uvs.findVariationSelectorIndex(variationSelector); + if (targetSelector != -1) { + int result = uvs.getGlyph(charCode, targetSelector); + if (result > 0) { + glyph = (char)(result & 0xFFFF); + } else { + glyph = getGlyph(charCode); + } } } return glyph; diff --git a/src/java.desktop/share/classes/sun/font/CharToGlyphMapper.java b/src/java.desktop/share/classes/sun/font/CharToGlyphMapper.java index bac385d81db23..0672ac7c8f609 100644 --- a/src/java.desktop/share/classes/sun/font/CharToGlyphMapper.java +++ b/src/java.desktop/share/classes/sun/font/CharToGlyphMapper.java @@ -88,14 +88,46 @@ public int charToVariationGlyph(int unicode, int variationSelector) { public abstract int getNumGlyphs(); - public abstract void charsToGlyphs(int count, - char[] unicodes, int[] glyphs); + private boolean charsToGlyphs(int count, char[] unicodes, int[] glyphs, boolean checkShaping) { + for (int i = 0; i < count; i++) { + int code = unicodes[i]; // char is unsigned. + if (code >= HI_SURROGATE_START && code <= HI_SURROGATE_END && i < count - 1) { + char low = unicodes[i + 1]; + if (low >= LO_SURROGATE_START && low <= LO_SURROGATE_END) { + code = (code - HI_SURROGATE_START) * 0x400 + low - LO_SURROGATE_START + 0x10000; + glyphs[i + 1] = INVISIBLE_GLYPH_ID; + } + } + + glyphs[i] = charToGlyph(code); + + if (code >= FontUtilities.MIN_LAYOUT_CHARCODE) { + if (checkShaping && + (FontUtilities.isComplexCharCode(code) || + CharToGlyphMapper.isVariationSelector(code))) { + return true; + } else if (code >= 0x10000) { + i++; // Empty glyph slot after surrogate. + } + } + } + return false; + } - public abstract boolean charsToGlyphsNS(int count, - char[] unicodes, int[] glyphs); + public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) { + charsToGlyphs(count, unicodes, glyphs, false); + } - public abstract void charsToGlyphs(int count, - int[] unicodes, int[] glyphs); + public boolean charsToGlyphsNS(int count, char[] unicodes, int[] glyphs) { + return charsToGlyphs(count, unicodes, glyphs, true); + } + + public void charsToGlyphs(int count, + int[] unicodes, int[] glyphs) { + for (int i = 0; i < count; i++) { + glyphs[i] = charToGlyph(unicodes[i]); + } + } public static boolean isVariationSelector(int charCode) { return ((charCode >= VSS_START && charCode <= VSS_END) || diff --git a/src/java.desktop/share/classes/sun/font/CompositeFont.java b/src/java.desktop/share/classes/sun/font/CompositeFont.java index bc136d75cfa1a..cb1d73c0c9cda 100644 --- a/src/java.desktop/share/classes/sun/font/CompositeFont.java +++ b/src/java.desktop/share/classes/sun/font/CompositeFont.java @@ -26,6 +26,8 @@ package sun.font; import java.awt.Font; +import java.util.Arrays; +import java.util.List; /* Remind: need to enhance to extend component list with a fallback * list, which is not used in metrics or queries on the composite, but @@ -46,13 +48,14 @@ public final class CompositeFont extends Font2D { /* because components can be lazily initialised the components field is * private, to ensure all clients call getSlotFont() */ - private PhysicalFont[] components; + private Font2D[] components; int numSlots; int numMetricsSlots; int[] exclusionRanges; int[] maxIndices; int numGlyphs = 0; int localeSlot = -1; // primary slot for this locale. + int slotShift, slotMask; /* See isStdComposite() for when/how this is used */ boolean isStdComposite = true; @@ -66,65 +69,58 @@ public CompositeFont(String name, String[] compFileNames, fullName = name; componentFileNames = compFileNames; componentNames = compNames; - if (compNames == null) { - numSlots = componentFileNames.length; - } else { - numSlots = componentNames.length; - } - /* We will limit the number of slots to 254. - * We store the slot for a glyph id in a byte and we may use one slot - * for an EUDC font, and we may also create a composite + /* We will limit the number of slots to 255. + * Previously we stored the slot for a glyph + * id in a byte, and we may create a composite * using this composite as a backup for a physical font. - * So we want to leave space for the two additional slots. + * That's not a problem anymore, as number of bits + * reserved for a slot index can vary (see #slotShift), + * but the more it takes, the less is left for actual + * glyph code, so we'll keep this limit for now. */ - numSlots = (numSlots <= 254) ? numSlots : 254; + List additionalFallbackFonts = fm.getAdditionalFallbackFonts(); + numSlots = Math.min(255, additionalFallbackFonts.size() + + (compNames != null ? compNames.length : compFileNames.length)); /* Only the first "numMetricsSlots" slots are used for font metrics. * the rest are considered "fallback" slots". */ - numMetricsSlots = metricsSlotCnt; + numMetricsSlots = Math.min(metricsSlotCnt, numSlots); exclusionRanges = exclRanges; maxIndices = maxIndexes; + components = new Font2D[numSlots]; + deferredInitialisation = new boolean[numSlots]; + if (defer) { + Arrays.fill(deferredInitialisation, 0, numMetricsSlots, true); + Arrays.fill(deferredInitialisation, numMetricsSlots + + additionalFallbackFonts.size(), numSlots, true); + } /* - * See if this is a windows locale which has a system EUDC font. - * If so add it as the final fallback component of the composite. + * Insert additional fallback fonts if any. * The caller could be responsible for this, but for now it seems * better that it is handled internally to the CompositeFont class. */ - if (fm.getEUDCFont() != null) { + if (!additionalFallbackFonts.isEmpty()) { int msCnt = numMetricsSlots; - int fbCnt = numSlots - msCnt; - numSlots++; + int adCnt = additionalFallbackFonts.size(); + int fbCnt = numSlots - msCnt - adCnt; if (componentNames != null) { componentNames = new String[numSlots]; System.arraycopy(compNames, 0, componentNames, 0, msCnt); - componentNames[msCnt] = fm.getEUDCFont().getFontName(null); - System.arraycopy(compNames, msCnt, - componentNames, msCnt+1, fbCnt); + for (int i = 0; i < adCnt; i++) { + componentNames[msCnt + i] = + additionalFallbackFonts.get(i).getFontName(null); + } + System.arraycopy(compNames, msCnt, componentNames, msCnt+adCnt, fbCnt); } if (componentFileNames != null) { componentFileNames = new String[numSlots]; - System.arraycopy(compFileNames, 0, - componentFileNames, 0, msCnt); - System.arraycopy(compFileNames, msCnt, - componentFileNames, msCnt+1, fbCnt); + System.arraycopy(compFileNames, 0, componentFileNames, 0, msCnt); + System.arraycopy(compFileNames, msCnt, componentFileNames, msCnt+adCnt, fbCnt); } - components = new PhysicalFont[numSlots]; - components[msCnt] = fm.getEUDCFont(); - deferredInitialisation = new boolean[numSlots]; - if (defer) { - for (int i=0; i>> slotShift; + } + + int compositeGlyphCode(int slot, int glyphCode) { + return (glyphCode << slotShift) | (slot & slotMask); } /* This is used for deferred initialisation, so that the components of @@ -300,15 +317,10 @@ private void doDeferredInitialisation(int slot) { * return composite font which cannot be casted to * physical font. */ - try { - components[slot] = - (PhysicalFont) fm.findFont2D(componentNames[slot], - style, - FontManager.PHYSICAL_FALLBACK); - } catch (ClassCastException cce) { - /* Assign default physical font to the slot */ - components[slot] = fm.getDefaultPhysicalFont(); - } + components[slot] = + fm.findFont2D(componentNames[slot], + style, + FontManager.PHYSICAL_FALLBACK); } } deferredInitialisation[slot] = false; @@ -316,7 +328,7 @@ private void doDeferredInitialisation(int slot) { } /* To called only by FontManager.replaceFont */ - void replaceComponentFont(PhysicalFont oldFont, PhysicalFont newFont) { + void replaceComponentFont(Font2D oldFont, Font2D newFont) { if (components == null) { return; } @@ -354,7 +366,7 @@ public boolean isExcludedChar(int slot, int charcode) { } public void getStyleMetrics(float pointSize, float[] metrics, int offset) { - PhysicalFont font = getSlotFont(0); + Font2D font = getSlotFont(0); if (font == null) { // possible? super.getStyleMetrics(pointSize, metrics, offset); } else { @@ -366,7 +378,7 @@ public int getNumSlots() { return numSlots; } - public PhysicalFont getSlotFont(int slot) { + public Font2D getSlotFont(int slot) { /* This is essentially the runtime overhead for deferred font * initialisation: a boolean test on obtaining a slot font, * which will happen per slot, on initialisation of a strike @@ -377,16 +389,12 @@ public PhysicalFont getSlotFont(int slot) { } SunFontManager fm = SunFontManager.getInstance(); try { - PhysicalFont font = components[slot]; + Font2D font = components[slot]; if (font == null) { - try { - font = (PhysicalFont) fm. - findFont2D(componentNames[slot], style, - FontManager.PHYSICAL_FALLBACK); - components[slot] = font; - } catch (ClassCastException cce) { - font = fm.getDefaultPhysicalFont(); - } + font = fm. + findFont2D(componentNames[slot], style, + FontManager.PHYSICAL_FALLBACK); + components[slot] = font; } return font; } catch (Exception e) { @@ -413,13 +421,13 @@ public boolean isStdComposite() { * glyph elsewhere. */ protected int getValidatedGlyphCode(int glyphCode) { - int slot = glyphCode >>> 24; + int slot = decodeSlot(glyphCode); if (slot >= numSlots) { return getMapper().getMissingGlyphCode(); } - int slotglyphCode = glyphCode & CompositeStrike.SLOTMASK; - PhysicalFont slotFont = getSlotFont(slot); + int slotglyphCode = decodeGlyphCode(glyphCode); + Font2D slotFont = getSlotFont(slot); if (slotFont.getValidatedGlyphCode(slotglyphCode) == slotFont.getMissingGlyphCode()) { return getMapper().getMissingGlyphCode(); @@ -488,6 +496,14 @@ public boolean useAAForPtSize(int ptsize) { return getSlotFont(localeSlot).useAAForPtSize(ptsize); } + @Override + public SlotInfo getSlotInfoForGlyph(int glyphCode) { + SlotInfo info = getSlotFont(decodeSlot(glyphCode)) + .getSlotInfoForGlyph(decodeGlyphCode(glyphCode)); + info.slotShift += slotShift; + return info; + } + public String toString() { String ls = System.lineSeparator(); String componentsStr = ""; diff --git a/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java b/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java index cd53d96d0f4e0..b8239c5536d34 100644 --- a/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java +++ b/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java @@ -44,9 +44,6 @@ public class CompositeGlyphMapper extends CharToGlyphMapper { - public static final int SLOTMASK = 0xff000000; - public static final int GLYPHMASK = 0x00ffffff; - public static final int NBLOCKS = 216; public static final int BLOCKSZ = 256; public static final int MAXUNICODE = NBLOCKS*BLOCKSZ; @@ -67,10 +64,6 @@ public CompositeGlyphMapper(CompositeFont compFont) { compFont.maxIndices != null; } - public int compositeGlyphCode(int slot, int glyphCode) { - return (slot << 24 | (glyphCode & GLYPHMASK)); - } - private void initMapper() { if (missingGlyph == CharToGlyphMapper.UNINITIALIZED_GLYPH) { if (glyphMaps == null) { @@ -79,7 +72,7 @@ private void initMapper() { slotMappers = new CharToGlyphMapper[font.numSlots]; /* This requires that slot 0 is never empty. */ missingGlyph = font.getSlotFont(0).getMissingGlyphCode(); - missingGlyph = compositeGlyphCode(0, missingGlyph); + missingGlyph = font.compositeGlyphCode(0, missingGlyph); } } @@ -117,15 +110,21 @@ private CharToGlyphMapper getSlotMapper(int slot) { return mapper; } - private int convertToGlyph(int unicode) { + protected int convertToGlyph(int unicode) { + return convertToGlyph(unicode, 0); + } + + protected int convertToGlyph(int unicode, int variationSelector) { for (int slot = 0; slot < font.numSlots; slot++) { if (!hasExcludes || !font.isExcludedChar(slot, unicode)) { CharToGlyphMapper mapper = getSlotMapper(slot); - int glyphCode = mapper.charToGlyph(unicode); + int glyphCode = mapper.charToVariationGlyph(unicode, variationSelector); if (glyphCode != mapper.getMissingGlyphCode()) { - glyphCode = compositeGlyphCode(slot, glyphCode); - setCachedGlyphCode(unicode, glyphCode); + glyphCode = font.compositeGlyphCode(slot, glyphCode); + if (variationSelector == 0) { + setCachedGlyphCode(unicode, glyphCode); + } return glyphCode; } } @@ -133,6 +132,17 @@ private int convertToGlyph(int unicode) { return missingGlyph; } + @Override + public int charToVariationGlyph(int unicode, int variationSelector) { + if (variationSelector == 0) { + return charToGlyph(unicode); + } else { + int glyph = convertToGlyph(unicode, variationSelector); + // Fallback to base glyph if variation was not found. + return glyph != missingGlyph ? glyph : charToGlyph(unicode); + } + } + public int getNumGlyphs() { int numGlyphs = 0; /* The number of glyphs in a composite is affected by @@ -169,7 +179,7 @@ public int charToGlyph(int unicode, int prefSlot) { CharToGlyphMapper mapper = getSlotMapper(prefSlot); int glyphCode = mapper.charToGlyph(unicode); if (glyphCode != mapper.getMissingGlyphCode()) { - return compositeGlyphCode(prefSlot, glyphCode); + return font.compositeGlyphCode(prefSlot, glyphCode); } } return charToGlyph(unicode); @@ -184,91 +194,4 @@ public int charToGlyph(char unicode) { return glyphCode; } - /* This variant checks if shaping is needed and immediately - * returns true if it does. A caller of this method should be expecting - * to check the return type because it needs to know how to handle - * the character data for display. - */ - public boolean charsToGlyphsNS(int count, char[] unicodes, int[] glyphs) { - - for (int i=0; i= HI_SURROGATE_START && - code <= HI_SURROGATE_END && i < count - 1) { - char low = unicodes[i + 1]; - - if (low >= LO_SURROGATE_START && - low <= LO_SURROGATE_END) { - code = (code - HI_SURROGATE_START) * - 0x400 + low - LO_SURROGATE_START + 0x10000; - glyphs[i + 1] = INVISIBLE_GLYPH_ID; - } - } - - int gc = glyphs[i] = getCachedGlyphCode(code); - if (gc == UNINITIALIZED_GLYPH) { - glyphs[i] = convertToGlyph(code); - } - - if (code < FontUtilities.MIN_LAYOUT_CHARCODE) { - continue; - } - else if (FontUtilities.isComplexCharCode(code) || - CharToGlyphMapper.isVariationSelector(code)) { - return true; - } - else if (code >= 0x10000) { - i += 1; // Empty glyph slot after surrogate - continue; - } - } - - return false; - } - - /* The conversion is not very efficient - looping as it does, converting - * one char at a time. However the cache should fill very rapidly. - */ - public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) { - for (int i=0; i= HI_SURROGATE_START && - code <= HI_SURROGATE_END && i < count - 1) { - char low = unicodes[i + 1]; - - if (low >= LO_SURROGATE_START && - low <= LO_SURROGATE_END) { - code = (code - HI_SURROGATE_START) * - 0x400 + low - LO_SURROGATE_START + 0x10000; - - int gc = glyphs[i] = getCachedGlyphCode(code); - if (gc == UNINITIALIZED_GLYPH) { - glyphs[i] = convertToGlyph(code); - } - i += 1; // Empty glyph slot after surrogate - glyphs[i] = INVISIBLE_GLYPH_ID; - continue; - } - } - - int gc = glyphs[i] = getCachedGlyphCode(code); - if (gc == UNINITIALIZED_GLYPH) { - glyphs[i] = convertToGlyph(code); - } - } - } - - public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) { - for (int i=0; i>> 24); + FontStrike getStrikeForGlyph(int glyphCode) { + return getStrikeForSlot(compFont.decodeSlot(glyphCode)); } - PhysicalStrike getStrikeForSlot(int slot) { + FontStrike getStrikeForSlot(int slot) { if (slot >= strikes.length) { slot = 0; } - PhysicalStrike strike = strikes[slot]; + FontStrike strike = strikes[slot]; if (strike == null) { strike = - (PhysicalStrike)(compFont.getSlotFont(slot).getStrike(desc)); + compFont.getSlotFont(slot).getStrike(desc); strikes[slot] = strike; } @@ -113,31 +111,32 @@ StrikeMetrics getFontMetrics() { * physical font for that slot will substitute the missing glyph. */ void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) { - PhysicalStrike strike = getStrikeForSlot(0); - int numptrs = strike.getSlot0GlyphImagePtrs(glyphCodes, images, len); + FontStrike strike = getStrikeForSlot(0); + int numptrs = strike.getSlot0GlyphImagePtrs(glyphCodes, images, len, + compFont.slotMask, compFont.slotShift); if (numptrs == len) { return; } for (int i=numptrs; i< len; i++) { strike = getStrikeForGlyph(glyphCodes[i]); - images[i] = strike.getGlyphImagePtr(glyphCodes[i] & SLOTMASK); + images[i] = strike.getGlyphImagePtr(compFont.decodeGlyphCode(glyphCodes[i])); } } long getGlyphImagePtr(int glyphCode) { - PhysicalStrike strike = getStrikeForGlyph(glyphCode); - return strike.getGlyphImagePtr(glyphCode & SLOTMASK); + FontStrike strike = getStrikeForGlyph(glyphCode); + return strike.getGlyphImagePtr(compFont.decodeGlyphCode(glyphCode)); } void getGlyphImageBounds(int glyphCode, Point2D.Float pt, Rectangle result) { - PhysicalStrike strike = getStrikeForGlyph(glyphCode); - strike.getGlyphImageBounds(glyphCode & SLOTMASK, pt, result); + FontStrike strike = getStrikeForGlyph(glyphCode); + strike.getGlyphImageBounds(compFont.decodeGlyphCode(glyphCode), pt, result); } Point2D.Float getGlyphMetrics(int glyphCode) { - PhysicalStrike strike = getStrikeForGlyph(glyphCode); - return strike.getGlyphMetrics(glyphCode & SLOTMASK); + FontStrike strike = getStrikeForGlyph(glyphCode); + return strike.getGlyphMetrics(compFont.decodeGlyphCode(glyphCode)); } Point2D.Float getCharMetrics(char ch) { @@ -145,8 +144,8 @@ Point2D.Float getCharMetrics(char ch) { } float getGlyphAdvance(int glyphCode) { - PhysicalStrike strike = getStrikeForGlyph(glyphCode); - return strike.getGlyphAdvance(glyphCode & SLOTMASK); + FontStrike strike = getStrikeForGlyph(glyphCode); + return strike.getGlyphAdvance(compFont.decodeGlyphCode(glyphCode)); } /* REMIND where to cache? @@ -161,14 +160,14 @@ float getCodePointAdvance(int cp) { } Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) { - PhysicalStrike strike = getStrikeForGlyph(glyphCode); - return strike.getGlyphOutlineBounds(glyphCode & SLOTMASK); + FontStrike strike = getStrikeForGlyph(glyphCode); + return strike.getGlyphOutlineBounds(compFont.decodeGlyphCode(glyphCode)); } GeneralPath getGlyphOutline(int glyphCode, float x, float y) { - PhysicalStrike strike = getStrikeForGlyph(glyphCode); - GeneralPath path = strike.getGlyphOutline(glyphCode & SLOTMASK, x, y); + FontStrike strike = getStrikeForGlyph(glyphCode); + GeneralPath path = strike.getGlyphOutline(compFont.decodeGlyphCode(glyphCode), x, y); if (path == null) { return new GeneralPath(); } else { @@ -176,42 +175,8 @@ GeneralPath getGlyphOutline(int glyphCode, float x, float y) { } } - /* The physical font slot for each glyph is encoded in the glyph ID - * To be as efficient as possible we find a run of glyphs from the - * same slot and create a temporary array of these glyphs decoded - * to the slot. The slot font is then queried for the GeneralPath - * for that run of glyphs. GeneralPaths from each run are appended - * to create the shape for the whole glyph array. - */ - GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { - GeneralPath path = null; - GeneralPath gp; - int glyphIndex = 0; - int[] tmpGlyphs; - - while (glyphIndex < glyphs.length) { - int start = glyphIndex; - int slot = glyphs[glyphIndex] >>> 24; - while (glyphIndex < glyphs.length && - (glyphs[glyphIndex+1] >>> 24) == slot) { - glyphIndex++; - } - int tmpLen = glyphIndex-start+1; - tmpGlyphs = new int[tmpLen]; - for (int i=0;i= 3.0 && matrix[0] <= 100.0) && - !((TrueTypeFont)fileFont).useEmbeddedBitmapsForSize(intPtSize)) { + !((TrueTypeFont)fileFont).useEmbeddedBitmapsForSize(intPtSize) && + !((TrueTypeFont)fileFont).hasCOLRTable()) { useNatives = true; } if (FontUtilities.isLogging() && FontUtilities.isWindows) { @@ -390,16 +391,17 @@ void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) { /* The following method is called from CompositeStrike as a special case. */ - int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len) { + int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len, int slotMask, int slotShift) { int convertedCnt = 0; for (int i=0; i>> 24 != 0) { + if ((glyphCode & slotMask) != 0) { return convertedCnt; } else { convertedCnt++; + glyphCode >>>= slotShift; } if (glyphCode >= INVISIBLE_GLYPHS) { images[i] = StrikeCache.invisibleGlyphPtr; @@ -739,15 +741,15 @@ void getGlyphImageBounds(int glyphCode, Point2D.Float pt, private int getGlyphImageMinX(long ptr, int origMinX) { - int width = StrikeCache.unsafe.getChar(ptr+StrikeCache.widthOffset); + byte format = StrikeCache.unsafe.getByte(ptr+StrikeCache.formatOffset); + if (format != StrikeCache.PIXEL_FORMAT_LCD) { + return origMinX; + } + int height = StrikeCache.unsafe.getChar(ptr+StrikeCache.heightOffset); int rowBytes = StrikeCache.unsafe.getChar(ptr+StrikeCache.rowBytesOffset); - if (rowBytes == width) { - return origMinX; - } - long pixelData = StrikeCache.unsafe.getAddress(ptr + StrikeCache.pixelDataOffset); @@ -934,9 +936,34 @@ GeneralPath getGlyphOutline(int glyphCode, float x, float y) { return gp; } - GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { - return fileFont.getGlyphVectorOutline(pScalerContext, - glyphs, glyphs.length, x, y); + private + WeakReference> glyphRenderDataMapRef; + + GlyphRenderData getGlyphRenderData(int glyphCode, float x, float y) { + + GlyphRenderData grd = null; + ConcurrentHashMap glyphRenderDataMap = null; + + if (glyphRenderDataMapRef != null) { + glyphRenderDataMap = glyphRenderDataMapRef.get(); + if (glyphRenderDataMap != null) { + grd = glyphRenderDataMap.get(glyphCode); + } + } + + if (grd == null) { + grd = fileFont.getGlyphRenderData(pScalerContext, glyphCode, 0, 0); + if (glyphRenderDataMap == null) { + glyphRenderDataMap = new ConcurrentHashMap<>(); + glyphRenderDataMapRef = new WeakReference<>(glyphRenderDataMap); + } + glyphRenderDataMap.put(glyphCode, grd); + } + grd = new GlyphRenderData(grd); // mutable! + if (x != 0f || y != 0f) { + grd.transform(AffineTransform.getTranslateInstance(x, y)); + } + return grd; } protected void adjustPoint(Point2D.Float pt) { diff --git a/src/java.desktop/share/classes/sun/font/Font2D.java b/src/java.desktop/share/classes/sun/font/Font2D.java index 64968086a3b46..e75d6ecf2c26f 100644 --- a/src/java.desktop/share/classes/sun/font/Font2D.java +++ b/src/java.desktop/share/classes/sun/font/Font2D.java @@ -570,4 +570,19 @@ public float getItalicAngle(Font font, AffineTransform at, } } + public static class SlotInfo { + public Font2D font; + public int slotShift; + public SlotInfo(Font2D font) { + this.font = font; + } + public int getSlotMask() { + return (1 << slotShift) - 1; + } + } + + public SlotInfo getSlotInfoForGlyph(int glyphCode) { + return new SlotInfo(this); + } + } diff --git a/src/java.desktop/share/classes/sun/font/FontRunIterator.java b/src/java.desktop/share/classes/sun/font/FontRunIterator.java index d20b1006765eb..268efd3a20d9c 100644 --- a/src/java.desktop/share/classes/sun/font/FontRunIterator.java +++ b/src/java.desktop/share/classes/sun/font/FontRunIterator.java @@ -34,37 +34,37 @@ * Iterates over runs of fonts in a CompositeFont, optionally taking script runs into account. */ public final class FontRunIterator { - CompositeFont font; - char[] text; - int start; - int limit; + private Font2D font; + private char[] text; + private int limit; - CompositeGlyphMapper mapper; // handy cache + private CharToGlyphMapper mapper; // handy cache - int slot = -1; - int pos; + private int slot; + private int pos; + private Font2D.SlotInfo slotInfo; - public void init(CompositeFont font, char[] text, int start, int limit) { + public void init(Font2D font, char[] text, int start, int limit) { if (font == null || text == null || start < 0 || limit < start || limit > text.length) { throw new IllegalArgumentException(); } this.font = font; this.text = text; - this.start = start; this.limit = limit; - this.mapper = (CompositeGlyphMapper)font.getMapper(); - this.slot = -1; + this.mapper = font.getMapper(); + this.slot = 0; this.pos = start; + this.slotInfo = null; } - public PhysicalFont getFont() { - return slot == -1 ? null : font.getSlotFont(slot); + public Font2D.SlotInfo getSlotInfo() { + return slotInfo; } - public int getGlyphMask() { - return slot << 24; + public int getSlot() { + return slot; } public int getPos() { @@ -112,22 +112,45 @@ public int getPos() { * the data instead of pull it? */ - public boolean next(int script, int lim) { + public boolean next(int lim) { if (pos == lim) { return false; } int ch = nextCodePoint(lim); - int sl = mapper.charToGlyph(ch) & CompositeGlyphMapper.SLOTMASK; - slot = sl >>> 24; - while ((ch = nextCodePoint(lim)) != DONE && (mapper.charToGlyph(ch) & CompositeGlyphMapper.SLOTMASK) == sl); + int nch = nextCodePoint(lim); + int vs = CharToGlyphMapper.isVariationSelector(nch) ? nch : 0; + int gl = mapper.charToVariationGlyph(ch, vs); + slotInfo = font.getSlotInfoForGlyph(gl); + int slotMask = slotInfo.getSlotMask(); + slot = gl & slotMask; + if (slotInfo.slotShift == 0) { + // This is a non-composite font, so return early. + pos = lim; + return true; + } + do { + if (vs == 0) { + ch = nch; + } else { + ch = nextCodePoint(lim); + } + nch = nextCodePoint(lim); + vs = CharToGlyphMapper.isVariationSelector(nch) ? nch : 0; + } while(ch != DONE && isSameRun(ch, vs, slotMask)); + pushback(nch); pushback(ch); return true; } - public boolean next() { - return next(Script.COMMON, limit); + private boolean isSameRun(int ch, int variationSelector, int slotMask) { + // Every font is meant to be able to render format chars + // So we make format chars stick to the current font run + if (CMap.getFormatCharGlyph(ch) == CharToGlyphMapper.INVISIBLE_GLYPH_ID) { + return true; + } + return (mapper.charToVariationGlyph(ch, variationSelector) & slotMask) == slot; } static final int SURROGATE_START = 0x10000; diff --git a/src/java.desktop/share/classes/sun/font/FontScaler.java b/src/java.desktop/share/classes/sun/font/FontScaler.java index ae22cec319f1f..4364b554914f6 100644 --- a/src/java.desktop/share/classes/sun/font/FontScaler.java +++ b/src/java.desktop/share/classes/sun/font/FontScaler.java @@ -164,8 +164,8 @@ abstract GeneralPath getGlyphOutline(long pScalerContext, int glyphCode, float x, float y) throws FontScalerException; - abstract GeneralPath getGlyphVectorOutline(long pScalerContext, int[] glyphs, - int numGlyphs, float x, float y) + abstract GlyphRenderData getGlyphRenderData(long pScalerContext, int glyphCode, + float x, float y) throws FontScalerException; /* Used by Java2D disposer to ensure native resources are released. diff --git a/src/java.desktop/share/classes/sun/font/FontStrike.java b/src/java.desktop/share/classes/sun/font/FontStrike.java index 2eff2f8169f46..2c12fb1a7db74 100644 --- a/src/java.desktop/share/classes/sun/font/FontStrike.java +++ b/src/java.desktop/share/classes/sun/font/FontStrike.java @@ -73,8 +73,10 @@ abstract void getGlyphImageBounds(int glyphcode, abstract GeneralPath getGlyphOutline(int glyphCode, float x, float y); - abstract GeneralPath - getGlyphVectorOutline(int[] glyphs, float x, float y); + abstract GlyphRenderData getGlyphRenderData(int glyphCode, float x, float y); + int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len, int slotMask, int slotShift) { + return 0; + } } diff --git a/src/java.desktop/share/classes/sun/font/FontUtilities.java b/src/java.desktop/share/classes/sun/font/FontUtilities.java index c4f8c317a92b8..7ed43e1f316ac 100644 --- a/src/java.desktop/share/classes/sun/font/FontUtilities.java +++ b/src/java.desktop/share/classes/sun/font/FontUtilities.java @@ -138,7 +138,7 @@ public Object run() { * where the caller interprets 'layout' to mean any case where * one 'char' (ie the java type char) does not map to one glyph */ - public static final int MAX_LAYOUT_CHARCODE = 0x206F; + public static final int MAX_LAYOUT_CHARCODE = CharToGlyphMapper.VSS_END; /** * Calls the private getFont2D() method in java.awt.Font objects. @@ -305,6 +305,21 @@ else if (code >= 0x202a && code <= 0x202e) { // directional control else if (code >= 0x206a && code <= 0x206f) { // directional control return true; } + else if (code >= 0x20d0 && code <= 0x20f0) { // U+20D0 - U+20F0 combining diacritical marks for symbols + return true; + } + else if (code >= 0x1f1e6 && code <= 0x1f1ff) { // U+1F1E6 - U+1F1FF flag letters https://emojipedia.org/emoji-flag-sequence/ + return true; + } + else if (code == 0x1f3f4) { // black flag https://emojipedia.org/emoji-tag-sequence/ + return true; + } + else if (code >= 0x1f3fb && code <= 0x1f3ff) { // U+1F3FB - U+1F3FF emoji modifiers + return true; + } + else if (CharToGlyphMapper.isVariationSelector(code)) { + return true; + } return false; } diff --git a/src/java.desktop/share/classes/sun/font/FreetypeFontScaler.java b/src/java.desktop/share/classes/sun/font/FreetypeFontScaler.java index 6d8d5512bae40..458b424613245 100644 --- a/src/java.desktop/share/classes/sun/font/FreetypeFontScaler.java +++ b/src/java.desktop/share/classes/sun/font/FreetypeFontScaler.java @@ -148,19 +148,19 @@ synchronized GeneralPath getGlyphOutline( getGlyphOutline(0L, glyphCode, x,y); } - synchronized GeneralPath getGlyphVectorOutline( - long pScalerContext, int[] glyphs, int numGlyphs, - float x, float y) throws FontScalerException { + synchronized GlyphRenderData getGlyphRenderData(long pScalerContext, int glyphCode, + float x, float y) throws FontScalerException { if (nativeScaler != 0L) { - return getGlyphVectorOutlineNative(font.get(), - pScalerContext, - nativeScaler, - glyphs, - numGlyphs, - x, y); + GlyphRenderData result = new GlyphRenderData(); + getGlyphRenderDataNative(font.get(), + pScalerContext, + nativeScaler, + glyphCode, + x, y, result); + return result; } - return FontScaler - .getNullScaler().getGlyphVectorOutline(0L, glyphs, numGlyphs, x, y); + return FontScaler.getNullScaler(). + getGlyphRenderData(0L, glyphCode, x,y); } /* This method should not be called directly, in case @@ -250,9 +250,9 @@ private native Rectangle2D.Float getGlyphOutlineBoundsNative(Font2D font, private native GeneralPath getGlyphOutlineNative(Font2D font, long pScalerContext, long pScaler, int glyphCode, float x, float y); - private native GeneralPath getGlyphVectorOutlineNative(Font2D font, - long pScalerContext, long pScaler, - int[] glyphs, int numGlyphs, float x, float y); + private native void getGlyphRenderDataNative(Font2D font, long pScalerContext, + long pScaler, int glyphCode, + float x, float y, GlyphRenderData result); private native Point2D.Float getGlyphPointNative(Font2D font, long pScalerContext, long pScaler, int glyphCode, int ptNumber); diff --git a/src/java.desktop/share/classes/sun/font/GlyphLayout.java b/src/java.desktop/share/classes/sun/font/GlyphLayout.java index fe2a8bf25998a..4771a61f2f16a 100644 --- a/src/java.desktop/share/classes/sun/font/GlyphLayout.java +++ b/src/java.desktop/share/classes/sun/font/GlyphLayout.java @@ -173,7 +173,7 @@ public static interface LayoutEngine { * If the GVData does not have room for the glyphs, throws an IndexOutOfBoundsException and * leave pt and the gvdata unchanged. */ - public void layout(FontStrikeDesc sd, float[] mat, float ptSize, int gmask, + public void layout(FontStrikeDesc sd, float[] mat, float ptSize, int slot, int slotShift, int baseIndex, TextRecord text, int typo_flags, Point2D.Float pt, GVData data); } @@ -407,36 +407,19 @@ public StandardGlyphVector layout(Font font, FontRenderContext frc, _textRecord.init(text, offset, lim, min, max); int start = offset; - if (font2D instanceof CompositeFont) { - _scriptRuns.init(text, offset, count); // ??? how to handle 'common' chars - _fontRuns.init((CompositeFont)font2D, text, offset, lim); - while (_scriptRuns.next()) { - int limit = _scriptRuns.getScriptLimit(); - int script = _scriptRuns.getScriptCode(); - while (_fontRuns.next(script, limit)) { - Font2D pfont = _fontRuns.getFont(); - /* layout can't deal with NativeFont instances. The - * native font is assumed to know of a suitable non-native - * substitute font. This currently works because - * its consistent with the way NativeFonts delegate - * in other cases too. - */ - if (pfont instanceof NativeFont) { - pfont = ((NativeFont)pfont).getDelegateFont(); - } - int gmask = _fontRuns.getGlyphMask(); - int pos = _fontRuns.getPos(); - nextEngineRecord(start, pos, script, lang, pfont, gmask); - start = pos; - } - } - } else { - _scriptRuns.init(text, offset, count); // ??? don't worry about 'common' chars - while (_scriptRuns.next()) { - int limit = _scriptRuns.getScriptLimit(); - int script = _scriptRuns.getScriptCode(); - nextEngineRecord(start, limit, script, lang, font2D, 0); - start = limit; + _scriptRuns.init(text, offset, count); // ??? how to handle 'common' chars + _fontRuns.init(font2D, text, offset, lim); + while (_scriptRuns.next()) { + int limit = _scriptRuns.getScriptLimit(); + int script = _scriptRuns.getScriptCode(); + while (_fontRuns.next(limit)) { + Font2D.SlotInfo slotInfo = _fontRuns.getSlotInfo(); + Font2D pfont = slotInfo.font; + int slotShift = slotInfo.slotShift; + int slot = _fontRuns.getSlot(); + int pos = _fontRuns.getPos(); + nextEngineRecord(start, pos, script, lang, pfont, slot, slotShift); + start = pos; } } @@ -508,7 +491,7 @@ private void init(int capacity) { this._gvdata.init(capacity); } - private void nextEngineRecord(int start, int limit, int script, int lang, Font2D font, int gmask) { + private void nextEngineRecord(int start, int limit, int script, int lang, Font2D font, int slot, int slotShift) { EngineRecord er = null; if (_ercount == _erecords.size()) { er = new EngineRecord(); @@ -516,7 +499,7 @@ private void nextEngineRecord(int start, int limit, int script, int lang, Font2D } else { er = _erecords.get(_ercount); } - er.init(start, limit, font, script, lang, gmask); + er.init(start, limit, font, script, lang, slot, slotShift); ++_ercount; } @@ -625,7 +608,8 @@ public StandardGlyphVector createGlyphVector(Font font, FontRenderContext frc, S private final class EngineRecord { private int start; private int limit; - private int gmask; + private int slot; + private int slotShift; private int eflags; private LayoutEngineKey key; private LayoutEngine engine; @@ -634,10 +618,11 @@ private final class EngineRecord { key = new LayoutEngineKey(); } - void init(int start, int limit, Font2D font, int script, int lang, int gmask) { + void init(int start, int limit, Font2D font, int script, int lang, int slot, int slotShift) { this.start = start; this.limit = limit; - this.gmask = gmask; + this.slot = slot; + this.slotShift = slotShift; this.key.init(font, script, lang); this.eflags = 0; @@ -666,7 +651,7 @@ void init(int start, int limit, Font2D font, int script, int lang, int gmask) { void layout() { _textRecord.start = start; _textRecord.limit = limit; - engine.layout(_sd, _mat, ptSize, gmask, start - _offset, _textRecord, + engine.layout(_sd, _mat, ptSize, slot, slotShift, start - _offset, _textRecord, _typo_flags | eflags, _pt, _gvdata); } } diff --git a/src/java.desktop/share/classes/sun/font/GlyphList.java b/src/java.desktop/share/classes/sun/font/GlyphList.java index 8d19d005b89d3..e147189f89ec5 100644 --- a/src/java.desktop/share/classes/sun/font/GlyphList.java +++ b/src/java.desktop/share/classes/sun/font/GlyphList.java @@ -502,15 +502,20 @@ private void fillBounds(int[] bounds, int endGlyphIndex) { } public static boolean canContainColorGlyphs() { - return FontUtilities.isMacOSX; + return true; + } + + /** + * @return {@link StrikeCache#PIXEL_FORMAT_GREYSCALE} for greyscale, + * {@link StrikeCache#PIXEL_FORMAT_LCD} for LCD and {@link StrikeCache#PIXEL_FORMAT_BGRA} for BGRA glyph + */ + public byte getPixelFormat(int glyphIndex) { + return StrikeCache.unsafe.getByte(images[glyphIndex] + + StrikeCache.formatOffset); } public boolean isColorGlyph(int glyphIndex) { - int width = StrikeCache.unsafe.getChar(images[glyphIndex] + - StrikeCache.widthOffset); - int rowBytes = StrikeCache.unsafe.getChar(images[glyphIndex] + - StrikeCache.rowBytesOffset); - return rowBytes == width * 4; + return getPixelFormat(glyphIndex) == StrikeCache.PIXEL_FORMAT_BGRA; } public SurfaceData getColorGlyphData() { diff --git a/src/java.desktop/share/classes/sun/font/GlyphRenderData.java b/src/java.desktop/share/classes/sun/font/GlyphRenderData.java new file mode 100644 index 0000000000000..57c59c0378ab6 --- /dev/null +++ b/src/java.desktop/share/classes/sun/font/GlyphRenderData.java @@ -0,0 +1,185 @@ +/* + * Copyright 2000-2022 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. + * + * 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. + */ + +package sun.font; + +import jdk.internal.misc.Unsafe; + +import java.awt.*; +import java.awt.color.ColorSpace; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.image.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Data for rendering any number of glyphs bypassing glyph cache. + */ +public class GlyphRenderData { + + public GeneralPath outline; + public List colorLayers; + public List bitmaps; + + public GlyphRenderData() {} + public GlyphRenderData(GlyphRenderData i) { + if (i.outline != null) { + outline = (GeneralPath) i.outline.clone(); + } + if (i.colorLayers != null) { + colorLayers = new ArrayList<>(i.colorLayers.size()); + for (ColorLayer l : i.colorLayers) { + colorLayers.add(new ColorLayer(l.color, (GeneralPath) l.outline.clone())); + } + } + if (i.bitmaps != null) { + bitmaps = new ArrayList<>(i.bitmaps.size()); + for (Bitmap b : i.bitmaps) { + bitmaps.add(new Bitmap(new AffineTransform(b.transform), b.image)); + } + } + } + + /** + * @param i must not be used afterwards + */ + public void merge(GlyphRenderData i) { + if (i.outline != null) { + if (outline == null) { + outline = i.outline; + } else { + outline.append(i.outline.getPathIterator(null), false); + } + } + if (i.colorLayers != null) { + if (colorLayers == null) { + colorLayers = i.colorLayers; + } else { + colorLayers.addAll(i.colorLayers); + } + } + if (i.bitmaps != null) { + if (bitmaps == null) { + bitmaps = i.bitmaps; + } else { + bitmaps.addAll(i.bitmaps); + } + } + } + + public void transform(AffineTransform transform) { + if (outline != null) { + outline.transform(transform); + } + if (colorLayers != null) { + for (ColorLayer layer : colorLayers) { + layer.outline.transform(transform); + } + } + if (bitmaps != null) { + for (Bitmap bitmap : bitmaps) { + bitmap.transform.preConcatenate(transform); + } + } + } + + public void draw(Graphics2D g) { + if (outline != null) { + g.fill(outline); + } + if (colorLayers != null) { + Color color = g.getColor(); + for (ColorLayer layer : colorLayers) { + g.setColor(layer.color == null ? color : layer.color); + g.fill(layer.outline); + } + g.setColor(color); + } + if (bitmaps != null) { + for (Bitmap bitmap : bitmaps) { + g.drawImage(bitmap.image, bitmap.transform, null); + } + } + } + + public record ColorLayer(Color color, GeneralPath outline) {} + + public record Bitmap(AffineTransform transform, Image image) {} + + // These methods exist for convenience and are called from native + + private void setColorLayersList(int capacity) { + colorLayers = new ArrayList<>(capacity); + } + + private void addColorLayers(GeneralPath outline) { + colorLayers.add(new ColorLayer(null, outline)); + } + + private void addColorLayers(int r, int g, int b, int a, GeneralPath outline) { + colorLayers.add(new ColorLayer(new Color(r, g, b, a), outline)); + } + + private static DirectColorModel colorModel(boolean premultiplied, int bits, int r, int g, int b, int a) { + if (Unsafe.getUnsafe().isBigEndian()) { + r = Integer.reverse(r) >>> (32 - bits); + g = Integer.reverse(g) >>> (32 - bits); + b = Integer.reverse(b) >>> (32 - bits); + a = Integer.reverse(a) >>> (32 - bits); + } + return new DirectColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + bits, r, g, b, a, premultiplied, DataBuffer.TYPE_INT); + } + private static final DirectColorModel[] BITMAP_COLOR_MODELS = { + colorModel(false, 32, // macOS RGBA + 0x000000ff, + 0x0000ff00, + 0x00ff0000, + 0xff000000), + colorModel(false, 32, // macOS ARGB + 0x0000ff00, + 0x00ff0000, + 0xff000000, + 0x000000ff), + colorModel(true, 32, // Freetype BGRA + 0x00ff0000, + 0x0000ff00, + 0x000000ff, + 0xff000000) + }; + private void addBitmap(double m00, double m10, + double m01, double m11, + double m02, double m12, + int width, int height, int pitch, + int colorModel, int[] data) { + if (bitmaps == null) { + bitmaps = new ArrayList<>(); + } + DirectColorModel color = BITMAP_COLOR_MODELS[colorModel]; + DataBuffer buffer = new DataBufferInt(data, data.length); + WritableRaster raster = Raster.createPackedRaster(buffer, width, height, pitch, color.getMasks(), null); + BufferedImage image = new BufferedImage(color, raster, color.isAlphaPremultiplied(), null); + bitmaps.add(new Bitmap(new AffineTransform(m00, m10, m01, m11, m02, m12), image)); + } +} diff --git a/src/java.desktop/share/classes/sun/font/NullFontScaler.java b/src/java.desktop/share/classes/sun/font/NullFontScaler.java index 7cdd2d6324688..7dea751e4a876 100644 --- a/src/java.desktop/share/classes/sun/font/NullFontScaler.java +++ b/src/java.desktop/share/classes/sun/font/NullFontScaler.java @@ -59,9 +59,9 @@ GeneralPath getGlyphOutline(long pScalerContext, int glyphCode, return new GeneralPath(); } - GeneralPath getGlyphVectorOutline(long pScalerContext, int[] glyphs, - int numGlyphs, float x, float y) { - return new GeneralPath(); + GlyphRenderData getGlyphRenderData(long pScalerContext, int glyphCode, + float x, float y) { + return new GlyphRenderData(); } long createScalerContext(double[] matrix, int aa, diff --git a/src/java.desktop/share/classes/sun/font/PhysicalFont.java b/src/java.desktop/share/classes/sun/font/PhysicalFont.java index dc05c0e0a5f33..d097a4702905b 100644 --- a/src/java.desktop/share/classes/sun/font/PhysicalFont.java +++ b/src/java.desktop/share/classes/sun/font/PhysicalFont.java @@ -108,7 +108,4 @@ abstract Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext, abstract GeneralPath getGlyphOutline(long pScalerContext, int glyphCode, float x, float y); - abstract GeneralPath getGlyphVectorOutline(long pScalerContext, - int[] glyphs, int numGlyphs, - float x, float y); } diff --git a/src/java.desktop/share/classes/sun/font/PhysicalStrike.java b/src/java.desktop/share/classes/sun/font/PhysicalStrike.java index 9b2e7442298d0..39186ae7416db 100644 --- a/src/java.desktop/share/classes/sun/font/PhysicalStrike.java +++ b/src/java.desktop/share/classes/sun/font/PhysicalStrike.java @@ -114,10 +114,6 @@ Point2D.Float getCharMetrics(char ch) { return getGlyphMetrics(physicalFont.getMapper().charToGlyph(ch)); } - int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len) { - return 0; - } - /* Used by the OpenType engine for mark positioning. */ Point2D.Float getGlyphPoint(int glyphCode, int ptNumber) { diff --git a/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java b/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java index e333a9de29173..a68d952eaad60 100644 --- a/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java +++ b/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java @@ -877,6 +877,21 @@ public float[] getGlyphInfo() { return result; } + public GlyphRenderData getGlyphRenderData(float x, float y) { + setFRCTX(); + initPositions(); + + GlyphRenderData result = new GlyphRenderData(); + for (int i = 0, n = 0; i < glyphs.length; ++i, n += 2) { + float px = x + positions[n]; + float py = y + positions[n+1]; + + getGlyphStrike(i).appendGlyphRenderData(glyphs[i], result, px, py); + } + + return result; + } + ////////////////////// // StandardGlyphVector package private methods ///////////////////// @@ -1067,7 +1082,10 @@ int[] getValidatedGlyphs(int[] oglyphs) { if (oglyphs[i] == 0xFFFE || oglyphs[i] == 0xFFFF) { vglyphs[i] = oglyphs[i]; } else { - vglyphs[i] = font2D.getValidatedGlyphCode(oglyphs[i]); + vglyphs[i] = font2D.getValidatedGlyphCode( + font2D instanceof CompositeFont comp ? + comp.compositeGlyphCode(0, oglyphs[i]) : + oglyphs[i]); } } return vglyphs; @@ -1816,6 +1834,19 @@ void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) { PathIterator iterator = gp.getPathIterator(null); result.append(iterator, false); } + + void appendGlyphRenderData(int glyphID, GlyphRenderData result, float x, float y) { + // !!! fontStrike needs a method for this. For that matter, GeneralPath does. + GlyphRenderData grd; + if (sgv.invdtx == null) { + grd = strike.getGlyphRenderData(glyphID, x + dx, y + dy); + } else { + grd = strike.getGlyphRenderData(glyphID, 0, 0); + grd.transform(sgv.invdtx); + grd.transform(AffineTransform.getTranslateInstance(x + dx, y + dy)); + } + result.merge(grd); + } } public String toString() { diff --git a/src/java.desktop/share/classes/sun/font/StrikeCache.java b/src/java.desktop/share/classes/sun/font/StrikeCache.java index 950ba5b3d465b..cb251b8dbab61 100644 --- a/src/java.desktop/share/classes/sun/font/StrikeCache.java +++ b/src/java.desktop/share/classes/sun/font/StrikeCache.java @@ -27,6 +27,7 @@ import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; +import java.lang.annotation.Native; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; @@ -114,8 +115,14 @@ public final class StrikeCache { static int pixelDataOffset; static int cacheCellOffset; static int managedOffset; + static int formatOffset; static long invisibleGlyphPtr; + @Native public static final byte PIXEL_FORMAT_UNKNOWN = -1; + @Native public static final byte PIXEL_FORMAT_GREYSCALE = 1; + @Native public static final byte PIXEL_FORMAT_LCD = 3; + @Native public static final byte PIXEL_FORMAT_BGRA = 4; + /* Native method used to return information used for unsafe * access to native data. * return values as follows:- @@ -140,7 +147,7 @@ public final class StrikeCache { @SuppressWarnings("removal") private static void initStatic() { - long[] nativeInfo = new long[13]; + long[] nativeInfo = new long[14]; getGlyphCacheDescription(nativeInfo); //Can also get address size from Unsafe class :- //nativeAddressSize = unsafe.addressSize(); @@ -157,6 +164,7 @@ private static void initStatic() { invisibleGlyphPtr = nativeInfo[10]; cacheCellOffset = (int) nativeInfo[11]; managedOffset = (int) nativeInfo[12]; + formatOffset = (int) nativeInfo[13]; if (nativeAddressSize < 4) { throw new InternalError("Unexpected address size for font data: " + diff --git a/src/java.desktop/share/classes/sun/font/SunFontManager.java b/src/java.desktop/share/classes/sun/font/SunFontManager.java index f2e4e530b2e40..fa820502446e4 100644 --- a/src/java.desktop/share/classes/sun/font/SunFontManager.java +++ b/src/java.desktop/share/classes/sun/font/SunFontManager.java @@ -296,9 +296,9 @@ public static final String getJDKFontDir() { return jreFontDirName; } - public TrueTypeFont getEUDCFont() { + public List getAdditionalFallbackFonts() { // Overridden in Windows. - return null; + return List.of(); } /* Initialise ptrs used by JNI methods */ @@ -458,7 +458,7 @@ public Font2DHandle getNewComposite(String family, int style, } CompositeFont oldComp = (CompositeFont)handle.font2D; - PhysicalFont oldFont = oldComp.getSlotFont(0); + Font2D oldFont = oldComp.getSlotFont(0); if (family == null) { family = oldFont.getFamilyName(null); @@ -468,16 +468,15 @@ public Font2DHandle getNewComposite(String family, int style, } Font2D newFont = findFont2D(family, style, NO_FALLBACK); - if (!(newFont instanceof PhysicalFont)) { - newFont = oldFont; + if (newFont == null) { + return handle; } - PhysicalFont physicalFont = (PhysicalFont)newFont; CompositeFont dialog2D = (CompositeFont)findFont2D("dialog", style, NO_FALLBACK); if (dialog2D == null) { /* shouldn't happen */ return handle; } - CompositeFont compFont = new CompositeFont(physicalFont, dialog2D); + CompositeFont compFont = new CompositeFont(newFont, dialog2D); Font2DHandle newHandle = new Font2DHandle(compFont); return newHandle; } diff --git a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java index f5e0a4bc77481..0b9c0e1870c71 100644 --- a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java +++ b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java @@ -162,7 +162,7 @@ private long getFacePtr(Font2D font2D) { return ref.getNativePtr(); } - public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int gmask, + public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int slot, int slotShift, int baseIndex, TextRecord tr, int typo_flags, Point2D.Float pt, GVData data) { Font2D font = key.font(); @@ -172,7 +172,7 @@ public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int gmask, shape(font, strike, ptSize, mat, pFace, tr.text, data, key.script(), tr.start, tr.limit, baseIndex, pt, - typo_flags, gmask); + typo_flags, slot, slotShift); } } @@ -182,7 +182,7 @@ public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int gmask, long pFace, char[] chars, GVData data, int script, int offset, int limit, - int baseIndex, Point2D.Float pt, int typo_flags, int slot); + int baseIndex, Point2D.Float pt, int typo_flags, int slot, int slotShift); private static native long createFace(Font2D font, long platformNativeFontPtr); diff --git a/src/java.desktop/share/classes/sun/font/TrueTypeFont.java b/src/java.desktop/share/classes/sun/font/TrueTypeFont.java index 3895ec6ca8260..ec8ca2f9a322a 100644 --- a/src/java.desktop/share/classes/sun/font/TrueTypeFont.java +++ b/src/java.desktop/share/classes/sun/font/TrueTypeFont.java @@ -97,6 +97,7 @@ public class TrueTypeFont extends FileFont { public static final int v1ttTag = 0x00010000; // 'v1tt' - Version 1 TT font public static final int trueTag = 0x74727565; // 'true' - Version 2 TT font public static final int ottoTag = 0x4f54544f; // 'otto' - OpenType font + public static final int COLRTag = 0x434f4c52; // 'COLR' /* -- ID's used in the 'name' table */ public static final int MAC_PLATFORM_ID = 1; @@ -877,6 +878,10 @@ DirectoryEntry getDirectoryEntry(int tag) { return null; } + boolean hasCOLRTable() { + return getDirectoryEntry(COLRTag) != null; + } + /* Used to determine if this size has embedded bitmaps, which * for CJK fonts should be used in preference to LCD glyphs. */ diff --git a/src/java.desktop/share/classes/sun/font/TrueTypeGlyphMapper.java b/src/java.desktop/share/classes/sun/font/TrueTypeGlyphMapper.java index 79b3eec24bb0f..e6c0f8f67af1e 100644 --- a/src/java.desktop/share/classes/sun/font/TrueTypeGlyphMapper.java +++ b/src/java.desktop/share/classes/sun/font/TrueTypeGlyphMapper.java @@ -132,77 +132,6 @@ public int charToVariationGlyph(int unicode, int variationSelector) { return glyph; } - public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) { - for (int i=0;i= HI_SURROGATE_START && - code <= HI_SURROGATE_END && i < count - 1) { - char low = unicodes[i + 1]; - - if (low >= LO_SURROGATE_START && - low <= LO_SURROGATE_END) { - code = (code - HI_SURROGATE_START) * - 0x400 + low - LO_SURROGATE_START + 0x10000; - - glyphs[i] = getGlyphFromCMAP(code); - i += 1; // Empty glyph slot after surrogate - glyphs[i] = INVISIBLE_GLYPH_ID; - continue; - } - } - glyphs[i] = getGlyphFromCMAP(code); - - } - } - - /* This variant checks if shaping is needed and immediately - * returns true if it does. A caller of this method should be expecting - * to check the return type because it needs to know how to handle - * the character data for display. - */ - public boolean charsToGlyphsNS(int count, char[] unicodes, int[] glyphs) { - - for (int i=0; i= HI_SURROGATE_START && - code <= HI_SURROGATE_END && i < count - 1) { - char low = unicodes[i + 1]; - - if (low >= LO_SURROGATE_START && - low <= LO_SURROGATE_END) { - code = (code - HI_SURROGATE_START) * - 0x400 + low - LO_SURROGATE_START + 0x10000; - glyphs[i + 1] = INVISIBLE_GLYPH_ID; - } - } - - glyphs[i] = getGlyphFromCMAP(code); - - if (code < FontUtilities.MIN_LAYOUT_CHARCODE) { - continue; - } - else if (FontUtilities.isComplexCharCode(code) || - CharToGlyphMapper.isVariationSelector(code)) { - return true; - } - else if (code >= 0x10000) { - i += 1; // Empty glyph slot after surrogate - continue; - } - } - - return false; - } - /* A pretty good heuristic is that the cmap we are using * supports 32 bit character codes. */ diff --git a/src/java.desktop/share/classes/sun/font/Type1GlyphMapper.java b/src/java.desktop/share/classes/sun/font/Type1GlyphMapper.java index 6006b70ed9347..42099fe0ccbd8 100644 --- a/src/java.desktop/share/classes/sun/font/Type1GlyphMapper.java +++ b/src/java.desktop/share/classes/sun/font/Type1GlyphMapper.java @@ -98,80 +98,4 @@ public int charToGlyph(int ch) { } } } - - public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) { - /* The conversion into surrogates is misleading. - * The Type1 glyph mapper only accepts 16 bit unsigned shorts. - * If its > not in the range it can use assign the missing glyph. - */ - for (int i=0; i= HI_SURROGATE_START && - code <= HI_SURROGATE_END && i < count - 1) { - char low = unicodes[i + 1]; - - if (low >= LO_SURROGATE_START && - low <= LO_SURROGATE_END) { - code = (code - HI_SURROGATE_START) * - 0x400 + low - LO_SURROGATE_START + 0x10000; - glyphs[i + 1] = 0xFFFF; // invisible glyph - } - } - glyphs[i] = charToGlyph(code); - if (code >= 0x10000) { - i += 1; // Empty glyph slot after surrogate - } - } - } - - public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) { - /* I believe this code path is never exercised. Its there mainly - * for surrogates and/or the opentype engine which aren't likely - * to be an issue for Type1 fonts. So no need to optimise it. - */ - for (int i=0; i= HI_SURROGATE_START && - code <= HI_SURROGATE_END && i < count - 1) { - char low = unicodes[i + 1]; - - if (low >= LO_SURROGATE_START && - low <= LO_SURROGATE_END) { - code = (code - HI_SURROGATE_START) * - 0x400 + low - LO_SURROGATE_START + 0x10000; - glyphs[i + 1] = INVISIBLE_GLYPH_ID; - } - } - - glyphs[i] = charToGlyph(code); - - if (code < FontUtilities.MIN_LAYOUT_CHARCODE) { - continue; - } - else if (FontUtilities.isComplexCharCode(code)) { - return true; - } - else if (code >= 0x10000) { - i += 1; // Empty glyph slot after surrogate - continue; - } - } - - return false; - } } diff --git a/src/java.desktop/share/classes/sun/java2d/pipe/GlyphListLoopPipe.java b/src/java.desktop/share/classes/sun/java2d/pipe/GlyphListLoopPipe.java index ebe1146483e75..41bf0dccfcf3a 100644 --- a/src/java.desktop/share/classes/sun/java2d/pipe/GlyphListLoopPipe.java +++ b/src/java.desktop/share/classes/sun/java2d/pipe/GlyphListLoopPipe.java @@ -26,6 +26,7 @@ package sun.java2d.pipe; import sun.awt.SunHints; +import sun.font.StrikeCache; import sun.java2d.SunGraphics2D; import sun.font.GlyphList; @@ -41,50 +42,49 @@ public abstract class GlyphListLoopPipe extends GlyphListPipe protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl, int aaHint) { int prevLimit = 0; - boolean isColor = false; + byte pixelFormat = StrikeCache.PIXEL_FORMAT_UNKNOWN; int len = gl.getNumGlyphs(); gl.startGlyphIteration(); if (GlyphList.canContainColorGlyphs()) { for (int i = 0; i < len; i++) { - boolean newIsColor = gl.isColorGlyph(i); - if (newIsColor != isColor) { - drawGlyphListSegment(sg2d, gl, prevLimit, i, aaHint, - isColor); + byte newFormat = gl.getPixelFormat(i); + if (newFormat != pixelFormat) { + drawGlyphListSegment(sg2d, gl, + prevLimit, i, aaHint, pixelFormat); prevLimit = i; - isColor = newIsColor; + pixelFormat = newFormat; } } } - drawGlyphListSegment(sg2d, gl, prevLimit, len, aaHint, isColor); + drawGlyphListSegment(sg2d, gl, prevLimit, len, aaHint, pixelFormat); } private void drawGlyphListSegment(SunGraphics2D sg2d, GlyphList gl, int fromglyph, int toGlyph, - int aaHint, boolean isColor) { + int aaHint, byte pixelFormat) { if (fromglyph >= toGlyph) return; - if (isColor) { - sg2d.loops.drawGlyphListColorLoop. - DrawGlyphListColor(sg2d, sg2d.surfaceData, - gl, fromglyph, toGlyph); - } else { - switch (aaHint) { - case SunHints.INTVAL_TEXT_ANTIALIAS_OFF: + switch (pixelFormat) { + case StrikeCache.PIXEL_FORMAT_GREYSCALE: + if (aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_OFF) { sg2d.loops.drawGlyphListLoop. DrawGlyphList(sg2d, sg2d.surfaceData, gl, fromglyph, toGlyph); - return; - case SunHints.INTVAL_TEXT_ANTIALIAS_ON: + } else { sg2d.loops.drawGlyphListAALoop. DrawGlyphListAA(sg2d, sg2d.surfaceData, gl, fromglyph, toGlyph); - return; - case SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB: - case SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB: - sg2d.loops.drawGlyphListLCDLoop. - DrawGlyphListLCD(sg2d, sg2d.surfaceData, - gl, fromglyph, toGlyph); - return; - } + } + return; + case StrikeCache.PIXEL_FORMAT_LCD: + sg2d.loops.drawGlyphListLCDLoop. + DrawGlyphListLCD(sg2d, sg2d.surfaceData, + gl, fromglyph, toGlyph); + return; + case StrikeCache.PIXEL_FORMAT_BGRA: + sg2d.loops.drawGlyphListColorLoop. + DrawGlyphListColor(sg2d, sg2d.surfaceData, + gl, fromglyph, toGlyph); + return; } } } diff --git a/src/java.desktop/share/classes/sun/java2d/pipe/OutlineTextRenderer.java b/src/java.desktop/share/classes/sun/java2d/pipe/OutlineTextRenderer.java index b657e434d2f91..e40e52afebadd 100644 --- a/src/java.desktop/share/classes/sun/java2d/pipe/OutlineTextRenderer.java +++ b/src/java.desktop/share/classes/sun/java2d/pipe/OutlineTextRenderer.java @@ -28,12 +28,13 @@ import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.font.TextLayout; + +import sun.font.GlyphRenderData; +import sun.font.StandardGlyphVector; import sun.java2d.SunGraphics2D; import sun.awt.SunHints; -import java.awt.Shape; -import java.awt.geom.AffineTransform; -import java.awt.font.TextLayout; +import java.awt.geom.GeneralPath; /** * A delegate pipe of SG2D for drawing "large" text with @@ -78,36 +79,22 @@ public void drawString(SunGraphics2D g2d, String str, double x, double y) { } TextLayout tl = new TextLayout(str, g2d.getFont(), g2d.getFontRenderContext()); - Shape s = tl.getOutline(AffineTransform.getTranslateInstance(x, y)); - - int textAAHint = g2d.getFontInfo().aaHint; - - int prevaaHint = - 1; - if (textAAHint != SunHints.INTVAL_TEXT_ANTIALIAS_OFF && - g2d.antialiasHint != SunHints.INTVAL_ANTIALIAS_ON) { - prevaaHint = g2d.antialiasHint; - g2d.antialiasHint = SunHints.INTVAL_ANTIALIAS_ON; - g2d.validatePipe(); - } else if (textAAHint == SunHints.INTVAL_TEXT_ANTIALIAS_OFF - && g2d.antialiasHint != SunHints.INTVAL_ANTIALIAS_OFF) { - prevaaHint = g2d.antialiasHint; - g2d.antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF; - g2d.validatePipe(); - } - g2d.fill(s); - - if (prevaaHint != -1) { - g2d.antialiasHint = prevaaHint; - g2d.validatePipe(); - } + // This will end up calling our drawGlyphVector + tl.draw(g2d, (float) x, (float) y); } public void drawGlyphVector(SunGraphics2D g2d, GlyphVector gv, float x, float y) { + GlyphRenderData grd; + if (gv instanceof StandardGlyphVector) { + grd = ((StandardGlyphVector) gv).getGlyphRenderData(x, y); + } else { + grd = new GlyphRenderData(); + grd.outline = new GeneralPath(gv.getOutline(x, y)); + } - Shape s = gv.getOutline(x, y); int prevaaHint = - 1; FontRenderContext frc = gv.getFontRenderContext(); boolean aa = frc.isAntiAliased(); @@ -134,7 +121,7 @@ public void drawGlyphVector(SunGraphics2D g2d, GlyphVector gv, g2d.validatePipe(); } - g2d.fill(s); + grd.draw(g2d); if (prevaaHint != -1) { g2d.antialiasHint = prevaaHint; diff --git a/src/java.desktop/share/native/common/font/fontscalerdefs.h b/src/java.desktop/share/native/common/font/fontscalerdefs.h index 520ba406e04c4..686bf4ae712e6 100644 --- a/src/java.desktop/share/native/common/font/fontscalerdefs.h +++ b/src/java.desktop/share/native/common/font/fontscalerdefs.h @@ -27,6 +27,7 @@ #define FontScalerDefsIncludesDefined #include "AccelGlyphCache.h" +#include "sun_font_StrikeCache.h" #ifdef __cplusplus extern "C" { @@ -74,7 +75,8 @@ typedef struct GlyphInfo { UInt16 width; UInt16 height; UInt16 rowBytes; - UInt8 managed; + UInt8 managed; + UInt8 format; // sun_font_StrikeCache_PIXEL_FORMAT_* float topLeftX; float topLeftY; void *cellInfo; diff --git a/src/java.desktop/share/native/common/font/sunfontids.h b/src/java.desktop/share/native/common/font/sunfontids.h index 2301f80b5bcbf..810a9d2c58a61 100644 --- a/src/java.desktop/share/native/common/font/sunfontids.h +++ b/src/java.desktop/share/native/common/font/sunfontids.h @@ -80,6 +80,12 @@ typedef struct FontManagerNativeIDs { /* sun/font/GlyphList */ jfieldID glyphListX, glyphListY, glyphListLen, glyphImages, glyphListUsePos, glyphListPos, lcdRGBOrder, lcdSubPixPos; + + /* sun/font/GlyphRenderData */ + jfieldID glyphRenderDataOutline, glyphRenderDataColorLayers; + jmethodID glyphRenderDataSetColorLayersListMID, + glyphRenderDataAddColorLayerMID, glyphRenderDataAddColorLayerFGMID, + glyphRenderDataAddBitmapMID; } FontManagerNativeIDs; /* Note: we share variable in the context of fontmanager lib diff --git a/src/java.desktop/share/native/common/java2d/opengl/OGLTextRenderer.c b/src/java.desktop/share/native/common/java2d/opengl/OGLTextRenderer.c index 19389066f872b..7baeb53ca08b1 100644 --- a/src/java.desktop/share/native/common/java2d/opengl/OGLTextRenderer.c +++ b/src/java.desktop/share/native/common/java2d/opengl/OGLTextRenderer.c @@ -1087,7 +1087,7 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps, continue; } - if (ginfo->rowBytes == ginfo->width) { + if (ginfo->format == sun_font_StrikeCache_PIXEL_FORMAT_GREYSCALE) { // grayscale or monochrome glyph data if (ginfo->width <= OGLTR_CACHE_CELL_WIDTH && ginfo->height <= OGLTR_CACHE_CELL_HEIGHT) @@ -1096,7 +1096,7 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps, } else { ok = OGLTR_DrawGrayscaleGlyphNoCache(oglc, ginfo, x, y); } - } else if (ginfo->rowBytes == ginfo->width * 4) { + } else if (ginfo->format == sun_font_StrikeCache_PIXEL_FORMAT_BGRA) { // color glyph data ok = OGLTR_DrawColorGlyphNoCache(oglc, ginfo, x, y); } else { diff --git a/src/java.desktop/share/native/libfontmanager/DrawGlyphList.c b/src/java.desktop/share/native/libfontmanager/DrawGlyphList.c index d32a720939100..b290ca49904be 100644 --- a/src/java.desktop/share/native/libfontmanager/DrawGlyphList.c +++ b/src/java.desktop/share/native/libfontmanager/DrawGlyphList.c @@ -545,8 +545,7 @@ GlyphBlitVector* setupLCDBlitVector(JNIEnv *env, jobject glyphlist, free(gbv); return (GlyphBlitVector*)NULL; } - /* rowBytes==width tests if its a B&W or LCD glyph */ - if (ginfo->width == ginfo->rowBytes) { + if (ginfo->format != sun_font_StrikeCache_PIXEL_FORMAT_LCD) { subPixPos = JNI_FALSE; } } diff --git a/src/java.desktop/share/native/libfontmanager/HBShaper.c b/src/java.desktop/share/native/libfontmanager/HBShaper.c index ffb3419286d92..5f62caf96e289 100644 --- a/src/java.desktop/share/native/libfontmanager/HBShaper.c +++ b/src/java.desktop/share/native/libfontmanager/HBShaper.c @@ -69,7 +69,7 @@ static int init_JNI_IDs(JNIEnv *env) { // gmask is the composite font slot mask // baseindex is to be added to the character (code point) index. jboolean storeGVData(JNIEnv* env, - jobject gvdata, jint slot, + jobject gvdata, jint slot, jint slotShift, jint baseIndex, int offset, jobject startPt, int charCount, int glyphCount, hb_glyph_info_t *glyphInfo, hb_glyph_position_t *glyphPos, float devScale) { @@ -135,7 +135,7 @@ jboolean storeGVData(JNIEnv* env, int storei = i + initialCount; int cluster = glyphInfo[i].cluster - offset; indices[storei] = baseIndex + cluster; - glyphs[storei] = (unsigned int)(glyphInfo[i].codepoint | slot); + glyphs[storei] = (unsigned int)((glyphInfo[i].codepoint << slotShift) | slot); positions[storei*2] = startX + x + glyphPos[i].x_offset * scale; positions[(storei*2)+1] = startY + y - glyphPos[i].y_offset * scale; x += glyphPos[i].x_advance * scale; @@ -240,7 +240,8 @@ JNIEXPORT jboolean JNICALL Java_sun_font_SunLayoutEngine_shape jint baseIndex, jobject startPt, jint flags, - jint slot) { + jint slot, + jint slotShift) { hb_buffer_t *buffer; hb_face_t* hbface; @@ -303,7 +304,7 @@ JNIEXPORT jboolean JNICALL Java_sun_font_SunLayoutEngine_shape glyphInfo = hb_buffer_get_glyph_infos(buffer, 0); glyphPos = hb_buffer_get_glyph_positions(buffer, &buflen); - ret = storeGVData(env, gvdata, slot, baseIndex, offset, startPt, + ret = storeGVData(env, gvdata, slot, slotShift, baseIndex, offset, startPt, limit - offset, glyphCount, glyphInfo, glyphPos, jdkFontInfo->devScale); diff --git a/src/java.desktop/share/native/libfontmanager/freetypeScaler.c b/src/java.desktop/share/native/libfontmanager/freetypeScaler.c index 8f5f66fe09f99..5b4272ff99d47 100644 --- a/src/java.desktop/share/native/libfontmanager/freetypeScaler.c +++ b/src/java.desktop/share/native/libfontmanager/freetypeScaler.c @@ -44,6 +44,49 @@ #include FT_LCD_FILTER_H #include FT_MODULE_H +#if defined(_WIN32) || defined(__APPLE__) +#include FT_COLOR_H +#define COLOR_OUTLINES_AVAILABLE (1) +#else +// Linux is built with system Freetype by default, +// and it's often a bit old and doesn't have FT_COLOR_H. +// Thus, we load required symbols dynamically on Linux. +#define DYNAMIC_COLOR_OUTLINES + +typedef struct FT_Color__Dynamic { + FT_Byte blue; + FT_Byte green; + FT_Byte red; + FT_Byte alpha; +} FT_Color_Dynamic; + +typedef struct FT_LayerIterator__Dynamic { + FT_UInt num_layers; + FT_UInt layer; + FT_Byte* p; +} FT_LayerIterator_Dynamic; + +#define FT_Color FT_Color_Dynamic +#define FT_LayerIterator FT_LayerIterator_Dynamic + +typedef FT_Error (*FT_Palette_Select_Func)(FT_Face face, + FT_UShort palette_index, + FT_Color* *apalette); + +typedef FT_Bool (*FT_Get_Color_Glyph_Layer_Func)(FT_Face face, + FT_UInt base_glyph, + FT_UInt *aglyph_index, + FT_UInt *acolor_index, + FT_LayerIterator* iterator); + +FT_Palette_Select_Func FT_Palette_Select_Dynamic = NULL; +FT_Get_Color_Glyph_Layer_Func FT_Get_Color_Glyph_Layer_Dynamic = NULL; + +#define FT_Palette_Select FT_Palette_Select_Dynamic +#define FT_Get_Color_Glyph_Layer FT_Get_Color_Glyph_Layer_Dynamic +#define COLOR_OUTLINES_AVAILABLE (FT_Palette_Select && FT_Get_Color_Glyph_Layer) +#endif + #include "fontscaler.h" #define CHECK_EXCEPTION(env, describe) \ @@ -56,6 +99,16 @@ #define FloatToFTFixed(f) (FT_Fixed)((f) * (float)(ftFixed1)) #define FTFixedToFloat(x) ((x) / (float)(ftFixed1)) #define FT26Dot6ToFloat(x) ((x) / ((float) (1<<6))) +#define FT26Dot6ToInt(x) (((int)(x)) >> 6) +#define FT26Dot6ToIntRound(x) (((int)(x + (1 << 5))) >> 6) +#define FT26Dot6ToIntCeil(x) (((int)(x - 1 + (1 << 6))) >> 6) +#define IntToFT26Dot6(x) (((FT_Fixed)(x)) << 6) + +// Define these manually when building with old Freetype (before 2.5) +#if !defined(FT_LOAD_COLOR) +#define FT_LOAD_COLOR ( 1L << 20 ) +#define FT_PIXEL_MODE_BGRA 7 +#endif typedef struct { /* Important note: @@ -90,8 +143,18 @@ typedef struct FTScalerContext { int renderFlags; /* configuration specific to particular engine */ int pathType; int ptsz; /* size in points */ + int fixedSizeIndex;/* -1 for scalable fonts and index inside + * scalerInfo->face->available_sizes otherwise */ + jboolean colorFont; } FTScalerContext; +/* SampledBGRABitmap contains (possibly) downscaled image data + * prepared for sampling when generating transformed bitmap */ +typedef struct SampledBGRABitmap { + unsigned char* data; + int left, top, width, height, rowBytes, xDownscale, yDownscale; +} SampledBGRABitmap; + #ifdef DEBUG /* These are referenced in the freetype sources if DEBUG macro is defined. To simplify work with debugging version of freetype we define @@ -115,6 +178,17 @@ Java_sun_font_FreetypeFontScaler_initIDs( debugFonts = JNU_CallStaticMethodByName(env, &ignoreException, "sun/font/FontUtilities", "debugFonts", "()Z").z; + +#ifdef DYNAMIC_COLOR_OUTLINES + void *lib = dlopen("libfreetype.so", RTLD_LOCAL|RTLD_LAZY); + if (!lib) { + lib = dlopen("libfreetype.so.6", RTLD_LOCAL|RTLD_LAZY); + } + if (lib) { + FT_Palette_Select = (FT_Palette_Select_Func)dlsym(lib, "FT_Palette_Select"); + FT_Get_Color_Glyph_Layer = (FT_Get_Color_Glyph_Layer_Func)dlsym(lib, "FT_Get_Color_Glyph_Layer"); + } +#endif } static void freeNativeResources(JNIEnv *env, FTScalerInfo* scalerInfo) { @@ -322,7 +396,7 @@ static void setInterpreterVersion(FT_Library library) { ((FT_MulFix(units_per_EM, y_scale) / BOLD_DIVISOR )) #define BOLD_MODIFIER(units_per_EM, y_scale) \ - (context->doBold ? BOLD_FACTOR(units_per_EM, y_scale) : 0) + ((context->doBold && !context->colorFont) ? BOLD_FACTOR(units_per_EM, y_scale) : 0) static void GlyphSlot_Embolden(FT_GlyphSlot slot, FT_Matrix transform) { FT_Pos extra = 0; @@ -561,7 +635,7 @@ Java_sun_font_FreetypeFontScaler_createScalerContextNative( static void setupTransform(FT_Matrix* target, FTScalerContext *context) { FT_Matrix* transform = &context->transform; - if (context->doItalize) { + if (context->doItalize && !context->colorFont) { // we cannot use FT_GlyphSlot_Oblique as it doesn't work well with arbitrary transforms, // so we add corresponding shear transform to the requested glyph transformation target->xx = FT_MATRIX_ONE; @@ -588,10 +662,36 @@ static int setupFTContext(JNIEnv *env, scalerInfo->font2D = font2D; if (context != NULL) { + context->colorFont = + FT_HAS_COLOR(scalerInfo->face) || !FT_IS_SCALABLE(scalerInfo->face) ? + JNI_TRUE : JNI_FALSE; + setupTransform(&matrix, context); FT_Set_Transform(scalerInfo->face, &matrix, NULL); - errCode = FT_Set_Char_Size(scalerInfo->face, 0, context->ptsz, 72, 72); + int charSize; + if (FT_IS_SCALABLE(scalerInfo->face)) { // Standard scalable face + context->fixedSizeIndex = -1; + charSize = context->ptsz; + } else { // Non-scalable face (that should only be bitmap faces) + const int ptsz = context->ptsz; + // Best size is smallest, but not smaller than requested + int bestSizeIndex = 0; + FT_Pos bestSize = scalerInfo->face->available_sizes[0].size; + int i; + for (i = 1; i < scalerInfo->face->num_fixed_sizes; i++) { + FT_Pos size = scalerInfo->face->available_sizes[i].size; + if ((size >= ptsz && bestSize >= ptsz && size < bestSize) || + (size < ptsz && bestSize < ptsz && size > bestSize) || + (size >= ptsz && bestSize < ptsz)) { + bestSizeIndex = i; + bestSize = size; + } + } + context->fixedSizeIndex = bestSizeIndex; + charSize = (int) bestSize; + } + errCode = FT_Set_Char_Size(scalerInfo->face, 0, charSize, 72, 72); if (errCode == 0) { errCode = FT_Activate_Size(scalerInfo->face->size); @@ -604,7 +704,8 @@ static int setupFTContext(JNIEnv *env, } // using same values as for the transformation matrix -#define OBLIQUE_MODIFIER(y) (context->doItalize ? ((y)*FT_MATRIX_OBLIQUE_XY/FT_MATRIX_ONE) : 0) +#define OBLIQUE_MODIFIER(y) \ + ((context->doItalize && !context->colorFont) ? ((y)*FT_MATRIX_OBLIQUE_XY/FT_MATRIX_ONE) : 0) /* * Class: sun_font_FreetypeFontScaler @@ -665,36 +766,62 @@ Java_sun_font_FreetypeFontScaler_getFontMetricsNative( (-FTFixedToFloat(context->transform.yx) * (x) + \ FTFixedToFloat(context->transform.yy) * (y)) - /* - * See FreeType source code: src/base/ftobjs.c ft_recompute_scaled_metrics() - * http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=1659 - */ - /* ascent */ - ax = 0; - ay = -(jfloat) (FT_MulFixFloatShift6( - ((jlong) scalerInfo->face->ascender), - (jlong) scalerInfo->face->size->metrics.y_scale)); - /* descent */ - dx = 0; - dy = -(jfloat) (FT_MulFixFloatShift6( - ((jlong) scalerInfo->face->descender), - (jlong) scalerInfo->face->size->metrics.y_scale)); - /* baseline */ - bx = by = 0; - - /* leading */ - lx = 0; - ly = (jfloat) (FT_MulFixFloatShift6( - (jlong) scalerInfo->face->height, - (jlong) scalerInfo->face->size->metrics.y_scale)) - + ay - dy; - /* max advance */ - mx = (jfloat) FT26Dot6ToFloat( - scalerInfo->face->size->metrics.max_advance + - OBLIQUE_MODIFIER(scalerInfo->face->size->metrics.height) + - BOLD_MODIFIER(scalerInfo->face->units_per_EM, - scalerInfo->face->size->metrics.y_scale)); - my = 0; + if (context->fixedSizeIndex == -1) { + /* + * See FreeType source code: src/base/ftobjs.c ft_recompute_scaled_metrics() + * http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=1659 + */ + /* ascent */ + ax = 0; + ay = -(jfloat) (FT_MulFixFloatShift6( + ((jlong) scalerInfo->face->ascender), + (jlong) scalerInfo->face->size->metrics.y_scale)); + /* descent */ + dx = 0; + dy = -(jfloat) (FT_MulFixFloatShift6( + ((jlong) scalerInfo->face->descender), + (jlong) scalerInfo->face->size->metrics.y_scale)); + /* baseline */ + bx = by = 0; + + /* leading */ + lx = 0; + ly = (jfloat) (FT_MulFixFloatShift6( + (jlong) scalerInfo->face->height, + (jlong) scalerInfo->face->size->metrics.y_scale)) + + ay - dy; + /* max advance */ + mx = (jfloat) FT26Dot6ToFloat( + scalerInfo->face->size->metrics.max_advance + + OBLIQUE_MODIFIER(scalerInfo->face->size->metrics.height) + + BOLD_MODIFIER(scalerInfo->face->units_per_EM, + scalerInfo->face->size->metrics.y_scale)); + my = 0; + } else { + /* Just manually scale metrics for non-scalable fonts */ + FT_Fixed scale = FT_DivFix(context->ptsz, + scalerInfo->face->available_sizes[context->fixedSizeIndex].size); + /* ascent */ + ax = 0; + ay = -(jfloat) FT_MulFixFloatShift6( + scalerInfo->face->size->metrics.ascender, scale); + /* descent */ + dx = 0; + dy = -(jfloat) FT_MulFixFloatShift6( + scalerInfo->face->size->metrics.descender, scale); + /* baseline */ + bx = by = 0; + + /* leading */ + lx = 0; + ly = (jfloat) FT_MulFixFloatShift6( + scalerInfo->face->size->metrics.height, scale) + ay - dy; + /* max advance */ + /* no bold/italic transformations for non-scalable fonts */ + mx = (jfloat) FT_MulFixFloatShift6( + scalerInfo->face->size->metrics.max_advance, scale); + my = 0; + } metrics = (*env)->NewObject(env, sunFontIDs.strikeMetricsClass, @@ -712,7 +839,7 @@ static jlong getGlyphImageNativeInternal( JNIEnv *env, jobject scaler, jobject font2D, jlong pScalerContext, jlong pScaler, jint glyphCode, - jboolean renderImage); + jboolean renderImage, jboolean setupContext); /* * Class: sun_font_FreetypeFontScaler @@ -741,7 +868,7 @@ Java_sun_font_FreetypeFontScaler_getGlyphAdvanceNative( jlong image; image = getGlyphImageNativeInternal( - env, scaler, font2D, pScalerContext, pScaler, glyphCode, JNI_FALSE); + env, scaler, font2D, pScalerContext, pScaler, glyphCode, JNI_FALSE, JNI_TRUE); info = (GlyphInfo*) jlong_to_ptr(image); if (info != NULL) { @@ -767,7 +894,7 @@ Java_sun_font_FreetypeFontScaler_getGlyphMetricsNative( jlong image = getGlyphImageNativeInternal( env, scaler, font2D, - pScalerContext, pScaler, glyphCode, JNI_FALSE); + pScalerContext, pScaler, glyphCode, JNI_FALSE, JNI_TRUE); info = (GlyphInfo*) jlong_to_ptr(image); if (info != NULL) { @@ -884,6 +1011,201 @@ static void CopyFTSubpixelVToSubpixel(const void* srcImage, int srcRowBytes, } +/* Get enclosing axis-aligned rectangle of transformed bitmap bounds */ +static FT_BBox getTransformedBitmapBoundingBox(FT_GlyphSlot ftglyph, + const FT_Matrix* transform) { + FT_Vector corners[4]; + corners[0].x = corners[2].x = IntToFT26Dot6(ftglyph->bitmap_left); + corners[0].y = corners[1].y = IntToFT26Dot6(ftglyph->bitmap_top); + corners[1].x = corners[3].x = IntToFT26Dot6(ftglyph->bitmap_left + + (FT_Int) ftglyph->bitmap.width); + corners[2].y = corners[3].y = IntToFT26Dot6(ftglyph->bitmap_top - + (FT_Int) ftglyph->bitmap.rows); + + FT_Vector_Transform(corners, transform); + FT_BBox bb = {corners[0].x, corners[0].y, corners[0].x, corners[0].y}; + int i; + for (i = 1; i < 4; i++) { + FT_Vector_Transform(corners + i, transform); + if (corners[i].x < bb.xMin) { + bb.xMin = corners[i].x; + } + if (corners[i].x > bb.xMax) { + bb.xMax = corners[i].x; + } + if (corners[i].y < bb.yMin) { + bb.yMin = corners[i].y; + } + if (corners[i].y > bb.yMax) { + bb.yMax = corners[i].y; + } + } + bb.xMin = FT26Dot6ToInt(bb.xMin); + bb.yMin = FT26Dot6ToInt(bb.yMin); + bb.xMax = FT26Dot6ToIntCeil(bb.xMax); + bb.yMax = FT26Dot6ToIntCeil(bb.yMax); + return bb; +} + +/* Generate SampledBGRABitmap, downscaling original image when necessary. + * It may allocate memory for downscaled image, + * so it must be freed with freeSampledBGRABitmap() */ +static SampledBGRABitmap createSampledBGRABitmap(FT_GlyphSlot ftglyph, + int xDownscale, + int yDownscale) { + SampledBGRABitmap sampledBitmap; + if (xDownscale == 1 && yDownscale == 1) { // No downscale, use original data + sampledBitmap.data = ftglyph->bitmap.buffer; + sampledBitmap.left = ftglyph->bitmap_left; + sampledBitmap.top = ftglyph->bitmap_top; + sampledBitmap.width = (int) ftglyph->bitmap.width; + sampledBitmap.height = (int) ftglyph->bitmap.rows; + sampledBitmap.rowBytes = ftglyph->bitmap.pitch; + sampledBitmap.xDownscale = 1; + sampledBitmap.yDownscale = 1; + } else { // Generate downscaled bitmap + sampledBitmap.left = ftglyph->bitmap_left / xDownscale; + sampledBitmap.top = (ftglyph->bitmap_top + yDownscale - 1) / yDownscale; + sampledBitmap.width = (int) + (ftglyph->bitmap_left + (FT_Pos) ftglyph->bitmap.width - + sampledBitmap.left * xDownscale + xDownscale - 1) / xDownscale; + sampledBitmap.height = (int) + (sampledBitmap.top * yDownscale - ftglyph->bitmap_top + + (FT_Pos) ftglyph->bitmap.rows + yDownscale - 1) / yDownscale; + sampledBitmap.data = + malloc(4 * sampledBitmap.width * sampledBitmap.height); + sampledBitmap.rowBytes = sampledBitmap.width * 4; + sampledBitmap.xDownscale = xDownscale; + sampledBitmap.yDownscale = yDownscale; + int xOffset = sampledBitmap.left * xDownscale - ftglyph->bitmap_left; + int yOffset = ftglyph->bitmap_top - sampledBitmap.top * yDownscale; + int x, y; + for (y = 0; y < sampledBitmap.height; y++) { + for (x = 0; x < sampledBitmap.width; x++) { + // Average pixels + int b = 0, g = 0, r = 0, a = 0; + int xFrom = x * xDownscale + xOffset, + yFrom = y * yDownscale + yOffset, + xTo = xFrom + xDownscale, + yTo = yFrom + yDownscale; + if (xFrom < 0) { + xFrom = 0; + } + if (xTo > (int) ftglyph->bitmap.width) { + xTo = (int) ftglyph->bitmap.width; + } + if (yFrom < 0) { + yFrom = 0; + } + if (yTo > (int) ftglyph->bitmap.rows) { + yTo = (int) ftglyph->bitmap.rows; + } + int i, j; + for (j = yFrom; j < yTo; j++) { + for (i = xFrom; i < xTo; i++) { + int offset = j * ftglyph->bitmap.pitch + i * 4; + b += ftglyph->bitmap.buffer[offset + 0]; + g += ftglyph->bitmap.buffer[offset + 1]; + r += ftglyph->bitmap.buffer[offset + 2]; + a += ftglyph->bitmap.buffer[offset + 3]; + } + } + int offset = y * sampledBitmap.rowBytes + x * 4; + sampledBitmap.data[offset + 0] = b / xDownscale / yDownscale; + sampledBitmap.data[offset + 1] = g / xDownscale / yDownscale; + sampledBitmap.data[offset + 2] = r / xDownscale / yDownscale; + sampledBitmap.data[offset + 3] = a / xDownscale / yDownscale; + } + } + } + return sampledBitmap; +} +static void freeSampledBGRABitmap(SampledBGRABitmap* bitmap) { + if (bitmap->xDownscale != 1 || bitmap->yDownscale != 1) { + free(bitmap->data); + } +} +/* Get color (returned via b, g, r and a variables, [0-256)) + * from specific pixel in bitmap. + * Returns black-transparent (0,0,0,0) color when sampling out of bounds */ +static void sampleBGRABitmapGlyph(int* b, int* g, int* r, int* a, + const SampledBGRABitmap* bitmap, + int x, int y) { + int column = x - bitmap->left, row = bitmap->top - y; + if (column < 0 || column >= bitmap->width || + row < 0 || row >= bitmap->height) { + *b = *g = *r = *a = 0; + } else { + int offset = row * bitmap->rowBytes + column * 4; + *b = bitmap->data[offset + 0]; + *g = bitmap->data[offset + 1]; + *r = bitmap->data[offset + 2]; + *a = bitmap->data[offset + 3]; + } +} +static int bilinearColorMix(int c00, int c10, int c01, int c11, + float x, float y) { + float top = (float) c00 + x * (float) (c10 - c00); + float bottom = (float) c01 + x * (float) (c11 - c01); + return (int) (top + y * (bottom - top)); +} +/* Transform ftglyph into pre-allocated glyphInfo with transform matrix */ +static void transformBGRABitmapGlyph(FT_GlyphSlot ftglyph, GlyphInfo* glyphInfo, + const FT_Matrix* transform, + const FT_BBox* dstBoundingBox, + const jboolean linear) { + FT_Matrix inv = *transform; + FT_Matrix_Invert(&inv); // Transformed -> original bitmap space + int invScaleX = (int) sqrt((double) FTFixedToFloat(FT_MulFix(inv.xx, inv.xx) + + FT_MulFix(inv.xy, inv.xy))); + int invScaleY = (int) sqrt((double) FTFixedToFloat(FT_MulFix(inv.yx, inv.yx) + + FT_MulFix(inv.yy, inv.yy))); + if (invScaleX < 1) { + invScaleX = 1; + } + if (invScaleY < 1) { + invScaleY = 1; + } + SampledBGRABitmap sampledBitmap = + createSampledBGRABitmap(ftglyph, invScaleX, invScaleY); + int x, y; + for (y = 0; y < glyphInfo->height; y++) { + for (x = 0; x < glyphInfo->width; x++) { + FT_Vector position = { + IntToFT26Dot6(dstBoundingBox->xMin + x), + IntToFT26Dot6(dstBoundingBox->yMax - y) + }; + FT_Vector_Transform(&position, &inv); + int sampleX = FT26Dot6ToInt(position.x / invScaleX), + sampleY = FT26Dot6ToInt(position.y / invScaleY); + int b, g, r, a; + sampleBGRABitmapGlyph(&b, &g, &r, &a, + &sampledBitmap, sampleX, sampleY); + if (linear) { + int bX, gX, rX, aX, bY, gY, rY, aY, bXY, gXY, rXY, aXY; + sampleBGRABitmapGlyph(&bX, &gX, &rX, &aX, + &sampledBitmap, sampleX + 1, sampleY); + sampleBGRABitmapGlyph(&bY, &gY, &rY, &aY, + &sampledBitmap, sampleX, sampleY + 1); + sampleBGRABitmapGlyph(&bXY, &gXY, &rXY, &aXY, + &sampledBitmap, sampleX + 1, sampleY + 1); + float fractX = FT26Dot6ToFloat((position.x / invScaleX) & 63), + fractY = FT26Dot6ToFloat((position.y / invScaleY) & 63); + b = bilinearColorMix(b, bX, bY, bXY, fractX, fractY); + g = bilinearColorMix(g, gX, gY, gXY, fractX, fractY); + r = bilinearColorMix(r, rX, rY, rXY, fractX, fractY); + a = bilinearColorMix(a, aX, aY, aXY, fractX, fractY); + } + glyphInfo->image[y*glyphInfo->rowBytes + x * 4 + 0] = b; + glyphInfo->image[y*glyphInfo->rowBytes + x * 4 + 1] = g; + glyphInfo->image[y*glyphInfo->rowBytes + x * 4 + 2] = r; + glyphInfo->image[y*glyphInfo->rowBytes + x * 4 + 3] = a; + } + } + freeSampledBGRABitmap(&sampledBitmap); +} + + /* JDK does not use glyph images for fonts with a * pixel size > 100 (see THRESHOLD in OutlineTextRenderer.java) * so if the glyph bitmap image dimension is > 1024 pixels, @@ -903,14 +1225,14 @@ Java_sun_font_FreetypeFontScaler_getGlyphImageNative( return getGlyphImageNativeInternal( env, scaler, font2D, - pScalerContext, pScaler, glyphCode, JNI_TRUE); + pScalerContext, pScaler, glyphCode, JNI_TRUE, JNI_TRUE); } static jlong getGlyphImageNativeInternal( JNIEnv *env, jobject scaler, jobject font2D, jlong pScalerContext, jlong pScaler, jint glyphCode, - jboolean renderImage) { + jboolean renderImage, jboolean setupContext) { static int PADBYTES = 3; int error, imageSize; @@ -928,10 +1250,12 @@ static jlong return ptr_to_jlong(getNullGlyphImage()); } - error = setupFTContext(env, font2D, scalerInfo, context); - if (error) { - invalidateJavaScaler(env, scaler, scalerInfo); - return ptr_to_jlong(getNullGlyphImage()); + if (setupContext) { + error = setupFTContext(env, font2D, scalerInfo, context); + if (error) { + invalidateJavaScaler(env, scaler, scalerInfo); + return ptr_to_jlong(getNullGlyphImage()); + } } /* @@ -941,11 +1265,17 @@ static jlong * as being compatible with what happened in earlier JDK versions * which did not use freetype. */ - if (context->aaType == TEXT_AA_ON && context->fmType == TEXT_FM_ON) { + if ((context->aaType == TEXT_AA_ON && context->fmType == TEXT_FM_ON) || + context->colorFont) { renderFlags |= FT_LOAD_NO_HINTING; } - if (!context->useSbits) { + if (context->colorFont) { + renderFlags |= FT_LOAD_COLOR; + } + + /* Don't disable bitmaps for color glyphs */ + if (!context->useSbits && !context->colorFont) { renderFlags |= FT_LOAD_NO_BITMAP; } @@ -955,10 +1285,10 @@ static jlong Or we can disable hinting. */ /* select appropriate hinting mode */ - if (context->aaType == TEXT_AA_OFF) { - target = FT_LOAD_TARGET_MONO; - } else if (context->aaType == TEXT_AA_ON) { + if (context->aaType == TEXT_AA_ON || context->colorFont) { target = FT_LOAD_TARGET_NORMAL; + } else if (context->aaType == TEXT_AA_OFF) { + target = FT_LOAD_TARGET_MONO; } else if (context->aaType == TEXT_AA_LCD_HRGB || context->aaType == TEXT_AA_LCD_HBGR) { target = FT_LOAD_TARGET_LCD; @@ -976,14 +1306,18 @@ static jlong ftglyph = scalerInfo->face->glyph; + /* After call to FT_Render_Glyph, glyph format will be changed from + * FT_GLYPH_FORMAT_OUTLINE to FT_GLYPH_FORMAT_BITMAP, so save this value */ + int outlineGlyph = ftglyph->format == FT_GLYPH_FORMAT_OUTLINE; + /* apply styles */ - if (context->doBold) { /* if bold style */ + if (context->doBold && outlineGlyph && !context->colorFont) { /* if bold style */ GlyphSlot_Embolden(ftglyph, context->transform); } /* generate bitmap if it is not done yet e.g. if algorithmic styling is performed and style was added to outline */ - if (renderImage && (ftglyph->format == FT_GLYPH_FORMAT_OUTLINE)) { + if (renderImage && outlineGlyph) { FT_BBox bbox; FT_Outline_Get_CBox(&(ftglyph->outline), &bbox); int w = (int)((bbox.xMax>>6)-(bbox.xMin>>6)); @@ -998,22 +1332,44 @@ static jlong } } + FT_Fixed manualScale = context->fixedSizeIndex == -1 ? ftFixed1 : FT_DivFix( + context->ptsz, scalerInfo->face->available_sizes[context->fixedSizeIndex].size); + FT_Matrix manualTransform; + FT_BBox manualTransformBoundingBox; if (renderImage) { - width = (UInt16) ftglyph->bitmap.width; - rowBytes = width; + if (context->fixedSizeIndex == -1) { + width = (UInt16) ftglyph->bitmap.width; + height = (UInt16) ftglyph->bitmap.rows; + } else { + /* Fixed size glyph, prepare matrix and + * bounding box for manual transformation */ + manualTransform.xx = FT_MulFix(context->transform.xx, manualScale); + manualTransform.xy = FT_MulFix(context->transform.xy, manualScale); + manualTransform.yx = FT_MulFix(context->transform.yx, manualScale); + manualTransform.yy = FT_MulFix(context->transform.yy, manualScale); + manualTransformBoundingBox = + getTransformedBitmapBoundingBox(ftglyph, &manualTransform); + width = (UInt16) (manualTransformBoundingBox.xMax - + manualTransformBoundingBox.xMin); + height = (UInt16) (manualTransformBoundingBox.yMax - + manualTransformBoundingBox.yMin); + } + if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) { + glyphInfo = getNullGlyphImage(); + return ptr_to_jlong(glyphInfo); + } if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD) { - rowBytes = PADBYTES + width + PADBYTES; + rowBytes = PADBYTES + width + PADBYTES; + } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { + rowBytes = width * 4; + } else { + rowBytes = width; } - height = (UInt16) ftglyph->bitmap.rows; - if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) { - glyphInfo = getNullGlyphImage(); - return ptr_to_jlong(glyphInfo); - } - } else { + } else { width = 0; rowBytes = 0; height = 0; - } + } imageSize = rowBytes*height; @@ -1028,9 +1384,23 @@ static jlong glyphInfo->width = width; glyphInfo->height = height; + if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { + glyphInfo->format = sun_font_StrikeCache_PIXEL_FORMAT_BGRA; + } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD || + ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V) { + glyphInfo->format = sun_font_StrikeCache_PIXEL_FORMAT_LCD; + } else { + glyphInfo->format = sun_font_StrikeCache_PIXEL_FORMAT_GREYSCALE; + } + if (renderImage) { - glyphInfo->topLeftX = (float) ftglyph->bitmap_left; - glyphInfo->topLeftY = (float) -ftglyph->bitmap_top; + if (context->fixedSizeIndex == -1) { + glyphInfo->topLeftX = (float) ftglyph->bitmap_left; + glyphInfo->topLeftY = (float) -ftglyph->bitmap_top; + } else { + glyphInfo->topLeftX = (float) manualTransformBoundingBox.xMin; + glyphInfo->topLeftY = (float) -manualTransformBoundingBox.yMax; + } if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD && width > 0) { glyphInfo->width = width/3; @@ -1041,7 +1411,7 @@ static jlong } } - if (context->fmType == TEXT_FM_ON) { + if (context->fmType == TEXT_FM_ON && outlineGlyph) { float advh = FTFixedToFloat(ftglyph->linearHoriAdvance); glyphInfo->advanceX = (float) (advh * FTFixedToFloat(context->transform.xx)); @@ -1049,14 +1419,18 @@ static jlong (float) - (advh * FTFixedToFloat(context->transform.yx)); } else { if (!ftglyph->advance.y) { - glyphInfo->advanceX = FT26Dot6ToFloat(ftglyph->advance.x); + glyphInfo->advanceX = FT26Dot6ToIntRound( + FT_MulFix(ftglyph->advance.x, manualScale)); glyphInfo->advanceY = 0; } else if (!ftglyph->advance.x) { glyphInfo->advanceX = 0; - glyphInfo->advanceY = FT26Dot6ToFloat(-ftglyph->advance.y); + glyphInfo->advanceY = FT26Dot6ToIntRound( + -FT_MulFix(ftglyph->advance.y, manualScale)); } else { - glyphInfo->advanceX = FT26Dot6ToFloat(ftglyph->advance.x); - glyphInfo->advanceY = FT26Dot6ToFloat(-ftglyph->advance.y); + glyphInfo->advanceX = FT26Dot6ToFloat( + FT_MulFix(ftglyph->advance.x, manualScale)); + glyphInfo->advanceY = FT26Dot6ToFloat( + -FT_MulFix(ftglyph->advance.y, manualScale)); } } @@ -1066,46 +1440,65 @@ static jlong glyphInfo->image = (unsigned char*) glyphInfo + sizeof(GlyphInfo); //convert result to output format //output format is either 3 bytes per pixel (for subpixel modes) + //4 bytes per pixel for BGRA glyphs // or 1 byte per pixel for AA and B&W - if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { - /* convert from 8 pixels per byte to 1 byte per pixel */ - CopyBW2Grey8(ftglyph->bitmap.buffer, - ftglyph->bitmap.pitch, - (void *) glyphInfo->image, - width, - width, - height); - } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) { - /* byte per pixel to byte per pixel => just copy */ - memcpy(glyphInfo->image, ftglyph->bitmap.buffer, imageSize); - } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY4) { - /* 4 bits per pixel to byte per pixel */ - CopyGrey4ToGrey8(ftglyph->bitmap.buffer, + if (context->fixedSizeIndex == -1) { + // Standard format convertation without image transformation + if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { + /* convert from 8 pixels per byte to 1 byte per pixel */ + CopyBW2Grey8(ftglyph->bitmap.buffer, ftglyph->bitmap.pitch, (void *) glyphInfo->image, width, width, height); - } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD) { - /* 3 bytes per pixel to 3 bytes per pixel */ - CopyFTSubpixelToSubpixel(ftglyph->bitmap.buffer, - ftglyph->bitmap.pitch, - (void *) (glyphInfo->image+PADBYTES), - rowBytes, - width, - height); - } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V) { - /* 3 bytes per pixel to 3 bytes per pixel */ - CopyFTSubpixelVToSubpixel(ftglyph->bitmap.buffer, - ftglyph->bitmap.pitch, - (void *) glyphInfo->image, - width*3, - width, - height); - glyphInfo->rowBytes *=3; + } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) { + /* byte per pixel to byte per pixel => just copy */ + memcpy(glyphInfo->image, ftglyph->bitmap.buffer, imageSize); + } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY4) { + /* 4 bits per pixel to byte per pixel */ + CopyGrey4ToGrey8(ftglyph->bitmap.buffer, + ftglyph->bitmap.pitch, + (void *) glyphInfo->image, + width, + width, + height); + } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD) { + /* 3 bytes per pixel to 3 bytes per pixel */ + CopyFTSubpixelToSubpixel(ftglyph->bitmap.buffer, + ftglyph->bitmap.pitch, + (void *) (glyphInfo->image+PADBYTES), + rowBytes, + width, + height); + } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V) { + /* 3 bytes per pixel to 3 bytes per pixel */ + CopyFTSubpixelVToSubpixel(ftglyph->bitmap.buffer, + ftglyph->bitmap.pitch, + (void *) glyphInfo->image, + width*3, + width, + height); + glyphInfo->rowBytes *=3; + } else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { + /* 4 bytes per pixel to 4 bytes per pixel => just copy */ + memcpy(glyphInfo->image, ftglyph->bitmap.buffer, imageSize); + } else { + free(glyphInfo); + glyphInfo = getNullGlyphImage(); + } } else { - free(glyphInfo); - glyphInfo = getNullGlyphImage(); + // Here we have to transform image manually + // Only BGRA format is supported (should be enough) + if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { + transformBGRABitmapGlyph(ftglyph, glyphInfo, + &manualTransform, + &manualTransformBoundingBox, + context->aaType != TEXT_AA_OFF); + } else { + free(glyphInfo); + glyphInfo = getNullGlyphImage(); + } } } @@ -1199,23 +1592,12 @@ Java_sun_font_FreetypeFontScaler_getGlyphCodeNative( #define FloatToF26Dot6(x) ((unsigned int) ((x)*64)) -static FT_Outline* getFTOutline(JNIEnv* env, jobject font2D, - FTScalerContext *context, FTScalerInfo* scalerInfo, - jint glyphCode, jfloat xpos, jfloat ypos) { +static FT_Outline* getFTOutlineNoSetup(FTScalerContext *context, FTScalerInfo* scalerInfo, + jint glyphCode, jfloat xpos, jfloat ypos) { int renderFlags; FT_Error error; FT_GlyphSlot ftglyph; - if (glyphCode >= INVISIBLE_GLYPHS || - isNullScalerContext(context) || scalerInfo == NULL) { - return NULL; - } - - error = setupFTContext(env, font2D, scalerInfo, context); - if (error) { - return NULL; - } - renderFlags = FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP; error = FT_Load_Glyph(scalerInfo->face, glyphCode, renderFlags); @@ -1224,9 +1606,10 @@ static FT_Outline* getFTOutline(JNIEnv* env, jobject font2D, } ftglyph = scalerInfo->face->glyph; + int outlineGlyph = ftglyph->format == FT_GLYPH_FORMAT_OUTLINE; /* apply styles */ - if (context->doBold) { /* if bold style */ + if (context->doBold && outlineGlyph && !context->colorFont) { /* if bold style */ GlyphSlot_Embolden(ftglyph, context->transform); } @@ -1237,6 +1620,24 @@ static FT_Outline* getFTOutline(JNIEnv* env, jobject font2D, return &ftglyph->outline; } +static FT_Outline* getFTOutline(JNIEnv* env, jobject font2D, + FTScalerContext *context, FTScalerInfo* scalerInfo, + jint glyphCode, jfloat xpos, jfloat ypos) { + FT_Error error; + + if (glyphCode >= INVISIBLE_GLYPHS || + isNullScalerContext(context) || scalerInfo == NULL) { + return NULL; + } + + error = setupFTContext(env, font2D, scalerInfo, context); + if (error) { + return NULL; + } + + return getFTOutlineNoSetup(context, scalerInfo, glyphCode, xpos, ypos); +} + #define F26Dot6ToFloat(n) (((float)(n))/((float) 64)) /* Types of GeneralPath segments. @@ -1393,19 +1794,13 @@ static void freeGP(GPData* gpdata) { } } -static jobject getGlyphGeneralPath(JNIEnv* env, jobject font2D, - FTScalerContext *context, FTScalerInfo *scalerInfo, - jint glyphCode, jfloat xpos, jfloat ypos) { +static jobject outlineToGeneralPath(JNIEnv* env, FT_Outline* outline) { - FT_Outline* outline; jobject gp = NULL; jbyteArray types; jfloatArray coords; GPData gpdata; - outline = getFTOutline(env, font2D, context, scalerInfo, - glyphCode, xpos, ypos); - if (outline == NULL || outline->n_points == 0) { return gp; } @@ -1443,6 +1838,78 @@ static jobject getGlyphGeneralPath(JNIEnv* env, jobject font2D, return gp; } +static jboolean addColorLayersRenderData(JNIEnv* env, FTScalerContext *context, + FTScalerInfo* scalerInfo, jint glyphCode, + jfloat xpos, jfloat ypos, jobject result) { + + if (!COLOR_OUTLINES_AVAILABLE) return JNI_FALSE; + FT_Error error; + + FT_Color* colors; + error = FT_Palette_Select(scalerInfo->face, 0, &colors); + if (error) { + return JNI_FALSE; + } + + FT_LayerIterator iterator; + iterator.p = NULL; + FT_UInt glyphIndex, colorIndex; + if (!FT_Get_Color_Glyph_Layer(scalerInfo->face, glyphCode, + &glyphIndex, &colorIndex, &iterator)) { + return JNI_FALSE; + } + (*env)->CallVoidMethod(env, result, sunFontIDs.glyphRenderDataSetColorLayersListMID, iterator.num_layers); + do { + FT_Outline* outline = getFTOutlineNoSetup(context, scalerInfo, glyphIndex, xpos, ypos); + jobject gp = outlineToGeneralPath(env, outline); + + if (colorIndex == 0xFFFF) { + (*env)->CallVoidMethod(env, result, sunFontIDs.glyphRenderDataAddColorLayerFGMID, gp); + } else { + (*env)->CallVoidMethod(env, result, sunFontIDs.glyphRenderDataAddColorLayerMID, + colors[colorIndex].red, colors[colorIndex].green, + colors[colorIndex].blue, colors[colorIndex].alpha, gp); + } + } while(FT_Get_Color_Glyph_Layer(scalerInfo->face, glyphCode, + &glyphIndex, &colorIndex, &iterator)); + + return JNI_TRUE; +} + +static void addBitmapRenderData(JNIEnv *env, jobject scaler, jobject font2D, + FTScalerContext *context, FTScalerInfo* scalerInfo, + jint glyphCode, jfloat xpos, jfloat ypos, jobject result) { + GlyphInfo* glyphInfo = (GlyphInfo*) jlong_to_ptr(getGlyphImageNativeInternal( + env, scaler, font2D, + ptr_to_jlong(context), ptr_to_jlong(scalerInfo), + glyphCode, JNI_FALSE, JNI_FALSE)); + + FT_GlyphSlot ftglyph = scalerInfo->face->glyph; + + if (ftglyph->bitmap.pixel_mode != FT_PIXEL_MODE_BGRA) { + return; + } + + int pitch = ftglyph->bitmap.pitch / 4; + int size = pitch * ftglyph->bitmap.rows; + jintArray array = (*env)->NewIntArray(env, size); + (*env)->SetIntArrayRegion(env, array, 0, size, (jint*) ftglyph->bitmap.buffer); + + double bitmapSize = (double) scalerInfo->face->available_sizes[context->fixedSizeIndex].size; + double scale = (double) context->ptsz / bitmapSize / (double) (ftFixed1); + double tx = ftglyph->bitmap_left + xpos * bitmapSize / (double) context->ptsz; + double ty = -ftglyph->bitmap_top + ypos * bitmapSize / (double) context->ptsz; + + jdouble m00 = (jdouble) context->transform.xx * scale, m10 = (jdouble) context->transform.xy * scale; + jdouble m01 = (jdouble) context->transform.yx * scale, m11 = (jdouble) context->transform.yy * scale; + jdouble m02 = m00 * tx + m01 * ty, m12 = m10 * tx + m11 * ty; + + free(glyphInfo); + (*env)->CallVoidMethod(env, result, sunFontIDs.glyphRenderDataAddBitmapMID, + m00, m10, m01, m11, m02, m12, + ftglyph->bitmap.width, ftglyph->bitmap.rows, pitch, 2, array); +} + /* * Class: sun_font_FreetypeFontScaler * Method: getGlyphOutlineNative @@ -1457,13 +1924,10 @@ Java_sun_font_FreetypeFontScaler_getGlyphOutlineNative( (FTScalerContext*) jlong_to_ptr(pScalerContext); FTScalerInfo* scalerInfo = (FTScalerInfo *) jlong_to_ptr(pScaler); - jobject gp = getGlyphGeneralPath(env, - font2D, - context, - scalerInfo, - glyphCode, - xpos, - ypos); + FT_Outline* outline = getFTOutline(env, font2D, context, + scalerInfo, glyphCode, + xpos, ypos); + jobject gp = outlineToGeneralPath(env, outline); if (gp == NULL) { /* can be legal */ gp = (*env)->NewObject(env, sunFontIDs.gpClass, @@ -1472,6 +1936,44 @@ Java_sun_font_FreetypeFontScaler_getGlyphOutlineNative( return gp; } +/* + * Class: sun_font_FreetypeFontScaler + * Method: getGlyphRenderDataNative + * Signature: (Lsun/font/Font2D;JIFFLsun/font/GlyphRenderData;)V + */ +JNIEXPORT void JNICALL +Java_sun_font_FreetypeFontScaler_getGlyphRenderDataNative( + JNIEnv *env, jobject scaler, jobject font2D, jlong pScalerContext, + jlong pScaler, jint glyphCode, jfloat xpos, jfloat ypos, jobject result) { + + FTScalerContext *context = + (FTScalerContext*) jlong_to_ptr(pScalerContext); + FTScalerInfo* scalerInfo = (FTScalerInfo *) jlong_to_ptr(pScaler); + + if (glyphCode >= INVISIBLE_GLYPHS || + isNullScalerContext(context) || scalerInfo == NULL) { + return; + } + + FT_Error error = setupFTContext(env, font2D, scalerInfo, context); + if (error) { + return; + } + + if (context->fixedSizeIndex == -1) { + if (!context->colorFont || + !addColorLayersRenderData(env, context, scalerInfo, glyphCode, xpos, ypos, result)) { + FT_Outline* outline = getFTOutlineNoSetup(context, scalerInfo, glyphCode, xpos, ypos); + jobject gp = outlineToGeneralPath(env, outline); + if (gp != NULL) { + (*env)->SetObjectField(env, result, sunFontIDs.glyphRenderDataOutline, gp); + } + } + } else { + addBitmapRenderData(env, scaler, font2D, context, scalerInfo, glyphCode, xpos, ypos, result); + } +} + /* * Class: sun_font_FreetypeFontScaler * Method: getGlyphOutlineBoundsNative @@ -1520,97 +2022,6 @@ Java_sun_font_FreetypeFontScaler_getGlyphOutlineBoundsNative( return bounds; } -/* - * Class: sun_font_FreetypeFontScaler - * Method: getGlyphVectorOutlineNative - * Signature: (Lsun/font/Font2D;J[IIFF)Ljava/awt/geom/GeneralPath; - */ -JNIEXPORT jobject -JNICALL -Java_sun_font_FreetypeFontScaler_getGlyphVectorOutlineNative( - JNIEnv *env, jobject scaler, jobject font2D, - jlong pScalerContext, jlong pScaler, - jintArray glyphArray, jint numGlyphs, jfloat xpos, jfloat ypos) { - - FT_Outline* outline; - jobject gp = NULL; - jbyteArray types; - jfloatArray coords; - GPData gpdata; - int i; - jint *glyphs; - - FTScalerContext *context = - (FTScalerContext*) jlong_to_ptr(pScalerContext); - FTScalerInfo *scalerInfo = - (FTScalerInfo*) jlong_to_ptr(pScaler); - - glyphs = NULL; - if (numGlyphs > 0 && 0xffffffffu / sizeof(jint) >= (unsigned int)numGlyphs) { - glyphs = (jint*) malloc(numGlyphs*sizeof(jint)); - } - if (glyphs == NULL) { - // We reach here if: - // 1. numGlyphs <= 0, - // 2. overflow check failed, or - // 3. malloc failed. - gp = (*env)->NewObject(env, sunFontIDs.gpClass, sunFontIDs.gpCtrEmpty); - return gp; - } - - (*env)->GetIntArrayRegion(env, glyphArray, 0, numGlyphs, glyphs); - - gpdata.numCoords = 0; - for (i=0; i= INVISIBLE_GLYPHS) { - continue; - } - outline = getFTOutline(env, - font2D, - context, - scalerInfo, - glyphs[i], - xpos, ypos); - - if (outline == NULL || outline->n_points == 0) { - continue; - } - - gpdata.pointTypes = NULL; - gpdata.pointCoords = NULL; - if (!allocateSpaceForGP(&gpdata, outline->n_points, - outline->n_contours)) { - break; - } - - addToGP(&gpdata, outline); - } - free(glyphs); - - if (gpdata.numCoords != 0) { - types = (*env)->NewByteArray(env, gpdata.numTypes); - coords = (*env)->NewFloatArray(env, gpdata.numCoords); - - if (types && coords) { - (*env)->SetByteArrayRegion(env, types, 0, - gpdata.numTypes, gpdata.pointTypes); - (*env)->SetFloatArrayRegion(env, coords, 0, - gpdata.numCoords, gpdata.pointCoords); - - gp=(*env)->NewObject(env, - sunFontIDs.gpClass, - sunFontIDs.gpCtr, - gpdata.wr, - types, - gpdata.numTypes, - coords, - gpdata.numCoords); - return gp; - } - } - return (*env)->NewObject(env, sunFontIDs.gpClass, sunFontIDs.gpCtrEmpty); -} - JNIEXPORT jlong JNICALL Java_sun_font_FreetypeFontScaler_getUnitsPerEMNative( JNIEnv *env, jobject scaler, jlong pScaler) { diff --git a/src/java.desktop/share/native/libfontmanager/sunFont.c b/src/java.desktop/share/native/libfontmanager/sunFont.c index 661dccae0c5c6..0e9438701c2e2 100644 --- a/src/java.desktop/share/native/libfontmanager/sunFont.c +++ b/src/java.desktop/share/native/libfontmanager/sunFont.c @@ -189,6 +189,20 @@ static void initFontIDs(JNIEnv *env) { CHECK_NULL(sunFontIDs.lcdSubPixPos = (*env)->GetFieldID(env, tmpClass, "lcdSubPixPos", "Z")); + CHECK_NULL(tmpClass = (*env)->FindClass(env, "sun/font/GlyphRenderData")); + CHECK_NULL(sunFontIDs.glyphRenderDataOutline = + (*env)->GetFieldID(env, tmpClass, "outline", "Ljava/awt/geom/GeneralPath;")); + CHECK_NULL(sunFontIDs.glyphRenderDataColorLayers = + (*env)->GetFieldID(env, tmpClass, "colorLayers", "Ljava/util/List;")); + CHECK_NULL(sunFontIDs.glyphRenderDataSetColorLayersListMID = + (*env)->GetMethodID(env, tmpClass, "setColorLayersList", "(I)V")); + CHECK_NULL(sunFontIDs.glyphRenderDataAddColorLayerMID = + (*env)->GetMethodID(env, tmpClass, "addColorLayers", "(IIIILjava/awt/geom/GeneralPath;)V")); + CHECK_NULL(sunFontIDs.glyphRenderDataAddColorLayerFGMID = + (*env)->GetMethodID(env, tmpClass, "addColorLayers", "(Ljava/awt/geom/GeneralPath;)V")); + CHECK_NULL(sunFontIDs.glyphRenderDataAddBitmapMID = + (*env)->GetMethodID(env, tmpClass, "addBitmap", "(DDDDDDIIII[I)V")); + initLCDGammaTables(); initialisedFontIDs = 1; @@ -314,7 +328,7 @@ Java_sun_font_StrikeCache_getGlyphCacheDescription GlyphInfo *info; size_t baseAddr; - if ((*env)->GetArrayLength(env, results) < 13) { + if ((*env)->GetArrayLength(env, results) < 14) { return; } @@ -341,6 +355,7 @@ Java_sun_font_StrikeCache_getGlyphCacheDescription nresults[10] = (jlong)(uintptr_t)info; /* invisible glyph */ nresults[11] = (size_t)&(info->cellInfo)-baseAddr; nresults[12] = (size_t)&(info->managed)-baseAddr; + nresults[13] = (size_t)&(info->format)-baseAddr; (*env)->ReleasePrimitiveArrayCritical(env, results, nresults, 0); } diff --git a/src/java.desktop/unix/classes/sun/font/DelegateStrike.java b/src/java.desktop/unix/classes/sun/font/DelegateStrike.java index 4f2de5ebc2f12..e046ae9c1f56f 100644 --- a/src/java.desktop/unix/classes/sun/font/DelegateStrike.java +++ b/src/java.desktop/unix/classes/sun/font/DelegateStrike.java @@ -107,8 +107,4 @@ GeneralPath getGlyphOutline(int glyphCode, float x, float y) { return delegateStrike.getGlyphOutline(glyphCode, x, y); } - GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { - return delegateStrike.getGlyphVectorOutline(glyphs, x, y); - } - } diff --git a/src/java.desktop/unix/classes/sun/font/NativeFont.java b/src/java.desktop/unix/classes/sun/font/NativeFont.java index 337d38c095ac0..6d361a7360e22 100644 --- a/src/java.desktop/unix/classes/sun/font/NativeFont.java +++ b/src/java.desktop/unix/classes/sun/font/NativeFont.java @@ -286,12 +286,6 @@ void getGlyphMetrics(long pScalerContext, int glyphCode, throw new RuntimeException("this should be called on the strike"); } - public GeneralPath getGlyphVectorOutline(long pScalerContext, - int[] glyphs, int numGlyphs, - float x, float y) { - return null; - } - private native int countGlyphs(byte[] platformNameBytes, int ptSize); public int getNumGlyphs() { @@ -310,6 +304,10 @@ PhysicalFont getDelegateFont() { return delegateFont; } + public SlotInfo getSlotInfoForGlyph(int glyphCode) { + return new SlotInfo(getDelegateFont()); + } + /* Specify that the dpi is 72x72, as this corresponds to JDK's * default user space. These are the 10th and 11th fields in the XLFD. * ptSize in XLFD is in 10th's of a point so multiply by 10, diff --git a/src/java.desktop/unix/classes/sun/font/NativeStrike.java b/src/java.desktop/unix/classes/sun/font/NativeStrike.java index bc735367b9936..87fee0e9e6443 100644 --- a/src/java.desktop/unix/classes/sun/font/NativeStrike.java +++ b/src/java.desktop/unix/classes/sun/font/NativeStrike.java @@ -288,8 +288,8 @@ GeneralPath getGlyphOutline(int glyphCode, float x, float y) { return new GeneralPath(); } - GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { - return new GeneralPath(); + GlyphRenderData getGlyphRenderData(int glyphCode, float x, float y) { + return new GlyphRenderData(); } } diff --git a/src/java.desktop/unix/classes/sun/font/X11TextRenderer.java b/src/java.desktop/unix/classes/sun/font/X11TextRenderer.java index bac23128cd8d3..6b5640d27921f 100644 --- a/src/java.desktop/unix/classes/sun/font/X11TextRenderer.java +++ b/src/java.desktop/unix/classes/sun/font/X11TextRenderer.java @@ -67,7 +67,7 @@ public void drawGlyphVector(SunGraphics2D sg2d, GlyphVector g, } } - native void doDrawGlyphList(long dstData, long xgc, + native boolean doDrawGlyphList(long dstData, long xgc, Region clip, GlyphList gl); protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl) { @@ -78,7 +78,27 @@ protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl) { long xgc = x11sd.getRenderGC(clip, SunGraphics2D.COMP_ISCOPY, null, sg2d.pixel); gl.startGlyphIteration(); - doDrawGlyphList(x11sd.getNativeOps(), xgc, clip, gl); + boolean allGlyphsRendered = doDrawGlyphList(x11sd.getNativeOps(), xgc, clip, gl); + // There are some color glyphs, which we couldn't draw + if (!allGlyphsRendered) { + gl.startGlyphIteration(); + for (int i = 0; i < gl.getNumGlyphs(); i++) { + if (gl.isColorGlyph(i)) { + int end; + for (end = i + 1; end < gl.getNumGlyphs(); end++) { + if (!gl.isColorGlyph(end)) { + break; + } + } + sg2d.loops.drawGlyphListColorLoop. + DrawGlyphListColor(sg2d, sg2d.surfaceData, + gl, i, end); + i = end - 1; + } else { + gl.setGlyphIndex(i); + } + } + } } finally { SunToolkit.awtUnlock(); } @@ -89,11 +109,11 @@ public X11TextRenderer traceWrap() { } public static class Tracer extends X11TextRenderer { - void doDrawGlyphList(long dstData, long xgc, + boolean doDrawGlyphList(long dstData, long xgc, Region clip, GlyphList gl) { GraphicsPrimitive.tracePrimitive("X11DrawGlyphs"); - super.doDrawGlyphList(dstData, xgc, clip, gl); + return super.doDrawGlyphList(dstData, xgc, clip, gl); } } } diff --git a/src/java.desktop/unix/classes/sun/font/XRGlyphCache.java b/src/java.desktop/unix/classes/sun/font/XRGlyphCache.java index 04b5c1a9504a5..e73378ca2a6d7 100644 --- a/src/java.desktop/unix/classes/sun/font/XRGlyphCache.java +++ b/src/java.desktop/unix/classes/sun/font/XRGlyphCache.java @@ -38,6 +38,12 @@ */ public class XRGlyphCache implements GlyphDisposedListener { + /** + * BGRA glyphs are rendered as images + * and therefore don't belong to any glyph set + */ + public static final int BGRA_GLYPH_SET = -1; + XRBackend con; XRCompositeManager maskBuffer; HashMap cacheMap = new HashMap(256); @@ -47,6 +53,7 @@ public class XRGlyphCache implements GlyphDisposedListener { int grayGlyphSet; int lcdGlyphSet; + final EnumMap glyphSetsByType; int time = 0; int cachedPixels = 0; @@ -63,6 +70,11 @@ public XRGlyphCache(XRCompositeManager maskBuf) { grayGlyphSet = con.XRenderCreateGlyphSet(XRUtils.PictStandardA8); lcdGlyphSet = con.XRenderCreateGlyphSet(XRUtils.PictStandardARGB32); + glyphSetsByType = new EnumMap<>(XRGlyphCacheEntry.Type.class); + glyphSetsByType.put(XRGlyphCacheEntry.Type.GRAYSCALE, grayGlyphSet); + glyphSetsByType.put(XRGlyphCacheEntry.Type.LCD, lcdGlyphSet); + glyphSetsByType.put(XRGlyphCacheEntry.Type.BGRA, BGRA_GLYPH_SET); + StrikeCache.addGlyphDisposedListener(this); } @@ -104,7 +116,7 @@ protected XRGlyphCacheEntry getEntryForPointer(long imgPtr) { return cacheMap.get(tmp); } - public XRGlyphCacheEntry[] cacheGlyphs(GlyphList glyphList) { + public XRGlyphCacheEntry[] cacheGlyphs(GlyphList glyphList, int parentXid) { time++; XRGlyphCacheEntry[] entries = new XRGlyphCacheEntry[glyphList.getNumGlyphs()]; @@ -114,7 +126,8 @@ public XRGlyphCacheEntry[] cacheGlyphs(GlyphList glyphList) { for (int i = 0; i < glyphList.getNumGlyphs(); i++) { XRGlyphCacheEntry glyph; - if (imgPtrs[i] == 0L) { + if (imgPtrs[i] == 0L || + imgPtrs[i] == StrikeCache.invisibleGlyphPtr) { continue; } // Find uncached glyphs and queue them for upload @@ -124,7 +137,7 @@ public XRGlyphCacheEntry[] cacheGlyphs(GlyphList glyphList) { cacheMap.put(new MutableInteger(glyph.getGlyphID()), glyph); if (uncachedGlyphs == null) { - uncachedGlyphs = new ArrayList(); + uncachedGlyphs = new ArrayList<>(); } uncachedGlyphs.add(glyph); } @@ -134,13 +147,15 @@ public XRGlyphCacheEntry[] cacheGlyphs(GlyphList glyphList) { // Add glyphs to cache if (uncachedGlyphs != null) { - uploadGlyphs(entries, uncachedGlyphs, glyphList, null); + uploadGlyphs(entries, uncachedGlyphs, glyphList, parentXid); } return entries; } - protected void uploadGlyphs(XRGlyphCacheEntry[] glyphs, ArrayList uncachedGlyphs, GlyphList gl, int[] glIndices) { + protected void uploadGlyphs(XRGlyphCacheEntry[] glyphs, + ArrayList uncachedGlyphs, + GlyphList gl, int parentXid) { for (XRGlyphCacheEntry glyph : uncachedGlyphs) { cachedPixels += glyph.getPixelCnt(); } @@ -149,67 +164,59 @@ protected void uploadGlyphs(XRGlyphCacheEntry[] glyphs, ArrayList[] seperatedGlyphList = seperateGlyphTypes(uncachedGlyphs, containsLCDGlyphs); - List grayGlyphList = seperatedGlyphList[0]; - List lcdGlyphList = seperatedGlyphList[1]; + EnumMap> + glyphListsByType = separateGlyphTypes(uncachedGlyphs); + + uploadGlyphs(grayGlyphSet, gl, + glyphListsByType.get(XRGlyphCacheEntry.Type.GRAYSCALE)); + uploadGlyphs(lcdGlyphSet, gl, + glyphListsByType.get(XRGlyphCacheEntry.Type.LCD)); + List bgraGlyphs = glyphListsByType.getOrDefault( + XRGlyphCacheEntry.Type.BGRA, List.of()); + if (!bgraGlyphs.isEmpty()) { + con.addBGRAGlyphImages(parentXid, bgraGlyphs); + } + } + private void uploadGlyphs(int glyphSet, GlyphList glyphList, + List cacheEntries) { + if (cacheEntries == null || cacheEntries.isEmpty()) { + return; + } /* * Some XServers crash when uploading multiple glyphs at once. TODO: * Implement build-switch in local case for distributors who know their * XServer is fixed */ if (batchGlyphUpload) { - if (grayGlyphList != null && grayGlyphList.size() > 0) { - con.XRenderAddGlyphs(grayGlyphSet, gl, grayGlyphList, generateGlyphImageStream(grayGlyphList)); - } - if (lcdGlyphList != null && lcdGlyphList.size() > 0) { - con.XRenderAddGlyphs(lcdGlyphSet, gl, lcdGlyphList, generateGlyphImageStream(lcdGlyphList)); - } + con.XRenderAddGlyphs(glyphSet, glyphList, cacheEntries, + generateGlyphImageStream(cacheEntries)); } else { - ArrayList tmpList = new ArrayList(1); + ArrayList tmpList = new ArrayList<>(1); tmpList.add(null); - - for (XRGlyphCacheEntry entry : uncachedGlyphs) { + for (XRGlyphCacheEntry entry : cacheEntries) { tmpList.set(0, entry); - - if (entry.getGlyphSet() == grayGlyphSet) { - con.XRenderAddGlyphs(grayGlyphSet, gl, tmpList, generateGlyphImageStream(tmpList)); - } else { - con.XRenderAddGlyphs(lcdGlyphSet, gl, tmpList, generateGlyphImageStream(tmpList)); - } + con.XRenderAddGlyphs(glyphSet, glyphList, tmpList, + generateGlyphImageStream(tmpList)); } } } /** - * Separates lcd and grayscale glyphs queued for upload, and sets the + * Separates bgra, lcd and grayscale glyphs queued for upload, and sets the * appropriate glyphset for the cache entries. */ - protected List[] seperateGlyphTypes(List glyphList, boolean containsLCDGlyphs) { - ArrayList lcdGlyphs = null; - ArrayList grayGlyphs = null; - + protected EnumMap> + separateGlyphTypes(List glyphList) { + EnumMap> glyphLists = + new EnumMap<>(XRGlyphCacheEntry.Type.class); for (XRGlyphCacheEntry cacheEntry : glyphList) { - if (cacheEntry.isGrayscale(containsLCDGlyphs)) { - if (grayGlyphs == null) { - grayGlyphs = new ArrayList<>(glyphList.size()); - } - cacheEntry.setGlyphSet(grayGlyphSet); - grayGlyphs.add(cacheEntry); - } else { - if (lcdGlyphs == null) { - lcdGlyphs = new ArrayList<>(glyphList.size()); - } - cacheEntry.setGlyphSet(lcdGlyphSet); - lcdGlyphs.add(cacheEntry); - } + XRGlyphCacheEntry.Type cacheEntryType = cacheEntry.getType(); + cacheEntry.setGlyphSet(glyphSetsByType.get(cacheEntryType)); + glyphLists.computeIfAbsent(cacheEntryType, ignore -> + new ArrayList<>(glyphList.size())).add(cacheEntry); } - // Arrays and generics don't play well together - @SuppressWarnings({"unchecked", "rawtypes"}) - List[] tmp = - (List[]) (new List[] { grayGlyphs, lcdGlyphs }); - return tmp; + return glyphLists; } /** @@ -226,20 +233,7 @@ protected byte[] generateGlyphImageStream(List glyphList) { return stream.toByteArray(); } - protected boolean containsLCDGlyphs(List entries) { - boolean containsLCDGlyphs = false; - - for (XRGlyphCacheEntry entry : entries) { - containsLCDGlyphs = !(entry.getSourceRowBytes() == entry.getWidth()); - - if (containsLCDGlyphs) { - return true; - } - } - return false; - } - - protected void clearCache(XRGlyphCacheEntry[] glyps) { + protected void clearCache(XRGlyphCacheEntry[] glyphs) { /* * Glyph uploading is so slow anyway, we can afford some inefficiency * here, as the cache should usually be quite small. TODO: Implement @@ -252,8 +246,10 @@ public int compare(XRGlyphCacheEntry e1, XRGlyphCacheEntry e2) { } }); - for (XRGlyphCacheEntry glyph : glyps) { - glyph.setPinned(); + for (XRGlyphCacheEntry glyph : glyphs) { + if (glyph != null) { + glyph.setPinned(); + } } GrowableIntArray deleteGlyphList = new GrowableIntArray(1, 10); @@ -268,8 +264,10 @@ public int compare(XRGlyphCacheEntry e1, XRGlyphCacheEntry e2) { } } - for (XRGlyphCacheEntry glyph : glyps) { - glyph.setUnpinned(); + for (XRGlyphCacheEntry glyph : glyphs) { + if (glyph != null) { + glyph.setUnpinned(); + } } freeGlyphs(deleteGlyphList); @@ -278,6 +276,8 @@ public int compare(XRGlyphCacheEntry e1, XRGlyphCacheEntry e2) { private void freeGlyphs(GrowableIntArray glyphIdList) { GrowableIntArray removedLCDGlyphs = new GrowableIntArray(1, 10); GrowableIntArray removedGrayscaleGlyphs = new GrowableIntArray(1, 10); + long[] removedBGRAGlyphPtrs = null; + int removedBGRAGlyphPtrsCount = 0; for (int i=0; i < glyphIdList.getSize(); i++) { int glyphId = glyphIdList.getInt(i); @@ -290,8 +290,19 @@ private void freeGlyphs(GrowableIntArray glyphIdList) { if (entry.getGlyphSet() == grayGlyphSet) { removedGrayscaleGlyphs.addInt(glyphId); - } else { + } else if (entry.getGlyphSet() == lcdGlyphSet) { removedLCDGlyphs.addInt(glyphId); + } else if (entry.getGlyphSet() == BGRA_GLYPH_SET) { + if (removedBGRAGlyphPtrs == null) { + removedBGRAGlyphPtrs = new long[10]; + } else if (removedBGRAGlyphPtrsCount >= removedBGRAGlyphPtrs.length) { + long[] n = new long[removedBGRAGlyphPtrs.length * 2]; + System.arraycopy(removedBGRAGlyphPtrs, 0, n, 0, + removedBGRAGlyphPtrs.length); + removedBGRAGlyphPtrs = n; + } + removedBGRAGlyphPtrs[removedBGRAGlyphPtrsCount++] = + entry.getBgraGlyphInfoPtr(); } entry.setGlyphID(0); @@ -304,5 +315,10 @@ private void freeGlyphs(GrowableIntArray glyphIdList) { if (removedLCDGlyphs.getSize() > 0) { con.XRenderFreeGlyphs(lcdGlyphSet, removedLCDGlyphs.getSizedArray()); } + + if (removedBGRAGlyphPtrsCount > 0) { + con.freeBGRAGlyphImages(removedBGRAGlyphPtrs, + removedBGRAGlyphPtrsCount); + } } } diff --git a/src/java.desktop/unix/classes/sun/font/XRGlyphCacheEntry.java b/src/java.desktop/unix/classes/sun/font/XRGlyphCacheEntry.java index fff6919c464e9..0037b414a7b31 100644 --- a/src/java.desktop/unix/classes/sun/font/XRGlyphCacheEntry.java +++ b/src/java.desktop/unix/classes/sun/font/XRGlyphCacheEntry.java @@ -34,7 +34,7 @@ */ public class XRGlyphCacheEntry { - long glyphInfoPtr; + long glyphInfoPtr, bgraGlyphInfoPtr; int lastUsed; boolean pinned; @@ -52,6 +52,14 @@ public XRGlyphCacheEntry(long glyphInfoPtr, GlyphList gl) { yOff = Math.round(getYAdvance()); } + public long getBgraGlyphInfoPtr() { + return bgraGlyphInfoPtr; + } + + public void setBgraGlyphInfoPtr(long bgraGlyphInfoPtr) { + this.bgraGlyphInfoPtr = bgraGlyphInfoPtr; + } + public int getXOff() { return xOff; } @@ -132,9 +140,9 @@ public void writePixelData(ByteArrayOutputStream os, boolean uploadAsLCD) { int width = getWidth(); int height = getHeight(); int rowBytes = getSourceRowBytes(); - int paddedWidth = getPaddedWidth(uploadAsLCD); + int paddedWidth = getPaddedWidth(); - if (!uploadAsLCD) { + if (getType() == Type.GRAYSCALE) { for (int line = 0; line < height; line++) { for(int x = 0; x < paddedWidth; x++) { if(x < width) { @@ -176,22 +184,30 @@ public long getGlyphInfoPtr() { return glyphInfoPtr; } - public boolean isGrayscale(boolean listContainsLCDGlyphs) { - return getSourceRowBytes() == getWidth() && !(getWidth() == 0 && getHeight() == 0 && listContainsLCDGlyphs); + public Type getType() { + byte format = StrikeCache.unsafe.getByte(glyphInfoPtr + StrikeCache.formatOffset); + if (format == StrikeCache.PIXEL_FORMAT_GREYSCALE) { + return Type.GRAYSCALE; + } else if (format == StrikeCache.PIXEL_FORMAT_LCD) { + return Type.LCD; + } else if (format == StrikeCache.PIXEL_FORMAT_BGRA) { + return Type.BGRA; + } else { + throw new IllegalStateException("Unknown glyph format: " + format); + } } - public int getPaddedWidth(boolean listContainsLCDGlyphs) { - int width = getWidth(); - return isGrayscale(listContainsLCDGlyphs) ? (int) Math.ceil(width / 4.0) * 4 : width; + public int getPaddedWidth() { + return getType() == Type.GRAYSCALE ? + (int) Math.ceil(getWidth() / 4.0) * 4 : getWidth(); } - public int getDestinationRowBytes(boolean listContainsLCDGlyphs) { - boolean grayscale = isGrayscale(listContainsLCDGlyphs); - return grayscale ? getPaddedWidth(grayscale) : getWidth() * 4; + public int getDestinationRowBytes() { + return getType() == Type.GRAYSCALE ? getPaddedWidth() : getWidth() * 4; } - public int getGlyphDataLenth(boolean listContainsLCDGlyphs) { - return getDestinationRowBytes(listContainsLCDGlyphs) * getHeight(); + public int getGlyphDataLenth() { + return getDestinationRowBytes() * getHeight(); } public void setPinned() { @@ -217,4 +233,13 @@ public int getPixelCnt() { public boolean isPinned() { return pinned; } + + + public enum Type { + GRAYSCALE, + LCD, + BGRA + } + + } diff --git a/src/java.desktop/unix/classes/sun/font/XRTextRenderer.java b/src/java.desktop/unix/classes/sun/font/XRTextRenderer.java index 6f23bf10899cc..7b09252908f3f 100644 --- a/src/java.desktop/unix/classes/sun/font/XRTextRenderer.java +++ b/src/java.desktop/unix/classes/sun/font/XRTextRenderer.java @@ -78,13 +78,20 @@ protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl) { advY += 0.5f; } - XRGlyphCacheEntry[] cachedGlyphs = glyphCache.cacheGlyphs(gl); + XRGlyphCacheEntry[] cachedGlyphs = + glyphCache.cacheGlyphs(gl, x11sd.getXid()); boolean containsLCDGlyphs = false; - int activeGlyphSet = cachedGlyphs[0].getGlyphSet(); + /* Do not initialize it to cachedGlyphs[0].getGlyphSet(), + * as it may cause NPE */ + int activeGlyphSet = 0; int eltIndex = -1; gl.startGlyphIteration(); float[] positions = gl.getPositions(); + /* Accumulated advances are used to adjust glyph positions + * when mixing BGRA and standard glyphs as they have + * completely different methods of rendering. */ + float accumulatedXEltAdvanceX = 0, accumulatedXEltAdvanceY = 0; for (int i = 0; i < gl.getNumGlyphs(); i++) { gl.setGlyphIndex(i); XRGlyphCacheEntry cacheEntry = cachedGlyphs[i]; @@ -92,9 +99,19 @@ protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl) { continue; } - eltList.getGlyphs().addInt(cacheEntry.getGlyphID()); int glyphSet = cacheEntry.getGlyphSet(); + if (glyphSet == XRGlyphCache.BGRA_GLYPH_SET) { + /* BGRA glyphs store pointers to BGRAGlyphInfo + * struct instead of glyph index */ + eltList.getGlyphs().addInt( + (int) (cacheEntry.getBgraGlyphInfoPtr() >> 32)); + eltList.getGlyphs().addInt( + (int) cacheEntry.getBgraGlyphInfoPtr()); + } else { + eltList.getGlyphs().addInt(cacheEntry.getGlyphID()); + } + containsLCDGlyphs |= (glyphSet == glyphCache.lcdGlyphSet); int posX = 0, posY = 0; @@ -103,7 +120,11 @@ protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl) { || cacheEntry.getYAdvance() != ((float) cacheEntry.getYOff()) || glyphSet != activeGlyphSet || eltIndex < 0 - || eltList.getCharCnt(eltIndex) == MAX_ELT_GLYPH_COUNT) { + /* We don't care about number of glyphs when + * rendering BGRA glyphs because they are not rendered + * using XRenderCompositeText. */ + || (glyphSet != XRGlyphCache.BGRA_GLYPH_SET && + eltList.getCharCnt(eltIndex) == MAX_ELT_GLYPH_COUNT)) { eltIndex = eltList.getNextIndex(); eltList.setCharCnt(eltIndex, 1); @@ -136,17 +157,31 @@ protected void drawGlyphList(SunGraphics2D sg2d, GlyphList gl) { advY += (cacheEntry.getYAdvance() - cacheEntry.getYOff()); } - // Offset of the current glyph is the difference - // to the last glyph and this one - eltList.setXOff(eltIndex, (posX - oldPosX)); - eltList.setYOff(eltIndex, (posY - oldPosY)); - - oldPosX = posX; - oldPosY = posY; + if (glyphSet == XRGlyphCache.BGRA_GLYPH_SET) { + // BGRA glyphs use absolute positions + eltList.setXOff(eltIndex, + (int) (accumulatedXEltAdvanceX + posX)); + eltList.setYOff(eltIndex, + (int) (accumulatedXEltAdvanceY + posY)); + } else { + // Offset of the current glyph is the difference + // to the last glyph and this one + eltList.setXOff(eltIndex, (posX - oldPosX)); + eltList.setYOff(eltIndex, (posY - oldPosY)); + oldPosX = posX; + oldPosY = posY; + } } else { eltList.setCharCnt(eltIndex, eltList.getCharCnt(eltIndex) + 1); } + if (glyphSet == XRGlyphCache.BGRA_GLYPH_SET) { + advX += cacheEntry.getXAdvance(); + advY += cacheEntry.getYAdvance(); + } else { + accumulatedXEltAdvanceX += cacheEntry.getXAdvance(); + accumulatedXEltAdvanceY += cacheEntry.getYAdvance(); + } } int maskFormat = containsLCDGlyphs ? XRUtils.PictStandardARGB32 : XRUtils.PictStandardA8; diff --git a/src/java.desktop/unix/classes/sun/java2d/xr/XRBackend.java b/src/java.desktop/unix/classes/sun/java2d/xr/XRBackend.java index b44bca61ef09c..0c00a4c4c223e 100644 --- a/src/java.desktop/unix/classes/sun/java2d/xr/XRBackend.java +++ b/src/java.desktop/unix/classes/sun/java2d/xr/XRBackend.java @@ -94,6 +94,11 @@ public void XRenderAddGlyphs(int glyphSet, GlyphList gl, public void XRenderFreeGlyphs(int glyphSet, int[] gids); + public void addBGRAGlyphImages(int drawable, + List cacheEntries); + + public void freeBGRAGlyphImages(long[] glyphInfoPointers, int glyphCount); + public void XRenderCompositeText(byte op, int src, int dst, int maskFormatID, int xSrc, int ySrc, int xDst, int yDst, diff --git a/src/java.desktop/unix/classes/sun/java2d/xr/XRBackendNative.java b/src/java.desktop/unix/classes/sun/java2d/xr/XRBackendNative.java index e5f337d34153f..122c503117860 100644 --- a/src/java.desktop/unix/classes/sun/java2d/xr/XRBackendNative.java +++ b/src/java.desktop/unix/classes/sun/java2d/xr/XRBackendNative.java @@ -250,6 +250,26 @@ private static native void XRAddGlyphsNative(int glyphSet, private static native void XRFreeGlyphsNative(int glyphSet, int[] gids, int idCnt); + public void addBGRAGlyphImages(int drawable, + List cacheEntries) { + long[] glyphInfoPtrs = getGlyphInfoPtrs(cacheEntries); + addBGRAGlyphImagesNative(drawable, glyphInfoPtrs, + glyphInfoPtrs.length, FMTPTR_ARGB32); + /* addBGRAGlyphImagesNative replaced values in + * glyphInfoPtrs with pointers to BGRAGlyphInfo structs, save them */ + int i = 0; + for (XRGlyphCacheEntry cacheEntry : cacheEntries) { + cacheEntry.setBgraGlyphInfoPtr(glyphInfoPtrs[i++]); + } + } + + private native void addBGRAGlyphImagesNative(int drawable, + long[] glyphInfoPtrs, + int glyphCnt, long format32); + + public native void freeBGRAGlyphImages(long[] glyphInfoPointers, + int glyphCount); + private static native void XRenderCompositeTextNative(int op, int src, int dst, int srcX, int srcY, long maskFormat, diff --git a/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c b/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c index 2ab6f287b690f..96058c7fe8fc1 100644 --- a/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c +++ b/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c @@ -289,6 +289,7 @@ JNIEXPORT jlong JNICALL AWTFontGenerateImage(AWTFont pFont, AWTChar2b* xChar) { glyphInfo->cellInfo = NULL; glyphInfo->width = width; glyphInfo->height = height; + glyphInfo->format = sun_font_StrikeCache_PIXEL_FORMAT_GREYSCALE; glyphInfo->topLeftX = xcs.lbearing; glyphInfo->topLeftY = -xcs.ascent; glyphInfo->advanceX = xcs.width; diff --git a/src/java.desktop/unix/native/common/java2d/x11/X11TextRenderer_md.c b/src/java.desktop/unix/native/common/java2d/x11/X11TextRenderer_md.c index c7aff6f34c84f..e0e9eb6ca8c46 100644 --- a/src/java.desktop/unix/native/common/java2d/x11/X11TextRenderer_md.c +++ b/src/java.desktop/unix/native/common/java2d/x11/X11TextRenderer_md.c @@ -103,7 +103,7 @@ static jboolean checkPixmap(JNIEnv *env, AwtGraphicsConfigDataPtr cData) static void FillBitmap(XImage *theImage, ImageRef *glyphs, jint totalGlyphs, jint clipLeft, jint clipTop, - jint clipRight, jint clipBottom) + jint clipRight, jint clipBottom, jboolean *allGlyphsRendered) { int glyphCounter; int scan = theImage->bytes_per_line; @@ -124,12 +124,17 @@ static void FillBitmap(XImage *theImage, if (!pixels) { continue; } - rowBytes = glyphs[glyphCounter].width; + rowBytes = glyphs[glyphCounter].rowBytes; left = glyphs[glyphCounter].x; top = glyphs[glyphCounter].y; width = glyphs[glyphCounter].width; height = glyphs[glyphCounter].height; + if ((int) rowBytes == width * 4) { // Skip colored glyphs + *allGlyphsRendered = JNI_FALSE; + continue; + } + /* if any clipping required, modify parameters now */ right = left + width; bottom = top + height; @@ -206,7 +211,7 @@ static void FillBitmap(XImage *theImage, JNIEXPORT void JNICALL AWTDrawGlyphList(JNIEnv *env, jobject xtr, jlong dstData, jlong gc, - SurfaceDataBounds *bounds, ImageRef *glyphs, jint totalGlyphs) + SurfaceDataBounds *bounds, ImageRef *glyphs, jint totalGlyphs, jboolean *allGlyphsRendered) { #ifndef HEADLESS GC xgc, theGC; @@ -259,7 +264,7 @@ AWTDrawGlyphList(JNIEnv *env, jobject xtr, FillBitmap(theImage, glyphs, totalGlyphs, - cx1, cy1, cx2, cy2); + cx1, cy1, cx2, cy2, allGlyphsRendered); // NOTE: Since we are tiling around by BM_W, BM_H offsets // and thePixmap is BM_W x BM_H, we do not have to move diff --git a/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c b/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c index 0ea86e3e9b3ce..e67c89ae8cc1f 100644 --- a/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c +++ b/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c @@ -59,6 +59,14 @@ typedef struct _XRadialGradient { } XRadialGradient; #endif +/* BGRA glyph that is rendered using XRenderComposite instead of + * XRenderCompositeText32. Used for colored glyphs */ +typedef struct _BGRAGlyphInfo { + GlyphInfo* glyphInfo; + Pixmap pixmap; + Picture picture; +} BGRAGlyphInfo; + #include #define BUILD_TRANSFORM_MATRIX(TRANSFORM, M00, M01, M02, M10, M11, M12) \ @@ -879,6 +887,74 @@ Java_sun_java2d_xr_XRBackendNative_XRFreeGlyphsNative } } +JNIEXPORT void JNICALL +Java_sun_java2d_xr_XRBackendNative_addBGRAGlyphImagesNative + (JNIEnv* env, jclass clazz, jint drawable, + jlongArray javaGlyphInfoPointersArray, jint glyphCnt, jlong format32) { + jlong* glyphInfoPointers; + if ((glyphInfoPointers = (jlong *) + (*env)->GetPrimitiveArrayCritical(env, javaGlyphInfoPointersArray, NULL)) == NULL) { + return; + } + + XRenderPictFormat* format = (XRenderPictFormat*) jlong_to_ptr(format32); + XRenderPictureAttributes pictureAttributes; + + int i; + for (i = 0; i < glyphCnt; i++) { + GlyphInfo* glyphInfo = (GlyphInfo*) jlong_to_ptr(glyphInfoPointers[i]); + + Pixmap pixmap = XCreatePixmap(awt_display, (Drawable) drawable, + glyphInfo->width, glyphInfo->height, 32); + GC gc = XCreateGC(awt_display, (Drawable) pixmap, 0L, NULL); + XImage* image = XCreateImage(awt_display, NULL, 32, ZPixmap, 0, + (char*) glyphInfo->image, + glyphInfo->width, glyphInfo->height, + 32, glyphInfo->rowBytes); + XPutImage(awt_display, pixmap, gc, image, 0, 0, 0, 0, + glyphInfo->width, glyphInfo->height); + image->data = NULL; + XDestroyImage(image); + XFreeGC(awt_display, gc); + Picture picture = XRenderCreatePicture(awt_display, pixmap, format, + 0, &pictureAttributes); + + BGRAGlyphInfo* bgraGlyphInfo = + (BGRAGlyphInfo*) malloc(sizeof(BGRAGlyphInfo)); + bgraGlyphInfo->glyphInfo = glyphInfo; + bgraGlyphInfo->pixmap = pixmap; + bgraGlyphInfo->picture = picture; + + glyphInfoPointers[i] = ptr_to_jlong(bgraGlyphInfo); + } + + (*env)->ReleasePrimitiveArrayCritical(env, javaGlyphInfoPointersArray, + glyphInfoPointers, JNI_ABORT); +} + +JNIEXPORT void JNICALL +Java_sun_java2d_xr_XRBackendNative_freeBGRAGlyphImages + (JNIEnv* env, jclass clazz, + jlongArray javaGlyphInfoPointersArray, jint glyphCnt) { + jlong* glyphInfoPointers; + if ((glyphInfoPointers = (jlong *) + (*env)->GetPrimitiveArrayCritical(env, javaGlyphInfoPointersArray, NULL)) == NULL) { + return; + } + + int i; + for (i = 0; i < glyphCnt; i++) { + BGRAGlyphInfo* bgraGlyphInfo = + (BGRAGlyphInfo*) jlong_to_ptr(glyphInfoPointers[i]); + XRenderFreePicture(awt_display, bgraGlyphInfo->picture); + XFreePixmap(awt_display, bgraGlyphInfo->pixmap); + free(bgraGlyphInfo); + } + + (*env)->ReleasePrimitiveArrayCritical(env, javaGlyphInfoPointersArray, + glyphInfoPointers, JNI_ABORT); +} + JNIEXPORT jint JNICALL Java_sun_java2d_xr_XRBackendNative_XRenderCreateGlyphSetNative (JNIEnv *env, jclass cls, jlong format) { @@ -956,19 +1032,48 @@ Java_sun_java2d_xr_XRBackendNative_XRenderCompositeTextNative xids[i] = ids[i]; } - for (i=0; i < eltCnt; i++) { - xelts[i].nchars = elts[i*4 + 0]; - xelts[i].xOff = elts[i*4 + 1]; - xelts[i].yOff = elts[i*4 + 2]; - xelts[i].glyphset = (GlyphSet) elts[i*4 + 3]; - xelts[i].chars = &xids[charCnt]; - - charCnt += xelts[i].nchars; + int totalXElts = 0; + for (i = 0; i < eltCnt; i++) { + int nchars = elts[i*4]; + int xOff = elts[i*4 + 1]; + int yOff = elts[i*4 + 2]; + int glyphset = (GlyphSet) elts[i*4 + 3]; + if (glyphset == -1) { // BGRA glyph, render as image + float x = (float) xOff; + float y = (float) yOff; + int ch; + for (ch = 0; ch < nchars; ch++) { + BGRAGlyphInfo* bgraGlyphInfo = (BGRAGlyphInfo*) + (((jlong) xids[charCnt + ch * 2] << 32) | + (((jlong) xids[charCnt + ch * 2 + 1]) & 0xFFFFFFFF)); + GlyphInfo* glyph = bgraGlyphInfo->glyphInfo; + XRenderComposite(awt_display, PictOpOver, + bgraGlyphInfo->picture, + (Picture) 0, (Picture) dst, + 0, 0, 0, 0, + (int) (x + glyph->topLeftX), + (int) (y + glyph->topLeftY), + glyph->width, glyph->height); + x += glyph->advanceX; + y += glyph->advanceY; + } + charCnt += nchars * 2; + } else { // Standard XRender glyph + xelts[totalXElts].nchars = nchars; + xelts[totalXElts].xOff = xOff; + xelts[totalXElts].yOff = yOff; + xelts[totalXElts].glyphset = glyphset; + xelts[totalXElts].chars = &xids[charCnt]; + charCnt += nchars; + totalXElts++; + } } - XRenderCompositeText32(awt_display, op, (Picture) src, (Picture) dst, - (XRenderPictFormat *) jlong_to_ptr(maskFmt), - sx, sy, 0, 0, xelts, eltCnt); + if (totalXElts > 0) { + XRenderCompositeText32(awt_display, op, (Picture) src, (Picture) dst, + (XRenderPictFormat *) jlong_to_ptr(maskFmt), + sx, sy, 0, 0, xelts, totalXElts); + } (*env)->ReleasePrimitiveArrayCritical(env, glyphIDArray, ids, JNI_ABORT); (*env)->ReleasePrimitiveArrayCritical(env, eltArray, elts, JNI_ABORT); diff --git a/src/java.desktop/unix/native/libfontmanager/X11TextRenderer.c b/src/java.desktop/unix/native/libfontmanager/X11TextRenderer.c index e1f64c566b38d..caf52a51be7b9 100644 --- a/src/java.desktop/unix/native/libfontmanager/X11TextRenderer.c +++ b/src/java.desktop/unix/native/libfontmanager/X11TextRenderer.c @@ -45,14 +45,14 @@ JNIEXPORT void JNICALL AWTDrawGlyphList (JNIEnv *env, jobject xtr, jlong dstData, jlong gc, - SurfaceDataBounds *bounds, ImageRef *glyphs, jint totalGlyphs); + SurfaceDataBounds *bounds, ImageRef *glyphs, jint totalGlyphs, jboolean *allGlyphsRendered); /* * Class: sun_font_X11TextRenderer * Method: doDrawGlyphList - * Signature: (Lsun/java2d/SurfaceData;Ljava/awt/Rectangle;ILsun/font/GlyphList;J)V + * Signature: (Lsun/java2d/SurfaceData;Ljava/awt/Rectangle;ILsun/font/GlyphList;J)Z */ -JNIEXPORT void JNICALL Java_sun_font_X11TextRenderer_doDrawGlyphList +JNIEXPORT jboolean JNICALL Java_sun_font_X11TextRenderer_doDrawGlyphList (JNIEnv *env, jobject xtr, jlong dstData, jlong xgc, jobject clip, jobject glyphlist) @@ -64,13 +64,15 @@ JNIEXPORT void JNICALL Java_sun_font_X11TextRenderer_doDrawGlyphList glyphCount = (*env)->GetIntField(env, glyphlist, sunFontIDs.glyphListLen); if ((gbv = setupBlitVector(env, glyphlist, 0, glyphCount)) == NULL) { - return; + return JNI_TRUE; } if (!RefineBounds(gbv, &bounds)) { free(gbv); - return; + return JNI_TRUE; } + jboolean allGlyphsRendered = JNI_TRUE; AWTDrawGlyphList(env, xtr, dstData, xgc, - &bounds, gbv->glyphs, gbv->numGlyphs); + &bounds, gbv->glyphs, gbv->numGlyphs, &allGlyphsRendered); free(gbv); + return allGlyphsRendered; } diff --git a/src/java.desktop/windows/classes/sun/awt/Win32FontManager.java b/src/java.desktop/windows/classes/sun/awt/Win32FontManager.java index 1d5f76f996047..6e5d7d22bfdeb 100644 --- a/src/java.desktop/windows/classes/sun/awt/Win32FontManager.java +++ b/src/java.desktop/windows/classes/sun/awt/Win32FontManager.java @@ -26,45 +26,40 @@ package sun.awt; -import java.awt.FontFormatException; -import java.awt.GraphicsEnvironment; +import java.awt.*; import java.io.File; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Locale; -import java.util.NoSuchElementException; -import java.util.StringTokenizer; +import java.util.*; +import java.util.List; import sun.awt.windows.WFontConfiguration; -import sun.font.FontManager; -import sun.font.SunFontManager; -import sun.font.TrueTypeFont; +import sun.font.*; /** - * The X11 implementation of {@link FontManager}. + * The Win32 implementation of {@link FontManager}. */ public final class Win32FontManager extends SunFontManager { @SuppressWarnings("removal") - private static final TrueTypeFont eudcFont = - AccessController.doPrivileged(new PrivilegedAction() { - public TrueTypeFont run() { - String eudcFile = getEUDCFontFile(); - if (eudcFile != null) { - try { - /* Must use Java rasteriser since GDI doesn't - * enumerate (allow direct use) of EUDC fonts. - */ - return new TrueTypeFont(eudcFile, null, 0, - true, false); - } catch (FontFormatException e) { - } + private static final List additionalFallbackFonts = + AccessController.doPrivileged((PrivilegedAction>) () -> { + List list = new ArrayList<>(); + list.add(new EmojiFont()); + // https://learn.microsoft.com/en-us/windows/win32/intl/end-user-defined-characters + String eudcFile = getEUDCFontFile(); + if (eudcFile != null) { + try { + /* Must use Java rasteriser since GDI doesn't + * enumerate (allow direct use) of EUDC fonts. + */ + list.add(new TrueTypeFont(eudcFile, null, 0, + true, false)); + } catch (FontFormatException e) { } - return null; } - }); + return Collections.unmodifiableList(list); + }); /* Used on Windows to obtain from the windows registry the name * of a file containing the system EUFC font. If running in one of @@ -74,8 +69,8 @@ public TrueTypeFont run() { */ private static native String getEUDCFontFile(); - public TrueTypeFont getEUDCFont() { - return eudcFont; + public List getAdditionalFallbackFonts() { + return additionalFallbackFonts; } @SuppressWarnings("removal") diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WPathGraphics.java b/src/java.desktop/windows/classes/sun/awt/windows/WPathGraphics.java index 84ae7e8f41839..fd4872d0a6dda 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WPathGraphics.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WPathGraphics.java @@ -566,7 +566,7 @@ public void drawString(String str, float x, float y, endChar++; } String substr = new String(chars, startChar,endChar-startChar); - PhysicalFont slotFont = compFont.getSlotFont(slot); + Font2D slotFont = compFont.getSlotFont(slot); textOut(substr, font, slotFont, frc, scaledFontSizeY, iangle, awScale, advanceScaleX, advanceScaleY, @@ -800,7 +800,7 @@ protected boolean printGlyphVector(GlyphVector gv, float x, float y) { * if only one slot is unavailable is not worth worrying * about. */ - PhysicalFont slotFont = compFont.getSlotFont(slot); + Font2D slotFont = compFont.getSlotFont(slot); if (!(slotFont instanceof TrueTypeFont)) { return false; } @@ -831,7 +831,7 @@ protected boolean printGlyphVector(GlyphVector gv, float x, float y) { } private void textOut(String str, - Font font, PhysicalFont font2D, + Font font, Font2D font2D, FontRenderContext frc, float deviceSize, int rotation, float awScale, double scaleFactorX, double scaleFactorY, diff --git a/src/java.desktop/windows/classes/sun/font/EmojiFont.java b/src/java.desktop/windows/classes/sun/font/EmojiFont.java new file mode 100644 index 0000000000000..d7e31387bb9ea --- /dev/null +++ b/src/java.desktop/windows/classes/sun/font/EmojiFont.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.font; + +import java.awt.*; +import java.awt.geom.GeneralPath; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +public class EmojiFont extends Font2D { + + private Font2D symbol, emoji; + private volatile boolean init; + + public EmojiFont() { + handle = new Font2DHandle(this); + fullName = "Emoji.plain"; + familyName = "Emoji"; + fontRank = JRE_RANK; + style = Font.PLAIN; + } + + private void init() { + if (!init) { + synchronized (this) { + if (!init) { + SunFontManager fm = SunFontManager.getInstance(); + symbol = fm.findFont2D("Segoe UI Symbol", Font.PLAIN, FontManager.NO_FALLBACK); + emoji = fm.findFont2D("Segoe UI Emoji", Font.PLAIN, FontManager.NO_FALLBACK); + init = true; + } + } + } + } + + @Override + public void getStyleMetrics(float pointSize, float[] metrics, int offset) { + init(); + if (emoji != null) { + emoji.getStyleMetrics(pointSize, metrics, offset); + } else if (symbol != null) { + symbol.getStyleMetrics(pointSize, metrics, offset); + } else { + super.getStyleMetrics(pointSize, metrics, offset); + } + } + + @Override + FontStrike createStrike(FontStrikeDesc desc) { + init(); + return new Strike(desc); + } + + @Override + protected int getValidatedGlyphCode(int glyphCode) { + init(); + Font2D slot = (glyphCode & 1) == 1 ? emoji : symbol; + if (emoji != null) { + int result = slot.getValidatedGlyphCode(glyphCode >>> 1); + if (result != slot.getMissingGlyphCode()) { + return glyphCode; + } + } + return getMissingGlyphCode(); + } + + @Override + CharToGlyphMapper getMapper() { + init(); + if (mapper == null) { + mapper = new Mapper(); + } + return mapper; + } + + @Override + public boolean hasSupplementaryChars() { + init(); + return (emoji != null && emoji.hasSupplementaryChars()) || + (symbol != null && symbol.hasSupplementaryChars()); + } + + @Override + public int getNumGlyphs() { + init(); + return Math.max(emoji != null ? emoji.getNumGlyphs() : 0, + symbol != null ? symbol.getNumGlyphs() : 0); + } + + @Override + public boolean canDisplay(int cp) { + return (Character.isEmoji(cp) || Character.isEmojiComponent(cp)) && getMapper().canDisplay(cp); + } + + @Override + public SlotInfo getSlotInfoForGlyph(int glyphCode) { + SlotInfo info = ((glyphCode & 1) == 1 ? emoji : symbol) + .getSlotInfoForGlyph(glyphCode >>> 1); + info.slotShift++; + return info; + } + + private class Strike extends FontStrike { + + private final FontStrike symbol, emoji; + + private Strike(FontStrikeDesc desc) { + this.desc = desc; + this.disposer = new FontStrikeDisposer(EmojiFont.this, desc); + symbol = EmojiFont.this.symbol == null ? null : + EmojiFont.this.symbol.getStrike(desc); + emoji = EmojiFont.this.emoji == null ? null : + EmojiFont.this.emoji.getStrike(desc); + } + + private FontStrike getStrikeForGlyph(int glyphCode) { + return (glyphCode & 1) == 1 ? emoji : symbol; + } + + @Override + public int getNumGlyphs() { + return EmojiFont.this.getNumGlyphs(); + } + + @Override + StrikeMetrics getFontMetrics() { + if (strikeMetrics == null) { + StrikeMetrics compMetrics = new StrikeMetrics(); + if (emoji != null) { + compMetrics.merge(emoji.getFontMetrics()); + } + if (symbol != null) { + compMetrics.merge(symbol.getFontMetrics()); + } + strikeMetrics = compMetrics; + } + return strikeMetrics; + } + + @Override + void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) { + for (int i = 0; i < len; i++) { + images[i] = getGlyphImagePtr(glyphCodes[i]); + } + } + + @Override + long getGlyphImagePtr(int glyphCode) { + FontStrike strike = getStrikeForGlyph(glyphCode); + return strike.getGlyphImagePtr(glyphCode >>> 1); + } + + @Override + void getGlyphImageBounds(int glyphCode, Point2D.Float pt, Rectangle result) { + FontStrike strike = getStrikeForGlyph(glyphCode); + strike.getGlyphImageBounds(glyphCode >>> 1, pt, result); + } + + @Override + Point2D.Float getGlyphMetrics(int glyphCode) { + FontStrike strike = getStrikeForGlyph(glyphCode); + return strike.getGlyphMetrics(glyphCode >>> 1); + } + + @Override + Point2D.Float getCharMetrics(char ch) { + return getGlyphMetrics(getMapper().charToGlyph(ch)); + } + + @Override + float getGlyphAdvance(int glyphCode) { + FontStrike strike = getStrikeForGlyph(glyphCode); + return strike.getGlyphAdvance(glyphCode >>> 1); + } + + @Override + float getCodePointAdvance(int cp) { + return getGlyphAdvance(getMapper().charToGlyph(cp)); + } + + @Override + Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) { + FontStrike strike = getStrikeForGlyph(glyphCode); + return strike.getGlyphOutlineBounds(glyphCode >>> 1); + } + + @Override + GeneralPath getGlyphOutline(int glyphCode, float x, float y) { + FontStrike strike = getStrikeForGlyph(glyphCode); + GeneralPath path = strike.getGlyphOutline(glyphCode >>> 1, x, y); + return path != null ? path : new GeneralPath(); + } + + @Override + GlyphRenderData getGlyphRenderData(int glyphCode, float x, float y) { + FontStrike strike = getStrikeForGlyph(glyphCode); + return strike.getGlyphRenderData(glyphCode >>> 1, x, y); + } + } + + class Mapper extends CharToGlyphMapper { + + private final CharToGlyphMapper symbol, emoji; + + private Mapper() { + symbol = EmojiFont.this.symbol == null ? null : + EmojiFont.this.symbol.getMapper(); + emoji = EmojiFont.this.emoji == null ? null : + EmojiFont.this.emoji.getMapper(); + if (EmojiFont.this.emoji != null) { + missingGlyph = compositeGlyphCode(true, EmojiFont.this.emoji.getMissingGlyphCode()); + } else if (EmojiFont.this.symbol != null) { + missingGlyph = compositeGlyphCode(false, EmojiFont.this.symbol.getMissingGlyphCode()); + } + } + + private int compositeGlyphCode(boolean slot, int glyphCode) { + return slot ? (glyphCode << 1) | 1 : glyphCode << 1; + } + + private boolean getSlot(int unicode, int variationSelector) { + return switch (variationSelector) { + case 0xFE0F -> true; + case 0xFE0E -> false; + default -> Character.isEmojiPresentation(unicode); + }; + } + + @Override + public int getNumGlyphs() { + return EmojiFont.this.getNumGlyphs(); + } + + @Override + public int charToVariationGlyph(int unicode, int variationSelector) { + boolean slot = getSlot(unicode, variationSelector); + CharToGlyphMapper mapper = slot ? emoji : symbol; + if (mapper != null) { + int glyph = mapper.charToGlyph(unicode); + if (glyph != mapper.getMissingGlyphCode()) { + return compositeGlyphCode(slot, glyph); + } + } + mapper = slot ? symbol : emoji; + if (mapper != null) { + int glyph = mapper.charToGlyph(unicode); + if (glyph != mapper.getMissingGlyphCode()) { + return compositeGlyphCode(!slot, glyph); + } + } + return missingGlyph; + } + + @Override + public int charToGlyph(int unicode) { + return charToVariationGlyph(unicode, 0); + } + + @Override + public int charToGlyph(char unicode) { + return charToGlyph((int) unicode); + } + } +} diff --git a/src/java.desktop/windows/classes/sun/font/NativeFont.java b/src/java.desktop/windows/classes/sun/font/NativeFont.java index 7a5c29c7c73e0..3efaa1762302c 100644 --- a/src/java.desktop/windows/classes/sun/font/NativeFont.java +++ b/src/java.desktop/windows/classes/sun/font/NativeFont.java @@ -79,8 +79,7 @@ public GeneralPath getGlyphOutline(long pScalerContext, return null; } - public GeneralPath getGlyphVectorOutline(long pScalerContext, - int[] glyphs, int numGlyphs, + public GlyphRenderData getGlyphRenderData(long pScalerContext, int glyphCode, float x, float y) { return null; } @@ -104,4 +103,8 @@ Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext, int glyphCode) { return new Rectangle2D.Float(0f, 0f, 0f, 0f); } + + public SlotInfo getSlotInfoForGlyph(int glyphCode) { + return new SlotInfo(getDelegateFont()); + } } diff --git a/src/java.desktop/windows/classes/sun/font/NativeStrike.java b/src/java.desktop/windows/classes/sun/font/NativeStrike.java index a28c47911ed47..8391bbac718dc 100644 --- a/src/java.desktop/windows/classes/sun/font/NativeStrike.java +++ b/src/java.desktop/windows/classes/sun/font/NativeStrike.java @@ -79,7 +79,7 @@ GeneralPath getGlyphOutline(int glyphCode, float x, float y) { return null; } - GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { + GlyphRenderData getGlyphRenderData(int glyphCode, float x, float y) { return null; } diff --git a/src/java.desktop/windows/data/fontconfig/fontconfig.properties b/src/java.desktop/windows/data/fontconfig/fontconfig.properties index 40e40ca55af9f..382e425eeace1 100644 --- a/src/java.desktop/windows/data/fontconfig/fontconfig.properties +++ b/src/java.desktop/windows/data/fontconfig/fontconfig.properties @@ -51,7 +51,6 @@ allfonts.mongolian=Mongolian Baiti allfonts.myanmar=Myanmar Text allfonts.dingbats=Wingdings allfonts.symbol=Symbol -allfonts.symbols=Segoe UI Symbol allfonts.thai=DokChampa allfonts.georgian=Sylfaen allfonts.korean-fallback=Malgun Gothic @@ -250,8 +249,7 @@ sequence.dialoginput.x-windows-949=alphabetic,korean,dingbats,symbol sequence.allfonts.x-windows-874=alphabetic,thai,dingbats,symbol -sequence.fallback=symbols,\ - chinese-ms950,chinese-hkscs,chinese-ms936,chinese-gb18030,\ +sequence.fallback=chinese-ms950,chinese-hkscs,chinese-ms936,chinese-gb18030,\ japanese,korean-fallback,chinese-ms950-extb,chinese-ms936-extb,\ georgian,devanagari,bengali,gujarati,gurmukhi,kannada,\ malayalam,oriya,sinhala,tamil,telugu,thai,khmer,mongolian,\ @@ -328,5 +326,6 @@ filename.Wingdings=WINGDING.TTF filename.Sylfaen=sylfaen.ttf filename.Segoe_UI_Symbol=SEGUISYM.TTF +filename.Segoe_UI_Emoji=seguiemj.ttf filename.Malgun_Gothic=malgun.ttf filename.Malgun_Gothic_Bold=malgunbd.TTF diff --git a/src/java.desktop/windows/native/libawt/java2d/d3d/D3DTextRenderer.cpp b/src/java.desktop/windows/native/libawt/java2d/d3d/D3DTextRenderer.cpp index debadc9e6e934..abc9ece738bd3 100644 --- a/src/java.desktop/windows/native/libawt/java2d/d3d/D3DTextRenderer.cpp +++ b/src/java.desktop/windows/native/libawt/java2d/d3d/D3DTextRenderer.cpp @@ -792,7 +792,7 @@ D3DTR_DrawGlyphList(D3DContext *d3dc, D3DSDOps *dstOps, break; } - grayscale = (ginfo->rowBytes == ginfo->width); + grayscale = (ginfo->format == sun_font_StrikeCache_PIXEL_FORMAT_GREYSCALE); if (usePositions) { jfloat posx = NEXT_FLOAT(positions); diff --git a/src/java.desktop/windows/native/libfontmanager/lcdglyph.c b/src/java.desktop/windows/native/libfontmanager/lcdglyph.c index 5e7015855b84e..b95ce622b8fc8 100644 --- a/src/java.desktop/windows/native/libfontmanager/lcdglyph.c +++ b/src/java.desktop/windows/native/libfontmanager/lcdglyph.c @@ -436,6 +436,7 @@ Java_sun_font_FileFontStrike__1getGlyphImageFromWindows glyphInfo->width -= 1; // must subtract 1 } glyphInfo->height = height; + glyphInfo->format = sun_font_StrikeCache_PIXEL_FORMAT_LCD; glyphInfo->advanceX = advanceX; glyphInfo->advanceY = advanceY; glyphInfo->topLeftX = (float)(topLeftX-1); diff --git a/test/jdk/java/awt/font/Emoji/ComplexEmoji.java b/test/jdk/java/awt/font/Emoji/ComplexEmoji.java new file mode 100644 index 0000000000000..f1adf4b14f25f --- /dev/null +++ b/test/jdk/java/awt/font/Emoji/ComplexEmoji.java @@ -0,0 +1,145 @@ +/* + * Copyright 2021 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. + * + * 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. + */ + +/* + * @test + * @key headful + * @bug 8269806 + * @summary Checks that complex emoji are rendered with proper shaping. + */ + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.stream.Stream; + +public class ComplexEmoji { + private static final int IMG_WIDTH = 60; + private static final int IMG_HEIGHT = 20; + + private static final String[] EMOJI = { + "\ud83d\udd25", // Fire + "\u2764\ufe0f", // Heart + color variation selector + "\ud83e\udd18\ud83c\udffb", // Horns sign - white hand + "\ud83d\udc41\ufe0f\u200d\ud83d\udde8\ufe0f", // Eye in speech bubble - ZWJ sequence + "\uD83C\uDDE6\uD83C\uDDF6", // Antarctica flag + "\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f", // England flag - tag sequence + }; + + public static void main(String[] args) { + requireFont("Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji"); + + // Platform-specific tricks + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + EMOJI[4] = EMOJI[5] = null; // Flags and tags are not supported on Windows + } + + BufferedImage img = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB); + String errors = ""; + for (int i = 0; i < EMOJI.length; i++) { + String emoji = EMOJI[i]; + if (emoji == null) { + continue; + } + drawEmoji(img, emoji); + String error = checkEmoji(img); + if (error != null) { + errors += "\n#" + i + ": " + error; + try { + ImageIO.write(img, "PNG", new File("ComplexEmoji" + i + ".png")); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + if (!errors.isEmpty()) { + throw new RuntimeException(errors); + } + } + + private static void drawEmoji(Image img, String emoji) { + Graphics g = img.getGraphics(); + g.setColor(Color.white); + g.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT); + g.setFont(new Font(Font.DIALOG, Font.PLAIN, 12)); + // Try to trick shaper by prepending "A" letter + // White on white will not be visible anyway + g.drawString("A" + emoji, 2, 15); + g.dispose(); + } + + private static String checkEmoji(BufferedImage img) { + Point min = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); + Point max = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE); + for (int x = 0; x < IMG_WIDTH; x++) { + for (int y = 0; y < IMG_HEIGHT; y++) { + int rgb = img.getRGB(x, y); + if (rgb != -1) { + if (x < min.x) { + min.x = x; + } + if (y < min.y) { + min.y = y; + } + if (x > max.x) { + max.x = x; + } + if (y > max.y) { + max.y = y; + } + } + } + } + if (min.x >= max.x || min.y >= max.y) { + return "Empty image"; + } + int width = max.x - min.x + 1; + int height = max.y - min.y + 1; + double ratio = (double) width / (double) height; + if (ratio > 1.5) { + return "Too wide image, is there few glyphs instead of one?"; + } + return null; + } + + private static void requireFont(String macOS, String windows, String linux) { + String os = System.getProperty("os.name").toLowerCase(); + String font; + if (os.contains("mac")) { + font = macOS; + } else if (os.contains("windows")) { + font = windows; + } else if (os.contains("linux")) { + font = linux; + } else { + return; + } + String[] fs = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + if (Stream.of(fs).noneMatch(s -> s.equals(font))) { + System.err.println("Required font not found: " + font); + System.exit(0); + } + } +} diff --git a/test/jdk/java/awt/font/MacEmoji.java b/test/jdk/java/awt/font/Emoji/Emoji.java similarity index 76% rename from test/jdk/java/awt/font/MacEmoji.java rename to test/jdk/java/awt/font/Emoji/Emoji.java index 198db5c107fbf..4035c64628007 100644 --- a/test/jdk/java/awt/font/MacEmoji.java +++ b/test/jdk/java/awt/font/Emoji/Emoji.java @@ -24,24 +24,29 @@ /* * @test * @key headful - * @bug 8263583 + * @bug 8263583 8269806 * @summary Checks that emoji character has a non-empty and identical * representation when rendered to different types of images, - * including an accelerated (OpenGL or Metal) surface. - * @requires (os.family == "mac") - * @run main/othervm -Dsun.java2d.uiScale=1 MacEmoji + * including an accelerated surface. + * @run main/othervm -Dsun.java2d.uiScale=1 Emoji */ import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import java.util.List; +import java.util.stream.Stream; -public class MacEmoji { +public class Emoji { private static final int IMG_WIDTH = 20; private static final int IMG_HEIGHT = 20; + private static final Font FONT = new Font( + System.getProperty("os.name").toLowerCase().contains("linux") ? + "Noto Color Emoji" : Font.DIALOG, Font.PLAIN, 12); public static void main(String[] args) { + requireFont("Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji"); + GraphicsConfiguration cfg = GraphicsEnvironment.getLocalGraphicsEnvironment() .getDefaultScreenDevice().getDefaultConfiguration(); @@ -101,8 +106,27 @@ private static void drawEmoji(Image img) { Graphics g = img.getGraphics(); g.setColor(Color.white); g.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT); - g.setFont(new Font(Font.DIALOG, Font.PLAIN, 12)); + g.setFont(FONT); g.drawString("\uD83D\uDE00" /* U+1F600 'GRINNING FACE' */, 2, 15); g.dispose(); } + + private static void requireFont(String macOS, String windows, String linux) { + String os = System.getProperty("os.name").toLowerCase(); + String font; + if (os.contains("mac")) { + font = macOS; + } else if (os.contains("windows")) { + font = windows; + } else if (os.contains("linux")) { + font = linux; + } else { + return; + } + String[] fs = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + if (Stream.of(fs).noneMatch(s -> s.equals(font))) { + System.err.println("Required font not found: " + font); + System.exit(0); + } + } } diff --git a/test/jdk/java/awt/font/Emoji/EmojiVariation.java b/test/jdk/java/awt/font/Emoji/EmojiVariation.java new file mode 100644 index 0000000000000..e20cf857241cb --- /dev/null +++ b/test/jdk/java/awt/font/Emoji/EmojiVariation.java @@ -0,0 +1,203 @@ +/* + * Copyright 2021 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. + * + * 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. + */ + +/* + * @test + * @key headful + * @bug 8269806 + * @summary Checks that variation selectors work. + */ + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING; +import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_OFF; + +public class EmojiVariation { + private static final int IMG_WIDTH = 100; + private static final int IMG_HEIGHT = 50; + + private static final Color SYMBOL_COLOR = Color.MAGENTA; + + // These emoji must be monochrome by default + private static final String[] SYMBOLS = { + "\u0023","\u002a","\u0030","\u0031","\u0032","\u0033","\u0034","\u0035","\u0036","\u0037","\u0038","\u0039", + "\u00a9","\u00ae","\u203c","\u2049","\u2122","\u2139","\u2194","\u2195","\u2196","\u2197","\u2198","\u2199", + "\u21a9","\u21aa","\u2328","\u23cf","\u23ed","\u23ee","\u23ef","\u23f1","\u23f2","\u23f8","\u23f9","\u23fa", + "\u24c2","\u25aa","\u25ab","\u25b6","\u25c0","\u25fb","\u25fc","\u2600","\u2601","\u2602","\u2603","\u2604", + "\u260e","\u2611","\u2618","\u261d","\u2620","\u2622","\u2623","\u2626","\u262a","\u262e","\u262f","\u2638", + "\u2639","\u263a","\u2640","\u2642","\u265f","\u2660","\u2663","\u2665","\u2666","\u2668","\u267b","\u267e", + "\u2692","\u2694","\u2695","\u2696","\u2697","\u2699","\u269b","\u269c","\u26a0","\u26b0","\u26b1","\u26c8", + "\u26cf","\u26d1","\u26d3","\u26e9","\u26f0","\u26f1","\u26f4","\u26f7","\u26f8","\u26f9","\u2702","\u2708", + "\u2709","\u270c","\u270d","\u270f","\u2712","\u2714","\u2716","\u271d","\u2721","\u2733","\u2734","\u2744", + "\u2747","\u2763","\u2764","\u27a1","\u2934","\u2935","\u2b05","\u2b06","\u2b07","\u3030","\u303d","\u3297", + "\u3299","\ud83c\udd70","\ud83c\udd71","\ud83c\udd7e","\ud83c\udd7f","\ud83c\ude02","\ud83c\ude37", + "\ud83c\udf21","\ud83c\udf24","\ud83c\udf25","\ud83c\udf26","\ud83c\udf27","\ud83c\udf28","\ud83c\udf29", + "\ud83c\udf2a","\ud83c\udf2b","\ud83c\udf2c","\ud83c\udf36","\ud83c\udf7d","\ud83c\udf96","\ud83c\udf97", + "\ud83c\udf99","\ud83c\udf9a","\ud83c\udf9b","\ud83c\udf9e","\ud83c\udf9f","\ud83c\udfcb","\ud83c\udfcc", + "\ud83c\udfcd","\ud83c\udfce","\ud83c\udfd4","\ud83c\udfd5","\ud83c\udfd6","\ud83c\udfd7","\ud83c\udfd8", + "\ud83c\udfd9","\ud83c\udfda","\ud83c\udfdb","\ud83c\udfdc","\ud83c\udfdd","\ud83c\udfde","\ud83c\udfdf", + "\ud83c\udff3","\ud83c\udff5","\ud83c\udff7","\ud83d\udc3f","\ud83d\udc41","\ud83d\udcfd","\ud83d\udd49", + "\ud83d\udd4a","\ud83d\udd6f","\ud83d\udd70","\ud83d\udd73","\ud83d\udd74","\ud83d\udd75","\ud83d\udd76", + "\ud83d\udd77","\ud83d\udd78","\ud83d\udd79","\ud83d\udd87","\ud83d\udd8a","\ud83d\udd8b","\ud83d\udd8c", + "\ud83d\udd8d","\ud83d\udd90","\ud83d\udda5","\ud83d\udda8","\ud83d\uddb1","\ud83d\uddb2","\ud83d\uddbc", + "\ud83d\uddc2","\ud83d\uddc3","\ud83d\uddc4","\ud83d\uddd1","\ud83d\uddd2","\ud83d\uddd3","\ud83d\udddc", + "\ud83d\udddd","\ud83d\uddde","\ud83d\udde1","\ud83d\udde3","\ud83d\udde8","\ud83d\uddef","\ud83d\uddf3", + "\ud83d\uddfa","\ud83d\udecb","\ud83d\udecd","\ud83d\udece","\ud83d\udecf","\ud83d\udee0","\ud83d\udee1", + "\ud83d\udee2","\ud83d\udee3","\ud83d\udee4","\ud83d\udee5","\ud83d\udee9","\ud83d\udef0","\ud83d\udef3", + }; + + private enum Variation { + DEFAULT(""), + MONO("\ufe0e"), + COLOR("\ufe0f"); + + final String suffix; + + Variation(String suffix) { + this.suffix = suffix; + } + } + + public static void main(String[] args) { + requireFont("Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji"); + requireFont("Zapf Dingbats", "Segoe UI Symbol", "DejaVu Sans"); + + // Platform-specific tricks + if (System.getProperty("os.name").toLowerCase().contains("linux")) { + // Many emoji on Linux don't have monochrome variants + Arrays.fill(SYMBOLS, 28, 37, null); + Arrays.fill(SYMBOLS, 83, 94, null); + Arrays.fill(SYMBOLS, 117, SYMBOLS.length, null); + } else if (System.getProperty("os.name").toLowerCase().contains("mac")) { + // Many emoji on macOS don't have monochrome variants + Arrays.fill(SYMBOLS, 28, 36, null); + Arrays.fill(SYMBOLS, 81, 94, null); + Arrays.fill(SYMBOLS, 127, SYMBOLS.length, null); + } + + BufferedImage img = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB); + String errors = ""; + for (String s : SYMBOLS) { + if (s == null) { + continue; + } + errors += test(img, s, Variation.DEFAULT, false); + errors += test(img, s, Variation.MONO, false); + errors += test(img, s, Variation.COLOR, true); + } + + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + // Bonus points: check that variation selectors work other way too + String s = "\ud83d\udd25"; + errors += test(img, s, Variation.DEFAULT, true); + errors += test(img, s, Variation.MONO, false); + errors += test(img, s, Variation.COLOR, true); + } + + if (!errors.isEmpty()) { + throw new RuntimeException(errors); + } + } + + private static String test(BufferedImage img, String symbol, Variation variation, boolean expectColor) { + draw(img, symbol + variation.suffix); + String error = check(img, expectColor); + if (error != null) { + String name = symbol.chars().mapToObj(c -> { + String s = Integer.toHexString(c); + return "0".repeat(4 - s.length()) + s; + }).collect(Collectors.joining("-")) + "-" + variation; + try { + ImageIO.write(img, "PNG", new File("EmojiVariation-" + name + ".png")); + } catch (IOException e) { + e.printStackTrace(); + } + return "\n" + name + ": " + error; + } + return ""; + } + + private static void draw(Image img, String symbol) { + Graphics2D g = (Graphics2D) img.getGraphics(); + g.setColor(Color.white); + g.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT); + g.setFont(new Font(Font.DIALOG, Font.PLAIN, 50)); + g.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_OFF); + g.setColor(SYMBOL_COLOR); + g.drawString(symbol, 2, 42); + g.dispose(); + } + + private static String check(BufferedImage img, boolean expectColor) { + boolean rendered = false; + boolean color = false; + for (int x = 0; x < IMG_WIDTH; x++) { + for (int y = 0; y < IMG_HEIGHT; y++) { + int rgb = img.getRGB(x, y); + if (rgb != Color.white.getRGB()) { + rendered = true; + if ((rgb & 0xff00ff) != 0xff00ff) { + // When monochrome symbol is rendered with AA=ON, + // pixel color may be anywhere between magenta (SYMBOL_COLOR) and white, + // which is 0xff00ff - 0xffffff. This means only green component may vary, + // red and green must always be 0xff + color = true; + } + } + } + } + if (!rendered) { + return "Empty image"; + } else if (color != expectColor) { + return expectColor ? "Expected color but rendered mono" : "Expected mono but rendered color"; + } + return null; + } + + private static void requireFont(String macOS, String windows, String linux) { + String os = System.getProperty("os.name").toLowerCase(); + String font; + if (os.contains("mac")) { + font = macOS; + } else if (os.contains("windows")) { + font = windows; + } else if (os.contains("linux")) { + font = linux; + } else { + return; + } + String[] fs = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + if (Stream.of(fs).noneMatch(s -> s.equals(font))) { + System.err.println("Required font not found: " + font); + System.exit(0); + } + } +} diff --git a/test/jdk/java/awt/font/Emoji/OutlineTextRendererEmoji.java b/test/jdk/java/awt/font/Emoji/OutlineTextRendererEmoji.java new file mode 100644 index 0000000000000..20288aa94ff5e --- /dev/null +++ b/test/jdk/java/awt/font/Emoji/OutlineTextRendererEmoji.java @@ -0,0 +1,201 @@ +/* + * Copyright 2021 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. + * + * 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. + */ + +/* + * @test + * @key headful + * @bug 8269806 + * @summary Checks that emoji rendered via glyph cache and bypassing it look similar. + */ + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.stream.Stream; + +import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING; +import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON; + +public class OutlineTextRendererEmoji { + private static final int IMG_WIDTH = 84; + private static final int IMG_HEIGHT = 84; + private static final int EMOJI_X = 0; + private static final int EMOJI_Y = 70; + private static final int FONT_SIZE = 70; + private static final String EMOJI = "\ud83d\udd25"; // Fire + + private static final int WINDOW_SIZE = 12; // In pixels + private static final double THRESHOLD = 0.98; + + public static void main(String[] args) throws Exception { + requireFont("Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji"); + + BufferedImage small = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB); + BufferedImage rescaled = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB); + BufferedImage big = new BufferedImage(IMG_WIDTH*2, IMG_HEIGHT*2, BufferedImage.TYPE_INT_RGB); + drawEmoji(small, EMOJI_X, EMOJI_Y, FONT_SIZE); + drawEmoji(big, EMOJI_X*2, EMOJI_Y*2, FONT_SIZE*2); + checkEmoji(small, big, rescaled); + } + + private static void drawEmoji(Image img, int x, int y, int size) { + Graphics2D g = (Graphics2D) img.getGraphics(); + g.setColor(Color.white); + g.fillRect(0, 0, img.getWidth(null), img.getHeight(null)); + g.setFont(new Font(Font.DIALOG, Font.PLAIN, size)); + g.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON); + g.drawString(EMOJI, x, y); + g.dispose(); + } + + private static void checkEmoji(BufferedImage small, BufferedImage big, BufferedImage rescaled) throws Exception { + Graphics2D g2d = rescaled.createGraphics(); + g2d.drawImage(big.getScaledInstance(small.getWidth(), small.getHeight(), Image.SCALE_SMOOTH), 0, 0, null); + g2d.dispose(); + + double ssim = SSIM.calculate(small, rescaled, WINDOW_SIZE); + System.out.println("SSIM is " + ssim); + + if (ssim < THRESHOLD) { + ImageIO.write(small, "PNG", new File("OutlineTextRendererEmoji-small.png")); + ImageIO.write(big, "PNG", new File("OutlineTextRendererEmoji-big.png")); + ImageIO.write(rescaled, "PNG", new File("OutlineTextRendererEmoji-rescaled.png")); + throw new Exception("Images mismatch: " + ssim); + } + } + + private static class SSIM { + private static double calculate(BufferedImage a, BufferedImage b, int windowSize) { + if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) { + throw new IllegalArgumentException("Images must have same size"); + } + if (a.getWidth() % windowSize != 0 || a.getHeight() % windowSize != 0) { + throw new IllegalArgumentException("Image sizes must be multiple of windowSize"); + } + + final double K1 = 0.01, K2 = 0.03; + final double L = 255; // dynamic range per component (2^8 - 1) + final double c1 = Math.pow(L * K1, 2); + final double c2 = Math.pow(L * K2, 2); + + double result = 0, alpha = 0; + int windows = 0; + for (int y = 0; y <= a.getHeight() - windowSize; y++) { + for (int x = 0; x <= a.getWidth() - windowSize; x++) { + + // Calculate averages + double[] avgA = vec(), avgB = vec(); + for (int py = 0; py < windowSize; py++) { + for (int px = 0; px < windowSize; px++) { + avgA = add(avgA, vec(a.getRGB(x + px, y + py))); + avgB = add(avgB, vec(b.getRGB(x + px, y + py))); + } + } + avgA = div(avgA, windowSize * windowSize); + avgB = div(avgB, windowSize * windowSize); + + // Calculate variance and covariance + double[] varA = vec(), varB = vec(), cov = vec(); + for (int py = 0; py < windowSize; py++) { + for (int px = 0; px < windowSize; px++) { + double[] da = sub(avgA, vec(a.getRGB(x + px, y + py))); + double[] db = sub(avgB, vec(b.getRGB(x + px, y + py))); + varA = add(varA, mul(da, da)); + varB = add(varB, mul(db, db)); + cov = add(cov, mul(da, db)); + } + } + varA = div(varA, windowSize * windowSize); + varB = div(varB, windowSize * windowSize); + cov = div(cov, windowSize * windowSize); + + // Calculate ssim + double[] ssim = vec(); + for (int i = 0; i < 4; i++) { + ssim[i] = ( + (2 * avgA[i] * avgB[i] + c1) * (2 * cov[i] + c2) + ) / ( + (avgA[i]*avgA[i] + avgB[i]*avgB[i] + c1) * (varA[i] + varB[i] + c2) + ); + } + + result += ssim[0] + ssim[1] + ssim[2]; + alpha += ssim[3]; + windows++; + } + } + if (alpha == windows) { + result /= 3.0; + } else { + result = (result + alpha) / 4.0; + } + return result / (double) windows; + } + + private static double[] vec(double... v) { + if (v.length == 0) { + return new double[4]; + } else if (v.length == 1) { + return new double[] {v[0],v[0],v[0],v[0]}; + } else { + return v; + } + } + private static double[] vec(int color) { + return vec(color & 0xff, (color >> 8) & 0xff, (color >> 16) & 0xff, (color >> 24) & 0xff); + } + + interface Op { double apply(double a, double b); } + private static double[] apply(Op op, double[] a, double... b) { + b = vec(b); + double[] r = new double[4]; + for (int i = 0; i < 4; i++) r[i] = op.apply(a[i], b[i]); + return r; + } + + private static double[] add(double[] a, double... b) { return apply((i, j) -> i + j, a, b); } + private static double[] sub(double[] a, double... b) { return apply((i, j) -> i - j, a, b); } + private static double[] mul(double[] a, double... b) { return apply((i, j) -> i * j, a, b); } + private static double[] div(double[] a, double... b) { return apply((i, j) -> i / j, a, b); } + } + + private static void requireFont(String macOS, String windows, String linux) { + String os = System.getProperty("os.name").toLowerCase(); + String font; + if (os.contains("mac")) { + font = macOS; + } else if (os.contains("windows")) { + font = windows; + } else if (os.contains("linux")) { + font = linux; + } else { + return; + } + String[] fs = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + if (Stream.of(fs).noneMatch(s -> s.equals(font))) { + System.err.println("Required font not found: " + font); + System.exit(0); + } + } +} diff --git a/test/jdk/java/awt/font/FontNames/FCCompositeTest.java b/test/jdk/java/awt/font/FontNames/FCCompositeTest.java index 63ac9872da8bf..08385ee56fda1 100644 --- a/test/jdk/java/awt/font/FontNames/FCCompositeTest.java +++ b/test/jdk/java/awt/font/FontNames/FCCompositeTest.java @@ -64,8 +64,8 @@ private static void test(int index) { System.out.println("WARNING: Not CompositeFont"); return; } - PhysicalFont pf = ((CompositeFont)f2d).getSlotFont(0); - fullName = pf.getFontName(Locale.ENGLISH); + Font2D slot = ((CompositeFont)f2d).getSlotFont(0); + fullName = slot.getFontName(Locale.ENGLISH); System.out.println("PF="+fullName); String[] command = {"fc-match",