Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Align TextFlowExt with future getLayoutInfo #1246

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 35 additions & 24 deletions richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package org.fxmisc.richtext;

import static org.fxmisc.richtext.model.TwoDimensional.Bias.*;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.IndexRange;
import org.fxmisc.richtext.model.TwoLevelNavigator;

import javafx.scene.shape.PathElement;
import javafx.scene.text.HitInfo;
import javafx.scene.text.TextFlow;
Expand All @@ -19,9 +16,14 @@
* Adds additional API to {@link TextFlow}.
*/
class TextFlowExt extends TextFlow {

private TextFlowLayout layout;

/*
* Rename to getLayoutInfo() and delete once JavaFX
* [PR1596](https://github.com/openjdk/jfx/pull/1596)
* is integrated and released. Also delete
* TextFlowLayout and TextFlowSpan.
*/
private TextFlowLayout textLayout()
{
if ( layout == null ) {
Expand All @@ -31,25 +33,25 @@ private TextFlowLayout textLayout()
}

int getLineCount() {
return textLayout().getLineCount();
return textLayout().getTextLineCount();
}

int getLineStartPosition(int charIdx) {
TwoLevelNavigator navigator = textLayout().getTwoLevelNavigator();
int currentLineIndex = navigator.offsetToPosition(charIdx, Forward).getMajor();
return navigator.position(currentLineIndex, 0).toOffset();
return textLayout().getTextLine( getLineOfCharacter(charIdx) ).getStart();
}

int getLineEndPosition(int charIdx) {
TwoLevelNavigator navigator = textLayout().getTwoLevelNavigator();
int currentLineIndex = navigator.offsetToPosition(charIdx, Forward).getMajor() + 1;
int minor = (currentLineIndex == getLineCount()) ? 0 : -1;
return navigator.position(currentLineIndex, minor).toOffset();
int line = getLineOfCharacter( charIdx );
int end = textLayout().getTextLine( line ).getEnd();
if ( line < (getLineCount() - 1) ) end--; // trailing space
return end;
}

int getLineOfCharacter(int charIdx) {
TwoLevelNavigator navigator = textLayout().getTwoLevelNavigator();
return navigator.offsetToPosition(charIdx, Forward).getMajor();
var layout = textLayout();
return IntStream.range( 0, getLineCount() )
.filter( l -> charIdx < layout.getTextLine( l ).getEnd() )
.findFirst().orElse( Math.max(0,getLineCount()-1) );
}

PathElement[] getCaretShape(int charIdx, boolean isLeading) {
Expand Down Expand Up @@ -83,9 +85,9 @@ PathElement[] getUnderlineShape(int from, int to) {
PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadius) {
// get a Path for the text underline
List<PathElement> result = new ArrayList<>();

PathElement[] shape = rangeShape( from, to );
// The shape is a closed Path for one or more rectangles AROUND the selected text.
// The shape is a closed Path for one or more rectangles AROUND the selected text.
// shape: [MoveTo origin, LineTo top R, LineTo bottom R, LineTo bottom L, LineTo origin, *]

// Extract the bottom left and right coordinates for each rectangle to get the underline path.
Expand Down Expand Up @@ -136,25 +138,34 @@ PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadi
}

CharacterHit hitLine(double x, int lineIndex) {
return hit(x, textLayout().getLineCenter( lineIndex ));
Rectangle2D r = textLayout().getTextLine( lineIndex ).getBounds();
double y = r.getMinY() + r.getHeight() / 2.0;
return hit( x, y, lineIndex );
}

CharacterHit hit(double x, double y) {
TextFlowSpan span = textLayout().getLineSpan( (float) y );
Rectangle2D lineBounds = span.getBounds();

var layout = textLayout();
int line = IntStream.range( 0, getLineCount() )
.filter( l -> y < layout.getTextLine( l ).getBounds().getMaxY() )
.findFirst().orElse( Math.max(0,getLineCount()-1) );
return hit( x, y, line );
}

CharacterHit hit(double x, double y, int line) {

Rectangle2D lineBounds = textLayout().getTextLine( line ).getBounds();
HitInfo hit = hitTest(new Point2D(x, y));
int charIdx = hit.getCharIndex();
boolean leading = hit.isLeading();

if (y >= span.getBounds().getMaxY()) {
if (y >= lineBounds.getMaxY()) {
return CharacterHit.insertionAt(charIdx);
}

if ( ! leading && getLineCount() > 1) {
// If this is a wrapped paragraph and hit character is at end of hit line, make sure that the
// "character hit" stays at the end of the hit line (and not at the beginning of the next line).
leading = (getLineOfCharacter(charIdx) + 1 < getLineCount() && charIdx + 1 >= span.getStart() + span.getLength());
leading = (getLineOfCharacter(charIdx) + 1 < getLineCount() && charIdx + 1 >= textLayout().getTextLine( line ).getEnd());
}

if(x < lineBounds.getMinX() || x > lineBounds.getMaxX()) {
Expand Down
32 changes: 18 additions & 14 deletions richtextfx/src/main/java/org/fxmisc/richtext/TextFlowLayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,41 +34,45 @@ class TextFlowLayout
}


@Deprecated
float getLineCenter( int lineNo ) {
return getLineCount() > 0 ? lineMetrics.get( lineNo ).getCenterY() : 1.0f;
return getTextLineCount() > 0 ? lineMetrics.get( lineNo ).getCenterY() : 1.0f;
}


@Deprecated
int getLineLength( int lineNo ) {
return getLineSpan( lineNo ).getLength();
return getTextLine( lineNo ).getLength();
}


TextFlowSpan getLineSpan( int lineNo ) {
return getLineCount() > 0 ? lineMetrics.get( lineNo ) : EMPTY_SPAN;
TextFlowSpan getTextLine( int lineNo ) {
return getTextLineCount() > 0 ? lineMetrics.get( lineNo ) : EMPTY_SPAN;
}


@Deprecated
TextFlowSpan getLineSpan( float y ) {
final int lastLine = getLineCount();
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::getLineCount, this::getLineLength );
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 getLineCount() {
int getTextLineCount() {

if ( lineCount > -1 ) return lineCount;

lineCount = 0;
Expand All @@ -77,7 +81,7 @@ int getLineCount() {
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 );
Expand All @@ -88,12 +92,12 @@ int getLineCount() {
if ( totLines > 0.0 ) lines--;
totLines += lines;
}
else if ( nodeMinY >= prevMaxY ) { // Node is on next line
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;
Expand Down Expand Up @@ -121,14 +125,14 @@ else if ( nodeMinY >= prevMaxY ) { // Node is on
}
}
else {
// Adjust current line metrics with additional Text or Node embedded in this line
// 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ float getCenterY() {

int getStart() { return start; }
int getLength() { return length; }
int getEnd() { return start + length; }
double getHeight() { return height; }
double getWidth() { return width; }

Expand Down