diff --git a/pom.xml b/pom.xml
index ee638a4..6c21848 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,6 +26,11 @@
javafx-fxml
${javafx.version}
+
+ org.openjfx
+ javafx-swing
+ ${javafx.version}
+
@@ -34,6 +39,13 @@
2.10.1
+
+
+ com.itextpdf
+ itextpdf
+ 5.5.13.3
+
+
org.junit.jupiter
junit-jupiter-api
diff --git a/src/main/java/dev/bytevibe/hyperpoint/Controller.java b/src/main/java/dev/bytevibe/hyperpoint/Controller.java
index 21711d0..181737c 100644
--- a/src/main/java/dev/bytevibe/hyperpoint/Controller.java
+++ b/src/main/java/dev/bytevibe/hyperpoint/Controller.java
@@ -50,6 +50,10 @@ public class Controller implements Initializable {
private Button openSlideButton; // 添加打开幻灯片按钮
@FXML
private Button saveSlideButton; // 添加保存幻灯片按钮
+ @FXML
+ private Button exportPageButton; // 添加导出页面按钮
+ @FXML
+ private Button exportSlideButton; // 添加导出幻灯片按钮
private Slide currentSlide;
private DrawingCanvas drawingCanvas;
@@ -430,6 +434,86 @@ public class Controller implements Initializable {
}
}
+ /**
+ * 导出页面为图片
+ */
+ @FXML
+ public void onExportPage(ActionEvent actionEvent) {
+ if (drawingCanvas == null) {
+ showWarning("请先选择页面", "请先选择一个页面。");
+ return;
+ }
+
+ FileChooser fileChooser = new FileChooser();
+ fileChooser.setTitle("导出页面为图片");
+ fileChooser.getExtensionFilters().addAll(
+ new FileChooser.ExtensionFilter("PNG图片 (*.png)", "*.png"),
+ new FileChooser.ExtensionFilter("JPG图片 (*.jpg)", "*.jpg"),
+ new FileChooser.ExtensionFilter("所有文件", "*.*")
+ );
+
+ SlidePage currentPage = pageListView.getSelectionModel().getSelectedItem();
+ if (currentPage != null) {
+ fileChooser.setInitialFileName(currentPage.getTitle() + ".png");
+ }
+
+ stage = (Stage) exportPageButton.getScene().getWindow();
+ File file = fileChooser.showSaveDialog(stage);
+
+ if (file != null) {
+ try {
+ String format = file.getName().endsWith(".jpg") || file.getName().endsWith(".jpeg") ? "jpg" : "png";
+ ExportUtil.exportPageAsImage(drawingCanvas, file, format);
+
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle("成功");
+ alert.setHeaderText("页面已导出");
+ alert.setContentText("页面已导出到: " + file.getAbsolutePath());
+ alert.showAndWait();
+ } catch (Exception e) {
+ showWarning("错误", "无法导出页面: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * 导出幻灯片为PDF
+ */
+ @FXML
+ public void onExportSlide(ActionEvent actionEvent) {
+ if (currentSlide == null) {
+ showWarning("无幻灯片", "请先创建或打开一个幻灯片。");
+ return;
+ }
+
+ FileChooser fileChooser = new FileChooser();
+ fileChooser.setTitle("导出幻灯片为PDF");
+ fileChooser.getExtensionFilters().addAll(
+ new FileChooser.ExtensionFilter("PDF文件 (*.pdf)", "*.pdf"),
+ new FileChooser.ExtensionFilter("所有文件", "*.*")
+ );
+ fileChooser.setInitialFileName(currentSlide.getName() + ".pdf");
+
+ stage = (Stage) exportSlideButton.getScene().getWindow();
+ File file = fileChooser.showSaveDialog(stage);
+
+ if (file != null) {
+ try {
+ ExportUtil.exportSlideToPDF(currentSlide, file);
+
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle("成功");
+ alert.setHeaderText("幻灯片已导出为PDF");
+ alert.setContentText("PDF已导出到: " + file.getAbsolutePath());
+ alert.showAndWait();
+ } catch (Exception e) {
+ showWarning("错误", "无法导出PDF: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ }
+
/**
* 显示警告对话框
*/
diff --git a/src/main/java/dev/bytevibe/hyperpoint/ExportUtil.java b/src/main/java/dev/bytevibe/hyperpoint/ExportUtil.java
new file mode 100644
index 0000000..9a07036
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/ExportUtil.java
@@ -0,0 +1,208 @@
+package dev.bytevibe.hyperpoint;
+
+import javafx.scene.image.WritableImage;
+import javafx.scene.image.PixelReader;
+import javafx.scene.layout.Pane;
+import com.itextpdf.text.*;
+import com.itextpdf.text.pdf.PdfWriter;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * 导出工具类,用于导出页面为图片和导出幻灯片为PDF
+ */
+public class ExportUtil {
+
+ /**
+ * 导出页面为图片(PNG或JPG)
+ *
+ * @param pane 要导出的页面Pane
+ * @param file 输出文件
+ * @param format 图片格式 "png" 或 "jpg"
+ * @throws IOException 如果导出失败
+ */
+ public static void exportPageAsImage(Pane pane, File file, String format) throws IOException {
+ // 获取Pane的宽度和高度
+ double width = pane.getWidth();
+ double height = pane.getHeight();
+
+ // 如果宽度或高度无效,尝试使用布局宽度/高度
+ if (width <= 0 || height <= 0) {
+ width = pane.getLayoutBounds().getWidth();
+ height = pane.getLayoutBounds().getHeight();
+ }
+
+ // 如果仍然无效,使用固定的默认大小
+ if (width <= 0) {
+ width = 1024;
+ }
+ if (height <= 0) {
+ height = 768;
+ }
+
+ // 创建WritableImage用于捕获页面内容
+ WritableImage writableImage = new WritableImage((int) width, (int) height);
+ pane.snapshot(null, writableImage);
+
+ // 转换为BufferedImage
+ BufferedImage bufferedImage = convertFXImageToBufferedImage(writableImage);
+
+ // 写入文件
+ String formatName = format.equalsIgnoreCase("jpg") ? "jpg" : "png";
+ if (!ImageIO.write(bufferedImage, formatName, file)) {
+ throw new IOException("无法写入" + formatName + "文件");
+ }
+ }
+
+ /**
+ * 将JavaFX WritableImage转换为BufferedImage
+ */
+ private static BufferedImage convertFXImageToBufferedImage(WritableImage fxImage) {
+ int width = (int) fxImage.getWidth();
+ int height = (int) fxImage.getHeight();
+ BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+
+ PixelReader pixelReader = fxImage.getPixelReader();
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int argb = pixelReader.getArgb(x, y);
+ bufferedImage.setRGB(x, y, argb);
+ }
+ }
+
+ return bufferedImage;
+ }
+
+ /**
+ * 导出整个幻灯片为PDF
+ *
+ * @param slide 要导出的幻灯片
+ * @param file 输出PDF文件
+ * @throws Exception 如果导出失败
+ */
+ public static void exportSlideToPDF(Slide slide, File file) throws Exception {
+ if (slide == null || slide.getPages().isEmpty()) {
+ throw new IOException("幻灯片没有页面");
+ }
+
+ // 创建PDF文档
+ Document document = new Document(PageSize.A4);
+ PdfWriter.getInstance(document, new FileOutputStream(file));
+ document.open();
+
+ // 为每一页添加内容
+ for (SlidePage page : slide.getPages()) {
+ // 添加页面标题
+ Paragraph title = new Paragraph(page.getTitle(), new com.itextpdf.text.Font(
+ com.itextpdf.text.Font.FontFamily.HELVETICA, 16, com.itextpdf.text.Font.BOLD));
+ title.setSpacingAfter(10);
+ document.add(title);
+
+ // 添加页面内容(如果有的话)
+ if (page.getContent() != null && !page.getContent().isEmpty()) {
+ Paragraph content = new Paragraph(page.getContent(), new com.itextpdf.text.Font(
+ com.itextpdf.text.Font.FontFamily.HELVETICA, 12));
+ content.setSpacingAfter(10);
+ document.add(content);
+ }
+
+ // 添加对象列表
+ addObjectsToDocument(document, page.getPageContent());
+
+ // 每页之间添加分页符
+ document.newPage();
+ }
+
+ document.close();
+ }
+
+ /**
+ * 将页面内容的对象添加到PDF文档
+ */
+ private static void addObjectsToDocument(Document document, PageContent pageContent) throws DocumentException {
+ for (DrawableObject obj : pageContent.getDrawableObjects()) {
+ if (obj instanceof TextObject) {
+ addTextObjectToDocument(document, (TextObject) obj);
+ } else if (obj instanceof ShapeObject) {
+ addShapeObjectToDocument(document, (ShapeObject) obj);
+ } else if (obj instanceof ImageObject) {
+ addImageObjectToDocument(document, (ImageObject) obj);
+ }
+ }
+ }
+
+ /**
+ * 添加文本对象到PDF
+ */
+ private static void addTextObjectToDocument(Document document, TextObject textObj) throws DocumentException {
+ float fontSize = (float) textObj.getFontSize();
+ int fontStyle = com.itextpdf.text.Font.NORMAL;
+
+ if (textObj.getFontStyle().contains("BOLD")) {
+ fontStyle |= com.itextpdf.text.Font.BOLD;
+ }
+ if (textObj.getFontStyle().contains("ITALIC")) {
+ fontStyle |= com.itextpdf.text.Font.ITALIC;
+ }
+
+ com.itextpdf.text.Font font = new com.itextpdf.text.Font(
+ com.itextpdf.text.Font.FontFamily.HELVETICA, fontSize, fontStyle);
+
+ Paragraph paragraph = new Paragraph(textObj.getText(), font);
+ paragraph.setSpacingAfter(5);
+ document.add(paragraph);
+ }
+
+ /**
+ * 添加形状对象到PDF
+ */
+ private static void addShapeObjectToDocument(Document document, ShapeObject shapeObj) throws DocumentException {
+ String shapeInfo = "图形: " + shapeObj.getShapeType().name() +
+ " (位置: " + (int)shapeObj.getX() + ", " + (int)shapeObj.getY() +
+ " 大小: " + (int)shapeObj.getWidth() + "x" + (int)shapeObj.getHeight() + ")";
+
+ Paragraph paragraph = new Paragraph(shapeInfo, new com.itextpdf.text.Font(
+ com.itextpdf.text.Font.FontFamily.HELVETICA, 10));
+ paragraph.setSpacingAfter(5);
+ document.add(paragraph);
+ }
+
+ /**
+ * 添加图片对象到PDF
+ */
+ private static void addImageObjectToDocument(Document document, ImageObject imageObj) throws DocumentException {
+ String imagePath = imageObj.getImagePath();
+ if (imagePath != null && !imagePath.isEmpty()) {
+ try {
+ // 检查文件是否存在
+ File imageFile = new File(imagePath);
+ if (imageFile.exists()) {
+ Image img = Image.getInstance(imagePath);
+ // 限制图片大小
+ float maxWidth = document.getPageSize().getWidth() - 40;
+ float maxHeight = document.getPageSize().getHeight() / 3;
+ if (img.getWidth() > maxWidth) {
+ img.scaleToFit(maxWidth, maxHeight);
+ }
+ img.setSpacingAfter(10);
+ document.add(img);
+ } else {
+ Paragraph para = new Paragraph("[图片不存在: " + imagePath + "]",
+ new com.itextpdf.text.Font(com.itextpdf.text.Font.FontFamily.HELVETICA, 10));
+ para.setSpacingAfter(5);
+ document.add(para);
+ }
+ } catch (Exception e) {
+ Paragraph para = new Paragraph("[无法加载图片: " + e.getMessage() + "]",
+ new com.itextpdf.text.Font(com.itextpdf.text.Font.FontFamily.HELVETICA, 10));
+ para.setSpacingAfter(5);
+ document.add(para);
+ }
+ }
+ }
+}
+
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index fd967a3..d586c38 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,9 +1,11 @@
module dev.bytevibe.hyperpoint {
requires javafx.controls;
requires javafx.fxml;
+ requires javafx.graphics;
requires java.desktop;
requires com.google.gson;
-
+ requires itextpdf;
+ requires javafx.swing;
opens dev.bytevibe.hyperpoint to javafx.fxml;
exports dev.bytevibe.hyperpoint;
diff --git a/src/main/resources/dev/bytevibe/hyperpoint/main.fxml b/src/main/resources/dev/bytevibe/hyperpoint/main.fxml
index 5b1c557..ec30974 100644
--- a/src/main/resources/dev/bytevibe/hyperpoint/main.fxml
+++ b/src/main/resources/dev/bytevibe/hyperpoint/main.fxml
@@ -19,6 +19,8 @@
+
+