@NonNull public CharSequence render(@NonNull SpannableConfiguration configuration, @NonNull Node node) { final SpannableBuilder builder = new SpannableBuilder(); node.accept(new SpannableMarkdownVisitor(configuration, builder)); return builder.text(); } }
@Test public void append_spanned_reversed() { // #append is called with reversed spanned content -> spans should be added as-are final SpannableBuilder spannableBuilder = new SpannableBuilder(); for (int i = 0; i < 3; i++) { spannableBuilder.append(String.valueOf(i), i); } assertTrue(builder.getSpans(0, builder.length()).isEmpty()); builder.append(spannableBuilder.spannableStringBuilder()); final SpannableStringBuilder spannableStringBuilder = builder.spannableStringBuilder(); final Object[] spans = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), Object.class); assertEquals(3, spans.length); for (int i = 0, length = spans.length; i < length; i++) { // in the end order should be as we expect in order to properly render it // (no matter if reversed is used or not) assertEquals(length - 1 - i, spans[i]); } }
@Override public void visit(Code code) { final int length = builder.length(); // NB, in order to provide a _padding_ feeling code is wrapped inside two unbreakable spaces // unfortunately we cannot use this for multiline code as we cannot control where a new line break will be inserted builder.append('\u00a0'); builder.append(code.getLiteral()); builder.append('\u00a0'); setSpan(length, factory.code(theme, false)); }
@Test public void append_spanned_normal() { // #append is called with regular Spanned content -> spans should be added in reverse final SpannableStringBuilder ssb = new SpannableStringBuilder(); for (int i = 0; i < 3; i++) { ssb.append(String.valueOf(i)); ssb.setSpan(i, i, i + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } assertTrue(builder.getSpans(0, builder.length()).isEmpty()); builder.append(ssb); assertEquals("012", builder.toString()); // this one would return normal order as spans are reversed here // final List<SpannableBuilder.Span> spans = builder.getSpans(0, builder.length()); final SpannableStringBuilder spannableStringBuilder = builder.spannableStringBuilder(); final Object[] spans = spannableStringBuilder.getSpans(0, builder.length(), Object.class); assertEquals(3, spans.length); for (int i = 0, length = spans.length; i < length; i++) { assertEquals(length - 1 - i, spans[i]); } }
public char lastChar() { return builder.charAt(length() - 1); }
@Override public void visit(CustomBlock customBlock) { if (!(customBlock instanceof JLatexMathBlock)) { super.visit(customBlock); return; } final String latex = ((JLatexMathBlock) customBlock).latex(); final int length = builder.length(); builder.append(latex); SpannableBuilder.setSpans( builder, configuration.factory().image( configuration.theme(), JLatexMathMedia.makeDestination(latex), configuration.asyncDrawableLoader(), configuration.imageSizeResolver(), new ImageSize(new ImageSize.Dimension(100, "%"), null), false ), length, builder.length() ); } };
/** * @since 2.0.0 */ public static void setSpans(@NonNull SpannableBuilder builder, @Nullable Object spans, int start, int end) { if (spans != null) { // setting a span for an invalid position can lead to silent fail (no exception, // but execution is stopped) if (!isPositionValid(builder.length(), start, end)) { return; } if (spans.getClass().isArray()) { for (Object o : ((Object[]) spans)) { builder.setSpan(o, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } else { builder.setSpan(spans, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } }
final int length = builder.length(); final int builderLength = builder.length(); addNewLine = builderLength > 0 && '\n' != builder.charAt(builderLength - 1); builder.append('\n'); builder.append('\u00a0'); final int length = builder.length(); visitChildren(cell); if (pendingTableRow == null) { builder.removeFromEnd(length) ));
@Test public void spans_reversed() { // resulting SpannableStringBuilder should have spans reversed final Object[] spans = { 0, 1, 2 }; for (Object span : spans) { builder.append(span.toString(), span); } final SpannableStringBuilder spannableStringBuilder = builder.spannableStringBuilder(); final Object[] actual = spannableStringBuilder.getSpans(0, builder.length(), Object.class); for (int start = 0, length = spans.length, end = length - 1; start < length; start++, end--) { assertEquals(spans[start], actual[end]); } }
.build(); final SpannableBuilder builder = new SpannableBuilder(); append(builder, "### Header 3\n", new Object()); final int start = builder.length(); final int end = builder.length(); append(builder, "# Footer 1\n", new Object()); final Object[] spans = builder.spannableStringBuilder().getSpans(start, end, Object.class);
@Test public void get_spans_out_of_range() { // let's test that if span.start >= range.start -> it will be less than range.end // if span.end <= end -> it will be greater than range.start for (int i = 0; i < 10; i++) { builder.append(String.valueOf(i)); builder.setSpan("" + i + "-" + (i + 1), i, i + 1); } assertEquals(10, getSpans(0, 10).size()); // so // 0-1 // 1-2 // 2-3 // etc //noinspection ArraysAsListWithZeroOrOneArgument assertEquals( "0-1", Arrays.asList("0-1"), getSpans(0, 1) ); assertEquals( "1-5", Arrays.asList("1-2", "2-3", "3-4", "4-5"), getSpans(1, 5) ); }
public List<Span> getSpans(int start, int end) { final int length = length(); if (!isPositionValid(length, start, end)) {
private void forceNewLine() { builder.append('\n'); }
@Override public void handle(@NonNull SpannableConfiguration configuration, @NonNull SpannableBuilder builder, @NonNull HtmlTag tag) { final Object spans = getSpans(configuration, tag); if (spans != null) { SpannableBuilder.setSpans(builder, spans, tag.start(), tag.end()); } } }