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 03dc988ea4d..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 @@ -1056,13 +1056,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; } @@ -1088,14 +1088,31 @@ public final PathElement[] caretShape(int charIndex, boolean caretBias) { /** * Returns the shape for the range of the text in local coordinates. + * 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 * @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); } /** @@ -1107,7 +1124,19 @@ 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); + } + + /** + * 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[] getStrikeThroughShape(int start, int end) { + return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH, 0.0); } private float getYAdjustment(BaseBounds bounds) { @@ -1769,7 +1798,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"); 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 4a26f839f06..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 @@ -193,11 +193,15 @@ private void checkOrientation() { /** * Maps local point to {@link HitInfo} in the content. + *

+ * 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 * @since 9 + * @deprecated replaced by {@link #getHitInfo(javafx.geometry.Point2D)} */ + @Deprecated(since="25") public final HitInfo hitTest(javafx.geometry.Point2D point) { if (point != null) { TextLayout layout = getTextLayout(); @@ -210,42 +214,132 @@ 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 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 * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 9 + * @deprecated replaced by {@link #getCaretShape(int, boolean)} */ + @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: + *

* * @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 {@link #getRangeShape(int, int, boolean)} */ + @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 + * @see LayoutInfo#getSelectionGeometry(int, int, boolean) + */ + 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); } /** * 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 * @return an array of {@code PathElement} which can be used to create a {@code Shape} * @since 21 + * @deprecated replaced by {@link #getUnderlineShape(int, int)} */ + @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 + * @see LayoutInfo#getUnderlineGeometry(int, int) + */ + public final PathElement[] getUnderlineShape(int start, int end) { + return getRange(start, end, TextLayout.TYPE_UNDERLINE, true, 0.0); + } + + /** + * 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 + * @see LayoutInfo#getStrikeThroughGeometry(int, int) + */ + public final PathElement[] getStrikeThroughShape(int start, int end) { + return getRange(start, end, TextLayout.TYPE_STRIKETHROUGH, true, 0.0); } @Override @@ -368,9 +462,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 { 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); + } }