private void forceNewLine() { builder.append('\n'); }
@Override public void visit(Text text) { builder.append(text.getLiteral()); }
@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)); }
@NonNull public SpannableBuilder append(@NonNull CharSequence cs, @NonNull Object span) { final int length = length(); append(cs); setSpan(span, length); return this; }
@NonNull public SpannableBuilder append(@NonNull CharSequence cs, @NonNull Object span, int flags) { final int length = length(); append(cs); setSpan(span, length, length(), flags); return this; }
private void newLine() { if (builder.length() > 0 && '\n' != builder.lastChar()) { builder.append('\n'); } }
@Override public void visit(SoftLineBreak softLineBreak) { // @since 1.1.1 there is an option to treat soft break as a hard break (thus adding new line) if (configuration.softBreakAddsNewLine()) { newLine(); } else { builder.append(' '); } }
private boolean visitIconNode(@NonNull CustomNode customNode) { if (customNode instanceof IconNode) { final IconNode node = (IconNode) customNode; final String name = node.name(); final String color = node.color(); final String size = node.size(); if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(color) && !TextUtils.isEmpty(size)) { final int length = builder.length(); builder.append(name); builder.setSpan(iconSpanProvider.provide(name, color, size), length); builder.append(' '); return true; } } return false; } }
/** * @param info tag of a code block * @param code content of a code block * @since 1.0.4 */ private void visitCodeBlock(@Nullable String info, @NonNull String code, @NonNull Node node) { newLine(); final int length = builder.length(); // empty lines on top & bottom builder.append('\u00a0').append('\n'); builder.append( configuration.syntaxHighlight() .highlight(info, code) ); newLine(); builder.append('\u00a0'); setSpan(length, factory.code(theme, true)); if (hasNext(node)) { newLine(); forceNewLine(); } }
private static void append(@NonNull SpannableBuilder builder, @NonNull String text, @NonNull Object span) { final int start = builder.length(); builder.append(text); builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } }
@Override public void visit(ThematicBreak thematicBreak) { newLine(); final int length = builder.length(); builder.append('\u00a0'); // without space it won't render setSpan(length, factory.thematicBreak(theme)); if (hasNext(thematicBreak)) { newLine(); forceNewLine(); } }
@Test public void set_spans_array_of_arrays() { // if array of arrays is supplied -> it won't be expanded to single elements builder.append('0'); assertTrue(builder.getSpans(0, builder.length()).isEmpty()); final Object[] spans = { new Object[]{ new Object(), new Object() }, new Object[]{ new Object(), new Object(), new Object() } }; setSpans(builder, spans, 0, 1); final List<SpannableBuilder.Span> actual = builder.getSpans(0, builder.length()); assertEquals(2, actual.size()); for (int i = 0, length = spans.length; i < length; i++) { assertEquals(spans[i], actual.get(i).what); } }
@Test public void set_spans_single() { // single span as `spans` argument correctly added builder.append('0'); assertTrue(builder.getSpans(0, builder.length()).isEmpty()); final Object span = new Object(); setSpans(builder, span, 0, 1); final List<SpannableBuilder.Span> spans = builder.getSpans(0, builder.length()); assertEquals(1, spans.size()); assertEquals(span, spans.get(0).what); }
@Test public void set_spans_array_detected() { // if supplied `spans` argument is an array -> it should be expanded builder.append('0'); assertTrue(builder.getSpans(0, builder.length()).isEmpty()); final Object[] spans = { new Object(), new Object(), new Object() }; setSpans(builder, spans, 0, 1); final List<SpannableBuilder.Span> actual = builder.getSpans(0, builder.length()); assertEquals(spans.length, actual.size()); for (int i = 0, length = spans.length; i < length; i++) { assertEquals(spans[i], actual.get(i).what); } }
@Test public void set_spans_position_invalid() { // if supplied position is invalid, no spans should be added builder.append('0'); assertTrue(builder.getSpans(0, builder.length()).isEmpty()); setSpans(builder, new Object(), -1, -1); assertTrue(builder.getSpans(0, builder.length()).isEmpty()); }
@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]); } }
@Test public void set_spans_null() { // if `spans` argument is null, then nothing will be added builder.append('0'); assertTrue(builder.getSpans(0, builder.length()).isEmpty()); setSpans(builder, null, 0, builder.length()); assertTrue(builder.getSpans(0, builder.length()).isEmpty()); }
@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]); } }
@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]); } }
@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() ); } };