Windows의 JavaFX에서 SwingNode의 흐릿한 렌더링
개요
JavaFX 응용 프로그램 내에서 FlyingSaucer를 사용하여 다양한 이유로 WebView를 방지합니다.
- 동기 동작을 위해 스크롤바에 직접 API 액세스를 제공하지 않습니다.
- 내 사용 사례에 큰 부풀려진 JavaScript를 번들로 제공합니다. 과
- Windows에서 실행하지 못했습니다.
FlyingSaucer는 Swing을 사용하는데, 이는 JavaFX와 함께 사용하기 위해 XHTMLPanel
(의 서브 클래스 JPanel
)를 래핑해야합니다 SwingNode
. 모든 것이 훌륭하게 작동하고 응용 프로그램은 Markdown을 실시간으로 렌더링하며 반응합니다. 다음 은 Linux에서 실행되는 애플리케이션 의 데모 비디오 입니다.
문제
Windows의 텍스트 렌더링이 흐릿합니다. 로 JFrame
래핑되지 않고 SwingNode
비디오에 표시된 동일한 응용 프로그램의 일부인에서 실행하는 경우 텍스트의 품질이 완벽합니다. 화면 캡처는 애플리케이션의 기본 창 (하단)을 보여 주며, 여기에는 SwingNode
앞서 언급 한 JFrame
(상단) 과 함께 포함됩니다 . "l"또는 "k"의 직선 가장자리를 확대하여 하나는 날카 롭고 다른 하나는 흐릿한 이유를 확인해야 할 수 있습니다.
이것은 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
는 좀 더 복잡 하지만 ( 전체 목록 참조 ) 다음은 관련 스 니펫입니다 ( 업데이트 중 원하지 않는 자동 스크롤을 억제하기 위해 HTMLPanel
에서 확장 됨 XHTMLPanel
).
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();
} );
} );
}
}
최소한의 작업 예제에서 흐릿한 캡처; 확대하면 글자 가장자리가 뚜렷한 대비가 아닌 앤티 앨리어싱이 심하게 나타납니다.
를 사용 JLabel
하면 동일한 퍼지 렌더링이 나타납니다.
final var label = new JLabel( "TEST" );
label.setFont( label.getFont().deriveFont( Font.BOLD, 128f ) );
final var swingNode = new SwingNode();
swingNode.setContent( label );
시도
블러를 제거하기 위해 시도한 대부분의 방법은 다음과 같습니다.
자바
Java 측에서는 누군가 다음을 사용하여 애플리케이션을 실행하도록 제안 했습니다.
-Dawt.useSystemAAFontSettings=off
-Dswing.aatext=false
텍스트 렌더링 힌트 가 도움이 되지 않았습니다 .
SwingNode
내부 의 내용을 SwingUtilities.invokeLater
설정해도 효과가 없습니다.
JavaFX
다른 사람이 언급 떨어져 캐싱을 돌리면 도움이되었다고하지만은 자바 FX를위한이었다 ScrollPane
내에서하지 하나 SwingNode
. 작동하지 않았습니다.
에 JScrollPane
포함 된 SwingNode
에는 정렬 X 및 정렬 Y가 각각 0.5 및 0.5로 설정됩니다. 다른 곳 에서는 절반 픽셀 오프셋을 확보하는 것이 좋습니다 . 내가 설정하는 것을 상상할 수 없다 Scene
사용하는 것은 StrokeType.INSIDE
내가 아무 소용이 하나의 획 너비를 사용해보십시오 않았지만, 어떤 차이를 만들 것입니다.
FlyingSaucer
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 ...
xr.image.
설정은 오히려 FlyingSaucer의 출력이 내 자바 FX가 렌더링되는 방법을보다 FlyingSaucer에 의해 렌더링 된 이미지에 영향을 미칠 SwingNode
.
CSS는 글꼴 크기에 포인트 를 사용 합니다.
연구
- https://stackoverflow.com/a/26227562/59087 -몇 가지 해결책이 도움이 될 것 같습니다.
- https://bugs.openjdk.java.net/browse/JDK-8089499-사용
SwingNode
하고 있기 때문에 적용되지 않는 것 같습니다JScrollPane
. - https://stackoverflow.com/a/24124020/59087 -사용중인 XML 씬 빌더가 없기 때문에 관련성이 없을 수 있습니다.
- https://www.cs.mcgill.ca/media/tech_reports/42_Lessons_Learned_in_Migrating_from_Swing_to_JavaFX_LzXl9Xv.pdf -8 페이지에서는 0.5 픽셀 이동에 대해 설명합니다.
- https://dlsc.com/2014/07/17/javafx-tip-9-do-not-mix-swing-javafx/ -JavaFX와 Swing을 혼합하지 않을 것을 제안하지만 순수한 Swing으로 이동하는 것은 옵션이 아닙니다. 다른 언어로 앱을 더 빨리 다시 작성하겠습니다.
OpenJDK / JavaFX에 대한 버그로 허용 :
- https://bugs.openjdk.java.net/browse/JDK-8252255
JDK 및 JRE
JavaFX가 번들로 제공되는 Bellsoft 의 OpenJDK 사용 . 내가 아는 한 OpenJDK는 한동안 Freetype을 지원했습니다. 또한 글꼴은 Linux에서 멋지게 보이므로 JDK가 아닐 수도 있습니다.
화면
다음 화면 사양은 문제를 나타내지 만 다른 사람들 (의심 할 여지없이 다른 모니터와 해상도에서 보는)이 문제를 언급했습니다.
- 15.6 형 4 : 3 HD (1366x768)
- 풀 HD (1920x1080)
- 광 시야각 LED 백라이트
- ASUS n56v
질문
왜 FlyingSaucer의는 않습니다 XHTMLPanel
내에서 포장 할 때 SwingNode
Windows에서이 될 흐릿 아직 같은 표시 XHTMLPanel
A의 JFrame
응용 프로그램이 나타납니다 선명 같은 자바 FX에서 실행? 문제를 어떻게 해결할 수 있습니까?
문제는 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'> </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에 대한 버그로 승인되었습니다.
- https://bugs.openjdk.java.net/browse/JDK-8252255
Mipa의 제안 중 어느 것도 실제로 작동하지 않습니다. FlyingSaucer는와 밀접하게 통합되어있어 FlyingSaucer가 JScrollPane
JavaFX 기반 패널에 강제로 렌더링 할 가능성을 배제합니다.
또 다른 가능성은 반대 방향으로가는 것입니다. Swing 애플리케이션을 만들고 JFXPanel 사용과 같은 JavaFX 컨트롤을 포함합니다 . 그러나 버그가 발생하기 전까지는 흐릿한 동작을 받아들이는 것이 더 현명 해 보일 것입니다.