adding experimental code for a new styled text control
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLine.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLine.java
new file mode 100644
index 0000000..5e45405
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLine.java
@@ -0,0 +1,155 @@
+package org.eclipse.fx.ui.controls.styledtext_ng;

+

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.stream.Collectors;

+

+import org.eclipse.fx.core.Range;

+import org.eclipse.fx.core.Subscription;

+import org.eclipse.fx.ui.controls.styledtext.StyledString;

+import org.eclipse.fx.ui.controls.styledtext.StyledStringSegment;

+

+import javafx.css.CssMetaData;

+import javafx.css.SimpleStyleableBooleanProperty;

+import javafx.css.SimpleStyleableObjectProperty;

+import javafx.css.Styleable;

+import javafx.css.StyleableProperty;

+import javafx.css.StyleablePropertyFactory;

+import javafx.scene.Group;

+import javafx.scene.Node;

+import javafx.scene.layout.Region;

+import javafx.scene.paint.Color;

+import javafx.scene.paint.Paint;

+

+public class StyledLine extends Region {

+	private final StyledLineRenderer renderer;

+	private final List<Subscription> ranges = new ArrayList<>();

+

+	public StyledLine(StyledLineRendererFactory factory) {

+		this.renderer = factory.createRenderer();

+		Node node = renderer.getNode();

+		getChildren().add(node);

+	}

+

+	@Override

+	protected void layoutChildren() {

+		getManagedChildren().forEach( c -> c.resizeRelocate(0, 0, c.prefWidth(-1), c.prefHeight(-1)));

+	}

+

+	public void setStyledString(StyledString string) {

+

+		Map<List<String>, SegmentNode> map = new HashMap<>();

+		renderer.combinedAction( () -> {

+			int idx = 0;

+			ranges.forEach(Subscription::dispose);

+			for( StyledStringSegment s : string.getSegmentList() ) {

+				SegmentNode node = map.computeIfAbsent(s.getStyleClass(), l -> new SegmentNode(l,this.renderer));

+				this.ranges.add(node.addRange(new Range(idx, idx += s.getText().length())));

+			}

+			this.renderer.setText(string.toString().toCharArray());

+		});

+

+		getChildren().addAll( map.values() );

+	}

+

+	public void setFont(String family, double size) {

+		this.renderer.setFont(family, size);

+	}

+

+	static class SegmentNode extends Group {

+		final StyledLineRenderer renderer;

+

+		final List<Range> ranges = new ArrayList<>();

+		List<Subscription> boldSubscriptions = new ArrayList<>();

+		List<Subscription> italicSubscriptions = new ArrayList<>();

+		List<Subscription> fillSubscriptions = new ArrayList<>();

+

+		public SegmentNode(List<String> styleclasses, StyledLineRenderer renderer) {

+			setManaged(false);

+			this.renderer = renderer;

+			getStyleClass().setAll(styleclasses);

+		}

+

+		public Subscription addRange(Range r) {

+			this.ranges.add(r);

+			if( bold.getValue() ) {

+				boldSubscriptions.add( this.renderer.setBold(r) );

+			}

+

+			if( italic.getValue() ) {

+				italicSubscriptions.add(this.renderer.setItalic(r));

+			}

+

+			fillSubscriptions.add(this.renderer.setForeground(fill.getValue(), r));

+

+			return () -> {

+				if( bold.getValue() ) {

+					boldSubscriptions.add( this.renderer.setBold(r) );

+				}

+

+				if( italic.getValue() ) {

+					italicSubscriptions.add(this.renderer.setItalic(r));

+				}

+

+				fillSubscriptions.add(this.renderer.setForeground(fill.getValue(), r));

+

+				this.ranges.remove(r);

+			};

+		}

+

+		private static StyleablePropertyFactory<SegmentNode> FACTORY = new StyleablePropertyFactory<>(Group.getClassCssMetaData());

+

+		private static final CssMetaData<SegmentNode, Boolean> BOLD = FACTORY.createBooleanCssMetaData("-efx-styled-bold", s -> s.bold, false, false);

+		private static final CssMetaData<SegmentNode, Boolean> ITALIC = FACTORY.createBooleanCssMetaData("-efx-styled-italic", s -> s.italic, false, false);

+		private static final CssMetaData<SegmentNode, Paint> FILL = FACTORY.createPaintCssMetaData("-efx-fill", s -> s.fill, Color.BLACK, false);

+

+		private final StyleableProperty<Boolean> bold = new SimpleStyleableBooleanProperty(BOLD, this, "bold") {

+			protected void invalidated() {

+				super.invalidated();

+

+				SegmentNode.this.renderer.combinedAction( () -> {

+					SegmentNode.this.boldSubscriptions.forEach(Subscription::dispose);

+					SegmentNode.this.boldSubscriptions.clear();

+					if( get() ) {

+						SegmentNode.this.boldSubscriptions = SegmentNode.this.ranges.stream().map( SegmentNode.this.renderer::setBold).collect(Collectors.toList());

+					}

+				});

+			}

+		};

+

+		private final StyleableProperty<Boolean> italic = new SimpleStyleableBooleanProperty(ITALIC, this, "italic") {

+			protected void invalidated() {

+				super.invalidated();

+				SegmentNode.this.renderer.combinedAction( () -> {

+					SegmentNode.this.italicSubscriptions.forEach(Subscription::dispose);

+					SegmentNode.this.italicSubscriptions.clear();

+					if( get() ) {

+						SegmentNode.this.italicSubscriptions = SegmentNode.this.ranges.stream().map( SegmentNode.this.renderer::setItalic).collect(Collectors.toList());

+					}

+				});

+			}

+		};

+

+		private final StyleableProperty<Paint> fill = new SimpleStyleableObjectProperty<Paint>(FILL, this, "fill", Color.BLACK) {

+			protected void invalidated() {

+				super.invalidated();

+				SegmentNode.this.renderer.combinedAction( () -> {

+					Paint p = get();

+					SegmentNode.this.ranges.forEach(r -> SegmentNode.this.renderer.setForeground(p, r));

+				});

+			}

+		};

+

+		public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {

+			return FACTORY.getCssMetaData();

+		}

+

+		@Override

+		public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {

+			return FACTORY.getCssMetaData();

+		}

+

+	}

+}

diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRenderer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRenderer.java
new file mode 100644
index 0000000..f50ed06
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRenderer.java
@@ -0,0 +1,22 @@
+package org.eclipse.fx.ui.controls.styledtext_ng;

+

+import org.eclipse.fx.core.Range;

+import org.eclipse.fx.core.Subscription;

+

+import javafx.scene.Node;

+import javafx.scene.paint.Paint;

+

+public interface StyledLineRenderer {

+	public void setVisibleRange(double minX, double width);

+

+	public void setFont(String family, double size);

+	public Subscription setBold(Range range);

+	public Subscription setItalic(Range range);

+	public Subscription setForeground(Paint paint, Range range);

+	public void combinedAction(Runnable r);

+	public void setText(char[] text);

+

+	public void clearStyles();

+

+	public Node getNode();

+}

diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRendererFactory.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRendererFactory.java
new file mode 100644
index 0000000..b462096
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/StyledLineRendererFactory.java
@@ -0,0 +1,5 @@
+package org.eclipse.fx.ui.controls.styledtext_ng;

+

+public interface StyledLineRendererFactory {

+	public StyledLineRenderer createRenderer();

+}

diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/TestStyledLine.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/TestStyledLine.java
new file mode 100644
index 0000000..72ff010
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/TestStyledLine.java
@@ -0,0 +1,63 @@
+package org.eclipse.fx.ui.controls.styledtext_ng;

+

+import org.eclipse.fx.ui.controls.styledtext.StyledString;

+import org.eclipse.fx.ui.controls.styledtext_ng.internal.SingleCharStyledLineRendererFactory;

+import org.eclipse.fx.ui.controls.styledtext_ng.internal.SingleTextStyledLineRendererFactory;

+

+import javafx.application.Application;

+import javafx.scene.Scene;

+import javafx.scene.control.Button;

+import javafx.scene.control.Label;

+import javafx.scene.control.ToggleButton;

+import javafx.scene.layout.VBox;

+import javafx.stage.Stage;

+

+public class TestStyledLine extends Application {

+

+	public static void main(String[] args) {

+		launch(args);

+	}

+

+	@Override

+	public void start(Stage primaryStage) throws Exception {

+		VBox p = new VBox();

+		StyledString ss = new StyledString();

+		ss.appendSegment("public", "keyword");

+		ss.appendSegment(" ", "default");

+		ss.appendSegment("class", "keyword");

+		ss.appendSegment(" Hello {", "default");

+

+//		{

+//			StyledLine l = new StyledLine(new SingleCharStyledLineRendererFactory());

+//			l.setFont("monospace", 20);

+//			l.setStyledString(ss);

+//			p.getChildren().add(l);

+//		}

+

+		{

+			StyledLine l = new StyledLine(new SingleTextStyledLineRendererFactory());

+			l.setFont("monospace", 20);

+			l.setStyledString(ss);

+			p.getChildren().add(l);

+		}

+

+

+		Scene s = new Scene(p, 400,400);

+

+		String changeColor = getClass().getResource("color-change.css").toExternalForm();

+

+		ToggleButton b = new ToggleButton("Change Styles");

+		b.setOnAction( e -> {

+			if( s.getStylesheets().contains(changeColor) ) {

+				s.getStylesheets().remove(changeColor);

+			} else {

+				s.getStylesheets().add(changeColor);

+			}

+		});

+		p.getChildren().add(b);

+

+		s.getStylesheets().add(getClass().getResource("test.css").toExternalForm());

+		primaryStage.setScene(s);

+		primaryStage.show();

+	}

+}

diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/color-change.css b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/color-change.css
new file mode 100644
index 0000000..5a6509c
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/color-change.css
@@ -0,0 +1,12 @@
+.root {

+	-fx-background-color: black;

+}

+

+.keyword {

+	-efx-styled-bold: true;

+	-efx-fill: #d78b40;

+}

+

+.default {

+	-efx-fill: #b8c4d1;

+}
\ No newline at end of file
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/BaseStyledLineRenderer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/BaseStyledLineRenderer.java
new file mode 100644
index 0000000..756e1a0
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/BaseStyledLineRenderer.java
@@ -0,0 +1,184 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;

+

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.List;

+

+import org.eclipse.fx.core.Subscription;

+import org.eclipse.fx.core.geom.Size;

+import org.eclipse.fx.core.text.TextUtil;

+import org.eclipse.fx.ui.controls.Util;

+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRenderer;

+

+import com.google.common.collect.Range;

+import com.google.common.collect.RangeSet;

+import com.google.common.collect.TreeRangeSet;

+

+import javafx.scene.paint.Paint;

+import javafx.scene.text.Font;

+import javafx.scene.text.FontPosture;

+import javafx.scene.text.FontWeight;

+

+public abstract class BaseStyledLineRenderer implements StyledLineRenderer {

+	char[] originalText = new char[0];

+	char[] displayedText = new char[0];

+	char[] tabReplace = new char[4];

+

+	Font normal;

+	Font bold;

+	Font italic;

+	Font boldItalic;

+	double w;

+	double h;

+

+	private RangeSet<Integer> boldRange = TreeRangeSet.create();

+	private RangeSet<Integer> italicRange = TreeRangeSet.create();

+	private List<PaintRange> paintRanges = new ArrayList<>();

+

+	int flag = 0;

+	boolean combinedAction;

+

+	static final int FONTS = 1;

+	static final int FILL = 1 << 1;

+	static final int TEXT = 1 << 2;

+

+	public BaseStyledLineRenderer() {

+		Arrays.fill(tabReplace, ' ');

+	}

+

+	protected RangeSet<Integer> getNormalRange() {

+		TreeRangeSet<Integer> set = TreeRangeSet.create();

+		set.add(Range.closed(0, this.originalText.length));

+		set.removeAll(this.boldRange);

+		set.removeAll(this.italicRange);

+		return set;

+	}

+

+	protected RangeSet<Integer> getBoldRange() {

+		if( this.italicRange.isEmpty() ) {

+			return this.boldRange;

+		}

+		RangeSet<Integer> onlyBold = TreeRangeSet.create(this.boldRange);

+		onlyBold.removeAll(this.italicRange);

+		return onlyBold;

+	}

+

+	protected RangeSet<Integer> getItalicRange() {

+		if( this.boldRange.isEmpty() ) {

+			return this.italicRange;

+		}

+		RangeSet<Integer> onlyItalic = TreeRangeSet.create(this.italicRange);

+		onlyItalic.removeAll(this.boldRange);

+		return onlyItalic;

+	}

+

+	protected RangeSet<Integer> getBoldItalicRange() {

+		if( this.italicRange.isEmpty() && this.boldRange.isEmpty() ) {

+			RangeSet<Integer> italicBoldRange = TreeRangeSet.create(boldRange);

+			italicBoldRange.removeAll(italicRange.complement());

+			return italicBoldRange;

+		}

+		return TreeRangeSet.create();

+	}

+

+	protected List<PaintRange> getPaintRanges() {

+		return paintRanges;

+	}

+

+	@Override

+	public void setFont(String family, double size) {

+		this.normal = Font.font(family, size);

+		this.bold = Font.font(family, FontWeight.BOLD, size);

+		this.italic = Font.font(family, FontPosture.ITALIC, size);

+		this.boldItalic = Font.font(family, FontWeight.BOLD, FontPosture.ITALIC, size);

+

+		Size dim = Util.getSize(this.normal, 'A');

+		this.w = dim.width;

+		this.h = dim.height;

+

+		rebuildFonts();

+	}

+

+	@Override

+	public Subscription setBold(org.eclipse.fx.core.Range r) {

+		Range<Integer> range = Range.closed(r.start, r.end-1);

+		this.boldRange.add(range);

+		rebuildFonts();

+		return () -> {

+			this.boldRange.remove(range);

+			rebuildFonts();

+		};

+	}

+

+	@Override

+	public Subscription setItalic(org.eclipse.fx.core.Range r) {

+		Range<Integer> range = Range.closed(r.start, r.end-1);

+		this.italicRange.add(range);

+		rebuildFonts();

+		return () -> {

+			this.italicRange.remove(range);

+			rebuildFonts();

+		};

+	}

+

+	@Override

+	public Subscription setForeground(Paint paint, org.eclipse.fx.core.Range r) {

+		Range<Integer> closed = Range.closed(r.start, r.end-1);

+		for( PaintRange pr : paintRanges ) {

+			if( pr.range.equals(closed) ) {

+				pr.paint = paint;

+				rebuildFill();

+				return null;

+			}

+		}

+

+		PaintRange range = new PaintRange(paint, closed);

+		this.paintRanges.add(range);

+		rebuildFill();

+		return () -> {

+			this.paintRanges.remove(range);

+			rebuildFill();

+		};

+	}

+

+	@Override

+	public void setText(char[] text) {

+		this.originalText = text;

+		this.displayedText = TextUtil.replaceAll(this.originalText, '\t', this.tabReplace);

+		rebuildText();

+	}

+

+	static class PaintRange {

+		Paint paint;

+		final Range<Integer> range;

+

+		public PaintRange(Paint paint, Range<Integer> range) {

+			this.paint = paint;

+			this.range = range;

+		}

+	}

+

+	public void combinedAction(Runnable r) {

+		this.combinedAction = true;

+		try {

+			r.run();

+		} finally {

+			this.combinedAction = false;

+		}

+

+		if( (this.flag & FONTS) == FONTS ) {

+			rebuildFonts();

+		}

+

+		if( (this.flag & FILL) == FILL ) {

+			rebuildFill();

+		}

+		if( (this.flag & TEXT) == TEXT ) {

+			rebuildText();

+		}

+	}

+

+	protected abstract void rebuildFill();

+	protected abstract void rebuildFonts();

+	protected abstract void rebuildText();

+}

diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/MultiTextStyledLineRenderer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/MultiTextStyledLineRenderer.java
new file mode 100644
index 0000000..03aa4bd
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/MultiTextStyledLineRenderer.java
@@ -0,0 +1,43 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;

+

+import javafx.scene.Node;

+

+public class MultiTextStyledLineRenderer extends BaseStyledLineRenderer {

+

+	@Override

+	public void setVisibleRange(double minX, double width) {

+		// TODO Auto-generated method stub

+

+	}

+

+	@Override

+	public void clearStyles() {

+		// TODO Auto-generated method stub

+

+	}

+

+	@Override

+	public Node getNode() {

+		// TODO Auto-generated method stub

+		return null;

+	}

+

+	@Override

+	protected void rebuildFill() {

+		// TODO Auto-generated method stub

+

+	}

+

+	@Override

+	protected void rebuildFonts() {

+		// TODO Auto-generated method stub

+

+	}

+

+	@Override

+	protected void rebuildText() {

+		// TODO Auto-generated method stub

+

+	}

+

+}

diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRenderer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRenderer.java
new file mode 100644
index 0000000..8454105
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRenderer.java
@@ -0,0 +1,308 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;

+

+import java.util.ArrayList;

+import java.util.List;

+

+import org.eclipse.fx.core.Subscription;

+import org.eclipse.fx.core.Triple;

+import org.eclipse.fx.core.geom.Size;

+import org.eclipse.fx.core.text.TextUtil;

+import org.eclipse.fx.ui.controls.Util;

+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRenderer;

+

+import com.google.common.collect.Range;

+import com.google.common.collect.RangeSet;

+import com.google.common.collect.TreeRangeSet;

+

+import javafx.beans.value.WritableValue;

+import javafx.collections.ObservableList;

+import javafx.scene.Node;

+import javafx.scene.layout.Region;

+import javafx.scene.paint.Color;

+import javafx.scene.paint.Paint;

+import javafx.scene.text.Font;

+import javafx.scene.text.FontPosture;

+import javafx.scene.text.FontWeight;

+import javafx.scene.text.Text;

+

+public class SingleCharStyledLineRenderer implements StyledLineRenderer {

+	private final LayoutPane node;

+	private final int tabAdvance = 4;

+	private int[] tabPositions;

+	private char[] renderedText = new char[0];

+	private char[] originalText = new char[0];

+	double h;

+	double w;

+	List<TextNode> currentNodes = new ArrayList<TextNode>();

+

+	private Font normal;

+	private Font bold;

+	private Font italic;

+	private Font boldItalic;

+

+	private RangeSet<Integer> boldRange = TreeRangeSet.create();

+	private RangeSet<Integer> italicRange = TreeRangeSet.create();

+	private List<PaintRange> paintRanges = new ArrayList<>();

+

+	private boolean combinedAction;

+	private int flag = 0;

+

+	private static final int FONTS = 1;

+	private static final int FILL = 1 << 1;

+

+	public SingleCharStyledLineRenderer() {

+		this.node = new LayoutPane(this);

+	}

+

+	@Override

+	public void setVisibleRange(double minX, double maxX) {

+		// TODO Auto-generated method stub

+

+	}

+

+	@Override

+	public void setFont(String family, double size) {

+		this.normal = Font.font(family, size);

+		this.bold = Font.font(family, FontWeight.BOLD, size);

+		this.italic = Font.font(family, FontPosture.ITALIC, size);

+		this.boldItalic = Font.font(family, FontWeight.BOLD, FontPosture.ITALIC, size);

+

+		Size dim = Util.getSize(this.normal, 'A');

+		this.w = dim.width;

+		this.h = dim.height;

+		rebuildFonts();

+		this.node.requestLayout();

+	}

+

+	@Override

+	public Subscription setBold(org.eclipse.fx.core.Range r) {

+		Range<Integer> range = Range.closed(r.start, r.end-1);

+		this.boldRange.add(range);

+		rebuildFonts();

+		return () -> {

+			this.boldRange.remove(range);

+			rebuildFonts();

+		};

+	}

+

+	@Override

+	public Subscription setItalic(org.eclipse.fx.core.Range r) {

+		Range<Integer> range = Range.closed(r.start, r.end-1);

+		this.italicRange.add(range);

+		rebuildFonts();

+		return () -> {

+			this.italicRange.remove(range);

+			rebuildFonts();

+		};

+	}

+

+	@Override

+	public Subscription setForeground(Paint paint, org.eclipse.fx.core.Range r) {

+		PaintRange range = new PaintRange(paint, Range.closed(r.start, r.end-1));

+		this.paintRanges.add(range);

+		rebuildFill();

+		return () -> {

+			this.paintRanges.remove(range);

+			rebuildFill();

+		};

+	}

+

+	static class PaintRange {

+		final Paint paint;

+		final Range<Integer> range;

+

+		public PaintRange(Paint paint, Range<Integer> range) {

+			this.paint = paint;

+			this.range = range;

+		}

+	}

+

+	@Override

+	public void setText(char[] text) {

+		Triple<char[], int[], int[]> rv = TextUtil.replaceTabBySpace(text, this.tabAdvance);

+		this.tabPositions = rv.value3;

+		this.originalText = text;

+		if( rv.value3.length == 0 ) {

+			this.renderedText = text;

+		} else {

+			this.renderedText = rv.value1;

+		}

+		rebuild();

+	}

+

+	private Paint getPaint(int index) {

+		for( PaintRange r : this.paintRanges ) {

+			if( r.range.contains(index) ) {

+				return r.paint;

+			}

+		}

+

+		return Color.BLACK;

+	}

+

+	private void rebuild() {

+		List<TextNode> l = new ArrayList<>(this.originalText.length);

+		for( int i = 0; i < this.renderedText.length; i++ ) {

+			if( this.renderedText[i] != ' ' ) {

+				TextNode node = new TextNode(i,this.renderedText[i]);

+				node.setFontData(this.boldRange.contains(i), this.italicRange.contains(i));

+				node.setFill(getPaint(i));

+				updateFont(node);

+				l.add( node );

+

+			}

+		}

+		this.currentNodes = l;

+		this.node.getChildren().setAll(this.currentNodes);

+

+		this.node.requestLayout();

+	}

+

+	private void rebuildFonts() {

+		if( this.combinedAction ) {

+			this.flag |= FONTS;

+		} else {

+			for( TextNode t : this.currentNodes ) {

+				t.setFontData(this.boldRange.contains(t.index), this.italicRange.contains(t.index));

+			}

+			this.currentNodes.forEach(this::updateFont);

+		}

+	}

+

+	private void rebuildFill() {

+		if( this.combinedAction ) {

+			this.flag |= FILL;

+		} else {

+			this.currentNodes.forEach( n -> n.setFill(getPaint(n.index)));

+		}

+	}

+

+	public void combinedAction(Runnable r) {

+		this.combinedAction = true;

+		try {

+			r.run();

+		} finally {

+			this.combinedAction = false;

+		}

+

+		if( (this.flag & FONTS) == FONTS ) {

+			rebuildFonts();

+		}

+

+		if( (this.flag & FILL) == FILL ) {

+			rebuildFill();

+		}

+	}

+

+	private void updateFont(TextNode node) {

+		if( node.bold && node.italic ) {

+			node.setFont(this.boldItalic);

+		} else if( node.bold ) {

+			node.setFont(this.bold);

+		} else if( node.italic ) {

+			node.setFont(this.italic);

+		} else {

+			node.setFont(this.normal);

+		}

+	}

+

+	@Override

+	public void clearStyles() {

+		// TODO Auto-generated method stub

+

+	}

+

+	@Override

+	public Node getNode() {

+		return this.node;

+	}

+

+	public double getCharWidth() {

+		return this.w;

+	}

+

+	public double getCharHeight() {

+		return this.h;

+	}

+

+	public double getWidth() {

+		return this.w * this.renderedText.length;

+	}

+

+	public double getHeight() {

+		return this.h;

+	}

+

+	static class TextNode extends Text {

+		final int index;

+		boolean bold;

+		boolean italic;

+

+		public TextNode(int index, char c) {

+			super(TextUtil.toString(c));

+			setManaged(false);

+			this.index = index;

+		}

+

+		public void setFontData(boolean bold, boolean italic) {

+			this.bold = bold;

+			this.italic = italic;

+		}

+	}

+

+	static class LayoutPane extends Region {

+		private SingleCharStyledLineRenderer r;

+		public LayoutPane(SingleCharStyledLineRenderer r) {

+			this.r = r;

+		}

+

+		@Override

+		public ObservableList<Node> getChildren() {

+			return super.getChildren();

+		}

+

+//		@Override

+//		protected void impl_processCSS(WritableValue<Boolean> unused) {

+//			// no css in this area

+//		}

+

+		@Override

+		protected void layoutChildren() {

+			double w = this.r.getCharWidth();

+			double h = this.r.getCharHeight();

+			this.r.currentNodes.forEach( n -> n.resizeRelocate(n.index * w, 0, w, h));

+		}

+

+		@Override

+		protected double computeMinWidth(double height) {

+			return r.getWidth();

+		}

+

+		@Override

+		protected double computeMinHeight(double width) {

+			System.err.println("MIn: " + getHeight());

+			return r.getCharHeight();

+		}

+

+		@Override

+		protected double computePrefHeight(double width) {

+			System.err.println("PREF: " + getHeight());

+			return r.getCharHeight();

+		}

+

+		@Override

+		protected double computePrefWidth(double height) {

+			return r.getWidth();

+		}

+

+		@Override

+		protected double computeMaxHeight(double width) {

+			return r.getCharHeight();

+		}

+

+		@Override

+		protected double computeMaxWidth(double height) {

+			return r.getWidth();

+		}

+	}

+}

diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRendererFactory.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRendererFactory.java
new file mode 100644
index 0000000..142b101
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleCharStyledLineRendererFactory.java
@@ -0,0 +1,13 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;

+

+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRenderer;

+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRendererFactory;

+

+public class SingleCharStyledLineRendererFactory implements StyledLineRendererFactory {

+

+	@Override

+	public StyledLineRenderer createRenderer() {

+		return new SingleCharStyledLineRenderer();

+	}

+

+}

diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRenderer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRenderer.java
new file mode 100644
index 0000000..001216a
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRenderer.java
@@ -0,0 +1,173 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;

+

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.Map.Entry;

+

+import org.eclipse.fx.core.text.TextUtil;

+import org.eclipse.fx.ui.controls.Util;

+

+import com.google.common.collect.RangeSet;

+import com.google.common.collect.TreeRangeSet;

+

+import javafx.collections.ObservableList;

+import javafx.scene.Node;

+import javafx.scene.layout.Region;

+import javafx.scene.paint.Paint;

+import javafx.scene.text.Font;

+import javafx.scene.text.Text;

+

+public class SingleTextStyledLineRenderer extends BaseStyledLineRenderer {

+	private final LayoutPane node;

+

+	public SingleTextStyledLineRenderer() {

+		this.node = new LayoutPane();

+	}

+

+	@Override

+	public void setVisibleRange(double minX, double width) {

+		// TODO Auto-generated method stub

+

+	}

+

+	@Override

+	public void clearStyles() {

+		// TODO Auto-generated method stub

+

+	}

+

+	@Override

+	public Node getNode() {

+		return node;

+	}

+

+

+	@Override

+	protected void rebuildFill() {

+		rebuildText();

+	}

+

+	@Override

+	protected void rebuildFonts() {

+		rebuildText();

+	}

+

+	@Override

+	protected void rebuildText() {

+		if( this.combinedAction ) {

+			this.flag |= TEXT;

+			return;

+		}

+

+		this.node.requestLayout();

+	}

+

+	private void refreshLayout() {

+		if( this.flag == 0 ) {

+			return;

+		}

+		this.flag = 0;

+		RangeSet<Integer> normalFont = getNormalRange();

+

+		RangeSet<Integer> boldRange = getBoldRange();

+		RangeSet<Integer> italicRange = getItalicRange();

+		RangeSet<Integer> boldItalicRange = getBoldItalicRange();

+

+		Map<Paint, RangeSet<Integer>> normalTextColors = new HashMap<>();

+		Map<Paint, RangeSet<Integer>> boldTextColors = new HashMap<>();

+		Map<Paint, RangeSet<Integer>> italicTextColors = new HashMap<>();

+		Map<Paint, RangeSet<Integer>> italicBoldTextColors = new HashMap<>();

+

+		for( PaintRange r : getPaintRanges() ) {

+			if( ! normalFont.subRangeSet(r.range).isEmpty() ) {

+				RangeSet<Integer> set = normalTextColors.computeIfAbsent(r.paint, p -> TreeRangeSet.create());

+				set.addAll(normalFont.subRangeSet(r.range));

+			} else {

+				if( ! boldItalicRange.isEmpty() && ! boldItalicRange.subRangeSet(r.range).isEmpty() ) {

+					RangeSet<Integer> set = italicBoldTextColors.computeIfAbsent(r.paint, p -> TreeRangeSet.create());

+					set.addAll(boldItalicRange.subRangeSet(r.range));

+				} else if( ! italicRange.subRangeSet(r.range).isEmpty() ) {

+					RangeSet<Integer> set = italicTextColors.computeIfAbsent(r.paint, p -> TreeRangeSet.create());

+					set.addAll(italicRange.subRangeSet(r.range));

+				} else if( ! boldRange.subRangeSet(r.range).isEmpty() ) {

+					RangeSet<Integer> set = boldTextColors.computeIfAbsent(r.paint, p -> TreeRangeSet.create());

+					set.addAll(boldRange.subRangeSet(r.range));

+				}

+			}

+		}

+

+		Arrays.fill(this.tabReplace, ' ');

+

+		List<Node> l = new ArrayList<>();

+		l.addAll(createTextNodes(normalTextColors, this.originalText, this.tabReplace, this.normal));

+		l.addAll(createTextNodes(boldTextColors, this.originalText, this.tabReplace, this.bold));

+		l.addAll(createTextNodes(italicTextColors, this.originalText, this.tabReplace, this.italic));

+		l.addAll(createTextNodes(italicBoldTextColors, this.originalText, this.tabReplace, this.boldItalic));

+

+		l.stream().forEach( n -> System.err.println(n));

+

+		this.node.getChildren().setAll(l);

+	}

+

+	private static List<Text> createTextNodes(Map<Paint, RangeSet<Integer>> textColorRanges, char[] text, char[] tabReplace, Font font) {

+		List<Text> nodes = new ArrayList<>();

+

+		for( Entry<Paint, RangeSet<Integer>> e : textColorRanges.entrySet() ) {

+			char[] txt = TextUtil.replace(text, ' ', ( idx, ch ) -> {

+				return ch != '\t' && ! e.getValue().contains(idx);

+			});

+			Text tNode = new Text(String.valueOf(TextUtil.replaceAll(txt, '\r', tabReplace)));

+			tNode.setFont(font);

+			tNode.setFill(e.getKey());

+			nodes.add(tNode);

+		}

+

+		return nodes;

+	}

+

+	class LayoutPane extends Region {

+		@Override

+		protected ObservableList<Node> getChildren() {

+			return super.getChildren();

+		}

+

+		@Override

+		protected void layoutChildren() {

+			refreshLayout();

+			getChildren().forEach( c -> c.resizeRelocate(0, 0, c.prefWidth(-1), c.prefHeight(-1)));

+		}

+

+		@Override

+		protected double computeMinHeight(double width) {

+			return Util.getSize(normal, ' ').height;

+		}

+

+		@Override

+		protected double computePrefHeight(double width) {

+			return super.computeMinHeight(width);

+		}

+

+		@Override

+		protected double computeMaxHeight(double width) {

+			return super.computeMinHeight(width);

+		}

+

+		@Override

+		protected double computeMinWidth(double height) {

+			return Util.getSize(SingleTextStyledLineRenderer.this.normal, ' ').width * SingleTextStyledLineRenderer.this.displayedText.length;

+		}

+

+		@Override

+		protected double computePrefWidth(double height) {

+			return super.computeMinWidth(height);

+		}

+

+		@Override

+		protected double computeMaxWidth(double height) {

+			return super.computeMinWidth(height);

+		}

+	}

+}

diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRendererFactory.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRendererFactory.java
new file mode 100644
index 0000000..430f44b
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/internal/SingleTextStyledLineRendererFactory.java
@@ -0,0 +1,13 @@
+package org.eclipse.fx.ui.controls.styledtext_ng.internal;

+

+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRenderer;

+import org.eclipse.fx.ui.controls.styledtext_ng.StyledLineRendererFactory;

+

+public class SingleTextStyledLineRendererFactory implements StyledLineRendererFactory {

+

+	@Override

+	public StyledLineRenderer createRenderer() {

+		return new SingleTextStyledLineRenderer();

+	}

+

+}

diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/test.css b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/test.css
new file mode 100644
index 0000000..021aa37
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext_ng/test.css
@@ -0,0 +1,8 @@
+.keyword {

+	-efx-styled-bold: true;

+	-efx-fill: rgb(127, 0, 85);

+}

+

+.default {

+	-efx-fill: black;

+}
\ No newline at end of file