diff --git a/CHANGELOG.md b/CHANGELOG.md index dff5b68b..0c0de41d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * `TextLayoutSpan` to obtain `Layout` in which markdown is displayed (applied by `TablePlugin`, more specifically `TableRowSpan` to propagate layout in which cell content is displayed) * `HtmlEmptyTagReplacement` now is configurable by `HtmlPlugin`, `iframe` handling ([#235]) * `AsyncDrawableLoader` now uses `TextView` width without padding instead of width of canvas +* Support for images inside table cells (`ext-tables` module) [#235]: https://github.com/noties/Markwon/issues/235 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 87456358..d6d561b0 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 @@ -530,7 +530,20 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @NonNull @Override public Rect resolveImageSize(@NonNull AsyncDrawable drawable) { - return drawable.getResult().getBounds(); + + // @since $nap; resolve inline size (scale down if exceed available width) + final Rect imageBounds = drawable.getResult().getBounds(); + final int canvasWidth = drawable.getLastKnownCanvasWidth(); + final int w = imageBounds.width(); + + 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; } } } diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java index 32abef40..d00c1850 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java @@ -4,6 +4,7 @@ import android.annotation.SuppressLint; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.text.Layout; import android.text.Spannable; import android.text.SpannableString; @@ -23,6 +24,8 @@ import java.util.ArrayList; import java.util.List; import io.noties.markwon.core.spans.TextLayoutSpan; +import io.noties.markwon.image.AsyncDrawable; +import io.noties.markwon.image.AsyncDrawableSpan; import io.noties.markwon.utils.LeadingMarginUtils; import io.noties.markwon.utils.SpanUtils; @@ -71,7 +74,7 @@ public class TableRowSpan extends ReplacementSpan { private final TableTheme theme; private final List cells; - private final List layouts; + private final List layouts; private final TextPaint textPaint; private final boolean header; private final boolean odd; @@ -112,7 +115,7 @@ public class TableRowSpan extends ReplacementSpan { if (fm != null) { int max = 0; - for (StaticLayout layout : layouts) { + for (Layout layout : layouts) { final int height = layout.getHeight(); if (height > max) { max = height; @@ -240,7 +243,7 @@ public class TableRowSpan extends ReplacementSpan { final int borderTop = isFirstTableRow ? borderWidth : 0; final int borderBottom = bottom - top - borderWidth; - StaticLayout layout; + Layout layout; for (int i = 0; i < size; i++) { layout = layouts.get(i); final int save = canvas.save(); @@ -297,34 +300,76 @@ public class TableRowSpan extends ReplacementSpan { final int w = (width / columns) - padding; this.layouts.clear(); - Cell cell; - StaticLayout layout; - Spannable spannable; for (int i = 0, size = cells.size(); i < size; i++) { + makeLayout(i, w, cells.get(i)); + } + } - cell = cells.get(i); + private void makeLayout(final int index, final int width, @NonNull final Cell cell) { - if (cell.text instanceof Spannable) { - spannable = (Spannable) cell.text; - } else { - spannable = new SpannableString(cell.text); + final Runnable recreate = new Runnable() { + @Override + public void run() { + final Invalidator invalidator = TableRowSpan.this.invalidator; + if (invalidator != null) { + layouts.remove(index); + makeLayout(index, width, cell); + invalidator.invalidate(); + } } + }; - layout = new StaticLayout( - spannable, - textPaint, - w, - alignment(cell.alignment), - 1.0F, - 0.0F, - false - ); + final Spannable spannable; - // @since $nap; - TextLayoutSpan.applyTo(spannable, layout); + if (cell.text instanceof Spannable) { + spannable = (Spannable) cell.text; + } else { + spannable = new SpannableString(cell.text); + } - layouts.add(layout); + final Layout layout = new StaticLayout( + spannable, + textPaint, + width, + alignment(cell.alignment), + 1.0F, + 0.0F, + false + ); + + // @since $nap; + TextLayoutSpan.applyTo(spannable, layout); + + // @since $nap; + scheduleAsyncDrawables(spannable, recreate); + + layouts.add(index, layout); + } + + private void scheduleAsyncDrawables(@NonNull Spannable spannable, @NonNull final Runnable recreate) { + + final AsyncDrawableSpan[] spans = spannable.getSpans(0, spannable.length(), AsyncDrawableSpan.class); + if (spans != null + && spans.length > 0) { + + for (AsyncDrawableSpan span : spans) { + + final AsyncDrawable drawable = span.getDrawable(); + + // it is absolutely crucial to check if drawable is already attached, + // otherwise we would end up with a loop + if (drawable.isAttached()) { + continue; + } + + drawable.setCallback2(new CallbackAdapter() { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + recreate.run(); + } + }); + } } } @@ -348,4 +393,21 @@ public class TableRowSpan extends ReplacementSpan { public void invalidator(@Nullable Invalidator invalidator) { this.invalidator = invalidator; } + + private static abstract class CallbackAdapter implements Drawable.Callback { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + + } + } } 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 b8a7d9e3..d2cd69ac 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,7 +68,8 @@ public class LatexActivity extends ActivityWithMenuOptions { .add("textColor", this::textColor) .add("defaultTextColor", this::defaultTextColor) .add("inlineAndBlock", this::inlineAndBlock) - .add("dark", this::dark); + .add("dark", this::dark) + .add("omega", this::omega); } @Override @@ -221,6 +222,18 @@ public class LatexActivity extends ActivityWithMenuOptions { renderWithBlocksAndInlines(md); } + private void omega() { + final String md = "" + + "# Block\n\n" + + "$$\n" + + "\\Omega\n" + + "$$\n\n" + + "# Inline\n\n" + + "$$\\Omega$$"; + + renderWithBlocksAndInlines(md); + } + @NonNull private static String wrapLatexInSampleMarkdown(@NonNull String latex) { return "" + diff --git a/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java b/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java index d7153ec6..3dade55c 100644 --- a/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java @@ -7,10 +7,11 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import io.noties.debug.Debug; import io.noties.markwon.Markwon; +import io.noties.markwon.ext.latex.JLatexMathPlugin; import io.noties.markwon.ext.tables.TablePlugin; -import io.noties.markwon.ext.tables.TableTheme; +import io.noties.markwon.image.ImagesPlugin; +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; import io.noties.markwon.linkify.LinkifyPlugin; import io.noties.markwon.sample.ActivityWithMenuOptions; import io.noties.markwon.sample.MenuOptions; @@ -25,7 +26,9 @@ public class TableActivity extends ActivityWithMenuOptions { public MenuOptions menuOptions() { return MenuOptions.create() .add("customize", this::customize) - .add("tableAndLinkify", this::tableAndLinkify); + .add("tableAndLinkify", this::tableAndLinkify) + .add("withImages", this::withImages) + .add("withLatex", this::withLatex); } private TextView textView; @@ -86,4 +89,47 @@ public class TableActivity extends ActivityWithMenuOptions { markwon.setMarkdown(textView, md); } + + private void withImages() { + + final String md = "" + + "| HEADER | HEADER |\n" + + "|:----:|:----:|\n" + + "| ![Build](https://github.com/noties/Markwon/workflows/Build/badge.svg) | Build |\n" + + "| Stable | ![stable](https://img.shields.io/maven-central/v/io.noties.markwon/core.svg?label=stable) |\n" + + "| BIG | ![image](https://images.pexels.com/photos/41171/brussels-sprouts-sprouts-cabbage-grocery-41171.jpeg) |\n" + + "\n"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(ImagesPlugin.create()) + .usePlugin(TablePlugin.create(this)) + .build(); + + markwon.setMarkdown(textView, md); + } + + private void withLatex() { + + String latex = "\\begin{array}{cc}"; + latex += "\\fbox{\\text{A framed box with \\textdbend}}&\\shadowbox{\\text{A shadowed box}}\\cr"; + latex += "\\doublebox{\\text{A double framed box}}&\\ovalbox{\\text{An oval framed box}}\\cr"; + latex += "\\end{array}"; + + final String md = "" + + "| HEADER | HEADER |\n" + + "|:----:|:----:|\n" + + "| ![Build](https://github.com/noties/Markwon/workflows/Build/badge.svg) | Build |\n" + + "| Stable | ![stable](https://img.shields.io/maven-central/v/io.noties.markwon/core.svg?label=stable) |\n" + + "| BIG | $$" + latex + "$$ |\n" + + "\n"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(ImagesPlugin.create()) + .usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> builder.inlinesEnabled(true))) + .usePlugin(TablePlugin.create(this)) + .build(); + + markwon.setMarkdown(textView, md); + } }