11package org .fxmisc .richtext ;
22
3- import static org .fxmisc .richtext .model .TwoDimensional .Bias .*;
4-
53import java .util .ArrayList ;
64import java .util .List ;
5+ import java .util .stream .IntStream ;
76
87import javafx .geometry .Point2D ;
98import javafx .geometry .Rectangle2D ;
109import javafx .scene .control .IndexRange ;
11- import org .fxmisc .richtext .model .TwoLevelNavigator ;
12-
1310import javafx .scene .shape .PathElement ;
1411import javafx .scene .text .HitInfo ;
1512import javafx .scene .text .TextFlow ;
1916 * Adds additional API to {@link TextFlow}.
2017 */
2118class TextFlowExt extends TextFlow {
22-
19+
2320 private TextFlowLayout layout ;
24-
21+ /*
22+ * Rename to getLayoutInfo() and delete once JavaFX
23+ * [PR1596](https://github.com/openjdk/jfx/pull/1596)
24+ * is integrated and released. Also delete
25+ * TextFlowLayout and TextFlowSpan.
26+ */
2527 private TextFlowLayout textLayout ()
2628 {
2729 if ( layout == null ) {
@@ -31,25 +33,22 @@ private TextFlowLayout textLayout()
3133 }
3234
3335 int getLineCount () {
34- return textLayout ().getLineCount ();
36+ return textLayout ().getTextLineCount ();
3537 }
3638
3739 int getLineStartPosition (int charIdx ) {
38- TwoLevelNavigator navigator = textLayout ().getTwoLevelNavigator ();
39- int currentLineIndex = navigator .offsetToPosition (charIdx , Forward ).getMajor ();
40- return navigator .position (currentLineIndex , 0 ).toOffset ();
40+ return textLayout ().getTextLineStart ( getLineOfCharacter (charIdx ) );
4141 }
4242
4343 int getLineEndPosition (int charIdx ) {
44- TwoLevelNavigator navigator = textLayout ().getTwoLevelNavigator ();
45- int currentLineIndex = navigator .offsetToPosition (charIdx , Forward ).getMajor () + 1 ;
46- int minor = (currentLineIndex == getLineCount ()) ? 0 : -1 ;
47- return navigator .position (currentLineIndex , minor ).toOffset ();
44+ return textLayout ().getTextLineEnd ( getLineOfCharacter (charIdx ) );
4845 }
4946
5047 int getLineOfCharacter (int charIdx ) {
51- TwoLevelNavigator navigator = textLayout ().getTwoLevelNavigator ();
52- return navigator .offsetToPosition (charIdx , Forward ).getMajor ();
48+ var layout = textLayout ();
49+ return IntStream .range ( 0 , getLineCount () )
50+ .filter ( l -> charIdx <= layout .getTextLineEnd ( l ) )
51+ .findFirst ().orElse ( Math .max (0 ,getLineCount ()-1 ) );
5352 }
5453
5554 PathElement [] getCaretShape (int charIdx , boolean isLeading ) {
@@ -83,9 +82,9 @@ PathElement[] getUnderlineShape(int from, int to) {
8382 PathElement [] getUnderlineShape (int from , int to , double offset , double waveRadius , double doubleGap ) {
8483 // get a Path for the text underline
8584 List <PathElement > result = new ArrayList <>();
86-
85+
8786 PathElement [] shape = rangeShape ( from , to );
88- // The shape is a closed Path for one or more rectangles AROUND the selected text.
87+ // The shape is a closed Path for one or more rectangles AROUND the selected text.
8988 // shape: [MoveTo origin, LineTo top R, LineTo bottom R, LineTo bottom L, LineTo origin, *]
9089
9190 boolean doubleLine = (doubleGap > 0.0 );
@@ -152,25 +151,34 @@ PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadi
152151 }
153152
154153 CharacterHit hitLine (double x , int lineIndex ) {
155- return hit (x , textLayout ().getLineCenter ( lineIndex ));
154+ Rectangle2D r = textLayout ().getLineBounds ( lineIndex );
155+ double y = r .getMinY () + r .getHeight () / 2.0 ;
156+ return hit ( x , y , lineIndex );
156157 }
157158
158159 CharacterHit hit (double x , double y ) {
159- TextFlowSpan span = textLayout ().getLineSpan ( (float ) y );
160- Rectangle2D lineBounds = span .getBounds ();
161-
160+ var layout = textLayout ();
161+ int line = IntStream .range ( 0 , getLineCount () )
162+ .filter ( l -> y < layout .getLineBounds ( l ).getMaxY () )
163+ .findFirst ().orElse ( Math .max (0 ,getLineCount ()-1 ) );
164+ return hit ( x , y , line );
165+ }
166+
167+ CharacterHit hit (double x , double y , int line ) {
168+
169+ Rectangle2D lineBounds = textLayout ().getLineBounds ( line );
162170 HitInfo hit = hitTest (new Point2D (x , y ));
163171 int charIdx = hit .getCharIndex ();
164172 boolean leading = hit .isLeading ();
165173
166- if (y >= span . getBounds () .getMaxY ()) {
174+ if (y >= lineBounds .getMaxY ()) {
167175 return CharacterHit .insertionAt (charIdx );
168176 }
169177
170178 if ( ! leading && getLineCount () > 1 ) {
171179 // If this is a wrapped paragraph and hit character is at end of hit line, make sure that the
172180 // "character hit" stays at the end of the hit line (and not at the beginning of the next line).
173- leading = (getLineOfCharacter (charIdx ) + 1 < getLineCount () && charIdx + 1 >= span . getStart () + span . getLength ( ));
181+ leading = (getLineOfCharacter (charIdx ) + 1 < getLineCount () && charIdx + 1 >= textLayout (). getTextLineEnd ( line ));
174182 }
175183
176184 if (x < lineBounds .getMinX () || x > lineBounds .getMaxX ()) {
0 commit comments