Windows上JavaFX中SwingNode的模糊渲染

问题描述

概述

出于各种原因,在JavaFX应用程序中使用FlyingSaucer以避免WebView:

  • 不提供对滚动条的直接API访问以实现同步行为;
  • 捆绑了JavaScript,这对我的用例来说是一个巨大的负担;和
  • 无法在Windows上运行。

FlyingSaucer使用Swing,它要求将其XHTMLPanelJPanel的子类)包装在SwingNode中,以与JavaFX一起使用。一切正常,该应用程序实时呈现Markdown并具有响应能力。这是在Linux上运行的应用程序的demo video

问题

Windows上的文本呈现模糊。如果在JFrame中运行,而不是被SwingNode包裹,但仍然是视频中显示的同一应用程序的一部分,则文本的质量是完美的。屏幕截图显示了应用程序的主窗口(底部),其中包括SwingNode和前面提到的JFrame(顶部)。您可能需要放大“ l”或“ k”的直边,以查看为什么一个尖锐而另一个模糊:

Blurry SwingNode

这仅在Windows上发生。通过系统的字体预览程序在Windows上查看字体时,使用LCD颜色对字体进行抗锯齿处理。该应用程序使用灰度。我怀疑如果有一种方法可以强制渲染将颜色用于抗锯齿而不是灰度,则问题可能会消失。再说一次,当在自己的JFrame中运行时,没有问题,并且不使用LCD颜色。

代码

这是JFrame的具有完美渲染效果代码

  private static class Flawless {
    private final XHTMLPanel panel = new XHTMLPanel();
    private final JFrame frame = new JFrame( "Single Page Demo" );

    private Flawless() {
      frame.getContentPane().add( new JScrollPane( panel ) );
      frame.pack();
      frame.setSize( 1024,768 );
    }

    private void update( final org.w3c.dom.Document html ) {
      frame.setVisible( true );

      try {
        panel.setDocument( html );
      } catch( Exception ignored ) {
      }
    }
  }

模糊的SwingNode代码要复杂得多(请参阅full listing),但是这里有一些相关的摘要(请注意,HTMLPanelXHTMLPanel扩展到在更新过程中取消一些不必要的自动滚动):

private final HTMLPanel mHtmlRenderer = new HTMLPanel();
private final SwingNode mSwingNode = new SwingNode();
private final JScrollPane mScrollPane = new JScrollPane( mHtmlRenderer );

// ...

final var context = getSharedContext();
final var textRenderer = context.getTextRenderer();
textRenderer.setSmoothingThreshold( 0 );

mSwingNode.setContent( mScrollPane );

// ...

// The "preview pane" contains the SwingNode.
final SplitPane splitPane = new SplitPane(
    getDeFinitionPane().getNode(),getFileEditorPane().getNode(),getPreviewPane().getNode() );

最小工作示例

这是一个非常简单的独立示例:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.stage.Stage;
import org.jsoup.Jsoup;
import org.jsoup.helper.W3CDom;
import org.xhtmlrenderer.simple.XHTMLPanel;

import javax.swing.*;

import static javax.swing.SwingUtilities.invokelater;
import static javax.swing.UIManager.getSystemLookAndFeelClassName;
import static javax.swing.UIManager.setLookAndFeel;

public class FlyingSourceTest extends Application {
  private final static String HTML = "<!DOCTYPE html><html><head" +
      "><style type='text/css'>body{font-family:serif; background-color: " +
      "#fff; color:#454545;}</style></head><body><p style=\"font-size: " +
      "300px\">TEST</p></body></html>";

  public static void main( String[] args ) {
    Application.launch( args );
  }

  @Override
  public void start( Stage primaryStage ) {
    invokelater( () -> {
      try {
        setLookAndFeel( getSystemLookAndFeelClassName() );
      } catch( Exception ignored ) {
      }

      primaryStage.setTitle( "Hello World!" );

      final var renderer = new XHTMLPanel();
      renderer.getSharedContext().getTextRenderer().setSmoothingThreshold( 0 );
      renderer.setDocument( new W3CDom().fromJsoup( Jsoup.parse( HTML ) ) );

      final var swingNode = new SwingNode();
      swingNode.setContent( new JScrollPane( renderer ) );

      final var root = new SplitPane( swingNode,swingNode );

      // ----------
      // Here be dragons? Using a StackPane,instead of a SplitPane,works.
      // ----------
      //StackPane root = new StackPane();
      //root.getChildren().add( mSwingNode );

      Platform.runLater( () -> {
        primaryStage.setScene( new Scene( root,300,250 ) );
        primaryStage.show();
      } );
    } );
  }
}

最小工作示例中的模糊捕获; 放大显示字母边缘高度抗锯齿,而不是鲜明的对比度:

Blurry

使用JLabel也会表现出相同的模糊渲染:

  final var label = new JLabel( "TEST" );
  label.setFont( label.getFont().deriveFont( Font.BOLD,128f ) );

  final var swingNode = new SwingNode();
  swingNode.setContent( label );

尝试

以下是我尝试消除模糊的大多数方法

Java

在Java端,someone suggested使用以下命令运行应用程序:

-Dawt.useSystemAAFontSettings=off
-Dswing.aatext=false

rendering hints文本都没有帮助。

SwingNode中设置SwingUtilities.invokelater内容无效。

JavaFX

关闭缓存的其他人mentioned很有帮助,但这是针对JavaFX ScrollPane的,而不是SwingNode中的一个。没用。

JScrollPane包含的SwingNode的对齐方式X和对齐方式Y分别设置为0.5和0.5。确保半像素偏移为recommended elsewhere。我无法想象将Scene设置为使用strokeType.INSIDE会有什么不同,尽管我确实尝试使用1的笔划宽度无济于事。

FlyingSaucer

FlyingSaucer的号码为configuration options。各种设置组合包括

java -Dxr.text.fractional-font-metrics=true \
     -Dxr.text.aa-smoothing-level=0 \
     -Dxr.image.render-quality=java.awt.RenderingHints.VALUE_INTERPOLATION_BICUBIC
     -Dxr.image.scale=HIGH \
     -Dxr.text.aa-rendering-hint=VALUE_TEXT_ANTIALIAS_GASP -jar ...

xr.image.设置仅影响FlyingSaucer渲染的图像,而不影响JavaFX在SwingNode中渲染FlyingSaucer的输出

CSS使用作为字体大小。

研究

被接受为针对OpenJDK / JavaFX的错误

JDK和JRE

使用捆绑了JavaFX的Bellsoft的OpenJDK。据我所知,OpenJDK已经有一段时间支持Freetype了。另外,该字体在Linux上看起来很棒,因此可能不是JDK。

屏幕

以下屏幕规格显示了问题,但是其他人(无疑是在不同的显示器和分辨率下查看)已经提到了该问题。

  • 15.6英寸4:3 HD(1366x768)
  • 全高清(1920x1080)
  • 宽视角LED背光灯
  • ASUS n56v

问题

为什么将FlyingSaucer的XHTMLPanel包裹在SwingNode中在Windows上变得模糊,而在同一JavaFX应用程序中运行的XHTMLPanel显示相同的JFrame却显得清晰?如何解决该问题?

问题涉及SplitPane

解决方法

尽管我不得不承认我不知道FlyingSaucer及其API,但您可以尝试一些选项。

FlyingSaucer具有不同的渲染器。因此,为了直接在JavaFX中进行所有渲染,有可能通过使用此库来完全避免Swing / AWT渲染。 https://github.com/jfree/fxgraphics2d

另一种可能性是让FlyingSaucer渲染成图像,并可以通过直接缓冲区非常有效地将其显示在JavaFX中。在这里查看我的存储库中的AWTImage代码:https://github.com/mipastgt/JFXToolsAndDemos

,

我自己无法重现该问题,因此您使用的JDK / JavaFX版本的组合中可能存在一些问题。也有可能仅在显示器尺寸和屏幕缩放的特定组合下出现此问题。

我的设置如下:

  • JavaFX 14
  • OpenJDK 14
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.jsoup.Jsoup;
import org.jsoup.helper.W3CDom;
import org.jsoup.nodes.Document;
import org.xhtmlrenderer.simple.XHTMLPanel;

import javax.swing.*;

public class FlyingSourceTest extends Application {

    private final static String HTML_PREFIX = "<!DOCTYPE html>\n"
            + "<html>\n"
            + "<body>\n";
    private static final String HTML_CONTENT =
            "<p style=\"font-size:500px\">TEST</p>";
    private final static String HTML_SUFFIX = "<p style='height=2em'>&nbsp;</p></body></html>";

    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        SwingUtilities.invokeLater(() -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
                e.printStackTrace();
            }
            primaryStage.setTitle("Hello World!");

            XHTMLPanel mHtmlRenderer = new XHTMLPanel();
            mHtmlRenderer.getSharedContext().getTextRenderer().setSmoothingThreshold(0);
            SwingNode mSwingNode = new SwingNode();
            JScrollPane mScrollPane = new JScrollPane(mHtmlRenderer);

            String htmlContent = HTML_PREFIX + HTML_CONTENT + HTML_SUFFIX;
            Document jsoupDoc = Jsoup.parse(htmlContent);
            org.w3c.dom.Document w3cDoc = new W3CDom().fromJsoup(jsoupDoc);

            mHtmlRenderer.setDocument(w3cDoc);

            mSwingNode.setContent(mScrollPane);
//            AnchorPane anchorPane = new AnchorPane();
//            anchorPane.getChildren().add(mSwingNode);
//            AnchorPane.setTopAnchor(mSwingNode,0.5);
//            AnchorPane.setLeftAnchor(mSwingNode,0.5);
//            mSwingNode.setTranslateX(0.5);
//            mSwingNode.setTranslateY(0.5);

            StackPane root = new StackPane();
            root.getChildren().add(mSwingNode);
            Platform.runLater(() -> {
                primaryStage.setScene(new Scene(root,300,250));
                primaryStage.show();
            });
        });
    }
}
,

该问题已被接受为针对OpenJDK / JavaFX的错误:

Mipa的任何建议在实践中都行不通。 FlyingSaucer与JScrollPane紧密集成,从而避免了强制将FlyingSaucer渲染到基于JavaFX的面板上的可能性。

另一种可能的方向相反:创建一个Swing应用程序并嵌入JavaFX控件,例如使用JFXPanel;但是,在错误消除之前,接受模糊的行为似乎更为谨慎。