优化:导出幻灯片为PDF
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user