การแสดงผล SwingNode ใน JavaFX บน Windows ไม่ชัดเจน
ภาพรวม
การใช้ FlyingSaucer ภายในแอปพลิเคชัน JavaFX เพื่อหลีกเลี่ยง WebView ด้วยเหตุผลหลายประการ:
- ไม่ได้ให้การเข้าถึง API โดยตรงไปยังแถบเลื่อนสำหรับพฤติกรรมซิงโครนัส
- รวม JavaScript ซึ่งขยายขนาดใหญ่สำหรับกรณีการใช้งานของฉัน และ
- ไม่สามารถทำงานบน Windows ได้
FlyingSaucer ใช้ Swing ซึ่งต้องใช้การห่อXHTMLPanel
(คลาสย่อยของJPanel
) SwingNode
เพื่อใช้ร่วมกับ JavaFX ทุกอย่างใช้งานได้ดีแอปพลิเคชันแสดงผล Markdown แบบเรียลไทม์และตอบสนอง นี่คือวิดีโอสาธิตของแอปพลิเคชันที่ทำงานบน Linux
ปัญหา
การแสดงข้อความบน Windows พร่ามัว เมื่อทำงานใน a JFrame
ไม่ได้ถูกห่อหุ้มด้วย a 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
ทางฝั่ง Java มีคนแนะนำให้เรียกใช้แอปพลิเคชันโดยใช้:
-Dawt.useSystemAAFontSettings=off
-Dswing.aatext=false
ไม่มีคำแนะนำในการแสดงข้อความใดช่วยได้
การตั้งค่าเนื้อหาSwingNode
ภายในSwingUtilities.invokeLater
ไม่มีผลใด ๆ
JavaFX
มีคนอื่นกล่าวว่าการปิดการแคชช่วยได้ แต่สำหรับ JavaFX ScrollPane
ไม่ใช่หนึ่งในไฟล์SwingNode
. มันไม่ได้ผล
สิ่งที่JScrollPane
บรรจุอยู่SwingNode
มีการจัดตำแหน่ง X และการจัดตำแหน่ง Y ตั้งค่าเป็น 0.5 และ 0.5 ตามลำดับ มั่นใจครึ่งพิกเซลชดเชยแนะนำอื่น ๆ ฉันนึกไม่ถึงว่าการตั้งค่าการScene
ใช้งานStrokeType.INSIDE
จะสร้างความแตกต่างได้แม้ว่าฉันจะลองใช้ความกว้างของเส้นขีดเป็น 1 แต่ก็ไม่มีประโยชน์
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 เท่านั้นแทนที่จะแสดงผลจาก FlyingSaucer โดย JavaFX ภายในไฟล์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
การใช้OpenJDK ของBellsoftกับ JavaFX ที่แถมมา จากความรู้ของฉัน OpenJDK ได้รับการสนับสนุน Freetype มาระยะหนึ่งแล้ว นอกจากนี้แบบอักษรยังดูดีบน Linux ดังนั้นจึงอาจไม่ใช่ JDK
หน้าจอ
ข้อมูลจำเพาะของหน้าจอต่อไปนี้แสดงปัญหา แต่คนอื่น ๆ (ดูบนจอภาพและความละเอียดที่แตกต่างกันอย่างไม่ต้องสงสัย) ได้กล่าวถึงปัญหานี้
- 15.6 นิ้ว 4: 3 HD (1366x768)
- Full HD (1920x1080)
- ไฟพื้นหลัง LED มุมมองกว้าง
- อัสซุส n56v
คำถาม
เหตุใด FlyingSaucer XHTMLPanel
เมื่อห่อภายในจึงไม่ชัดเจนSwingNode
บน Windows แต่การแสดงผลแบบเดียวกันXHTMLPanel
ในการJFrame
ทำงานในแอปพลิเคชัน JavaFX เดียวกันจึงดูคมชัด ปัญหาจะแก้ไขได้อย่างไร?
SplitPane
ปัญหาที่เกี่ยวข้องกับการ
คำตอบ
มีตัวเลือกสองสามอย่างที่คุณอาจลองแม้ว่าฉันต้องยอมรับว่าฉันไม่รู้จัก FlyingSaucer และ API
FlyingSaucer มีตัวแสดงผลที่แตกต่างกัน ดังนั้นจึงอาจเป็นไปได้ที่จะหลีกเลี่ยงการแสดงผล Swing / AWT อย่างสมบูรณ์โดยใช้ไลบรารีนี้แทนเพื่อทำการเรนเดอร์ทั้งหมดโดยตรงใน JavaFXhttps://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 ถูกรวมเข้ากับ a อย่างแน่นหนาJScrollPane
ซึ่งป้องกันความเป็นไปได้ในการบังคับให้ FlyingSaucer แสดงผลบนพาเนลที่ใช้ JavaFX
เป็นไปได้ก็คือการไปในทิศทางตรงข้าม: สร้างโปรแกรมประยุกต์สวิงและฝังตัวควบคุม JavaFX เช่นใช้JFXPanel ; อย่างไรก็ตามดูเหมือนว่าจะระมัดระวังมากขึ้นในการยอมรับพฤติกรรมที่ไม่ชัดเจนจนกว่าข้อบกพร่องจะถูกทำลาย