JDK-8224109 : Text spaced incorrectly by drawString under rotation with fractional metrics
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 11,12,13,14,15
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2019-05-16
  • Updated: 2020-06-01
  • Resolved: 2020-01-17
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 13 JDK 15
11.0.8-oracleFixed 13.0.4Fixed 15 b07Fixed
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Description
ADDITIONAL SYSTEM INFORMATION :
Windows 7 - JDK 11 & JDK 12

A DESCRIPTION OF THE PROBLEM :
When the current transform has different x and y scales, rendering text at different angles the glyph spacing is proportional to the angle. This is apparent in Java 11, but was not this way in Java 6, 7 & 8.

REGRESSION : Last worked in version 8u202

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
The following will produce a png file with some text which shows the differences when run on Java 8 vs Java 11 or later.

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.RenderingHints;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.AttributedString;
import java.util.Map;

import javax.imageio.ImageIO;
public class TransformedText_JDK8 {

    private static BufferedImage awtImage;
    private static Graphics2D buffer_g2d;
    private static Font originalFont;
    
    private static void initaliseGraphics()
    {
    	awtImage = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_RGB);
        buffer_g2d = (Graphics2D) awtImage.getGraphics();
        
        buffer_g2d.setBackground(Color.WHITE);
        buffer_g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_DEFAULT);

        buffer_g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        buffer_g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        buffer_g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        buffer_g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        buffer_g2d.setColor(Color.WHITE);
        buffer_g2d.fillRect(10, 10, 980, 980);
        
        buffer_g2d.setColor(Color.BLACK);
        
        originalFont = new Font("Segoe UI", Font.PLAIN, 30);
    }
    
    private static void renderHeader()
    {
        AffineTransform cachedAt = buffer_g2d.getTransform();
        AffineTransform localAt = (AffineTransform) cachedAt.clone();
        
        localAt.translate(100, 100);
        System.out.println(System.getProperty("java.version"));

        AttributedString header1 = new AttributedString("java.version, " + System.getProperty("java.version"));

        header1.addAttribute(TextAttribute.FONT, originalFont);
        header1.addAttribute(TextAttribute.KERNING, TextAttribute.KERNING_ON);
        buffer_g2d.setTransform(localAt);
        buffer_g2d.drawString(header1.getIterator(), 0, 0);
        
        buffer_g2d.setTransform(cachedAt);
    }
    
    private static void renderTransformedText()
    {
        AffineTransform cachedAt = buffer_g2d.getTransform();;
        AffineTransform localAt;
        
        AttributedString test1 = new AttributedString("      Test String");
        test1.addAttribute(TextAttribute.FONT, originalFont);
        test1.addAttribute(TextAttribute.KERNING, TextAttribute.KERNING_ON);
        
        for (int i = 0; i <= 12; i++) {
            localAt = (AffineTransform) cachedAt.clone();
            localAt.translate(500, 500);

            localAt.rotate(Math.toRadians(i * 30));
            localAt.scale(1.0, 2.0);
            
            buffer_g2d.setTransform(localAt);
            buffer_g2d.drawString(test1.getIterator(), 0, 0);

            buffer_g2d.setTransform(cachedAt);
        }
    }
    
    private static void savePng() 
    {
        try {
            ImageIO.write(awtImage, "png",new File("c:\\temp\\FontTest02.png"));
        } catch (IOException e) {
            // TODO Auto-generated catch block
        }
    }
    
    public static void main(String[] a) {
        initaliseGraphics();
        renderHeader();
        renderTransformedText();
        savePng();
    }
}


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Each of the 'Test String' renderings should have the same glyph spacing - when the example is run on Java 8 the rendering is correct.
ACTUAL -
When run on Java 11 or later the strings become progressively stretched as they are rotated from the horizontal.

---------- BEGIN SOURCE ----------
// See  the example code given in the Steps to Reproduce
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
None found

FREQUENCY : always



Comments
Fix version (13u) Requesting backport to 13u for parity with 11u, applies cleanly.
28-05-2020

Fix Request jdk11u release is affected by this bug. Patch applies cleanly.
03-04-2020

URL: https://hg.openjdk.java.net/jdk/jdk/rev/f446d8919043 User: psadhukhan Date: 2020-01-22 08:47:51 +0000
22-01-2020

URL: https://hg.openjdk.java.net/jdk/client/rev/f446d8919043 User: prr Date: 2020-01-17 20:21:18 +0000
17-01-2020

Whilst testing the vast number of text rendering APIs and scenarios I found a problem in rotating a laid out glyphvector. It is un-related to fractional metrics or freetype and is reproducible from JDK 9 since harfbuzz was integrated. I filed https://bugs.openjdk.java.net/browse/JDK-8236451
20-12-2019

I think I see the problem(s). The sign of the y advance is wrong in the fractional metrics path. Also the wrong transform component is being used ifor the rotation.
17-12-2019

Reported with JDK 12.0.1, the text is not spaced correctly when rotated and the scale transform x != y When verified with respective version could confirm the result. Result: ======== 8u212: OK 9: OK 10.0.2: OK 11: Fail 12.0.1: Fail 13 ea b20: Fail This is a regression introduced in JDK 11. See attached screenshot as reference (JDK 8,9,10 versus JDK 11) To verify, run the attached test case with respective JDK version.
17-05-2019