Dave Jarvis' Repositories

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

Rasterization code clean up

AuthorDaveJarvis <email>
Date2021-09-28 20:51:18 GMT-0700
Commitcb46ba5209e4af88ee45041a78fd20bd81fab5d1
Parentb7a7695
Delta348 lines added, 328 lines removed, 20-line increase
src/main/java/com/keenwrite/preview/SvgRasterizer.java
import static org.apache.batik.bridge.UnitProcessor.svgHorizontalLengthToUserSpace;
import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH;
-import static org.apache.batik.transcoder.image.ImageTranscoder.KEY_PIXEL_UNIT_TO_MILLIMETER;
-import static org.apache.batik.util.SVGConstants.SVG_WIDTH_ATTRIBUTE;
-import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName;
-
-/**
- * Responsible for converting SVG images into rasterized PNG images.
- */
-public final class SvgRasterizer {
- /**
- * <a href="https://issues.apache.org/jira/browse/BATIK-1112">Bug fix</a>
- */
- public static final class InkscapeCssParser extends Parser {
- public void parseStyleDeclaration( final String source )
- throws CSSException, IOException {
- super.parseStyleDeclaration(
- source.replaceAll( "-inkscape-font-specification:[^;\"]*;", "" )
- );
- }
- }
-
- static {
- XMLResourceDescriptor.setCSSParserClassName(
- InkscapeCssParser.class.getName()
- );
- }
-
- private static final UserAgent USER_AGENT = new UserAgentAdapter();
- private static final BridgeContext BRIDGE_CONTEXT = new BridgeContext(
- USER_AGENT, new DocumentLoader( USER_AGENT )
- );
-
- private static final SAXSVGDocumentFactory FACTORY_DOM =
- new SAXSVGDocumentFactory( getXMLParserClassName() );
-
- private static final NumberFormat INT_FORMAT = getIntegerInstance();
-
- public static final BufferedImage BROKEN_IMAGE_PLACEHOLDER;
-
- /**
- * A FontAwesome camera icon, cleft asunder.
- */
- public static final String BROKEN_IMAGE_SVG =
- "<svg height='19pt' viewBox='0 0 25 19' width='25pt' xmlns='http://www" +
- ".w3.org/2000/svg'><g fill='#454545'><path d='m8.042969 11.085938c" +
- ".332031 1.445312 1.660156 2.503906 3.214843 2.558593zm0 0'/><path " +
- "d='m6.792969 9.621094-.300781.226562.242187.195313c.015625-.144531" +
- ".03125-.28125.058594-.421875zm0 0'/><path d='m10.597656.949219-2" +
- ".511718.207031c-.777344.066406-1.429688.582031-1.636719 1.292969l-" +
- ".367188 1.253906-3.414062.28125c-1.027344.085937-1.792969.949219-1" +
- ".699219 1.925781l.976562 10.621094c.089844.976562.996094 1.699219 " +
- "2.023438 1.613281l11.710938-.972656-3.117188-2.484375c-.246094" +
- ".0625-.5.109375-.765625.132812-2.566406.210938-4.835937-1.597656-5" +
- ".0625-4.039062-.023437-.25-.019531-.496094 0-.738281l-.242187-" +
- ".195313.300781-.226562c.359375-1.929688 2.039062-3.472656 4" +
- ".191406-3.652344.207031-.015625.414063-.015625.617187-.007812l" +
- ".933594-.707032zm0 0'/><path d='m10.234375 11.070312 2.964844 2" +
- ".820313c.144531.015625.285156.027344.433593.027344 1.890626 0 3" +
- ".429688-1.460938 3.429688-3.257813 0-1.792968-1.539062-3.257812-3" +
- ".429688-3.257812-1.890624 0-3.429687 1.464844-3.429687 3.257812 0 " +
- ".140625.011719.277344.03125.410156zm0 0'/><path d='m14.488281" +
- ".808594 1.117188 4.554687-1.042969.546875c2.25.476563 3.84375 2" +
- ".472656 3.636719 4.714844-.199219 2.191406-2.050781 3.871094-4" +
- ".285157 4.039062l2.609376 2.957032 4.4375.371094c1.03125.085937 1" +
- ".9375-.640626 2.027343-1.617188l.976563-10.617188c.089844-.980468-" +
- ".667969-1.839843-1.699219-1.925781l-3.414063-.285156-.371093-1" +
- ".253906c-.207031-.710938-.859375-1.226563-1.636719-1.289063zm0 " +
- "0'/></g></svg>";
-
- static {
- // The width and height cannot be embedded in the SVG above because the
- // path element values are relative to the viewBox dimensions.
- final int w = 75;
- final int h = 75;
- BufferedImage image;
-
- try {
- image = rasterizeString( BROKEN_IMAGE_SVG, w );
- } catch( final Exception ex ) {
- image = new BufferedImage( w, h, TYPE_INT_RGB );
- final var graphics = (Graphics2D) image.getGraphics();
- graphics.setRenderingHints( RENDERING_HINTS );
-
- // Fall back to a (\) symbol.
- graphics.setColor( new Color( 204, 204, 204 ) );
- graphics.fillRect( 0, 0, w, h );
- graphics.setColor( new Color( 255, 204, 204 ) );
- graphics.setStroke( new BasicStroke( 4 ) );
- graphics.drawOval( w / 4, h / 4, w / 2, h / 2 );
- graphics.drawLine( w / 4 + (int) (w / 4 / Math.PI),
- h / 4 + (int) (w / 4 / Math.PI),
- w / 2 + w / 4 - (int) (w / 4 / Math.PI),
- h / 2 + h / 4 - (int) (w / 4 / Math.PI) );
- }
-
- BROKEN_IMAGE_PLACEHOLDER = image;
- }
-
- /**
- * Responsible for creating a new {@link ImageRenderer} implementation that
- * can render a DOM as an SVG image.
- */
- 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 getImage() {
- 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;
- }
- }
-
- /**
- * Rasterizes the given SVG input stream into an image at 96 DPI.
- *
- * @param svg The SVG data to rasterize, must be closed by caller.
- * @return The given input stream converted to a rasterized image.
- */
- public static BufferedImage rasterize( final InputStream svg )
- throws TranscoderException {
- return rasterize( svg, 96 );
- }
-
- /**
- * Rasterizes the given SVG input stream into an image.
- *
- * @param svg The SVG data to rasterize, must be closed by caller.
- * @param dpi Resolution to use when rasterizing (default is 96 DPI).
- * @return The given input stream converted to a rasterized image at the
- * given resolution.
- */
- public static BufferedImage rasterize(
- final InputStream svg, final float dpi ) throws TranscoderException {
- final var transcoder = new BufferedImageTranscoder();
- transcoder.addTranscodingHint(
- KEY_PIXEL_UNIT_TO_MILLIMETER, 1f / dpi * 25.4f );
- transcoder.transcode( new TranscoderInput( svg ), null );
- return transcoder.getImage();
- }
-
- /**
- * Rasterizes the given document into an image.
- *
- * @param svg The SVG {@link Document} to rasterize.
- * @param width The rasterized image's width (in pixels).
- * @return The rasterized image.
- */
- public static BufferedImage rasterize( final Document svg, final int width )
- throws TranscoderException {
- final var transcoder = new BufferedImageTranscoder();
- transcoder.addTranscodingHint(
- KEY_WIDTH, fit( svg.getDocumentElement(), width )
- );
- transcoder.transcode( new TranscoderInput( svg ), null );
- return transcoder.getImage();
- }
-
- /**
- * Returns either the given element's SVG document width, or the display
- * width, whichever is smaller.
- *
- * @param root The SVG document's root node.
- * @param width The display width (e.g., rendering canvas width).
- * @return The lower value of the document's width or the display width.
- */
- private static float fit( final Element root, final int width ) {
- final String w = root.getAttribute( SVG_WIDTH_ATTRIBUTE );
-
- return w == null || w.isBlank() ? width : fit( root, w, width );
- }
-
- /**
- * Returns the width in user space units (pixels?).
- *
- * @param root The element containing the width attribute.
- * @param w The element's width attribute value.
- * @param width The rendering canvas width.
- * @return Either the rendering canvas width or SVG document width,
- * whichever is smaller.
- */
- private static float fit(
- final Element root, final String w, final int width ) {
- final float usWidth = svgHorizontalLengthToUserSpace(
- w, SVG_WIDTH_ATTRIBUTE, createContext( BRIDGE_CONTEXT, root )
- );
-
- // If the image is too small, scale it to 1/4 the canvas width.
- return Math.min( usWidth < 5 ? width / 4.0f : usWidth, (float) width );
- }
-
- /**
- * Rasterizes the given vector graphic file using the width dimension
- * specified by the document's width attribute.
- *
- * @param document The {@link Document} containing a vector graphic.
- * @return A rasterized image as an instance of {@link BufferedImage}, or
- * {@link #BROKEN_IMAGE_PLACEHOLDER} if the graphic could not be rasterized.
- */
- public static BufferedImage rasterize( final Document document )
- throws ParseException, TranscoderException {
- final var root = document.getDocumentElement();
- final var width = root.getAttribute( "width" );
- return rasterize( document, INT_FORMAT.parse( width ).intValue() );
- }
-
- /**
- * Rasterizes the vector graphic file at the given URI. If any exception
- * happens, a broken image icon is returned instead.
- *
- * @param path The {@link Path} to a vector graphic file.
- * @param width Scale the image to the given width (px); aspect ratio is
- * maintained.
- * @return A rasterized image as an instance of {@link BufferedImage}.
- */
- public static BufferedImage rasterize( final Path path, final int width ) {
- return rasterize( path.toUri(), width );
- }
-
- /**
- * Rasterizes the vector graphic file at the given URI. If any exception
- * happens, a broken image icon is returned instead.
- *
- * @param uri The URI to a vector graphic file, which must include the
- * protocol scheme (such as file:// or https://).
- * @param width Scale the image to the given width (px); aspect ratio is
- * maintained.
- * @return A rasterized image as an instance of {@link BufferedImage}.
- */
- public static BufferedImage rasterize( final String uri, final int width ) {
- return rasterize( new File( uri ).toURI(), width );
- }
-
- /**
- * Converts an SVG drawing into a rasterized image that can be drawn on
- * a graphics context.
- *
- * @param uri The path to the image (can be web address).
- * @param width Scale the image to the given width (px); aspect ratio is
- * maintained.
- * @return The vector graphic transcoded into a raster image format.
- */
- public static BufferedImage rasterize( final URI uri, final int width ) {
- try {
- return rasterize( FACTORY_DOM.createDocument( uri.toString() ), width );
- } catch( final Exception ex ) {
- clue( ex );
- }
-
- return BROKEN_IMAGE_PLACEHOLDER;
- }
-
- /**
- * Converts an SVG string into a rasterized image that can be drawn on
- * a graphics context. The dimensions are determined from the document.
- *
- * @param xml The SVG xml document.
- * @return The vector graphic transcoded into a raster image format.
- */
- public static BufferedImage rasterizeString( final String xml )
- throws ParseException, TranscoderException {
- final var document = toDocument( xml );
- final var root = document.getDocumentElement();
- final var width = root.getAttribute( "width" );
- return rasterizeString( xml, INT_FORMAT.parse( width ).intValue() );
- }
-
- /**
- * Converts an SVG string into a rasterized image that can be drawn on
- * a graphics context.
- *
- * @param svg The SVG xml document.
- * @param w Scale the image width to this size (aspect ratio is
- * maintained).
- * @return The vector graphic transcoded into a raster image format.
- */
- public static BufferedImage rasterizeString( final String svg, final int w )
- throws TranscoderException {
- return rasterize( toDocument( svg ), w );
- }
-
- /**
- * Given a document object model (DOM) {@link Element}, this will convert that
- * element to a string.
- *
- * @param root The DOM node to convert to a string.
- * @return The DOM node as an escaped, plain text string.
- */
- public static String toSvg( final Element root ) {
- try {
- return transform( root ).replaceAll( "xmlns=\"\" ", "" );
- } catch( final Exception ex ) {
- clue( ex );
- }
-
- return BROKEN_IMAGE_SVG;
- }
-
- /**
- * Converts an SVG XML string into a new {@link Document} instance.
- *
- * @param xml The XML containing SVG elements.
- * @return The SVG contents parsed into a {@link Document} object model.
- */
- private static Document toDocument( final String xml ) {
- try( final var reader = new StringReader( xml ) ) {
- return FACTORY_DOM.createSVGDocument(
- "http://www.w3.org/2000/svg", reader );
- } catch( final Exception ex ) {
- throw new IllegalArgumentException( ex );
- }
+import static org.apache.batik.transcoder.TranscodingHints.Key;
+import static org.apache.batik.transcoder.image.ImageTranscoder.KEY_PIXEL_UNIT_TO_MILLIMETER;
+import static org.apache.batik.util.SVGConstants.SVG_WIDTH_ATTRIBUTE;
+import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName;
+
+/**
+ * Responsible for converting SVG images into rasterized PNG images.
+ */
+public final class SvgRasterizer {
+ /**
+ * <a href="https://issues.apache.org/jira/browse/BATIK-1112">Bug fix</a>
+ */
+ public static final class InkscapeCssParser extends Parser {
+ public void parseStyleDeclaration( final String source )
+ throws CSSException, IOException {
+ super.parseStyleDeclaration(
+ source.replaceAll( "-inkscape-font-specification:[^;\"]*;", "" )
+ );
+ }
+ }
+
+ static {
+ XMLResourceDescriptor.setCSSParserClassName(
+ InkscapeCssParser.class.getName()
+ );
+ }
+
+ private static final UserAgent USER_AGENT = new UserAgentAdapter();
+ private static final BridgeContext BRIDGE_CONTEXT = new BridgeContext(
+ USER_AGENT, new DocumentLoader( USER_AGENT )
+ );
+
+ private static final SAXSVGDocumentFactory FACTORY_DOM =
+ new SAXSVGDocumentFactory( getXMLParserClassName() );
+
+ private static final NumberFormat INT_FORMAT = getIntegerInstance();
+
+ public static final BufferedImage BROKEN_IMAGE_PLACEHOLDER;
+
+ /**
+ * A FontAwesome camera icon, cleft asunder.
+ */
+ public static final String BROKEN_IMAGE_SVG =
+ "<svg height='19pt' viewBox='0 0 25 19' width='25pt' xmlns='http://www" +
+ ".w3.org/2000/svg'><g fill='#454545'><path d='m8.042969 11.085938c" +
+ ".332031 1.445312 1.660156 2.503906 3.214843 2.558593zm0 0'/><path " +
+ "d='m6.792969 9.621094-.300781.226562.242187.195313c.015625-.144531" +
+ ".03125-.28125.058594-.421875zm0 0'/><path d='m10.597656.949219-2" +
+ ".511718.207031c-.777344.066406-1.429688.582031-1.636719 1.292969l-" +
+ ".367188 1.253906-3.414062.28125c-1.027344.085937-1.792969.949219-1" +
+ ".699219 1.925781l.976562 10.621094c.089844.976562.996094 1.699219 " +
+ "2.023438 1.613281l11.710938-.972656-3.117188-2.484375c-.246094" +
+ ".0625-.5.109375-.765625.132812-2.566406.210938-4.835937-1.597656-5" +
+ ".0625-4.039062-.023437-.25-.019531-.496094 0-.738281l-.242187-" +
+ ".195313.300781-.226562c.359375-1.929688 2.039062-3.472656 4" +
+ ".191406-3.652344.207031-.015625.414063-.015625.617187-.007812l" +
+ ".933594-.707032zm0 0'/><path d='m10.234375 11.070312 2.964844 2" +
+ ".820313c.144531.015625.285156.027344.433593.027344 1.890626 0 3" +
+ ".429688-1.460938 3.429688-3.257813 0-1.792968-1.539062-3.257812-3" +
+ ".429688-3.257812-1.890624 0-3.429687 1.464844-3.429687 3.257812 0 " +
+ ".140625.011719.277344.03125.410156zm0 0'/><path d='m14.488281" +
+ ".808594 1.117188 4.554687-1.042969.546875c2.25.476563 3.84375 2" +
+ ".472656 3.636719 4.714844-.199219 2.191406-2.050781 3.871094-4" +
+ ".285157 4.039062l2.609376 2.957032 4.4375.371094c1.03125.085937 1" +
+ ".9375-.640626 2.027343-1.617188l.976563-10.617188c.089844-.980468-" +
+ ".667969-1.839843-1.699219-1.925781l-3.414063-.285156-.371093-1" +
+ ".253906c-.207031-.710938-.859375-1.226563-1.636719-1.289063zm0 " +
+ "0'/></g></svg>";
+
+ static {
+ // The width and height cannot be embedded in the SVG above because the
+ // path element values are relative to the viewBox dimensions.
+ final int w = 75;
+ final int h = 75;
+ BufferedImage image;
+
+ try {
+ image = rasterizeString( BROKEN_IMAGE_SVG, w );
+ } catch( final Exception ex ) {
+ image = new BufferedImage( w, h, TYPE_INT_RGB );
+ final var graphics = (Graphics2D) image.getGraphics();
+ graphics.setRenderingHints( RENDERING_HINTS );
+
+ // Fall back to a (\) symbol.
+ graphics.setColor( new Color( 204, 204, 204 ) );
+ graphics.fillRect( 0, 0, w, h );
+ graphics.setColor( new Color( 255, 204, 204 ) );
+ graphics.setStroke( new BasicStroke( 4 ) );
+ graphics.drawOval( w / 4, h / 4, w / 2, h / 2 );
+ graphics.drawLine( w / 4 + (int) (w / 4 / Math.PI),
+ h / 4 + (int) (w / 4 / Math.PI),
+ w / 2 + w / 4 - (int) (w / 4 / Math.PI),
+ h / 2 + h / 4 - (int) (w / 4 / Math.PI) );
+ }
+
+ BROKEN_IMAGE_PLACEHOLDER = image;
+ }
+
+ /**
+ * Responsible for creating a new {@link ImageRenderer} implementation that
+ * can render a DOM as an SVG image.
+ */
+ 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 getImage() {
+ 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;
+ }
+ }
+
+ /**
+ * Rasterizes the given SVG input stream into an image at 96 DPI.
+ *
+ * @param svg The SVG data to rasterize, must be closed by caller.
+ * @return The given input stream converted to a rasterized image.
+ */
+ public static BufferedImage rasterize( final InputStream svg )
+ throws TranscoderException {
+ return rasterize( svg, 96 );
+ }
+
+ /**
+ * Rasterizes the given SVG input stream into an image.
+ *
+ * @param svg The SVG data to rasterize, must be closed by caller.
+ * @param dpi Resolution to use when rasterizing (default is 96 DPI).
+ * @return The given input stream converted to a rasterized image at the
+ * given resolution.
+ */
+ public static BufferedImage rasterize(
+ final InputStream svg, final float dpi ) throws TranscoderException {
+ return rasterize(
+ new TranscoderInput( svg ),
+ KEY_PIXEL_UNIT_TO_MILLIMETER,
+ 1f / dpi * 25.4f
+ );
+ }
+
+ /**
+ * Rasterizes the given document into an image.
+ *
+ * @param svg The SVG {@link Document} to rasterize.
+ * @param width The rasterized image's width (in pixels).
+ * @return The rasterized image.
+ */
+ public static BufferedImage rasterize(
+ final Document svg, final int width ) throws TranscoderException {
+ return rasterize(
+ new TranscoderInput( svg ),
+ KEY_WIDTH,
+ fit( svg.getDocumentElement(), width )
+ );
+ }
+
+ /**
+ * Rasterizes the given vector graphic file using the width dimension
+ * specified by the document's width attribute.
+ *
+ * @param document The {@link Document} containing a vector graphic.
+ * @return A rasterized image as an instance of {@link BufferedImage}, or
+ * {@link #BROKEN_IMAGE_PLACEHOLDER} if the graphic could not be rasterized.
+ */
+ public static BufferedImage rasterize( final Document document )
+ throws ParseException, TranscoderException {
+ final var root = document.getDocumentElement();
+ final var width = root.getAttribute( SVG_WIDTH_ATTRIBUTE );
+ return rasterize( document, INT_FORMAT.parse( width ).intValue() );
+ }
+
+ /**
+ * Rasterizes the vector graphic file at the given URI. If any exception
+ * happens, a broken image icon is returned instead.
+ *
+ * @param path The {@link Path} to a vector graphic file.
+ * @param width Scale the image to the given width (px); aspect ratio is
+ * maintained.
+ * @return A rasterized image as an instance of {@link BufferedImage}.
+ */
+ public static BufferedImage rasterize( final Path path, final int width ) {
+ return rasterize( path.toUri(), width );
+ }
+
+ /**
+ * Rasterizes the vector graphic file at the given URI. If any exception
+ * happens, a broken image icon is returned instead.
+ *
+ * @param uri The URI to a vector graphic file, which must include the
+ * protocol scheme (such as file:// or https://).
+ * @param width Scale the image to the given width (px); aspect ratio is
+ * maintained.
+ * @return A rasterized image as an instance of {@link BufferedImage}.
+ */
+ public static BufferedImage rasterize( final String uri, final int width ) {
+ return rasterize( new File( uri ).toURI(), width );
+ }
+
+ /**
+ * Converts an SVG drawing into a rasterized image that can be drawn on
+ * a graphics context.
+ *
+ * @param uri The path to the image (can be web address).
+ * @param width Scale the image to the given width (px); aspect ratio is
+ * maintained.
+ * @return The vector graphic transcoded into a raster image format.
+ */
+ public static BufferedImage rasterize( final URI uri, final int width ) {
+ try {
+ return rasterize( FACTORY_DOM.createDocument( uri.toString() ), width );
+ } catch( final Exception ex ) {
+ clue( ex );
+ }
+
+ return BROKEN_IMAGE_PLACEHOLDER;
+ }
+
+ /**
+ * Converts an SVG string into a rasterized image that can be drawn on
+ * a graphics context. The dimensions are determined from the document.
+ *
+ * @param xml The SVG xml document.
+ * @return The vector graphic transcoded into a raster image format.
+ */
+ public static BufferedImage rasterizeString( final String xml )
+ throws ParseException, TranscoderException {
+ final var document = toDocument( xml );
+ final var root = document.getDocumentElement();
+ final var width = root.getAttribute( SVG_WIDTH_ATTRIBUTE );
+ return rasterizeString( xml, INT_FORMAT.parse( width ).intValue() );
+ }
+
+ /**
+ * Converts an SVG string into a rasterized image that can be drawn on
+ * a graphics context.
+ *
+ * @param svg The SVG xml document.
+ * @param w Scale the image width to this size (aspect ratio is
+ * maintained).
+ * @return The vector graphic transcoded into a raster image format.
+ */
+ public static BufferedImage rasterizeString( final String svg, final int w )
+ throws TranscoderException {
+ return rasterize( toDocument( svg ), w );
+ }
+
+ /**
+ * Given a document object model (DOM) {@link Element}, this will convert that
+ * element to a string.
+ *
+ * @param root The DOM node to convert to a string.
+ * @return The DOM node as an escaped, plain text string.
+ */
+ public static String toSvg( final Element root ) {
+ try {
+ return transform( root ).replaceAll( "xmlns=\"\" ", "" );
+ } catch( final Exception ex ) {
+ clue( ex );
+ }
+
+ return BROKEN_IMAGE_SVG;
+ }
+
+ /**
+ * Converts an SVG XML string into a new {@link Document} instance.
+ *
+ * @param xml The XML containing SVG elements.
+ * @return The SVG contents parsed into a {@link Document} object model.
+ */
+ private static Document toDocument( final String xml ) {
+ try( final var reader = new StringReader( xml ) ) {
+ return FACTORY_DOM.createSVGDocument(
+ "http://www.w3.org/2000/svg", reader );
+ } catch( final Exception ex ) {
+ throw new IllegalArgumentException( ex );
+ }
+ }
+
+ /**
+ * Creates a rasterized image of the given source document.
+ *
+ * @param input The source document to transcode.
+ * @param key Transcoding hint key.
+ * @param width Transcoding hint value.
+ * @return A new {@link BufferedImageTranscoder} instance with the given
+ * transcoding hint applied.
+ */
+ private static BufferedImage rasterize(
+ final TranscoderInput input, final Key key, final float width )
+ throws TranscoderException {
+ final var transcoder = new BufferedImageTranscoder();
+
+ transcoder.addTranscodingHint( key, width );
+ transcoder.transcode( input, null );
+
+ return transcoder.getImage();
+ }
+
+ /**
+ * Returns either the given element's SVG document width, or the display
+ * width, whichever is smaller.
+ *
+ * @param root The SVG document's root node.
+ * @param width The display width (e.g., rendering canvas width).
+ * @return The lower value of the document's width or the display width.
+ */
+ private static float fit( final Element root, final int width ) {
+ final var w = root.getAttribute( SVG_WIDTH_ATTRIBUTE );
+
+ return w == null || w.isBlank() ? width : fit( root, w, width );
+ }
+
+ /**
+ * Returns the width in user space units (pixels?).
+ *
+ * @param root The element containing the width attribute.
+ * @param w The element's width attribute value.
+ * @param width The rendering canvas width.
+ * @return Either the rendering canvas width or SVG document width,
+ * whichever is smaller.
+ */
+ private static float fit(
+ final Element root, final String w, final int width ) {
+ final var usWidth = svgHorizontalLengthToUserSpace(
+ w, SVG_WIDTH_ATTRIBUTE, createContext( BRIDGE_CONTEXT, root )
+ );
+
+ // If the image is too small, scale it to 1/4 the canvas width.
+ return Math.min( usWidth < 5 ? width / 4.0f : usWidth, (float) width );
}
}