Update sample configuration for latex block_and_inline renderMode

This commit is contained in:
Dimitry Ivanov 2020-02-26 15:08:00 +03:00
parent c7494a9225
commit a80ff09e15
9 changed files with 419 additions and 360 deletions

View File

@ -0,0 +1,50 @@
package io.noties.markwon.ext.latex;
import android.graphics.Rect;
import androidx.annotation.NonNull;
import io.noties.markwon.image.AsyncDrawable;
import io.noties.markwon.image.ImageSizeResolver;
// we must make drawable fit canvas (if specified), but do not keep the ratio whilst scaling up
// @since 4.0.0
class JLatexBlockImageSizeResolver extends ImageSizeResolver {
private final boolean fitCanvas;
JLatexBlockImageSizeResolver(boolean fitCanvas) {
this.fitCanvas = fitCanvas;
}
@NonNull
@Override
public Rect resolveImageSize(@NonNull AsyncDrawable drawable) {
final Rect imageBounds = drawable.getResult().getBounds();
final int canvasWidth = drawable.getLastKnownCanvasWidth();
if (fitCanvas) {
// we modify bounds only if `fitCanvas` is true
final int w = imageBounds.width();
if (w < canvasWidth) {
// increase width and center formula (keep height as-is)
return new Rect(0, 0, canvasWidth, imageBounds.height());
}
// @since 4.0.2 we additionally scale down the resulting formula (keeping the ratio)
// the thing is - JLatexMathDrawable will do it anyway, but it will modify its own
// bounds (which AsyncDrawable won't catch), thus leading to an empty space after the formula
if (w > canvasWidth) {
// here we must scale it down (keeping the ratio)
final float ratio = (float) w / imageBounds.height();
final int h = (int) (canvasWidth / ratio + .5F);
return new Rect(0, 0, canvasWidth, h);
}
}
return imageBounds;
}
}

View File

@ -0,0 +1,61 @@
package io.noties.markwon.ext.latex;
import android.graphics.Paint;
import android.graphics.Rect;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.image.AsyncDrawable;
import io.noties.markwon.image.AsyncDrawableSpan;
/**
* @since 4.3.0-SNAPSHOT
*/
class JLatexInlineAsyncDrawableSpan extends AsyncDrawableSpan {
private final AsyncDrawable drawable;
JLatexInlineAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, int alignment, boolean replacementTextIsLink) {
super(theme, drawable, alignment, replacementTextIsLink);
this.drawable = drawable;
}
@Override
public int getSize(
@NonNull Paint paint,
CharSequence text,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
@Nullable Paint.FontMetricsInt fm) {
// if we have no async drawable result - we will just render text
final int size;
if (drawable.hasResult()) {
final Rect rect = drawable.getBounds();
if (fm != null) {
final int half = rect.bottom / 2;
fm.ascent = -half;
fm.descent = half;
fm.top = fm.ascent;
fm.bottom = 0;
}
size = rect.right;
} else {
// NB, no specific text handling (no new lines, etc)
size = (int) (paint.measureText(text, start, end) + .5F);
}
return size;
}
}

View File

@ -1,7 +1,5 @@
package io.noties.markwon.ext.latex;
import android.util.Log;
import androidx.annotation.NonNull;
import org.commonmark.internal.util.Parsing;
@ -13,6 +11,10 @@ import org.commonmark.parser.block.BlockStart;
import org.commonmark.parser.block.MatchedBlockParser;
import org.commonmark.parser.block.ParserState;
/**
* @since 4.3.0-SNAPSHOT (although there was a class with the same name,
* which is renamed now to {@link JLatexMathBlockParserLegacy})
*/
public class JLatexMathBlockParser extends AbstractBlockParser {
private static final char DOLLAR = '$';
@ -22,8 +24,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
private final StringBuilder builder = new StringBuilder();
// private boolean isClosed;
private final int signs;
@SuppressWarnings("WeakerAccess")
@ -44,12 +44,9 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
// check for closing
if (parserState.getIndent() < Parsing.CODE_BLOCK_INDENT) {
Log.e("LTX", String.format("signs: %d, skip dollar: %s", signs, Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length)));
// if (Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) == signs) {
if (consume(DOLLAR, line, nextNonSpaceIndex, length) == signs) {
// okay, we have our number of signs
// let's consume spaces until the end
Log.e("LTX", String.format("length; %d, skip spaces: %s", length, Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length)));
if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) == length) {
return BlockContinue.finished();
}
@ -61,22 +58,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
@Override
public void addLine(CharSequence line) {
//
// if (builder.length() > 0) {
// builder.append('\n');
// }
//
// builder.append(line);
//
// final int length = builder.length();
// if (length > 1) {
// isClosed = '$' == builder.charAt(length - 1)
// && '$' == builder.charAt(length - 2);
// if (isClosed) {
// builder.replace(length - 2, length, "");
// }
// }
Log.e("LTX", "addLine: " + line);
builder.append(line);
builder.append('\n');
}
@ -88,8 +69,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
public static class Factory extends AbstractBlockParserFactory {
// private static final Pattern RE = Pattern.compile("(\\${2,}) *$");
@Override
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
@ -111,7 +90,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
final CharSequence line = state.getLine();
final int length = line.length();
// final int signs = Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) - 1;
final int signs = consume(DOLLAR, line, nextNonSpaceIndex, length);
// 2 is minimum
@ -120,73 +98,16 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
}
// consume spaces until the end of the line, if any other content is found -> NONE
// TODO: here we can check mode in which we operate (legacy or not)
if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) {
return BlockStart.none();
}
Log.e("LTX", String.format("signs: %s, next: %d, length: %d, line: '%s'", signs, nextNonSpaceIndex, length, line));
return BlockStart.of(new JLatexMathBlockParser(signs))
.atIndex(length + 1);
// // check if it's an indented code block
// if (indent < Parsing.CODE_BLOCK_INDENT) {
//
// final int nextNonSpaceIndex = state.getNextNonSpaceIndex();
// final CharSequence line = state.getLine();
// final int length = line.length();
//
// final int signs = Parsing.skip('$', line, nextNonSpaceIndex, length);
//
// // 2 is minimum
// if (signs < 2) {
// return BlockStart.none();
// }
//
// // consume spaces until the end of the line, if any other content is found -> NONE
// if (Parsing.skip(' ', line, nextNonSpaceIndex + signs, length) != length) {
// return BlockStart.none();
// }
//
//// // consume spaces until the end of the line, if any other content is found -> NONE
//// if ((nextNonSpaceIndex + signs) < length) {
//// // check if more content is available
//// if (Parsing.skip(' ', line,nextNonSpaceIndex + signs, length) != length) {
//// return BlockStart.none();
//// }
//// }
//
//// final Matcher matcher = RE.matcher(line);
//// matcher.region(nextNonSpaceIndex, length);
//
//// Log.e("LATEX", String.format("nonSpace: %d, length: %s, line: '%s'", nextNonSpaceIndex, length, line));
//
// // we are looking for 2 `$$` subsequent signs
// // and immediate new-line or arbitrary number of white spaces (we check for the first one)
// // so, nextNonSpaceIndex + 2 >= length and both symbols are `$`s
// final int diff = length - (nextNonSpaceIndex + 2);
// if (diff >= 0) {
// // check for both `$`
// if (line.charAt(nextNonSpaceIndex) == '$'
// && line.charAt(nextNonSpaceIndex + 1) == '$') {
//
// if (diff > 0) {
// if (!Character.isWhitespace(line.charAt(nextNonSpaceIndex + 2))) {
// return BlockStart.none();
// }
// return BlockStart.of(new JLatexMathBlockParser()).atIndex(nextNonSpaceIndex + 3);
// }
//
// }
// }
// }
//
// return BlockStart.none();
}
}
@SuppressWarnings("SameParameterValue")
private static int consume(char c, @NonNull CharSequence line, int start, int end) {
for (int i = start; i < end; i++) {
if (c != line.charAt(i)) {

View File

@ -0,0 +1,82 @@
package io.noties.markwon.ext.latex;
import org.commonmark.node.Block;
import org.commonmark.parser.block.AbstractBlockParser;
import org.commonmark.parser.block.AbstractBlockParserFactory;
import org.commonmark.parser.block.BlockContinue;
import org.commonmark.parser.block.BlockStart;
import org.commonmark.parser.block.MatchedBlockParser;
import org.commonmark.parser.block.ParserState;
/**
* @since 4.3.0-SNAPSHOT (although it is just renamed parser from previous versions)
*/
public class JLatexMathBlockParserLegacy extends AbstractBlockParser {
private final JLatexMathBlock block = new JLatexMathBlock();
private final StringBuilder builder = new StringBuilder();
private boolean isClosed;
@Override
public Block getBlock() {
return block;
}
@Override
public BlockContinue tryContinue(ParserState parserState) {
if (isClosed) {
return BlockContinue.finished();
}
return BlockContinue.atIndex(parserState.getIndex());
}
@Override
public void addLine(CharSequence line) {
if (builder.length() > 0) {
builder.append('\n');
}
builder.append(line);
final int length = builder.length();
if (length > 1) {
isClosed = '$' == builder.charAt(length - 1)
&& '$' == builder.charAt(length - 2);
if (isClosed) {
builder.replace(length - 2, length, "");
}
}
}
@Override
public void closeBlock() {
block.latex(builder.toString());
}
public static class Factory extends AbstractBlockParserFactory {
@Override
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
final CharSequence line = state.getLine();
final int length = line != null
? line.length()
: 0;
if (length > 1) {
if ('$' == line.charAt(0)
&& '$' == line.charAt(1)) {
return BlockStart.of(new JLatexMathBlockParserLegacy())
.atIndex(state.getIndex() + 2);
}
}
return BlockStart.none();
}
}
}

View File

@ -1,6 +1,5 @@
package io.noties.markwon.ext.latex;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@ -10,7 +9,6 @@ import android.text.Spanned;
import android.util.Log;
import android.widget.TextView;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
@ -28,14 +26,11 @@ import java.util.concurrent.Future;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.MarkwonVisitor;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.image.AsyncDrawable;
import io.noties.markwon.image.AsyncDrawableLoader;
import io.noties.markwon.image.AsyncDrawableScheduler;
import io.noties.markwon.image.AsyncDrawableSpan;
import io.noties.markwon.image.ImageSize;
import io.noties.markwon.image.ImageSizeResolver;
import io.noties.markwon.image.ImageSizeResolverDef;
import io.noties.markwon.inlineparser.MarkwonInlineParser;
import ru.noties.jlatexmath.JLatexMathDrawable;
@ -68,7 +63,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
BLOCKS_AND_INLINES
}
// TODO: inlines are not moved to a new line when exceed available width.. (api 23, emulator)
public interface BuilderConfigure {
void configureBuilder(@NonNull Builder builder);
}
@ -78,52 +72,65 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
return new JLatexMathPlugin(builder(textSize).build());
}
/**
* @since 4.3.0-SNAPSHOT
*/
@NonNull
public static JLatexMathPlugin create(@Px float inlineTextSize, @Px float blockTextSize) {
return new JLatexMathPlugin(builder(inlineTextSize, blockTextSize).build());
}
@NonNull
public static JLatexMathPlugin create(@NonNull Config config) {
return new JLatexMathPlugin(config);
}
@NonNull
public static JLatexMathPlugin create(float textSize, @NonNull BuilderConfigure builderConfigure) {
final Builder builder = new Builder(textSize);
public static JLatexMathPlugin create(@Px float textSize, @NonNull BuilderConfigure builderConfigure) {
final Builder builder = builder(textSize);
builderConfigure.configureBuilder(builder);
return new JLatexMathPlugin(builder.build());
}
/**
* @since 4.3.0-SNAPSHOT
*/
@NonNull
public static JLatexMathPlugin create(
@Px float inlineTextSize,
@Px float blockTextSize,
@NonNull BuilderConfigure builderConfigure) {
final Builder builder = builder(inlineTextSize, blockTextSize);
builderConfigure.configureBuilder(builder);
return new JLatexMathPlugin(builder.build());
}
@NonNull
public static JLatexMathPlugin.Builder builder(float textSize) {
return new Builder(textSize);
public static JLatexMathPlugin.Builder builder(@Px float textSize) {
return new Builder(JLatexMathTheme.builder(textSize));
}
/**
* @since 4.3.0-SNAPSHOT
*/
@NonNull
public static JLatexMathPlugin.Builder builder(@Px float inlineTextSize, @Px float blockTextSize) {
return new Builder(JLatexMathTheme.builder(inlineTextSize, blockTextSize));
}
public static class Config {
private final float textSize;
// @since 4.3.0-SNAPSHOT
private final JLatexMathTheme theme;
// @since 4.0.0
private final JLatexMathTheme.BackgroundProvider backgroundProvider;
// @since 4.3.0-SNAPSHOT
private final RenderMode renderMode;
@JLatexMathDrawable.Align
private final int align;
private final boolean fitCanvas;
// @since 4.0.0
private final int paddingHorizontal;
// @since 4.0.0
private final int paddingVertical;
// @since 4.0.0
private final ExecutorService executorService;
Config(@NonNull Builder builder) {
this.textSize = builder.textSize;
this.backgroundProvider = builder.backgroundProvider;
this.align = builder.align;
this.fitCanvas = builder.fitCanvas;
this.paddingHorizontal = builder.paddingHorizontal;
this.paddingVertical = builder.paddingVertical;
this.theme = builder.theme.build();
this.renderMode = builder.renderMode;
// @since 4.0.0
ExecutorService executorService = builder.executorService;
if (executorService == null) {
@ -133,29 +140,46 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
}
}
private final Config config;
private final JLatextAsyncDrawableLoader jLatextAsyncDrawableLoader;
private final JLatexImageSizeResolver jLatexImageSizeResolver;
private final JLatexBlockImageSizeResolver jLatexBlockImageSizeResolver;
private final ImageSizeResolver inlineImageSizeResolver;
@SuppressWarnings("WeakerAccess")
JLatexMathPlugin(@NonNull Config config) {
this.config = config;
this.jLatextAsyncDrawableLoader = new JLatextAsyncDrawableLoader(config);
this.jLatexImageSizeResolver = new JLatexImageSizeResolver(config.fitCanvas);
this.jLatexBlockImageSizeResolver = new JLatexBlockImageSizeResolver(config.theme.blockFitCanvas());
this.inlineImageSizeResolver = new InlineImageSizeResolver();
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
// what we can do:
// [0-3] spaces before block start/end
// if it's $$\n -> block
// if it's $$\\dhdsfjh$$ -> inline
// TODO: 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
builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
switch (config.renderMode) {
final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder()
.addInlineProcessor(new JLatexMathInlineProcessor())
.build();
builder.inlineParserFactory(factory);
case LEGACY: {
builder.customBlockParserFactory(new JLatexMathBlockParserLegacy.Factory());
}
break;
case BLOCKS_AND_INLINES: {
builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder()
.addInlineProcessor(new JLatexMathInlineProcessor())
.build();
builder.inlineParserFactory(factory);
}
break;
default:
throw new RuntimeException("Unexpected `renderMode`: " + config.renderMode);
}
}
@Override
@ -182,7 +206,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
new JLatextAsyncDrawable(
latex,
jLatextAsyncDrawableLoader,
jLatexImageSizeResolver,
jLatexBlockImageSizeResolver,
null,
true),
AsyncDrawableSpan.ALIGN_CENTER,
@ -196,34 +220,39 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
}
}
});
builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathNode jLatexMathNode) {
final String latex = jLatexMathNode.latex();
final int length = visitor.length();
// @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));
if (RenderMode.BLOCKS_AND_INLINES == config.renderMode) {
final MarkwonConfiguration configuration = visitor.configuration();
builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathNode jLatexMathNode) {
final String latex = jLatexMathNode.latex();
final AsyncDrawableSpan span = new JLatexAsyncDrawableSpan(
configuration.theme(),
new JLatextAsyncDrawable(
latex,
jLatextAsyncDrawableLoader,
new ImageSizeResolverDef(),
null,
false),
AsyncDrawableSpan.ALIGN_CENTER,
false);
final int length = visitor.length();
visitor.setSpans(length, span);
}
});
// @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();
final AsyncDrawableSpan span = new JLatexInlineAsyncDrawableSpan(
configuration.theme(),
new JLatextAsyncDrawable(
latex,
jLatextAsyncDrawableLoader,
inlineImageSizeResolver,
null,
false),
AsyncDrawableSpan.ALIGN_CENTER,
false);
visitor.setSpans(length, span);
}
});
}
}
@Override
@ -245,61 +274,30 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
public static class Builder {
private final float textSize;
// @since 4.3.0-SNAPSHOT
private final JLatexMathTheme.Builder theme;
// @since 4.0.0
private JLatexMathTheme.BackgroundProvider backgroundProvider;
@JLatexMathDrawable.Align
private int align = JLatexMathDrawable.ALIGN_CENTER;
private boolean fitCanvas = false;
// @since 4.0.0
private int paddingHorizontal;
// @since 4.0.0
private int paddingVertical;
// @since 4.3.0-SNAPSHOT
private RenderMode renderMode = RenderMode.BLOCKS_AND_INLINES;
// @since 4.0.0
private ExecutorService executorService;
Builder(float textSize) {
this.textSize = textSize;
Builder(@NonNull JLatexMathTheme.Builder builder) {
this.theme = builder;
}
@NonNull
public Builder backgroundProvider(@NonNull JLatexMathTheme.BackgroundProvider backgroundProvider) {
this.backgroundProvider = backgroundProvider;
return this;
}
@NonNull
public Builder align(@JLatexMathDrawable.Align int align) {
this.align = align;
return this;
}
@NonNull
public Builder fitCanvas(boolean fitCanvas) {
this.fitCanvas = fitCanvas;
return this;
}
@NonNull
public Builder padding(@Px int padding) {
this.paddingHorizontal = padding;
this.paddingVertical = padding;
return this;
public JLatexMathTheme.Builder theme() {
return theme;
}
/**
* @since 4.0.0
* @since 4.3.0-SNAPSHOT
*/
@NonNull
public Builder builder(@Px int paddingHorizontal, @Px int paddingVertical) {
this.paddingHorizontal = paddingHorizontal;
this.paddingVertical = paddingVertical;
public Builder renderMode(@NonNull RenderMode renderMode) {
this.renderMode = renderMode;
return this;
}
@ -319,7 +317,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
}
// @since 4.0.0
private static class JLatextAsyncDrawableLoader extends AsyncDrawableLoader {
static class JLatextAsyncDrawableLoader extends AsyncDrawableLoader {
private final Config config;
private final Handler handler = new Handler(Looper.getMainLooper());
@ -358,57 +356,15 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
private void execute() {
// @since 4.0.1 (background provider can be null)
final JLatexMathTheme.BackgroundProvider backgroundProvider = config.backgroundProvider;
final JLatexMathDrawable jLatexMathDrawable;
// TODO: obtain real values from theme (for blocks and inlines)
final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable;
if (jLatextAsyncDrawable.isBlock) {
// create JLatexMathDrawable
//noinspection ConstantConditions
jLatexMathDrawable =
JLatexMathDrawable.builder(drawable.getDestination())
.textSize(config.textSize)
.background(backgroundProvider != null ? backgroundProvider.provide() : null)
.align(config.align)
.fitCanvas(config.fitCanvas)
.padding(
config.paddingHorizontal,
config.paddingVertical,
config.paddingHorizontal,
config.paddingVertical)
.build();
} else {
jLatexMathDrawable =
JLatexMathDrawable.builder(drawable.getDestination())
.textSize(config.textSize)
// .background(backgroundProvider != null ? backgroundProvider.provide() : null)
// .align(config.align)
// .fitCanvas(config.fitCanvas)
// .padding(
// config.paddingHorizontal,
// config.paddingVertical,
// config.paddingHorizontal,
// config.paddingVertical)
.build();
}
// create JLatexMathDrawable
// //noinspection ConstantConditions
// final JLatexMathDrawable jLatexMathDrawable =
// JLatexMathDrawable.builder(drawable.getDestination())
// .textSize(config.textSize)
// .background(backgroundProvider != null ? backgroundProvider.provide() : null)
// .align(config.align)
// .fitCanvas(config.fitCanvas)
// .padding(
// config.paddingHorizontal,
// config.paddingVertical,
// config.paddingHorizontal,
// config.paddingVertical)
// .build();
if (jLatextAsyncDrawable.isBlock()) {
jLatexMathDrawable = createBlockDrawable(jLatextAsyncDrawable.getDestination());
} else {
jLatexMathDrawable = createInlineDrawable(jLatextAsyncDrawable.getDestination());
}
// we must post to handler, but also have a way to identify the drawable
// for which we are posting (in case of cancellation)
@ -447,109 +403,63 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
public Drawable placeholder(@NonNull AsyncDrawable drawable) {
return null;
}
// @since 4.3.0-SNAPSHOT
@NonNull
private JLatexMathDrawable createBlockDrawable(@NonNull String latex) {
final JLatexMathTheme theme = config.theme;
final JLatexMathTheme.BackgroundProvider backgroundProvider = theme.blockBackgroundProvider();
final JLatexMathTheme.Padding padding = theme.blockPadding();
final JLatexMathDrawable.Builder builder = JLatexMathDrawable.builder(latex)
.textSize(theme.blockTextSize())
.align(theme.blockHorizontalAlignment())
.fitCanvas(theme.blockFitCanvas());
if (backgroundProvider != null) {
builder.background(backgroundProvider.provide());
}
if (padding != null) {
builder.padding(padding.left, padding.top, padding.right, padding.bottom);
}
return builder.build();
}
// @since 4.3.0-SNAPSHOT
@NonNull
private JLatexMathDrawable createInlineDrawable(@NonNull String latex) {
final JLatexMathTheme theme = config.theme;
final JLatexMathTheme.BackgroundProvider backgroundProvider = theme.inlineBackgroundProvider();
final JLatexMathTheme.Padding padding = theme.inlinePadding();
final JLatexMathDrawable.Builder builder = JLatexMathDrawable.builder(latex)
.textSize(theme.inlineTextSize())
.fitCanvas(false);
if (backgroundProvider != null) {
builder.background(backgroundProvider.provide());
}
if (padding != null) {
builder.padding(padding.left, padding.top, padding.right, padding.bottom);
}
return builder.build();
}
}
// we must make drawable fit canvas (if specified), but do not keep the ratio whilst scaling up
// @since 4.0.0
private static class JLatexImageSizeResolver extends ImageSizeResolver {
private final boolean fitCanvas;
JLatexImageSizeResolver(boolean fitCanvas) {
this.fitCanvas = fitCanvas;
}
private static class InlineImageSizeResolver extends ImageSizeResolver {
@NonNull
@Override
public Rect resolveImageSize(@NonNull AsyncDrawable drawable) {
final Rect imageBounds = drawable.getResult().getBounds();
final int canvasWidth = drawable.getLastKnownCanvasWidth();
if (fitCanvas) {
// we modify bounds only if `fitCanvas` is true
final int w = imageBounds.width();
if (w < canvasWidth) {
// increase width and center formula (keep height as-is)
return new Rect(0, 0, canvasWidth, imageBounds.height());
}
// @since 4.0.2 we additionally scale down the resulting formula (keeping the ratio)
// the thing is - JLatexMathDrawable will do it anyway, but it will modify its own
// bounds (which AsyncDrawable won't catch), thus leading to an empty space after the formula
if (w > canvasWidth) {
// here we must scale it down (keeping the ratio)
final float ratio = (float) w / imageBounds.height();
final int h = (int) (canvasWidth / ratio + .5F);
return new Rect(0, 0, canvasWidth, h);
}
}
return imageBounds;
}
}
private static class JLatextAsyncDrawable extends AsyncDrawable {
private final boolean isBlock;
public JLatextAsyncDrawable(
@NonNull String destination,
@NonNull AsyncDrawableLoader loader,
@NonNull ImageSizeResolver imageSizeResolver,
@Nullable ImageSize imageSize,
boolean isBlock
) {
super(destination, loader, imageSizeResolver, imageSize);
this.isBlock = isBlock;
}
}
private static class JLatexAsyncDrawableSpan extends AsyncDrawableSpan {
private final AsyncDrawable drawable;
public JLatexAsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, int alignment, boolean replacementTextIsLink) {
super(theme, drawable, alignment, replacementTextIsLink);
this.drawable = drawable;
}
@Override
public int getSize(
@NonNull Paint paint,
CharSequence text,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
@Nullable Paint.FontMetricsInt fm) {
// if we have no async drawable result - we will just render text
final int size;
if (drawable.hasResult()) {
final Rect rect = drawable.getBounds();
if (fm != null) {
final int half = rect.bottom / 2;
fm.ascent = -half;
fm.descent = half;
fm.top = fm.ascent;
fm.bottom = 0;
}
size = rect.right;
} else {
// NB, no specific text handling (no new lines, etc)
size = (int) (paint.measureText(text, start, end) + .5F);
}
return size;
return drawable.getResult().getBounds();
}
}
}

View File

@ -128,9 +128,9 @@ public abstract class JLatexMathTheme {
private BackgroundProvider inlineBackgroundProvider;
private BackgroundProvider blockBackgroundProvider;
private boolean blockFitCanvas;
private boolean blockFitCanvas = true;
// horizontal alignment (when there is additional horizontal space)
private int blockHorizontalAlignment;
private int blockHorizontalAlignment = JLatexMathDrawable.ALIGN_CENTER;
private Padding padding;
private Padding inlinePadding;
@ -196,7 +196,7 @@ public abstract class JLatexMathTheme {
@NonNull
public JLatexMathTheme build() {
return null;
return new Impl(this);
}
}

View File

@ -0,0 +1,32 @@
package io.noties.markwon.ext.latex;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.noties.markwon.image.AsyncDrawable;
import io.noties.markwon.image.AsyncDrawableLoader;
import io.noties.markwon.image.ImageSize;
import io.noties.markwon.image.ImageSizeResolver;
/**
* @since 4.3.0-SNAPSHOT
*/
class JLatextAsyncDrawable extends AsyncDrawable {
private final boolean isBlock;
JLatextAsyncDrawable(
@NonNull String destination,
@NonNull AsyncDrawableLoader loader,
@NonNull ImageSizeResolver imageSizeResolver,
@Nullable ImageSize imageSize,
boolean isBlock
) {
super(destination, loader, imageSizeResolver, imageSize);
this.isBlock = isBlock;
}
public boolean isBlock() {
return isBlock;
}
}

View File

@ -1,7 +1,7 @@
package io.noties.markwon.sample.latex;
import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.TextView;
@ -14,7 +14,6 @@ import io.noties.markwon.ext.latex.JLatexMathTheme;
import io.noties.markwon.sample.ActivityWithMenuOptions;
import io.noties.markwon.sample.MenuOptions;
import io.noties.markwon.sample.R;
import ru.noties.jlatexmath.JLatexMathDrawable;
public class LatexActivity extends ActivityWithMenuOptions {
@ -87,7 +86,7 @@ public class LatexActivity extends ActivityWithMenuOptions {
private static String wrapLatexInSampleMarkdown(@NonNull String latex) {
return "" +
"# Example of LaTeX\n\n" +
"(inline): $$" + latex + "$$ so nice, really? Now, (block):\n\n" +
"(inline): $$" + latex + "$$ so nice, really-really really-really really-really? Now, (block):\n\n" +
"$$\n" +
"" + latex + "\n" +
"$$\n\n" +
@ -95,23 +94,22 @@ public class LatexActivity extends ActivityWithMenuOptions {
}
private void render(@NonNull String markdown) {
final float textSize = textView.getTextSize();
final Resources r = getResources();
final Markwon markwon = Markwon.builder(this)
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() {
@Override
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
builder
.backgroundProvider(new JLatexMathTheme.BackgroundProvider() {
@NonNull
@Override
public Drawable provide() {
return new ColorDrawable(0x40ff0000);
}
})
.fitCanvas(true)
.align(JLatexMathDrawable.ALIGN_CENTER)
.padding(48)
;
}
.usePlugin(JLatexMathPlugin.create(textSize, textSize * 1.25F, builder -> {
builder.theme()
.inlineBackgroundProvider(() -> new ColorDrawable(0x1000ff00))
.blockBackgroundProvider(() -> new ColorDrawable(0x10ff0000))
.blockPadding(JLatexMathTheme.Padding.symmetric(
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();

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="latex_block_padding_vertical">8dip</dimen>
<dimen name="latex_block_padding_horizontal">16dip</dimen>
</resources>