Merge 708f9fc6d59632645ce233e6628ad7479612cd10 into 2ea148c30a07f91ffa37c0aa36af1cf2670441af
This commit is contained in:
		
						commit
						f0e2fe71a5
					
				@ -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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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,32 +418,12 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
 | 
			
		||||
                        try {
 | 
			
		||||
                            execute();
 | 
			
		||||
                        } catch (Throwable t) {
 | 
			
		||||
                            // @since 4.3.0 add error handling
 | 
			
		||||
                            final ErrorHandler errorHandler = config.errorHandler;
 | 
			
		||||
                            if (errorHandler == null) {
 | 
			
		||||
                                // as before
 | 
			
		||||
                                Log.e(
 | 
			
		||||
                                        "JLatexMathPlugin",
 | 
			
		||||
                                        "Error displaying latex: `" + drawable.getDestination() + "`",
 | 
			
		||||
                                        t);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                // just call `getDestination` without casts and checks
 | 
			
		||||
                                final Drawable errorDrawable = errorHandler.handleError(
 | 
			
		||||
                                        drawable.getDestination(),
 | 
			
		||||
                                        t
 | 
			
		||||
                                );
 | 
			
		||||
                                if (errorDrawable != null) {
 | 
			
		||||
                                    DrawableUtils.applyIntrinsicBoundsIfEmpty(errorDrawable);
 | 
			
		||||
                                    setResult(drawable, errorDrawable);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            handleRenderingError(drawable, t);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    private void execute() {
 | 
			
		||||
 | 
			
		||||
                        final JLatexMathDrawable jLatexMathDrawable;
 | 
			
		||||
 | 
			
		||||
                        final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable;
 | 
			
		||||
 | 
			
		||||
                        if (jLatextAsyncDrawable.isBlock()) {
 | 
			
		||||
@ -418,12 +432,38 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
 | 
			
		||||
                            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) {
 | 
			
		||||
                // as before
 | 
			
		||||
                Log.e(
 | 
			
		||||
                        "JLatexMathPlugin",
 | 
			
		||||
                        "Error displaying latex: `" + drawable.getDestination() + "`",
 | 
			
		||||
                        t);
 | 
			
		||||
            } else {
 | 
			
		||||
                // just call `getDestination` without casts and checks
 | 
			
		||||
                final Drawable errorDrawable = errorHandler.handleError(
 | 
			
		||||
                        drawable.getDestination(),
 | 
			
		||||
                        t
 | 
			
		||||
                );
 | 
			
		||||
                if (errorDrawable != null) {
 | 
			
		||||
                    DrawableUtils.applyIntrinsicBoundsIfEmpty(errorDrawable);
 | 
			
		||||
                    setResult(drawable, errorDrawable);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void cancel(@NonNull AsyncDrawable drawable) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user