mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2026-01-11 02:51:42 +01:00
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 commit5b814d6b34) cherry picked from commitsb871188f44,0a9f16dc90,9cc82c39d9(cherry picked from commitdf8821d745)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
114
test/jdk/javax/swing/JEditorPane/JEditorPaneFontFallback.java
Normal file
114
test/jdk/javax/swing/JEditorPane/JEditorPaneFontFallback.java
Normal 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]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user