Skip to content

Commit 7815815

Browse files
committed
Align TextFlowExt with future getLayoutInfo
1 parent 269bb5e commit 7815815

File tree

2 files changed

+65
-36
lines changed

2 files changed

+65
-36
lines changed

richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package org.fxmisc.richtext;
22

3-
import static org.fxmisc.richtext.model.TwoDimensional.Bias.*;
4-
53
import java.util.ArrayList;
64
import java.util.List;
5+
import java.util.stream.IntStream;
76

87
import javafx.geometry.Point2D;
98
import javafx.geometry.Rectangle2D;
109
import javafx.scene.control.IndexRange;
11-
import org.fxmisc.richtext.model.TwoLevelNavigator;
12-
1310
import javafx.scene.shape.PathElement;
1411
import javafx.scene.text.HitInfo;
1512
import javafx.scene.text.TextFlow;
@@ -19,9 +16,14 @@
1916
* Adds additional API to {@link TextFlow}.
2017
*/
2118
class 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()) {

richtextfx/src/main/java/org/fxmisc/richtext/TextFlowLayout.java

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import javafx.beans.Observable;
99
import javafx.geometry.Bounds;
1010
import javafx.geometry.Point2D;
11+
import javafx.geometry.Rectangle2D;
1112
import javafx.scene.Node;
1213
import javafx.scene.shape.LineTo;
1314
import javafx.scene.shape.MoveTo;
@@ -34,41 +35,61 @@ class TextFlowLayout
3435
}
3536

3637

38+
@Deprecated
3739
float getLineCenter( int lineNo ) {
38-
return (lineNo >= 0 && getLineCount() > 0) ? lineMetrics.get( lineNo ).getCenterY() : 1.0f;
40+
return (lineNo >= 0 && getTextLineCount() > 0) ? lineMetrics.get( lineNo ).getCenterY() : 1.0f;
3941
}
4042

4143

44+
@Deprecated
4245
int getLineLength( int lineNo ) {
4346
return getLineSpan( lineNo ).getLength();
4447
}
4548

4649

4750
TextFlowSpan getLineSpan( int lineNo ) {
48-
return (lineNo >= 0 && getLineCount() > 0) ? lineMetrics.get( lineNo ) : EMPTY_SPAN;
51+
return (lineNo >= 0 && getTextLineCount() > 0) ? lineMetrics.get( lineNo ) : EMPTY_SPAN;
4952
}
5053

5154

55+
@Deprecated
5256
TextFlowSpan getLineSpan( float y ) {
53-
final int lastLine = getLineCount();
57+
final int lastLine = getTextLineCount();
5458
if ( lastLine < 1 ) return EMPTY_SPAN;
5559
return lineMetrics.stream().filter( tfs -> y < tfs.getBounds().getMaxY() )
5660
.findFirst().orElse( lineMetrics.get( Math.max(0,lastLine-1) ) );
5761
}
5862

5963

64+
@Deprecated
6065
TwoLevelNavigator getTwoLevelNavigator() {
61-
return new TwoLevelNavigator( this::getLineCount, this::getLineLength );
66+
return new TwoLevelNavigator( this::getTextLineCount, this::getLineLength );
6267
}
63-
68+
69+
70+
Rectangle2D getLineBounds( int line ) {
71+
return getLineSpan( line ).getBounds();
72+
}
73+
74+
int getTextLineStart( int line ) {
75+
return getLineSpan( line ).getStart();
76+
}
77+
78+
int getTextLineEnd( int line ) {
79+
TextFlowSpan span = getLineSpan( line );
80+
int end = span.getStart() + span.getLength();
81+
if ( line < (getTextLineCount() - 1) ) end--;
82+
return end;
83+
}
84+
6485

6586
/*
6687
* Iterate through the nodes in the TextFlow to determine the number of lines of text.
6788
* Also calculates the following metrics for each line along the way: line height,
6889
* line width, centerY, length (character count), start (character offset from 1st line)
6990
*/
70-
int getLineCount() {
71-
91+
int getTextLineCount() {
92+
7293
if ( lineCount > -1 ) return lineCount;
7394

7495
lineCount = 0;
@@ -77,7 +98,7 @@ int getLineCount() {
7798
int totCharSoFar = 0;
7899

79100
for ( Node n : flow.getChildrenUnmodifiable() ) if ( n.isManaged() ) {
80-
101+
81102
Bounds nodeBounds = n.getBoundsInParent();
82103
int length = (n instanceof Text) ? ((Text) n).getText().length() : 1;
83104
PathElement[] shape = flow.rangeShape( totCharSoFar, totCharSoFar+length );
@@ -88,12 +109,12 @@ int getLineCount() {
88109
if ( totLines > 0.0 ) lines--;
89110
totLines += lines;
90111
}
91-
else if ( nodeMinY >= prevMaxY ) { // Node is on next line
112+
else if ( nodeMinY >= prevMaxY ) { // Node is on next line
92113
totLines++;
93114
}
94115

95116
if ( lineMetrics.size() < totLines ) { // Add additional lines
96-
117+
97118
if ( shape.length == 0 ) {
98119
lineMetrics.add( new TextFlowSpan( totCharSoFar, length, nodeMinY, nodeBounds.getWidth(), nodeBounds.getHeight() ) );
99120
totCharSoFar += length;
@@ -121,14 +142,14 @@ else if ( nodeMinY >= prevMaxY ) { // Node is on
121142
}
122143
}
123144
else {
124-
// Adjust current line metrics with additional Text or Node embedded in this line
145+
// Adjust current line metrics with additional Text or Node embedded in this line
125146
adjustLineMetrics( length, nodeBounds.getWidth(), nodeBounds.getHeight() );
126147
totCharSoFar += length;
127148
}
128149

129150
prevMaxY = Math.max( prevMaxY, nodeBounds.getMaxY() );
130151
}
131-
152+
132153
lineCount = (int) totLines;
133154
if ( lineCount > 0 ) return lineCount;
134155
lineCount = -1;

0 commit comments

Comments
 (0)