为Graphics2D绘制高质量的HTML(类似于drawString())

时间:2012-06-14 21:03:24

标签: java html graphics2d

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

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

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

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

任何帮助都将不胜感激。

2 个答案:

答案 0 :(得分:1)

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

这是example of rendering a piechart node to a png。根据html的复杂程度,有时高质量的图像会很好地压缩到(例如)png文件。在pichart示例中,带有文本和彩色渐变的2000x2000像素饼图保存为168kb的png文件。

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

以上所有可能会或可能不会最终为您提供您想要的结果,但这似乎是一个有前景的途径。

<强>更新

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

答案 1 :(得分:0)

经过大量搜索和拼凑,我发现上面 Update oracle论坛链接中的示例的唯一问题是固定的Webview大小和需要在html中使用的CSS (在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);
    }
}