From 8daa59709b3ba352e16ef2ba55a2ad4b219f0d4d Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 4 Jul 2019 17:17:33 +0300 Subject: [PATCH] Sanitize latex text placeholder (no new lines) --- gradle.properties | 2 +- markwon-ext-latex/build.gradle | 8 +- .../markwon/ext/latex/JLatexMathPlugin.java | 17 ++- .../ext/latex/JLatexMathPluginTest.java | 113 ++++++++++++++++++ .../markwon/sample/latex/LatexActivity.java | 31 ++--- 5 files changed, 153 insertions(+), 18 deletions(-) create mode 100644 markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java diff --git a/gradle.properties b/gradle.properties index 0269b018..766c5812 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ android.enableJetifier=true android.enableBuildCache=true android.buildCacheDir=build/pre-dex-cache -VERSION_NAME=4.0.1 +VERSION_NAME=4.0.2-SNAPSHOT GROUP=io.noties.markwon POM_DESCRIPTION=Markwon markdown for Android diff --git a/markwon-ext-latex/build.gradle b/markwon-ext-latex/build.gradle index da8a9971..6e684440 100644 --- a/markwon-ext-latex/build.gradle +++ b/markwon-ext-latex/build.gradle @@ -16,8 +16,14 @@ android { dependencies { api project(':markwon-core') - api project(':markwon-image') + api deps['jlatexmath-android'] + + deps['test'].with { + testImplementation it['junit'] + testImplementation it['robolectric'] + testImplementation it['mockito'] + } } registerArtifact(this) \ No newline at end of file diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index 75fe50b4..291d361a 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -12,6 +12,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; +import androidx.annotation.VisibleForTesting; import org.commonmark.parser.Parser; @@ -84,8 +85,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { // @since 4.0.0 private final int paddingHorizontal; - // @since 4.0.0 + // @since 4.0.0 private final int paddingVertical; // @since 4.0.0 @@ -132,7 +133,10 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final int length = visitor.length(); - visitor.builder().append(latex); + // @since 4.0.2 we cannot append _raw_ latex as a placeholder-text, + // because Android will draw formula for each line of text, thus + // leading to formula duplicated (drawn on each line of text) + visitor.builder().append(prepareLatexTextPlaceholder(latex)); final MarkwonConfiguration configuration = visitor.configuration(); @@ -161,6 +165,13 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { AsyncDrawableScheduler.schedule(textView); } + // @since 4.0.2 + @VisibleForTesting + @NonNull + static String prepareLatexTextPlaceholder(@NonNull String latex) { + return latex.replace('\n', ' ').trim(); + } + public static class Builder { private final float textSize; @@ -350,6 +361,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { final Rect imageBounds = drawable.getResult().getBounds(); final int canvasWidth = drawable.getLastKnownCanvasWidth(); + // todo: scale down when formula is greater than width (keep ratio and apply height) + if (fitCanvas && imageBounds.width() < canvasWidth) { // we increase only width (keep height as-is) diff --git a/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java b/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java new file mode 100644 index 00000000..8190a470 --- /dev/null +++ b/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathPluginTest.java @@ -0,0 +1,113 @@ +package io.noties.markwon.ext.latex; + +import androidx.annotation.NonNull; + +import org.commonmark.parser.Parser; +import org.commonmark.parser.block.BlockParserFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.concurrent.ExecutorService; + +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.SpannableBuilder; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class JLatexMathPluginTest { + + @Test + public void latex_text_placeholder() { + // text placeholder cannot have new-line characters and should be trimmed from ends + + final String[] in = { + "hello", + "he\nllo", + " hello\n\n", + "\n\nhello\n\n", + "\n", + " \nhello\n " + }; + + for (String latex : in) { + final String placeholder = JLatexMathPlugin.prepareLatexTextPlaceholder(latex); + assertTrue(placeholder, placeholder.indexOf('\n') < 0); + if (placeholder.length() > 0) { + assertFalse(placeholder, Character.isWhitespace(placeholder.charAt(0))); + assertFalse(placeholder, Character.isWhitespace(placeholder.charAt(placeholder.length() - 1))); + } + } + } + + @Test + public void block_parser_registered() { + final JLatexMathPlugin plugin = JLatexMathPlugin.create(0); + final Parser.Builder builder = mock(Parser.Builder.class); + plugin.configureParser(builder); + verify(builder, times(1)).customBlockParserFactory(any(BlockParserFactory.class)); + } + + @Test + public void visitor_registered() { + final JLatexMathPlugin plugin = JLatexMathPlugin.create(0); + final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class); + plugin.configureVisitor(builder); + //noinspection unchecked + verify(builder, times(1)) + .on(eq(JLatexMathBlock.class), any(MarkwonVisitor.NodeVisitor.class)); + } + + @Test + public void visit() { + final JLatexMathPlugin plugin = JLatexMathPlugin.create(0, new JLatexMathPlugin.BuilderConfigure() { + @Override + public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { + // no async in test (nooped for this test) + builder.executorService(mock(ExecutorService.class)); + } + }); + final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class); + final ArgumentCaptor captor = + ArgumentCaptor.forClass(MarkwonVisitor.NodeVisitor.class); + plugin.configureVisitor(builder); + //noinspection unchecked + verify(builder, times(1)) + .on(eq(JLatexMathBlock.class), captor.capture()); + final MarkwonVisitor.NodeVisitor nodeVisitor = captor.getValue(); + + final MarkwonVisitor visitor = mock(MarkwonVisitor.class); + final JLatexMathBlock block = mock(JLatexMathBlock.class); + when(block.latex()).thenReturn(" first\nsecond\n "); + + final SpannableBuilder spannableBuilder = mock(SpannableBuilder.class); + when(visitor.builder()).thenReturn(spannableBuilder); + when(visitor.configuration()).thenReturn(mock(MarkwonConfiguration.class)); + + //noinspection unchecked + nodeVisitor.visit(visitor, block); + + verify(block, times(1)).latex(); + verify(visitor, times(1)).length(); + + final ArgumentCaptor stringArgumentCaptor = ArgumentCaptor.forClass(String.class); + verify(spannableBuilder, times(1)).append(stringArgumentCaptor.capture()); + + final String placeholder = stringArgumentCaptor.getValue(); + assertTrue(placeholder, placeholder.indexOf('\n') < 0); + + verify(visitor, times(1)).setSpans(eq(0), any()); + } +} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java index e2c45dc5..7adee8e8 100644 --- a/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/latex/LatexActivity.java @@ -68,20 +68,23 @@ public class LatexActivity extends Activity { .build(); if (true) { - final String l = "$$\n" + - " P(X=r)=\\frac{\\lambda^r e^{-\\lambda}}{r!}\n" + - "$$\n" + - "\n" + - "$$\n" + - " P(Xr)=1-P(Xr)=1-P(X