From 285db28334f0b350d29a1a671843156343b682ed Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 8 Oct 2024 08:59:11 -0700 Subject: [PATCH 01/43] draft api --- .../com/sun/javafx/scene/text/TextLine.java | 6 +- .../com/sun/javafx/text/PrismLayoutInfo.java | 90 +++++++++++++++++++ .../java/javafx/scene/text/LayoutInfo.java | 78 ++++++++++++++++ .../src/main/java/javafx/scene/text/Text.java | 10 +++ .../main/java/javafx/scene/text/TextFlow.java | 28 ++++-- 5 files changed, 200 insertions(+), 12 deletions(-) create mode 100644 modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java create mode 100644 modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLine.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLine.java index 49d1a20d00a..06a195125a1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLine.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLine.java @@ -37,11 +37,11 @@ public interface TextLine { public GlyphList[] getRuns(); /** - * Returns metrics information about the line as follow: + * Returns metrics information about the line as follows: * * bounds().getWidth() - the width of the line. * The width for the line is sum of all run's width in the line, it is not - * affect by any wrapping width but it will include any changes caused by + * affected by any wrapping width but it will include any changes caused by * justification. * * bounds().getHeight() - the height of the line. @@ -73,7 +73,7 @@ public interface TextLine { public int getStart(); /** - * Returns the line length in character. + * Returns the line length in characters. */ public int getLength(); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java new file mode 100644 index 00000000000..e0d61526cc9 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024, 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 com.sun.javafx.text; + +import javafx.geometry.Rectangle2D; +import javafx.scene.text.LayoutInfo; +import com.sun.javafx.geom.RectBounds; +import com.sun.javafx.scene.text.TextLayout; + +/** + * Layout information as reported by PrismLayout. + */ +public final class PrismLayoutInfo implements LayoutInfo { + // TODO + // also available: + // public BaseBounds getBounds(); + private final TLine[] lines; + + public PrismLayoutInfo(TLine[] lines) { + this.lines = lines; + } + + public static LayoutInfo of(TextLayout la) { + com.sun.javafx.scene.text.TextLine[] ls = la.getLines(); + TLine[] lines = new TLine[ls.length]; + for (int i = 0; i < ls.length; i++) { + com.sun.javafx.scene.text.TextLine ln = ls[i]; + lines[i] = new TLine(ls[i]); + } + return new PrismLayoutInfo(lines); + } + + @Override + public int getTextLineCount() { + return lines.length; + } + + @Override + public int getTextLineStart(int ix) { + return lines[ix].start; + } + + @Override + public int getTextLineEnd(int ix) { + return lines[ix].end; + } + + @Override + public Rectangle2D getLineBounds(int ix) { + RectBounds r = lines[ix].bounds; + return new Rectangle2D(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); + } + + private static class TLine { + public int start; + public int end; + public RectBounds bounds; + + public TLine(com.sun.javafx.scene.text.TextLine t) { + this.start = t.getStart(); + this.end = t.getStart() + t.getLength(); + this.bounds = t.getBounds(); + // TODO also available: + // public float getLeftSideBearing(); // what is that?? + // public float getRightSideBearing(); + } + } +} diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java new file mode 100644 index 00000000000..aa79856479e --- /dev/null +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, 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 javafx.scene.text; + +import javafx.geometry.Rectangle2D; + +/** + * Represents an immutable snapshot of certain aspects of the text layout. + * + * @since 24 + */ +public interface LayoutInfo { + /** + * Provides the number of text lines in the layout. + * @return the number of text lines + */ + public int getTextLineCount(); + + /** + * Returns the start offset for the line at index {@code index}. + * @param index the line index + * @return the start offset + */ + public int getTextLineStart(int index); + + /** + * Returns the end offset for the line at index {@code index}. + * @param index the line index + * @return the end offset + */ + public int getTextLineEnd(int index); + + /** + * Returns metrics information about the line as follows: + *

+ * {@code minX} - the x origin of the line (relative to the layout). + * The x origin is defined by TextAlignment of the text layout, always zero + * for left-aligned text. + *

+ * {@code minY} - the ascent of the line (negative). + * The ascent of the line is the max ascent of all fonts in the line. + *

+ * {@code width} - the width of the line. + * The width for the line is sum of all the run widths in the line, it is not + * affect by the wrapping width but it will include any changes caused by + * justification. + *

+ * {@code height} - the height of the line. + * The height of the line is sum of the max ascent, max descent, and + * max line gap of all the fonts in the line. + * + * @param index the line index + * @return the line bounds + */ + public Rectangle2D getLineBounds(int index); +} diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java index 989411fca6c..1c4f6e7d6ad 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java @@ -46,6 +46,7 @@ import com.sun.javafx.sg.prism.NGShape; import com.sun.javafx.sg.prism.NGText; import com.sun.javafx.scene.text.FontHelper; +import com.sun.javafx.text.PrismLayoutInfo; import com.sun.javafx.text.TextRun; import com.sun.javafx.tk.Toolkit; import javafx.beans.DefaultProperty; @@ -2065,4 +2066,13 @@ public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... default: return super.queryAccessibleAttribute(attribute, parameters); } } + + /** + * Obtains the snapshot of the current text layout information. + * @return the layout information + * @since 24 + */ + public final LayoutInfo getLayoutInfo() { + return PrismLayoutInfo.of(getTextLayout()); + } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index b317342d081..9f5143b7e05 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -29,7 +29,16 @@ import java.util.Collections; import java.util.List; import javafx.beans.property.DoubleProperty; +import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; +import javafx.css.CssMetaData; +import javafx.css.Styleable; +import javafx.css.StyleableDoubleProperty; +import javafx.css.StyleableIntegerProperty; +import javafx.css.StyleableObjectProperty; +import javafx.css.StyleableProperty; +import javafx.css.converter.EnumConverter; +import javafx.css.converter.SizeConverter; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.NodeOrientation; @@ -40,11 +49,6 @@ import javafx.scene.Node; import javafx.scene.layout.Pane; import javafx.scene.shape.PathElement; -import javafx.css.StyleableDoubleProperty; -import javafx.css.StyleableObjectProperty; -import javafx.css.CssMetaData; -import javafx.css.converter.EnumConverter; -import javafx.css.converter.SizeConverter; import com.sun.javafx.geom.BaseBounds; import com.sun.javafx.geom.Point2D; import com.sun.javafx.geom.RectBounds; @@ -52,11 +56,8 @@ import com.sun.javafx.scene.text.TextLayout; import com.sun.javafx.scene.text.TextLayoutFactory; import com.sun.javafx.scene.text.TextSpan; +import com.sun.javafx.text.PrismLayoutInfo; import com.sun.javafx.tk.Toolkit; -import javafx.beans.property.IntegerProperty; -import javafx.css.Styleable; -import javafx.css.StyleableIntegerProperty; -import javafx.css.StyleableProperty; /** * A specialized layout for rich text. @@ -687,4 +688,13 @@ public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... default: return super.queryAccessibleAttribute(attribute, parameters); } } + + /** + * Obtains the snapshot of the current text layout information. + * @return the layout information + * @since 24 + */ + public final LayoutInfo getLayoutInfo() { + return PrismLayoutInfo.of(getTextLayout()); + } } From 43ac0e12ba011a0ae3c2df4a37a10e374b745baa Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 8 Oct 2024 11:11:16 -0700 Subject: [PATCH 02/43] bounds --- .../com/sun/javafx/text/PrismLayoutInfo.java | 81 ++++++++++++++----- .../java/javafx/scene/text/LayoutInfo.java | 29 +++++-- 2 files changed, 86 insertions(+), 24 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java index e0d61526cc9..8a82981b023 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java @@ -26,6 +26,7 @@ import javafx.geometry.Rectangle2D; import javafx.scene.text.LayoutInfo; +import com.sun.javafx.geom.BaseBounds; import com.sun.javafx.geom.RectBounds; import com.sun.javafx.scene.text.TextLayout; @@ -33,23 +34,30 @@ * Layout information as reported by PrismLayout. */ public final class PrismLayoutInfo implements LayoutInfo { - // TODO - // also available: - // public BaseBounds getBounds(); private final TLine[] lines; + private final Rectangle2D bounds; - public PrismLayoutInfo(TLine[] lines) { + public PrismLayoutInfo(TLine[] lines, Rectangle2D bounds) { this.lines = lines; + this.bounds = bounds; } - public static LayoutInfo of(TextLayout la) { - com.sun.javafx.scene.text.TextLine[] ls = la.getLines(); + public static LayoutInfo of(TextLayout layout) { + com.sun.javafx.scene.text.TextLine[] ls = layout.getLines(); TLine[] lines = new TLine[ls.length]; for (int i = 0; i < ls.length; i++) { com.sun.javafx.scene.text.TextLine ln = ls[i]; - lines[i] = new TLine(ls[i]); + lines[i] = TLine.of(ls[i]); } - return new PrismLayoutInfo(lines); + + BaseBounds b = layout.getBounds(); + Rectangle2D bounds = new Rectangle2D(b.getMinX(), b.getMinY(), b.getWidth(), b.getHeight()); + return new PrismLayoutInfo(lines, bounds); + } + + @Override + public Rectangle2D getBounds() { + return bounds; } @Override @@ -69,22 +77,59 @@ public int getTextLineEnd(int ix) { @Override public Rectangle2D getLineBounds(int ix) { - RectBounds r = lines[ix].bounds; - return new Rectangle2D(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); + return lines[ix].bounds; } - private static class TLine { - public int start; - public int end; - public RectBounds bounds; + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("PrismLayoutInfo{"); + sb.append("lines=["); + boolean sep = false; + for (TLine li : lines) { + if (sep) { + sb.append(","); + } else { + sep = true; + } + li.print(sb); + } + sb.append("]"); + sb.append(", bounds=").append(bounds); + sb.append("}"); + return sb.toString(); + } + + /** + * Contains a snapshot of the mutable TextLine. + */ + private static record TLine( + int start, + int end, + Rectangle2D bounds) { + + public static TLine of(com.sun.javafx.scene.text.TextLine t) { + int start = t.getStart(); + int end = t.getStart() + t.getLength(); + RectBounds r = t.getBounds(); + Rectangle2D bounds = new Rectangle2D(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); - public TLine(com.sun.javafx.scene.text.TextLine t) { - this.start = t.getStart(); - this.end = t.getStart() + t.getLength(); - this.bounds = t.getBounds(); + return new TLine( + start, + end, + bounds + ); // TODO also available: // public float getLeftSideBearing(); // what is that?? // public float getRightSideBearing(); + // TODO we could also capture the text runs information if needed + } + + void print(StringBuilder sb) { + sb.append("{start=").append(start); + sb.append(", end=").append(end); + sb.append(", bounds=").append(bounds); + sb.append("}"); } } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java index aa79856479e..abfcd0ca085 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java @@ -33,13 +33,27 @@ */ public interface LayoutInfo { /** - * Provides the number of text lines in the layout. + * Returns the logical bounds of the layout: + *

+ * + * @return the layout bounds + */ + public Rectangle2D getBounds(); + + /** + * Returns the number of text lines in the layout. * @return the number of text lines */ public int getTextLineCount(); /** * Returns the start offset for the line at index {@code index}. + * * @param index the line index * @return the start offset */ @@ -47,29 +61,32 @@ public interface LayoutInfo { /** * Returns the end offset for the line at index {@code index}. + * * @param index the line index * @return the end offset */ public int getTextLineEnd(int index); /** - * Returns metrics information about the line as follows: - *

+ * Returns the information about the line: + *

* * @param index the line index * @return the line bounds From 21dbe6c6cdd44ddfeb59468b5f90d2bd7cad4bf1 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 9 Oct 2024 12:13:11 -0700 Subject: [PATCH 03/43] review comments --- .../com/sun/javafx/text/PrismLayoutInfo.java | 2 +- .../java/javafx/scene/text/LayoutInfo.java | 19 ++++++++++++------- .../src/main/java/javafx/scene/text/Text.java | 5 +++++ .../main/java/javafx/scene/text/TextFlow.java | 5 +++++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java index 8a82981b023..0daf5530645 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java @@ -33,7 +33,7 @@ /** * Layout information as reported by PrismLayout. */ -public final class PrismLayoutInfo implements LayoutInfo { +public final class PrismLayoutInfo extends LayoutInfo { private final TLine[] lines; private final Rectangle2D bounds; diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java index abfcd0ca085..e729dbc3476 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java @@ -27,11 +27,16 @@ import javafx.geometry.Rectangle2D; /** - * Represents an immutable snapshot of certain aspects of the text layout. + * Represents an immutable snapshot of certain aspects of the text layout + * in a {@code Text} or {@TextFlow} node, + * such as break up of the text into lines and the their bounds. + *

+ * The snapshot is valid until the layout changes due to any change that + * triggers that, such as resizing of the container or modification of properties. * * @since 24 */ -public interface LayoutInfo { +public sealed abstract class LayoutInfo permits com.sun.javafx.text.PrismLayoutInfo { /** * Returns the logical bounds of the layout: *

* - * @since 24 + * @since 25 */ public record TextLineInfo(int start, int end, Rectangle2D bounds) { } 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 68db7a4c75f..544f246b401 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, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 diff --git a/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java b/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java index 95391193d7c..f5c46b33fcb 100644 --- a/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java +++ b/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, 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 @@ -345,7 +345,7 @@ public static void beforeAll() { } @AfterAll - public static void exit() { + public static void afterAll() { Util.shutdown(); } diff --git a/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java b/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java index 6bea20259a1..0e327205b51 100644 --- a/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java +++ b/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, 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 @@ -327,7 +327,7 @@ public static void beforeAll() { } @AfterAll - public static void exit() { + public static void afterAll() { Util.shutdown(); } From d688d7468d183581b5069e221607d0af97cec756 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 5 Feb 2025 13:25:03 -0800 Subject: [PATCH 24/43] review comments --- .../com/sun/javafx/scene/text/TextLayout.java | 4 +-- .../com/sun/javafx/text/PrismLayoutInfo.java | 6 +---- .../com/sun/javafx/text/PrismTextLayout.java | 2 +- .../java/com/sun/javafx/text/TextUtils.java | 26 +++++++++---------- .../src/main/java/javafx/scene/text/Text.java | 2 +- .../main/java/javafx/scene/text/TextFlow.java | 2 +- .../com/sun/javafx/pgstub/StubTextLayout.java | 2 +- 7 files changed, 20 insertions(+), 24 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLayout.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLayout.java index 6d99204103a..74a21c8cbd4 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLayout.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLayout.java @@ -253,7 +253,7 @@ public String toString() { * @param leading whether the caret is biased on the leading edge of the character * @return the caret geometry */ - public float[] getCaretInf(int offset, boolean leading); + public float[] getCaretGeometry(int offset, boolean leading); /** * Queries the range geometry of the range of text within the text layout for one of the three possible types: @@ -265,7 +265,7 @@ public String toString() { * * @param start the start offset * @param end the end offset - * @param the type of the geometry + * @param type the type of the geometry * @param client the callback to invoke for each rectangular shape */ public void getRange(int start, int end, int type, GeometryCallback client); diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java index 8cd966bb80f..4299661631a 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java @@ -123,16 +123,12 @@ private List getGeometry(int start, int end, int type, double lineS return Collections.unmodifiableList(rv); } - private TextLine line(int ix) { - return layout.getLines()[ix]; - } - @Override public CaretInfo caretInfo(int charIndex, boolean leading) { Insets m = insets(); double dx = m.getLeft(); // TODO RTL? double dy = m.getTop(); - float[] c = layout.getCaretInf(charIndex, leading); + float[] c = layout.getCaretGeometry(charIndex, leading); Rectangle2D[] parts; if (c.length == 3) { 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 c053f3c9b14..9d910759a96 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 @@ -308,7 +308,7 @@ public BaseBounds getBounds(TextSpan filter, BaseBounds bounds) { } @Override - public float[] getCaretInf(int offset, boolean isLeading) { + public float[] getCaretGeometry(int offset, boolean isLeading) { ensureLayout(); int lineIndex = 0; int lineCount = getLineCount(); diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java index e6553c5d3fb..fbc278269e2 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java @@ -42,11 +42,11 @@ public final class TextUtils { /** * Queries the range geometry of the range of text within the text layout as - * an array of {@code PathElement}s, for for one of the three possible types: + * an array of {@code PathElement}s, for one of the three possible types: *
    - *
  • {@link #TYPE_STRIKETHROUGH} - strike-through shape - *
  • {@link #TYPE_TEXT} - text selection shape - *
  • {@link #TYPE_UNDERLINE} - underline shape + *
  • {@link TextLayout#TYPE_STRIKETHROUGH} - strike-through shape + *
  • {@link TextLayout#TYPE_TEXT} - text selection shape + *
  • {@link TextLayout#TYPE_UNDERLINE} - underline shape *
* * @param layout the text layout @@ -60,15 +60,15 @@ public final class TextUtils { public static PathElement[] getRange(TextLayout layout, int start, int end, int type, double dx, double dy) { ArrayList a = new ArrayList<>(); layout.getRange(start, end, type, (left, top, right, bottom) -> { - left += dx; - right += dx; - top += dy; - bottom += dy; - a.add(new MoveTo(left, top)); - a.add(new LineTo(right, top)); - a.add(new LineTo(right, bottom)); - a.add(new LineTo(left, bottom)); - a.add(new LineTo(left, top)); + double leftEdge = left + dx; + double rightEdge = right + dx; + double topEdge = top + dy; + double bottomEdge = bottom + dy; + a.add(new MoveTo(leftEdge, topEdge)); + a.add(new LineTo(rightEdge, topEdge)); + a.add(new LineTo(rightEdge, bottomEdge)); + a.add(new LineTo(leftEdge, bottomEdge)); + a.add(new LineTo(leftEdge, topEdge)); }); return a.toArray(PathElement[]::new); } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java index af27d1ace1d..7ac37965ffb 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java @@ -1082,7 +1082,7 @@ public final PathElement[] caretShape(int charIndex, boolean caretBias) { if (0 <= charIndex && charIndex <= getTextInternal().length()) { double dx = getX(); double dy = getY() - getYRendering(); - float[] c = getTextLayout().getCaretInf(charIndex, caretBias); + float[] c = getTextLayout().getCaretGeometry(charIndex, caretBias); return TextUtils.getCaretShape(c, dx, dy); } else { return null; diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index b0e227abe52..2679d56d12f 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -219,7 +219,7 @@ public final HitInfo hitTest(javafx.geometry.Point2D point) { * @since 9 */ public PathElement[] caretShape(int charIndex, boolean leading) { - float[] c = getTextLayout().getCaretInf(charIndex, leading); + float[] c = getTextLayout().getCaretGeometry(charIndex, leading); // TODO padding JDK-8341438? return TextUtils.getCaretShape(c, 0.0, 0.0); } 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 544f246b401..8e04e3da40b 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 @@ -205,7 +205,7 @@ public Hit getHitInfo(float x, float y) { } @Override - public float[] getCaretInf(int offset, boolean isLeading) { + public float[] getCaretGeometry(int offset, boolean isLeading) { // FIX this can be implemented if needed, following the logic used in getBounds() and getHitInfo() return null; } From 0ad6f66d8878f621d72d03b2646b30acb6989230 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Mon, 10 Mar 2025 09:27:04 -0700 Subject: [PATCH 25/43] review comments --- .../src/main/java/com/sun/javafx/text/PrismCaretInfo.java | 6 ++++-- .../src/main/java/com/sun/javafx/text/PrismLayoutInfo.java | 5 ++++- .../src/main/java/javafx/scene/text/CaretInfo.java | 2 ++ .../src/main/java/javafx/scene/text/LayoutInfo.java | 2 ++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismCaretInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismCaretInfo.java index 10ba639eeed..9e766241127 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismCaretInfo.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismCaretInfo.java @@ -24,6 +24,7 @@ */ package com.sun.javafx.text; +import java.util.Objects; import javafx.geometry.Rectangle2D; import javafx.scene.text.CaretInfo; @@ -45,20 +46,21 @@ public int getSegmentCount() { @Override public Rectangle2D getSegmentAt(int index) { + Objects.checkIndex(index, parts.length); return parts[index]; } @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("PrismCaretInfo{parts=["); + sb.append("["); for (int i = 0; i < getSegmentCount(); i++) { if (i > 0) { sb.append(","); } sb.append(getSegmentAt(i)); } - sb.append("]}"); + sb.append("]"); return sb.toString(); } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java index 4299661631a..868dc8ba8e8 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import javafx.geometry.Insets; import javafx.geometry.Rectangle2D; import javafx.scene.text.CaretInfo; @@ -84,11 +85,13 @@ public List getTextLines(boolean includeLineSpacing) { @Override public TextLineInfo getTextLine(int index, boolean includeLineSpacing) { + TextLine[] lines = layout.getLines(); + Objects.checkIndex(index, lines.length); Insets m = insets(); double dx = m.getLeft(); // TODO rtl? double dy = m.getTop(); double sp = includeLineSpacing ? lineSpacing() : 0.0; - return TextUtils.toLineInfo(layout.getLines()[index], dx, dy, sp); + return TextUtils.toLineInfo(lines[index], dx, dy, sp); } @Override diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java index 42d21f38e32..e09ea15f2df 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java @@ -56,6 +56,8 @@ protected CaretInfo() { * * @param index the line index * @return the bounds of the caret segment + * @throws IndexOutOfBoundsException if the index is outside of the range if the index is out of range + * {@code (index < 0 || index > getSegmentCount())} */ public abstract Rectangle2D getSegmentAt(int index); } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java index 1ccca36bf28..8d04d7ba34a 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java @@ -76,6 +76,8 @@ protected LayoutInfo() { * @param index the line index * @param includeLineSpacing determines whether the result includes the line spacing * @return the {@code TextLineInfo} object + * @throws IndexOutOfBoundsException if the index is outside of the range if the index is out of range + * {@code (index < 0 || index > getTextLineCount())} */ public abstract TextLineInfo getTextLine(int index, boolean includeLineSpacing); From 9d499c1605bb9b8c9434d542a55744dc5615bc6f Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Mon, 10 Mar 2025 09:32:43 -0700 Subject: [PATCH 26/43] tests --- .../javafx/scene/TextFlow_TextLayout_Test.java | 15 +++++++++++++++ .../robot/javafx/scene/Text_TextLayout_Test.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java b/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java index f5c46b33fcb..79a3668f306 100644 --- a/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java +++ b/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java @@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -298,6 +299,20 @@ public void testUnderline() { assertEquals(s1.getHeight(), s2.getHeight(), EPS); } + // testing IOOBE exceptions + @Test + public void testIOOBExceptions() { + setText("__________\n", "______\n", "_\n"); + waitForIdle(); + LayoutInfo la = textFlow.getLayoutInfo(); + assertThrows(IndexOutOfBoundsException.class, () -> la.getTextLine(-1, true)); + assertThrows(IndexOutOfBoundsException.class, () -> la.getTextLine(la.getTextLineCount(), true)); + + CaretInfo ci = la.caretInfo(0, true); + assertThrows(IndexOutOfBoundsException.class, () -> ci.getSegmentAt(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> ci.getSegmentAt(ci.getSegmentCount())); + } + private void setText(String... segments) { Util.runAndWait(() -> { textFlow.getChildren().clear(); diff --git a/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java b/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java index 0e327205b51..3dd602d8e14 100644 --- a/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java +++ b/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java @@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -299,6 +300,20 @@ public void testUnderline() { assertEquals(s1.getHeight(), s2.getHeight(), EPS); } + // testing IOOBE exceptions + @Test + public void testIOOBExceptions() { + setText("__\n____\n______"); + waitForIdle(); + LayoutInfo la = text.getLayoutInfo(); + assertThrows(IndexOutOfBoundsException.class, () -> la.getTextLine(-1, true)); + assertThrows(IndexOutOfBoundsException.class, () -> la.getTextLine(la.getTextLineCount(), true)); + + CaretInfo ci = la.caretInfo(0, true); + assertThrows(IndexOutOfBoundsException.class, () -> ci.getSegmentAt(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> ci.getSegmentAt(ci.getSegmentCount())); + } + private void setText(String s) { Util.runAndWait(() -> { text = new Text(s); From b7033cf68994681f9d08afbacab0030393bbbbcf Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Mon, 10 Mar 2025 10:09:40 -0700 Subject: [PATCH 27/43] twice --- .../src/main/java/javafx/scene/text/CaretInfo.java | 2 +- .../src/main/java/javafx/scene/text/LayoutInfo.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java index e09ea15f2df..bd18fdf9bf9 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java @@ -56,7 +56,7 @@ protected CaretInfo() { * * @param index the line index * @return the bounds of the caret segment - * @throws IndexOutOfBoundsException if the index is outside of the range if the index is out of range + * @throws IndexOutOfBoundsException if the index is out of range * {@code (index < 0 || index > getSegmentCount())} */ public abstract Rectangle2D getSegmentAt(int index); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java index 8d04d7ba34a..f274ec17572 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java @@ -76,7 +76,7 @@ protected LayoutInfo() { * @param index the line index * @param includeLineSpacing determines whether the result includes the line spacing * @return the {@code TextLineInfo} object - * @throws IndexOutOfBoundsException if the index is outside of the range if the index is out of range + * @throws IndexOutOfBoundsException if the index is out of range * {@code (index < 0 || index > getTextLineCount())} */ public abstract TextLineInfo getTextLine(int index, boolean includeLineSpacing); From fa23f097d4ab0ce962db8e2523908db333a0c0c9 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Mon, 7 Apr 2025 11:40:09 -0700 Subject: [PATCH 28/43] indent --- .../java/javafx/scene/text/TextLineInfo.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextLineInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextLineInfo.java index 0983d2afec4..8906e69d225 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextLineInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextLineInfo.java @@ -33,22 +33,22 @@ * @param end the end offset for the line (index of the last character + 1) * @param bounds the bounds of the text line, in local coordinates: *
    - *
  • - * {@code minX} - the x origin of the line (relative to the layout). - * The x origin is defined by TextAlignment of the text layout, always zero - * for left-aligned text. - *
  • - * {@code minY} - the ascent of the line (negative). - * The ascent of the line is the max ascent of all fonts in the line. - *
  • - * {@code width} - the width of the line. - * The width for the line is sum of all the run widths in the line, it is not - * affect by the wrapping width but it will include any changes caused by - * justification. - *
  • - * {@code height} - the height of the line. - * The height of the line is sum of the max ascent, max descent, and - * max line gap of all the fonts in the line. + *
  • + * {@code minX} - the x origin of the line (relative to the layout). + * The x origin is defined by TextAlignment of the text layout, always zero + * for left-aligned text. + *
  • + * {@code minY} - the ascent of the line (negative). + * The ascent of the line is the max ascent of all fonts in the line. + *
  • + * {@code width} - the width of the line. + * The width for the line is sum of all the run widths in the line, it is not + * affect by the wrapping width but it will include any changes caused by + * justification. + *
  • + * {@code height} - the height of the line. + * The height of the line is sum of the max ascent, max descent, and + * max line gap of all the fonts in the line. *
* * @since 25 From 4d2bdf5dda4bab3a2ba7df6bc69ccc6cb0431045 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Mon, 7 Apr 2025 12:32:20 -0700 Subject: [PATCH 29/43] sealed --- .../src/main/java/com/sun/javafx/text/PrismLayoutInfo.java | 2 +- .../src/main/java/javafx/scene/text/LayoutInfo.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java index 868dc8ba8e8..d78344fe990 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java @@ -40,7 +40,7 @@ /** * Layout information as reported by PrismLayout. */ -public abstract class PrismLayoutInfo extends LayoutInfo { +public abstract non-sealed class PrismLayoutInfo extends LayoutInfo { protected abstract double lineSpacing(); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java index f274ec17572..b354bf789f7 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java @@ -26,6 +26,7 @@ import java.util.List; import javafx.geometry.Rectangle2D; +import com.sun.javafx.text.PrismLayoutInfo; /** * Provides a view into the text layout used in a {@code Text} or a {@code TextFlow} node, @@ -38,7 +39,7 @@ * * @since 25 */ -public abstract class LayoutInfo { +public abstract sealed class LayoutInfo permits PrismLayoutInfo { /** * Constructor for subclasses to call. */ From 982ef1f735563056b699b4d44a1360990210f44b Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 2 May 2025 10:37:52 -0700 Subject: [PATCH 30/43] review comments --- .../com/sun/javafx/text/PrismLayoutInfo.java | 10 ++++----- .../java/javafx/scene/text/CaretInfo.java | 2 +- .../java/javafx/scene/text/LayoutInfo.java | 22 +++++++++---------- .../src/main/java/javafx/scene/text/Text.java | 21 +++++------------- .../main/java/javafx/scene/text/TextFlow.java | 9 ++------ .../scene/TextFlow_TextLayout_Test.java | 18 +++++++-------- .../javafx/scene/Text_TextLayout_Test.java | 18 +++++++-------- 7 files changed, 42 insertions(+), 58 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java index d78344fe990..c1e183f6ba3 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java @@ -53,7 +53,7 @@ public PrismLayoutInfo(TextLayout layout) { } @Override - public Rectangle2D getBounds(boolean includeLineSpacing) { + public Rectangle2D getLogicalBounds(boolean includeLineSpacing) { BaseBounds b = layout.getBounds(); Insets m = insets(); double dx = m.getLeft(); // TODO rtl? @@ -95,18 +95,18 @@ public TextLineInfo getTextLine(int index, boolean includeLineSpacing) { } @Override - public List selectionShape(int start, int end, boolean includeLineSpacing) { + public List getSelectionGeometry(int start, int end, boolean includeLineSpacing) { double sp = includeLineSpacing ? lineSpacing() : 0.0; return getGeometry(start, end, TextLayout.TYPE_TEXT, sp); } @Override - public List strikeThroughShape(int start, int end) { + public List getStrikeThroughGeometry(int start, int end) { return getGeometry(start, end, TextLayout.TYPE_STRIKETHROUGH, 0.0); } @Override - public List underlineShape(int start, int end) { + public List getUnderlineGeometry(int start, int end) { return getGeometry(start, end, TextLayout.TYPE_UNDERLINE, 0.0); } @@ -127,7 +127,7 @@ private List getGeometry(int start, int end, int type, double lineS } @Override - public CaretInfo caretInfo(int charIndex, boolean leading) { + public CaretInfo caretInfoAt(int charIndex, boolean leading) { Insets m = insets(); double dx = m.getLeft(); // TODO RTL? double dy = m.getTop(); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java index bd18fdf9bf9..eb72e4d9cdc 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/CaretInfo.java @@ -57,7 +57,7 @@ protected CaretInfo() { * @param index the line index * @return the bounds of the caret segment * @throws IndexOutOfBoundsException if the index is out of range - * {@code (index < 0 || index > getSegmentCount())} + * {@code (index < 0 || index >= getSegmentCount())} */ public abstract Rectangle2D getSegmentAt(int index); } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java index b354bf789f7..54693fc6277 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java @@ -29,13 +29,11 @@ import com.sun.javafx.text.PrismLayoutInfo; /** - * Provides a view into the text layout used in a {@code Text} or a {@code TextFlow} node, - * with purpose of querying the details of the layout such as break up of the text into lines, - * as well as geometry of other shapes derived from the layout (selection, underline, etc.). + * Provides a snapshot of the text layout geometry in a {@code Text} or a {@code TextFlow} node, + * such as break up of the text into lines, as well as other shapes derived from the layout + * (selection, underline, etc.). *

- * The information obtained via this object may change after the next layout cycle, which may come as a result - * of actions such as resizing of the container, or modification of certain properties. - * For example updating the text or the font might change the layout, but a change of color would not. + * The information in this object is valid until the next layout cycle. * * @since 25 */ @@ -54,7 +52,7 @@ protected LayoutInfo() { * @param includeLineSpacing determines whether the line spacing after last text line should be included * @return the layout bounds */ - public abstract Rectangle2D getBounds(boolean includeLineSpacing); + public abstract Rectangle2D getLogicalBounds(boolean includeLineSpacing); /** * Returns the number of text lines in the layout. @@ -78,7 +76,7 @@ protected LayoutInfo() { * @param includeLineSpacing determines whether the result includes the line spacing * @return the {@code TextLineInfo} object * @throws IndexOutOfBoundsException if the index is out of range - * {@code (index < 0 || index > getTextLineCount())} + * {@code (index < 0 || index >= getTextLineCount())} */ public abstract TextLineInfo getTextLine(int index, boolean includeLineSpacing); @@ -91,7 +89,7 @@ protected LayoutInfo() { * @param includeLineSpacing determines whether the result includes the line spacing * @return the immutable list of {@code Rectangle2D} objects */ - public abstract List selectionShape(int start, int end, boolean includeLineSpacing); + public abstract List getSelectionGeometry(int start, int end, boolean includeLineSpacing); /** * Returns the geometry of the strike-through shape, as an immutable list of {@code Rectangle2D} objects, @@ -101,7 +99,7 @@ protected LayoutInfo() { * @param end the end offset * @return the immutable list of {@code Rectangle2D} objects */ - public abstract List strikeThroughShape(int start, int end); + public abstract List getStrikeThroughGeometry(int start, int end); /** * Returns the geometry of the underline shape, as an immutable list of {@code Rectangle2D} objects, @@ -111,7 +109,7 @@ protected LayoutInfo() { * @param end the end offset * @return the immutable list of {@code Rectangle2D} objects */ - public abstract List underlineShape(int start, int end); + public abstract List getUnderlineGeometry(int start, int end); /** * Returns the information related to the caret at the specified character index and the character bias. @@ -120,5 +118,5 @@ protected LayoutInfo() { * @param leading whether the caret is biased on the leading edge of the character * @return the {@code CaretInfo} object */ - public abstract CaretInfo caretInfo(int charIndex, boolean leading); + public abstract CaretInfo caretInfoAt(int charIndex, boolean leading); } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java index 6592a43d683..a00c83ab304 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java @@ -862,8 +862,7 @@ public final PathElement[] getSelectionShape() { } /** - * The shape of the selection in coordinates - * relative to the font base line. + * The shape of the selection in local coordinates. * * @return the {@code selectionShape} property * @@ -1090,8 +1089,7 @@ public final PathElement[] caretShape(int charIndex, boolean caretBias) { } /** - * Returns the shape for the range of the text in coordinates - * relative to the font base line. + * Returns the shape for the range of the text in local coordinates. * * @param start the beginning character index for the range * @param end the end character index (non-inclusive) for the range @@ -1103,8 +1101,7 @@ public final PathElement[] rangeShape(int start, int end) { } /** - * Returns the shape for the underline in coordinates - * relative to the font base line. + * Returns the shape for the underline in local coordinates. * * @param start the beginning character index for the range * @param end the end character index (non-inclusive) for the range @@ -1116,15 +1113,14 @@ public final PathElement[] underlineShape(int start, int end) { } /** - * Returns the shape for the strike-through in coordinates - * relative to the font base line. + * Returns the shape for the strike-through in local coordinates. * * @param start the beginning character index for the range * @param end the end character index (non-inclusive) for the range * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 25 */ - public final PathElement[] strikeThroughShape(int start, int end) { + public final PathElement[] getStrikeThroughShape(int start, int end) { return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH); } @@ -2092,16 +2088,11 @@ public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... } /** - * Returns the object which provides a view into the text layout for this node, which allows for querying - * the details of the layout. + * Returns the object which provides a snapshot of the text layout geometry for this node. *

* While there is no general guarantee that successive invocations of this method return the same instance, * it is safe to either cache this object or call this method each time, since the information obtained from * this lightweight object remains valid until the next layout cycle. - *

- * The information obtained after the next layout cycle might be different as a result - * of actions such as resizing of the container, or modification of certain properties. - * For example updating the text or the font might change the layout, but a change of color would not. * * @return the layout information * @since 25 diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index 2679d56d12f..535cf300e6f 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -256,7 +256,7 @@ public final PathElement[] underlineShape(int start, int end) { * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 25 */ - public final PathElement[] strikeThroughShape(int start, int end) { + public final PathElement[] getStrikeThroughShape(int start, int end) { return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH); } @@ -716,16 +716,11 @@ public TextLayout getTextLayout(TextFlow f) { } /** - * Returns the object which provides a view into the text layout for this node, which allows for querying - * the details of the layout. + * Returns the object which provides a snapshot of the text layout geometry for this node. *

* While there is no general guarantee that successive invocations of this method return the same instance, * it is safe to either cache this object or call this method each time, since the information obtained from * this lightweight object remains valid until the next layout cycle. - *

- * The information obtained after the next layout cycle might be different as a result - * of actions such as resizing of the container, or modification of certain properties. - * For example updating the text or the font might change the layout, but a change of color would not. * * @return the layout information * @since 25 diff --git a/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java b/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java index 79a3668f306..ba8622dd3f5 100644 --- a/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java +++ b/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java @@ -75,7 +75,7 @@ public void testCaretInfo() { waitForIdle(); LayoutInfo la = textFlow.getLayoutInfo(); - CaretInfo ci = la.caretInfo(0, true); + CaretInfo ci = la.caretInfoAt(0, true); // caret is one line assertEquals(ci.getSegmentCount(), 1); @@ -89,7 +89,7 @@ public void testCaretInfo() { // caret at the end int len = textLength(); - ci = la.caretInfo(len - 1, false); + ci = la.caretInfoAt(len - 1, false); assertEquals(ci.getSegmentCount(), 1); Rectangle2D r2 = ci.getSegmentAt(0); @@ -108,8 +108,8 @@ public void testBounds() { waitForIdle(); LayoutInfo la = textFlow.getLayoutInfo(); - Rectangle2D r0 = la.getBounds(false); - Rectangle2D r1 = la.getBounds(true); + Rectangle2D r0 = la.getLogicalBounds(false); + Rectangle2D r1 = la.getLogicalBounds(true); // non-empty assertTrue((r0.getWidth() > 0) && (r0.getHeight() > 0)); @@ -193,7 +193,7 @@ public void testSelection() { // spacing = 0 int len = textLength(); - List ss = la.selectionShape(0, len, false); + List ss = la.getSelectionGeometry(0, len, false); assertEquals(ss.size(), 3); Rectangle2D s0 = ss.get(0); Rectangle2D s1 = ss.get(1); @@ -219,7 +219,7 @@ public void testSelection() { }); waitForIdle(); - List SS = la.selectionShape(0, len, true); + List SS = la.getSelectionGeometry(0, len, true); assertEquals(ss.size(), 3); Rectangle2D S0 = SS.get(0); Rectangle2D S1 = SS.get(1); @@ -251,7 +251,7 @@ public void testStrikeThrough() { LayoutInfo la = textFlow.getLayoutInfo(); int len = textLength(); - List ss = la.strikeThroughShape(0, len); + List ss = la.getStrikeThroughGeometry(0, len); assertEquals(ss.size(), 3); Rectangle2D s0 = ss.get(0); Rectangle2D s1 = ss.get(1); @@ -279,7 +279,7 @@ public void testUnderline() { LayoutInfo la = textFlow.getLayoutInfo(); int len = textLength(); - List ss = la.underlineShape(0, len); + List ss = la.getUnderlineGeometry(0, len); assertEquals(ss.size(), 3); Rectangle2D s0 = ss.get(0); Rectangle2D s1 = ss.get(1); @@ -308,7 +308,7 @@ public void testIOOBExceptions() { assertThrows(IndexOutOfBoundsException.class, () -> la.getTextLine(-1, true)); assertThrows(IndexOutOfBoundsException.class, () -> la.getTextLine(la.getTextLineCount(), true)); - CaretInfo ci = la.caretInfo(0, true); + CaretInfo ci = la.caretInfoAt(0, true); assertThrows(IndexOutOfBoundsException.class, () -> ci.getSegmentAt(-1)); assertThrows(IndexOutOfBoundsException.class, () -> ci.getSegmentAt(ci.getSegmentCount())); } diff --git a/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java b/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java index 3dd602d8e14..7a5775fac56 100644 --- a/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java +++ b/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java @@ -76,7 +76,7 @@ public void testCaretInfo() { waitForIdle(); LayoutInfo la = text.getLayoutInfo(); - CaretInfo ci = la.caretInfo(0, true); + CaretInfo ci = la.caretInfoAt(0, true); // caret is one line assertEquals(ci.getSegmentCount(), 1); @@ -90,7 +90,7 @@ public void testCaretInfo() { // caret at the end int len = textLength(); - ci = la.caretInfo(len - 1, false); + ci = la.caretInfoAt(len - 1, false); assertEquals(ci.getSegmentCount(), 1); Rectangle2D r2 = ci.getSegmentAt(0); @@ -109,8 +109,8 @@ public void testBounds() { waitForIdle(); LayoutInfo la = text.getLayoutInfo(); - Rectangle2D r0 = la.getBounds(false); - Rectangle2D r1 = la.getBounds(true); + Rectangle2D r0 = la.getLogicalBounds(false); + Rectangle2D r1 = la.getLogicalBounds(true); // non-empty assertTrue((r0.getWidth() > 0) && (r0.getHeight() > 0)); @@ -194,7 +194,7 @@ public void testSelection() { // spacing = 0 int len = textLength(); - List ss = la.selectionShape(0, len, false); + List ss = la.getSelectionGeometry(0, len, false); assertEquals(ss.size(), 3); Rectangle2D s0 = ss.get(0); Rectangle2D s1 = ss.get(1); @@ -220,7 +220,7 @@ public void testSelection() { }); waitForIdle(); - List SS = la.selectionShape(0, len, true); + List SS = la.getSelectionGeometry(0, len, true); assertEquals(ss.size(), 3); Rectangle2D S0 = SS.get(0); Rectangle2D S1 = SS.get(1); @@ -252,7 +252,7 @@ public void testStrikeThrough() { LayoutInfo la = text.getLayoutInfo(); int len = textLength(); - List ss = la.strikeThroughShape(0, len); + List ss = la.getStrikeThroughGeometry(0, len); assertEquals(ss.size(), 3); Rectangle2D s0 = ss.get(0); Rectangle2D s1 = ss.get(1); @@ -280,7 +280,7 @@ public void testUnderline() { LayoutInfo la = text.getLayoutInfo(); int len = textLength(); - List ss = la.underlineShape(0, len); + List ss = la.getUnderlineGeometry(0, len); assertEquals(ss.size(), 3); Rectangle2D s0 = ss.get(0); Rectangle2D s1 = ss.get(1); @@ -309,7 +309,7 @@ public void testIOOBExceptions() { assertThrows(IndexOutOfBoundsException.class, () -> la.getTextLine(-1, true)); assertThrows(IndexOutOfBoundsException.class, () -> la.getTextLine(la.getTextLineCount(), true)); - CaretInfo ci = la.caretInfo(0, true); + CaretInfo ci = la.caretInfoAt(0, true); assertThrows(IndexOutOfBoundsException.class, () -> ci.getSegmentAt(-1)); assertThrows(IndexOutOfBoundsException.class, () -> ci.getSegmentAt(ci.getSegmentCount())); } From 0ac75dae87aee321b473ccd3e535087212deea5e Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 21 May 2025 15:49:07 -0700 Subject: [PATCH 31/43] review comments --- .../src/main/java/javafx/scene/text/LayoutInfo.java | 2 +- .../src/main/java/javafx/scene/text/Text.java | 5 +++-- .../src/main/java/javafx/scene/text/TextFlow.java | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java index 54693fc6277..88389e4a607 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/LayoutInfo.java @@ -29,7 +29,7 @@ import com.sun.javafx.text.PrismLayoutInfo; /** - * Provides a snapshot of the text layout geometry in a {@code Text} or a {@code TextFlow} node, + * Holds a snapshot of the text layout geometry in a {@code Text} or a {@code TextFlow} node, * such as break up of the text into lines, as well as other shapes derived from the layout * (selection, underline, etc.). *

diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java index a00c83ab304..fe0861a7ce2 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java @@ -2088,13 +2088,14 @@ public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... } /** - * Returns the object which provides a snapshot of the text layout geometry for this node. + * Returns a copy of the of the text layout geometry for this node. This copy is a snapshot + * of the text layout at the time the method is called. *

* While there is no general guarantee that successive invocations of this method return the same instance, * it is safe to either cache this object or call this method each time, since the information obtained from * this lightweight object remains valid until the next layout cycle. * - * @return the layout information + * @return a copy of the layout information * @since 25 */ public final LayoutInfo getLayoutInfo() { diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index 535cf300e6f..4addb762388 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -716,13 +716,14 @@ public TextLayout getTextLayout(TextFlow f) { } /** - * Returns the object which provides a snapshot of the text layout geometry for this node. + * Returns a copy of the of the text layout geometry for this node. This copy is a snapshot + * of the text layout at the time the method is called. *

* While there is no general guarantee that successive invocations of this method return the same instance, * it is safe to either cache this object or call this method each time, since the information obtained from * this lightweight object remains valid until the next layout cycle. * - * @return the layout information + * @return a copy of the layout information * @since 25 */ public final LayoutInfo getLayoutInfo() { From c885173b857c046c86b8b0642663b652abb54cc5 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Thu, 22 May 2025 10:15:35 -0700 Subject: [PATCH 32/43] caret geometry --- .../com/sun/javafx/scene/text/TextLayout.java | 30 +++++--- .../com/sun/javafx/text/PrismLayoutInfo.java | 25 +----- .../com/sun/javafx/text/PrismTextLayout.java | 12 ++- .../java/com/sun/javafx/text/TextUtils.java | 54 +++++++------ .../src/main/java/javafx/scene/text/Text.java | 77 +++++++++---------- .../main/java/javafx/scene/text/TextFlow.java | 4 +- 6 files changed, 99 insertions(+), 103 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLayout.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLayout.java index 74a21c8cbd4..ddb5c8b2325 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLayout.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/text/TextLayout.java @@ -239,21 +239,12 @@ public String toString() { /** * Queries the caret geometry and associated information at the specified text position. - *

- * The geometry is encoded as a sequence of coordinates using two different formats, - * depending on whether the caret is drawn as a single vertical line or as two separate - * lines (a "split" caret). - *

    - *
  • {@code [x, y, h]} - corresponds to a single line from (x, y) to (x, y + h) - *
  • {@code [x, y, x2, h]} - corresponds to a split caret drawn as two lines, the first line - * drawn from (x, y) to (x, y + h/2), the second line drawn from (x2, y + h/2) to (x2, y + h). - *
* * @param offset the character offset * @param leading whether the caret is biased on the leading edge of the character * @return the caret geometry */ - public float[] getCaretGeometry(int offset, boolean leading); + public CaretGeometry getCaretGeometry(int offset, boolean leading); /** * Queries the range geometry of the range of text within the text layout for one of the three possible types: @@ -269,4 +260,23 @@ public String toString() { * @param client the callback to invoke for each rectangular shape */ public void getRange(int start, int end, int type, GeometryCallback client); + + /** + * Encodes the caret geometry, which can be either a single vertical line, + * or two vertical lines (a "split" caret), represented by {@code Single} + * and {@code Split} classes respectively. + */ + public sealed interface CaretGeometry { + /** + * Represents a single line from (x, y) to (x, y + height) + */ + public record Single(float x, float y, float height) implements CaretGeometry {} + + /** + * Represents a split caret drawn as two lines, the first line + * from (x1, y) to (x1, y + height/2), + * the second line from (x2, y + height/2) to (x2, y + height). + */ + public record Split(float x1, float x2, float y, float height) implements CaretGeometry {} + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java index c1e183f6ba3..cdc372009a9 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java @@ -128,32 +128,11 @@ private List getGeometry(int start, int end, int type, double lineS @Override public CaretInfo caretInfoAt(int charIndex, boolean leading) { + TextLayout.CaretGeometry g = layout.getCaretGeometry(charIndex, leading); Insets m = insets(); double dx = m.getLeft(); // TODO RTL? double dy = m.getTop(); - float[] c = layout.getCaretGeometry(charIndex, leading); - - Rectangle2D[] parts; - if (c.length == 3) { - // [x, y, h] - corresponds to a single line from (x, y) to (x, y + h) - double x = c[0] + dx; - double y = c[1] + dy; - double h = c[2]; - parts = new Rectangle2D[] { - new Rectangle2D(x, y, 0.0, h) - }; - } else { - // [x, y, x2, h] - corresponds to a split caret drawn as two lines, the first line - // drawn from (x, y) to (x, y + h/2), the second line drawn from (x2, y + h/2) to (x2, y + h). - double x = c[0] + dx; - double y = c[1] + dy; - double x2 = c[2] + dx; - double h2 = c[3] / 2.0; - parts = new Rectangle2D[] { - new Rectangle2D(x, y, 0.0, h2), - new Rectangle2D(x2, y + h2, 0.0, h2) - }; - } + Rectangle2D[] parts = TextUtils.getCaretRectangles(g, dx, dy); return new PrismCaretInfo(parts); } } 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 43ff95dc3a3..c53b4ce5ae2 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 @@ -31,8 +31,6 @@ import java.util.Arrays; import java.util.Hashtable; import java.util.concurrent.atomic.AtomicBoolean; -import javafx.scene.shape.LineTo; -import javafx.scene.shape.MoveTo; import javafx.scene.shape.PathElement; import com.sun.javafx.font.CharToGlyphMapper; import com.sun.javafx.font.FontResource; @@ -314,7 +312,7 @@ public BaseBounds getBounds(TextSpan filter, BaseBounds bounds) { } @Override - public float[] getCaretGeometry(int offset, boolean isLeading) { + public TextLayout.CaretGeometry getCaretGeometry(int offset, boolean isLeading) { ensureLayout(); int lineIndex = 0; int lineCount = getLineCount(); @@ -404,22 +402,22 @@ public float[] getCaretGeometry(int offset, boolean isLeading) { lineX2 = getMirroringWidth() - lineX2; } // split caret - return new float[] { + return new TextLayout.CaretGeometry.Split( lineX, lineY, lineX2, lineHeight - }; + ); } } } } // regular caret - return new float[] { + return new TextLayout.CaretGeometry.Single( lineX, lineY, lineHeight - }; + ); } @Override diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java index fbc278269e2..d64dab8f49e 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java @@ -95,33 +95,43 @@ public static Rectangle2D toRectangle2D(BaseBounds b, double dx, double dy, doub return new Rectangle2D(b.getMinX() + dx, dy, w, b.getHeight() + lineSpacing); } - public static PathElement[] getCaretShape(float[] c, double dx, double dy) { - if (c == null) { - return null; - } else if (c.length == 3) { - // [x, y, h] - corresponds to a single line from (x, y) to (x, y + h) - double x = c[0] + dx; - double y = c[1] + dy; - double h = c[2]; - + public static PathElement[] getCaretPathElements(TextLayout.CaretGeometry g, double dx, double dy) { + switch (g) { + case TextLayout.CaretGeometry.Single s: + double x = s.x() + dx; + double y = s.y() + dy; return new PathElement[] { new MoveTo(x, y), - new LineTo(x, y + h) + new LineTo(x, y + s.height()) }; - } else { - // [x, y, x2, h] - corresponds to a split caret drawn as two lines, the first line - // drawn from (x, y) to (x, y + h/2), the second line drawn from (x2, y + h/2) to (x2, y + h). - double x = c[0] + dx; - double y = c[1] + dy; - double x2 = c[2] + dx; - double h = c[3]; - double y2 = y + h/2.0; - + case TextLayout.CaretGeometry.Split s: + double x1 = s.x1() + dx; + double x2 = s.x2() + dx; + double y1 = s.y() + dy; + double y2 = y1 + s.height() / 2.0; return new PathElement[] { - new MoveTo(x, y), - new LineTo(x, y2), + new MoveTo(x1, y1), + new LineTo(x1, y2), new MoveTo(x2, y2), - new LineTo(x2, y + h) + new LineTo(x2, y1 + s.height()) + }; + } + } + + public static Rectangle2D[] getCaretRectangles(TextLayout.CaretGeometry g, double dx, double dy) { + switch (g) { + case TextLayout.CaretGeometry.Single s: + return new Rectangle2D[] { + new Rectangle2D(s.x() + dx, s.y() + dy, 0.0, s.height()) + }; + case TextLayout.CaretGeometry.Split s: + double x1 = s.x1() + dx; + double x2 = s.x2() + dx; + double y = s.y() + dy; + double h2 = s.height() / 2.0; + return new Rectangle2D[] { + new Rectangle2D(x1, y, 0.0, h2), + new Rectangle2D(x2, y + h2, 0.0, h2) }; } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java index fe0861a7ce2..923eff6316c 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java @@ -25,47 +25,13 @@ package javafx.scene.text; -import javafx.css.converter.BooleanConverter; -import javafx.css.converter.EnumConverter; -import javafx.css.converter.SizeConverter; -import com.sun.javafx.geom.BaseBounds; -import com.sun.javafx.geom.Path2D; -import com.sun.javafx.geom.RectBounds; -import com.sun.javafx.geom.TransformedShape; -import com.sun.javafx.geom.transform.BaseTransform; -import com.sun.javafx.scene.DirtyBits; -import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.shape.ShapeHelper; -import com.sun.javafx.scene.shape.TextHelper; -import com.sun.javafx.scene.text.GlyphList; -import com.sun.javafx.scene.text.TextLayout; -import com.sun.javafx.scene.text.TextLayoutFactory; -import com.sun.javafx.scene.text.TextLine; -import com.sun.javafx.scene.text.TextSpan; -import com.sun.javafx.sg.prism.NGNode; -import com.sun.javafx.sg.prism.NGShape; -import com.sun.javafx.sg.prism.NGText; -import com.sun.javafx.scene.text.FontHelper; -import com.sun.javafx.text.PrismLayoutInfo; -import com.sun.javafx.text.TextRun; -import com.sun.javafx.text.TextUtils; -import com.sun.javafx.tk.Toolkit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import javafx.beans.DefaultProperty; import javafx.beans.InvalidationListener; import javafx.beans.binding.DoubleBinding; import javafx.beans.binding.ObjectBinding; -import javafx.scene.AccessibleAttribute; -import javafx.scene.AccessibleRole; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import javafx.scene.shape.LineTo; -import javafx.scene.shape.MoveTo; -import javafx.scene.shape.PathElement; -import javafx.scene.shape.Shape; -import javafx.scene.shape.StrokeType; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.DoublePropertyBase; @@ -88,13 +54,46 @@ import javafx.css.StyleableIntegerProperty; import javafx.css.StyleableObjectProperty; import javafx.css.StyleableProperty; +import javafx.css.converter.BooleanConverter; +import javafx.css.converter.EnumConverter; +import javafx.css.converter.SizeConverter; import javafx.geometry.BoundingBox; import javafx.geometry.Bounds; import javafx.geometry.Insets; import javafx.geometry.NodeOrientation; import javafx.geometry.Point2D; import javafx.geometry.VPos; +import javafx.scene.AccessibleAttribute; +import javafx.scene.AccessibleRole; import javafx.scene.Node; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.PathElement; +import javafx.scene.shape.Shape; +import javafx.scene.shape.StrokeType; +import com.sun.javafx.geom.BaseBounds; +import com.sun.javafx.geom.Path2D; +import com.sun.javafx.geom.RectBounds; +import com.sun.javafx.geom.TransformedShape; +import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.javafx.scene.DirtyBits; +import com.sun.javafx.scene.NodeHelper; +import com.sun.javafx.scene.shape.ShapeHelper; +import com.sun.javafx.scene.shape.TextHelper; +import com.sun.javafx.scene.text.FontHelper; +import com.sun.javafx.scene.text.GlyphList; +import com.sun.javafx.scene.text.TextLayout; +import com.sun.javafx.scene.text.TextLayoutFactory; +import com.sun.javafx.scene.text.TextLine; +import com.sun.javafx.scene.text.TextSpan; +import com.sun.javafx.sg.prism.NGNode; +import com.sun.javafx.sg.prism.NGShape; +import com.sun.javafx.sg.prism.NGText; +import com.sun.javafx.text.PrismLayoutInfo; +import com.sun.javafx.text.TextUtils; +import com.sun.javafx.tk.Toolkit; /** * The {@code Text} class defines a node that displays a text. @@ -1081,8 +1080,8 @@ public final PathElement[] caretShape(int charIndex, boolean caretBias) { if (0 <= charIndex && charIndex <= getTextInternal().length()) { double dx = getX(); double dy = getY() - getYRendering(); - float[] c = getTextLayout().getCaretGeometry(charIndex, caretBias); - return TextUtils.getCaretShape(c, dx, dy); + TextLayout.CaretGeometry g = getTextLayout().getCaretGeometry(charIndex, caretBias); + return TextUtils.getCaretPathElements(g, dx, dy); } else { return null; } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index 4addb762388..b22d307ed44 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -219,9 +219,9 @@ public final HitInfo hitTest(javafx.geometry.Point2D point) { * @since 9 */ public PathElement[] caretShape(int charIndex, boolean leading) { - float[] c = getTextLayout().getCaretGeometry(charIndex, leading); + TextLayout.CaretGeometry g = getTextLayout().getCaretGeometry(charIndex, leading); // TODO padding JDK-8341438? - return TextUtils.getCaretShape(c, 0.0, 0.0); + return TextUtils.getCaretPathElements(g, 0.0, 0.0); } /** From 0f02fe9d047995279ea1a1bbb65dadac209ba06d Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Thu, 22 May 2025 11:07:57 -0700 Subject: [PATCH 33/43] removed getStrikeThroughShape --- .../src/main/java/javafx/scene/text/Text.java | 7 ++++--- .../src/main/java/javafx/scene/text/TextFlow.java | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java index 923eff6316c..7d666c5ed3d 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java @@ -1119,9 +1119,10 @@ public final PathElement[] underlineShape(int start, int end) { * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 25 */ - public final PathElement[] getStrikeThroughShape(int start, int end) { - return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH); - } +// TODO +// public final PathElement[] getStrikeThroughShape(int start, int end) { +// return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH); +// } private float getYAdjustment(BaseBounds bounds) { VPos origin = getTextOrigin(); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index b22d307ed44..9e261f4f51f 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -256,9 +256,10 @@ public final PathElement[] underlineShape(int start, int end) { * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 25 */ - public final PathElement[] getStrikeThroughShape(int start, int end) { - return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH); - } +// TODO +// public final PathElement[] getStrikeThroughShape(int start, int end) { +// return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH); +// } @Override public boolean usesMirroring() { From 34b0646b64f6be1871a86890b22a8003f25ed3a1 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Thu, 22 May 2025 11:56:27 -0700 Subject: [PATCH 34/43] cleanup --- .../src/main/java/javafx/scene/text/Text.java | 13 ------------- .../src/main/java/javafx/scene/text/TextFlow.java | 13 ------------- 2 files changed, 26 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java index 7d666c5ed3d..52e960d26ec 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java @@ -1111,19 +1111,6 @@ public final PathElement[] underlineShape(int start, int end) { return getRange(start, end, TextLayout.TYPE_UNDERLINE); } - /** - * Returns the shape for the strike-through in local coordinates. - * - * @param start the beginning character index for the range - * @param end the end character index (non-inclusive) for the range - * @return an array of {@code PathElement} which can be used to create a {@code Shape} - * @since 25 - */ -// TODO -// public final PathElement[] getStrikeThroughShape(int start, int end) { -// return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH); -// } - private float getYAdjustment(BaseBounds bounds) { VPos origin = getTextOrigin(); if (origin == null) origin = DEFAULT_TEXT_ORIGIN; diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index 9e261f4f51f..0693abc74b8 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -248,19 +248,6 @@ public final PathElement[] underlineShape(int start, int end) { return getRange(start, end, TextLayout.TYPE_UNDERLINE); } - /** - * Returns the shape for the strike-through in local coordinates. - * - * @param start the beginning character index for the range - * @param end the end character index (non-inclusive) for the range - * @return an array of {@code PathElement} which can be used to create a {@code Shape} - * @since 25 - */ -// TODO -// public final PathElement[] getStrikeThroughShape(int start, int end) { -// return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH); -// } - @Override public boolean usesMirroring() { return false; From 46b48bf4aa35564a262789a16dbf53e929701f3c Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 23 May 2025 16:17:36 -0700 Subject: [PATCH 35/43] corrected api --- .../com/sun/javafx/text/PrismLayoutInfo.java | 30 +++-- .../java/com/sun/javafx/text/TextUtils.java | 5 +- .../src/main/java/javafx/scene/text/Text.java | 39 +++++-- .../main/java/javafx/scene/text/TextFlow.java | 110 ++++++++++++++++-- 4 files changed, 149 insertions(+), 35 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java index cdc372009a9..d57b31ae0cf 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java @@ -28,7 +28,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import javafx.geometry.Insets; import javafx.geometry.Rectangle2D; import javafx.scene.text.CaretInfo; import javafx.scene.text.LayoutInfo; @@ -44,7 +43,9 @@ public abstract non-sealed class PrismLayoutInfo extends LayoutInfo { protected abstract double lineSpacing(); - protected abstract Insets insets(); + protected abstract double dx(); + + protected abstract double dy(); private final TextLayout layout; @@ -55,9 +56,8 @@ public PrismLayoutInfo(TextLayout layout) { @Override public Rectangle2D getLogicalBounds(boolean includeLineSpacing) { BaseBounds b = layout.getBounds(); - Insets m = insets(); - double dx = m.getLeft(); // TODO rtl? - double dy = m.getTop(); + double dx = dx(); + double dy = dy(); double sp = includeLineSpacing ? lineSpacing() : 0.0; return TextUtils.toRectangle2D(b, dx, dy, sp); } @@ -70,9 +70,8 @@ public int getTextLineCount() { @Override public List getTextLines(boolean includeLineSpacing) { TextLine[] lines = layout.getLines(); - Insets m = insets(); - double dx = m.getLeft(); // TODO rtl? - double dy = m.getTop(); + double dx = dx(); + double dy = dy(); double sp = includeLineSpacing ? lineSpacing() : 0.0; int sz = lines.length; @@ -87,9 +86,8 @@ public List getTextLines(boolean includeLineSpacing) { public TextLineInfo getTextLine(int index, boolean includeLineSpacing) { TextLine[] lines = layout.getLines(); Objects.checkIndex(index, lines.length); - Insets m = insets(); - double dx = m.getLeft(); // TODO rtl? - double dy = m.getTop(); + double dx = dx(); + double dy = dy(); double sp = includeLineSpacing ? lineSpacing() : 0.0; return TextUtils.toLineInfo(lines[index], dx, dy, sp); } @@ -111,9 +109,8 @@ public List getUnderlineGeometry(int start, int end) { } private List getGeometry(int start, int end, int type, double lineSpacing) { - Insets m = insets(); - double dx = m.getLeft(); // TODO RTL? - double dy = m.getTop(); + double dx = dx(); + double dy = dy(); ArrayList rv = new ArrayList<>(); layout.getRange(start, end, type, (left, top, right, bottom) -> { @@ -129,9 +126,8 @@ private List getGeometry(int start, int end, int type, double lineS @Override public CaretInfo caretInfoAt(int charIndex, boolean leading) { TextLayout.CaretGeometry g = layout.getCaretGeometry(charIndex, leading); - Insets m = insets(); - double dx = m.getLeft(); // TODO RTL? - double dy = m.getTop(); + double dx = dx(); + double dy = dy(); Rectangle2D[] parts = TextUtils.getCaretRectangles(g, dx, dy); return new PrismCaretInfo(parts); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java index d64dab8f49e..c471060051e 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/TextUtils.java @@ -55,15 +55,16 @@ public final class TextUtils { * @param type the type of geometry to query * @param dx the x offset to add to each path element * @param dy the y offset to add to each path element + * @param lineSpacing the line spacing (applies only to TYPE_TEXT) * @return the array of {@code PathElement}s */ - public static PathElement[] getRange(TextLayout layout, int start, int end, int type, double dx, double dy) { + public static PathElement[] getRange(TextLayout layout, int start, int end, int type, double dx, double dy, double lineSpacing) { ArrayList a = new ArrayList<>(); layout.getRange(start, end, type, (left, top, right, bottom) -> { double leftEdge = left + dx; double rightEdge = right + dx; double topEdge = top + dy; - double bottomEdge = bottom + dy; + double bottomEdge = bottom + dy + lineSpacing; a.add(new MoveTo(leftEdge, topEdge)); a.add(new LineTo(rightEdge, topEdge)); a.add(new LineTo(rightEdge, bottomEdge)); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java index 923eff6316c..cd86bb7295d 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java @@ -1057,13 +1057,13 @@ private int findFirstRunStart() { return start; } - private PathElement[] getRange(int start, int end, int type) { + private PathElement[] getRange(int start, int end, int type, double lineSpacing) { int length = getTextInternal().length(); if (0 <= start && start < end && end <= length) { TextLayout layout = getTextLayout(); double dx = getX(); double dy = getY() - getYRendering(); - return TextUtils.getRange(layout, start, end, type, dx, dy); + return TextUtils.getRange(layout, start, end, type, dx, dy, lineSpacing); } return EMPTY_PATH_ELEMENT_ARRAY; } @@ -1089,14 +1089,32 @@ public final PathElement[] caretShape(int charIndex, boolean caretBias) { /** * Returns the shape for the range of the text in local coordinates. + *

+ * NOTE: this shapes returned do not include line spacing. * * @param start the beginning character index for the range * @param end the end character index (non-inclusive) for the range * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 9 + * @see #getRangeShape(int, int, boolean) */ public final PathElement[] rangeShape(int start, int end) { - return getRange(start, end, TextLayout.TYPE_TEXT); + return getRange(start, end, TextLayout.TYPE_TEXT, 0.0); + } + + /** + * Returns the shape for the range of the text in local coordinates, + * with or without line spacing. + * + * @param start the beginning character index for the range + * @param end the end character index (non-inclusive) for the range + * @param includeLineSpacing whether the shapes include line spacing + * @return an array of {@code PathElement} which can be used to create a {@code Shape} + * @since 25 + */ + public final PathElement[] getRangeShape(int start, int end, boolean includeLineSpacing) { + double lineSpacing = includeLineSpacing ? getLineSpacing() : 0.0; + return getRange(start, end, TextLayout.TYPE_TEXT, lineSpacing); } /** @@ -1108,7 +1126,7 @@ public final PathElement[] rangeShape(int start, int end) { * @since 9 */ public final PathElement[] underlineShape(int start, int end) { - return getRange(start, end, TextLayout.TYPE_UNDERLINE); + return getRange(start, end, TextLayout.TYPE_UNDERLINE, 0.0); } /** @@ -1120,7 +1138,7 @@ public final PathElement[] underlineShape(int start, int end) { * @since 25 */ public final PathElement[] getStrikeThroughShape(int start, int end) { - return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH); + return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH, 0.0); } private float getYAdjustment(BaseBounds bounds) { @@ -1782,7 +1800,7 @@ final ReadOnlyObjectProperty selectionShapeProperty() { @Override protected PathElement[] computeValue() { int start = getSelectionStart(); int end = getSelectionEnd(); - return getRange(start, end, TextLayout.TYPE_TEXT); + return getRange(start, end, TextLayout.TYPE_TEXT, 0.0); } }; selectionShape = new SimpleObjectProperty<>(Text.this, "selectionShape"); @@ -2105,8 +2123,13 @@ public double lineSpacing() { } @Override - public Insets insets() { - return Insets.EMPTY; + protected double dx() { + return getLayoutBounds().getMinX(); + } + + @Override + protected double dy() { + return getLayoutBounds().getMinY(); } }; } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index b22d307ed44..b148cca8872 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -193,11 +193,15 @@ private void checkOrientation() { /** * Maps local point to {@link HitInfo} in the content. + *

+ * NOTE: this method may return incorrect value with non-empty border or padding. * * @param point the specified point to be tested * @return a {@code HitInfo} representing the character index found * @since 9 + * @deprecated replaced by {@code getHitInfo()} */ + @Deprecated(since="25") public final HitInfo hitTest(javafx.geometry.Point2D point) { if (point != null) { TextLayout layout = getTextLayout(); @@ -210,30 +214,90 @@ public final HitInfo hitTest(javafx.geometry.Point2D point) { } } + /** + * Maps local point to {@link HitInfo} in the content. + * + * @param point the specified point to be tested + * @return a {@code HitInfo} representing the character index found + * @since 25 + */ + public final HitInfo getHitInfo(javafx.geometry.Point2D point) { + if (point != null) { + TextLayout layout = getTextLayout(); + double x = point.getX() - snappedLeftInset(); + double y = point.getY() - snappedTopInset(); + TextLayout.Hit h = layout.getHitInfo((float)x, (float)y); + return new HitInfo(h.getCharIndex(), h.getInsertionIndex(), h.isLeading()); + } else { + return null; + } + } + /** * Returns shape of caret in local coordinates. + *

+ * NOTE: this method does not account for padding/borders. * * @param charIndex the character index for the caret * @param leading whether the caret is biased on the leading edge of the character * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 9 + * @deprecated replaced by {@code getCaretShape()} */ + @Deprecated(since="25") public PathElement[] caretShape(int charIndex, boolean leading) { TextLayout.CaretGeometry g = getTextLayout().getCaretGeometry(charIndex, leading); - // TODO padding JDK-8341438? return TextUtils.getCaretPathElements(g, 0.0, 0.0); } + /** + * Returns shape of caret in local coordinates. + * + * @param charIndex the character index for the caret + * @param leading whether the caret is biased on the leading edge of the character + * @return an array of {@code PathElement} which can be used to create a {@code Shape} + * @since 25 + */ + public PathElement[] getCaretShape(int charIndex, boolean leading) { + TextLayout.CaretGeometry g = getTextLayout().getCaretGeometry(charIndex, leading); + double dx = snappedLeftInset(); + double dy = snappedTopInset(); + return TextUtils.getCaretPathElements(g, dx, dy); + } + /** * Returns shape for the range of the text in local coordinates. + *

+ * NOTES: + *

    + *
  • this method may return incorrect values with non-empty padding or border + *
  • the shapes returned do not include line spacing + *
* * @param start the beginning character index for the range * @param end the end character index (non-inclusive) for the range * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 9 + * @deprecated replaced by {@code getRangeShape()} */ + @Deprecated(since="25") public final PathElement[] rangeShape(int start, int end) { - return getRange(start, end, TextLayout.TYPE_TEXT); + return getRange(start, end, TextLayout.TYPE_TEXT, false, 0.0); + } + + /** + * Returns shape for the range of the text in local coordinates. + * + * @param start the beginning character index for the range + * @param end the end character index (non-inclusive) for the range + * @param includeLineSpacing determines whether the result includes the line spacing + * @return an array of {@code PathElement} which can be used to create a {@code Shape} + * @since 25 + */ + // TODO see also LayoutInfo.getSelectionGeometry + public final PathElement[] getRangeShape(int start, int end, boolean includeLineSpacing) { + double lineSpacing = includeLineSpacing ? getLineSpacing() : 0.0; + return getRange(start, end, TextLayout.TYPE_TEXT, true, lineSpacing); } /** @@ -243,9 +307,24 @@ public final PathElement[] rangeShape(int start, int end) { * @param end the end character index (non-inclusive) for the range * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 21 + * @deprecated replaced by {@code getUnderlineShape()} */ + @Deprecated(since="25") public final PathElement[] underlineShape(int start, int end) { - return getRange(start, end, TextLayout.TYPE_UNDERLINE); + return getRange(start, end, TextLayout.TYPE_UNDERLINE, false, 0.0); + } + + /** + * Returns the shape for the underline in local coordinates. + * + * @param start the beginning character index for the range + * @param end the end character index (non-inclusive) for the range + * @return an array of {@code PathElement} which can be used to create a {@code Shape} + * @since 25 + */ + // TODO see also LayoutInfo.getUnderlineGeometry + public final PathElement[] getUnderlineShape(int start, int end) { + return getRange(start, end, TextLayout.TYPE_UNDERLINE, true, 0.0); } /** @@ -256,8 +335,9 @@ public final PathElement[] underlineShape(int start, int end) { * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 25 */ + // TODO see also LayoutInfo.getStrikeThroughGeometry public final PathElement[] getStrikeThroughShape(int start, int end) { - return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH); + return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH, true, 0.0); } @Override @@ -380,9 +460,18 @@ public boolean usesMirroring() { inLayout = false; } - private PathElement[] getRange(int start, int end, int type) { + private PathElement[] getRange(int start, int end, int type, boolean accountForInsets, double lineSpacing) { + double dx; + double dy; + if(accountForInsets) { + dx = snappedLeftInset(); + dy = snappedTopInset(); + } else { + dx = 0.0; + dy = 0.0; + } TextLayout layout = getTextLayout(); - return TextUtils.getRange(layout, start, end, type, 0, 0); + return TextUtils.getRange(layout, start, end, type, dx, dy, lineSpacing); } private static class EmbeddedSpan implements TextSpan { @@ -734,8 +823,13 @@ public double lineSpacing() { } @Override - public Insets insets() { - return getInsets(); + protected double dx() { + return snappedLeftInset(); + } + + @Override + protected double dy() { + return snappedTopInset(); } }; } From de1016be519950314631060d6e561b54b244d804 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 27 May 2025 08:03:31 -0700 Subject: [PATCH 36/43] cleanup --- .../com/sun/javafx/text/PrismLayoutInfo.java | 30 ++++++++----------- .../src/main/java/javafx/scene/text/Text.java | 10 +++++-- .../main/java/javafx/scene/text/TextFlow.java | 9 ++++-- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java index cdc372009a9..ce307ed08a4 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/PrismLayoutInfo.java @@ -28,7 +28,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import javafx.geometry.Insets; import javafx.geometry.Rectangle2D; import javafx.scene.text.CaretInfo; import javafx.scene.text.LayoutInfo; @@ -44,7 +43,9 @@ public abstract non-sealed class PrismLayoutInfo extends LayoutInfo { protected abstract double lineSpacing(); - protected abstract Insets insets(); + protected abstract double dx(); + + protected abstract double dy(); private final TextLayout layout; @@ -55,9 +56,8 @@ public PrismLayoutInfo(TextLayout layout) { @Override public Rectangle2D getLogicalBounds(boolean includeLineSpacing) { BaseBounds b = layout.getBounds(); - Insets m = insets(); - double dx = m.getLeft(); // TODO rtl? - double dy = m.getTop(); + double dx = dx(); + double dy = dy(); double sp = includeLineSpacing ? lineSpacing() : 0.0; return TextUtils.toRectangle2D(b, dx, dy, sp); } @@ -70,9 +70,8 @@ public int getTextLineCount() { @Override public List getTextLines(boolean includeLineSpacing) { TextLine[] lines = layout.getLines(); - Insets m = insets(); - double dx = m.getLeft(); // TODO rtl? - double dy = m.getTop(); + double dx = dx(); + double dy = dy(); double sp = includeLineSpacing ? lineSpacing() : 0.0; int sz = lines.length; @@ -87,9 +86,8 @@ public List getTextLines(boolean includeLineSpacing) { public TextLineInfo getTextLine(int index, boolean includeLineSpacing) { TextLine[] lines = layout.getLines(); Objects.checkIndex(index, lines.length); - Insets m = insets(); - double dx = m.getLeft(); // TODO rtl? - double dy = m.getTop(); + double dx = dx(); + double dy = dy(); double sp = includeLineSpacing ? lineSpacing() : 0.0; return TextUtils.toLineInfo(lines[index], dx, dy, sp); } @@ -111,9 +109,8 @@ public List getUnderlineGeometry(int start, int end) { } private List getGeometry(int start, int end, int type, double lineSpacing) { - Insets m = insets(); - double dx = m.getLeft(); // TODO RTL? - double dy = m.getTop(); + double dx = dx(); + double dy = dy(); ArrayList rv = new ArrayList<>(); layout.getRange(start, end, type, (left, top, right, bottom) -> { @@ -129,9 +126,8 @@ private List getGeometry(int start, int end, int type, double lineS @Override public CaretInfo caretInfoAt(int charIndex, boolean leading) { TextLayout.CaretGeometry g = layout.getCaretGeometry(charIndex, leading); - Insets m = insets(); - double dx = m.getLeft(); // TODO RTL? - double dy = m.getTop(); + double dx = dx(); + double dy = dy(); Rectangle2D[] parts = TextUtils.getCaretRectangles(g, dx, dy); return new PrismCaretInfo(parts); } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java index 52e960d26ec..03dc988ea4d 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java @@ -59,7 +59,6 @@ import javafx.css.converter.SizeConverter; import javafx.geometry.BoundingBox; import javafx.geometry.Bounds; -import javafx.geometry.Insets; import javafx.geometry.NodeOrientation; import javafx.geometry.Point2D; import javafx.geometry.VPos; @@ -2093,8 +2092,13 @@ public double lineSpacing() { } @Override - public Insets insets() { - return Insets.EMPTY; + protected double dx() { + return getLayoutBounds().getMinX(); + } + + @Override + protected double dy() { + return getLayoutBounds().getMinY(); } }; } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index 0693abc74b8..4a26f839f06 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -722,8 +722,13 @@ public double lineSpacing() { } @Override - public Insets insets() { - return getInsets(); + protected double dx() { + return snappedLeftInset(); + } + + @Override + protected double dy() { + return snappedTopInset(); } }; } From 266eb9f31481745cd5d20ea9100917731b370d5b Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 27 May 2025 16:17:28 -0700 Subject: [PATCH 37/43] tests --- .../test/javafx/scene/text/TextFlowTest.java | 149 +++++++++- .../java/test/javafx/scene/text/TextTest.java | 266 +++++++++++++++++- 2 files changed, 393 insertions(+), 22 deletions(-) diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/text/TextFlowTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/text/TextFlowTest.java index cab5618b91d..b548e016e19 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/text/TextFlowTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/text/TextFlowTest.java @@ -25,21 +25,28 @@ package test.javafx.scene.text; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import javafx.geometry.Bounds; +import javafx.geometry.Insets; +import javafx.geometry.Point2D; +import javafx.scene.Scene; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Path; +import javafx.scene.shape.PathElement; +import javafx.scene.text.Font; +import javafx.scene.text.HitInfo; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; -import javafx.scene.layout.VBox; -import javafx.scene.Scene; import javafx.stage.Stage; -import test.com.sun.javafx.pgstub.StubToolkit; - -import com.sun.javafx.tk.Toolkit; - import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import com.sun.javafx.tk.Toolkit; +import test.com.sun.javafx.pgstub.StubToolkit; public class TextFlowTest { + private static final double EPSILON = 0.00001; + @Test public void testTabSize() { Toolkit tk = Toolkit.getToolkit(); @@ -111,6 +118,130 @@ public void testTabSize() { } finally { stage.hide(); } + } + + private static Text text(String text) { + Text t = new Text(text); + t.setFont(new Font("System", 12.0)); + return t; + } + // new StubTextLayout generates prodictable text shapes + private static void checkNear(PathElement[] em, double ex, double ey, double ew, double eh) { + Bounds b = new Path(em).getBoundsInLocal(); + double x = b.getMinX(); + double y = b.getMinY(); + double w = b.getWidth(); + double h = b.getHeight(); + assertEquals(ex, x, EPSILON); + assertEquals(ey, y, EPSILON); + assertEquals(ew, w, EPSILON); + assertEquals(eh, h, EPSILON); + } + + private void checkNear(HitInfo h, int expectedCharIndex, boolean expectedLeading, int expectedInsert) { + assertEquals(expectedCharIndex, h.getCharIndex()); + assertEquals(expectedLeading, h.isLeading()); + assertEquals(expectedInsert, h.getInsertionIndex()); + } + + @Test + public void caretShape() { + int first = 0; + int second = 6; + + TextFlow f = new TextFlow(text("01234\n56789")); + checkNear(f.caretShape(first, true), -1, -1, 2, 14); + checkNear(f.caretShape(second, true), -1, 11, 2, 14); + checkNear(f.getCaretShape(first, true), -1, -1, 2, 14); + checkNear(f.getCaretShape(second, true), -1, 11, 2, 14); + + f.setPadding(new Insets(100)); + // legacy implementation accounts for no insets + checkNear(f.caretShape(first, true), -1, -1, 2, 14); + checkNear(f.caretShape(second, true), -1, 11, 2, 14); + // new implementation accounts for insets + checkNear(f.getCaretShape(first, true), 99, 99, 2, 14); + checkNear(f.getCaretShape(second, true), 99, 111, 2, 14); + + f.setLineSpacing(50); + // legacy implementation accounts neither for insets nor line spacing + checkNear(f.caretShape(first, true), -1, -1, 2, 14); + checkNear(f.caretShape(second, true), -1, 61, 2, 14); + // new implementation accounts for insets and line spacing + checkNear(f.getCaretShape(first, true), 99, 99, 2, 14); + checkNear(f.getCaretShape(second, true), 99, 161, 2, 14); + } + + @Test + public void hitInfo() { + double first = 0; + double second = 12; + double padding = 100; + double lineSpacing = 50; + + TextFlow f = new TextFlow(text("01234\n56789")); + checkNear(f.hitTest(new Point2D(0, first)), 0, true, 0); + checkNear(f.hitTest(new Point2D(0, second)), 6, true, 6); + checkNear(f.getHitInfo(new Point2D(0, first)), 0, true, 0); + checkNear(f.getHitInfo(new Point2D(0, second)), 6, true, 6); + + f.setPadding(new Insets(padding)); + // legacy implementation accounts for no insets + checkNear(f.hitTest(new Point2D(padding, padding + first)), 11, false, 12); + checkNear(f.hitTest(new Point2D(padding, padding + second)), 11, false, 12); + // new implementation accounts for insets + checkNear(f.getHitInfo(new Point2D(padding, padding + first)), 0, true, 0); + checkNear(f.getHitInfo(new Point2D(padding, padding + second)), 6, true, 6); + + f.setLineSpacing(lineSpacing); + // legacy implementation accounts neither for insets nor line spacing + checkNear(f.hitTest(new Point2D(padding, padding + first)), 10, false, 11); + checkNear(f.hitTest(new Point2D(padding, padding + second)), 10, false, 11); + // new implementation accounts for insets and line spacing + checkNear(f.getHitInfo(new Point2D(padding, padding + first)), 0, true, 0); + checkNear(f.getHitInfo(new Point2D(padding, padding + lineSpacing + second)), 6, true, 6); + } + + @Test + public void rangeShape() { + TextFlow f = new TextFlow(text("01234\n56789")); + checkNear(f.rangeShape(0, 10), -1, -1, 62, 26); + checkNear(f.getRangeShape(0, 10, false), -1, -1, 62, 26); + checkNear(f.getRangeShape(0, 10, true), -1, -1, 62, 26); + + f.setPadding(new Insets(100)); + // legacy implementation accounts for no insets + checkNear(f.rangeShape(0, 10), -1, -1, 62, 26); + // new implementation accounts for insets + checkNear(f.getRangeShape(0, 10, false), 99, 99, 62, 26); + checkNear(f.getRangeShape(0, 10, true), 99, 99, 62, 26); + + f.setLineSpacing(50); + // legacy implementation accounts neither for insets nor line spacing + checkNear(f.rangeShape(0, 10), -1, -1, 62, 76); + // new implementation accounts for insets and line spacing + checkNear(f.getRangeShape(0, 10, false), 99, 99, 62, 76); + checkNear(f.getRangeShape(0, 10, true), 99, 99, 62, 126); + } + + @Test + public void strikeThroughShape() { + TextFlow f = new TextFlow(text("01234567890")); + checkNear(f.getStrikeThroughShape(0, 10), -1, 8.6, 122, 3); + + f.setPadding(new Insets(100)); + checkNear(f.getStrikeThroughShape(0, 10), 99, 108.6, 122, 3); + } + + @Test + public void underlineShape() { + TextFlow f = new TextFlow(text("01234567890")); + checkNear(f.underlineShape(0, 10), -1, 9.6, 122, 3); + checkNear(f.getUnderlineShape(0, 10), -1, 9.6, 122, 3); + + f.setPadding(new Insets(100)); + checkNear(f.underlineShape(0, 10), -1, 9.6, 122, 3); + checkNear(f.getUnderlineShape(0, 10), 99, 109.6, 122, 3); } -} \ No newline at end of file +} diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/text/TextTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/text/TextTest.java index f30144e2afd..75a0fe4fa34 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/text/TextTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/text/TextTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, 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 @@ -25,24 +25,30 @@ package test.javafx.scene.text; -import javafx.scene.text.Font; -import javafx.scene.text.Text; -import javafx.scene.layout.HBox; -import javafx.scene.Scene; -import javafx.stage.Stage; -import test.com.sun.javafx.pgstub.StubToolkit; - -import com.sun.javafx.tk.Toolkit; - -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; +import javafx.geometry.Bounds; +import javafx.geometry.Point2D; +import javafx.geometry.VPos; +import javafx.scene.Scene; +import javafx.scene.layout.HBox; +import javafx.scene.shape.Path; +import javafx.scene.shape.PathElement; +import javafx.scene.text.Font; +import javafx.scene.text.HitInfo; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import org.junit.jupiter.api.Test; +import com.sun.javafx.tk.Toolkit; +import test.com.sun.javafx.pgstub.StubToolkit; public class TextTest { + private static final double EPSILON = 0.00001; + @Test public void testCtors() { Text t1 = new Text(); @@ -318,5 +324,239 @@ public void testTabSize() { } finally { stage.hide(); } - } + } + + private static Text text(String text) { + Text t = new Text(text); + t.setFont(new Font("System", 12.0)); + return t; + } + + // new StubTextLayout generates prodictable text shapes + private static void checkNear(PathElement[] em, double ex, double ey, double ew, double eh) { + Bounds b = new Path(em).getBoundsInLocal(); + double x = b.getMinX(); + double y = b.getMinY(); + double w = b.getWidth(); + double h = b.getHeight(); + assertEquals(ex, x, EPSILON); + assertEquals(ey, y, EPSILON); + assertEquals(ew, w, EPSILON); + assertEquals(eh, h, EPSILON); + } + + private void checkNear(HitInfo h, int expectedCharIndex, boolean expectedLeading, int expectedInsert) { + assertEquals(expectedCharIndex, h.getCharIndex()); + assertEquals(expectedLeading, h.isLeading()); + assertEquals(expectedInsert, h.getInsertionIndex()); + } + + @Test + public void caretShape() { + int first = 0; + int second = 6; + + Text t = text("01234\n56789"); + + // top + t.setTextOrigin(VPos.TOP); + t.setLineSpacing(0); + checkNear(t.caretShape(first, true), -1, -1, 2, 14); + checkNear(t.caretShape(second, true), -1, 11, 2, 14); + t.setLineSpacing(50); + checkNear(t.caretShape(first, true), -1, -1, 2, 14); + checkNear(t.caretShape(second, true), -1, 61, 2, 14); + + // baseline + t.setTextOrigin(VPos.BASELINE); + t.setLineSpacing(0); + checkNear(t.caretShape(first, true), -1, -10.6, 2, 14); + checkNear(t.caretShape(second, true), -1, 1.4, 2, 14); + t.setLineSpacing(50); + checkNear(t.caretShape(first, true), -1, -10.6, 2, 14); + checkNear(t.caretShape(second, true), -1, 51.4, 2, 14); + + // center + t.setTextOrigin(VPos.CENTER); + t.setLineSpacing(0); + checkNear(t.caretShape(first, true), -1, -13, 2, 14); + checkNear(t.caretShape(second, true), -1, -1, 2, 14); + t.setLineSpacing(50); + checkNear(t.caretShape(first, true), -1, -38, 2, 14); + checkNear(t.caretShape(second, true), -1, 24, 2, 14); + + // bottom + t.setTextOrigin(VPos.BOTTOM); + t.setLineSpacing(0); + checkNear(t.caretShape(first, true), -1, -25, 2, 14); + checkNear(t.caretShape(second, true), -1, -13, 2, 14); + t.setLineSpacing(50); + checkNear(t.caretShape(first, true), -1, -75, 2, 14); + checkNear(t.caretShape(second, true), -1, -13, 2, 14); + } + + @Test + public void hitInfo() { + double first = 0; + double second = 12; + double baseline = -9; + double height = -24; + double lineSpacing = 50; + + Text t = text("01234\n56789"); + + // top + t.setTextOrigin(VPos.TOP); + t.setLineSpacing(0); + checkNear(t.hitTest(new Point2D(0, first)), 0, true, 0); + checkNear(t.hitTest(new Point2D(0, second)), 6, true, 6); + t.setLineSpacing(lineSpacing); + checkNear(t.hitTest(new Point2D(0, first)), 0, true, 0); + checkNear(t.hitTest(new Point2D(0, lineSpacing + second)), 6, true, 6); + + // baseline + t.setTextOrigin(VPos.BASELINE); + t.setLineSpacing(0); + checkNear(t.hitTest(new Point2D(0, baseline + first)), 0, true, 0); + checkNear(t.hitTest(new Point2D(0, baseline + second)), 6, true, 6); + t.setLineSpacing(lineSpacing); + checkNear(t.hitTest(new Point2D(0, baseline + first)), 0, true, 0); + checkNear(t.hitTest(new Point2D(0, baseline + lineSpacing + second)), 6, true, 6); + + // center + t.setTextOrigin(VPos.CENTER); + t.setLineSpacing(0); + checkNear(t.hitTest(new Point2D(0, height / 2 + first)), 0, true, 0); + checkNear(t.hitTest(new Point2D(0, height / 2 + second)), 6, true, 6); + t.setLineSpacing(lineSpacing); + checkNear(t.hitTest(new Point2D(0, height / 2 + first)), 0, true, 0); + checkNear(t.hitTest(new Point2D(0, height / 2 + lineSpacing + second)), 6, true, 6); + + // bottom + t.setTextOrigin(VPos.BOTTOM); + t.setLineSpacing(0); + checkNear(t.hitTest(new Point2D(0, height + first)), 0, true, 0); + checkNear(t.hitTest(new Point2D(0, height + second)), 6, true, 6); + t.setLineSpacing(lineSpacing); + checkNear(t.hitTest(new Point2D(0, height + first)), 0, true, 0); + checkNear(t.hitTest(new Point2D(0, height + lineSpacing + second)), 6, true, 6); + } + + @Test + public void rangeShape() { + Text t = text("01234\n56789"); + double lineSpacing = 50; + + // top + t.setTextOrigin(VPos.TOP); + t.setLineSpacing(0); + checkNear(t.rangeShape(0, 10), -1, -1, 62, 26); + checkNear(t.getRangeShape(0, 10, false), -1, -1, 62, 26); + checkNear(t.getRangeShape(0, 10, true), -1, -1, 62, 26); + t.setLineSpacing(lineSpacing); + checkNear(t.rangeShape(0, 10), -1, -1, 62, 76); + checkNear(t.getRangeShape(0, 10, false), -1, -1, 62, 76); + checkNear(t.getRangeShape(0, 10, true), -1, -1, 62, 126); + + // baseline + t.setTextOrigin(VPos.BASELINE); + t.setLineSpacing(0); + checkNear(t.rangeShape(0, 10), -1, -10.6, 62, 26); + checkNear(t.getRangeShape(0, 10, false), -1, -10.6, 62, 26); + checkNear(t.getRangeShape(0, 10, true), -1, -10.6, 62, 26); + t.setLineSpacing(lineSpacing); + checkNear(t.rangeShape(0, 10), -1, -10.6, 62, 76); + checkNear(t.getRangeShape(0, 10, false), -1, -10.6, 62, 76); + checkNear(t.getRangeShape(0, 10, true), -1, -10.6, 62, 126); + + // center + t.setTextOrigin(VPos.CENTER); + t.setLineSpacing(0); + checkNear(t.rangeShape(0, 10), -1, -13, 62, 26); + checkNear(t.getRangeShape(0, 10, false), -1, -13, 62, 26); + checkNear(t.getRangeShape(0, 10, true), -1, -13, 62, 26); + t.setLineSpacing(lineSpacing); + checkNear(t.rangeShape(0, 10), -1, -38, 62, 76); + checkNear(t.getRangeShape(0, 10, false), -1, -38, 62, 76); + checkNear(t.getRangeShape(0, 10, true), -1, -38, 62, 126); + + // bottom + t.setTextOrigin(VPos.BOTTOM); + t.setLineSpacing(0); + checkNear(t.rangeShape(0, 10), -1, -25, 62, 26); + checkNear(t.getRangeShape(0, 10, false), -1, -25, 62, 26); + checkNear(t.getRangeShape(0, 10, true), -1, -25, 62, 26); + t.setLineSpacing(lineSpacing); + checkNear(t.rangeShape(0, 10), -1, -75, 62, 76); + checkNear(t.getRangeShape(0, 10, false), -1, -75, 62, 76); + checkNear(t.getRangeShape(0, 10, true), -1, -75, 62, 126); + } + + @Test + public void strikeThroughShape() { + Text t = text("01234\n56789"); + double lineSpacing = 50; + + // top + t.setTextOrigin(VPos.TOP); + t.setLineSpacing(0); + checkNear(t.getStrikeThroughShape(0, 10), -1, 8.6, 62, 15); + t.setLineSpacing(lineSpacing); + checkNear(t.getStrikeThroughShape(0, 10), -1, 8.6, 62, 65); + + // baseline + t.setTextOrigin(VPos.BASELINE); + t.setLineSpacing(0); + checkNear(t.getStrikeThroughShape(0, 10), -1, -1, 62, 15); + t.setLineSpacing(lineSpacing); + checkNear(t.getStrikeThroughShape(0, 10), -1, -1, 62, 65); + + // center + t.setTextOrigin(VPos.CENTER); + t.setLineSpacing(0); + checkNear(t.getStrikeThroughShape(0, 10), -1, -3.4, 62, 15); + t.setLineSpacing(lineSpacing); + checkNear(t.getStrikeThroughShape(0, 10), -1, -28.4, 62, 65); + + // bottom + t.setTextOrigin(VPos.BOTTOM); + t.setLineSpacing(0); + checkNear(t.getStrikeThroughShape(0, 10), -1, -15.4, 62, 15); + t.setLineSpacing(lineSpacing); + checkNear(t.getStrikeThroughShape(0, 10), -1, -65.4, 62, 65); + } + + @Test + public void underlineShape() { + Text t = text("01234\n56789"); + double lineSpacing = 50; + + // top + t.setTextOrigin(VPos.TOP); + t.setLineSpacing(0); + checkNear(t.underlineShape(0, 10), -1, 9.6, 62, 15); + t.setLineSpacing(lineSpacing); + checkNear(t.underlineShape(0, 10), -1, 9.6, 62, 65); + + // baseline + t.setTextOrigin(VPos.BASELINE); + t.setLineSpacing(0); + checkNear(t.underlineShape(0, 10), -1, 0, 62, 15); + t.setLineSpacing(lineSpacing); + checkNear(t.underlineShape(0, 10), -1, 0, 62, 65); + + // center + t.setTextOrigin(VPos.CENTER); + t.setLineSpacing(0); + checkNear(t.underlineShape(0, 10), -1, -2.4, 62, 15); + t.setLineSpacing(lineSpacing); + checkNear(t.underlineShape(0, 10), -1, -27.4, 62, 65); + + // bottom + t.setTextOrigin(VPos.BOTTOM); + t.setLineSpacing(0); + checkNear(t.underlineShape(0, 10), -1, -14.4, 62, 15); + t.setLineSpacing(lineSpacing); + checkNear(t.underlineShape(0, 10), -1, -64.4, 62, 65); + } } From 03c66a41cdd982ad0999e2c5c6456b5a5733d748 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 28 May 2025 12:43:38 -0700 Subject: [PATCH 38/43] layout info --- .../src/main/java/javafx/scene/text/TextFlow.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index b148cca8872..9b3a9dea706 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -293,8 +293,8 @@ public final PathElement[] rangeShape(int start, int end) { * @param includeLineSpacing determines whether the result includes the line spacing * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 25 + * @see LayoutInfo#getSelectionGeometry(int, int, boolean) */ - // TODO see also LayoutInfo.getSelectionGeometry public final PathElement[] getRangeShape(int start, int end, boolean includeLineSpacing) { double lineSpacing = includeLineSpacing ? getLineSpacing() : 0.0; return getRange(start, end, TextLayout.TYPE_TEXT, true, lineSpacing); @@ -321,8 +321,8 @@ public final PathElement[] underlineShape(int start, int end) { * @param end the end character index (non-inclusive) for the range * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 25 + * @see LayoutInfo#getUnderlineGeometry(int, int) */ - // TODO see also LayoutInfo.getUnderlineGeometry public final PathElement[] getUnderlineShape(int start, int end) { return getRange(start, end, TextLayout.TYPE_UNDERLINE, true, 0.0); } @@ -334,8 +334,8 @@ public final PathElement[] getUnderlineShape(int start, int end) { * @param end the end character index (non-inclusive) for the range * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 25 + * @see LayoutInfo#getStrikeThroughGeometry(int, int) */ - // TODO see also LayoutInfo.getStrikeThroughGeometry public final PathElement[] getStrikeThroughShape(int start, int end) { return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH, true, 0.0); } From c1d7029c3610ca84ceb1e4409349891aa3a5f1ef Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 17 Jun 2025 08:37:43 -0700 Subject: [PATCH 39/43] cleanup --- .../java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java | 1 + .../java/test/robot/javafx/scene/Text_TextLayout_Test.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java b/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java index 21479f17800..4803336cc82 100644 --- a/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java +++ b/tests/system/src/test/java/test/robot/javafx/scene/TextFlow_TextLayout_Test.java @@ -47,6 +47,7 @@ import javafx.stage.Stage; import javafx.stage.StageStyle; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java b/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java index 9d26f83174b..965ebd3fd1f 100644 --- a/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java +++ b/tests/system/src/test/java/test/robot/javafx/scene/Text_TextLayout_Test.java @@ -43,11 +43,14 @@ import javafx.scene.text.Font; import javafx.scene.text.LayoutInfo; import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; import javafx.scene.text.TextLineInfo; import javafx.stage.Stage; import javafx.stage.StageStyle; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import test.util.Util; From 16ed27eb496ed5854d0d581094060fe645798ce9 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 9 Jul 2025 08:25:34 -0700 Subject: [PATCH 40/43] review comments --- .../src/main/java/javafx/scene/text/Text.java | 3 +-- .../src/main/java/javafx/scene/text/TextFlow.java | 8 +++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java index 3008fa1cf3d..799cafe5263 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java @@ -1088,8 +1088,7 @@ public final PathElement[] caretShape(int charIndex, boolean caretBias) { /** * Returns the shape for the range of the text in local coordinates. - *

- * NOTE: this shapes returned do not include line spacing. + * The returned value does not include line spacing. * * @param start the beginning character index for the range * @param end the end character index (non-inclusive) for the range diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index 9b3a9dea706..6fdfa454452 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -194,7 +194,7 @@ private void checkOrientation() { /** * Maps local point to {@link HitInfo} in the content. *

- * NOTE: this method may return incorrect value with non-empty border or padding. + * NOTE: this method does not take border or padding into account. * * @param point the specified point to be tested * @return a {@code HitInfo} representing the character index found @@ -236,7 +236,7 @@ public final HitInfo getHitInfo(javafx.geometry.Point2D point) { /** * Returns shape of caret in local coordinates. *

- * NOTE: this method does not account for padding/borders. + * NOTE: this method does not take border or padding into account. * * @param charIndex the character index for the caret * @param leading whether the caret is biased on the leading edge of the character @@ -270,7 +270,7 @@ public PathElement[] getCaretShape(int charIndex, boolean leading) { *

* NOTES: *

    - *
  • this method may return incorrect values with non-empty padding or border + *
  • this method does not take border or padding into account *
  • the shapes returned do not include line spacing *
* @@ -302,6 +302,8 @@ public final PathElement[] getRangeShape(int start, int end, boolean includeLine /** * Returns the shape for the underline in local coordinates. + *

+ * NOTE: this method does not take border or padding into account. * * @param start the beginning character index for the range * @param end the end character index (non-inclusive) for the range From d466ebc9d208815d94fb7cfb762a47cb388e34e3 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 9 Jul 2025 13:31:08 -0700 Subject: [PATCH 41/43] link --- .../src/main/java/javafx/scene/text/TextFlow.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index 6fdfa454452..06d7d25219b 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -199,7 +199,7 @@ private void checkOrientation() { * @param point the specified point to be tested * @return a {@code HitInfo} representing the character index found * @since 9 - * @deprecated replaced by {@code getHitInfo()} + * @deprecated replaced by {@link #getHitInfo()} */ @Deprecated(since="25") public final HitInfo hitTest(javafx.geometry.Point2D point) { @@ -242,7 +242,7 @@ public final HitInfo getHitInfo(javafx.geometry.Point2D point) { * @param leading whether the caret is biased on the leading edge of the character * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 9 - * @deprecated replaced by {@code getCaretShape()} + * @deprecated replaced by {@link #getCaretShape()} */ @Deprecated(since="25") public PathElement[] caretShape(int charIndex, boolean leading) { @@ -278,7 +278,7 @@ public PathElement[] getCaretShape(int charIndex, boolean leading) { * @param end the end character index (non-inclusive) for the range * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 9 - * @deprecated replaced by {@code getRangeShape()} + * @deprecated replaced by {@link #getRangeShape()} */ @Deprecated(since="25") public final PathElement[] rangeShape(int start, int end) { @@ -309,7 +309,7 @@ public final PathElement[] getRangeShape(int start, int end, boolean includeLine * @param end the end character index (non-inclusive) for the range * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 21 - * @deprecated replaced by {@code getUnderlineShape()} + * @deprecated replaced by {@link #getUnderlineShape()} */ @Deprecated(since="25") public final PathElement[] underlineShape(int start, int end) { From a2099f0264df84f9cbbeb3597c26f5637a93242e Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 9 Jul 2025 13:44:36 -0700 Subject: [PATCH 42/43] javadoc --- .../src/main/java/javafx/scene/text/TextFlow.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index 06d7d25219b..71f82796b8e 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -199,7 +199,7 @@ private void checkOrientation() { * @param point the specified point to be tested * @return a {@code HitInfo} representing the character index found * @since 9 - * @deprecated replaced by {@link #getHitInfo()} + * @deprecated replaced by {@link #getHitInfo(javafx.geometry.Point2D)} */ @Deprecated(since="25") public final HitInfo hitTest(javafx.geometry.Point2D point) { @@ -242,7 +242,7 @@ public final HitInfo getHitInfo(javafx.geometry.Point2D point) { * @param leading whether the caret is biased on the leading edge of the character * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 9 - * @deprecated replaced by {@link #getCaretShape()} + * @deprecated replaced by {@link #getCaretShape(int, boolean)} */ @Deprecated(since="25") public PathElement[] caretShape(int charIndex, boolean leading) { @@ -278,7 +278,7 @@ public PathElement[] getCaretShape(int charIndex, boolean leading) { * @param end the end character index (non-inclusive) for the range * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 9 - * @deprecated replaced by {@link #getRangeShape()} + * @deprecated replaced by {@link #getRangeShape(int, int, boolean)} */ @Deprecated(since="25") public final PathElement[] rangeShape(int start, int end) { @@ -309,7 +309,7 @@ public final PathElement[] getRangeShape(int start, int end, boolean includeLine * @param end the end character index (non-inclusive) for the range * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 21 - * @deprecated replaced by {@link #getUnderlineShape()} + * @deprecated replaced by {@link #getUnderlineShape(int, int)} */ @Deprecated(since="25") public final PathElement[] underlineShape(int start, int end) { From a283479fca372f2c8d21c65adb196fe716cb0837 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 11 Jul 2025 08:08:01 -0700 Subject: [PATCH 43/43] if --- .../src/main/java/javafx/scene/text/TextFlow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java index 71f82796b8e..893e7e39392 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java @@ -465,7 +465,7 @@ public boolean usesMirroring() { private PathElement[] getRange(int start, int end, int type, boolean accountForInsets, double lineSpacing) { double dx; double dy; - if(accountForInsets) { + if (accountForInsets) { dx = snappedLeftInset(); dy = snappedTopInset(); } else {