JRE-430 Font fallback sometimes doesn't work in Swing text components

port commit fc8003ad from JBR 9

port from JBR 11 to JBR 15 (cherry picked from commit 5b814d6b34)

cherry picked from commits b871188f44, 0a9f16dc90, 9cc82c39d9

(cherry picked from commit df8821d745)
This commit is contained in:
Dmitry Batrak
2018-12-24 16:10:55 +03:00
committed by jbrbot
parent 1f8197a23b
commit 6f2716bdd9
5 changed files with 169 additions and 35 deletions

View File

@@ -269,8 +269,8 @@ public class Font implements java.io.Serializable
font.font2DHandle = handle;
}
public void setCreatedFont(Font font) {
font.createdFont = true;
public void setWithFallback(Font font) {
font.withFallback = true;
}
public boolean isCreatedFont(Font font) {
@@ -457,6 +457,12 @@ public class Font implements java.io.Serializable
*/
private transient boolean createdFont = false;
/*
* Font with fallback components (using CompositeFont), its font2dHandle
* should be copied to derived fonts.
*/
private transient boolean withFallback;
/*
* This is true if the font transform is not identity. It
* is used to avoid unnecessary instantiation of an AffineTransform.
@@ -608,26 +614,26 @@ public class Font implements java.io.Serializable
/* This constructor is used by deriveFont when attributes is null */
private Font(String name, int style, float sizePts,
boolean created, Font2DHandle handle) {
boolean created, boolean withFallback,
Font2DHandle handle) {
this(name, style, sizePts);
this.createdFont = created;
this.withFallback = withFallback;
/* Fonts created from a stream will use the same font2D instance
* as the parent.
* One exception is that if the derived font is requested to be
* in a different style, then also check if its a CompositeFont
* and if so build a new CompositeFont from components of that style.
* CompositeFonts can only be marked as "created" if they are used
* to add fall backs to a physical font. And non-composites are
* always from "Font.createFont()" and shouldn't get this treatment.
* When a derived font is requested to be in a different style
* than a base font with fallback, then build a new CompositeFont
* from components of that style.
*/
if (created) {
if (handle.font2D instanceof CompositeFont &&
handle.font2D.getStyle() != style) {
if (withFallback) {
if (handle.font2D.getStyle() != style) {
FontManager fm = FontManagerFactory.getInstance();
this.font2DHandle = fm.getNewComposite(null, style, handle);
} else {
this.font2DHandle = handle;
}
} else if (created) {
this.font2DHandle = handle;
}
}
@@ -654,7 +660,7 @@ public class Font implements java.io.Serializable
* parent. They can be distinguished because the "created" argument
* will be "true". Since there is no way to recreate these fonts they
* need to have the handle to the underlying font2D passed in.
* "created" is also true when a special composite is referenced by the
* "withFallback" flag is set when a special composite is referenced by the
* handle for essentially the same reasons.
* But when deriving a font in these cases two particular attributes
* need special attention: family/face and style.
@@ -673,10 +679,12 @@ public class Font implements java.io.Serializable
* In these cases there is no need to interrogate "values".
*/
private Font(AttributeValues values, String oldName, int oldStyle,
boolean created, Font2DHandle handle) {
boolean created, boolean withFallback,
Font2DHandle handle) {
this.createdFont = created;
if (created) {
this.withFallback = withFallback;
if (created || withFallback) {
this.font2DHandle = handle;
String newName = null;
@@ -692,7 +700,7 @@ public class Font implements java.io.Serializable
if (values.getPosture() >= .2f) newStyle |= ITALIC;
if (oldStyle == newStyle) newStyle = -1;
}
if (handle.font2D instanceof CompositeFont) {
if (withFallback) {
if (newStyle != -1 || newName != null) {
FontManager fm = FontManagerFactory.getInstance();
this.font2DHandle =
@@ -700,6 +708,7 @@ public class Font implements java.io.Serializable
}
} else if (newName != null) {
this.createdFont = false;
this.withFallback = false;
this.font2DHandle = null;
}
}
@@ -742,6 +751,7 @@ public class Font implements java.io.Serializable
}
this.font2DHandle = font.font2DHandle;
this.createdFont = font.createdFont;
this.withFallback = font.withFallback;
}
/**
@@ -871,7 +881,8 @@ public class Font implements java.io.Serializable
values = font.getAttributeValues().clone();
values.merge(attributes, SECONDARY_MASK);
return new Font(values, font.name, font.style,
font.createdFont, font.font2DHandle);
font.createdFont, font.withFallback,
font.font2DHandle);
}
return new Font(attributes);
}
@@ -882,7 +893,8 @@ public class Font implements java.io.Serializable
AttributeValues values = font.getAttributeValues().clone();
values.merge(attributes, SECONDARY_MASK);
return new Font(values, font.name, font.style,
font.createdFont, font.font2DHandle);
font.createdFont, font.withFallback,
font.font2DHandle);
}
return font;
@@ -1844,6 +1856,7 @@ public class Font implements java.io.Serializable
nonIdentityTx == font.nonIdentityTx &&
hasLayoutAttributes == font.hasLayoutAttributes &&
pointSize == font.pointSize &&
withFallback == font.withFallback &&
name.equals(font.name)) {
/* 'values' is usually initialized lazily, except when
@@ -2077,13 +2090,15 @@ public class Font implements java.io.Serializable
*/
public Font deriveFont(int style, float size){
if (values == null) {
return new Font(name, style, size, createdFont, font2DHandle);
return new Font(name, style, size, createdFont, withFallback,
font2DHandle);
}
AttributeValues newValues = getAttributeValues().clone();
int oldStyle = (this.style != style) ? this.style : -1;
applyStyle(style, newValues);
newValues.setSize(size);
return new Font(newValues, null, oldStyle, createdFont, font2DHandle);
return new Font(newValues, null, oldStyle, createdFont, withFallback,
font2DHandle);
}
/**
@@ -2102,7 +2117,8 @@ public class Font implements java.io.Serializable
int oldStyle = (this.style != style) ? this.style : -1;
applyStyle(style, newValues);
applyTransform(trans, newValues);
return new Font(newValues, null, oldStyle, createdFont, font2DHandle);
return new Font(newValues, null, oldStyle, createdFont, withFallback,
font2DHandle);
}
/**
@@ -2114,11 +2130,13 @@ public class Font implements java.io.Serializable
*/
public Font deriveFont(float size){
if (values == null) {
return new Font(name, style, size, createdFont, font2DHandle);
return new Font(name, style, size, createdFont, withFallback,
font2DHandle);
}
AttributeValues newValues = getAttributeValues().clone();
newValues.setSize(size);
return new Font(newValues, null, -1, createdFont, font2DHandle);
return new Font(newValues, null, -1, createdFont, withFallback,
font2DHandle);
}
/**
@@ -2134,7 +2152,8 @@ public class Font implements java.io.Serializable
public Font deriveFont(AffineTransform trans){
AttributeValues newValues = getAttributeValues().clone();
applyTransform(trans, newValues);
return new Font(newValues, null, -1, createdFont, font2DHandle);
return new Font(newValues, null, -1, createdFont, withFallback,
font2DHandle);
}
/**
@@ -2146,12 +2165,14 @@ public class Font implements java.io.Serializable
*/
public Font deriveFont(int style){
if (values == null) {
return new Font(name, style, size, createdFont, font2DHandle);
return new Font(name, style, size, createdFont, withFallback,
font2DHandle);
}
AttributeValues newValues = getAttributeValues().clone();
int oldStyle = (this.style != style) ? this.style : -1;
applyStyle(style, newValues);
return new Font(newValues, null, oldStyle, createdFont, font2DHandle);
return new Font(newValues, null, oldStyle, createdFont, withFallback,
font2DHandle);
}
/**
@@ -2171,7 +2192,7 @@ public class Font implements java.io.Serializable
AttributeValues newValues = getAttributeValues().clone();
newValues.merge(attributes, RECOGNIZED_MASK);
return new Font(newValues, name, style, createdFont, font2DHandle);
return new Font(newValues, name, style, createdFont, withFallback, font2DHandle);
}
/**

View File

@@ -44,7 +44,7 @@ public abstract class FontAccess {
public abstract Font2D getFont2D(Font f);
public abstract void setFont2D(Font f, Font2DHandle h);
public abstract void setCreatedFont(Font f);
public abstract void setWithFallback(Font f);
public abstract boolean isCreatedFont(Font f);
public abstract FontPeer getFontPeer(Font f);
}

View File

@@ -428,10 +428,10 @@ public final class FontUtilities {
compMap.put(physicalFont, compFont);
}
FontAccess.getFontAccess().setFont2D(fuir, compFont.handle);
/* marking this as a created font is needed as only created fonts
* copy their creator's handles.
/* marking this as a font with fallback to make sure its
* handle is copied to derived fonts.
*/
FontAccess.getFontAccess().setCreatedFont(fuir);
FontAccess.getFontAccess().setWithFallback(fuir);
return fuir;
}

View File

@@ -722,14 +722,13 @@ public final class X11FontManager extends FcFontManager {
/* The name of the font will be that of the physical font in slot,
* but by setting the handle to that of the CompositeFont it
* renders as that CompositeFont.
* It also needs to be marked as a created font which is the
* current mechanism to signal that deriveFont etc must copy
* the handle from the original font.
* Font is marked as having fallback components to signal that
* deriveFont etc must copy the handle from the original font.
*/
FontUIResource fuir =
new FontUIResource(font2D.getFamilyName(null), style, size);
FontAccess.getFontAccess().setFont2D(fuir, font2D.handle);
FontAccess.getFontAccess().setCreatedFont(fuir);
FontAccess.getFontAccess().setWithFallback(fuir);
return fuir;
}
}

View File

@@ -0,0 +1,114 @@
/*
* 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
* @bug 8185261
* @summary Tests that font fallback works reliably in JEditorPane
* @key headful
*/
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
public class JEditorPaneFontFallback {
public static final char CHINESE_CHAR = '\u4e2d';
public static void main(String[] args) throws Exception {
String fontFamily = findSuitableFont();
if (fontFamily == null) {
System.out.println("No suitable fonts, test cannot be performed");
return;
}
System.out.println("Fount font: " + fontFamily);
BufferedImage img1 = renderJEditorPaneInSubprocess(fontFamily, false);
BufferedImage img2 = renderJEditorPaneInSubprocess(fontFamily, true);
if (!imagesAreEqual(img1, img2)) {
throw new RuntimeException("Unexpected rendering in JEditorPane");
}
}
private static String findSuitableFont() {
String[] familyNames = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
for (String familyName : familyNames) {
if (!familyName.contains("'") && !familyName.contains("<") && !familyName.contains("&")) {
Font font = new Font(familyName, Font.PLAIN, 1);
if (!font.canDisplay(CHINESE_CHAR)) return familyName;
}
}
return null;
}
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;
}
private static BufferedImage renderJEditorPaneInSubprocess(String fontFamilyName, boolean afterFontInfoCaching)
throws Exception {
String tmpFileName = "image.png";
int exitCode = Runtime.getRuntime().exec(new String[]{
System.getProperty("java.home") + File.separator + "bin" + File.separator + "java",
"-cp",
System.getProperty("test.classes", "."),
JEditorPaneRenderer.class.getName(),
fontFamilyName,
Boolean.toString(afterFontInfoCaching),
tmpFileName
}).waitFor();
if (exitCode != 0) {
throw new RuntimeException("Sub-process exited abnormally: " + exitCode);
}
return ImageIO.read(new File(tmpFileName));
}
}
class JEditorPaneRenderer {
private static final int FONT_SIZE = 12;
private static final int WIDTH = 20;
private static final int HEIGHT = 20;
public static void main(String[] args) throws Exception {
String fontFamily = args[0];
JEditorPane pane = new JEditorPane("text/html",
"<html><head><style>body {font-family:'" + fontFamily + "'; font-size:" + FONT_SIZE +
"pt;}</style></head><body>" + JEditorPaneFontFallback.CHINESE_CHAR + "</body></html>");
pane.setSize(WIDTH, HEIGHT);
if (Boolean.parseBoolean(args[1])) pane.getFontMetrics(new Font(fontFamily, Font.PLAIN, FONT_SIZE));
BufferedImage img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
pane.paint(g);
g.dispose();
ImageIO.write(img, "png", new File(args[2]));
}
}