OpenJDK / jdk / jdk
changeset 42713:593783862f40
8169202: [macos] Font substitution does not work for supplementary characters
Reviewed-by: serb, prr
Contributed-by: Dmitry Batrak <dmitry.batrak@jetbrains.com>
author | serb |
---|---|
date | Thu, 17 Nov 2016 02:31:04 +0300 |
parents | 679c4f3300b1 |
children | 40a20e211360 |
files | jdk/src/java.desktop/macosx/native/libawt_lwawt/font/CGGlyphImages.m jdk/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.h jdk/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.m jdk/test/java/awt/font/Fallback/SurrogatesFallbackTest.java |
diffstat | 4 files changed, 124 insertions(+), 14 deletions(-) [+] |
line wrap: on
line diff
--- a/jdk/src/java.desktop/macosx/native/libawt_lwawt/font/CGGlyphImages.m Wed Nov 16 14:27:39 2016 -0800 +++ b/jdk/src/java.desktop/macosx/native/libawt_lwawt/font/CGGlyphImages.m Thu Nov 17 02:31:04 2016 +0300 @@ -591,7 +591,7 @@ static inline GlyphInfo * CGGI_CreateImageForUnicode (CGGI_GlyphCanvas *canvas, const AWTStrike *strike, - const CGGI_RenderingMode *mode, const UniChar uniChar) + const CGGI_RenderingMode *mode, const UnicodeScalarValue uniChar) { // save the state of the world CGContextSaveGState(canvas->context); @@ -668,7 +668,7 @@ const AWTStrike *strike, const CGGI_RenderingMode *mode, jlong glyphInfos[], - const UniChar uniChars[], + const UnicodeScalarValue uniChars[], const CGGlyph glyphs[], const CFIndex len) { @@ -720,7 +720,7 @@ static inline void CGGI_FillImagesForGlyphs(jlong *glyphInfos, const AWTStrike *strike, const CGGI_RenderingMode *mode, - const UniChar uniChars[], const CGGlyph glyphs[], + const UnicodeScalarValue uniChars[], const CGGlyph glyphs[], const size_t maxWidth, const size_t maxHeight, const CFIndex len) { @@ -767,7 +767,7 @@ static inline void CGGI_CreateGlyphInfos(jlong *glyphInfos, const AWTStrike *strike, const CGGI_RenderingMode *mode, - const UniChar uniChars[], const CGGlyph glyphs[], + const UnicodeScalarValue uniChars[], const CGGlyph glyphs[], CGSize advances[], CGRect bboxes[], const CFIndex len) { AWTFont *font = strike->fAWTFont; @@ -817,7 +817,7 @@ const AWTStrike *strike, const CGGI_RenderingMode *mode, jint rawGlyphCodes[], - UniChar uniChars[], CGGlyph glyphs[], + UnicodeScalarValue uniChars[], CGGlyph glyphs[], CGSize advances[], CGRect bboxes[], const CFIndex len) { @@ -860,7 +860,7 @@ CGRect bboxes[len]; CGSize advances[len]; CGGlyph glyphs[len]; - UniChar uniChars[len]; + UnicodeScalarValue uniChars[len]; CGGI_CreateGlyphsAndScanForComplexities(glyphInfos, strike, &mode, rawGlyphCodes, uniChars, glyphs, @@ -871,7 +871,7 @@ // just do one malloc, and carve it up for all the buffers void *buffer = malloc(sizeof(CGRect) * sizeof(CGSize) * - sizeof(CGGlyph) * sizeof(UniChar) * len); + sizeof(CGGlyph) * sizeof(UnicodeScalarValue) * len); if (buffer == NULL) { [[NSException exceptionWithName:NSMallocException reason:@"Failed to allocate memory for the temporary glyph strike and measurement buffers." userInfo:nil] raise]; @@ -880,7 +880,7 @@ CGRect *bboxes = (CGRect *)(buffer); CGSize *advances = (CGSize *)(bboxes + sizeof(CGRect) * len); CGGlyph *glyphs = (CGGlyph *)(advances + sizeof(CGGlyph) * len); - UniChar *uniChars = (UniChar *)(glyphs + sizeof(UniChar) * len); + UnicodeScalarValue *uniChars = (UnicodeScalarValue *)(glyphs + sizeof(UnicodeScalarValue) * len); CGGI_CreateGlyphsAndScanForComplexities(glyphInfos, strike, &mode, rawGlyphCodes, uniChars, glyphs,
--- a/jdk/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.h Wed Nov 16 14:27:39 2016 -0800 +++ b/jdk/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.h Thu Nov 17 02:31:04 2016 +0300 @@ -32,7 +32,9 @@ #pragma mark --- CoreText Support --- #define HI_SURROGATE_START 0xD800 +#define HI_SURROGATE_END 0xDBFF #define LO_SURROGATE_START 0xDC00 +#define LO_SURROGATE_END 0xDFFF /* * Transform Unicode characters into glyphs.
--- a/jdk/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.m Wed Nov 16 14:27:39 2016 -0800 +++ b/jdk/src/java.desktop/macosx/native/libawt_lwawt/font/CoreTextSupport.m Thu Nov 17 02:31:04 2016 +0300 @@ -103,24 +103,34 @@ 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; + CGGlyph glyph = glyphs[i]; if (glyph > 0) { glyphsAsInts[i] = glyph; + if (surrogatePair) i++; continue; } - UniChar unicode = unicodes[i]; - const CTFontRef fallback = JRSFontCreateFallbackFontForCharacters((CTFontRef)font->fFont, &unicode, 1); + const CTFontRef fallback = JRSFontCreateFallbackFontForCharacters((CTFontRef)font->fFont, &unicodes[i], + surrogatePair ? 2 : 1); if (fallback) { - CTFontGetGlyphsForCharacters(fallback, &unicode, &glyph, 1); + CTFontGetGlyphsForCharacters(fallback, &unicodes[i], &glyphs[i], surrogatePair ? 2 : 1); + glyph = glyphs[i]; CFRelease(fallback); } if (glyph > 0) { - glyphsAsInts[i] = -unicode; // set the glyph code to the negative unicode value + 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++; } } @@ -158,8 +168,18 @@ return (CTFontRef)font->fFont; } - UTF16Char character = -glyphCode; - return CTS_CopyCTFallbackFontAndGlyphForUnicode(font, &character, glyphRef, 1); + int codePoint = -glyphCode; + if (codePoint >= 0x10000) { + UTF16Char chars[2]; + CGGlyph glyphs[2]; + CTS_BreakupUnicodeIntoSurrogatePairs(codePoint, chars); + CTFontRef result = CTS_CopyCTFallbackFontAndGlyphForUnicode(font, chars, glyphs, 2); + *glyphRef = glyphs[0]; + return result; + } else { + UTF16Char character = codePoint; + return CTS_CopyCTFallbackFontAndGlyphForUnicode(font, &character, glyphRef, 1); + } } // Breakup a 32 bit unicode value into the component surrogate pairs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/awt/font/Fallback/SurrogatesFallbackTest.java Thu Nov 17 02:31:04 2016 +0300 @@ -0,0 +1,88 @@ +/* + * Copyright 2016 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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 + * @bug 8169202 + * @summary verify font fallback for surrogate pairs on macOS + * @requires os.family == "mac" + */ + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.font.GlyphVector; +import java.awt.image.BufferedImage; +import java.util.function.Consumer; + +public class SurrogatesFallbackTest { + private static final int CHARACTER = 0x1d400; // MATHEMATICAL BOLD CAPITAL A + private static final Font FONT = new Font("Menlo", // expected to fallback to STIXGeneral for the character above + Font.PLAIN, + 12); + private static final int IMAGE_WIDTH = 20; + private static final int IMAGE_HEIGHT = 20; + private static final int GLYPH_X = 5; + private static final int GLYPH_Y = 15; + + public static void main(String[] args) { + BufferedImage noGlyph = createImage(g -> {}); + BufferedImage missingGlyph = createImage(g -> { + GlyphVector gv = FONT.createGlyphVector(g.getFontRenderContext(), new int[]{FONT.getMissingGlyphCode()}); + g.drawGlyphVector(gv, GLYPH_X, GLYPH_Y); + }); + BufferedImage surrogateCharGlyph = createImage(g -> { + g.setFont(FONT); + g.drawString(new String(Character.toChars(CHARACTER)), GLYPH_X, GLYPH_Y); + }); + + if (imagesAreEqual(surrogateCharGlyph, noGlyph)) { + throw new RuntimeException("Character was not rendered"); + } + if (imagesAreEqual(surrogateCharGlyph, missingGlyph)) { + throw new RuntimeException("Character is rendered as missing"); + } + } + + private static BufferedImage createImage(Consumer<Graphics2D> drawing) { + BufferedImage image = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + g.setColor(Color.white); + g.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT); + g.setColor(Color.black); + drawing.accept(g); + g.dispose(); + return image; + } + + private static boolean imagesAreEqual(BufferedImage i1, BufferedImage i2) { + if (i1.getWidth() != i2.getWidth() || i1.getHeight() != i2.getHeight()) return false; + for (int i = 0; i < i1.getWidth(); i++) { + for (int j = 0; j < i1.getHeight(); j++) { + if (i1.getRGB(i, j) != i2.getRGB(i, j)) { + return false; + } + } + } + return true; + } +} +