Add trimWhiteSpaceEnd configuration option

This commit is contained in:
Dimitry Ivanov 2018-08-18 12:38:29 +03:00
parent 359fd8699d
commit 4a6e5002e8
4 changed files with 105 additions and 22 deletions

View File

@ -96,6 +96,7 @@ public class MarkdownRenderer {
.codeTextColor(prism4jTheme.textColor()) .codeTextColor(prism4jTheme.textColor())
.build()) .build())
.factory(new GifAwareSpannableFactory(gifPlaceholder)) .factory(new GifAwareSpannableFactory(gifPlaceholder))
.trimWhiteSpaceEnd(false)
.build(); .build();
final long start = SystemClock.uptimeMillis(); final long start = SystemClock.uptimeMillis();

View File

@ -26,7 +26,8 @@ public class SpannableBuilder implements Appendable, CharSequence {
// we will be using SpannableStringBuilder anyway as a backing store // we will be using SpannableStringBuilder anyway as a backing store
// as it has tight connection with system (implements some hidden methods, etc) // as it has tight connection with system (implements some hidden methods, etc)
private final SpannableStringBuilder builder; // private final SpannableStringBuilder builder;
private final StringBuilder builder;
// actually we might be just using ArrayList // actually we might be just using ArrayList
private final Deque<Span> spans = new ArrayDeque<>(8); private final Deque<Span> spans = new ArrayDeque<>(8);
@ -36,7 +37,8 @@ public class SpannableBuilder implements Appendable, CharSequence {
} }
public SpannableBuilder(@NonNull CharSequence cs) { public SpannableBuilder(@NonNull CharSequence cs) {
this.builder = new SpannableStringBuilderImpl(cs.toString()); // this.builder = new SpannableStringBuilderImpl(cs.toString());
this.builder = new StringBuilder(cs);
copySpans(0, cs); copySpans(0, cs);
} }
@ -65,7 +67,7 @@ public class SpannableBuilder implements Appendable, CharSequence {
copySpans(length(), cs); copySpans(length(), cs);
builder.append(cs.toString()); builder.append(cs);
return this; return this;
} }
@ -80,7 +82,7 @@ public class SpannableBuilder implements Appendable, CharSequence {
final CharSequence cs = csq.subSequence(start, end); final CharSequence cs = csq.subSequence(start, end);
copySpans(length(), cs); copySpans(length(), cs);
builder.append(cs.toString()); builder.append(cs);
return this; return this;
} }
@ -172,8 +174,73 @@ public class SpannableBuilder implements Appendable, CharSequence {
return builder.toString(); return builder.toString();
} }
/**
* Moved from {@link #text()} method
*
* @since 2.0.0
*/
public void trimWhiteSpaceEnd() {
// now, let's remove trailing & leading newLines (so small amounts of text are displayed correctly)
// @since 1.0.2
int length = builder.length();
if (length > 0) {
length = builder.length();
int amount = 0;
for (int i = length - 1; i >= 0; i--) {
if (Character.isWhitespace(builder.charAt(i))) {
amount += 1;
} else {
break;
}
}
if (amount > 0) {
final int newLength = length - amount;
builder.replace(newLength, length, "");
// additionally we should apply new length to the spans (otherwise
// sometimes system cannot handle spans with length greater than total length
// which causes some internal failure (no exceptions, no logs) in Layout class
// and no markdown is displayed at all
if (spans.size() > 0) {
Span span;
final Iterator<Span> iterator = spans.iterator();
while (iterator.hasNext()) {
span = iterator.next();
// if span start is greater than newLength, then remove it... one should
// not use white space for spanning resulting text
if (span.start > newLength) {
iterator.remove();
} else if (span.end > newLength) {
span.end = newLength;
}
}
}
}
}
}
@NonNull @NonNull
public CharSequence text() { public CharSequence text() {
// @since 2.0.0 redirects this call to `#spannableStringBuilder()`
return spannableStringBuilder();
}
/**
* Simple method to create a SpannableStringBuilder, which is created anyway. Unlike {@link #text()}
* method which returns the same SpannableStringBuilder there is no need to cast the resulting
* CharSequence
*
* @since 2.0.0
*/
@NonNull
public SpannableStringBuilder spannableStringBuilder() {
// okay, in order to not allow external modification and keep our spans order // okay, in order to not allow external modification and keep our spans order
// we should not return our builder // we should not return our builder
@ -183,30 +250,13 @@ public class SpannableBuilder implements Appendable, CharSequence {
// so, we will defensively copy builder // so, we will defensively copy builder
// as we do not expose builder and do no apply spans to it, we are safe to NOT to convert to String // as we do not expose builder and do no apply spans to it, we are safe to NOT to convert to String
final SpannableStringBuilderImpl impl = new SpannableStringBuilderImpl(builder); final SpannableStringBuilderImpl impl = new SpannableStringBuilderImpl(builder);
for (Span span : spans) { for (Span span : spans) {
impl.setSpan(span.what, span.start, span.end, span.flags); impl.setSpan(span.what, span.start, span.end, span.flags);
} }
// now, let's remove trailing newLines (so small amounts of text are displayed correctly)
// @since 1.0.2
final int length = impl.length();
if (length > 0) {
int amount = 0;
for (int i = length - 1; i >= 0; i--) {
if (Character.isWhitespace(impl.charAt(i))) {
amount += 1;
} else {
break;
}
}
if (amount > 0) {
impl.replace(length - amount, length, "");
}
}
return impl; return impl;
} }

View File

@ -32,6 +32,7 @@ public class SpannableConfiguration {
private final SpannableHtmlParser htmlParser; private final SpannableHtmlParser htmlParser;
private final ImageSizeResolver imageSizeResolver; private final ImageSizeResolver imageSizeResolver;
private final SpannableFactory factory; // @since 1.1.0 private final SpannableFactory factory; // @since 1.1.0
private final boolean trimWhiteSpaceEnd; // @since 2.0.0
private SpannableConfiguration(@NonNull Builder builder) { private SpannableConfiguration(@NonNull Builder builder) {
this.theme = builder.theme; this.theme = builder.theme;
@ -42,6 +43,7 @@ public class SpannableConfiguration {
this.htmlParser = builder.htmlParser; this.htmlParser = builder.htmlParser;
this.imageSizeResolver = builder.imageSizeResolver; this.imageSizeResolver = builder.imageSizeResolver;
this.factory = builder.factory; this.factory = builder.factory;
this.trimWhiteSpaceEnd = builder.trimWhiteSpaceEnd;
} }
@NonNull @NonNull
@ -84,6 +86,13 @@ public class SpannableConfiguration {
return factory; return factory;
} }
/**
* @since 2.0.0
*/
public boolean trimWhiteSpaceEnd() {
return trimWhiteSpaceEnd;
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static class Builder { public static class Builder {
@ -96,6 +105,7 @@ public class SpannableConfiguration {
private SpannableHtmlParser htmlParser; private SpannableHtmlParser htmlParser;
private ImageSizeResolver imageSizeResolver; private ImageSizeResolver imageSizeResolver;
private SpannableFactory factory; private SpannableFactory factory;
private boolean trimWhiteSpaceEnd = true;
Builder(@NonNull Context context) { Builder(@NonNull Context context) {
this.context = context; this.context = context;
@ -155,6 +165,18 @@ public class SpannableConfiguration {
return this; return this;
} }
/**
* Will trim white space(s) from the end from resulting text.
* By default `true`
*
* @since 2.0.0
*/
@NonNull
public Builder trimWhiteSpaceEnd(boolean trimWhiteSpaceEnd) {
this.trimWhiteSpaceEnd = trimWhiteSpaceEnd;
return this;
}
@NonNull @NonNull
public SpannableConfiguration build() { public SpannableConfiguration build() {

View File

@ -15,6 +15,7 @@ import org.commonmark.node.BulletList;
import org.commonmark.node.Code; import org.commonmark.node.Code;
import org.commonmark.node.CustomBlock; import org.commonmark.node.CustomBlock;
import org.commonmark.node.CustomNode; import org.commonmark.node.CustomNode;
import org.commonmark.node.Document;
import org.commonmark.node.Emphasis; import org.commonmark.node.Emphasis;
import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.FencedCodeBlock;
import org.commonmark.node.HardLineBreak; import org.commonmark.node.HardLineBreak;
@ -77,6 +78,15 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
this.factory = configuration.factory(); this.factory = configuration.factory();
} }
@Override
public void visit(Document document) {
super.visit(document);
if (configuration.trimWhiteSpaceEnd()) {
builder.trimWhiteSpaceEnd();
}
}
@Override @Override
public void visit(Text text) { public void visit(Text text) {
builder.append(text.getLiteral()); builder.append(text.getLiteral());