Windows 上 JavaFX 中 SwingNode 的模糊渲染

Dav*_*vis 12 java swing javafx flying-saucer

概述

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

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

FlyingSaucer 使用 Swing,这需要将其XHTMLPanel( 的子类JPanel)包装在 a 中SwingNode以与 JavaFX 一起使用。一切正常,应用程序实时呈现 Markdown,并且响应迅速。这是在 Linux 上运行的应用程序的演示视频

问题

Windows 上的文本渲染是模糊的。在 中运行时JFrame,没有被 包裹SwingNode,但仍是视频中显示的同一应用程序的一部分,文本质量完美无瑕。屏幕截图显示了应用程序的主窗口(底部),其中包括SwingNode前面提到的JFrame(顶部)。您可能需要放大“l”或“k”的直边才能看到为什么一个清晰而另一个模糊:

模糊的 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 ) {
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

模糊的代码SwingNode有点复杂(参见完整列表),但这里有一些相关的片段(注意,HTMLPanelXHTMLPanelonly扩展是为了在更新期间抑制一些不需要的自动滚动):

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() );
Run Code Online (Sandbox Code Playgroud)

最小工作示例

这是一个相当小的自包含示例:

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();
      } );
    } );
  }
}
Run Code Online (Sandbox Code Playgroud)

来自最小工作示例的模糊捕获;放大显示字母边缘被严重抗锯齿而不是鲜明的对比:

模糊

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

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

  final var swingNode = new SwingNode();
  swingNode.setContent( label );
Run Code Online (Sandbox Code Playgroud)

尝试

以下是我尝试消除模糊的大部分方法。

爪哇

在 Java 方面,有人建议使用以下方法运行应用程序:

-Dawt.useSystemAAFontSettings=off
-Dswing.aatext=false
Run Code Online (Sandbox Code Playgroud)

没有任何文本呈现提示有帮助。

设置的内容SwingNodeSwingUtilities.invokeLater没有任何影响。

JavaFX

其他人提到关闭缓存有帮助,但那是针对 JavaFX 的ScrollPane,而不是SwingNode. 它没有用。

JScrollPane包含在由SwingNode具有其对准X和对准是设定到分别为0.5和0.5。建议在其他地方确保半像素偏移。我无法想象设置Sceneto useStrokeType.INSIDE会有什么不同,尽管我确实尝试使用 1 的笔触宽度但无济于事。

飞碟

FlyingSaucer 有许多配置选项。各种设置组合包括:

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 ...
Run Code Online (Sandbox Code Playgroud)

这些xr.image.设置仅影响 FlyingSaucer 渲染的图像,而不影响 FlyingSaucer 的输出如何在SwingNode.

CSS 使用来表示字体大小。

研究

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

JDK 和 JRE

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

屏幕

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

  • 15.6 英寸 4:3 高清 (1366x768)
  • 全高清 (1920x1080)
  • 广视角 LED 背光
  • 华硕 n56v

为什么FlyingSaucer的XHTMLPanel时候内裹SwingNode在Windows上变得模糊,然而显示同样XHTMLPanelJFrame在同一个JavaFX应用程序都显得明快运行?如何解决问题?

问题涉及SplitPane.

Dav*_*vis 0

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

米帕的建议在实践中都行不通。FlyingSaucer 与 紧密集成JScrollPane,这排除了强制 FlyingSaucer 渲染到基于 JavaFX 的面板上的可能性。

另一种可能性是采取相反的方向:创建一个 Swing 应用程序并嵌入 JavaFX 控件,例如使用JFXPanel;然而,在 bug 被彻底解决之前,接受这种模糊的行为似乎更为谨慎。