Stabilizing latex API

This commit is contained in:
Dimitry Ivanov 2020-03-09 17:40:16 +03:00
parent c90675d67b
commit 69c2d1255c
7 changed files with 279 additions and 134 deletions

View File

@ -2,17 +2,9 @@
# 4.3.0-SNAPSHOT
* add `MarkwonInlineParserPlugin` in `inline-parser` module
* `JLatexMathPlugin` now supports both inline and block structures
this comes with a breaking change: `JLatexMathPlugin` now depends on `inline-parser` module and `MarkwonInlineParserPlugin` must be explicitly added to a `Markwon` instance:
```java
final Markwon markwon = Markwon.builder(this)
.usePlugin(MarkwonInlineParserPlugin.create())
.usePlugin(JLatexMathPlugin.create(textSize))
.build();
```
* `JLatexMathPlugin` now supports inline LaTeX structures via `MarkwonInlineParserPlugin`
dependency (must be explicitly added to `Markwon` whilst configuring)
* `JLatexMathPlugin`: add `theme` (to customize both inlines and blocks)
* `JLatexMathPlugin`: add `renderMode` to use previous (pre `4.3.0`) LaTeX rendering (`LEGACY` & `BLOCKS_AND_INLINES`)
* add `JLatexMathPlugin.ErrorHandler` to catch latex rendering errors and (optionally) display error drawable ([#204])
* `JLatexMathPlugin` add text color customization ([#207])
* `JLatexMathPlugin` will use text color of widget in which it is displayed **if color is not set explicitly**
@ -23,6 +15,34 @@
* `linkify` - option to use `LinkifyCompat` in `LinkifyPlugin` ([#201])
<br>Thanks to [@drakeet]
```java
// default usage: new blocks parser, no inlines
final Markwon markwon = Markwon.builder(this)
.usePlugin(JLatexMathPlugin.create(textSize))
.build();
```
```java
// legacy blocks (pre `4.3.0`) parsing, no inlines
final Markwon markwon = Markwon.builder(this)
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> builder.blocksLegacy(true)))
.build();
```
```java
// new blocks parsing and inline parsing
final Markwon markwon = Markwon.builder(this)
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> {
// blocksEnabled and blocksLegacy can be omitted
builder
.blocksEnabled(true)
.blocksLegacy(false)
.inlinesEnabled(true);
}))
.build();
```
[#189]: https://github.com/noties/Markwon/issues/189
[#75]: https://github.com/noties/Markwon/issues/75
[#204]: https://github.com/noties/Markwon/issues/204

View File

@ -2,50 +2,120 @@
<MavenBadge4 :artifact="'ext-latex'" />
This is an extension that will help you display LaTeX formulas in your markdown.
Syntax is pretty simple: pre-fix and post-fix your latex with `$$` (double dollar sign).
`$$` should be the first characters in a line.
This is an extension that will help you display LaTeX content in your markdown.
Since <Badge text="4.3.0" /> supports both blocks and inlines markdown structures (blocks only before `4.3.0`).
## Blocks
Start a line with 2 (or more) `$` symbols followed by a new line:
```markdown
$$
\\text{A long division \\longdiv{12345}{13}
$$
```
LaTeX block content will be considered ended when a starting sequence of `$` is found on
a new line. If block was started with `$$$` it must be ended with `$$$` symbols.
## Inline
Exactly `$$` before and after _inline_ LaTeX content:
```markdown
$$\\text{A long division \\longdiv{12345}{13}$$
```
:::warning
By default inline nodes are disabled and must be enabled explicitly:
```java
Markwon.builder(context)
.use(JLatexMathPlugin.create(textSize))
final Markwon markwon = Markwon.builder(this)
// required plugin to support inline parsing
.usePlugin(MarkwonInlineParserPlugin.create())
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() {
@Override
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
// ENABLE inlines
builder.inlinesEnabled(true);
}
}))
.build();
```
Please note that usage of inline nodes **require** [MarkwonInlineParserPlugin](../inline-parser/)
:::
This extension uses [jlatexmath-android](https://github.com/noties/jlatexmath-android) artifact to create LaTeX drawable.
## Config
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(JLatexMathPlugin.create(textSize, new BuilderConfigure() {
// create default instance of plugin and use specified text size for both blocks and inlines
JLatexMathPlugin.create(textView.getTextSize());
// create default instance of plugin and use specified text sizes
JLatexMathPlugin.create(inlineTextSize, blockTextSize);
JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() {
@Override
public void configureBuilder(@NonNull Builder builder) {
builder
.align(JLatexMathDrawable.ALIGN_CENTER)
.fitCanvas(true)
.padding(paddingPx)
// @since 4.0.0 - horizontal and vertical padding
.padding(paddingHorizontalPx, paddingVerticalPx)
// @since 4.0.0 - change to provider
.backgroundProvider(() -> new MyDrawable()))
// @since 4.0.0 - optional, by default cached-thread-pool will be used
.executorService(Executors.newCachedThreadPool());
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
// enable inlines (require `MarkwonInlineParserPlugin`), by default `false`
builder.inlinesEnabled(true);
// use pre-4.3.0 LaTeX block parsing (by default `false`)
builder.blocksLegacy(true);
// by default true
builder.blocksEnabled(true);
// @since 4.3.0
builder.errorHandler(new JLatexMathPlugin.ErrorHandler() {
@Nullable
@Override
public Drawable handleError(@NonNull String latex, @NonNull Throwable error) {
// Receive error and optionally return drawable to be displayed instead
return null;
}
}))
.build();
});
// executor on which parsing of LaTeX is done (by default `Executors.newCachedThreadPool()`)
builder.executorService(Executors.newCachedThreadPool());
}
});
```
## Theme
```java
JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() {
@Override
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
// background provider for both inlines and blocks
// or more specific: `inlineBackgroundProvider` & `blockBackgroundProvider`
builder.theme().backgroundProvider(new JLatexMathTheme.BackgroundProvider() {
@NonNull
@Override
public Drawable provide() {
return new ColorDrawable(0xFFff0000);
}
});
// should block fit the whole canvas width, by default true
builder.theme().blockFitCanvas(true);
// horizontal alignment for block, by default ALIGN_CENTER
builder.theme().blockHorizontalAlignment(JLatexMathDrawable.ALIGN_CENTER);
// padding for both inlines and blocks
builder.theme().padding(JLatexMathTheme.Padding.all(8));
// padding for inlines
builder.theme().inlinePadding(JLatexMathTheme.Padding.symmetric(16, 8));
// padding for blocks
builder.theme().blockPadding(new JLatexMathTheme.Padding(0, 1, 2, 3));
// text color of LaTeX content for both inlines and blocks
// or more specific: `inlineTextColor` & `blockTextColor`
builder.theme().textColor(Color.RED);
}
});
```
:::tip
Sometimes it is enough to use rendered to an image LaTeX formula and

View File

@ -26,7 +26,6 @@ class JLatexMathBlockParser extends AbstractBlockParser {
private final int signs;
@SuppressWarnings("WeakerAccess")
JLatexMathBlockParser(int signs) {
this.signs = signs;
}

View File

@ -39,30 +39,6 @@ import ru.noties.jlatexmath.JLatexMathDrawable;
*/
public class JLatexMathPlugin extends AbstractMarkwonPlugin {
/**
* @since 4.3.0-SNAPSHOT
*/
public enum RenderMode {
/**
* <em>LEGACY</em> mode mimics pre {@code 4.3.0-SNAPSHOT} behavior by rendering LaTeX blocks only.
* In this mode LaTeX is started by `$$` (that must be exactly at the start of a line) and
* ended at whatever line that is ended with `$$` characters exactly.
*/
LEGACY,
/**
* Starting with {@code 4.3.0-SNAPSHOT} it is possible to have LaTeX inlines (which flows inside
* a text paragraph without breaking it). Inline LaTeX starts and ends with `$$` symbols. For example:
* {@code
* **bold $$\\begin{array}\\end{array}$$ bold-end**, and whatever more
* }
* LaTeX block starts on a new line started by 0-3 spaces and 2 (or more) {@code $} signs
* followed by a new-line (with any amount of space characters in-between). And ends on a new-line
* starting with 0-3 spaces followed by number of {@code $} signs that was used to <em>start the block</em>.
*/
BLOCKS_AND_INLINES
}
/**
* @since 4.3.0-SNAPSHOT
*/
@ -140,7 +116,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
final JLatexMathTheme theme;
// @since 4.3.0-SNAPSHOT
final RenderMode renderMode;
final boolean blocksEnabled;
final boolean blocksLegacy;
final boolean inlinesEnabled;
// @since 4.3.0-SNAPSHOT
final ErrorHandler errorHandler;
@ -149,7 +127,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
Config(@NonNull Builder builder) {
this.theme = builder.theme.build();
this.renderMode = builder.renderMode;
this.blocksEnabled = builder.blocksEnabled;
this.blocksLegacy = builder.blocksLegacy;
this.inlinesEnabled = builder.inlinesEnabled;
this.errorHandler = builder.errorHandler;
// @since 4.0.0
ExecutorService executorService = builder.executorService;
@ -177,7 +157,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
@Override
public void configure(@NonNull Registry registry) {
if (RenderMode.BLOCKS_AND_INLINES == config.renderMode) {
if (config.inlinesEnabled) {
registry.require(MarkwonInlineParserPlugin.class)
.factoryBuilder()
.addInlineProcessor(new JLatexMathInlineProcessor());
@ -186,31 +166,27 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
@Override
public void configureParser(@NonNull Parser.Builder builder) {
// depending on renderMode we should register our parsing here
// * for LEGACY -> just add custom block parser
// * for INLINE.. -> require InlinePlugin, add inline processor + add block parser
switch (config.renderMode) {
case LEGACY: {
// @since $nap;
if (config.blocksEnabled) {
if (config.blocksLegacy) {
builder.customBlockParserFactory(new JLatexMathBlockParserLegacy.Factory());
}
break;
case BLOCKS_AND_INLINES: {
} else {
builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
// inline processor is added through `registry`
}
break;
default:
throw new RuntimeException("Unexpected `renderMode`: " + config.renderMode);
}
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
addBlockVisitor(builder);
addInlineVisitor(builder);
}
private void addBlockVisitor(@NonNull MarkwonVisitor.Builder builder) {
if (!config.blocksEnabled) {
return;
}
builder.on(JLatexMathBlock.class, new MarkwonVisitor.NodeVisitor<JLatexMathBlock>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathBlock jLatexMathBlock) {
@ -247,9 +223,13 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
}
}
});
}
private void addInlineVisitor(@NonNull MarkwonVisitor.Builder builder) {
if (RenderMode.BLOCKS_AND_INLINES == config.renderMode) {
if (!config.inlinesEnabled) {
return;
}
builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() {
@Override
@ -280,7 +260,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
}
});
}
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
@ -299,13 +278,16 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
return latex.replace('\n', ' ').trim();
}
@SuppressWarnings({"unused", "UnusedReturnValue"})
public static class Builder {
// @since 4.3.0-SNAPSHOT
private final JLatexMathTheme.Builder theme;
// @since 4.3.0-SNAPSHOT
private RenderMode renderMode = RenderMode.BLOCKS_AND_INLINES;
private boolean blocksEnabled = true;
private boolean blocksLegacy;
private boolean inlinesEnabled;
// @since 4.3.0-SNAPSHOT
private ErrorHandler errorHandler;
@ -323,16 +305,35 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
}
/**
* @since 4.3.0-SNAPSHOT
* @since $nap;
*/
@SuppressWarnings("UnusedReturnValue")
@NonNull
public Builder renderMode(@NonNull RenderMode renderMode) {
this.renderMode = renderMode;
public Builder blocksEnabled(boolean blocksEnabled) {
this.blocksEnabled = blocksEnabled;
return this;
}
/**
* @param blocksLegacy indicates if blocks should be handled in legacy mode ({@code pre 4.3.0})
* @since 4.3.0-SNAPSHOT
*/
@NonNull
public Builder blocksLegacy(boolean blocksLegacy) {
this.blocksLegacy = blocksLegacy;
return this;
}
/**
* @param inlinesEnabled indicates if inline parsing should be enabled.
* NB, this requires `MarkwonInlineParserPlugin` to be used when creating `MarkwonInstance`
* @since 4.3.0-SNAPSHOT
*/
@NonNull
public Builder inlinesEnabled(boolean inlinesEnabled) {
this.inlinesEnabled = inlinesEnabled;
return this;
}
@SuppressWarnings("UnusedReturnValue")
@NonNull
public Builder errorHandler(@Nullable ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
@ -342,7 +343,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
/**
* @since 4.0.0
*/
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess"})
@SuppressWarnings("WeakerAccess")
@NonNull
public Builder executorService(@NonNull ExecutorService executorService) {
this.executorService = executorService;

View File

@ -128,7 +128,8 @@ public class JLatexMathPluginTest {
final JLatexMathPlugin plugin = JLatexMathPlugin.create(1, new JLatexMathPlugin.BuilderConfigure() {
@Override
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY);
builder.blocksLegacy(true);
builder.inlinesEnabled(false);
}
});
@ -167,12 +168,19 @@ public class JLatexMathPluginTest {
public void blocks_inlines_implicit() {
final JLatexMathPlugin plugin = JLatexMathPlugin.create(1);
final JLatexMathPlugin.Config config = plugin.config;
assertEquals(JLatexMathPlugin.RenderMode.BLOCKS_AND_INLINES, config.renderMode);
assertTrue("blocksEnabled", config.blocksEnabled);
assertFalse("blocksLegacy", config.blocksLegacy);
assertFalse("inlinesEnabled", config.inlinesEnabled);
}
@Test
public void blocks_inlines() {
final JLatexMathPlugin plugin = JLatexMathPlugin.create(12);
final JLatexMathPlugin plugin = JLatexMathPlugin.create(12, new JLatexMathPlugin.BuilderConfigure() {
@Override
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
builder.inlinesEnabled(true);
}
});
// registry
{

View File

@ -25,3 +25,20 @@ A new version must be pushed to MavenCentral and new git-tag with version name m
created in the repository.
Rinse and repeat.
## `@since` annotation
Although it is not required it is a nice thing to do: add `@since $VERSION` comment to the code
whenever it is possible (at least for publicly accessible code - API). This would help
navigating the project without the need to checkout the full VCS history. As keeping track of
current and/or upcoming version can be error-prone it is better to insert a generic `@since code`
that can be properly substituted upon a release.
For example, `@since $nap` seems like a good candidate. For this a live template can be created and used
whenever a new API method/field/functionality-change is introduced (`snc`):
```
@since $nap;
```
This live template would be possible to use in both inline comment and javadoc comment.

View File

@ -64,7 +64,8 @@ public class LatexActivity extends ActivityWithMenuOptions {
.add("error", this::error)
.add("legacy", this::legacy)
.add("textColor", this::textColor)
.add("defaultTextColor", this::defaultTextColor);
.add("defaultTextColor", this::defaultTextColor)
.add("inlineAndBlock", this::inlineAndBlock);
}
@Override
@ -87,19 +88,19 @@ public class LatexActivity extends ActivityWithMenuOptions {
}
private void array() {
render(wrapLatexInSampleMarkdown(LATEX_ARRAY));
renderWithBlocksAndInlines(wrapLatexInSampleMarkdown(LATEX_ARRAY));
}
private void longDivision() {
render(wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION));
renderWithBlocksAndInlines(wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION));
}
private void bangle() {
render(wrapLatexInSampleMarkdown(LATEX_BANGLE));
renderWithBlocksAndInlines(wrapLatexInSampleMarkdown(LATEX_BANGLE));
}
private void boxes() {
render(wrapLatexInSampleMarkdown(LATEX_BOXES));
renderWithBlocksAndInlines(wrapLatexInSampleMarkdown(LATEX_BOXES));
}
private void insideBlockQuote() {
@ -107,7 +108,7 @@ public class LatexActivity extends ActivityWithMenuOptions {
final String md = "" +
"# LaTeX inside a blockquote\n" +
"> $$" + latex + "$$\n";
render(md);
renderWithBlocksAndInlines(md);
}
private void error() {
@ -116,6 +117,7 @@ public class LatexActivity extends ActivityWithMenuOptions {
final Markwon markwon = Markwon.builder(this)
.usePlugin(MarkwonInlineParserPlugin.create())
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> {
builder.inlinesEnabled(true);
//noinspection Convert2Lambda
builder.errorHandler(new JLatexMathPlugin.ErrorHandler() {
@Nullable
@ -137,7 +139,7 @@ public class LatexActivity extends ActivityWithMenuOptions {
final Markwon markwon = Markwon.builder(this)
// LEGACY does not require inline parser
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> {
builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY);
builder.blocksLegacy(true);
builder.theme()
.backgroundProvider(() -> new ColorDrawable(0x100000ff))
.padding(JLatexMathTheme.Padding.all(48));
@ -151,27 +153,56 @@ public class LatexActivity extends ActivityWithMenuOptions {
final String md = wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION);
final Markwon markwon = Markwon.builder(this)
.usePlugin(MarkwonInlineParserPlugin.create())
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> builder.theme()
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> {
builder.inlinesEnabled(true);
builder.theme()
.inlineTextColor(Color.RED)
.blockTextColor(Color.GREEN)
.inlineBackgroundProvider(() -> new ColorDrawable(Color.YELLOW))
.blockBackgroundProvider(() -> new ColorDrawable(Color.GRAY))))
.blockBackgroundProvider(() -> new ColorDrawable(Color.GRAY));
}))
.build();
markwon.setMarkdown(textView, md);
}
private void defaultTextColor() {
// @since 4.3.0-SNAPSHOT text color is automatically taken from textView
// (if it's not specified explicitly via configuration)
textView.setTextColor(0xFFff0000);
final String md = wrapLatexInSampleMarkdown(LATEX_LONG_DIVISION);
final Markwon markwon = Markwon.builder(this)
.usePlugin(MarkwonInlineParserPlugin.create())
.usePlugin(JLatexMathPlugin.create(textView.getTextSize()))
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() {
@Override
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
builder.inlinesEnabled(true);
// override default text color
builder.theme()
.inlineTextColor(0xFF00ffff);
}
}))
.build();
markwon.setMarkdown(textView, md);
}
private void inlineAndBlock() {
final String md = "" +
"# Inline and block\n\n" +
"$$\\int_{a}^{b} f(x)dx = F(b) - F(a)$$\n\n" +
"this was **inline** _LaTeX_ $$\\int_{a}^{b} f(x)dx = F(b) - F(a)$$ and once again it was\n\n" +
"Now a block:\n\n" +
"$$\n" +
"\\int_{a}^{b} f(x)dx = F(b) - F(a)\n" +
"$$\n\n" +
"Not a block (content on delimited line), but inline instead:\n\n" +
"$$\\int_{a}^{b} f(x)dx = F(b) - F(a)$$" +
"\n\n" +
"that's it";
renderWithBlocksAndInlines(md);
}
@NonNull
private static String wrapLatexInSampleMarkdown(@NonNull String latex) {
return "" +
@ -183,7 +214,7 @@ public class LatexActivity extends ActivityWithMenuOptions {
"the end";
}
private void render(@NonNull String markdown) {
private void renderWithBlocksAndInlines(@NonNull String markdown) {
final float textSize = textView.getTextSize();
final Resources r = getResources();
@ -192,6 +223,8 @@ public class LatexActivity extends ActivityWithMenuOptions {
// NB! `MarkwonInlineParserPlugin` is required in order to parse inlines
.usePlugin(MarkwonInlineParserPlugin.create())
.usePlugin(JLatexMathPlugin.create(textSize, textSize * 1.25F, builder -> {
// Important thing to do is to enable inlines (by default disabled)
builder.inlinesEnabled(true);
builder.theme()
.inlineBackgroundProvider(() -> new ColorDrawable(0x1000ff00))
.blockBackgroundProvider(() -> new ColorDrawable(0x10ff0000))
@ -199,9 +232,6 @@ public class LatexActivity extends ActivityWithMenuOptions {
r.getDimensionPixelSize(R.dimen.latex_block_padding_vertical),
r.getDimensionPixelSize(R.dimen.latex_block_padding_horizontal)
));
// explicitly request LEGACY rendering mode
// builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY);
}))
.build();