Dave Jarvis' Repositories

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

Remove custom image parser that scales images upon window resize

AuthorDaveJarvis <email>
Date2020-11-11 19:16:39 GMT-0800
Commit3b997aa65c63fa070b3219615e62c643c7909c7a
Parente7f3a05
Delta153 lines added, 158 lines removed, 5-line decrease
src/main/java/com/keenwrite/processors/HtmlPreviewProcessor.java
* updated. This decouples knowledge of changes to the editor panel from the
* HTML preview panel as well as any processing that takes place before the
- * final HTML preview is rendered. This should be the last link in the processor
+ * final HTML preview is rendered. This is the last link in the processor
* chain.
*/
public class HtmlPreviewProcessor extends ExecutorProcessor<String> {
- // There is only one preview panel.
+ /**
+ * There is only one preview panel.
+ */
private static HtmlPreview sHtmlPreviewPane;
* should not contain a doctype, head, or body tag, only
* content to render within the body.
- * @return {@code null} to indicate no more processors in the chain.
+ * @return The given {@code html} string.
*/
@Override
public String apply( final String html ) {
getHtmlPreviewPane().process( html );
-
- // No more processing required.
- return null;
+ return html;
}
src/main/java/com/keenwrite/preview/HtmlPanel.java
+package com.keenwrite.preview;
+
+import com.keenwrite.adapters.DocumentAdapter;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import org.jsoup.helper.W3CDom;
+import org.xhtmlrenderer.layout.SharedContext;
+import org.xhtmlrenderer.render.Box;
+import org.xhtmlrenderer.simple.XHTMLPanel;
+import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler;
+import org.xhtmlrenderer.swing.BasicPanel;
+import org.xhtmlrenderer.swing.FSMouseListener;
+import org.xhtmlrenderer.swing.HoverListener;
+import org.xhtmlrenderer.swing.LinkListener;
+
+import java.net.URI;
+
+import static com.keenwrite.StatusBarNotifier.clue;
+import static com.keenwrite.util.ProtocolResolver.getProtocol;
+import static java.awt.Desktop.Action.BROWSE;
+import static java.awt.Desktop.getDesktop;
+import static org.jsoup.Jsoup.parse;
+
+/**
+ * Responsible for configuring FlyingSaucer's {@link XHTMLPanel}.
+ */
+public class HtmlPanel extends XHTMLPanel {
+
+ /**
+ * Suppresses scroll attempts until after the document has loaded.
+ */
+ private static final class DocumentEventHandler extends DocumentAdapter {
+ private final BooleanProperty mReadyProperty = new SimpleBooleanProperty();
+
+ public BooleanProperty readyProperty() {
+ return mReadyProperty;
+ }
+
+ @Override
+ public void documentStarted() {
+ mReadyProperty.setValue( Boolean.FALSE );
+ }
+
+ @Override
+ public void documentLoaded() {
+ mReadyProperty.setValue( Boolean.TRUE );
+ }
+ }
+
+ /**
+ * Responsible for opening hyperlinks. External hyperlinks are opened in
+ * the system's default browser; local file system links are opened in the
+ * editor.
+ */
+ private static class HyperlinkListener extends LinkListener {
+ @Override
+ public void linkClicked( final BasicPanel panel, final String link ) {
+ try {
+ switch( getProtocol( link ) ) {
+ case HTTP:
+ final var desktop = getDesktop();
+
+ if( desktop.isSupported( BROWSE ) ) {
+ desktop.browse( new URI( link ) );
+ }
+ break;
+ case FILE:
+ // TODO: #88 -- publish a message to the event bus.
+ break;
+ }
+ } catch( final Exception ex ) {
+ clue( ex );
+ }
+ }
+ }
+
+ private static final W3CDom W3C_DOM = new W3CDom();
+ private static final XhtmlNamespaceHandler XNH = new XhtmlNamespaceHandler();
+
+ public HtmlPanel() {
+ addDocumentListener( new DocumentEventHandler() );
+ removeMouseTrackingListeners();
+ addMouseTrackingListener( new HyperlinkListener() );
+ }
+
+ public void render( final String html, final String baseUri ) {
+ setDocument( W3C_DOM.fromJsoup( parse( html ) ), baseUri, XNH );
+ }
+
+ /**
+ * Delegates to the {@link SharedContext}.
+ *
+ * @param id The HTML element identifier to retrieve in {@link Box} form.
+ * @return The {@link Box} that corresponds to the given element ID, or
+ * {@code null} if none found.
+ */
+ public Box getBoxById( final String id ) {
+ return getSharedContext().getBoxById( id );
+ }
+
+ /**
+ * Suppress scrolling to the top on updates.
+ */
+ @Override
+ public void resetScrollPosition() {
+ }
+
+ /**
+ * The default mouse click listener attempts navigation within the preview
+ * panel. We want to usurp that behaviour to open the link in a
+ * platform-specific browser.
+ */
+ private void removeMouseTrackingListeners() {
+ for( final var listener : getMouseTrackingListeners() ) {
+ if( !(listener instanceof HoverListener) ) {
+ removeMouseTrackingListener( (FSMouseListener) listener );
+ }
+ }
+ }
+}
src/main/java/com/keenwrite/preview/HtmlPreview.java
package com.keenwrite.preview;
-import com.keenwrite.adapters.DocumentAdapter;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.SimpleBooleanProperty;
import javafx.embed.swing.SwingNode;
import javafx.scene.Node;
-import org.jsoup.Jsoup;
-import org.jsoup.helper.W3CDom;
-import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.render.Box;
-import org.xhtmlrenderer.simple.XHTMLPanel;
-import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler;
-import org.xhtmlrenderer.swing.*;
+import org.xhtmlrenderer.swing.SwingReplacedElementFactory;
import javax.swing.*;
import java.awt.*;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.net.URI;
import java.nio.file.Path;
import static com.keenwrite.Constants.STYLESHEET_PREVIEW;
-import static com.keenwrite.StatusBarNotifier.clue;
-import static com.keenwrite.util.ProtocolResolver.getProtocol;
-import static java.awt.Desktop.Action.BROWSE;
-import static java.awt.Desktop.getDesktop;
import static java.lang.Math.max;
import static java.lang.String.format;
+import static javafx.scene.CacheHint.SPEED;
import static javax.swing.SwingUtilities.invokeLater;
-import static org.xhtmlrenderer.swing.ImageResourceLoader.NO_OP_REPAINT_LISTENER;
/**
- * Responsible for rendering an HTML document.
+ * Responsible for parsing an HTML document.
*/
public final class HtmlPreview extends SwingNode {
- /**
- * Suppresses scrolling to the top on every key press.
- */
- private static class HtmlPanel extends XHTMLPanel {
- @Override
- public void resetScrollPosition() {
- }
- }
-
- /**
- * Suppresses scroll attempts until after the document has loaded.
- */
- private static final class DocumentEventHandler extends DocumentAdapter {
- private final BooleanProperty mReadyProperty = new SimpleBooleanProperty();
-
- public BooleanProperty readyProperty() {
- return mReadyProperty;
- }
-
- @Override
- public void documentStarted() {
- mReadyProperty.setValue( Boolean.FALSE );
- }
-
- @Override
- public void documentLoaded() {
- mReadyProperty.setValue( Boolean.TRUE );
- }
- }
-
- /**
- * Ensure that images are constrained to the panel width upon resizing.
- */
- private final class ResizeListener extends ComponentAdapter {
- @Override
- public void componentResized( final ComponentEvent e ) {
- setWidth( e );
- }
-
- @Override
- public void componentShown( final ComponentEvent e ) {
- setWidth( e );
- }
-
- /**
- * Sets the width of the {@link HtmlPreview} so that images can be
- * scaled to fit. The scale factor is adjusted a bit below the full width
- * to prevent the horizontal scrollbar from appearing.
- *
- * @param event The component that defines the image scaling width.
- */
- private void setWidth( final ComponentEvent event ) {
- final int width = (int) (event.getComponent().getWidth() * .95);
- HtmlPreview.this.mImageLoader.widthProperty().set( width );
- }
- }
-
- /**
- * Responsible for opening hyperlinks. External hyperlinks are opened in
- * the system's default browser; local file system links are opened in the
- * editor.
- */
- private static class HyperlinkListener extends LinkListener {
- @Override
- public void linkClicked( final BasicPanel panel, final String link ) {
- try {
- switch( getProtocol( link ) ) {
- case HTTP:
- final var desktop = getDesktop();
-
- if( desktop.isSupported( BROWSE ) ) {
- desktop.browse( new URI( link ) );
- }
- break;
- case FILE:
- // TODO: #88 -- publish a message to the event bus.
- break;
- }
- } catch( final Exception ex ) {
- clue( ex );
- }
- }
- }
/**
*/
private static final int HTML_PREFIX_LENGTH = HTML_HEAD_OPEN.length();
-
- private static final W3CDom W3C_DOM = new W3CDom();
- private static final XhtmlNamespaceHandler NS_HANDLER =
- new XhtmlNamespaceHandler();
/**
* The buffer is reused so that previous memory allocations need not repeat.
*/
private final StringBuilder mHtmlDocument = new StringBuilder( 65536 );
- private final HtmlPanel mHtmlRenderer = new HtmlPanel();
- private final JScrollPane mScrollPane = new JScrollPane( mHtmlRenderer );
- private final CustomImageLoader mImageLoader = new CustomImageLoader();
+ private HtmlPanel mView;
private String mBaseUriPath = "";
final var factory = new ChainedReplacedElementFactory();
factory.addFactory( new SvgReplacedElementFactory() );
- factory.addFactory( new SwingReplacedElementFactory(
- NO_OP_REPAINT_LISTENER, mImageLoader ) );
-
- final var context = getSharedContext();
- final var textRenderer = context.getTextRenderer();
- context.setReplacedElementFactory( factory );
- textRenderer.setSmoothingThreshold( 0 );
+ factory.addFactory( new SwingReplacedElementFactory() );
- setContent( mScrollPane );
- mHtmlRenderer.addDocumentListener( new DocumentEventHandler() );
- mHtmlRenderer.addComponentListener( new ResizeListener() );
+ invokeLater( () -> {
+ mView = new HtmlPanel();
+ final var scrollPane = new JScrollPane( mView );
- // The default mouse click listener attempts navigation within the
- // preview panel. We want to usurp that behaviour to open the link in
- // a platform-specific browser.
- for( final var listener : mHtmlRenderer.getMouseTrackingListeners() ) {
- if( !(listener instanceof HoverListener) ) {
- mHtmlRenderer.removeMouseTrackingListener( (FSMouseListener) listener );
- }
- }
+ setContent( scrollPane );
+ setCache( true );
+ setCacheHint( SPEED );
- mHtmlRenderer.addMouseTrackingListener( new HyperlinkListener() );
+ final var context = mView.getSharedContext();
+ final var textRenderer = context.getTextRenderer();
+ context.setReplacedElementFactory( factory );
+ textRenderer.setSmoothingThreshold( 0 );
+ } );
}
/**
* Updates the internal HTML source, loads it into the preview pane, then
* scrolls to the caret position.
*
* @param html The new HTML document to display.
*/
public void process( final String html ) {
- final var docJsoup = Jsoup.parse( decorate( html ) );
- final var docW3c = W3C_DOM.fromJsoup( docJsoup );
-
// Access to a Swing component must occur from the Event Dispatch
// Thread (EDT) according to Swing threading restrictions.
- invokeLater(
- () -> mHtmlRenderer.setDocument( docW3c, getBaseUri(), NS_HANDLER )
- );
+ invokeLater( () -> mView.render( decorate( html ), getBaseUri() ) );
}
*/
public void scrollTo( final String id ) {
- scrollTo( getBoxById( id ) );
+ scrollTo( mView.getBoxById( id ) );
repaintScrollPane();
}
private void scrollTo( final Point point ) {
- mHtmlRenderer.scrollTo( point );
- }
-
- private Box getBoxById( final String id ) {
- return getSharedContext().getBoxById( id );
+ mView.scrollTo( point );
}
private String decorate( final String html ) {
// Trim the HTML back to only the prefix.
mHtmlDocument.setLength( HTML_PREFIX_LENGTH );
// Write the HTML body element followed by closing tags.
- return mHtmlDocument.append( HTML_HEAD_OPEN )
- .append( mBaseUriHtml )
+ return mHtmlDocument.append( mBaseUriHtml )
.append( HTML_HEAD_CLOSE )
.append( html )
private Point createPoint( final Box box ) {
assert box != null;
-
- int x = box.getAbsX();
// Scroll back up by half the height of the scroll bar to keep the typing
// area within the view port. Otherwise the view port will have jumped too
// high up and the most recently typed letters won't be visible.
- int y = max(
- box.getAbsY() - (mScrollPane.getVerticalScrollBar().getHeight() / 2),
- 0 );
+ int y = max( box.getAbsY() - getVerticalScrollBarHeight() / 2, 0 );
+ int x = box.getAbsX();
if( !box.getStyle().isInline() ) {
- final var margin = box.getMargin( mHtmlRenderer.getLayoutContext() );
- x += margin.left();
+ final var margin = box.getMargin( mView.getLayoutContext() );
y += margin.top();
+ x += margin.left();
}
private JScrollPane getScrollPane() {
- return mScrollPane;
+ return (JScrollPane) getContent();
}
- private SharedContext getSharedContext() {
- return mHtmlRenderer.getSharedContext();
+ private int getVerticalScrollBarHeight() {
+ return getScrollPane().getVerticalScrollBar().getHeight();
}
}
src/main/java/com/keenwrite/preview/SvgReplacedElementFactory.java
final var uri = new URI( baseUri ).getPath();
final var path = Paths.get( uri, src );
- System.out.println( "SVG PATH: " + path );
image = getCachedImage(