Dave Jarvis' Repositories

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

Cache images, simplify CSS

AuthorDaveJarvis <email>
Date2020-06-19 23:31:03 GMT-0700
Commit58a1faee8dddd8054fbfe3fcac01001fab87d619
Parent5eeed00
Delta120 lines added, 111 lines removed, 9-line increase
src/main/resources/com/scrivenvar/preview/webview.css
-/*
-This software is released under the MIT license:
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*/
-
-/* Source: https://github.com/nicolashery/markdownpad-github */
-
-/* GitHub stylesheet for MarkdownPad (http://markdownpad.com) */
-
-/* RESET
-=============================================================================*/
-
-html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6,
-p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn,
-em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var,
-b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label,
-legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas,
-details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output,
-ruby, section, summary, time, mark, audio, video {
- margin: 0;
- padding: 0;
- border: 0;
-}
-
/* BODY
=============================================================================*/
-
body {
- font-family: serif;
- font-size: 14px;
- line-height: 1.6;
- color: #333;
+ font-family: Vollkorn, serif;
+ font-size: 16px;
background-color: #fff;
- padding: 20px;
- max-width: 960px;
margin: 0 auto;
+ max-width: 960px;
+ line-height: 1.6;
+ color: #454545;
+ padding: 0 10px
}
p, blockquote, ul, ol, dl, table, pre {
- margin: 15px 0;
+ margin: 20px 0;
}
/* HEADERS
=============================================================================*/
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
- font-weight: bold;
- -webkit-font-smoothing: antialiased;
}
h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code,
h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
font-size: inherit;
}
h1 {
font-size: 28px;
- color: #000;
}
h2 {
font-size: 24px;
border-bottom: 1px solid #ccc;
- color: #000;
}
h3 {
- font-size: 18px;
+ font-size: 20px;
}
h4 {
- font-size: 16px;
+ font-size: 18px;
}
h5 {
- font-size: 14px;
+ font-size: 16px;
}
h6 {
- color: #777;
font-size: 14px;
}
a {
- color: #4183C4;
+ color: #0077aa;
text-decoration: none;
}
ul, ol {
- padding-left: 30px;
+ padding-left: 20px;
}
padding: 0px 0px;
white-space: nowrap;
- border: 1px solid #eaeaea;
+ border: 1px solid #eee;
background-color: #f8f8f8;
border-radius: 3px;
blockquote {
- border-left: 4px solid #DDD;
+ border-left: 5px solid #DDD;
padding: 0 15px;
color: #777;
hr {
clear: both;
- margin: 15px 0;
+ margin: 10px 0;
height: 0px;
overflow: hidden;
border: none;
background: transparent;
- border-bottom: 4px solid #ddd;
+ border-bottom: 4px solid #eee;
padding: 0;
}
/* TABLES
=============================================================================*/
-table th {
- font-weight: bold;
+table {
+ width: 100%;
+ border-collapse: collapse;
}
-table th, table td {
- border: 1px solid #ccc;
- padding: 6px 13px;
+tr:nth-child(odd) {
+ background-color: #eee;
}
-table tr {
- border-top: 1px solid #ccc;
- background-color: #fff;
+th {
+ background-color: #454545;
+ color: #fff;
}
-table tr:nth-child(2n) {
- background-color: #f8f8f8;
+th, td {
+ text-align: left;
+ padding: 0 1em;
}
/* IMAGES
=============================================================================*/
img {
- max-width: 100%
+ max-width: 100%;
+ height: auto;
}
src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
*/
public final class HTMLPreviewPane extends Pane {
+ /**
+ * Prevent scrolling to the top on every key press.
+ */
private static class HTMLPanel extends XHTMLPanel {
- /**
- * Prevent scrolling to the top.
- */
@Override
public void resetScrollPosition() {
*/
public HTMLPreviewPane() {
- final ChainedReplacedElementFactory factory =
- new ChainedReplacedElementFactory();
+ final var factory = new ChainedReplacedElementFactory();
factory.addFactory( new SVGReplacedElementFactory() );
factory.addFactory( new SwingReplacedElementFactory() );
public void update( final String html ) {
final Document jsoupDoc = Jsoup.parse( decorate( html ) );
- org.w3c.dom.Document w3cDoc = mW3cDom.fromJsoup( jsoupDoc );
+ final org.w3c.dom.Document w3cDoc = mW3cDom.fromJsoup( jsoupDoc );
mRenderer.setDocument( w3cDoc, getBaseUrl(), mNamespaceHandler );
}
private String decorate( final String html ) {
+ // Trim the HTML back to the header.
mHtml.setLength( mHtmlPrefixLength );
+
+ // Write the HTML body element followed by closing tags.
return mHtml.append( html )
.append( HTML_FOOTER )
public void clear() {
update( "" );
- }
-
- private String getBaseUrl() {
- final Path basePath = getPath();
- final Path parent = basePath == null ? null : basePath.getParent();
-
- return parent == null ? "" : parent.toUri().toString();
}
public JScrollBar getVerticalScrollBar() {
return getScrollPane().getVerticalScrollBar();
+ }
+
+ private String getBaseUrl() {
+ final Path basePath = getPath();
+ final Path parent = basePath == null ? null : basePath.getParent();
+
+ return parent == null ? "" : parent.toUri().toString();
}
}
src/main/java/com/scrivenvar/preview/SVGRasterizer.java
import java.util.Map;
+import static java.awt.Color.RED;
import static java.awt.Color.WHITE;
import static java.awt.RenderingHints.*;
+import static java.awt.image.BufferedImage.TYPE_INT_RGB;
import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH;
import static org.apache.batik.transcoder.image.ImageTranscoder.KEY_BACKGROUND_COLOR;
}
- public static BufferedImage rasterize( final String url, final int width )
- throws IOException, TranscoderException {
- return rasterize( new URL( url ), width );
+ /**
+ * Rasterizes the vector graphic file at the given URL. If any exception
+ * happens, a red circle is returned instead.
+ *
+ * @param url The URL to a vector graphic file, which must include the
+ * protocol scheme (such as file:// or https://).
+ * @param width The number of pixels wide to render the image. The aspect
+ * ratio is maintained.
+ * @return Either the rasterized image upon success or a red circle.
+ */
+ public static Image rasterize( final String url, final int width ) {
+ try {
+ return rasterize( new URL( url ), width );
+ } catch( final Exception e ) {
+ return createPlaceholderImage( width );
+ }
}
return transcoder.getBufferedImage();
+ }
+
+ @SuppressWarnings("SuspiciousNameCombination")
+ private static Image createPlaceholderImage( final int width ) {
+ final var image = new BufferedImage( width, width, TYPE_INT_RGB );
+ final var graphics = (Graphics2D) image.getGraphics();
+
+ graphics.setColor( RED );
+ graphics.setStroke( new BasicStroke( 5 ) );
+ graphics.drawOval( 5, 5, width / 2, width / 2 );
+
+ return image;
}
}
src/main/java/com/scrivenvar/preview/SVGReplacedElementFactory.java
import java.awt.*;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static com.scrivenvar.preview.SVGRasterizer.rasterize;
public class SVGReplacedElementFactory
private static final String HTML_IMAGE_SRC = "src";
- public ReplacedElement createReplacedElement(
- final LayoutContext c, final BlockBox box, final UserAgentCallback uac,
- final int cssWidth, final int cssHeight ) {
- final Element e = box.getElement();
+ /**
+ * Constrain memory.
+ */
+ private static final int MAX_CACHED_IMAGES = 100;
- if( e == null ) {
- return null;
+ /**
+ * Where to put document inline evaluated R expressions.
+ */
+ private final Map<String, Image> mImageCache = new LinkedHashMap<>() {
+ @Override
+ protected boolean removeEldestEntry(
+ final Map.Entry<String, Image> eldest ) {
+ return size() > MAX_CACHED_IMAGES;
}
+ };
- final String nodeName = e.getNodeName();
- ReplacedElement result = null;
+ public ReplacedElement createReplacedElement(
+ final LayoutContext c,
+ final BlockBox box,
+ final UserAgentCallback uac,
+ final int cssWidth,
+ final int cssHeight ) {
+ final Element e = box.getElement();
- if( HTML_IMAGE.equals( nodeName ) ) {
- final String src = e.getAttribute( HTML_IMAGE_SRC );
- final String ext = FilenameUtils.getExtension( src );
+ if( e != null ) {
+ final String nodeName = e.getNodeName();
- if( SVG_FILE.equalsIgnoreCase( ext ) ) {
- try {
- final int width = box.getContentWidth();
- final Image image = SVGRasterizer.rasterize( src, width );
+ if( HTML_IMAGE.equals( nodeName ) ) {
+ final String src = e.getAttribute( HTML_IMAGE_SRC );
+ final String ext = FilenameUtils.getExtension( src );
- final int w = image.getWidth( null );
- final int h = image.getHeight( null );
+ if( SVG_FILE.equalsIgnoreCase( ext ) ) {
+ try {
+ final int width = box.getContentWidth();
+ final Image image = getImage( src, width );
- result = new ImageReplacedElement( image, w, h );
- } catch( final Exception ex ) {
- getNotifier().notify( ex );
+ final int w = image.getWidth( null );
+ final int h = image.getHeight( null );
+
+ return new ImageReplacedElement( image, w, h );
+ } catch( final Exception ex ) {
+ getNotifier().notify( ex );
+ }
}
}
}
- return result;
+ return null;
}
@Override
public void reset() {
}
@Override
- public void remove( Element e ) {
+ public void remove( final Element e ) {
}
@Override
public void setFormSubmissionListener( FormSubmissionListener listener ) {
+ }
+
+ private Image getImage( final String src, final int width ) {
+ return mImageCache.computeIfAbsent( src, v -> rasterize( src, width ) );
}