Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
M build.gradle
5959
  implementation 'org.jsoup:jsoup:1.13.1'
6060
  implementation 'org.xhtmlrenderer:flying-saucer-core:9.1.20'
61
  implementation 'org.xhtmlrenderer:flying-saucer-swt:9.1.20'
6261
6362
  // R
A images/broken-camera.svg
1
<svg height='79pt' viewBox='0 0 100 79' width='100pt' xmlns='http://www.w3.org/2000/svg'><g fill='#454545'><path d='m32.175781 46.207031c1.316407 6.023438 6.628907 10.4375 12.847657 10.675781zm0 0'/><path d='m27.167969 40.105469-1.195313.949219.96875.804687c.050782-.59375.125-1.175781.226563-1.753906zm0 0'/><path d='m42.394531 3.949219-10.054687.875c-3.105469.269531-5.71875 2.414062-6.546875 5.382812l-1.464844 5.222657-13.660156 1.183593c-4.113281.355469-7.160157 3.949219-6.800781 8.023438l3.910156 44.269531c.363281 4.070312 3.992187 7.085938 8.105468 6.730469l46.832032-4.058594-12.457032-10.347656c-.992187.253906-2.007812.453125-3.0625.542969-10.277343.890624-19.359374-6.65625-20.261718-16.832032-.089844-1.042968-.070313-2.070312.007812-3.082031l-.96875-.804687 1.195313-.949219c1.4375-8.042969 8.160156-14.476563 16.765625-15.222657.835937-.074218 1.660156-.070312 2.476562-.035156l3.726563-2.953125zm0 0'/><path d='m40.9375 46.152344 11.859375 11.742187c.570313.070313 1.144531.121094 1.730469.121094 7.558594 0 13.714844-6.09375 13.714844-13.578125 0-7.480469-6.15625-13.578125-13.714844-13.578125s-13.714844 6.097656-13.714844 13.578125c0 .582031.050781 1.152344.125 1.714844zm0 0'/><path d='m57.953125 3.363281 4.472656 19-4.183593 2.269531c9.007812 1.988282 15.382812 10.316407 14.554687 19.664063-.804687 9.132813-8.207031 16.128906-17.148437 16.824219l10.453124 12.335937 17.75 1.539063c4.113282.355468 7.742188-2.660156 8.101563-6.734375l3.910156-44.265625c.363281-4.074219-2.683593-7.667969-6.796875-8.023438l-13.660156-1.183594-1.480469-5.226562c-.832031-2.96875-3.441406-5.113281-6.546875-5.382812zm0 0'/></g></svg>
12
M src/main/java/com/scrivenvar/FileEditorTab.java
4040
import javafx.event.EventHandler;
4141
import javafx.event.EventType;
42
import javafx.scene.Node;
4342
import javafx.scene.Scene;
4443
import javafx.scene.control.Tab;
4544
import javafx.scene.control.Tooltip;
4645
import javafx.scene.text.Text;
4746
import javafx.stage.Window;
47
import org.fxmisc.flowless.VirtualizedScrollPane;
4848
import org.fxmisc.richtext.StyleClassedTextArea;
4949
import org.fxmisc.undo.UndoManager;
...
154154
  private void initLayout() {
155155
    setContent( getScrollPane() );
156
  }
157
158
  private Node getScrollPane() {
159
    return getEditorPane().getScrollPane();
160156
  }
161157
...
192188
      getEditor().selectRange( index, index + needle.length() );
193189
    }
190
  }
191
192
  /**
193
   * Gets a reference to the scroll pane that houses the editor.
194
   *
195
   * @return The editor's scroll pane, containing a vertical scrollbar.
196
   */
197
  public VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() {
198
    return getEditorPane().getScrollPane();
194199
  }
195200
M src/main/java/com/scrivenvar/MainWindow.java
7878
7979
import java.nio.file.Path;
80
import java.util.*;
80
import java.util.HashMap;
81
import java.util.Map;
82
import java.util.Observable;
83
import java.util.Observer;
8184
import java.util.function.Function;
8285
import java.util.prefs.Preferences;
...
322325
323326
  private void initScrollEventListener( final FileEditorTab tab ) {
324
    final var scrollPane = tab.getEditorPane().getScrollPane();
327
    final var scrollPane = tab.getScrollPane();
325328
    final var scrollBar = getPreviewPane().getVerticalScrollBar();
326329
A src/main/java/com/scrivenvar/preview/CustomImageLoader.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.preview;
29
30
import javafx.beans.property.IntegerProperty;
31
import javafx.beans.property.SimpleIntegerProperty;
32
import org.xhtmlrenderer.extend.FSImage;
33
import org.xhtmlrenderer.resource.ImageResource;
34
import org.xhtmlrenderer.swing.ImageResourceLoader;
35
36
import java.net.URI;
37
import java.nio.file.Files;
38
import java.nio.file.Paths;
39
40
import static com.scrivenvar.preview.SVGRasterizer.BROKEN_IMAGE_PLACEHOLDER;
41
import static org.xhtmlrenderer.swing.AWTFSImage.createImage;
42
43
/**
44
 * Responsible for loading images. If the image cannot be found, a placeholder
45
 * is used instead.
46
 */
47
public class CustomImageLoader extends ImageResourceLoader {
48
  /**
49
   * Placeholder that's displayed when image cannot be found.
50
   */
51
  private static final FSImage BROKEN_IMAGE = createImage(
52
      BROKEN_IMAGE_PLACEHOLDER );
53
54
  private final IntegerProperty mWidthProperty = new SimpleIntegerProperty();
55
56
  public CustomImageLoader() {
57
  }
58
59
  /**
60
   * Gets an {@link IntegerProperty} that represents the maximum width an
61
   * image should be scaled.
62
   *
63
   * @return The maximum width for an image.
64
   */
65
  public IntegerProperty widthProperty() {
66
    return mWidthProperty;
67
  }
68
69
  /**
70
   * Gets an image resolved from the given URI. If the image cannot be found,
71
   * this will return a custom placeholder image indicating the reference
72
   * is broken.
73
   *
74
   * @param uri    Path to the image resource to load.
75
   * @param width  Maximum image width (scaled to fit), in pixels.
76
   * @param height Image height, in pixels.
77
   * @return The scaled image, or a placeholder image if the URI's content
78
   * could not be retrieved.
79
   */
80
  @Override
81
  public synchronized ImageResource get(
82
      final String uri, final int width, final int height ) {
83
    assert uri != null;
84
    assert width >= 0;
85
    assert height >= 0;
86
87
    boolean exists;
88
89
    try {
90
      exists = Files.exists( Paths.get( new URI( uri ) ) );
91
    } catch( final Exception e ) {
92
      exists = false;
93
    }
94
95
    return exists
96
        ? scale( uri, width, height )
97
        : new ImageResource( uri, BROKEN_IMAGE );
98
  }
99
100
  /**
101
   * Scales the image found at the given URI.
102
   *
103
   * @param uri Path to the image file to load.
104
   * @param w   Unused (usually -1, which is useless).
105
   * @param h   Unused (ditto).
106
   * @return Resource representing the rendered image and path.
107
   */
108
  private ImageResource scale( final String uri, final int w, final int h ) {
109
    final var ir = super.get( uri, w, h );
110
    final var image = ir.getImage();
111
    final var imageWidth = image.getWidth();
112
    final var imageHeight = image.getHeight();
113
114
    int maxWidth = mWidthProperty.get();
115
    int newWidth = imageWidth;
116
    int newHeight = imageHeight;
117
118
    // Maintain aspect ratio while shrinking image to view port bounds.
119
    if( imageWidth > maxWidth ) {
120
      newWidth = maxWidth;
121
      newHeight = (newWidth * imageHeight) / imageWidth;
122
    }
123
124
    image.scale( newWidth, newHeight );
125
    return ir;
126
  }
127
}
1128
D src/main/java/com/scrivenvar/preview/CustomImageResourceLoader.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.preview;
29
30
import javafx.beans.property.IntegerProperty;
31
import javafx.beans.property.SimpleIntegerProperty;
32
import org.xhtmlrenderer.extend.FSImage;
33
import org.xhtmlrenderer.resource.ImageResource;
34
import org.xhtmlrenderer.swing.ImageResourceLoader;
35
36
import java.net.URI;
37
import java.nio.file.Files;
38
import java.nio.file.Paths;
39
40
import static com.scrivenvar.preview.SVGRasterizer.BROKEN_IMAGE_PLACEHOLDER;
41
import static org.xhtmlrenderer.swing.AWTFSImage.createImage;
42
43
/**
44
 * Responsible for loading images. If the image cannot be found, a placeholder
45
 * is used instead.
46
 */
47
public class CustomImageResourceLoader extends ImageResourceLoader {
48
  /**
49
   * Placeholder that's displayed when image cannot be found.
50
   */
51
  private static final FSImage FS_PLACEHOLDER_IMAGE =
52
      createImage( BROKEN_IMAGE_PLACEHOLDER );
53
54
  private final IntegerProperty mMaxWidthProperty = new SimpleIntegerProperty();
55
56
  public CustomImageResourceLoader() {
57
  }
58
59
  public IntegerProperty widthProperty() {
60
    return mMaxWidthProperty;
61
  }
62
63
  @Override
64
  public synchronized ImageResource get(
65
      final String uri, final int width, final int height ) {
66
    assert uri != null;
67
    assert width >= 0;
68
    assert height >= 0;
69
70
    boolean exists;
71
72
    try {
73
      exists = Files.exists( Paths.get( new URI( uri ) ) );
74
    } catch( final Exception e ) {
75
      exists = false;
76
    }
77
78
    return exists
79
        ? scale( uri, width, height )
80
        : new ImageResource( uri, FS_PLACEHOLDER_IMAGE );
81
  }
82
83
  /**
84
   * Scales the image found at the given uri.
85
   *
86
   * @param uri Path to the image file to load.
87
   * @param w   Unused (usually -1, which is useless).
88
   * @param h   Unused (ditto).
89
   * @return Resource representing the rendered image and path.
90
   */
91
  private ImageResource scale( final String uri, final int w, final int h ) {
92
    final var ir = super.get( uri, w, h );
93
    final var image = ir.getImage();
94
    final var imageWidth = image.getWidth();
95
    final var imageHeight = image.getHeight();
96
97
    int maxWidth = mMaxWidthProperty.get();
98
    int newWidth = imageWidth;
99
    int newHeight = imageHeight;
100
101
    // Maintain aspect ratio while shrinking image to view port bounds.
102
    if( imageWidth > maxWidth ) {
103
      newWidth = maxWidth;
104
      newHeight = (newWidth * imageHeight) / imageWidth;
105
    }
106
107
    image.scale( newWidth, newHeight );
108
    return ir;
109
  }
110
}
1111
M src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
2828
package com.scrivenvar.preview;
2929
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.events.Notifier;
3032
import javafx.beans.property.BooleanProperty;
3133
import javafx.beans.property.SimpleBooleanProperty;
...
4345
import org.xhtmlrenderer.simple.XHTMLPanel;
4446
import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler;
45
import org.xhtmlrenderer.swing.SwingReplacedElementFactory;
46
import org.xhtmlrenderer.util.Configuration;
47
import org.xhtmlrenderer.swing.*;
4748
4849
import javax.swing.*;
4950
import java.awt.*;
5051
import java.awt.event.ComponentEvent;
5152
import java.awt.event.ComponentListener;
53
import java.io.IOException;
54
import java.net.URI;
55
import java.net.URISyntaxException;
5256
import java.nio.file.Path;
5357
5458
import static com.scrivenvar.Constants.PARAGRAPH_ID_PREFIX;
5559
import static com.scrivenvar.Constants.STYLESHEET_PREVIEW;
60
import static java.awt.Desktop.Action.BROWSE;
61
import static java.awt.Desktop.getDesktop;
5662
import static org.xhtmlrenderer.swing.ImageResourceLoader.NO_OP_REPAINT_LISTENER;
5763
5864
/**
5965
 * HTML preview pane is responsible for rendering an HTML document.
6066
 */
6167
public final class HTMLPreviewPane extends Pane {
68
  private final static Notifier NOTIFIER = Services.load( Notifier.class );
69
6270
  /**
63
   * Prevent scrolling to the top on every key press.
71
   * Suppresses scrolling to the top on every key press.
6472
   */
6573
  private static class HTMLPanel extends XHTMLPanel {
6674
    @Override
6775
    public void resetScrollPosition() {
6876
    }
6977
  }
7078
7179
  /**
72
   * Prevent scroll attempts until after the document has loaded.
80
   * Suppresses scroll attempts until after the document has loaded.
7381
   */
7482
  private static final class DocumentEventHandler implements DocumentListener {
...
95103
    @Override
96104
    public void onRenderException( final Throwable t ) {
105
    }
106
  }
107
108
  /**
109
   * Responsible for ensuring that images are constrained to the panel width
110
   * upon resizing.
111
   */
112
  private final class ResizeListener implements ComponentListener {
113
    @Override
114
    public void componentResized( final ComponentEvent e ) {
115
      // Scaling a bit below the full width prevents the horizontal scrollbar
116
      // from appearing.
117
      final int width = (int) (e.getComponent().getWidth() * .95);
118
      HTMLPreviewPane.this.mImageLoader.widthProperty().set( width );
119
    }
120
121
    @Override
122
    public void componentMoved( final ComponentEvent e ) {
123
    }
124
125
    @Override
126
    public void componentShown( final ComponentEvent e ) {
127
    }
128
129
    @Override
130
    public void componentHidden( final ComponentEvent e ) {
131
    }
132
  }
133
134
  /**
135
   * Responsible for launching hyperlinks in the system's default browser.
136
   */
137
  private static class HyperlinkListener extends LinkListener {
138
    @Override
139
    public void linkClicked( final BasicPanel panel, final String uri ) {
140
      try {
141
        final var desktop = getDesktop();
142
143
        if( desktop.isSupported( BROWSE ) ) {
144
          desktop.browse( new URI( uri ) );
145
        }
146
      } catch( final Exception e ) {
147
        NOTIFIER.notify( e );
148
      }
97149
    }
98150
  }
...
106158
      + "<body>";
107159
  private final static String HTML_FOOTER = "</body></html>";
160
161
  private final static W3CDom W3C_DOM = new W3CDom();
162
  private final static XhtmlNamespaceHandler NS_HANDLER =
163
      new XhtmlNamespaceHandler();
108164
109165
  private final StringBuilder mHtmlDocument = new StringBuilder( 65536 );
110166
  private final int mHtmlPrefixLength;
111167
112
  private final W3CDom mW3cDom = new W3CDom();
113
  private final XhtmlNamespaceHandler mNamespaceHandler =
114
      new XhtmlNamespaceHandler();
115168
  private final HTMLPanel mHtmlRenderer = new HTMLPanel();
116169
  private final SwingNode mSwingNode = new SwingNode();
117170
  private final JScrollPane mScrollPane = new JScrollPane( mHtmlRenderer );
118
  private final DocumentEventHandler mDocumentHandler =
119
      new DocumentEventHandler();
120
  private final CustomImageResourceLoader mImageLoader =
121
      new CustomImageResourceLoader();
171
  private final DocumentEventHandler mDocHandler = new DocumentEventHandler();
172
  private final CustomImageLoader mImageLoader = new CustomImageLoader();
122173
123174
  private Path mPath;
124175
125176
  /**
126177
   * Creates a new preview pane that can scroll to the caret position within the
127178
   * document.
128179
   */
129180
  public HTMLPreviewPane() {
181
    setStyle( "-fx-background-color: white;" );
182
183
    // No need to append the header each time the HTML content is updated.
130184
    mHtmlDocument.append( HTML_HEADER );
131185
    mHtmlPrefixLength = mHtmlDocument.length();
132186
187
    // Inject an SVG renderer that produces high-quality SVG buffered images.
133188
    final var factory = new ChainedReplacedElementFactory();
134189
    factory.addFactory( new SVGReplacedElementFactory() );
135190
    factory.addFactory( new SwingReplacedElementFactory(
136191
        NO_OP_REPAINT_LISTENER, mImageLoader ) );
137192
193
    // Ensure fonts are always anti-aliased.
138194
    final var context = getSharedContext();
139195
    context.setReplacedElementFactory( factory );
140196
    context.getTextRenderer().setSmoothingThreshold( 0 );
141197
142198
    mSwingNode.setContent( mScrollPane );
143199
    mSwingNode.setCache( true );
144
145
    mHtmlRenderer.addDocumentListener( mDocumentHandler );
146
    setStyle( "-fx-background-color: white;" );
147
148
    mHtmlRenderer.addComponentListener( new ComponentListener() {
149
      @Override
150
      public void componentResized( final ComponentEvent e ) {
151
        // Scaling a bit below the full width prevents the horizontal scrollbar
152
        // from appearing.
153
        final int width = (int) (e.getComponent().getWidth() * .95);
154
        mImageLoader.widthProperty().set( width );
155
      }
156200
157
      @Override
158
      public void componentMoved( final ComponentEvent e ) {
159
      }
201
    mHtmlRenderer.addDocumentListener( mDocHandler );
202
    mHtmlRenderer.addComponentListener( new ResizeListener() );
160203
161
      @Override
162
      public void componentShown( final ComponentEvent e ) {
204
    // The default mouse click listener attempts navigation within the
205
    // preview panel. We want to usurp that behaviour to open the link in
206
    // a platform-specific browser.
207
    for( final var listener : mHtmlRenderer.getMouseTrackingListeners() ) {
208
      if( !(listener instanceof HoverListener) ) {
209
        mHtmlRenderer.removeMouseTrackingListener( (FSMouseListener) listener );
163210
      }
211
    }
164212
165
      @Override
166
      public void componentHidden( final ComponentEvent e ) {
167
      }
168
    } );
213
    mHtmlRenderer.addMouseTrackingListener( new HyperlinkListener() );
169214
  }
170215
...
177222
  public void update( final String html ) {
178223
    final Document jsoupDoc = Jsoup.parse( decorate( html ) );
179
    final org.w3c.dom.Document w3cDoc = mW3cDom.fromJsoup( jsoupDoc );
224
    final org.w3c.dom.Document w3cDoc = W3C_DOM.fromJsoup( jsoupDoc );
180225
181
    mHtmlRenderer.setDocument( w3cDoc, getBaseUrl(), mNamespaceHandler );
226
    mHtmlRenderer.setDocument( w3cDoc, getBaseUrl(), NS_HANDLER );
182227
  }
183228
...
198243
          scrollTo( id );
199244
200
          mDocumentHandler.readyProperty().removeListener( this );
245
          mDocHandler.readyProperty().removeListener( this );
201246
        }
202247
      }
203248
    };
204249
205
    mDocumentHandler.readyProperty().addListener( listener );
250
    mDocHandler.readyProperty().addListener( listener );
206251
  }
207252
M src/main/java/com/scrivenvar/preview/SVGRasterizer.java
8484
  static {
8585
    // A FontAwesome camera icon, cleft asunder.
86
    // @formatter:off
87
    final String BROKEN_IMAGE_SVG = "<svg xmlns='http://www.w3.org/2000/svg' " +
88
        "viewBox='0 0 150 150' width='150pt' height='150pt' " +
89
        "fill='#454545'><path d='m41.738281 87.738281c2.480469 11.433594 12" +
90
        ".464844 19.8125 24.15625 20.269531zm0 0'/><path d='m32.328125 76" +
91
        ".152344-2.25 1.800781 1.824219 1.527344c.089844-1.125.230468-2.230469" +
92
        ".425781-3.328125zm0 0'/><path d='m60.953125 7.5-18.90625 1.65625c-5" +
93
        ".835937.511719-10.746094 4.585938-12.308594 10.222656l-2.75 9" +
94
        ".917969-25.683593 2.246094c-7.734376.675781-13.460938 7.5-12.785157 15" +
95
        ".234375l7.355469 84.054687c.675781 7.734375 7.5 13.457031 15.234375 12" +
96
        ".78125l88.042969-7.703125-23.417969-19.648437c-1.867187.480469-3" +
97
        ".777344.855469-5.757813 1.027343-19.324218 1.691407-36.398437-12" +
98
        ".636718-38.089843-31.957031-.171875-1.980469-.136719-3.929687.015625-5" +
99
        ".851562l-1.824219-1.527344 2.25-1.800781c2.703125-15.277344 15" +
100
        ".339844-27.492188 31.519531-28.90625 1.570313-.140625 3.121094-.132813" +
101
        " 4.65625-.066406l7-5.605469zm0 0'/><path d='m58.210938 87.628906 22" +
102
        ".296874 22.300782c1.070313.132812 2.148438.226562 3.253907.226562 14" +
103
        ".210937 0 25.78125-11.570312 25.78125-25.78125 0-14.207031-11" +
104
        ".570313-25.78125-25.78125-25.78125-14.207031 0-25.78125 11.574219-25" +
105
        ".78125 25.78125 0 1.105469.09375 2.1875.230469 3.253906zm0 0'/><path " +
106
        "d='m90.203125 6.382812 8.410156 36.082032-7.867187 4.304687c16.9375 3" +
107
        ".773438 28.917968 19.589844 27.363281 37.339844-1.515625 17.339844-15" +
108
        ".433594 30.621094-32.238281 31.945313l19.652344 23.421874 33.367187 2" +
109
        ".917969c7.734375.675781 14.558594-5.050781 15.234375-12.785156l7" +
110
        ".351562-84.050781c.679688-7.734375-5.046874-14.558594-12.78125-15" +
111
        ".234375l-25.683593-2.246094-2.78125-9.921875c-1.5625-5.632812-6" +
112
        ".472657-9.710938-12.308594-10.222656zm0 0'/></svg>";
113
    // @formatter:on
86
    final String BROKEN_IMAGE_SVG = "<svg height='79pt' viewBox='0 0 100 79' " +
87
        "width='100pt' xmlns='http://www.w3.org/2000/svg'><g " +
88
        "fill='#454545'><path d='m32.175781 46.207031c1.316407 6.023438 6" +
89
        ".628907 10.4375 12.847657 10.675781zm0 0'/><path d='m27.167969 40" +
90
        ".105469-1.195313.949219.96875.804687c.050782-.59375.125-1.175781" +
91
        ".226563-1.753906zm0 0'/><path d='m42.394531 3.949219-10.054687" +
92
        ".875c-3.105469.269531-5.71875 2.414062-6.546875 5.382812l-1.464844 5" +
93
        ".222657-13.660156 1.183593c-4.113281.355469-7.160157 3.949219-6" +
94
        ".800781 8.023438l3.910156 44.269531c.363281 4.070312 3.992187 7" +
95
        ".085938 8.105468 6.730469l46.832032-4.058594-12.457032-10.347656c-" +
96
        ".992187.253906-2.007812.453125-3.0625.542969-10.277343.890624-19" +
97
        ".359374-6.65625-20.261718-16.832032-.089844-1.042968-.070313-2" +
98
        ".070312.007812-3.082031l-.96875-.804687 1.195313-.949219c1.4375-8" +
99
        ".042969 8.160156-14.476563 16.765625-15.222657.835937-.074218 1" +
100
        ".660156-.070312 2.476562-.035156l3.726563-2.953125zm0 0'/><path " +
101
        "d='m40.9375 46.152344 11.859375 11.742187c.570313.070313 1.144531" +
102
        ".121094 1.730469.121094 7.558594 0 13.714844-6.09375 13.714844-13" +
103
        ".578125 0-7.480469-6.15625-13.578125-13.714844-13.578125s-13.714844 " +
104
        "6.097656-13.714844 13.578125c0 .582031.050781 1.152344.125 1" +
105
        ".714844zm0 0'/><path d='m57.953125 3.363281 4.472656 19-4.183593 2" +
106
        ".269531c9.007812 1.988282 15.382812 10.316407 14.554687 19.664063-" +
107
        ".804687 9.132813-8.207031 16.128906-17.148437 16.824219l10.453124 12" +
108
        ".335937 17.75 1.539063c4.113282.355468 7.742188-2.660156 8.101563-6" +
109
        ".734375l3.910156-44.265625c.363281-4.074219-2.683593-7.667969-6" +
110
        ".796875-8.023438l-13.660156-1.183594-1.480469-5.226562c-.832031-2" +
111
        ".96875-3.441406-5.113281-6.546875-5.382812zm0 0'/></g></svg>";
114112
115113
    // The width and height cannot be embedded in the SVG above because the
M src/main/resources/com/scrivenvar/preview/webview.css
131131
  /* Must be bundled in JAR file. */
132132
  font-family: "Fira Code", monospace;
133
  font-size: 14pt;
133
  font-size: 10pt;
134134
  white-space: pre-wrap;
135135
  word-wrap: break-word;
136136
  overflow-wrap: anywhere;
137137
}
138138
139139
code, tt {
140
  border: 1px solid #ccc;
141140
  background-color: #f8f8f8;
142
  border-radius: 3px;
141
  min-width: 1em;
142
  padding: .2em .3em;
143
  text-align: center;
144
  text-decoration: none;
145
  border-radius: .3em;
146
  border: none;
143147
}
144148
...
151155
  background-color: #f8f8f8;
152156
  border: .125em solid #ccc;
153
  line-height: 1.6;
154157
  padding: .25em .5em;
155158
  border-radius: .25em;
156159
}
157160
158161
pre code, pre tt {
159162
  background-color: transparent;
160163
  border: none;
161
}
162
163
kbd {
164
  background-color: #ccc;
165
  background-repeat: repeat-x;
166
  border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
167
  border-radius: 2px;
168
  border-style: solid;
169
  border-width: 1px;
170
  font-family: sans-serif;
171
  line-height: 1.25;
172
  padding: 1px 4px;
173164
}
174165