Skip to content

Commit

Permalink
Merge pull request #317 from afester/dottedUnderline
Browse files Browse the repository at this point in the history
Support underlined text with some customization of the underline.
  • Loading branch information
TomasMikula committed May 24, 2016
2 parents 2d34cef + 45b1241 commit 7c32514
Show file tree
Hide file tree
Showing 7 changed files with 429 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package org.fxmisc.richtext.demo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.BreakIterator;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.StyleClassedTextArea;
import org.fxmisc.richtext.StyleSpans;
import org.fxmisc.richtext.StyleSpansBuilder;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class SpellChecking extends Application {

private static final Set<String> dictionary = new HashSet<String>();

public static void main(String[] args) {
launch(args);
}

@Override
public void start(Stage primaryStage) {
StyleClassedTextArea textArea = new StyleClassedTextArea();
textArea.setWrapText(true);

textArea.richChanges()
.filter(ch -> !ch.getInserted().equals(ch.getRemoved())) // XXX
.subscribe(change -> {
textArea.setStyleSpans(0, computeHighlighting(textArea.getText()));
});

// load the dictionary
try (InputStream input = getClass().getResourceAsStream("spellchecking.dict");
BufferedReader br = new BufferedReader(new InputStreamReader(input))) {
String line;
while ((line = br.readLine()) != null) {
dictionary.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}

// load the sample document
InputStream input2 = getClass().getResourceAsStream("spellchecking.txt");
try(java.util.Scanner s = new java.util.Scanner(input2)) {
String document = s.useDelimiter("\\A").hasNext() ? s.next() : "";
textArea.replaceText(0, 0, document);
}

Scene scene = new Scene(new StackPane(new VirtualizedScrollPane<>(textArea)), 600, 400);
scene.getStylesheets().add(getClass().getResource("spellchecking.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setTitle("Spell Checking Demo");
primaryStage.show();
}


private static StyleSpans<Collection<String>> computeHighlighting(String text) {

StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>();

BreakIterator wb = BreakIterator.getWordInstance();
wb.setText(text);

int lastIndex = wb.first();
int lastKwEnd = 0;
while(lastIndex != BreakIterator.DONE) {
int firstIndex = lastIndex;
lastIndex = wb.next();

if (lastIndex != BreakIterator.DONE
&& Character.isLetterOrDigit(text.charAt(firstIndex))) {
String word = text.substring(firstIndex, lastIndex).toLowerCase();
if (!dictionary.contains(word)) {
spansBuilder.add(Collections.emptyList(), firstIndex - lastKwEnd);
spansBuilder.add(Collections.singleton("underlined"), lastIndex - firstIndex);
lastKwEnd = lastIndex;
}
System.err.println();
}
}
spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);

return spansBuilder.create();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.underlined {
-fx-underline-color: red;
-fx-underline-dash-array: 2 2;
-fx-underline-width: 1;
-fx-underline-cap: butt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
a
applied
basic
brown
but
could
document
dog
fox
here
if
is
its
jumps
lazy
no
over
quick
rendering
sample
see
styling
the
there
this
were
you
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The quik brown fox jumps over the lazy dog.
Ths is a sample dokument.
There is no styling aplied, but if there were, you could see its basic rndering here.
109 changes: 99 additions & 10 deletions richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.fxmisc.richtext;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
Expand All @@ -17,6 +18,7 @@
import javafx.scene.paint.Paint;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.StrokeLineCap;

import org.reactfx.value.Val;
import org.reactfx.value.Var;
Expand Down Expand Up @@ -45,6 +47,7 @@ public ObjectProperty<Paint> highlightTextFillProperty() {
private final Path caretShape = new Path();
private final Path selectionShape = new Path();
private final List<Path> backgroundShapes = new ArrayList<>();
private final List<Path> underlineShapes = new ArrayList<>();

// proxy for caretShape.visibleProperty() that implements unbind() correctly.
// This is necessary due to a bug in BooleanPropertyBase#unbind().
Expand Down Expand Up @@ -108,14 +111,22 @@ public ParagraphText(Paragraph<PS, S> par, BiConsumer<? super TextExt, S> applyS
getChildren().add(t);

// add corresponding background node (empty)

Path backgroundShape = new Path();
backgroundShape.setManaged(false);
backgroundShape.setStrokeWidth(0);
backgroundShape.layoutXProperty().bind(leftInset);
backgroundShape.layoutYProperty().bind(topInset);
backgroundShapes.add(backgroundShape);
getChildren().add(0, backgroundShape);

// add corresponding underline node (empty)
Path underlineShape = new Path();
underlineShape.setManaged(false);
underlineShape.setStrokeWidth(0);
underlineShape.layoutXProperty().bind(leftInset);
underlineShape.layoutYProperty().bind(topInset);
underlineShapes.add(underlineShape);
getChildren().add(0, underlineShape);
}
}

Expand Down Expand Up @@ -181,23 +192,101 @@ private void updateBackgroundShapes() {
FilteredList<Node> nodeList = getChildren().filtered(node -> node instanceof TextExt);
for (Node node : nodeList) {
TextExt text = (TextExt) node;
Path backgroundShape = backgroundShapes.get(index++);
int end = start + text.getText().length();

// Set fill
Paint paint = text.backgroundFillProperty().get();
if (paint != null) {
backgroundShape.setFill(paint);
updateBackground(text, start, end, index);
updateUnderline(text, start, end, index);

start = end;
index++;
}
}


/**
* Updates the background shape for a text segment.
*
* @param text The text node which specified the style attributes
* @param start The index of the first character
* @param end The index of the last character
* @param index The index of the background shape
*/
private void updateBackground(TextExt text, int start, int end, int index) {
// Set fill
Paint paint = text.backgroundFillProperty().get();
if (paint != null) {
Path backgroundShape = backgroundShapes.get(index);
backgroundShape.setFill(paint);

// Set path elements
PathElement[] shape = getRangeShape(start, end);
backgroundShape.getElements().setAll(shape);
}
}


// Set path elements
PathElement[] shape = getRangeShape(start, end);
backgroundShape.getElements().setAll(shape);
/**
* Updates the shape which renders the text underline.
*
* @param text The text node which specified the style attributes
* @param start The index of the first character
* @param end The index of the last character
* @param index The index of the background shape
*/
private void updateUnderline(TextExt text, int start, int end, int index) {

// get all CSS properties for the underline

Paint underlineColor = text.underlineColorProperty().get();
Number underlineWidth = text.underlineWidthProperty().get();

// get the dash array - JavaFX CSS parser seems to return either a Number[] array
// or a single value, depending on whether only one or more than one value has been
// specified in the CSS
Double[] underlineDashArray = null;
Object underlineDashArrayProp = text.underlineDashArrayProperty().get();
if (underlineDashArrayProp != null) {
if (underlineDashArrayProp.getClass().isArray()) {
Number[] numberArray = (Number[]) underlineDashArrayProp;
underlineDashArray = new Double[numberArray.length];
int idx = 0;
for (Number d : numberArray) {
underlineDashArray[idx++] = (Double) d;
}
} else {
underlineDashArray = new Double[1];
underlineDashArray[0] = ((Double) underlineDashArrayProp).doubleValue();
}
}

start = end;
StrokeLineCap underlineCap = text.underlineCapProperty().get();

// apply style and render the underline

Path underlineShape = underlineShapes.get(index);
if (underlineColor != null) {
underlineShape.setStroke(underlineColor);
}
if (underlineWidth != null) {
underlineShape.setStrokeWidth(underlineWidth.doubleValue());
}
if (underlineDashArray != null) {
underlineShape.getStrokeDashArray().addAll(underlineDashArray);
underlineShape.setStrokeLineCap(StrokeLineCap.BUTT);
}
if (underlineCap != null) {
underlineShape.setStrokeLineCap(underlineCap);
}

if (underlineColor != null || underlineWidth != null) {

// Set path elements
PathElement[] shape = getUnderlineShape(start, end);
underlineShape.getElements().setAll(shape);
}
}


@Override
protected void layoutChildren() {
super.layoutChildren();
Expand Down
Loading

0 comments on commit 7c32514

Please sign in to comment.