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; package io.noties.markwon.ext.latex;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.commonmark.internal.util.Parsing; 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.MatchedBlockParser;
import org.commonmark.parser.block.ParserState; 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 { public class JLatexMathBlockParser extends AbstractBlockParser {
private static final char DOLLAR = '$'; private static final char DOLLAR = '$';
@ -22,8 +24,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
private final StringBuilder builder = new StringBuilder(); private final StringBuilder builder = new StringBuilder();
// private boolean isClosed;
private final int signs; private final int signs;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@ -44,12 +44,9 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
// check for closing // check for closing
if (parserState.getIndent() < Parsing.CODE_BLOCK_INDENT) { 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) { if (consume(DOLLAR, line, nextNonSpaceIndex, length) == signs) {
// okay, we have our number of signs // okay, we have our number of signs
// let's consume spaces until the end // 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) { if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) == length) {
return BlockContinue.finished(); return BlockContinue.finished();
} }
@ -61,22 +58,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
@Override @Override
public void addLine(CharSequence line) { 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(line);
builder.append('\n'); builder.append('\n');
} }
@ -88,8 +69,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
public static class Factory extends AbstractBlockParserFactory { public static class Factory extends AbstractBlockParserFactory {
// private static final Pattern RE = Pattern.compile("(\\${2,}) *$");
@Override @Override
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
@ -111,7 +90,6 @@ public class JLatexMathBlockParser extends AbstractBlockParser {
final CharSequence line = state.getLine(); final CharSequence line = state.getLine();
final int length = line.length(); final int length = line.length();
// final int signs = Parsing.skip(DOLLAR, line, nextNonSpaceIndex, length) - 1;
final int signs = consume(DOLLAR, line, nextNonSpaceIndex, length); final int signs = consume(DOLLAR, line, nextNonSpaceIndex, length);
// 2 is minimum // 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 // 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) { if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) {
return BlockStart.none(); 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)) return BlockStart.of(new JLatexMathBlockParser(signs))
.atIndex(length + 1); .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) { private static int consume(char c, @NonNull CharSequence line, int start, int end) {
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
if (c != line.charAt(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; package io.noties.markwon.ext.latex;
import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Handler; import android.os.Handler;
@ -10,7 +9,6 @@ import android.text.Spanned;
import android.util.Log; import android.util.Log;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.Px; import androidx.annotation.Px;
@ -28,14 +26,11 @@ import java.util.concurrent.Future;
import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.MarkwonVisitor; import io.noties.markwon.MarkwonVisitor;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.image.AsyncDrawable; import io.noties.markwon.image.AsyncDrawable;
import io.noties.markwon.image.AsyncDrawableLoader; import io.noties.markwon.image.AsyncDrawableLoader;
import io.noties.markwon.image.AsyncDrawableScheduler; import io.noties.markwon.image.AsyncDrawableScheduler;
import io.noties.markwon.image.AsyncDrawableSpan; import io.noties.markwon.image.AsyncDrawableSpan;
import io.noties.markwon.image.ImageSize;
import io.noties.markwon.image.ImageSizeResolver; import io.noties.markwon.image.ImageSizeResolver;
import io.noties.markwon.image.ImageSizeResolverDef;
import io.noties.markwon.inlineparser.MarkwonInlineParser; import io.noties.markwon.inlineparser.MarkwonInlineParser;
import ru.noties.jlatexmath.JLatexMathDrawable; import ru.noties.jlatexmath.JLatexMathDrawable;
@ -68,7 +63,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
BLOCKS_AND_INLINES BLOCKS_AND_INLINES
} }
// TODO: inlines are not moved to a new line when exceed available width.. (api 23, emulator)
public interface BuilderConfigure { public interface BuilderConfigure {
void configureBuilder(@NonNull Builder builder); void configureBuilder(@NonNull Builder builder);
} }
@ -78,52 +72,65 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
return new JLatexMathPlugin(builder(textSize).build()); 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 @NonNull
public static JLatexMathPlugin create(@NonNull Config config) { public static JLatexMathPlugin create(@NonNull Config config) {
return new JLatexMathPlugin(config); return new JLatexMathPlugin(config);
} }
@NonNull @NonNull
public static JLatexMathPlugin create(float textSize, @NonNull BuilderConfigure builderConfigure) { public static JLatexMathPlugin create(@Px float textSize, @NonNull BuilderConfigure builderConfigure) {
final Builder builder = new Builder(textSize); 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); builderConfigure.configureBuilder(builder);
return new JLatexMathPlugin(builder.build()); return new JLatexMathPlugin(builder.build());
} }
@NonNull @NonNull
public static JLatexMathPlugin.Builder builder(float textSize) { public static JLatexMathPlugin.Builder builder(@Px float textSize) {
return new Builder(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 { public static class Config {
private final float textSize; // @since 4.3.0-SNAPSHOT
private final JLatexMathTheme theme;
// @since 4.0.0 // @since 4.3.0-SNAPSHOT
private final JLatexMathTheme.BackgroundProvider backgroundProvider; 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; private final ExecutorService executorService;
Config(@NonNull Builder builder) { Config(@NonNull Builder builder) {
this.textSize = builder.textSize; this.theme = builder.theme.build();
this.backgroundProvider = builder.backgroundProvider; this.renderMode = builder.renderMode;
this.align = builder.align;
this.fitCanvas = builder.fitCanvas;
this.paddingHorizontal = builder.paddingHorizontal;
this.paddingVertical = builder.paddingVertical;
// @since 4.0.0 // @since 4.0.0
ExecutorService executorService = builder.executorService; ExecutorService executorService = builder.executorService;
if (executorService == null) { if (executorService == null) {
@ -133,29 +140,46 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
} }
} }
private final Config config;
private final JLatextAsyncDrawableLoader jLatextAsyncDrawableLoader; private final JLatextAsyncDrawableLoader jLatextAsyncDrawableLoader;
private final JLatexImageSizeResolver jLatexImageSizeResolver; private final JLatexBlockImageSizeResolver jLatexBlockImageSizeResolver;
private final ImageSizeResolver inlineImageSizeResolver;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
JLatexMathPlugin(@NonNull Config config) { JLatexMathPlugin(@NonNull Config config) {
this.config = config;
this.jLatextAsyncDrawableLoader = new JLatextAsyncDrawableLoader(config); this.jLatextAsyncDrawableLoader = new JLatextAsyncDrawableLoader(config);
this.jLatexImageSizeResolver = new JLatexImageSizeResolver(config.fitCanvas); this.jLatexBlockImageSizeResolver = new JLatexBlockImageSizeResolver(config.theme.blockFitCanvas());
this.inlineImageSizeResolver = new InlineImageSizeResolver();
} }
@Override @Override
public void configureParser(@NonNull Parser.Builder builder) { public void configureParser(@NonNull Parser.Builder builder) {
// what we can do: // TODO: depending on renderMode we should register our parsing here
// [0-3] spaces before block start/end // * for LEGACY -> just add custom block parser
// if it's $$\n -> block // * for INLINE.. -> require InlinePlugin, add inline processor + add block parser
// if it's $$\\dhdsfjh$$ -> inline
builder.customBlockParserFactory(new JLatexMathBlockParser.Factory()); switch (config.renderMode) {
final InlineParserFactory factory = MarkwonInlineParser.factoryBuilder() case LEGACY: {
.addInlineProcessor(new JLatexMathInlineProcessor()) builder.customBlockParserFactory(new JLatexMathBlockParserLegacy.Factory());
.build(); }
builder.inlineParserFactory(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 @Override
@ -182,7 +206,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
new JLatextAsyncDrawable( new JLatextAsyncDrawable(
latex, latex,
jLatextAsyncDrawableLoader, jLatextAsyncDrawableLoader,
jLatexImageSizeResolver, jLatexBlockImageSizeResolver,
null, null,
true), true),
AsyncDrawableSpan.ALIGN_CENTER, 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, if (RenderMode.BLOCKS_AND_INLINES == config.renderMode) {
// 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(); 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( final int length = visitor.length();
configuration.theme(),
new JLatextAsyncDrawable(
latex,
jLatextAsyncDrawableLoader,
new ImageSizeResolverDef(),
null,
false),
AsyncDrawableSpan.ALIGN_CENTER,
false);
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 @Override
@ -245,61 +274,30 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
public static class Builder { public static class Builder {
private final float textSize; // @since 4.3.0-SNAPSHOT
private final JLatexMathTheme.Builder theme;
// @since 4.0.0 // @since 4.3.0-SNAPSHOT
private JLatexMathTheme.BackgroundProvider backgroundProvider; private RenderMode renderMode = RenderMode.BLOCKS_AND_INLINES;
@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.0.0 // @since 4.0.0
private ExecutorService executorService; private ExecutorService executorService;
Builder(float textSize) { Builder(@NonNull JLatexMathTheme.Builder builder) {
this.textSize = textSize; this.theme = builder;
} }
@NonNull @NonNull
public Builder backgroundProvider(@NonNull JLatexMathTheme.BackgroundProvider backgroundProvider) { public JLatexMathTheme.Builder theme() {
this.backgroundProvider = backgroundProvider; return theme;
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;
} }
/** /**
* @since 4.0.0 * @since 4.3.0-SNAPSHOT
*/ */
@NonNull @NonNull
public Builder builder(@Px int paddingHorizontal, @Px int paddingVertical) { public Builder renderMode(@NonNull RenderMode renderMode) {
this.paddingHorizontal = paddingHorizontal; this.renderMode = renderMode;
this.paddingVertical = paddingVertical;
return this; return this;
} }
@ -319,7 +317,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
} }
// @since 4.0.0 // @since 4.0.0
private static class JLatextAsyncDrawableLoader extends AsyncDrawableLoader { static class JLatextAsyncDrawableLoader extends AsyncDrawableLoader {
private final Config config; private final Config config;
private final Handler handler = new Handler(Looper.getMainLooper()); private final Handler handler = new Handler(Looper.getMainLooper());
@ -358,57 +356,15 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
private void execute() { private void execute() {
// @since 4.0.1 (background provider can be null)
final JLatexMathTheme.BackgroundProvider backgroundProvider = config.backgroundProvider;
final JLatexMathDrawable jLatexMathDrawable; final JLatexMathDrawable jLatexMathDrawable;
// TODO: obtain real values from theme (for blocks and inlines)
final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable; 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 if (jLatextAsyncDrawable.isBlock()) {
// //noinspection ConstantConditions jLatexMathDrawable = createBlockDrawable(jLatextAsyncDrawable.getDestination());
// final JLatexMathDrawable jLatexMathDrawable = } else {
// JLatexMathDrawable.builder(drawable.getDestination()) jLatexMathDrawable = createInlineDrawable(jLatextAsyncDrawable.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();
// we must post to handler, but also have a way to identify the drawable // we must post to handler, but also have a way to identify the drawable
// for which we are posting (in case of cancellation) // for which we are posting (in case of cancellation)
@ -447,109 +403,63 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
public Drawable placeholder(@NonNull AsyncDrawable drawable) { public Drawable placeholder(@NonNull AsyncDrawable drawable) {
return null; 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 private static class InlineImageSizeResolver extends ImageSizeResolver {
// @since 4.0.0
private static class JLatexImageSizeResolver extends ImageSizeResolver {
private final boolean fitCanvas;
JLatexImageSizeResolver(boolean fitCanvas) {
this.fitCanvas = fitCanvas;
}
@NonNull @NonNull
@Override @Override
public Rect resolveImageSize(@NonNull AsyncDrawable drawable) { public Rect resolveImageSize(@NonNull AsyncDrawable drawable) {
return drawable.getResult().getBounds();
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;
} }
} }
} }

View File

@ -128,9 +128,9 @@ public abstract class JLatexMathTheme {
private BackgroundProvider inlineBackgroundProvider; private BackgroundProvider inlineBackgroundProvider;
private BackgroundProvider blockBackgroundProvider; private BackgroundProvider blockBackgroundProvider;
private boolean blockFitCanvas; private boolean blockFitCanvas = true;
// horizontal alignment (when there is additional horizontal space) // horizontal alignment (when there is additional horizontal space)
private int blockHorizontalAlignment; private int blockHorizontalAlignment = JLatexMathDrawable.ALIGN_CENTER;
private Padding padding; private Padding padding;
private Padding inlinePadding; private Padding inlinePadding;
@ -196,7 +196,7 @@ public abstract class JLatexMathTheme {
@NonNull @NonNull
public JLatexMathTheme build() { 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; package io.noties.markwon.sample.latex;
import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.widget.TextView; 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.ActivityWithMenuOptions;
import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.MenuOptions;
import io.noties.markwon.sample.R; import io.noties.markwon.sample.R;
import ru.noties.jlatexmath.JLatexMathDrawable;
public class LatexActivity extends ActivityWithMenuOptions { public class LatexActivity extends ActivityWithMenuOptions {
@ -87,7 +86,7 @@ public class LatexActivity extends ActivityWithMenuOptions {
private static String wrapLatexInSampleMarkdown(@NonNull String latex) { private static String wrapLatexInSampleMarkdown(@NonNull String latex) {
return "" + return "" +
"# Example of LaTeX\n\n" + "# 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" + "$$\n" +
"" + latex + "\n" + "" + latex + "\n" +
"$$\n\n" + "$$\n\n" +
@ -95,23 +94,22 @@ public class LatexActivity extends ActivityWithMenuOptions {
} }
private void render(@NonNull String markdown) { private void render(@NonNull String markdown) {
final float textSize = textView.getTextSize();
final Resources r = getResources();
final Markwon markwon = Markwon.builder(this) final Markwon markwon = Markwon.builder(this)
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), new JLatexMathPlugin.BuilderConfigure() { .usePlugin(JLatexMathPlugin.create(textSize, textSize * 1.25F, builder -> {
@Override builder.theme()
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) { .inlineBackgroundProvider(() -> new ColorDrawable(0x1000ff00))
builder .blockBackgroundProvider(() -> new ColorDrawable(0x10ff0000))
.backgroundProvider(new JLatexMathTheme.BackgroundProvider() { .blockPadding(JLatexMathTheme.Padding.symmetric(
@NonNull r.getDimensionPixelSize(R.dimen.latex_block_padding_vertical),
@Override r.getDimensionPixelSize(R.dimen.latex_block_padding_horizontal)
public Drawable provide() { ));
return new ColorDrawable(0x40ff0000);
} // explicitly request LEGACY rendering mode
}) // builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY);
.fitCanvas(true)
.align(JLatexMathDrawable.ALIGN_CENTER)
.padding(48)
;
}
})) }))
.build(); .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>