feature:实现了幻灯片的pdf和图片导出
This commit is contained in:
@@ -26,6 +26,11 @@
|
|||||||
<artifactId>javafx-fxml</artifactId>
|
<artifactId>javafx-fxml</artifactId>
|
||||||
<version>${javafx.version}</version>
|
<version>${javafx.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-swing</artifactId>
|
||||||
|
<version>${javafx.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Gson库,用于JSON序列化 -->
|
<!-- Gson库,用于JSON序列化 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -34,6 +39,13 @@
|
|||||||
<version>2.10.1</version>
|
<version>2.10.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- iText库,用于PDF生成 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.itextpdf</groupId>
|
||||||
|
<artifactId>itextpdf</artifactId>
|
||||||
|
<version>5.5.13.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-api</artifactId>
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ public class Controller implements Initializable {
|
|||||||
private Button openSlideButton; // 添加打开幻灯片按钮
|
private Button openSlideButton; // 添加打开幻灯片按钮
|
||||||
@FXML
|
@FXML
|
||||||
private Button saveSlideButton; // 添加保存幻灯片按钮
|
private Button saveSlideButton; // 添加保存幻灯片按钮
|
||||||
|
@FXML
|
||||||
|
private Button exportPageButton; // 添加导出页面按钮
|
||||||
|
@FXML
|
||||||
|
private Button exportSlideButton; // 添加导出幻灯片按钮
|
||||||
|
|
||||||
private Slide currentSlide;
|
private Slide currentSlide;
|
||||||
private DrawingCanvas drawingCanvas;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示警告对话框
|
* 显示警告对话框
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
module dev.bytevibe.hyperpoint {
|
module dev.bytevibe.hyperpoint {
|
||||||
requires javafx.controls;
|
requires javafx.controls;
|
||||||
requires javafx.fxml;
|
requires javafx.fxml;
|
||||||
|
requires javafx.graphics;
|
||||||
requires java.desktop;
|
requires java.desktop;
|
||||||
requires com.google.gson;
|
requires com.google.gson;
|
||||||
|
requires itextpdf;
|
||||||
|
requires javafx.swing;
|
||||||
|
|
||||||
opens dev.bytevibe.hyperpoint to javafx.fxml;
|
opens dev.bytevibe.hyperpoint to javafx.fxml;
|
||||||
exports dev.bytevibe.hyperpoint;
|
exports dev.bytevibe.hyperpoint;
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
<Button fx:id="newPageButton" mnemonicParsing="false" onAction="#onNewPage" text="新建页面" />
|
<Button fx:id="newPageButton" mnemonicParsing="false" onAction="#onNewPage" text="新建页面" />
|
||||||
<Button fx:id="deletePageButton" mnemonicParsing="false" onAction="#onDeletePage" text="删除页面" />
|
<Button fx:id="deletePageButton" mnemonicParsing="false" onAction="#onDeletePage" text="删除页面" />
|
||||||
<Button fx:id="saveSlideButton" mnemonicParsing="false" onAction="#onSaveSlide" text="保存幻灯片" />
|
<Button fx:id="saveSlideButton" mnemonicParsing="false" onAction="#onSaveSlide" text="保存幻灯片" />
|
||||||
|
<Button fx:id="exportPageButton" mnemonicParsing="false" onAction="#onExportPage" text="导出页面" />
|
||||||
|
<Button fx:id="exportSlideButton" mnemonicParsing="false" onAction="#onExportSlide" text="导出幻灯片" />
|
||||||
<Label text=" | " />
|
<Label text=" | " />
|
||||||
<Button fx:id="addTextButton" mnemonicParsing="false" onAction="#onAddText" text="添加文本" />
|
<Button fx:id="addTextButton" mnemonicParsing="false" onAction="#onAddText" text="添加文本" />
|
||||||
<Button fx:id="addLineButton" mnemonicParsing="false" onAction="#onAddLine" text="直线" />
|
<Button fx:id="addLineButton" mnemonicParsing="false" onAction="#onAddLine" text="直线" />
|
||||||
|
|||||||
Reference in New Issue
Block a user