diff --git a/modules/javafx.controls/.classpath b/modules/javafx.controls/.classpath index 0bdee41918a..a73c53af332 100644 --- a/modules/javafx.controls/.classpath +++ b/modules/javafx.controls/.classpath @@ -11,6 +11,7 @@ + diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/skin/Utils.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/skin/Utils.java index cf84677486a..01c9f510c91 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/skin/Utils.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/skin/Utils.java @@ -86,15 +86,16 @@ public class Utils { private static final TextBoundsType DEFAULT_BOUNDS_TYPE = textInstance.getBoundsType(); private static final AtomicBoolean helperGuard = new AtomicBoolean(false); - /* Using TextLayout directly for simple text measurement. + /** + * Using TextLayout directly for a simple text measurement. * Instead of restoring the TextLayout attributes to default values * (each renders the TextLayout unable to efficiently cache layout data). * It always sets all the attributes pertinent to calculation being performed. * Note that lineSpacing and boundsType are important when computing the height * but irrelevant when computing the width. - * - * Note: This code assumes that TextBoundsType#VISUAL is never used by controls. - * */ + *
+ * Note: This code assumes that the callers never use TextBoundsType#VISUAL. + */ private static final TextLayout layoutInstance = Toolkit.getToolkit().getTextLayoutFactory().createLayout(); private static final AtomicBoolean layoutGuard = new AtomicBoolean(false); @@ -167,6 +168,8 @@ public static double computeTextWidth(Font font, String text, double wrappingWid try { layout.setContent(text != null ? text : "", FontHelper.getNativeFont(font)); layout.setWrapWidth((float)wrappingWidth); + layout.setLineSpacing(0); + layout.setBoundsType(TextLayout.BOUNDS_CENTER); return layout.getBounds().getWidth(); } finally { release(layout); diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/StubFontContractTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/StubFontContractTest.java deleted file mode 100644 index 6897fcdfcc1..00000000000 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/StubFontContractTest.java +++ /dev/null @@ -1,197 +0,0 @@ -package test.javafx.scene.control.skin; - -import com.sun.javafx.scene.control.skin.Utils; -import javafx.scene.control.Label; -import javafx.scene.text.Font; -import javafx.scene.text.FontPosture; -import javafx.scene.text.FontWeight; -import org.junit.jupiter.api.Test; -import test.com.sun.javafx.scene.control.infrastructure.StageLoader; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * This is a test for fonts, font loading and their computed sizes. - * Note that while we only test the stub font loading here, we still want to verify some basic rules that apply - * for all headless tests. Some rules are somewhat derived from the real font loading in JavaFX. - *

- * See the javadoc for every method to get more details. - * @see test.com.sun.javafx.pgstub.StubFontLoader - * @see test.com.sun.javafx.pgstub.StubTextLayout - */ -class StubFontContractTest { - - /** - * An unknown font will fall back to the system font. Note that this is the same behaviour in JavaFX. - */ - @Test - public void testUnknownFont() { - Font font = new Font("bla", 10); - - assertEquals("bla", font.getName()); - assertEquals("System", font.getFamily()); - assertEquals("Regular", font.getStyle()); - assertEquals(10, font.getSize()); - - assertEquals(100, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(10, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - - /** - * Even if the font name is equal with a family name, we do not know the font by name. - */ - @Test - public void testFamilyAsFontName() { - Font font = new Font("System", 10); - - assertEquals("System", font.getName()); - assertEquals("System", font.getFamily()); - assertEquals("Regular", font.getStyle()); - assertEquals(10, font.getSize()); - - assertEquals(100, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(10, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - - /** - * System Regular is the default font for headless testing but also for JavaFX itself. - * 12 is the default font size for headless testing. This is different from JavaFX, as size from the OS will be used. - */ - @Test - public void testDefaultFont() { - Label lbl = new Label(); - Font defaultFont = lbl.getFont(); - - assertEquals("System Regular", defaultFont.getName()); - assertEquals("System", defaultFont.getFamily()); - assertEquals("Regular", defaultFont.getStyle()); - assertEquals(12, defaultFont.getSize()); - - assertEquals(120, Utils.computeTextWidth(defaultFont, "ABCDEFGHIJ", -1)); - assertEquals(12, Utils.computeTextHeight(defaultFont, "ABCDEFGHIJ", 0, null)); - } - - @Test - public void testDefaultFontSet() { - Label lbl = new Label(); - Font font = lbl.getFont(); - - assertEquals("System Regular", font.getName()); - assertEquals(12, font.getSize()); - - lbl.setFont(Font.font("system", FontWeight.BOLD, 20)); - - font = lbl.getFont(); - assertEquals("System Bold", font.getName()); - assertEquals(20, font.getSize()); - - assertEquals(210, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(20, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - - @Test - public void testDefaultFontCssSet() { - Label lbl = new Label(); - Font font = lbl.getFont(); - - assertEquals("System Regular", font.getName()); - assertEquals(12, font.getSize()); - - StageLoader stageLoader = new StageLoader(lbl); - - lbl.setStyle("-fx-font: bold 20px System;"); - lbl.applyCss(); - - stageLoader.dispose(); - - font = lbl.getFont(); - assertEquals("System Bold", font.getName()); - assertEquals(20, font.getSize()); - - assertEquals(210, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(20, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - - /** - * System Regular is a font that is available for testing. - */ - @Test - public void testFontByName() { - Font font = new Font("System Regular", 11); - - assertEquals(110, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(11, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - - /** - * The System family is available for testing. - */ - @Test - public void testFontByFamily() { - Font font = Font.font("System", 11); - - assertEquals("Regular", font.getStyle()); - assertEquals(110, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(11, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - - /** - * The system font can be loaded with a normal weight. - */ - @Test - public void testFontByFamilyNormal() { - Font font = Font.font("System", FontWeight.NORMAL, 11); - - assertEquals("Regular", font.getStyle()); - assertEquals(110, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(11, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - - /** - * The system font can also be bold. In that case it is a bit wider than the normal one. - */ - @Test - public void testFontByFamilyBold() { - Font font = Font.font("System", FontWeight.BOLD, 13); - - assertEquals("Bold", font.getStyle()); - assertEquals(140, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(13, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - - /** - * The system font can also be italic. - */ - @Test - public void testFontByFamilyItalic() { - Font font = Font.font("System", FontPosture.ITALIC, 11); - - assertEquals("Italic", font.getStyle()); - assertEquals(110, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(11, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - - /** - * The system font can also be bold and Italic. In that case it is a bit wider than the normal one. - */ - @Test - public void testFontByFamilyBoldItalic() { - Font font = Font.font("System", FontWeight.BOLD, FontPosture.ITALIC, 13); - - assertEquals("Bold Italic", font.getStyle()); - assertEquals(140, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(13, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - - /** - * Amble is the other font we support for headless testing. - */ - @Test - public void testAmbleFont() { - Font font = Font.font("Amble", 11); - - assertEquals(110, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); - assertEquals(11, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); - } - -} diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TextLayoutUtilsContractTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TextLayoutUtilsContractTest.java new file mode 100644 index 00000000000..2d7f2166322 --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TextLayoutUtilsContractTest.java @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2024, 2026, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package test.javafx.scene.control.skin; + +import com.sun.javafx.scene.control.skin.Utils; +import com.sun.javafx.scene.text.FontHelper; +import com.sun.javafx.scene.text.TextLayout; +import com.sun.javafx.text.GlyphLayout; +import javafx.scene.control.Label; +import javafx.scene.text.Font; +import javafx.scene.text.FontPosture; +import javafx.scene.text.FontWeight; +import org.junit.jupiter.api.Test; +import test.com.sun.javafx.pgstub.StubTextLayout; +import test.com.sun.javafx.scene.control.infrastructure.StageLoader; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * This class tests the contract for fonts, text, and computed sizes. + * While we only use the stub font and text framework here, we can still verify some rules + * that apply for all headless tests. + * Most rules are derived from the real font loading and text rendering in JavaFX. + *

+ * See the Javadoc for every method to get more details. + * @see test.com.sun.javafx.pgstub.StubFontLoader + * @see test.com.sun.javafx.pgstub.StubTextLayout + */ +class TextLayoutUtilsContractTest { + + /** + * An unknown font will fall back to the system font. Note that this is the same behavior in JavaFX. + */ + @Test + public void testUnknownFont() { + Font font = new Font("bla", 10); + + assertEquals("bla", font.getName()); + assertEquals("System", font.getFamily()); + assertEquals("Regular", font.getStyle()); + assertEquals(10, font.getSize()); + + assertEquals(100, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(10, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + /** + * Even if the font name is equal with a family name, we do not know the font by name. + */ + @Test + public void testFamilyAsFontName() { + Font font = new Font("System", 10); + + assertEquals("System", font.getName()); + assertEquals("System", font.getFamily()); + assertEquals("Regular", font.getStyle()); + assertEquals(10, font.getSize()); + + assertEquals(100, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(10, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + /** + * System Regular is the default font for headless testing but also for JavaFX itself. + * 12 is the default font size for headless testing. + * This is different from JavaFX, where the default size of the OS will be used. + */ + @Test + public void testDefaultFont() { + Label lbl = new Label(); + Font defaultFont = lbl.getFont(); + + assertEquals("System Regular", defaultFont.getName()); + assertEquals("System", defaultFont.getFamily()); + assertEquals("Regular", defaultFont.getStyle()); + assertEquals(12, defaultFont.getSize()); + + assertEquals(120, Utils.computeTextWidth(defaultFont, "ABCDEFGHIJ", -1)); + assertEquals(12, Utils.computeTextHeight(defaultFont, "ABCDEFGHIJ", 0, null)); + } + + @Test + public void testDefaultFontSet() { + Label lbl = new Label(); + Font font = lbl.getFont(); + + assertEquals("System Regular", font.getName()); + assertEquals(12, font.getSize()); + + lbl.setFont(Font.font("system", FontWeight.BOLD, 20)); + + font = lbl.getFont(); + assertEquals("System Bold", font.getName()); + assertEquals(20, font.getSize()); + + assertEquals(210, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(20, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + @Test + public void testDefaultFontCssSet() { + Label lbl = new Label(); + Font font = lbl.getFont(); + + assertEquals("System Regular", font.getName()); + assertEquals(12, font.getSize()); + + StageLoader stageLoader = new StageLoader(lbl); + + lbl.setStyle("-fx-font: bold 20px System;"); + lbl.applyCss(); + + stageLoader.dispose(); + + font = lbl.getFont(); + assertEquals("System Bold", font.getName()); + assertEquals(20, font.getSize()); + + assertEquals(210, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(20, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + /** + * System Regular is a font that is available for testing. + */ + @Test + public void testFontByName() { + Font font = new Font("System Regular", 11); + + assertEquals(110, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(11, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + /** + * The System family is available for testing. + */ + @Test + public void testFontByFamily() { + Font font = Font.font("System", 11); + + assertEquals("Regular", font.getStyle()); + assertEquals(110, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(11, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + /** + * The system font can be loaded with a normal weight. + */ + @Test + public void testFontByFamilyNormal() { + Font font = Font.font("System", FontWeight.NORMAL, 11); + + assertEquals("Regular", font.getStyle()); + assertEquals(110, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(11, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + /** + * The system font can also be bold. In that case it is a bit wider than the normal one. + */ + @Test + public void testFontByFamilyBold() { + Font font = Font.font("System", FontWeight.BOLD, 13); + + assertEquals("Bold", font.getStyle()); + assertEquals(140, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(13, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + /** + * The system font can also be italic. + */ + @Test + public void testFontByFamilyItalic() { + Font font = Font.font("System", FontPosture.ITALIC, 11); + + assertEquals("Italic", font.getStyle()); + assertEquals(110, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(11, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + /** + * The system font can also be bold and Italic. In that case it is a bit wider than the normal one. + */ + @Test + public void testFontByFamilyBoldItalic() { + Font font = Font.font("System", FontWeight.BOLD, FontPosture.ITALIC, 13); + + assertEquals("Bold Italic", font.getStyle()); + assertEquals(140, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(13, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + /** + * Amble is the other font we support for headless testing. + */ + @Test + public void testAmbleFont() { + Font font = Font.font("Amble", 11); + + assertEquals(110, Utils.computeTextWidth(font, "ABCDEFGHIJ", -1)); + assertEquals(11, Utils.computeTextHeight(font, "ABCDEFGHIJ", 0, null)); + } + + /** + * Tests that the text layout will cache layout results when the input parameters are the same. + * We closely simulate the measurement calls here that are usually done by {@link Utils}. + *
+ * As the stub text layout is very close to that of the actual JavaFX text layout toolchain, + * we can test the actual caching logic by verifying that no glyph layout is created when there is a cache hit. + */ + @Test + public void testTextLayoutIsCached() { + Font font = Font.font("Amble", 11); + Object nativeFont = FontHelper.getNativeFont(font); + + AtomicInteger glyphLayoutCreationCounter = new AtomicInteger(); + + StubTextLayout textLayout = new StubTextLayout(256) { + @Override + protected GlyphLayout glyphLayout() { + glyphLayoutCreationCounter.addAndGet(1); + return super.glyphLayout(); + } + }; + setTextLayout(textLayout, "TEXT", nativeFont, TextLayout.BOUNDS_CENTER); + assertEquals(1, glyphLayoutCreationCounter.get()); + + setTextLayout(textLayout, "ANOTHER", nativeFont, TextLayout.BOUNDS_CENTER); + assertEquals(2, glyphLayoutCreationCounter.get()); + + // We set the same text again, so we expect a cache hit and no glyph layout creation. + setTextLayout(textLayout, "TEXT", nativeFont, TextLayout.BOUNDS_CENTER); + assertEquals(2, glyphLayoutCreationCounter.get()); + } + + /** + * Tests that the text layout will not cache layout results when the bounds-type is not BOUNDS_CENTER. + * We closely simulate the measurement calls here that are usually done by {@link Utils}. + *
+ * As the stub text layout is very close to that of the actual JavaFX text layout toolchain, + * we can test the actual caching logic by verifying that no glyph layout is created when there is a cache hit. + */ + @Test + public void testTextLayoutWithWrongBoundsTypeIsNotCached() { + Font font = Font.font("Amble", 11); + Object nativeFont = FontHelper.getNativeFont(font); + + AtomicInteger glyphLayoutCreationCounter = new AtomicInteger(); + + StubTextLayout textLayout = new StubTextLayout(256) { + @Override + protected GlyphLayout glyphLayout() { + glyphLayoutCreationCounter.addAndGet(1); + return super.glyphLayout(); + } + }; + setTextLayout(textLayout, "TEXT", nativeFont, 0); + assertEquals(1, glyphLayoutCreationCounter.get()); + + setTextLayout(textLayout, "ANOTHER", nativeFont, 0); + assertEquals(2, glyphLayoutCreationCounter.get()); + + // We set the same text again, but still expect a cache miss due to the set bounds-type. + setTextLayout(textLayout, "TEXT", nativeFont, 0); + assertEquals(3, glyphLayoutCreationCounter.get()); + } + + /** + * Tests that the text layout will be cached and reused when changing the bounds-type from BOUNDS_CENTER to 0. + * In this case, the runs will be reused. + * We closely simulate the measurement calls here that are usually done by {@link Utils}. + *
+ * As the stub text layout is very close to that of the actual JavaFX text layout toolchain, + * we can test the actual caching logic by verifying that no glyph layout is created when there is a cache hit. + */ + @Test + public void testTextLayoutBoundsTypeChangeFromCenter() { + Font font = Font.font("Amble", 11); + Object nativeFont = FontHelper.getNativeFont(font); + + AtomicInteger glyphLayoutCreationCounter = new AtomicInteger(); + + StubTextLayout textLayout = new StubTextLayout(256) { + @Override + protected GlyphLayout glyphLayout() { + glyphLayoutCreationCounter.addAndGet(1); + return super.glyphLayout(); + } + }; + setTextLayout(textLayout, "TEXT", nativeFont, TextLayout.BOUNDS_CENTER); + assertEquals(1, glyphLayoutCreationCounter.get()); + + setTextLayout(textLayout, "TEXT", nativeFont, 0); + assertEquals(1, glyphLayoutCreationCounter.get()); + + setTextLayout(textLayout, "TEXT", nativeFont, TextLayout.BOUNDS_CENTER); + assertEquals(1, glyphLayoutCreationCounter.get()); + } + + /** + * Tests that the text layout will not be cached and reused when changing the bounds-type from 0 to BOUNDS_CENTER. + * In this case, there will be no cache entry. + * We closely simulate the measurement calls here that are usually done by {@link Utils}. + *
+ * As the stub text layout is very close to that of the actual JavaFX text layout toolchain, + * we can test the actual caching logic by verifying that no glyph layout is created when there is a cache hit. + */ + @Test + public void testTextLayoutBoundsTypeChangeFrom0() { + Font font = Font.font("Amble", 11); + Object nativeFont = FontHelper.getNativeFont(font); + + AtomicInteger glyphLayoutCreationCounter = new AtomicInteger(); + + StubTextLayout textLayout = new StubTextLayout(256) { + @Override + protected GlyphLayout glyphLayout() { + glyphLayoutCreationCounter.addAndGet(1); + return super.glyphLayout(); + } + }; + setTextLayout(textLayout, "TEXT", nativeFont, 0); + assertEquals(1, glyphLayoutCreationCounter.get()); + + setTextLayout(textLayout, "TEXT", nativeFont, TextLayout.BOUNDS_CENTER); + assertEquals(2, glyphLayoutCreationCounter.get()); + + setTextLayout(textLayout, "TEXT", nativeFont, 0); + assertEquals(2, glyphLayoutCreationCounter.get()); + } + + private void setTextLayout(StubTextLayout textLayout, String text, Object nativeFont, int boundsType) { + textLayout.setContent(text, nativeFont); + textLayout.setWrapWidth(0); + textLayout.setLineSpacing(0); + textLayout.setBoundsType(boundsType); + // We need to call this to ensure the layout run. + assertNotNull(textLayout.getBounds()); + } + +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismTextLayout.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismTextLayout.java index e4c219e13d8..2e1666d9cd2 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismTextLayout.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismTextLayout.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -715,10 +715,20 @@ private int getLineIndex(float y) { return index; } - private boolean copyCache() { + /** + * Decides whether the text layout is worth caching and/or if we can reuse it completely or only the runs. + * A text layout is worth caching and to be reused completely + * when the text layout uses the (simple) default settings. + *
+ * If the text layout was cached, and we now have a text layout with the same text and font + * but settings that differ from the default, we can reuse the runs as they are structurally the same. + * However, the metrics need to be (re)calculated, so we cannot use more from the cache. + * + * @return true, if only the runs can be reused + */ + private boolean onlyReuseRuns() { int align = flags & ALIGN_MASK; int boundsType = flags & BOUNDS_MASK; - /* Caching for boundsType == Center, bias towards Modena */ return wrapWidth != 0 || align != ALIGN_LEFT || boundsType == 0 || isMirrored(); } @@ -734,7 +744,7 @@ private void initCache() { } } if (layoutCache != null) { - if (copyCache()) { + if (onlyReuseRuns()) { /* This instance has some property that requires it to * build its own lines (i.e. wrapping width). Thus, only use * the runs from the cache (and it needs to make a copy @@ -1477,7 +1487,7 @@ private void layout() { if (layoutCache != null) { - if (cacheKey != null && !layoutCache.valid && !copyCache()) { + if (cacheKey != null && !layoutCache.valid && !onlyReuseRuns()) { /* After layoutCache is added to the stringCache it can be * accessed by multiple threads. All the data in it must * be immutable. See copyCache() for the cases where the entire diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubFontResource.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubFontResource.java index c46c5a33b61..b11dededccc 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubFontResource.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubFontResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -27,6 +27,7 @@ import java.lang.ref.WeakReference; import java.util.Locale; import java.util.Map; +import java.util.Objects; import javafx.scene.text.Font; import com.sun.javafx.font.CharToGlyphMapper; import com.sun.javafx.font.FontResource; @@ -98,7 +99,7 @@ public boolean isBold() { String name = font.getStyle(); bold = name.toLowerCase(Locale.ROOT).contains("bold"); } - return bold.booleanValue(); + return bold; } @Override @@ -189,4 +190,17 @@ public int getGlyphCode(int charCode) { } }; } + + @Override + public boolean equals(Object o) { + if (!(o instanceof StubFontResource that)) { + return false; + } + return Objects.equals(font, that.font) && Objects.equals(bold, that.bold); + } + + @Override + public int hashCode() { + return Objects.hash(font, bold); + } } diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubFontStrike.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubFontStrike.java index 62c60f2e91b..297028f191d 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubFontStrike.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubFontStrike.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -36,16 +36,7 @@ /** * */ -public class StubFontStrike implements FontStrike { - private final FontResource fontResource; - private final float size; - private final BaseTransform transform; - - public StubFontStrike(FontResource r, float size, BaseTransform t) { - this.fontResource = r; - this.size = size; - this.transform = t; - } +public record StubFontStrike(FontResource fontResource, float size, BaseTransform transform) implements FontStrike { @Override public FontResource getFontResource() { diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubTextLayout.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubTextLayout.java index 113c508e4f1..5827eee227d 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubTextLayout.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubTextLayout.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -37,6 +37,10 @@ public StubTextLayout() { super(0); } + public StubTextLayout(int maxCacheSize) { + super(maxCacheSize); + } + @Override protected GlyphLayout glyphLayout() { return new StubGlyphLayout();