Skip to content

Commit d4fd9ac

Browse files
Merge pull request #598 from JordanMartinez/paragraphCleanup
Paragraph - clean up and performance improvements
2 parents 28a01b3 + ae6f7a2 commit d4fd9ac

1 file changed

Lines changed: 62 additions & 47 deletions

File tree

richtextfx/src/main/java/org/fxmisc/richtext/model/Paragraph.java

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,19 @@ public final class Paragraph<PS, SEG, S> {
4242

4343
private static <SEG, S> Tuple2<List<SEG>, StyleSpans<S>> decompose(List<StyledSegment<SEG, S>> list,
4444
SegmentOps<SEG, S> segmentOps) {
45-
// TODO: optimize this so that join-able segments/styles are joined before returning tuple
4645
List<SEG> segs = new ArrayList<>();
4746
StyleSpansBuilder<S> builder = new StyleSpansBuilder<>();
4847
for (StyledSegment<SEG, S> styledSegment : list) {
49-
segs.add(styledSegment.getSegment());
48+
// attempt to merge differently-styled consecutive segments into one
49+
int lastIndex = segs.size() - 1;
50+
SEG previousSeg = segs.get(lastIndex);
51+
Optional<SEG> merged = segmentOps.joinSeg(previousSeg, styledSegment.getSegment());
52+
if (merged.isPresent()) {
53+
segs.set(lastIndex, merged.get());
54+
} else {
55+
segs.add(styledSegment.getSegment());
56+
}
57+
// builder merges styles shared between consecutive different segments
5058
builder.add(styledSegment.getStyle(), segmentOps.length(styledSegment.getSegment()));
5159
}
5260
return Tuples.t(segs, builder.create());
@@ -73,17 +81,14 @@ private static <T> List<T> list(T head, T... tail) {
7381

7482
public Paragraph(PS paragraphStyle, SegmentOps<SEG, S> segmentOps, List<StyledSegment<SEG, S>> styledSegments) {
7583
this(paragraphStyle, segmentOps, decompose(styledSegments, segmentOps));
76-
7784
}
7885

7986
private Paragraph(PS paragraphStyle, SegmentOps<SEG, S> segmentOps, Tuple2<List<SEG>, StyleSpans<S>> decomposedList) {
8087
this(paragraphStyle, segmentOps, decomposedList._1, decomposedList._2);
8188
}
8289

8390
public Paragraph(PS paragraphStyle, SegmentOps<SEG, S> segmentOps, SEG segment, S style) {
84-
this(paragraphStyle, segmentOps, segment,
85-
new StyleSpansBuilder<S>().add(style, segmentOps.length(segment)).create()
86-
);
91+
this(paragraphStyle, segmentOps, segment, StyleSpans.singleton(style, segmentOps.length(segment)));
8792
}
8893

8994
public Paragraph(PS paragraphStyle, SegmentOps<SEG, S> segmentOps, SEG segment, StyleSpans<S> styles) {
@@ -109,47 +114,16 @@ public Paragraph(PS paragraphStyle, SegmentOps<SEG, S> segmentOps, List<SEG> seg
109114
);
110115
}
111116

112-
public List<StyledSegment<SEG, S>> getStyledSegments() {
113-
if (segments.size() == 1 && styles.getSpanCount() == 1) {
114-
return Collections.singletonList(
115-
new StyledSegment<>(segments.get(0), styles.getStyleSpan(0).getStyle())
116-
);
117-
}
117+
private List<StyledSegment<SEG, S>> styledSegments = null;
118118

119-
List<StyledSegment<SEG, S>> styledSegments = new LinkedList<>();
120-
Iterator<SEG> segIterator = segments.iterator();
121-
Iterator<StyleSpan<S>> styleIterator = styles.iterator();
122-
SEG segCurrent = segIterator.next();
123-
StyleSpan<S> styleCurrent = styleIterator.next();
124-
int segOffset = 0, styleOffset = 0;
125-
boolean finished = false;
126-
while (!finished) {
127-
int segLength = segmentOps.length(segCurrent) - segOffset;
128-
int styleLength = styleCurrent.getLength() - styleOffset;
129-
130-
if (segLength < styleLength) {
131-
SEG splitSeg = segmentOps.subSequence(segCurrent, segOffset);
132-
styledSegments.add(new StyledSegment<>(splitSeg, styleCurrent.getStyle()));
133-
segCurrent = segIterator.next();
134-
segOffset = 0;
135-
styleOffset += segLength;
136-
} else if (styleLength < segLength) {
137-
SEG splitSeg = segmentOps.subSequence(segCurrent, segOffset, segOffset + styleLength);
138-
styledSegments.add(new StyledSegment<>(splitSeg, styleCurrent.getStyle()));
139-
styleCurrent = styleIterator.next();
140-
styleOffset = 0;
141-
segOffset += styleLength;
119+
public List<StyledSegment<SEG, S>> getStyledSegments() {
120+
if (styledSegments == null) {
121+
if (segments.size() == 1 && styles.getSpanCount() == 1) {
122+
styledSegments = Collections.singletonList(
123+
new StyledSegment<>(segments.get(0), styles.getStyleSpan(0).getStyle())
124+
);
142125
} else {
143-
SEG splitSeg = segmentOps.subSequence(segCurrent, segOffset, segOffset + styleLength);
144-
styledSegments.add(new StyledSegment<>(splitSeg, styleCurrent.getStyle()));
145-
if (segIterator.hasNext() && styleIterator.hasNext()) {
146-
segCurrent = segIterator.next();
147-
segOffset = 0;
148-
styleCurrent = styleIterator.next();
149-
styleOffset = 0;
150-
} else {
151-
finished = true;
152-
}
126+
styledSegments = createStyledSegments();
153127
}
154128
}
155129
return styledSegments;
@@ -164,6 +138,7 @@ public PS getParagraphStyle() {
164138
}
165139

166140
private int length = -1;
141+
167142
public int length() {
168143
if(length == -1) {
169144
length = segments.stream().mapToInt(segmentOps::length).sum();
@@ -301,8 +276,7 @@ public Paragraph<PS, SEG, S> delete(int start, int end) {
301276
* @return The new paragraph with the restyled segments.
302277
*/
303278
public Paragraph<PS, SEG, S> restyle(S style) {
304-
StyleSpans<S> spans = new StyleSpansBuilder<S>().add(new StyleSpan<>(style, length())).create();
305-
return new Paragraph<>(paragraphStyle, segmentOps, segments, spans);
279+
return new Paragraph<>(paragraphStyle, segmentOps, segments, StyleSpans.singleton(style, length()));
306280
}
307281

308282
public Paragraph<PS, SEG, S> restyle(int from, int to, S style) {
@@ -401,6 +375,7 @@ public StyleSpans<S> getStyleSpans(int from, int to) {
401375
}
402376

403377
private String text = null;
378+
404379
/**
405380
* Returns the plain text content of this paragraph,
406381
* not including the line terminator.
@@ -444,4 +419,44 @@ public boolean equals(Object other) {
444419
public int hashCode() {
445420
return Objects.hash(paragraphStyle, segments, styles);
446421
}
422+
423+
private List<StyledSegment<SEG, S>> createStyledSegments() {
424+
List<StyledSegment<SEG, S>> styledSegments = new LinkedList<>();
425+
Iterator<SEG> segIterator = segments.iterator();
426+
Iterator<StyleSpan<S>> styleIterator = styles.iterator();
427+
SEG segCurrent = segIterator.next();
428+
StyleSpan<S> styleCurrent = styleIterator.next();
429+
int segOffset = 0, styleOffset = 0;
430+
boolean finished = false;
431+
while (!finished) {
432+
int segLength = segmentOps.length(segCurrent) - segOffset;
433+
int styleLength = styleCurrent.getLength() - styleOffset;
434+
435+
if (segLength < styleLength) {
436+
SEG splitSeg = segmentOps.subSequence(segCurrent, segOffset);
437+
styledSegments.add(new StyledSegment<>(splitSeg, styleCurrent.getStyle()));
438+
segCurrent = segIterator.next();
439+
segOffset = 0;
440+
styleOffset += segLength;
441+
} else if (styleLength < segLength) {
442+
SEG splitSeg = segmentOps.subSequence(segCurrent, segOffset, segOffset + styleLength);
443+
styledSegments.add(new StyledSegment<>(splitSeg, styleCurrent.getStyle()));
444+
styleCurrent = styleIterator.next();
445+
styleOffset = 0;
446+
segOffset += styleLength;
447+
} else {
448+
SEG splitSeg = segmentOps.subSequence(segCurrent, segOffset, segOffset + styleLength);
449+
styledSegments.add(new StyledSegment<>(splitSeg, styleCurrent.getStyle()));
450+
if (segIterator.hasNext() && styleIterator.hasNext()) {
451+
segCurrent = segIterator.next();
452+
segOffset = 0;
453+
styleCurrent = styleIterator.next();
454+
styleOffset = 0;
455+
} else {
456+
finished = true;
457+
}
458+
}
459+
}
460+
return styledSegments;
461+
}
447462
}

0 commit comments

Comments
 (0)