优化:导出幻灯片为PDF

This commit is contained in:
liujing133
2025-12-08 00:32:15 +08:00
parent 12437bb6a6
commit 5b159a719a
@@ -1,17 +1,28 @@
package dev.bytevibe.hyperpoint; package dev.bytevibe.hyperpoint;
import javafx.scene.image.WritableImage; import java.awt.Color;
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.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import javax.imageio.ImageIO;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.RectangleReadOnly;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfWriter;
import javafx.scene.image.PixelReader;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
/** /**
* 导出工具类,用于导出页面为图片和导出幻灯片为PDF * 导出工具类,用于导出页面为图片和导出幻灯片为PDF
*/ */
@@ -85,124 +96,299 @@ public class ExportUtil {
* @throws Exception 如果导出失败 * @throws Exception 如果导出失败
*/ */
public static void exportSlideToPDF(Slide slide, File file) throws Exception { public static void exportSlideToPDF(Slide slide, File file) throws Exception {
// 检查文件是否已存在且被占用
if (file.exists()) {
try (FileOutputStream fos = new FileOutputStream(file, false)) {
} catch (IOException e) {
throw new IOException("文件已被占用,请关闭该文件后重试:\n" + file.getAbsolutePath(), e);
}
}
if (slide == null || slide.getPages().isEmpty()) { if (slide == null || slide.getPages().isEmpty()) {
throw new IOException("幻灯片没有页面"); throw new IOException("幻灯片没有页面");
} }
// 创建PDF文档 try (FileOutputStream fos = new FileOutputStream(file)) {
Document document = new Document(PageSize.A4); // 创建PDF文档,使用与编辑界面相同的宽高比
PdfWriter.getInstance(document, new FileOutputStream(file)); Document document = new Document(new RectangleReadOnly(
document.open(); (float)DrawingCanvas.PAGE_WIDTH,
(float)DrawingCanvas.PAGE_HEIGHT
));
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file));
document.open();
PdfContentByte canvas = writer.getDirectContent();
// 为每一页添加内容 // 为每一页添加内容
for (SlidePage page : slide.getPages()) { for (SlidePage page : slide.getPages()) {
// 添加页面标题 // 添加页面标题
Paragraph title = new Paragraph(page.getTitle(), new com.itextpdf.text.Font( Paragraph title = new Paragraph(page.getTitle(), new Font(
com.itextpdf.text.Font.FontFamily.HELVETICA, 16, com.itextpdf.text.Font.BOLD)); Font.FontFamily.HELVETICA, 16, Font.BOLD));
title.setSpacingAfter(10); title.setSpacingAfter(10);
document.add(title); document.add(title);
// 添加页面内容(如果有的话) // 添加页面内容对象
if (page.getContent() != null && !page.getContent().isEmpty()) { addObjectsToDocumentWithPosition(document, canvas, page.getPageContent());
Paragraph content = new Paragraph(page.getContent(), new com.itextpdf.text.Font(
com.itextpdf.text.Font.FontFamily.HELVETICA, 12)); document.newPage();
content.setSpacingAfter(10);
document.add(content);
} }
// 添加对象列表 document.close();
addObjectsToDocument(document, page.getPageContent()); writer.close();
// 每页之间添加分页符
document.newPage();
} }
document.close();
} }
/** /**
* 将页面内容的对象添加到PDF文档 * 按位置添加对象到PDF文档
*/ */
private static void addObjectsToDocument(Document document, PageContent pageContent) throws DocumentException { private static void addObjectsToDocumentWithPosition(Document document, PdfContentByte canvas, PageContent pageContent) throws DocumentException {
float margin = 50;
float pdfWidth = (float)(document.getPageSize().getWidth() - 2 * margin);
float pdfHeight = (float)(document.getPageSize().getHeight() - 2 * margin);
float scaleX = pdfWidth / (float)DrawingCanvas.PAGE_WIDTH;
float scaleY = pdfHeight / (float)DrawingCanvas.PAGE_HEIGHT;
float scale = Math.min(scaleX, scaleY);
for (DrawableObject obj : pageContent.getDrawableObjects()) { for (DrawableObject obj : pageContent.getDrawableObjects()) {
float x = margin + (float)obj.getX() * scale;
float y = margin + (float)(DrawingCanvas.PAGE_HEIGHT - obj.getY() - obj.getHeight()) * scale;
float width = (float)obj.getWidth() * scale;
float height = (float)obj.getHeight() * scale;
if (obj instanceof TextObject) { if (obj instanceof TextObject) {
addTextObjectToDocument(document, (TextObject) obj); addTextObjectWithPosition(canvas, (TextObject)obj, x, y, width, height);
} else if (obj instanceof ShapeObject) { } else if (obj instanceof ShapeObject) {
addShapeObjectToDocument(document, (ShapeObject) obj); addShapeObjectWithPosition(canvas, (ShapeObject)obj, x, y, width, height);
} else if (obj instanceof ImageObject) { } else if (obj instanceof ImageObject) {
addImageObjectToDocument(document, (ImageObject) obj); addImageObjectWithPosition(document, canvas, (ImageObject)obj, x, y, width, height);
} }
} }
} }
/** /**
* 添加文本对象到PDF * 按位置添加文本对象
*/ */
private static void addTextObjectToDocument(Document document, TextObject textObj) throws DocumentException { private static void addTextObjectWithPosition(PdfContentByte canvas, TextObject textObj, float x, float y, float width, float height) {
float fontSize = (float) textObj.getFontSize(); try {
int fontStyle = com.itextpdf.text.Font.NORMAL;
// 处理字体
if (textObj.getFontStyle().contains("BOLD")) { BaseFont baseFont;
fontStyle |= com.itextpdf.text.Font.BOLD; String fontFamily = textObj.getFontFamily();
if (fontFamily.contains("Times")) {
baseFont = BaseFont.createFont(BaseFont.TIMES_ROMAN, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
} else if (fontFamily.contains("Courier")) {
baseFont = BaseFont.createFont(BaseFont.COURIER, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
} else if (fontFamily.contains("Georgia")) {
baseFont = BaseFont.createFont(BaseFont.TIMES_ROMAN, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
} else if (fontFamily.contains("Verdana")) {
baseFont = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
} else {
baseFont = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
}
// 处理字体样式(粗体/斜体)
int style = Font.NORMAL;
String fontStyle = textObj.getFontStyle();
if (fontStyle.contains("BOLD")) style |= Font.BOLD;
if (fontStyle.contains("ITALIC")) style |= Font.ITALIC;
// 处理文本颜色
BaseColor textColor = BaseColor.BLACK;
if (textObj.getTextColor() != null && !textObj.getTextColor().isEmpty()) {
Color fxColor = Color.decode("#" + textObj.getTextColor());
textColor = new BaseColor(fxColor.getRed(), fxColor.getGreen(), fxColor.getBlue());
}
// 处理旋转
double rotation = textObj.getRotation();
if (rotation != 0) {
canvas.saveState();
canvas.concatCTM(
(float) Math.cos(Math.toRadians(rotation)),
(float) Math.sin(Math.toRadians(rotation)),
(float) -Math.sin(Math.toRadians(rotation)),
(float) Math.cos(Math.toRadians(rotation)),
x, y
);
x = 0;
y = 0;
canvas.beginText();
canvas.setFontAndSize(baseFont, (float) textObj.getFontSize());
canvas.setColorFill(textColor);
canvas.setTextMatrix(x, y + (float) textObj.getFontSize());
canvas.showText(textObj.getText());
canvas.endText();
canvas.restoreState();
} else {
canvas.beginText();
canvas.setFontAndSize(baseFont, (float) textObj.getFontSize());
canvas.setColorFill(textColor);
canvas.setTextMatrix(x, y + (float) textObj.getFontSize());
canvas.showText(textObj.getText());
canvas.endText();
}
} catch (Exception e) {
e.printStackTrace();
} }
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 { private static void addShapeObjectWithPosition(PdfContentByte canvas, ShapeObject shapeObj, float x, float y, float width, float height) {
String shapeInfo = "图形: " + shapeObj.getShapeType().name() + try {
" (位置: " + (int)shapeObj.getX() + ", " + (int)shapeObj.getY() + canvas.saveState();
" 大小: " + (int)shapeObj.getWidth() + "x" + (int)shapeObj.getHeight() + ")";
// 设置线条和填充颜色
Paragraph paragraph = new Paragraph(shapeInfo, new com.itextpdf.text.Font( if (shapeObj.getStrokeColor() != null) {
com.itextpdf.text.Font.FontFamily.HELVETICA, 10)); Color strokeColor = Color.decode("#" + shapeObj.getStrokeColor());
paragraph.setSpacingAfter(5); canvas.setColorStroke(new BaseColor(strokeColor.getRed(), strokeColor.getGreen(), strokeColor.getBlue()));
document.add(paragraph); }
} if (shapeObj.getFillColor() != null && !shapeObj.getFillColor().isEmpty()) {
Color fillColor = Color.decode("#" + shapeObj.getFillColor());
/** canvas.setColorFill(new BaseColor(fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue()));
* 添加图片对象到PDF }
*/ canvas.setLineWidth((float)shapeObj.getStrokeWidth());
private static void addImageObjectToDocument(Document document, ImageObject imageObj) throws DocumentException {
String imagePath = imageObj.getImagePath(); // 处理旋转
if (imagePath != null && !imagePath.isEmpty()) { double rotation = shapeObj.getRotation();
try { if (rotation != 0) {
// 检查文件是否存在 canvas.saveState();
File imageFile = new File(imagePath); float centerX = x + width / 2f;
if (imageFile.exists()) { float centerY = y + height / 2f;
Image img = Image.getInstance(imagePath); canvas.concatCTM(
// 限制图片大小 (float) Math.cos(Math.toRadians(rotation)),
float maxWidth = document.getPageSize().getWidth() - 40; (float) Math.sin(Math.toRadians(rotation)),
float maxHeight = document.getPageSize().getHeight() / 3; (float) -Math.sin(Math.toRadians(rotation)),
if (img.getWidth() > maxWidth) { (float) Math.cos(Math.toRadians(rotation)),
img.scaleToFit(maxWidth, maxHeight); centerX - centerX * (float) Math.cos(Math.toRadians(rotation)) + centerY * (float) Math.sin(Math.toRadians(rotation)),
centerY - centerX * (float) Math.sin(Math.toRadians(rotation)) - centerY * (float) Math.cos(Math.toRadians(rotation))
);
switch (shapeObj.getShapeType()) {
case LINE:
canvas.moveTo(x, y + height);
canvas.lineTo(x + width, y);
canvas.stroke();
break;
case RECTANGLE:
canvas.rectangle(x, y, width, height);
if (shapeObj.getFillColor() != null && !shapeObj.getFillColor().isEmpty()) {
canvas.fillStroke();
} else {
canvas.stroke();
} }
img.setSpacingAfter(10); break;
document.add(img); case CIRCLE:
} else { float radius = Math.min(width, height) / 2f;
Paragraph para = new Paragraph("[图片不存在: " + imagePath + "]", centerX = x + radius;
new com.itextpdf.text.Font(com.itextpdf.text.Font.FontFamily.HELVETICA, 10)); centerY = y + radius;
para.setSpacingAfter(5); canvas.ellipse(centerX + radius, centerY + radius, centerX - radius, centerY - radius); // 宽高相等才是正圆
document.add(para); if (shapeObj.getFillColor() != null && !shapeObj.getFillColor().isEmpty()) {
canvas.fillStroke();
} else {
canvas.stroke();
}
break;
case ELLIPSE:
float ellipseRadiusX = width / 2f;
float ellipseRadiusY = height / 2f;
float ellipseCenterX = x + ellipseRadiusX;
float ellipseCenterY = y + ellipseRadiusY;
canvas.ellipse(ellipseCenterX + ellipseRadiusX, ellipseCenterY + ellipseRadiusY, ellipseCenterX - ellipseRadiusX, ellipseCenterY - ellipseRadiusY);
if (shapeObj.getFillColor() != null && !shapeObj.getFillColor().isEmpty()) {
canvas.fillStroke();
} else {
canvas.stroke();
}
break;
}
canvas.restoreState();
} else {
switch (shapeObj.getShapeType()) {
case LINE:
canvas.moveTo(x, y + height);
canvas.lineTo(x + width, y);
canvas.stroke();
break;
case RECTANGLE:
canvas.rectangle(x, y, width, height);
if (shapeObj.getFillColor() != null && !shapeObj.getFillColor().isEmpty()) {
canvas.fillStroke();
} else {
canvas.stroke();
}
break;
case CIRCLE:
float radius = Math.min(width, height) / 2f;
float centerX = x + radius;
float centerY = y + radius;
canvas.ellipse(centerX + radius, centerY - radius, centerX - radius, centerY + radius); // 宽高相等才是正圆
if (shapeObj.getFillColor() != null && !shapeObj.getFillColor().isEmpty()) {
canvas.fillStroke();
} else {
canvas.stroke();
}
break;
case ELLIPSE:
float ellipseRadiusX = width / 2f;
float ellipseRadiusY = height / 2f;
float ellipseCenterX = x + ellipseRadiusX;
float ellipseCenterY = y + ellipseRadiusY;
canvas.ellipse(ellipseCenterX + ellipseRadiusX, ellipseCenterY - ellipseRadiusY, ellipseCenterX - ellipseRadiusX, ellipseCenterY + ellipseRadiusY);
if (shapeObj.getFillColor() != null && !shapeObj.getFillColor().isEmpty()) {
canvas.fillStroke();
} else {
canvas.stroke();
}
break;
} }
} 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);
} }
canvas.restoreState();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 按位置添加图片对象
*/
private static void addImageObjectWithPosition(Document document, PdfContentByte canvas, ImageObject imageObj, float x, float y, float width, float height) {
try {
String imagePath = imageObj.getImagePath();
if (imagePath != null && new File(imagePath).exists()) {
Image img = Image.getInstance(imagePath);
// 设置图片大小
img.scaleAbsolute(width, height);
// 处理旋转
double rotation = imageObj.getRotation();
if (rotation != 0) {
canvas.saveState();
float centerX = x + width / 2f;
float centerY = y + height / 2f;
canvas.concatCTM(
(float) Math.cos(Math.toRadians(rotation)),
(float) Math.sin(Math.toRadians(rotation)),
(float) -Math.sin(Math.toRadians(rotation)),
(float) Math.cos(Math.toRadians(rotation)),
centerX - centerX * (float) Math.cos(Math.toRadians(rotation)) + centerY * (float) Math.sin(Math.toRadians(rotation)),
centerY - centerX * (float) Math.sin(Math.toRadians(rotation)) - centerY * (float) Math.cos(Math.toRadians(rotation))
);
img.setAbsolutePosition(x, y);
canvas.addImage(img);
canvas.restoreState();
} else {
img.setAbsolutePosition(x, y);
canvas.addImage(img);
}
}
} catch (Exception e) {
e.printStackTrace();
} }
} }
} }