Skip to content

Commit 8c5d3a9

Browse files
authored
Double underline (#1256)
1 parent ac869fd commit 8c5d3a9

3 files changed

Lines changed: 56 additions & 6 deletions

File tree

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ public ObjectProperty<Paint> highlightTextFillProperty() {
216216
underlineShape.getStrokeDashArray().setAll(attributes.dashArray);
217217
}
218218
PathElement[] shape = getUnderlineShape(tuple._2.getStart(), tuple._2.getEnd(),
219-
attributes.offset, attributes.waveRadius);
219+
attributes.offset, attributes.waveRadius, attributes.doubleGap);
220220
underlineShape.getElements().setAll(shape);
221221
},
222222
addToForeground,
@@ -604,24 +604,32 @@ private static class UnderlineAttributes extends LineAttributesBase {
604604
final StrokeLineCap cap;
605605
final double offset;
606606
final double waveRadius;
607+
final double doubleGap;
607608

608609
UnderlineAttributes(TextExt text) {
609610
super(text.getUnderlineColor(), text.getUnderlineWidth(), text.underlineDashArrayProperty());
610611
cap = text.getUnderlineCap();
612+
611613
Number waveNumber = text.getUnderlineWaveRadius();
612614
waveRadius = waveNumber == null ? 0 : waveNumber.doubleValue();
615+
613616
Number offsetNumber = text.getUnderlineOffset();
614617
offset = offsetNumber == null ? waveRadius * 0.5 : offsetNumber.doubleValue();
615618
// The larger the radius the bigger the offset needs to be, so
616619
// a reasonable default is provided if no offset is specified.
620+
621+
Number doubleGapNumber = text.getUnderlineDoubleGap();
622+
if (doubleGapNumber == null) doubleGap = 0;
623+
else doubleGap = doubleGapNumber.doubleValue() + width;
617624
}
618625

619626
/**
620627
* Same as {@link #equals(Object)} but no need to check the object for its class
621628
*/
622629
public boolean equalsFaster(UnderlineAttributes attr) {
623630
return super.equalsFaster(attr) && Objects.equals(cap, attr.cap)
624-
&& offset == attr.offset && waveRadius == attr.waveRadius;
631+
&& offset == attr.offset && waveRadius == attr.waveRadius
632+
&& doubleGap == attr.doubleGap;
625633
}
626634

627635
@Override

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class TextExt extends Text {
4646
styleables.add(StyleableProperties.UNDERLINE_OFFSET);
4747
styleables.add(StyleableProperties.UNDERLINE_WAVE_RADIUS);
4848
styleables.add(StyleableProperties.UNDERLINE_DASH_ARRAY);
49+
styleables.add(StyleableProperties.UNDERLINE_DOUBLE_GAP);
4950
styleables.add(StyleableProperties.UNDERLINE_CAP);
5051

5152
CSS_META_DATA_LIST = Collections.unmodifiableList(styleables);
@@ -91,6 +92,10 @@ public class TextExt extends Text {
9192
null, "underlineDashArray", this, StyleableProperties.UNDERLINE_DASH_ARRAY
9293
);
9394

95+
private final StyleableObjectProperty<Number> underlineDoubleGap = new CustomStyleableProperty<>(
96+
null, "underlineDoubleGap", this, StyleableProperties.UNDERLINE_DOUBLE_GAP
97+
);
98+
9499
private final StyleableObjectProperty<StrokeLineCap> underlineCap = new CustomStyleableProperty<>(
95100
null, "underlineCap", this, StyleableProperties.UNDERLINE_CAP
96101
);
@@ -266,6 +271,22 @@ public ObjectProperty<Paint> borderStrokeColorProperty() {
266271
*/
267272
public ObjectProperty<Number> underlineWaveRadiusProperty() { return underlineWaveRadius; }
268273

274+
public Number getUnderlineDoubleGap() { return underlineDoubleGap.get(); }
275+
public void setUnderlineDoubleGap(Number radius) { underlineDoubleGap.set(radius); }
276+
277+
/**
278+
* The size of the gap between two parallel underline lines or wave forms. If null or zero, the
279+
* underline will be a single line or wave form.
280+
*
281+
* Can be styled from CSS using the "-rtfx-underline-double-gap" property.
282+
*
283+
* <p>Note that the underline properties specified here are orthogonal to the {@link #underlineProperty()} inherited
284+
* from {@link Text}. The underline properties defined here in {@link TextExt} will cause an underline to be
285+
* drawn if {@link #underlineWidthProperty()} is non-null and greater than zero, regardless of
286+
* the value of {@link #underlineProperty()}.</p>
287+
*/
288+
public ObjectProperty<Number> underlineDoubleGapProperty() { return underlineDoubleGap; }
289+
269290
// Dash array for the text underline
270291
public Number[] getUnderlineDashArray() { return underlineDashArray.get(); }
271292
public void setUnderlineDashArray(Number[] dashArray) { underlineDashArray.set(dashArray); }
@@ -315,7 +336,7 @@ private static class StyleableProperties {
315336
);
316337

317338
private static final CssMetaData<TextExt, StrokeType> BORDER_TYPE = new CustomCssMetaData<>(
318-
"-rtfx-border-stroke-type", (StyleConverter<?, StrokeType>) StyleConverter.getEnumConverter(StrokeType.class),
339+
"-rtfx-border-stroke-type", StyleConverter.getEnumConverter(StrokeType.class),
319340
StrokeType.INSIDE, n -> n.borderStrokeType
320341
);
321342

@@ -349,8 +370,13 @@ private static class StyleableProperties {
349370
new Double[0], n -> n.underlineDashArray
350371
);
351372

373+
private static final CssMetaData<TextExt, Number> UNDERLINE_DOUBLE_GAP = new CustomCssMetaData<>(
374+
"-rtfx-underline-double-gap", StyleConverter.getSizeConverter(),
375+
0, n -> n.underlineDoubleGap
376+
);
377+
352378
private static final CssMetaData<TextExt, StrokeLineCap> UNDERLINE_CAP = new CustomCssMetaData<>(
353-
"-rtfx-underline-cap", (StyleConverter<?, StrokeLineCap>) StyleConverter.getEnumConverter(StrokeLineCap.class),
379+
"-rtfx-underline-cap", StyleConverter.getEnumConverter(StrokeLineCap.class),
354380
StrokeLineCap.SQUARE, n -> n.underlineCap
355381
);
356382
}

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ PathElement[] getUnderlineShape(IndexRange range) {
6969
}
7070

7171
PathElement[] getUnderlineShape(int from, int to) {
72-
return getUnderlineShape(from, to, 0, 0);
72+
return getUnderlineShape(from, to, 0, 0, 0);
7373
}
7474

7575
/**
@@ -80,14 +80,17 @@ PathElement[] getUnderlineShape(int from, int to) {
8080
* @return An array with the PathElement objects which define an
8181
* underline from the first to the last character.
8282
*/
83-
PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadius) {
83+
PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadius, double doubleGap) {
8484
// get a Path for the text underline
8585
List<PathElement> result = new ArrayList<>();
8686

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

91+
boolean doubleLine = (doubleGap > 0.0);
92+
List<PathElement> result2 = new ArrayList<>();
93+
9194
// Extract the bottom left and right coordinates for each rectangle to get the underline path.
9295
for ( int ele = 2; ele < shape.length; ele += 5 )
9396
{
@@ -100,12 +103,20 @@ PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadi
100103
if (waveRadius <= 0) {
101104
result.add(new MoveTo( leftx, y ));
102105
result.add(new LineTo( snapSizeX( br.getX() ), y ));
106+
if (doubleLine) {
107+
y += doubleGap;
108+
result2.add(new MoveTo( leftx, y ));
109+
result2.add(new LineTo( snapSizeX( br.getX() ), y ));
110+
}
103111
}
104112
else {
105113
// For larger wave radii increase the X radius to stretch out the wave.
106114
double radiusX = waveRadius > 1 ? waveRadius * 1.25 : waveRadius;
107115
double rightx = br.getX();
108116
result.add(new MoveTo( leftx, y ));
117+
if (doubleLine) {
118+
result2.add(new MoveTo( leftx, y+doubleGap ));
119+
}
109120
boolean sweep = true;
110121
while ( leftx < rightx ) {
111122
leftx += waveRadius * 2;
@@ -127,10 +138,15 @@ PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadi
127138
leftx = rightx;
128139
}
129140
result.add(new ArcTo( radiusX, waveRadius, 0.0, leftx, y, false, sweep ));
141+
if (doubleLine) {
142+
result2.add(new ArcTo( radiusX, waveRadius, 0.0, leftx, y+doubleGap, false, sweep ));
143+
}
130144
sweep = !sweep;
131145
}
132146
}
133147
}
148+
149+
if (doubleLine) result.addAll( result2 );
134150

135151
return result.toArray(new PathElement[0]);
136152
}

0 commit comments

Comments
 (0)