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
--- a/jdk/src/macosx/classes/sun/font/CStrike.java Thu Jul 25 17:14:39 2013 +0400
+++ b/jdk/src/macosx/classes/sun/font/CStrike.java Fri Jul 26 21:18:42 2013 +0400
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -31,7 +31,7 @@
import sun.awt.SunHints;
-public class CStrike extends FontStrike {
+public final class CStrike extends FontStrike {
// Creates the native strike
private static native long createNativeStrikePtr(long nativeFontPtr,
@@ -68,10 +68,10 @@
Rectangle2D.Float result,
double x, double y);
- private CFont nativeFont;
+ private final CFont nativeFont;
private AffineTransform invDevTx;
- private GlyphInfoCache glyphInfoCache;
- private GlyphAdvanceCache glyphAdvanceCache;
+ private final GlyphInfoCache glyphInfoCache;
+ private final GlyphAdvanceCache glyphAdvanceCache;
private long nativeStrikePtr;
CStrike(final CFont font, final FontStrikeDesc inDesc) {
@@ -84,11 +84,11 @@
// Normally the device transform should be the identity transform
// for screen operations. The device transform only becomes
// 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()) {
try {
invDevTx = inDesc.devTx.createInverse();
- } catch (NoninvertibleTransformException e) {
+ } catch (NoninvertibleTransformException ignored) {
// ignored, since device transforms should not be that
// complicated, and if they are - there is nothing we can do,
// so we won't worry about it.
@@ -134,15 +134,13 @@
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() {
return nativeFont.getNumGlyphs();
}
+ @Override
StrikeMetrics getFontMetrics() {
if (strikeMetrics == null) {
StrikeMetrics metrics = getFontMetrics(getNativeStrikePtr());
@@ -155,74 +153,24 @@
return strikeMetrics;
}
- float getGlyphAdvance(int glyphCode) {
- return getScaledAdvanceForAdvance(getCachedNativeGlyphAdvance(glyphCode));
- }
-
- float getCodePointAdvance(int cp) {
- float advance = getCachedNativeGlyphAdvance(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
- private float getScaledAdvanceForAdvance(float advance) {
- if (invDevTx != null) {
- advance *= invDevTx.getScaleX();
- }
- advance *= desc.glyphTx.getScaleX();
- return useFractionalMetrics() ? advance : Math.round(advance);
+ @Override
+ float getGlyphAdvance(final int glyphCode) {
+ return getCachedNativeGlyphAdvance(glyphCode);
}
- Point2D.Float getCharMetrics(char ch) {
- return getScaledPointForAdvance(getCachedNativeGlyphAdvance(nativeFont.getMapper().charToGlyph(ch)));
- }
-
- Point2D.Float getGlyphMetrics(int glyphCode) {
- return getScaledPointForAdvance(getCachedNativeGlyphAdvance(glyphCode));
+ @Override
+ float getCodePointAdvance(final int cp) {
+ return getGlyphAdvance(nativeFont.getMapper().charToGlyph(cp));
}
- // 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;
+ @Override
+ Point2D.Float getCharMetrics(final char ch) {
+ return getGlyphMetrics(nativeFont.getMapper().charToGlyph(ch));
}
- 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;
+ @Override
+ Point2D.Float getGlyphMetrics(final int glyphCode) {
+ return new Point2D.Float(getGlyphAdvance(glyphCode), 0.0f);
}
Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) {
@@ -414,9 +362,7 @@
private SparseBitShiftingTwoLayerArray secondLayerCache;
private HashMap<Integer, Long> generalCache;
- public GlyphInfoCache(final Font2D nativeFont,
- final FontStrikeDesc desc)
- {
+ GlyphInfoCache(final Font2D nativeFont, final FontStrikeDesc desc) {
super(nativeFont, desc);
firstLayerCache = new long[FIRST_LAYER_SIZE];
}
@@ -527,7 +473,7 @@
final int shift;
final int secondLayerLength;
- public SparseBitShiftingTwoLayerArray(final int size, final int shift) {
+ SparseBitShiftingTwoLayerArray(final int size, final int shift) {
this.shift = shift;
this.cache = new long[1 << shift][];
this.secondLayerLength = size >> shift;
@@ -559,6 +505,12 @@
private SparseBitShiftingTwoLayerArray secondLayerCache;
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) {
if (index < 0) {
if (-index < SECOND_LAYER_SIZE) {
@@ -609,9 +561,7 @@
final int shift;
final int secondLayerLength;
- public SparseBitShiftingTwoLayerArray(final int size,
- final int shift)
- {
+ SparseBitShiftingTwoLayerArray(final int size, final int shift) {
this.shift = shift;
this.cache = new float[1 << shift][];
this.secondLayerLength = size >> shift;
--- a/jdk/src/macosx/native/sun/font/AWTStrike.h Thu Jul 25 17:14:39 2013 +0400
+++ b/jdk/src/macosx/native/sun/font/AWTStrike.h Fri Jul 26 21:18:42 2013 +0400
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -31,11 +31,12 @@
@interface AWTStrike : NSObject {
@public
AWTFont * fAWTFont;
- CGFloat fSize;
+ CGFloat fSize;
JRSFontRenderingStyle fStyle;
- jint fAAStyle;
+ jint fAAStyle;
CGAffineTransform fTx;
+ CGAffineTransform fDevTx;
CGAffineTransform fAltTx; // alternate strike tx used for Sun2D
CGAffineTransform fFontTx;
}
--- a/jdk/src/macosx/native/sun/font/AWTStrike.m Thu Jul 25 17:14:39 2013 +0400
+++ b/jdk/src/macosx/native/sun/font/AWTStrike.m Fri Jul 26 21:18:42 2013 +0400
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -65,6 +65,7 @@
invDevTx.b *= -1;
invDevTx.c *= -1;
fFontTx = CGAffineTransformConcat(CGAffineTransformConcat(tx, invDevTx), sInverseTX);
+ fDevTx = CGAffineTransformInvert(invDevTx);
// the "font size" is the square root of the determinant of the matrix
fSize = sqrt(abs(fFontTx.a * fFontTx.d - fFontTx.b * fFontTx.c));
@@ -148,7 +149,8 @@
{
CGSize advance;
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
// to indicate we should use CoreText to substitute the character
@@ -156,6 +158,10 @@
const CTFontRef fallback = CTS_CopyCTFallbackFontAndGlyphForJavaGlyphCode(awtFont, glyphCode, &glyph);
CTFontGetAdvancesForGlyphs(fallback, kCTFontDefaultOrientation, &glyph, &advance, 1);
CFRelease(fallback);
+ advance = CGSizeApplyAffineTransform(advance, awtStrike->fFontTx);
+ if (!JRSFontStyleUsesFractionalMetrics(awtStrike->fStyle)) {
+ advance.width = round(advance.width);
+ }
JNF_COCOA_EXIT(env);
return advance.width;
--- a/jdk/src/macosx/native/sun/font/CGGlyphImages.m Thu Jul 25 17:14:39 2013 +0400
+++ b/jdk/src/macosx/native/sun/font/CGGlyphImages.m Fri Jul 26 21:18:42 2013 +0400
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -455,6 +455,7 @@
#define CGGI_GLYPH_BBOX_PADDING 2.0f
static inline GlyphInfo *
CGGI_CreateNewGlyphInfoFrom(CGSize advance, CGRect bbox,
+ const AWTStrike *strike,
const CGGI_RenderingMode *mode)
{
size_t pixelSize = mode->glyphDescriptor->pixelSize;
@@ -477,6 +478,12 @@
width = 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
// create separate memory
@@ -564,10 +571,10 @@
JRSFontGetBoundingBoxesForGlyphsAndStyle(fallback, &tx, style, &glyph, 1, &bbox);
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
- 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
CGGI_SizeCanvas(canvas, info->width, info->height, mode->cgFontMode);
@@ -715,7 +722,7 @@
JRSFontRenderingStyle bboxCGMode = JRSFontAlignStyleForFractionalMeasurement(strike->fStyle);
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 maxHeight = 1;
@@ -732,7 +739,7 @@
CGSize advance = advances[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 (maxHeight < glyphInfo->height) maxHeight = glyphInfo->height;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Graphics2D/DrawString/DrawRotatedString.java Fri Jul 26 21:18:42 2013 +0400
@@ -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;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Graphics2D/IncorrectTextSize/IncorrectTextSize.java Fri Jul 26 21:18:42 2013 +0400
@@ -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();
+ }
+ }
+}