-
Notifications
You must be signed in to change notification settings - Fork 237
/
Copy pathTextFlowLayout.java
150 lines (117 loc) · 5.33 KB
/
TextFlowLayout.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package org.fxmisc.richtext;
import java.util.ArrayList;
import java.util.List;
import org.fxmisc.richtext.model.TwoLevelNavigator;
import javafx.beans.Observable;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.PathElement;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
/**
* @author Jurgen ([email protected])
*/
class TextFlowLayout
{
private TextFlow flow;
private List<TextFlowSpan> lineMetrics = new ArrayList<>();
private int lineCount = -1;
private static final TextFlowSpan EMPTY_SPAN = new TextFlowSpan( 0, 0, 0, 0, 0 );
TextFlowLayout( TextFlow tf ) {
tf.getChildren().addListener( (Observable ob) -> lineCount = -1 );
tf.widthProperty().addListener( (Observable ob) -> lineCount = -1 );
flow = tf;
}
@Deprecated
float getLineCenter( int lineNo ) {
return getTextLineCount() > 0 ? lineMetrics.get( lineNo ).getCenterY() : 1.0f;
}
@Deprecated
int getLineLength( int lineNo ) {
return getTextLine( lineNo ).getLength();
}
TextFlowSpan getTextLine( int lineNo ) {
return getTextLineCount() > 0 ? lineMetrics.get( lineNo ) : EMPTY_SPAN;
}
@Deprecated
TextFlowSpan getLineSpan( float y ) {
final int lastLine = getTextLineCount();
if ( lastLine < 1 ) return EMPTY_SPAN;
return lineMetrics.stream().filter( tfs -> y < tfs.getBounds().getMaxY() )
.findFirst().orElse( lineMetrics.get( Math.max(0,lastLine-1) ) );
}
@Deprecated
TwoLevelNavigator getTwoLevelNavigator() {
return new TwoLevelNavigator( this::getTextLineCount, this::getLineLength );
}
/*
* Iterate through the nodes in the TextFlow to determine the number of lines of text.
* Also calculates the following metrics for each line along the way: line height,
* line width, centerY, length (character count), start (character offset from 1st line)
*/
int getTextLineCount() {
if ( lineCount > -1 ) return lineCount;
lineCount = 0;
lineMetrics.clear();
double totLines = 0.0, prevMaxY = -1.0;
int totCharSoFar = 0;
for ( Node n : flow.getChildrenUnmodifiable() ) if ( n.isManaged() ) {
Bounds nodeBounds = n.getBoundsInParent();
int length = (n instanceof Text) ? ((Text) n).getText().length() : 1;
PathElement[] shape = flow.rangeShape( totCharSoFar, totCharSoFar+length );
double lines = Math.max( 1.0, Math.floor( shape.length / 5 ) );
double nodeMinY = Math.max( 0.0, nodeBounds.getMinY() );
if ( lines > 1.0 ) { // Staggered multiline text node
if ( totLines > 0.0 ) lines--;
totLines += lines;
}
else if ( nodeMinY >= prevMaxY ) { // Node is on next line
totLines++;
}
if ( lineMetrics.size() < totLines ) { // Add additional lines
if ( shape.length == 0 ) {
lineMetrics.add( new TextFlowSpan( totCharSoFar, length, nodeMinY, nodeBounds.getWidth(), nodeBounds.getHeight() ) );
totCharSoFar += length;
}
else for ( int ele = 1; ele < shape.length; ele += 5 ) {
// Calculate the segment's line's length and width up to this point
LineTo eleLine = (LineTo) shape[ele];
double segWidth = eleLine.getX(), lineMinY = eleLine.getY();
double charHeight = ((LineTo) shape[ele+1]).getY() - lineMinY;
Point2D endPoint = new Point2D( segWidth-1, lineMinY + charHeight / 2 );
// hitTest queries TextFlow layout internally and returns the position of the
// last char (nearest endPoint) on the line, irrespective of the current Text node !
int segLen = flow.hitTest( endPoint ).getCharIndex();
segLen -= totCharSoFar - 1;
if ( ele == 1 && nodeMinY < prevMaxY ) {
adjustLineMetrics( segLen, segWidth - ((MoveTo) shape[ele-1]).getX(), charHeight );
}
else {
lineMetrics.add( new TextFlowSpan( totCharSoFar, segLen, lineMinY, segWidth, charHeight ) );
}
totCharSoFar += segLen;
}
}
else {
// Adjust current line metrics with additional Text or Node embedded in this line
adjustLineMetrics( length, nodeBounds.getWidth(), nodeBounds.getHeight() );
totCharSoFar += length;
}
prevMaxY = Math.max( prevMaxY, nodeBounds.getMaxY() );
}
lineCount = (int) totLines;
if ( lineCount > 0 ) return lineCount;
lineCount = -1;
return 0;
}
private void adjustLineMetrics( int length, double width, double height ) {
TextFlowSpan span = lineMetrics.get( lineMetrics.size()-1 );
span.addLengthAndWidth( length, width );
if ( height > span.getHeight() ) {
span.setHeight( height );
}
}
}