Skip to content

Commit

Permalink
Double underline (#1256)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jugen authored Nov 29, 2024
1 parent ac869fd commit 8c5d3a9
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 6 deletions.
12 changes: 10 additions & 2 deletions richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public ObjectProperty<Paint> highlightTextFillProperty() {
underlineShape.getStrokeDashArray().setAll(attributes.dashArray);
}
PathElement[] shape = getUnderlineShape(tuple._2.getStart(), tuple._2.getEnd(),
attributes.offset, attributes.waveRadius);
attributes.offset, attributes.waveRadius, attributes.doubleGap);
underlineShape.getElements().setAll(shape);
},
addToForeground,
Expand Down Expand Up @@ -604,24 +604,32 @@ private static class UnderlineAttributes extends LineAttributesBase {
final StrokeLineCap cap;
final double offset;
final double waveRadius;
final double doubleGap;

UnderlineAttributes(TextExt text) {
super(text.getUnderlineColor(), text.getUnderlineWidth(), text.underlineDashArrayProperty());
cap = text.getUnderlineCap();

Number waveNumber = text.getUnderlineWaveRadius();
waveRadius = waveNumber == null ? 0 : waveNumber.doubleValue();

Number offsetNumber = text.getUnderlineOffset();
offset = offsetNumber == null ? waveRadius * 0.5 : offsetNumber.doubleValue();
// The larger the radius the bigger the offset needs to be, so
// a reasonable default is provided if no offset is specified.

Number doubleGapNumber = text.getUnderlineDoubleGap();
if (doubleGapNumber == null) doubleGap = 0;
else doubleGap = doubleGapNumber.doubleValue() + width;
}

/**
* Same as {@link #equals(Object)} but no need to check the object for its class
*/
public boolean equalsFaster(UnderlineAttributes attr) {
return super.equalsFaster(attr) && Objects.equals(cap, attr.cap)
&& offset == attr.offset && waveRadius == attr.waveRadius;
&& offset == attr.offset && waveRadius == attr.waveRadius
&& doubleGap == attr.doubleGap;
}

@Override
Expand Down
30 changes: 28 additions & 2 deletions richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class TextExt extends Text {
styleables.add(StyleableProperties.UNDERLINE_OFFSET);
styleables.add(StyleableProperties.UNDERLINE_WAVE_RADIUS);
styleables.add(StyleableProperties.UNDERLINE_DASH_ARRAY);
styleables.add(StyleableProperties.UNDERLINE_DOUBLE_GAP);
styleables.add(StyleableProperties.UNDERLINE_CAP);

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

private final StyleableObjectProperty<Number> underlineDoubleGap = new CustomStyleableProperty<>(
null, "underlineDoubleGap", this, StyleableProperties.UNDERLINE_DOUBLE_GAP
);

private final StyleableObjectProperty<StrokeLineCap> underlineCap = new CustomStyleableProperty<>(
null, "underlineCap", this, StyleableProperties.UNDERLINE_CAP
);
Expand Down Expand Up @@ -266,6 +271,22 @@ public ObjectProperty<Paint> borderStrokeColorProperty() {
*/
public ObjectProperty<Number> underlineWaveRadiusProperty() { return underlineWaveRadius; }

public Number getUnderlineDoubleGap() { return underlineDoubleGap.get(); }
public void setUnderlineDoubleGap(Number radius) { underlineDoubleGap.set(radius); }

/**
* The size of the gap between two parallel underline lines or wave forms. If null or zero, the
* underline will be a single line or wave form.
*
* Can be styled from CSS using the "-rtfx-underline-double-gap" property.
*
* <p>Note that the underline properties specified here are orthogonal to the {@link #underlineProperty()} inherited
* from {@link Text}. The underline properties defined here in {@link TextExt} will cause an underline to be
* drawn if {@link #underlineWidthProperty()} is non-null and greater than zero, regardless of
* the value of {@link #underlineProperty()}.</p>
*/
public ObjectProperty<Number> underlineDoubleGapProperty() { return underlineDoubleGap; }

// Dash array for the text underline
public Number[] getUnderlineDashArray() { return underlineDashArray.get(); }
public void setUnderlineDashArray(Number[] dashArray) { underlineDashArray.set(dashArray); }
Expand Down Expand Up @@ -315,7 +336,7 @@ private static class StyleableProperties {
);

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

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

private static final CssMetaData<TextExt, Number> UNDERLINE_DOUBLE_GAP = new CustomCssMetaData<>(
"-rtfx-underline-double-gap", StyleConverter.getSizeConverter(),
0, n -> n.underlineDoubleGap
);

private static final CssMetaData<TextExt, StrokeLineCap> UNDERLINE_CAP = new CustomCssMetaData<>(
"-rtfx-underline-cap", (StyleConverter<?, StrokeLineCap>) StyleConverter.getEnumConverter(StrokeLineCap.class),
"-rtfx-underline-cap", StyleConverter.getEnumConverter(StrokeLineCap.class),
StrokeLineCap.SQUARE, n -> n.underlineCap
);
}
Expand Down
20 changes: 18 additions & 2 deletions richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ PathElement[] getUnderlineShape(IndexRange range) {
}

PathElement[] getUnderlineShape(int from, int to) {
return getUnderlineShape(from, to, 0, 0);
return getUnderlineShape(from, to, 0, 0, 0);
}

/**
Expand All @@ -80,14 +80,17 @@ PathElement[] getUnderlineShape(int from, int to) {
* @return An array with the PathElement objects which define an
* underline from the first to the last character.
*/
PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadius) {
PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadius, double doubleGap) {
// 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.
// shape: [MoveTo origin, LineTo top R, LineTo bottom R, LineTo bottom L, LineTo origin, *]

boolean doubleLine = (doubleGap > 0.0);
List<PathElement> result2 = new ArrayList<>();

// Extract the bottom left and right coordinates for each rectangle to get the underline path.
for ( int ele = 2; ele < shape.length; ele += 5 )
{
Expand All @@ -100,12 +103,20 @@ PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadi
if (waveRadius <= 0) {
result.add(new MoveTo( leftx, y ));
result.add(new LineTo( snapSizeX( br.getX() ), y ));
if (doubleLine) {
y += doubleGap;
result2.add(new MoveTo( leftx, y ));
result2.add(new LineTo( snapSizeX( br.getX() ), y ));
}
}
else {
// For larger wave radii increase the X radius to stretch out the wave.
double radiusX = waveRadius > 1 ? waveRadius * 1.25 : waveRadius;
double rightx = br.getX();
result.add(new MoveTo( leftx, y ));
if (doubleLine) {
result2.add(new MoveTo( leftx, y+doubleGap ));
}
boolean sweep = true;
while ( leftx < rightx ) {
leftx += waveRadius * 2;
Expand All @@ -127,10 +138,15 @@ PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadi
leftx = rightx;
}
result.add(new ArcTo( radiusX, waveRadius, 0.0, leftx, y, false, sweep ));
if (doubleLine) {
result2.add(new ArcTo( radiusX, waveRadius, 0.0, leftx, y+doubleGap, false, sweep ));
}
sweep = !sweep;
}
}
}

if (doubleLine) result.addAll( result2 );

return result.toArray(new PathElement[0]);
}
Expand Down

0 comments on commit 8c5d3a9

Please sign in to comment.