Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git

Move image caching into factory chain

AuthorDaveJarvis <email>
Date2020-12-29 15:44:09 GMT-0800
Commita4b139829033a9ccedf3e3074840194879c77625
Parent343c019
Delta77 lines added, 103 lines removed, 26-line decrease
src/main/java/com/keenwrite/preview/ChainedReplacedElementFactory.java
import com.keenwrite.ui.adapters.ReplacedElementAdapter;
+import com.keenwrite.util.BoundedCache;
import org.w3c.dom.Element;
import org.xhtmlrenderer.extend.ReplacedElement;
import org.xhtmlrenderer.extend.ReplacedElementFactory;
import org.xhtmlrenderer.extend.UserAgentCallback;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.render.BlockBox;
import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Set;
+
+import static com.keenwrite.preview.SvgReplacedElementFactory.HTML_IMAGE;
+import static com.keenwrite.preview.SvgReplacedElementFactory.HTML_IMAGE_SRC;
+import static com.keenwrite.processors.markdown.tex.TexNode.HTML_TEX;
/**
*/
private final Set<ReplacedElementFactory> mFactories = new LinkedHashSet<>();
+
+ /**
+ * A bounded cache that removes the oldest image if the maximum number of
+ * cached images has been reached. This constrains the number of images
+ * loaded into memory.
+ */
+ private final Map<String, ReplacedElement> mCache = new BoundedCache<>( 150 );
@Override
public ReplacedElement createReplacedElement(
final LayoutContext c,
final BlockBox box,
final UserAgentCallback uac,
final int width,
final int height ) {
- for( final var factory : mFactories ) {
- var replacement =
- factory.createReplacedElement( c, box, uac, width, height );
+ for( final var f : mFactories ) {
+ final var e = box.getElement();
- if( replacement != null ) {
- return replacement;
+ // Exit early for super-speed.
+ if( e == null ) {
+ break;
+ }
+
+ // If the source image is cached, don't bother fetching. This optimization
+ // avoids making multiple HTTP requests for the same URI.
+ final var node = e.getNodeName();
+ final var source = switch( node ) {
+ case HTML_IMAGE -> e.getAttribute( HTML_IMAGE_SRC );
+ case HTML_TEX -> e.getTextContent();
+ default -> "";
+ };
+
+ // HTML <img> or <tex> elements without source data shall not pass.
+ if( source.isBlank() ) {
+ break;
+ }
+
+ final var replaced = mCache.computeIfAbsent(
+ source, k -> f.createReplacedElement( c, box, uac, width, height )
+ );
+
+ if( replaced != null ) {
+ return replaced;
}
}
src/main/java/com/keenwrite/preview/SvgReplacedElementFactory.java
import com.keenwrite.io.HttpMediaType;
import com.keenwrite.io.MediaType;
-import com.keenwrite.util.BoundedCache;
+import com.keenwrite.ui.adapters.ReplacedElementAdapter;
import org.w3c.dom.Element;
import org.xhtmlrenderer.extend.ReplacedElement;
-import org.xhtmlrenderer.extend.ReplacedElementFactory;
import org.xhtmlrenderer.extend.UserAgentCallback;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.render.BlockBox;
-import org.xhtmlrenderer.simple.extend.FormSubmissionListener;
import org.xhtmlrenderer.swing.ImageReplacedElement;
import java.awt.image.BufferedImage;
import java.net.URI;
import java.nio.file.Paths;
-import java.util.Map;
-import java.util.Optional;
import static com.keenwrite.StatusBarNotifier.clue;
* a document to transform them into rasterized versions.
*/
-public class SvgReplacedElementFactory implements ReplacedElementFactory {
+public class SvgReplacedElementFactory extends ReplacedElementAdapter {
/**
}
- private static final String HTML_IMAGE = "img";
- private static final String HTML_IMAGE_SRC = "src";
+ public static final String HTML_IMAGE = "img";
+ public static final String HTML_IMAGE_SRC = "src";
private static final ImageReplacedElement BROKEN_IMAGE =
createImageReplacedElement( BROKEN_IMAGE_PLACEHOLDER );
-
- /**
- * A bounded cache that removes the oldest image if the maximum number of
- * cached images has been reached. This constrains the number of images
- * loaded into memory.
- */
- private final Map<String, ImageReplacedElement> mImageCache =
- new BoundedCache<>( 150 );
@Override
public ReplacedElement createReplacedElement(
final LayoutContext c,
final BlockBox box,
final UserAgentCallback uac,
final int cssWidth,
final int cssHeight ) {
final var e = box.getElement();
-
- // Exit early for the speeds.
- if( e == null ) {
- return null;
- }
-
- // If the source image is cached, don't bother fetching. This optimization
- // avoids making multiple HTTP requests for the same URI.
- final var node = e.getNodeName();
- final var source = switch( node ) {
- case HTML_IMAGE -> e.getAttribute( HTML_IMAGE_SRC );
- case HTML_TEX -> e.getTextContent();
- default -> "";
- };
-
- // Non-image HTML elements shall not pass.
- if( source.isBlank() ) {
- return null;
- }
- final var image = new ImageReplacedElement[ 1 ];
- getCachedImage( source ).ifPresentOrElse(
- ( i ) -> image[ 0 ] = i,
- () -> {
- try {
- BufferedImage raster = null;
+ ImageReplacedElement image = null;
- switch( node ) {
- case HTML_IMAGE -> {
- URI uri = null;
+ try {
+ BufferedImage raster = null;
- if( getProtocol( source ).isHttp() ) {
- var mediaType = MediaType.valueFrom( source );
+ switch( e.getNodeName() ) {
+ case HTML_IMAGE -> {
+ final var source = e.getAttribute( HTML_IMAGE_SRC );
+ URI uri = null;
- if( isSvg( mediaType ) || mediaType == UNDEFINED ) {
- // Attempt to rasterize SVG depending on URL resource content.
- uri = new URI( source );
+ if( getProtocol( source ).isHttp() ) {
+ var mediaType = MediaType.valueFrom( source );
- // Attempt rasterization for SVG or plain text formats.
- if( !isSvg( HttpMediaType.valueFrom( uri ) ) ) {
- uri = null;
- }
- }
- }
- else if( isSvg( MediaType.valueFrom( source ) ) ) {
- // Attempt to rasterize based on file name.
- final var base = new URI( getBaseUri( e ) ).getPath();
- uri = Paths.get( base, source ).toUri();
- }
+ if( isSvg( mediaType ) || mediaType == UNDEFINED ) {
+ uri = new URI( source );
- if( uri != null ) {
- raster = rasterize( uri, box.getContentWidth() );
+ // Attempt to rasterize SVG depending on URL resource content.
+ if( !isSvg( HttpMediaType.valueFrom( uri ) ) ) {
+ uri = null;
}
}
- case HTML_TEX ->
- // Convert the TeX element to a raster graphic.
- raster = rasterize( getInstance().render( source ) );
+ }
+ else if( isSvg( MediaType.valueFrom( source ) ) ) {
+ // Attempt to rasterize based on file name.
+ final var base = new URI( getBaseUri( e ) ).getPath();
+ uri = Paths.get( base, source ).toUri();
}
- if( raster != null ) {
- image[ 0 ] = putCachedImage( source, raster );
+ if( uri != null ) {
+ raster = rasterize( uri, box.getContentWidth() );
}
- } catch( final Exception ex ) {
- image[ 0 ] = BROKEN_IMAGE;
- clue( ex );
}
+ case HTML_TEX ->
+ // Convert the TeX element to a raster graphic.
+ raster = rasterize( getInstance().render( e.getTextContent() ) );
}
- );
- return image[ 0 ];
+ if( raster != null ) {
+ image = createImageReplacedElement( raster );
+ }
+ } catch( final Exception ex ) {
+ image = BROKEN_IMAGE;
+ clue( ex );
+ }
+
+ return image;
}
return "";
- }
-
- @Override
- public void reset() {
- }
-
- @Override
- public void remove( final Element e ) {
- }
-
- @Override
- public void setFormSubmissionListener( FormSubmissionListener listener ) {
- }
-
- private ImageReplacedElement putCachedImage(
- final String source, final BufferedImage image ) {
- assert source != null;
- assert image != null;
-
- final var result = createImageReplacedElement( image );
- mImageCache.put( source, result );
- return result;
- }
-
- private Optional<ImageReplacedElement> getCachedImage( final String source ) {
- return Optional.ofNullable( mImageCache.get( source ) );
}