| 59 | 59 | implementation 'org.jsoup:jsoup:1.13.1' |
| 60 | 60 | implementation 'org.xhtmlrenderer:flying-saucer-core:9.1.20' |
| 61 | implementation 'org.xhtmlrenderer:flying-saucer-swt:9.1.20' | |
| 62 | 61 | |
| 63 | 62 | // R |
| 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> | |
| 1 | 2 |
| 40 | 40 | import javafx.event.EventHandler; |
| 41 | 41 | import javafx.event.EventType; |
| 42 | import javafx.scene.Node; | |
| 43 | 42 | import javafx.scene.Scene; |
| 44 | 43 | import javafx.scene.control.Tab; |
| 45 | 44 | import javafx.scene.control.Tooltip; |
| 46 | 45 | import javafx.scene.text.Text; |
| 47 | 46 | import javafx.stage.Window; |
| 47 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 48 | 48 | import org.fxmisc.richtext.StyleClassedTextArea; |
| 49 | 49 | import org.fxmisc.undo.UndoManager; |
| ... | ||
| 154 | 154 | private void initLayout() { |
| 155 | 155 | setContent( getScrollPane() ); |
| 156 | } | |
| 157 | ||
| 158 | private Node getScrollPane() { | |
| 159 | return getEditorPane().getScrollPane(); | |
| 160 | 156 | } |
| 161 | 157 | |
| ... | ||
| 192 | 188 | getEditor().selectRange( index, index + needle.length() ); |
| 193 | 189 | } |
| 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(); | |
| 194 | 199 | } |
| 195 | 200 | |
| 78 | 78 | |
| 79 | 79 | 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; | |
| 81 | 84 | import java.util.function.Function; |
| 82 | 85 | import java.util.prefs.Preferences; |
| ... | ||
| 322 | 325 | |
| 323 | 326 | private void initScrollEventListener( final FileEditorTab tab ) { |
| 324 | final var scrollPane = tab.getEditorPane().getScrollPane(); | |
| 327 | final var scrollPane = tab.getScrollPane(); | |
| 325 | 328 | final var scrollBar = getPreviewPane().getVerticalScrollBar(); |
| 326 | 329 | |
| 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 | } | |
| 1 | 128 |
| 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 | } | |
| 111 | 1 |
| 28 | 28 | package com.scrivenvar.preview; |
| 29 | 29 | |
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.service.events.Notifier; | |
| 30 | 32 | import javafx.beans.property.BooleanProperty; |
| 31 | 33 | import javafx.beans.property.SimpleBooleanProperty; |
| ... | ||
| 43 | 45 | import org.xhtmlrenderer.simple.XHTMLPanel; |
| 44 | 46 | import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler; |
| 45 | import org.xhtmlrenderer.swing.SwingReplacedElementFactory; | |
| 46 | import org.xhtmlrenderer.util.Configuration; | |
| 47 | import org.xhtmlrenderer.swing.*; | |
| 47 | 48 | |
| 48 | 49 | import javax.swing.*; |
| 49 | 50 | import java.awt.*; |
| 50 | 51 | import java.awt.event.ComponentEvent; |
| 51 | 52 | import java.awt.event.ComponentListener; |
| 53 | import java.io.IOException; | |
| 54 | import java.net.URI; | |
| 55 | import java.net.URISyntaxException; | |
| 52 | 56 | import java.nio.file.Path; |
| 53 | 57 | |
| 54 | 58 | import static com.scrivenvar.Constants.PARAGRAPH_ID_PREFIX; |
| 55 | 59 | import static com.scrivenvar.Constants.STYLESHEET_PREVIEW; |
| 60 | import static java.awt.Desktop.Action.BROWSE; | |
| 61 | import static java.awt.Desktop.getDesktop; | |
| 56 | 62 | import static org.xhtmlrenderer.swing.ImageResourceLoader.NO_OP_REPAINT_LISTENER; |
| 57 | 63 | |
| 58 | 64 | /** |
| 59 | 65 | * HTML preview pane is responsible for rendering an HTML document. |
| 60 | 66 | */ |
| 61 | 67 | public final class HTMLPreviewPane extends Pane { |
| 68 | private final static Notifier NOTIFIER = Services.load( Notifier.class ); | |
| 69 | ||
| 62 | 70 | /** |
| 63 | * Prevent scrolling to the top on every key press. | |
| 71 | * Suppresses scrolling to the top on every key press. | |
| 64 | 72 | */ |
| 65 | 73 | private static class HTMLPanel extends XHTMLPanel { |
| 66 | 74 | @Override |
| 67 | 75 | public void resetScrollPosition() { |
| 68 | 76 | } |
| 69 | 77 | } |
| 70 | 78 | |
| 71 | 79 | /** |
| 72 | * Prevent scroll attempts until after the document has loaded. | |
| 80 | * Suppresses scroll attempts until after the document has loaded. | |
| 73 | 81 | */ |
| 74 | 82 | private static final class DocumentEventHandler implements DocumentListener { |
| ... | ||
| 95 | 103 | @Override |
| 96 | 104 | 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 | } | |
| 97 | 149 | } |
| 98 | 150 | } |
| ... | ||
| 106 | 158 | + "<body>"; |
| 107 | 159 | 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(); | |
| 108 | 164 | |
| 109 | 165 | private final StringBuilder mHtmlDocument = new StringBuilder( 65536 ); |
| 110 | 166 | private final int mHtmlPrefixLength; |
| 111 | 167 | |
| 112 | private final W3CDom mW3cDom = new W3CDom(); | |
| 113 | private final XhtmlNamespaceHandler mNamespaceHandler = | |
| 114 | new XhtmlNamespaceHandler(); | |
| 115 | 168 | private final HTMLPanel mHtmlRenderer = new HTMLPanel(); |
| 116 | 169 | private final SwingNode mSwingNode = new SwingNode(); |
| 117 | 170 | 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(); | |
| 122 | 173 | |
| 123 | 174 | private Path mPath; |
| 124 | 175 | |
| 125 | 176 | /** |
| 126 | 177 | * Creates a new preview pane that can scroll to the caret position within the |
| 127 | 178 | * document. |
| 128 | 179 | */ |
| 129 | 180 | public HTMLPreviewPane() { |
| 181 | setStyle( "-fx-background-color: white;" ); | |
| 182 | ||
| 183 | // No need to append the header each time the HTML content is updated. | |
| 130 | 184 | mHtmlDocument.append( HTML_HEADER ); |
| 131 | 185 | mHtmlPrefixLength = mHtmlDocument.length(); |
| 132 | 186 | |
| 187 | // Inject an SVG renderer that produces high-quality SVG buffered images. | |
| 133 | 188 | final var factory = new ChainedReplacedElementFactory(); |
| 134 | 189 | factory.addFactory( new SVGReplacedElementFactory() ); |
| 135 | 190 | factory.addFactory( new SwingReplacedElementFactory( |
| 136 | 191 | NO_OP_REPAINT_LISTENER, mImageLoader ) ); |
| 137 | 192 | |
| 193 | // Ensure fonts are always anti-aliased. | |
| 138 | 194 | final var context = getSharedContext(); |
| 139 | 195 | context.setReplacedElementFactory( factory ); |
| 140 | 196 | context.getTextRenderer().setSmoothingThreshold( 0 ); |
| 141 | 197 | |
| 142 | 198 | mSwingNode.setContent( mScrollPane ); |
| 143 | 199 | 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 | } | |
| 156 | 200 | |
| 157 | @Override | |
| 158 | public void componentMoved( final ComponentEvent e ) { | |
| 159 | } | |
| 201 | mHtmlRenderer.addDocumentListener( mDocHandler ); | |
| 202 | mHtmlRenderer.addComponentListener( new ResizeListener() ); | |
| 160 | 203 | |
| 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 ); | |
| 163 | 210 | } |
| 211 | } | |
| 164 | 212 | |
| 165 | @Override | |
| 166 | public void componentHidden( final ComponentEvent e ) { | |
| 167 | } | |
| 168 | } ); | |
| 213 | mHtmlRenderer.addMouseTrackingListener( new HyperlinkListener() ); | |
| 169 | 214 | } |
| 170 | 215 | |
| ... | ||
| 177 | 222 | public void update( final String html ) { |
| 178 | 223 | 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 ); | |
| 180 | 225 | |
| 181 | mHtmlRenderer.setDocument( w3cDoc, getBaseUrl(), mNamespaceHandler ); | |
| 226 | mHtmlRenderer.setDocument( w3cDoc, getBaseUrl(), NS_HANDLER ); | |
| 182 | 227 | } |
| 183 | 228 | |
| ... | ||
| 198 | 243 | scrollTo( id ); |
| 199 | 244 | |
| 200 | mDocumentHandler.readyProperty().removeListener( this ); | |
| 245 | mDocHandler.readyProperty().removeListener( this ); | |
| 201 | 246 | } |
| 202 | 247 | } |
| 203 | 248 | }; |
| 204 | 249 | |
| 205 | mDocumentHandler.readyProperty().addListener( listener ); | |
| 250 | mDocHandler.readyProperty().addListener( listener ); | |
| 206 | 251 | } |
| 207 | 252 | |
| 84 | 84 | static { |
| 85 | 85 | // 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>"; | |
| 114 | 112 | |
| 115 | 113 | // The width and height cannot be embedded in the SVG above because the |
| 131 | 131 | /* Must be bundled in JAR file. */ |
| 132 | 132 | font-family: "Fira Code", monospace; |
| 133 | font-size: 14pt; | |
| 133 | font-size: 10pt; | |
| 134 | 134 | white-space: pre-wrap; |
| 135 | 135 | word-wrap: break-word; |
| 136 | 136 | overflow-wrap: anywhere; |
| 137 | 137 | } |
| 138 | 138 | |
| 139 | 139 | code, tt { |
| 140 | border: 1px solid #ccc; | |
| 141 | 140 | 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; | |
| 143 | 147 | } |
| 144 | 148 | |
| ... | ||
| 151 | 155 | background-color: #f8f8f8; |
| 152 | 156 | border: .125em solid #ccc; |
| 153 | line-height: 1.6; | |
| 154 | 157 | padding: .25em .5em; |
| 155 | 158 | border-radius: .25em; |
| 156 | 159 | } |
| 157 | 160 | |
| 158 | 161 | pre code, pre tt { |
| 159 | 162 | background-color: transparent; |
| 160 | 163 | 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; | |
| 173 | 164 | } |
| 174 | 165 | |