优化:导出幻灯片为PDF
This commit is contained in:
@@ -1,17 +1,28 @@
|
||||
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.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
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
|
||||
*/
|
||||
@@ -85,124 +96,299 @@ public class ExportUtil {
|
||||
* @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()) {
|
||||
throw new IOException("幻灯片没有页面");
|
||||
}
|
||||
|
||||
// 创建PDF文档
|
||||
Document document = new Document(PageSize.A4);
|
||||
PdfWriter.getInstance(document, new FileOutputStream(file));
|
||||
document.open();
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
// 创建PDF文档,使用与编辑界面相同的宽高比
|
||||
Document document = new Document(new RectangleReadOnly(
|
||||
(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()) {
|
||||
// 添加页面标题
|
||||
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);
|
||||
// 为每一页添加内容
|
||||
for (SlidePage page : slide.getPages()) {
|
||||
// 添加页面标题
|
||||
Paragraph title = new Paragraph(page.getTitle(), new Font(
|
||||
Font.FontFamily.HELVETICA, 16, 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);
|
||||
// 添加页面内容对象
|
||||
addObjectsToDocumentWithPosition(document, canvas, page.getPageContent());
|
||||
|
||||
document.newPage();
|
||||
}
|
||||
|
||||
// 添加对象列表
|
||||
addObjectsToDocument(document, page.getPageContent());
|
||||
|
||||
// 每页之间添加分页符
|
||||
document.newPage();
|
||||
document.close();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
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()) {
|
||||
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) {
|
||||
addTextObjectToDocument(document, (TextObject) obj);
|
||||
addTextObjectWithPosition(canvas, (TextObject)obj, x, y, width, height);
|
||||
} else if (obj instanceof ShapeObject) {
|
||||
addShapeObjectToDocument(document, (ShapeObject) obj);
|
||||
addShapeObjectWithPosition(canvas, (ShapeObject)obj, x, y, width, height);
|
||||
} 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 {
|
||||
float fontSize = (float) textObj.getFontSize();
|
||||
int fontStyle = com.itextpdf.text.Font.NORMAL;
|
||||
|
||||
if (textObj.getFontStyle().contains("BOLD")) {
|
||||
fontStyle |= com.itextpdf.text.Font.BOLD;
|
||||
private static void addTextObjectWithPosition(PdfContentByte canvas, TextObject textObj, float x, float y, float width, float height) {
|
||||
try {
|
||||
|
||||
// 处理字体
|
||||
BaseFont baseFont;
|
||||
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 {
|
||||
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);
|
||||
private static void addShapeObjectWithPosition(PdfContentByte canvas, ShapeObject shapeObj, float x, float y, float width, float height) {
|
||||
try {
|
||||
canvas.saveState();
|
||||
|
||||
// 设置线条和填充颜色
|
||||
if (shapeObj.getStrokeColor() != null) {
|
||||
Color strokeColor = Color.decode("#" + shapeObj.getStrokeColor());
|
||||
canvas.setColorStroke(new BaseColor(strokeColor.getRed(), strokeColor.getGreen(), strokeColor.getBlue()));
|
||||
}
|
||||
if (shapeObj.getFillColor() != null && !shapeObj.getFillColor().isEmpty()) {
|
||||
Color fillColor = Color.decode("#" + shapeObj.getFillColor());
|
||||
canvas.setColorFill(new BaseColor(fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue()));
|
||||
}
|
||||
canvas.setLineWidth((float)shapeObj.getStrokeWidth());
|
||||
|
||||
// 处理旋转
|
||||
double rotation = shapeObj.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))
|
||||
);
|
||||
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);
|
||||
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);
|
||||
break;
|
||||
case CIRCLE:
|
||||
float radius = Math.min(width, height) / 2f;
|
||||
centerX = x + radius;
|
||||
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;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user