2

我知道如何使用 Swing 有限的内置 HTML 支持(参见http://www.java.net/node/680674)将 HTML 绘制到 Graphics2D 对象,但我需要更好的渲染。最重要的是,对于这个特殊的化学图表绘图应用程序,Swing 的 HTML 支持不会扩展到嵌套的子/上标。更好的 CSS 支持也会很好。我不需要图像嵌入或交互功能,例如 Javascript 或热链接。

HTML 文本被缩放和旋转,然后绘制成可能包含其他文本和图形的图表。Graphics2D 目标可以是屏幕、打印机或(通过 iText)PDF 文件。我怀疑任何涉及通过 BufferedImage 等进行转换的解决方案在生成具有出版质量的 PDF 文件时是否足够紧凑。

我(可能不正确)的印象是 JavaFX 2.0 还没有解决这个问题,尽管它最终可能会。(如果早期版本可以做到这一点,那可能是一个解决方案。)将整个应用程序从 Swing 重写为 JavaFX 是不现实的。

这个应用程序是免费和开源的,所以它使用的任何工具都可能需要自由分发。否则,我相信 JWebEngine 可能符合要求。

任何帮助,将不胜感激。

4

2 回答 2

1

您可以使用 JavaFX WebView节点 - 它具有非常好的 HTML 标记和 css 支持。您可以使用 JavaFX 原语旋转缩放WebView 节点。 可以在 WebView 中使用MathJax来获得高质量的方程渲染(如果仅纯 html 和 css 不能为您完成这项工作)。使用JavaFX 2.2,您可以拍摄 WebView 节点的快照并将其呈现为 JavaFX 图像。您可以使用 JavaFX 2.2 SwingFXUtils 将该 JavaFX 图像转换为 awt BufferedImage 并使用ImageIO将其写入多种格式的文件。

这是将饼图节点渲染为 png 的示例。根据 html 的复杂性,有时高质量的图像会很好地压缩为(例如)png 文件。在图表示例中,带有文本和彩色渐变的 2000x2000 像素饼图保存为 168kb 的 png 文件。

无需将整个应用程序从 Swing 重写为 JavaFX,因为 JavaFX 包含用于将 JavaFX 应用程序嵌入现有 Swing 应用程序的JFXPanel 。节点快照步骤甚至不需要将节点呈现到屏幕上(这都可以通过内存缓冲区完成)——尽管 JavaFX 系统可能需要首先在 JavaFX 应用程序或 JFXPanel 中启动和启动。

以上所有内容可能会或可能不会最终给您想要的结果,但这似乎是一个很有前途的检查途径。

更新

我对此进行了一些测试,尽管我可以按照本文中的说明对屏幕上显示的 WebView 进行快照,但由于 JavaFX 2.2 的一些限制,我无法对作为屏幕外场景的一部分显示的 WebView 进行快照。这意味着此答案中的信息是准确的,但仅适用于可以在屏幕上的 WebView 中显示的 HTML 部分;例如,该技术目前不适用于像素大小超过屏幕像素大小的大型文档。有关一些示例代码,请参阅https://forums.oracle.com/forums/thread.jspa?threadID=2456191

于 2012-06-16T00:40:36.353 回答
0

经过大量搜索和拼凑后,我发现上面更新 oracle 论坛链接中的示例的唯一问题是 webview 的大小是固定的,并且我的css 在 html 中使用(而不是在 JavaFX 中)需要。

overflow-x: hidden;
overflow-y: hidden;

隐藏最后一个滚动条。

所以我想出了以下快照方法(带有动画的应用程序作为您的应用程序的示例):

package application;

import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import javafx.animation.Animation;
import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.web.WebView;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;

public class WebViewSnapshot extends Application {

    BorderPane rootPane;
    TranslateTransition animation;

    @Override
    public void start(Stage primaryStage) {

        Rectangle rect = new Rectangle(50, 50, 50, 50);
        rect.setFill(Color.CORAL);

        animation = createAnimation(rect);

        Button snapshotButton = new Button("Take snapshot");

        Pane pane = new Pane(rect);
        pane.setMinSize(600, 150);

        rootPane = new BorderPane(pane, null, null, snapshotButton, new Label("This is the main scene"));

        snapshotButton.setOnAction(e -> {
            // html file being shown in webview
            File htmlFile = new File ("generated/template.html");
            // the resulting snapshot png file
            File aboutFile = new File ("generated/about.png");
            generate(htmlFile, aboutFile, 1280, 720);
        });

        BorderPane.setAlignment(snapshotButton, Pos.CENTER);
        BorderPane.setMargin(snapshotButton, new Insets(5));
        Scene scene = new Scene(rootPane);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private TranslateTransition createAnimation(Rectangle rect) {
        TranslateTransition animation = new TranslateTransition(Duration.seconds(1), rect);
        animation.setByX(400);
        animation.setCycleCount(Animation.INDEFINITE);
        animation.setAutoReverse(true);
        animation.play();
        return animation;
    }

    public void generate(File htmlFile, File outputFile, double width, double height) {
        animation.pause();
        // rootPane is the root of original scene in an FXML controller you get this when you assign it an id
        rootPane.setEffect(new GaussianBlur());
        Stage primaryStage = (Stage)rootPane.getScene().getWindow();
        // creating separate webview holding same html content as in original scene
        WebView webView = new WebView();
        // with the size I want the snapshot
        webView.setPrefSize(width, height);
        AnchorPane snapshotRoot = new AnchorPane(webView);
        webView.getEngine().load(htmlFile.toURI().toString());
        Stage popupStage = new Stage(StageStyle.TRANSPARENT);
        popupStage.initOwner(primaryStage);
        popupStage.initModality(Modality.APPLICATION_MODAL);
        // this popup doesn't really show anything size = 1x1, it just holds the snapshot-webview
        popupStage.setScene(new Scene(snapshotRoot, 1, 1));
        // pausing to make sure the webview/picture is completely rendered
        PauseTransition pt = new PauseTransition(Duration.seconds(2));
        pt.setOnFinished(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent event) {
                WritableImage image = webView.snapshot(null, null);
                // writing a png to outputFile
                // writing a JPG like this will result in a pink JPG, see other posts
                // if somebody can scrape me simple code to convert it ARGB to RGB or something
                String format = "png";
                try {
                    ImageIO.write(SwingFXUtils.fromFXImage(image, null), format, outputFile);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } finally {
                    rootPane.setEffect(null);
                    popupStage.hide();
                    animation.play();
                }
            }
        });
        // pausing, after pause onFinished event will take + write snapshot
        pt.play();
        // GO!
        popupStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
于 2019-09-22T08:46:22.490 回答