Dave Jarvis' Repositories

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

Integrate SVG rendering engine

AuthorDaveJarvis <email>
Date2020-06-16 23:07:48 GMT-0700
Commite39ee82d4e1409714d56620ff2503abe632022f5
Parentb7d56fc
Delta299 lines added, 17 lines removed, 282-line increase
src/main/java/com/scrivenvar/util/ProtocolResolver.java
+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;
+ }
+}
src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
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();
}
src/main/java/com/scrivenvar/preview/SVGRasterizer.java
+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();
+ }
+}
src/main/java/com/scrivenvar/preview/SVGReplacedElementFactory.java
+/*
+ * {{{ 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;
+ }
+}