Merge 708f9fc6d59632645ce233e6628ad7479612cd10 into 2ea148c30a07f91ffa37c0aa36af1cf2670441af

This commit is contained in:
Stepan Makhorin 2025-03-07 09:05:57 +00:00 committed by GitHub
commit f0e2fe71a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 207 additions and 24 deletions

View File

@ -0,0 +1,143 @@
package io.noties.markwon.ext.latex;
import android.graphics.drawable.Drawable;
import android.util.LruCache;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import ru.noties.jlatexmath.JLatexMathDrawable;
/**
* Cache for JLatexMathDrawable instances to improve performance
* by avoiding redundant rendering of the same LaTeX formulas.
*
*/
public class JLatexMathDrawableCache {
private static final int DEFAULT_CACHE_SIZE = 32;
// Singleton instance
private static volatile JLatexMathDrawableCache instance;
// LRU cache to store rendered LaTeX drawables
private final LruCache<String, Object> cache;
// Whether to enable the cache
private final boolean enabled;
private JLatexMathDrawableCache(int maxSize, boolean enabled) {
this.cache = new LruCache<>(maxSize);
this.enabled = enabled;
}
/**
* Get the singleton instance of the cache
*/
@NonNull
public static JLatexMathDrawableCache getInstance() {
if (instance == null) {
synchronized (JLatexMathDrawableCache.class) {
if (instance == null) {
instance = new JLatexMathDrawableCache(DEFAULT_CACHE_SIZE, true);
}
}
}
return instance;
}
/**
* Create a new instance with custom configuration
*
* @param maxSize maximum number of entries in the cache
* @param enabled whether the cache is enabled
* @return a new cache instance
*/
@NonNull
public static JLatexMathDrawableCache create(int maxSize, boolean enabled) {
return new JLatexMathDrawableCache(maxSize, enabled);
}
/**
* Get a drawable from the cache
*
* @param key the LaTeX formula string
* @return the cached drawable or null if not found
*/
/**
* Cache entry containing both the drawable and its configuration
*/
private static class CacheEntry {
final Drawable drawable;
final JLatexMathDrawable.Builder builder;
CacheEntry(Drawable drawable, JLatexMathDrawable.Builder builder) {
this.drawable = drawable;
this.builder = builder;
}
}
@Nullable
public Drawable get(@NonNull String key) {
if (!enabled) {
return null;
}
synchronized (cache) {
final CacheEntry entry = (CacheEntry) cache.get(key);
if (entry != null) {
if (entry.drawable instanceof JLatexMathDrawable && entry.builder != null) {
// Recreate the drawable with the same configuration
return entry.builder.build();
}
return entry.drawable;
}
return null;
}
}
/**
* Store a drawable in the cache
*
* @param key the LaTeX formula string
* @param drawable the drawable to cache
*/
public void put(@NonNull String key, @NonNull Drawable drawable) {
put(key, drawable, null);
}
/**
* Store a drawable in the cache with its builder for recreation
*
* @param key the LaTeX formula string
* @param drawable the drawable to cache
* @param builder the builder used to create the drawable
*/
public void put(@NonNull String key, @NonNull Drawable drawable, @Nullable JLatexMathDrawable.Builder builder) {
if (!enabled) {
return;
}
synchronized (cache) {
cache.put(key, new CacheEntry(drawable, builder));
}
}
/**
* Clear the cache
*/
public void clear() {
synchronized (cache) {
cache.evictAll();
}
}
/**
* Get the current size of the cache
*/
public int size() {
synchronized (cache) {
return cache.size();
}
}
}

View File

@ -359,16 +359,51 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
private final Config config;
private final Handler handler = new Handler(Looper.getMainLooper());
private final Map<AsyncDrawable, Future<?>> cache = new HashMap<>(3);
private final JLatexMathDrawableCache drawableCache;
JLatextAsyncDrawableLoader(@NonNull Config config) {
this.config = config;
this.drawableCache = config.cacheEnabled
? JLatexMathDrawableCache.create(config.cacheSize, true)
: JLatexMathDrawableCache.create(0, false);
}
@Override
public void load(@NonNull final AsyncDrawable drawable) {
// this method must be called from main-thread only (thus synchronization can be skipped)
final String latex = drawable.getDestination();
// Check the cache first
final Drawable cachedDrawable = drawableCache.get(latex);
if (cachedDrawable != null) {
// Use cached drawable immediately
drawable.setResult(cachedDrawable);
return;
}
// If synchronous rendering is enabled, render immediately on the main thread
if (config.syncRendering) {
try {
final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable;
final JLatexMathDrawable result;
if (jLatextAsyncDrawable.isBlock()) {
result = createBlockDrawable(jLatextAsyncDrawable);
} else {
result = createInlineDrawable(jLatextAsyncDrawable);
}
// Cache the result
drawableCache.put(latex, result);
// Set the result immediately
drawable.setResult(result);
} catch (Throwable t) {
handleRenderingError(drawable, t);
}
return;
}
// check for currently running tasks associated with provided drawable
final Future<?> future = cache.get(drawable);
@ -376,7 +411,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
// as asyncDrawable is immutable, it won't have destination changed (so there is no need
// to cancel any started tasks)
if (future == null) {
cache.put(drawable, config.executorService.submit(new Runnable() {
@Override
public void run() {
@ -384,6 +418,30 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
try {
execute();
} catch (Throwable t) {
handleRenderingError(drawable, t);
}
}
private void execute() {
final JLatexMathDrawable jLatexMathDrawable;
final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable;
if (jLatextAsyncDrawable.isBlock()) {
jLatexMathDrawable = createBlockDrawable(jLatextAsyncDrawable);
} else {
jLatexMathDrawable = createInlineDrawable(jLatextAsyncDrawable);
}
// Cache the result
drawableCache.put(latex, jLatexMathDrawable);
setResult(drawable, jLatexMathDrawable);
}
}));
}
}
private void handleRenderingError(@NonNull AsyncDrawable drawable, @NonNull Throwable t) {
// @since 4.3.0 add error handling
final ErrorHandler errorHandler = config.errorHandler;
if (errorHandler == null) {
@ -404,25 +462,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
}
}
}
}
private void execute() {
final JLatexMathDrawable jLatexMathDrawable;
final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable;
if (jLatextAsyncDrawable.isBlock()) {
jLatexMathDrawable = createBlockDrawable(jLatextAsyncDrawable);
} else {
jLatexMathDrawable = createInlineDrawable(jLatextAsyncDrawable);
}
setResult(drawable, jLatexMathDrawable);
}
}));
}
}
@Override
public void cancel(@NonNull AsyncDrawable drawable) {