| Author | DaveJarvis <email> |
|---|---|
| Date | 2020-06-16 23:07:48 GMT-0700 |
| Commit | e39ee82d4e1409714d56620ff2503abe632022f5 |
| Parent | b7d56fc |
| Delta | 299 lines added, 17 lines removed, 282-line increase |
| +package com.scrivenvar.util; | ||
| + | ||
| +import java.io.File; | ||
| +import java.net.URI; | ||
| +import java.net.URL; | ||
| + | ||
| +import static com.scrivenvar.Constants.DEFINITION_PROTOCOL_UNKNOWN; | ||
| + | ||
| +/** | ||
| + * Responsible for determining the protocol of a resource. | ||
| + */ | ||
| +public class ProtocolResolver { | ||
| + /** | ||
| + * Returns the protocol for a given URI or filename. | ||
| + * | ||
| + * @param resource Determine the protocol for this URI or filename. | ||
| + * @return The protocol for the given source. | ||
| + */ | ||
| + public static String getProtocol( final String resource ) { | ||
| + String protocol; | ||
| + | ||
| + try { | ||
| + final URI uri = new URI( resource ); | ||
| + | ||
| + if( uri.isAbsolute() ) { | ||
| + protocol = uri.getScheme(); | ||
| + } | ||
| + else { | ||
| + final URL url = new URL( resource ); | ||
| + protocol = url.getProtocol(); | ||
| + } | ||
| + } catch( final Exception e ) { | ||
| + // Could be HTTP, HTTPS? | ||
| + if( resource.startsWith( "//" ) ) { | ||
| + throw new IllegalArgumentException( "Relative context: " + resource ); | ||
| + } | ||
| + else { | ||
| + final File file = new File( resource ); | ||
| + protocol = getProtocol( file ); | ||
| + } | ||
| + } | ||
| + | ||
| + return protocol; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the protocol for a given file. | ||
| + * | ||
| + * @param file Determine the protocol for this file. | ||
| + * @return The protocol for the given file. | ||
| + */ | ||
| + public static String getProtocol( final File file ) { | ||
| + String result; | ||
| + | ||
| + try { | ||
| + result = file.toURI().toURL().getProtocol(); | ||
| + } catch( final Exception e ) { | ||
| + result = DEFINITION_PROTOCOL_UNKNOWN; | ||
| + } | ||
| + | ||
| + return result; | ||
| + } | ||
| +} | ||
| import org.xhtmlrenderer.simple.XHTMLPanel; | ||
| import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler; | ||
| +import org.xhtmlrenderer.swing.SwingReplacedElementFactory; | ||
| import javax.swing.*; | ||
| } | ||
| } | ||
| + | ||
| + private final static String HTML_HEADER = "<!DOCTYPE html>" | ||
| + + "<html>" | ||
| + + "<head>" | ||
| + + "<link rel='stylesheet' href='" + | ||
| + HTMLPreviewPane.class.getResource( STYLESHEET_PREVIEW ) + "'/>" | ||
| + + "</head>" | ||
| + + "<body>"; | ||
| + private final static String HTML_FOOTER = "</body></html>"; | ||
| + | ||
| + private final StringBuilder mHtml = new StringBuilder( 65536 ); | ||
| + private final int mHtmlPrefixLength; | ||
| private final W3CDom mW3cDom = new W3CDom(); | ||
| */ | ||
| public HTMLPreviewPane() { | ||
| - mSwingNode.setContent( mScrollPane ); | ||
| + final ChainedReplacedElementFactory factory = | ||
| + new ChainedReplacedElementFactory(); | ||
| + factory.addFactory( new SVGReplacedElementFactory() ); | ||
| + factory.addFactory( new SwingReplacedElementFactory() ); | ||
| + | ||
| + mRenderer.getSharedContext().setReplacedElementFactory( factory ); | ||
| mRenderer.getSharedContext().getTextRenderer().setSmoothingThreshold( 0 ); | ||
| + mSwingNode.setContent( mScrollPane ); | ||
| + | ||
| + mHtml.append( HTML_HEADER ); | ||
| + mHtmlPrefixLength = mHtml.length(); | ||
| } | ||
| mRenderer.setDocument( w3cDoc, getBaseUrl(), mNamespaceHandler ); | ||
| } | ||
| - | ||
| - private final static String HTML_HEADER = "<!DOCTYPE html>" | ||
| - + "<html>" | ||
| - + "<head>" | ||
| - + "<link rel='stylesheet' href='" + | ||
| - HTMLPreviewPane.class.getResource( STYLESHEET_PREVIEW ) + "'/>" | ||
| - + "</head>" | ||
| - + "<body>"; | ||
| - private final static String HTML_FOOTER = "</body></html>"; | ||
| - | ||
| - private final StringBuilder htmlDoc = new StringBuilder( 65536 ); | ||
| private String decorate( final String html ) { | ||
| - htmlDoc.setLength( 0 ); | ||
| - return htmlDoc.append( HTML_HEADER ) | ||
| - .append( html ) | ||
| - .append( HTML_FOOTER ) | ||
| - .toString(); | ||
| + mHtml.setLength( mHtmlPrefixLength ); | ||
| + return mHtml.append( html ) | ||
| + .append( HTML_FOOTER ) | ||
| + .toString(); | ||
| } | ||
| +package com.scrivenvar.preview; | ||
| + | ||
| +import org.apache.batik.anim.dom.SAXSVGDocumentFactory; | ||
| +import org.apache.batik.gvt.renderer.ImageRenderer; | ||
| +import org.apache.batik.transcoder.TranscoderException; | ||
| +import org.apache.batik.transcoder.TranscoderInput; | ||
| +import org.apache.batik.transcoder.TranscoderOutput; | ||
| +import org.apache.batik.transcoder.image.ImageTranscoder; | ||
| +import org.w3c.dom.svg.SVGDocument; | ||
| + | ||
| +import java.awt.*; | ||
| +import java.awt.image.BufferedImage; | ||
| +import java.io.IOException; | ||
| +import java.net.URL; | ||
| +import java.util.Map; | ||
| + | ||
| +import static java.awt.Color.WHITE; | ||
| +import static java.awt.RenderingHints.*; | ||
| +import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH; | ||
| +import static org.apache.batik.transcoder.image.ImageTranscoder.KEY_BACKGROUND_COLOR; | ||
| +import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName; | ||
| + | ||
| +public class SVGRasterizer { | ||
| + private final static SAXSVGDocumentFactory mFactory = | ||
| + new SAXSVGDocumentFactory( getXMLParserClassName() ); | ||
| + | ||
| + private final static Map<Object, Object> RENDERING_HINTS = Map.of( | ||
| + KEY_ALPHA_INTERPOLATION, | ||
| + VALUE_ALPHA_INTERPOLATION_QUALITY, | ||
| + KEY_INTERPOLATION, | ||
| + VALUE_INTERPOLATION_BICUBIC, | ||
| + KEY_ANTIALIASING, | ||
| + VALUE_ANTIALIAS_ON, | ||
| + KEY_COLOR_RENDERING, | ||
| + VALUE_COLOR_RENDER_QUALITY, | ||
| + KEY_DITHERING, | ||
| + VALUE_DITHER_DISABLE, | ||
| + KEY_RENDERING, | ||
| + VALUE_RENDER_QUALITY, | ||
| + KEY_STROKE_CONTROL, | ||
| + VALUE_STROKE_PURE, | ||
| + KEY_FRACTIONALMETRICS, | ||
| + VALUE_FRACTIONALMETRICS_ON, | ||
| + KEY_TEXT_ANTIALIASING, | ||
| + VALUE_TEXT_ANTIALIAS_OFF | ||
| + ); | ||
| + | ||
| + private static class BufferedImageTranscoder extends ImageTranscoder { | ||
| + private BufferedImage mImage; | ||
| + | ||
| + @Override | ||
| + public BufferedImage createImage( final int w, final int h ) { | ||
| + return new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB ); | ||
| + } | ||
| + | ||
| + @Override | ||
| + public void writeImage( | ||
| + final BufferedImage image, final TranscoderOutput output ) { | ||
| + mImage = image; | ||
| + } | ||
| + | ||
| + public BufferedImage getBufferedImage() { | ||
| + return mImage; | ||
| + } | ||
| + | ||
| + @Override | ||
| + protected ImageRenderer createRenderer() { | ||
| + final ImageRenderer renderer = super.createRenderer(); | ||
| + final RenderingHints hints = renderer.getRenderingHints(); | ||
| + hints.putAll( RENDERING_HINTS ); | ||
| + | ||
| + renderer.setRenderingHints( hints ); | ||
| + | ||
| + return renderer; | ||
| + } | ||
| + } | ||
| + | ||
| + public static BufferedImage rasterize( final URL url, final int width ) | ||
| + throws IOException, TranscoderException { | ||
| + return rasterize( | ||
| + (SVGDocument) mFactory.createDocument( url.toString() ), width ); | ||
| + } | ||
| + | ||
| + public static BufferedImage rasterize( | ||
| + final SVGDocument svg, final int width ) throws TranscoderException { | ||
| + final var transcoder = new BufferedImageTranscoder(); | ||
| + final var input = new TranscoderInput( svg ); | ||
| + | ||
| + transcoder.addTranscodingHint( KEY_BACKGROUND_COLOR, WHITE ); | ||
| + transcoder.addTranscodingHint( KEY_WIDTH, (float) width ); | ||
| + transcoder.transcode( input, null ); | ||
| + | ||
| + return transcoder.getBufferedImage(); | ||
| + } | ||
| +} | ||
| +/* | ||
| + * {{{ header & license | ||
| + * Copyright (c) 2006 Patrick Wright | ||
| + * Copyright (c) 2007 Wisconsin Court System | ||
| + * | ||
| + * This program is free software; you can redistribute it and/or | ||
| + * modify it under the terms of the GNU Lesser General Public License | ||
| + * as published by the Free Software Foundation; either version 2.1 | ||
| + * of the License, or (at your option) any later version. | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU Lesser General Public License for more details. | ||
| + * | ||
| + * You should have received a copy of the GNU Lesser General Public License | ||
| + * along with this program; if not, write to the Free Software | ||
| + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | ||
| + * }}} | ||
| + */ | ||
| +package com.scrivenvar.preview; | ||
| + | ||
| +import com.scrivenvar.Services; | ||
| +import com.scrivenvar.service.events.Notifier; | ||
| +import org.apache.commons.io.FilenameUtils; | ||
| +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.*; | ||
| +import java.io.File; | ||
| +import java.net.MalformedURLException; | ||
| +import java.net.URL; | ||
| + | ||
| +import static com.scrivenvar.util.ProtocolResolver.getProtocol; | ||
| + | ||
| +public class SVGReplacedElementFactory | ||
| + implements ReplacedElementFactory { | ||
| + | ||
| + private final static Notifier sNotifier = Services.load( Notifier.class ); | ||
| + | ||
| + /** | ||
| + * SVG filename extension. | ||
| + */ | ||
| + private static final String SVG_FILE = "svg"; | ||
| + private static final String HTML_IMAGE = "img"; | ||
| + private static final String HTML_IMAGE_SRC = "src"; | ||
| + | ||
| + public ReplacedElement createReplacedElement( | ||
| + LayoutContext c, | ||
| + BlockBox box, | ||
| + UserAgentCallback uac, | ||
| + int cssWidth, | ||
| + int cssHeight ) { | ||
| + | ||
| + final Element e = box.getElement(); | ||
| + | ||
| + if( e == null ) { | ||
| + return null; | ||
| + } | ||
| + | ||
| + final String nodeName = e.getNodeName(); | ||
| + ReplacedElement result = null; | ||
| + | ||
| + if( HTML_IMAGE.equals( nodeName ) ) { | ||
| + final String src = e.getAttribute( HTML_IMAGE_SRC ); | ||
| + final String ext = FilenameUtils.getExtension( src ); | ||
| + | ||
| + if( SVG_FILE.equalsIgnoreCase( ext ) ) { | ||
| + try { | ||
| + final URL url = getUrl( src ); | ||
| + final int width = box.getContentWidth(); | ||
| + final Image image = SVGRasterizer.rasterize( url, width ); | ||
| + | ||
| + final int w = image.getWidth( null ); | ||
| + final int h = image.getHeight( null ); | ||
| + | ||
| + result = new ImageReplacedElement( image, w, h ); | ||
| + } catch( final Exception ex ) { | ||
| + getNotifier().notify( ex ); | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + return result; | ||
| + } | ||
| + | ||
| + @Override | ||
| + public void reset() { | ||
| + } | ||
| + | ||
| + @Override | ||
| + public void remove( Element e ) { | ||
| + } | ||
| + | ||
| + @Override | ||
| + public void setFormSubmissionListener( FormSubmissionListener listener ) { | ||
| + } | ||
| + | ||
| + private URL getUrl( final String src ) throws MalformedURLException { | ||
| + return "file".equals( getProtocol( src ) ) | ||
| + ? new File( src ).toURI().toURL() | ||
| + : new URL( src ); | ||
| + } | ||
| + | ||
| + private Notifier getNotifier() { | ||
| + return sNotifier; | ||
| + } | ||
| +} | ||