| | 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 ) ); |
| | } |
| | |