Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
.gitattributes
*.otf binary
*.ttf binary
+*.ods binary
build.gradle
}
content {
- includeGroup 'com.github.css4j'
- includeGroup 'io.sf.graphics'
- includeGroup 'io.sf.carte'
- includeGroup 'io.sf.jclf'
+ includeGroupByRegex 'io\\.sf\\..*'
}
}
}
// Assume a cross-platform überjar unless targetOs is set.
-String[] os = ['win', 'mac', 'linux']
+String[] os = [ 'win', 'mac', 'linux' ]
-if (project.hasProperty( 'targetOs' )) {
- if ('windows' == targetOs) {
- os = ['win']
- } else if ('macos' == targetOs) {
- os = ['mac']
+if( project.hasProperty( 'targetOs' ) ) {
+ if( 'windows' == targetOs ) {
+ os = [ 'win' ]
+ } else if( 'macos' == targetOs ) {
+ os = [ 'mac' ]
} else {
- os = [targetOs]
+ os = [ targetOs ]
}
}
javafx {
version = javaVersion
- modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing']
+ modules = [ 'javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing' ]
configuration = 'compileOnly'
}
dependencies {
def v_junit = '5.11.4'
def v_flexmark = '0.64.8'
def v_jackson = '2.17.3'
- def v_echosvg = '1.2.3'
+ def v_echosvg = '2.2'
def v_picocli = '4.7.6'
// R
- implementation 'org.apache.commons:commons-compress:1.27.1'
+ implementation 'org.apache.commons:commons-compress:1.28.0'
implementation 'org.codehaus.plexus:plexus-utils:4.0.2'
implementation 'org.renjin:renjin-script-engine:3.5-beta76'
// KeenQuotes, KeenType, KeenSpell, KeenCount.
- implementation fileTree( include: ['**/*.jar'], dir: 'libs' )
+ implementation fileTree( include: [ '**/*.jar' ], dir: 'libs' )
- def fx = ['controls', 'graphics', 'fxml', 'swing']
+ def fx = [ 'controls', 'graphics', 'fxml', 'swing' ]
fx.each { fxitem ->
contents {
- from { ['LICENSE.md', 'README.md'] }
+ from { [ 'LICENSE.md', 'README.md' ] }
into( 'images' ) {
from { 'images' }
src/main/java/com/keenwrite/ExportFormat.java
case APP_PDF -> APPLICATION_PDF;
case TEXT_XML -> XHTML_TEX;
- default -> throw new IllegalArgumentException( format(
- "Unrecognized format type and subtype: '%s' and '%s'", type, modifier
- ) );
+ default -> NONE;
};
}
src/main/java/com/keenwrite/preview/SvgRasterizer.java
import static io.sf.carte.echosvg.bridge.UnitProcessor.createContext;
import static io.sf.carte.echosvg.bridge.UnitProcessor.svgHorizontalLengthToUserSpace;
-import static io.sf.carte.echosvg.transcoder.SVGAbstractTranscoder.KEY_HEIGHT;
-import static io.sf.carte.echosvg.transcoder.SVGAbstractTranscoder.KEY_WIDTH;
-import static io.sf.carte.echosvg.transcoder.TranscodingHints.Key;
-import static io.sf.carte.echosvg.transcoder.image.ImageTranscoder.KEY_PIXEL_UNIT_TO_MILLIMETER;
-import static io.sf.carte.echosvg.util.SVGConstants.SVG_HEIGHT_ATTRIBUTE;
-import static io.sf.carte.echosvg.util.SVGConstants.SVG_WIDTH_ATTRIBUTE;
-import static java.awt.image.BufferedImage.TYPE_INT_RGB;
-import static java.text.NumberFormat.getIntegerInstance;
-
-/**
- * Responsible for converting SVG images into rasterized PNG images.
- */
-public final class SvgRasterizer {
-
- /**
- * Prevent rudely barfing stack traces to the console.
- */
- private static final class SvgErrorHandler implements ErrorHandler {
- @Override
- public void error( final TranscoderException ex ) {
- clue( ex );
- }
-
- @Override
- public void fatalError( final TranscoderException ex ) {
- clue( ex );
- }
-
- @Override
- public void warning( final TranscoderException ex ) {
- clue( ex );
- }
- }
-
- 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 ErrorHandler sErrorHandler = new SvgErrorHandler();
-
- private static final SAXSVGDocumentFactory FACTORY_DOM =
- new SAXSVGDocumentFactory();
-
- 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 = rasterizeImage( 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;
-
- /**
- * Prevent barfing a stack trace when the transcoder encounters problems
- * parsing SVG contents.
- */
- @Override
- protected UserAgent createUserAgent() {
- return new SVGAbstractTranscoderUserAgent() {
- @Override
- public void displayError( final Exception ex ) {
- clue( ex );
- }
- };
- }
-
- @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.
- *
- * @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 String svg )
- throws TranscoderException, ParseException {
- return rasterize( toDocument( svg ) );
- }
-
- /**
- * 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 <code>file://</code> or
- * <code>https://</code>).
- * @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 svg The SVG xml document.
- * @param scale The scaling factor to apply when transcoding.
- * @return The vector graphic transcoded into a raster image format.
- */
- @SuppressWarnings( "unused" )
- public static BufferedImage rasterizeImage(
- final String svg, final double scale )
- throws ParseException, TranscoderException {
- final var document = toDocument( svg );
- final var root = document.getDocumentElement();
- final var width = root.getAttribute( SVG_WIDTH_ATTRIBUTE );
- final var height = root.getAttribute( SVG_HEIGHT_ATTRIBUTE );
- final var w = INT_FORMAT.parse( width ).intValue() * scale;
- final var h = INT_FORMAT.parse( height ).intValue() * scale;
-
- return rasterize( svg, w, h );
- }
-
- /**
- * 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 rasterizeImage( 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 hintKey Transcoding hint key.
- * @param hintValue Transcoding hint value.
- * @return A new {@link BufferedImageTranscoder} instance with the given
- * transcoding hint applied.
- */
- private static BufferedImage rasterize(
- final TranscoderInput input, final Key hintKey, final float hintValue )
- throws TranscoderException {
- final var hints = new HashMap<Key, Object>();
- hints.put( hintKey, hintValue );
-
- return rasterize( input, hints );
- }
-
- private static BufferedImage rasterize(
- final String svg, final double w, final double h )
- throws TranscoderException {
- final var hints = new HashMap<Key, Object>();
- hints.put( KEY_WIDTH, (float) w );
- hints.put( KEY_HEIGHT, (float) h );
-
- return rasterize( new TranscoderInput( toDocument( svg ) ), hints );
- }
-
- public static BufferedImage rasterize(
- final TranscoderInput input,
- final Map<TranscodingHints.Key, Object> hints ) throws TranscoderException {
- final var transcoder = new BufferedImageTranscoder();
-
- for( final var hint : hints.entrySet() ) {
- transcoder.addTranscodingHint( hint.getKey(), hint.getValue() );
- }
-
- transcoder.setErrorHandler( sErrorHandler );
- 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.
- */
- @SuppressWarnings( "ConstantValue" )
+import static io.sf.carte.echosvg.transcoder.SVGAbstractTranscoder.*;
+import static io.sf.carte.echosvg.transcoder.TranscodingHints.Key;
+import static io.sf.carte.echosvg.util.SVGConstants.SVG_HEIGHT_ATTRIBUTE;
+import static io.sf.carte.echosvg.util.SVGConstants.SVG_WIDTH_ATTRIBUTE;
+import static java.awt.image.BufferedImage.TYPE_INT_RGB;
+import static java.text.NumberFormat.getIntegerInstance;
+
+/**
+ * Responsible for converting SVG images into rasterized PNG images.
+ */
+public final class SvgRasterizer {
+
+ /**
+ * Prevent rudely barfing stack traces to the console.
+ */
+ private static final class SvgErrorHandler implements ErrorHandler {
+ @Override
+ public void error( final TranscoderException ex ) {
+ clue( ex );
+ }
+
+ @Override
+ public void fatalError( final TranscoderException ex ) {
+ clue( ex );
+ }
+
+ @Override
+ public void warning( final TranscoderException ex ) {
+ clue( ex );
+ }
+ }
+
+ 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 ErrorHandler sErrorHandler = new SvgErrorHandler();
+
+ private static final SAXSVGDocumentFactory FACTORY_DOM =
+ new SAXSVGDocumentFactory();
+
+ 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 = rasterizeImage( 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 );
+
+ final var offset = (int) ((double) w / 4 / Math.PI);
+
+ // 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 + offset,
+ h / 4 + offset,
+ w / 2 + w / 4 - offset,
+ h / 2 + h / 4 - offset
+ );
+ }
+
+ 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;
+
+ /**
+ * Prevent barfing a stack trace when the transcoder encounters problems
+ * parsing SVG contents.
+ */
+ @Override
+ protected UserAgent createUserAgent() {
+ return new SVGAbstractTranscoderUserAgent() {
+ @Override
+ public void displayError( final Exception ex ) {
+ clue( ex );
+ }
+ };
+ }
+
+ @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.
+ *
+ * @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 String svg )
+ throws TranscoderException, ParseException {
+ return rasterize( toDocument( svg ) );
+ }
+
+ /**
+ * 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_RESOLUTION_DPI,
+ 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 <code>file://</code> or
+ * <code>https://</code>).
+ * @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 svg The SVG xml document.
+ * @param scale The scaling factor to apply when transcoding.
+ * @return The vector graphic transcoded into a raster image format.
+ */
+ @SuppressWarnings("unused")
+ public static BufferedImage rasterizeImage(
+ final String svg, final double scale )
+ throws ParseException, TranscoderException {
+ final var document = toDocument( svg );
+ final var root = document.getDocumentElement();
+ final var width = root.getAttribute( SVG_WIDTH_ATTRIBUTE );
+ final var height = root.getAttribute( SVG_HEIGHT_ATTRIBUTE );
+ final var w = INT_FORMAT.parse( width ).intValue() * scale;
+ final var h = INT_FORMAT.parse( height ).intValue() * scale;
+
+ return rasterize( svg, w, h );
+ }
+
+ /**
+ * 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 rasterizeImage( 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 hintKey Transcoding hint key.
+ * @param hintValue Transcoding hint value.
+ * @return A new {@link BufferedImageTranscoder} instance with the given
+ * transcoding hint applied.
+ */
+ private static BufferedImage rasterize(
+ final TranscoderInput input, final Key hintKey, final float hintValue )
+ throws TranscoderException {
+ final var hints = new HashMap<Key, Object>();
+ hints.put( hintKey, hintValue );
+
+ return rasterize( input, hints );
+ }
+
+ private static BufferedImage rasterize(
+ final String svg, final double w, final double h )
+ throws TranscoderException {
+ final var hints = new HashMap<Key, Object>();
+ hints.put( KEY_WIDTH, (float) w );
+ hints.put( KEY_HEIGHT, (float) h );
+
+ return rasterize( new TranscoderInput( toDocument( svg ) ), hints );
+ }
+
+ public static BufferedImage rasterize(
+ final TranscoderInput input,
+ final Map<TranscodingHints.Key, Object> hints ) throws TranscoderException {
+ final var transcoder = new BufferedImageTranscoder();
+
+ for( final var hint : hints.entrySet() ) {
+ transcoder.addTranscodingHint( hint.getKey(), hint.getValue() );
+ }
+
+ transcoder.setErrorHandler( sErrorHandler );
+ 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.
+ */
+ @SuppressWarnings("ConstantValue")
private static float fit( final Element root, final int width ) {
final var w = root.getAttribute( SVG_WIDTH_ATTRIBUTE );
www/.gitattributes
*.zip binary
*.tar.gz binary
+*.tgz binary
+*.bz2 binary
+*.ods binary
www/blog/2025/08/18/feature-matrix/index.md
# Markdown
-The following documents show Markdown's versatility:
+The following Markdown documents show its versatility:
* [Impacts](https://impacts.to/downloads/lowres/impacts.pdf)
* [autónoma](pdf/autónoma.pdf)
* [Software Design](pdf/software-design.pdf)
* [Jekyll and Mr. Hyde](pdf/jekyll-hyde.pdf)
* [Last Will and Testament](pdf/last-will-and-testament.pdf)
-This page delves into a feature matrix that compares common technical
-writing text formats, which will hopefully dispel a few myths.
+Let's explore a feature matrix comparing common technical writing text formats,
+which I hope may dispel some misunderstandings.
-Full disclosure: I'm the main author of KeenWrite. While I've tried to
-make this post as unbiased as possible, there may be errors. Please [/contact.shtml](send me) your corrections or suggestions.
+Full disclosure: I'm the main author of KeenWrite, a Markdown text editor.
+While I've tried to make this post unbiased, there may be errors. Please
+[contact me](/contact.shtml) with your corrections or suggestions.
# Features
File systems are well-suited for grouping files.
-# History
+# Extending CommonMark
-Markdown is a markup language for formatting text. John Gruber created it based on decades of syntax conventions that had evolved in email, mailing lists, Usenet newsgroups, and online forums. His original goal was to make a text format that could be easily converted to HTML while remaining human-readable. Instead of `<em>emphasis</em>`, you write `*emphasis*`. Gruber released a specification alongside a script to convert a Markdown document into valid HTML.
+## Limitations
-The format gained popularity among those who appreciated its simplicity, editor-agnostic format, and version-control friendliness. Adopting Markdown for project README files significantly boosted its widespread use.
+The CommonMark standard was frozen before core features were added that would have made it an easy choice over other plain text formats. Although CommonMark is extensible, such flexibility inevitably leads to fracturing and incompatibilities. Missing features include:
-However, ambiguity in his specification resulted in different implementations interpreting edge cases differently. This fragmentation prompted John MacFarlane to spearhead CommonMark, a Markdown standard aimed to eliminate ambiguities.
+* Consistent captions for figures, tables, equations, algorithms, music, etc.
+* Diagram code blocks
+* Extensible cross-references
+* Citations for bibliographic references
-Meanwhile, various "flavors" of Markdown emerged, including GitHub Flavored Markdown (GFM), which added features like tables and syntax highlighting for code blocks, and other variants that extended the basic syntax for specific use cases.
-Today, Markdown has become ubiquitous across the web, used in everything from documentation sites and blogs to messaging platforms and note-taking applications. Its success lies in striking the right balance between simplicity and functionality, making it accessible to non-technical users while remaining powerful enough for complex documentation needs.
+## Consistent captions
+
+KeenWrite supports a double-colon syntax for captions, where the caption comes
+after the element being captioned. Consider the following text:
+
+``` markdown
+| Header | Header |
+|--------|--------|
+| Value | Value |
+
+:: Table caption
+
+![alt text](logo.svg)
+
+:: Image caption
+
+$$E=mc^2$$
+
+:: Equation caption
+```
+
+Note the blank lines before the caption. Sample output:
+
+![captions](captions.png)
+
+## Diagram code blocks
+
+KeenWrite distinguishes between diagrams to draw and source code blocks to
+list by using a `diagram-` prefix, such as:
+
+ ``` diagram-plantuml
+ @startuml
+ Alice -> Bob: Hello
+ @enduml
+ ```
+
+This has a few "benefits." First, it allows rendering diagram types using
+a service without having to codify all possible services. Second, when a new
+type of diagram is added, it's available immediately without needing to
+upgrade. Third, it clearly distinguishes between a code block intented to be
+rendered as a figure and one intended to be displayed as verbatim source code.
+
+Typically, source code is presented in code blocks that include the language
+name so that syntax highlighting can be applied:
+
+ ``` c
+ main() {
+ printf("hello, world");
+ }
+ ```
+
+GitHub created a _de facto_ standard prevents parsers from dynamically
+distinguishing between a diagram to render and source code to list. That is,
+does the following fenced code block specify a language syntax or should it
+draw a pie chart?
+
+ ``` mermaid
+ pie
+ title Turkish Empire Proportions, 1789
+ "Asia" : 66
+ "Africa" : 20
+ "Europe" : 14
+ ```
+
+While `mermaid-lang` could help, it creates a special case that needs to be
+programmed into Markdown parsers. KeenWrite's `diagram-` prefix side-steps the
+issue while keeping true to the human-readability aspect of Markdown. For this
+reason, using `mermaid` alone for a fenced code block will not draw a diagram.
+
+### Mermaid
+
+At time of writing,
+
+
+
+## Cross-references
+
+## Citations
+
+# History
+
+John Gruber created Markdown based on decades of syntax conventions that had evolved in email, mailing lists, Usenet newsgroups, and elsewhere. His original goal was to make a text format that could be easily converted to HTML while remaining human-readable. Instead of `<em>emphasis</em>`, one writes `*emphasis*`. Gruber released a specification alongside a script that converts a Markdown document into valid HTML.
+The format gained popularity among developers, leading to its adoption for project README files, which significantly boosted its widespread use.
+However, specification ambiguities resulted in conversion software interpreting edge cases differently. This fragmentation prompted John MacFarlane to spearhead CommonMark, a Markdown standard aimed to eliminate ambiguities.

Upgrades EchoSVG and Commons Compress

Author DaveJarvis <email>
Date 2025-08-19 11:56:07 GMT-0700
Commit a5487493cecb528f169de3e390c34229e01225a9
Parent 397b26b
Delta 509 lines added, 422 lines removed, 87-line increase