7190349: [macosx] Text (Label) is incorrectly drawn with a rotated g2d

8013569: [macosx] JLabel preferred size incorrect on retina displays with non-default font size

Reviewed-by: prr
This commit is contained in:
Sergey Bylokhov 2013-07-26 21:18:42 +04:00
parent 969c84555e
commit 4a42ba3816
6 changed files with 212 additions and 90 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -31,7 +31,7 @@ import java.util.*;
import sun.awt.SunHints; import sun.awt.SunHints;
public class CStrike extends FontStrike { public final class CStrike extends FontStrike {
// Creates the native strike // Creates the native strike
private static native long createNativeStrikePtr(long nativeFontPtr, private static native long createNativeStrikePtr(long nativeFontPtr,
@ -68,10 +68,10 @@ public class CStrike extends FontStrike {
Rectangle2D.Float result, Rectangle2D.Float result,
double x, double y); double x, double y);
private CFont nativeFont; private final CFont nativeFont;
private AffineTransform invDevTx; private AffineTransform invDevTx;
private GlyphInfoCache glyphInfoCache; private final GlyphInfoCache glyphInfoCache;
private GlyphAdvanceCache glyphAdvanceCache; private final GlyphAdvanceCache glyphAdvanceCache;
private long nativeStrikePtr; private long nativeStrikePtr;
CStrike(final CFont font, final FontStrikeDesc inDesc) { CStrike(final CFont font, final FontStrikeDesc inDesc) {
@ -84,11 +84,11 @@ public class CStrike extends FontStrike {
// Normally the device transform should be the identity transform // Normally the device transform should be the identity transform
// for screen operations. The device transform only becomes // for screen operations. The device transform only becomes
// interesting when we are outputting between different dpi surfaces, // interesting when we are outputting between different dpi surfaces,
// like when we are printing to postscript. // like when we are printing to postscript or use retina.
if (inDesc.devTx != null && !inDesc.devTx.isIdentity()) { if (inDesc.devTx != null && !inDesc.devTx.isIdentity()) {
try { try {
invDevTx = inDesc.devTx.createInverse(); invDevTx = inDesc.devTx.createInverse();
} catch (NoninvertibleTransformException e) { } catch (NoninvertibleTransformException ignored) {
// ignored, since device transforms should not be that // ignored, since device transforms should not be that
// complicated, and if they are - there is nothing we can do, // complicated, and if they are - there is nothing we can do,
// so we won't worry about it. // so we won't worry about it.
@ -134,15 +134,13 @@ public class CStrike extends FontStrike {
nativeStrikePtr = 0; nativeStrikePtr = 0;
} }
// the fractional metrics default on our platform is OFF
private boolean useFractionalMetrics() {
return desc.fmHint == SunHints.INTVAL_FRACTIONALMETRICS_ON;
}
@Override
public int getNumGlyphs() { public int getNumGlyphs() {
return nativeFont.getNumGlyphs(); return nativeFont.getNumGlyphs();
} }
@Override
StrikeMetrics getFontMetrics() { StrikeMetrics getFontMetrics() {
if (strikeMetrics == null) { if (strikeMetrics == null) {
StrikeMetrics metrics = getFontMetrics(getNativeStrikePtr()); StrikeMetrics metrics = getFontMetrics(getNativeStrikePtr());
@ -155,74 +153,24 @@ public class CStrike extends FontStrike {
return strikeMetrics; return strikeMetrics;
} }
float getGlyphAdvance(int glyphCode) { @Override
return getScaledAdvanceForAdvance(getCachedNativeGlyphAdvance(glyphCode)); float getGlyphAdvance(final int glyphCode) {
return getCachedNativeGlyphAdvance(glyphCode);
} }
float getCodePointAdvance(int cp) { @Override
float advance = getCachedNativeGlyphAdvance(nativeFont.getMapper().charToGlyph(cp)); float getCodePointAdvance(final int cp) {
return getGlyphAdvance(nativeFont.getMapper().charToGlyph(cp));
double glyphScaleX = desc.glyphTx.getScaleX();
double devScaleX = desc.devTx.getScaleX();
if (devScaleX == 0) {
glyphScaleX = Math.sqrt(desc.glyphTx.getDeterminant());
devScaleX = Math.sqrt(desc.devTx.getDeterminant());
}
if (devScaleX == 0) {
devScaleX = Double.NaN; // this an undefined graphics state
}
advance = (float) (advance * glyphScaleX / devScaleX);
return useFractionalMetrics() ? advance : Math.round(advance);
} }
// calculate an advance, and round if not using fractional metrics @Override
private float getScaledAdvanceForAdvance(float advance) { Point2D.Float getCharMetrics(final char ch) {
if (invDevTx != null) { return getGlyphMetrics(nativeFont.getMapper().charToGlyph(ch));
advance *= invDevTx.getScaleX();
}
advance *= desc.glyphTx.getScaleX();
return useFractionalMetrics() ? advance : Math.round(advance);
} }
Point2D.Float getCharMetrics(char ch) { @Override
return getScaledPointForAdvance(getCachedNativeGlyphAdvance(nativeFont.getMapper().charToGlyph(ch))); Point2D.Float getGlyphMetrics(final int glyphCode) {
} return new Point2D.Float(getGlyphAdvance(glyphCode), 0.0f);
Point2D.Float getGlyphMetrics(int glyphCode) {
return getScaledPointForAdvance(getCachedNativeGlyphAdvance(glyphCode));
}
// calculate an advance point, and round if not using fractional metrics
private Point2D.Float getScaledPointForAdvance(float advance) {
Point2D.Float pt = new Point2D.Float(advance, 0);
if (!desc.glyphTx.isIdentity()) {
return scalePoint(pt);
}
if (!useFractionalMetrics()) {
pt.x = Math.round(pt.x);
}
return pt;
}
private Point2D.Float scalePoint(Point2D.Float pt) {
if (invDevTx != null) {
// transform the point out of the device space first
invDevTx.transform(pt, pt);
}
desc.glyphTx.transform(pt, pt);
pt.x -= desc.glyphTx.getTranslateX();
pt.y -= desc.glyphTx.getTranslateY();
if (!useFractionalMetrics()) {
pt.x = Math.round(pt.x);
pt.y = Math.round(pt.y);
}
return pt;
} }
Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) { Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) {
@ -414,9 +362,7 @@ public class CStrike extends FontStrike {
private SparseBitShiftingTwoLayerArray secondLayerCache; private SparseBitShiftingTwoLayerArray secondLayerCache;
private HashMap<Integer, Long> generalCache; private HashMap<Integer, Long> generalCache;
public GlyphInfoCache(final Font2D nativeFont, GlyphInfoCache(final Font2D nativeFont, final FontStrikeDesc desc) {
final FontStrikeDesc desc)
{
super(nativeFont, desc); super(nativeFont, desc);
firstLayerCache = new long[FIRST_LAYER_SIZE]; firstLayerCache = new long[FIRST_LAYER_SIZE];
} }
@ -527,7 +473,7 @@ public class CStrike extends FontStrike {
final int shift; final int shift;
final int secondLayerLength; final int secondLayerLength;
public SparseBitShiftingTwoLayerArray(final int size, final int shift) { SparseBitShiftingTwoLayerArray(final int size, final int shift) {
this.shift = shift; this.shift = shift;
this.cache = new long[1 << shift][]; this.cache = new long[1 << shift][];
this.secondLayerLength = size >> shift; this.secondLayerLength = size >> shift;
@ -559,6 +505,12 @@ public class CStrike extends FontStrike {
private SparseBitShiftingTwoLayerArray secondLayerCache; private SparseBitShiftingTwoLayerArray secondLayerCache;
private HashMap<Integer, Float> generalCache; private HashMap<Integer, Float> generalCache;
// Empty non private constructor was added because access to this
// class shouldn't be emulated by a synthetic accessor method.
GlyphAdvanceCache() {
super();
}
public synchronized float get(final int index) { public synchronized float get(final int index) {
if (index < 0) { if (index < 0) {
if (-index < SECOND_LAYER_SIZE) { if (-index < SECOND_LAYER_SIZE) {
@ -609,9 +561,7 @@ public class CStrike extends FontStrike {
final int shift; final int shift;
final int secondLayerLength; final int secondLayerLength;
public SparseBitShiftingTwoLayerArray(final int size, SparseBitShiftingTwoLayerArray(final int size, final int shift) {
final int shift)
{
this.shift = shift; this.shift = shift;
this.cache = new float[1 << shift][]; this.cache = new float[1 << shift][];
this.secondLayerLength = size >> shift; this.secondLayerLength = size >> shift;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -31,11 +31,12 @@
@interface AWTStrike : NSObject { @interface AWTStrike : NSObject {
@public @public
AWTFont * fAWTFont; AWTFont * fAWTFont;
CGFloat fSize; CGFloat fSize;
JRSFontRenderingStyle fStyle; JRSFontRenderingStyle fStyle;
jint fAAStyle; jint fAAStyle;
CGAffineTransform fTx; CGAffineTransform fTx;
CGAffineTransform fDevTx;
CGAffineTransform fAltTx; // alternate strike tx used for Sun2D CGAffineTransform fAltTx; // alternate strike tx used for Sun2D
CGAffineTransform fFontTx; CGAffineTransform fFontTx;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -65,6 +65,7 @@ static CGAffineTransform sInverseTX = { 1, 0, 0, -1, 0, 0 };
invDevTx.b *= -1; invDevTx.b *= -1;
invDevTx.c *= -1; invDevTx.c *= -1;
fFontTx = CGAffineTransformConcat(CGAffineTransformConcat(tx, invDevTx), sInverseTX); fFontTx = CGAffineTransformConcat(CGAffineTransformConcat(tx, invDevTx), sInverseTX);
fDevTx = CGAffineTransformInvert(invDevTx);
// the "font size" is the square root of the determinant of the matrix // the "font size" is the square root of the determinant of the matrix
fSize = sqrt(abs(fFontTx.a * fFontTx.d - fFontTx.b * fFontTx.c)); fSize = sqrt(abs(fFontTx.a * fFontTx.d - fFontTx.b * fFontTx.c));
@ -148,7 +149,8 @@ Java_sun_font_CStrike_getNativeGlyphAdvance
{ {
CGSize advance; CGSize advance;
JNF_COCOA_ENTER(env); JNF_COCOA_ENTER(env);
AWTFont *awtFont = ((AWTStrike *)jlong_to_ptr(awtStrikePtr))->fAWTFont; AWTStrike *awtStrike = (AWTStrike *)jlong_to_ptr(awtStrikePtr);
AWTFont *awtFont = awtStrike->fAWTFont;
// negative glyph codes are really unicodes, which were placed there by the mapper // negative glyph codes are really unicodes, which were placed there by the mapper
// to indicate we should use CoreText to substitute the character // to indicate we should use CoreText to substitute the character
@ -156,6 +158,10 @@ JNF_COCOA_ENTER(env);
const CTFontRef fallback = CTS_CopyCTFallbackFontAndGlyphForJavaGlyphCode(awtFont, glyphCode, &glyph); const CTFontRef fallback = CTS_CopyCTFallbackFontAndGlyphForJavaGlyphCode(awtFont, glyphCode, &glyph);
CTFontGetAdvancesForGlyphs(fallback, kCTFontDefaultOrientation, &glyph, &advance, 1); CTFontGetAdvancesForGlyphs(fallback, kCTFontDefaultOrientation, &glyph, &advance, 1);
CFRelease(fallback); CFRelease(fallback);
advance = CGSizeApplyAffineTransform(advance, awtStrike->fFontTx);
if (!JRSFontStyleUsesFractionalMetrics(awtStrike->fStyle)) {
advance.width = round(advance.width);
}
JNF_COCOA_EXIT(env); JNF_COCOA_EXIT(env);
return advance.width; return advance.width;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -455,6 +455,7 @@ CGGI_ClearCanvas(CGGI_GlyphCanvas *canvas, GlyphInfo *info)
#define CGGI_GLYPH_BBOX_PADDING 2.0f #define CGGI_GLYPH_BBOX_PADDING 2.0f
static inline GlyphInfo * static inline GlyphInfo *
CGGI_CreateNewGlyphInfoFrom(CGSize advance, CGRect bbox, CGGI_CreateNewGlyphInfoFrom(CGSize advance, CGRect bbox,
const AWTStrike *strike,
const CGGI_RenderingMode *mode) const CGGI_RenderingMode *mode)
{ {
size_t pixelSize = mode->glyphDescriptor->pixelSize; size_t pixelSize = mode->glyphDescriptor->pixelSize;
@ -477,6 +478,12 @@ CGGI_CreateNewGlyphInfoFrom(CGSize advance, CGRect bbox,
width = 1; width = 1;
height = 1; height = 1;
} }
advance = CGSizeApplyAffineTransform(advance, strike->fFontTx);
if (!JRSFontStyleUsesFractionalMetrics(strike->fStyle)) {
advance.width = round(advance.width);
advance.height = round(advance.height);
}
advance = CGSizeApplyAffineTransform(advance, strike->fDevTx);
#ifdef USE_IMAGE_ALIGNED_MEMORY #ifdef USE_IMAGE_ALIGNED_MEMORY
// create separate memory // create separate memory
@ -564,10 +571,10 @@ CGGI_CreateImageForUnicode
JRSFontGetBoundingBoxesForGlyphsAndStyle(fallback, &tx, style, &glyph, 1, &bbox); JRSFontGetBoundingBoxesForGlyphsAndStyle(fallback, &tx, style, &glyph, 1, &bbox);
CGSize advance; CGSize advance;
JRSFontGetAdvancesForGlyphsAndStyle(fallback, &tx, strike->fStyle, &glyph, 1, &advance); CTFontGetAdvancesForGlyphs(fallback, kCTFontDefaultOrientation, &glyph, &advance, 1);
// create the Sun2D GlyphInfo we are going to strike into // create the Sun2D GlyphInfo we are going to strike into
GlyphInfo *info = CGGI_CreateNewGlyphInfoFrom(advance, bbox, mode); GlyphInfo *info = CGGI_CreateNewGlyphInfoFrom(advance, bbox, strike, mode);
// fix the context size, just in case the substituted character is unexpectedly large // fix the context size, just in case the substituted character is unexpectedly large
CGGI_SizeCanvas(canvas, info->width, info->height, mode->cgFontMode); CGGI_SizeCanvas(canvas, info->width, info->height, mode->cgFontMode);
@ -715,7 +722,7 @@ CGGI_CreateGlyphInfos(jlong *glyphInfos, const AWTStrike *strike,
JRSFontRenderingStyle bboxCGMode = JRSFontAlignStyleForFractionalMeasurement(strike->fStyle); JRSFontRenderingStyle bboxCGMode = JRSFontAlignStyleForFractionalMeasurement(strike->fStyle);
JRSFontGetBoundingBoxesForGlyphsAndStyle((CTFontRef)font->fFont, &tx, bboxCGMode, glyphs, len, bboxes); JRSFontGetBoundingBoxesForGlyphsAndStyle((CTFontRef)font->fFont, &tx, bboxCGMode, glyphs, len, bboxes);
JRSFontGetAdvancesForGlyphsAndStyle((CTFontRef)font->fFont, &tx, strike->fStyle, glyphs, len, advances); CTFontGetAdvancesForGlyphs((CTFontRef)font->fFont, kCTFontDefaultOrientation, glyphs, advances, len);
size_t maxWidth = 1; size_t maxWidth = 1;
size_t maxHeight = 1; size_t maxHeight = 1;
@ -732,7 +739,7 @@ CGGI_CreateGlyphInfos(jlong *glyphInfos, const AWTStrike *strike,
CGSize advance = advances[i]; CGSize advance = advances[i];
CGRect bbox = bboxes[i]; CGRect bbox = bboxes[i];
GlyphInfo *glyphInfo = CGGI_CreateNewGlyphInfoFrom(advance, bbox, mode); GlyphInfo *glyphInfo = CGGI_CreateNewGlyphInfoFrom(advance, bbox, strike, mode);
if (maxWidth < glyphInfo->width) maxWidth = glyphInfo->width; if (maxWidth < glyphInfo->width) maxWidth = glyphInfo->width;
if (maxHeight < glyphInfo->height) maxHeight = glyphInfo->height; if (maxHeight < glyphInfo->height) maxHeight = glyphInfo->height;

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2013, 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.
*/
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
* @test
* @bug 7190349
* @summary Verifies that we get correct direction, when draw rotated string.
* @author Sergey Bylokhov
* @run main/othervm DrawRotatedString
*/
public final class DrawRotatedString {
private static final int SIZE = 500;
public static void main(final String[] args) throws IOException {
BufferedImage bi = createBufferedImage(true);
verify(bi);
bi = createBufferedImage(false);
verify(bi);
System.out.println("Passed");
}
private static void verify(BufferedImage bi) throws IOException {
for (int i = 0; i < SIZE; ++i) {
for (int j = 0; j < 99; ++j) {
//Text should not appear before 100
if (bi.getRGB(i, j) != Color.RED.getRGB()) {
ImageIO.write(bi, "png", new File("image.png"));
throw new RuntimeException("Failed: wrong text location");
}
}
}
}
private static BufferedImage createBufferedImage(final boolean aa) {
final BufferedImage bi = new BufferedImage(SIZE, SIZE,
BufferedImage.TYPE_INT_RGB);
final Graphics2D bg = bi.createGraphics();
bg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
aa ? RenderingHints.VALUE_ANTIALIAS_ON
: RenderingHints.VALUE_ANTIALIAS_OFF);
bg.setColor(Color.RED);
bg.fillRect(0, 0, SIZE, SIZE);
bg.translate(100, 100);
bg.rotate(Math.toRadians(90));
bg.setColor(Color.BLACK);
bg.setFont(bg.getFont().deriveFont(20.0f));
bg.drawString("MMMMMMMMMMMMMMMM", 0, 0);
bg.dispose();
return bi;
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2013, 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.
*/
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
* @test
* @bug 8013569
* @author Sergey Bylokhov
*/
public final class IncorrectTextSize {
static final int scale = 2;
static final int width = 1200;
static final int height = 100;
static BufferedImage bi = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
static final String TEXT = "The quick brown fox jumps over the lazy dog"
+ "The quick brown fox jumps over the lazy dog";
public static void main(final String[] args) throws IOException {
for (int point = 5; point < 11; ++point) {
Graphics2D g2d = bi.createGraphics();
g2d.setFont(new Font(Font.DIALOG, Font.PLAIN, point));
g2d.scale(scale, scale);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.green);
g2d.drawString(TEXT, 0, 20);
int length = g2d.getFontMetrics().stringWidth(TEXT);
if (length < 0) {
throw new RuntimeException("Negative length");
}
for (int i = (length + 1) * scale; i < width; ++i) {
for (int j = 0; j < height; ++j) {
if (bi.getRGB(i, j) != Color.white.getRGB()) {
g2d.drawLine(length, 0, length, height);
ImageIO.write(bi, "png", new File("image.png"));
System.out.println("length = " + length);
System.err.println("Wrong color at x=" + i + ",y=" + j);
System.err.println("Color is:" + new Color(bi.getRGB(i,
j)));
throw new RuntimeException("Test failed.");
}
}
}
g2d.dispose();
}
}
}