diff --git a/src/main/java/dev/bytevibe/hyperpoint/ExportUtil.java b/src/main/java/dev/bytevibe/hyperpoint/ExportUtil.java index 9a07036..91901b5 100644 --- a/src/main/java/dev/bytevibe/hyperpoint/ExportUtil.java +++ b/src/main/java/dev/bytevibe/hyperpoint/ExportUtil.java @@ -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(); } } } -