|
|
|
@ -13,13 +13,34 @@ import javax.imageio.ImageIO;
|
|
|
|
import java.awt.image.BufferedImage;
|
|
|
|
import java.awt.image.BufferedImage;
|
|
|
|
import java.io.*;
|
|
|
|
import java.io.*;
|
|
|
|
import java.net.URL;
|
|
|
|
import java.net.URL;
|
|
|
|
|
|
|
|
import java.nio.file.Files;
|
|
|
|
|
|
|
|
import java.nio.file.Paths;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
|
|
|
|
|
import java.util.concurrent.Executors;
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
|
|
|
|
import java.util.zip.Deflater;
|
|
|
|
|
|
|
|
import java.util.zip.ZipEntry;
|
|
|
|
|
|
|
|
import java.util.zip.ZipOutputStream;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class PDFGenerator {
|
|
|
|
public class PDFGenerator {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 缓存字体实例以避免重复加载
|
|
|
|
|
|
|
|
private static volatile PDType0Font cachedFont = null;
|
|
|
|
|
|
|
|
private static final Object fontLock = new Object();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 缓存已处理的图片以避免重复下载和处理
|
|
|
|
|
|
|
|
private static final ConcurrentHashMap<String, PDImageXObject> imageCache = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 线程池用于并发处理图片
|
|
|
|
|
|
|
|
private static final ExecutorService imageProcessingPool = Executors.newFixedThreadPool(4);
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 表格参数配置
|
|
|
|
// 1. 表格参数配置
|
|
|
|
|
|
|
|
|
|
|
|
// 参数配置(优化后)
|
|
|
|
// 参数配置(优化后)
|
|
|
|
@ -33,9 +54,13 @@ public class PDFGenerator {
|
|
|
|
private static final float[] COLUMN_WIDTHS = {LEFT_COL_WIDTH, RIGHT_COL_WIDTH};
|
|
|
|
private static final float[] COLUMN_WIDTHS = {LEFT_COL_WIDTH, RIGHT_COL_WIDTH};
|
|
|
|
private static final float MIN_IMAGE_HEIGHT = 50;
|
|
|
|
private static final float MIN_IMAGE_HEIGHT = 50;
|
|
|
|
public static void generateInstallationPDF(DeviceInstallInfo entity, OutputStream outputStream) throws IOException {
|
|
|
|
public static void generateInstallationPDF(DeviceInstallInfo entity, OutputStream outputStream) throws IOException {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 首先生成PDF内容到字节数组
|
|
|
|
|
|
|
|
ByteArrayOutputStream pdfBaos = new ByteArrayOutputStream();
|
|
|
|
|
|
|
|
|
|
|
|
try (PDDocument document = new PDDocument()) {
|
|
|
|
try (PDDocument document = new PDDocument()) {
|
|
|
|
// 加载字体(需替换实际路径)
|
|
|
|
// 加载字体(需替换实际路径)
|
|
|
|
//PDType0Font font = PDType0Font.load(document, new File("D:\\NotoSansCJK-Regular.ttf"));
|
|
|
|
// PDType0Font font = PDType0Font.load(document, new File("D:\\NotoSansCJK-Regular.ttf"));
|
|
|
|
//PDType0Font font = PDType0Font.load(document, new File("/ttf/simsun.ttf"));
|
|
|
|
//PDType0Font font = PDType0Font.load(document, new File("/ttf/simsun.ttf"));
|
|
|
|
PDType0Font font = PDType0Font.load(document, new File("/ttf/NotoSansCJK-Regular.ttf"));
|
|
|
|
PDType0Font font = PDType0Font.load(document, new File("/ttf/NotoSansCJK-Regular.ttf"));
|
|
|
|
// 初始化第一页
|
|
|
|
// 初始化第一页
|
|
|
|
@ -76,15 +101,18 @@ public class PDFGenerator {
|
|
|
|
// 图片处理(带自动分页)
|
|
|
|
// 图片处理(带自动分页)
|
|
|
|
// 图片处理(带自动分页)
|
|
|
|
// 图片处理(带自动分页)
|
|
|
|
List<String[]> imageGroups = Arrays.asList(
|
|
|
|
List<String[]> imageGroups = Arrays.asList(
|
|
|
|
|
|
|
|
new String[]{entity.getDeviceInfoImage(), "设备信息图片"},
|
|
|
|
new String[]{entity.getBeforeInstallationImage(), "安装前图片"},
|
|
|
|
new String[]{entity.getBeforeInstallationImage(), "安装前图片"},
|
|
|
|
new String[]{entity.getWorkingOfTheDetectorImage(), "安装完成探测器工作图片"},
|
|
|
|
new String[]{entity.getWorkingOfTheDetectorImage(), "安装完成探测器工作图片"},
|
|
|
|
new String[]{entity.getSideLeakageImage(), "测漏图片"},
|
|
|
|
new String[]{entity.getSideLeakageImage(), "测漏图片"},
|
|
|
|
|
|
|
|
new String[]{entity.getFormSideLeakageImage(), "泡沫水测漏图片"},
|
|
|
|
new String[]{entity.getIgnitionPictureImage(), "点火图片"},
|
|
|
|
new String[]{entity.getIgnitionPictureImage(), "点火图片"},
|
|
|
|
new String[]{entity.getInstallThePanoramicImage(), "安装完成全景图片"},
|
|
|
|
new String[]{entity.getInstallThePanoramicImage(), "安装完成全景图片"},
|
|
|
|
new String[]{entity.getOfGasMeterImage(), "燃气表图片"},
|
|
|
|
new String[]{entity.getOfGasMeterImage(), "燃气表号图片"},
|
|
|
|
new String[]{entity.getWorkOrderImage(), "工单图片"},
|
|
|
|
new String[]{entity.getWorkOrderImage(), "工单图片"},
|
|
|
|
new String[]{entity.getPunchingImage(), "打孔图片"},
|
|
|
|
new String[]{entity.getPunchingImage(), "打孔图片"},
|
|
|
|
new String[]{entity.getFiexImage(), "安装电源线图片"}
|
|
|
|
new String[]{entity.getFiexImage(), "安装电源线图片"},
|
|
|
|
|
|
|
|
new String[]{entity.getHouseNumberImage(), "门牌号图片"}
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
for (String[] group : imageGroups) {
|
|
|
|
for (String[] group : imageGroups) {
|
|
|
|
@ -98,7 +126,40 @@ public class PDFGenerator {
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
|
|
|
|
|
|
if (!validUrls.isEmpty()) {
|
|
|
|
if (!validUrls.isEmpty()) {
|
|
|
|
// float totalHeight = calculateTotalHeight(validUrls);
|
|
|
|
// 处理可能需要跨页的多图片情况
|
|
|
|
|
|
|
|
if (validUrls.size() > 2) {
|
|
|
|
|
|
|
|
// 对于多于2张的图片,逐个或分组处理以支持跨页
|
|
|
|
|
|
|
|
for (int i = 0; i < validUrls.size(); i += 2) { // 每次处理最多2张图片
|
|
|
|
|
|
|
|
List<String> subList = validUrls.subList(i,
|
|
|
|
|
|
|
|
Math.min(i + 2, validUrls.size()));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
List<String> wrappedTitleLines = wrapText(title,LEFT_COL_WIDTH - 2 * CELL_PADDING);
|
|
|
|
|
|
|
|
float titleHeight = wrappedTitleLines.size() * LINE_HEIGHT;
|
|
|
|
|
|
|
|
// 计算图片组的自适应高度
|
|
|
|
|
|
|
|
float imagesHeight = calculateAdjustedImagesHeight(document, subList,
|
|
|
|
|
|
|
|
RIGHT_COL_WIDTH - 2 * CELL_PADDING,
|
|
|
|
|
|
|
|
PDRectangle.A4.getHeight() - 2 * TABLE_MARGIN - titleHeight);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float totalHeight = Math.max(imagesHeight, titleHeight);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (currentY < TABLE_MARGIN + Math.max(totalHeight, titleHeight)) {
|
|
|
|
|
|
|
|
cs.close();
|
|
|
|
|
|
|
|
currentPage = new PDPage(PDRectangle.A4);
|
|
|
|
|
|
|
|
document.addPage(currentPage);
|
|
|
|
|
|
|
|
cs = new PDPageContentStream(document, currentPage);
|
|
|
|
|
|
|
|
currentY = PDRectangle.A4.getHeight() - TABLE_MARGIN;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 添加标识,表明这是多图片的一部分
|
|
|
|
|
|
|
|
String subTitle = title + " (" + (i+1) + "-" + Math.min(i+2, validUrls.size()) + "/" + validUrls.size() + ")";
|
|
|
|
|
|
|
|
List<String> subWrappedTitleLines = wrapText(subTitle, LEFT_COL_WIDTH - 2 * CELL_PADDING);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
drawImageGroup(cs, document, font, subWrappedTitleLines, subList, currentY,
|
|
|
|
|
|
|
|
Math.max(totalHeight, titleHeight));
|
|
|
|
|
|
|
|
currentY -= Math.max(totalHeight, titleHeight);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// 原有的单/双图片处理逻辑
|
|
|
|
List<String> wrappedTitleLines = wrapText(title,LEFT_COL_WIDTH - 2 * CELL_PADDING);
|
|
|
|
List<String> wrappedTitleLines = wrapText(title,LEFT_COL_WIDTH - 2 * CELL_PADDING);
|
|
|
|
float titleHeight = wrappedTitleLines.size() * LINE_HEIGHT;
|
|
|
|
float titleHeight = wrappedTitleLines.size() * LINE_HEIGHT;
|
|
|
|
// 计算图片组的自适应高度
|
|
|
|
// 计算图片组的自适应高度
|
|
|
|
@ -122,24 +183,99 @@ public class PDFGenerator {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
} finally {
|
|
|
|
cs.close();
|
|
|
|
cs.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// document.save("D:/output.pdf");
|
|
|
|
// 将PDF保存到ByteArrayOutputStream
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
|
document.save(pdfBaos);
|
|
|
|
document.save(baos);
|
|
|
|
|
|
|
|
outputStream.flush();
|
|
|
|
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
|
baos.writeTo(outputStream);
|
|
|
|
// document.save(baos);
|
|
|
|
// document.save(outputStream);
|
|
|
|
// outputStream.flush();
|
|
|
|
|
|
|
|
// baos.writeTo(outputStream);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 创建ZIP压缩包并将PDF写入输出流
|
|
|
|
|
|
|
|
try (ZipOutputStream zipOut = new ZipOutputStream(outputStream)) {
|
|
|
|
|
|
|
|
// 设置最高压缩级别
|
|
|
|
|
|
|
|
zipOut.setLevel(Deflater.BEST_COMPRESSION);
|
|
|
|
|
|
|
|
// 创建ZIP条目
|
|
|
|
|
|
|
|
ZipEntry entry = new ZipEntry(entity.getUserName()+entity.getCommunityName()+entity.getBuildingUnit()+entity.getRoomNo()+".pdf");
|
|
|
|
|
|
|
|
zipOut.putNextEntry(entry);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 将PDF数据写入ZIP
|
|
|
|
|
|
|
|
byte[] pdfBytes = pdfBaos.toByteArray();
|
|
|
|
|
|
|
|
zipOut.write(pdfBytes, 0, pdfBytes.length);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭当前条目
|
|
|
|
|
|
|
|
zipOut.closeEntry();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 加载字体,确保使用正确的文档实例
|
|
|
|
|
|
|
|
private static PDType0Font loadFont(PDDocument document) throws IOException {
|
|
|
|
|
|
|
|
// 尝试多个可能的字体路径
|
|
|
|
|
|
|
|
String[] fontPaths = {
|
|
|
|
|
|
|
|
"D:\\NotoSansCJK-Regular.ttf",
|
|
|
|
|
|
|
|
"C:\\Windows\\Fonts\\simsun.ttc",
|
|
|
|
|
|
|
|
"C:\\Windows\\Fonts\\msyh.ttc",
|
|
|
|
|
|
|
|
"/System/Library/Fonts/Helvetica.dfont", // macOS
|
|
|
|
|
|
|
|
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" // Linux
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (String path : fontPaths) {
|
|
|
|
|
|
|
|
if (Files.exists(Paths.get(path))) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
return PDType0Font.load(document, new File(path));
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
|
|
|
continue; // 尝试下一个字体文件
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return PDType0Font.load(document, new File("/ttf/NotoSansCJK-Regular.ttf"));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 缓存已处理的图片以避免重复下载和处理
|
|
|
|
|
|
|
|
private static final ConcurrentHashMap<String, byte[]> imageByteCache = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 预加载图片以避免重复下载
|
|
|
|
|
|
|
|
private static PDImageXObject loadImageFromUrl(PDDocument document, String url) throws IOException {
|
|
|
|
|
|
|
|
byte[] imageBytes = imageByteCache.computeIfAbsent(url, key -> {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
return new URL(key).openStream().readAllBytes();
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
|
|
|
return null; // 或者抛出异常
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (imageBytes == null) {
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return PDImageXObject.createFromByteArray(document, imageBytes, "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static List<String> wrapText(String text, float maxWidth) {
|
|
|
|
private static List<String> wrapText(String text, float maxWidth) {
|
|
|
|
List<String> lines = new ArrayList<>();
|
|
|
|
List<String> lines = new ArrayList<>();
|
|
|
|
int maxCharsPerLine = (int)(maxWidth / (FONT_SIZE * 0.5f)); // 中文字符估算
|
|
|
|
if (text == null || text.isEmpty()) return lines;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 优化的文本换行逻辑,减少计算量
|
|
|
|
|
|
|
|
int maxCharsPerLine = (int)(maxWidth / (FONT_SIZE * 0.4f)); // 调整字符估算系数
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (text.length() <= maxCharsPerLine) {
|
|
|
|
|
|
|
|
lines.add(text);
|
|
|
|
|
|
|
|
return lines;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
StringBuilder currentLine = new StringBuilder();
|
|
|
|
StringBuilder currentLine = new StringBuilder();
|
|
|
|
for (String word : text.split("")) {
|
|
|
|
String[] words = text.split("");
|
|
|
|
|
|
|
|
for (String word : words) {
|
|
|
|
if (currentLine.length() + word.length() <= maxCharsPerLine) {
|
|
|
|
if (currentLine.length() + word.length() <= maxCharsPerLine) {
|
|
|
|
currentLine.append(word);
|
|
|
|
currentLine.append(word);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
@ -152,6 +288,24 @@ public class PDFGenerator {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return lines;
|
|
|
|
return lines;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// private static List<String> wrapText(String text, float maxWidth) {
|
|
|
|
|
|
|
|
// List<String> lines = new ArrayList<>();
|
|
|
|
|
|
|
|
// int maxCharsPerLine = (int)(maxWidth / (FONT_SIZE * 0.5f)); // 中文字符估算
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// StringBuilder currentLine = new StringBuilder();
|
|
|
|
|
|
|
|
// for (String word : text.split("")) {
|
|
|
|
|
|
|
|
// if (currentLine.length() + word.length() <= maxCharsPerLine) {
|
|
|
|
|
|
|
|
// currentLine.append(word);
|
|
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
|
|
// lines.add(currentLine.toString());
|
|
|
|
|
|
|
|
// currentLine = new StringBuilder(word);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// if (currentLine.length() > 0) {
|
|
|
|
|
|
|
|
// lines.add(currentLine.toString());
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// return lines;
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static float calculateTotalHeight(List<String> imageUrls) throws IOException {
|
|
|
|
private static float calculateTotalHeight(List<String> imageUrls) throws IOException {
|
|
|
|
@ -275,8 +429,8 @@ public class PDFGenerator {
|
|
|
|
cs.endText();
|
|
|
|
cs.endText();
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制第一行图片(自动缩放)
|
|
|
|
// 绘制第一行图片(自动缩放)
|
|
|
|
PDImageXObject image1 = PDImageXObject.createFromByteArray(doc,
|
|
|
|
PDImageXObject image1 = loadImageFromUrl(doc, imageUrls.get(0));
|
|
|
|
new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image");
|
|
|
|
if (image1 != null) {
|
|
|
|
float scale1 = Math.min(
|
|
|
|
float scale1 = Math.min(
|
|
|
|
imageAvailableWidth / image1.getWidth(),
|
|
|
|
imageAvailableWidth / image1.getWidth(),
|
|
|
|
singleImageHeight / image1.getHeight()
|
|
|
|
singleImageHeight / image1.getHeight()
|
|
|
|
@ -288,6 +442,7 @@ public class PDFGenerator {
|
|
|
|
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
startY - CELL_PADDING - scaledHeight1,
|
|
|
|
startY - CELL_PADDING - scaledHeight1,
|
|
|
|
scaledWidth1, scaledHeight1);
|
|
|
|
scaledWidth1, scaledHeight1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else if (imageUrls.size() == 2) {
|
|
|
|
} else if (imageUrls.size() == 2) {
|
|
|
|
// 双图片模式:改为上下布局
|
|
|
|
// 双图片模式:改为上下布局
|
|
|
|
@ -306,23 +461,25 @@ public class PDFGenerator {
|
|
|
|
cs.endText();
|
|
|
|
cs.endText();
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制第一张图片(上)
|
|
|
|
// 绘制第一张图片(上)
|
|
|
|
PDImageXObject image1 = PDImageXObject.createFromByteArray(doc,
|
|
|
|
PDImageXObject image1 = loadImageFromUrl(doc, imageUrls.get(0));
|
|
|
|
new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image");
|
|
|
|
float scaledHeight1 = 0;
|
|
|
|
|
|
|
|
if (image1 != null) {
|
|
|
|
float scale1 = Math.min(
|
|
|
|
float scale1 = Math.min(
|
|
|
|
imageAvailableWidth / image1.getWidth(),
|
|
|
|
imageAvailableWidth / image1.getWidth(),
|
|
|
|
imageHeightPer / image1.getHeight()
|
|
|
|
imageHeightPer / image1.getHeight()
|
|
|
|
);
|
|
|
|
);
|
|
|
|
float scaledHeight1 = image1.getHeight() * scale1;
|
|
|
|
scaledHeight1 = image1.getHeight() * scale1;
|
|
|
|
float scaledWidth1 = image1.getWidth() * scale1;
|
|
|
|
float scaledWidth1 = image1.getWidth() * scale1;
|
|
|
|
|
|
|
|
|
|
|
|
cs.drawImage(image1,
|
|
|
|
cs.drawImage(image1,
|
|
|
|
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
startY - CELL_PADDING - scaledHeight1,
|
|
|
|
startY - CELL_PADDING - scaledHeight1,
|
|
|
|
scaledWidth1, scaledHeight1);
|
|
|
|
scaledWidth1, scaledHeight1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制第二张图片(下)
|
|
|
|
// 绘制第二张图片(下)
|
|
|
|
PDImageXObject image2 = PDImageXObject.createFromByteArray(doc,
|
|
|
|
PDImageXObject image2 = loadImageFromUrl(doc, imageUrls.get(1));
|
|
|
|
new URL(imageUrls.get(1)).openStream().readAllBytes(), "embedded-image");
|
|
|
|
if (image2 != null) {
|
|
|
|
float scale2 = Math.min(
|
|
|
|
float scale2 = Math.min(
|
|
|
|
imageAvailableWidth / image2.getWidth(),
|
|
|
|
imageAvailableWidth / image2.getWidth(),
|
|
|
|
imageHeightPer / image2.getHeight()
|
|
|
|
imageHeightPer / image2.getHeight()
|
|
|
|
@ -334,6 +491,7 @@ public class PDFGenerator {
|
|
|
|
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
startY - CELL_PADDING - scaledHeight1 - CELL_PADDING - scaledHeight2,
|
|
|
|
startY - CELL_PADDING - scaledHeight1 - CELL_PADDING - scaledHeight2,
|
|
|
|
scaledWidth2, scaledHeight2);
|
|
|
|
scaledWidth2, scaledHeight2);
|
|
|
|
|
|
|
|
}
|
|
|
|
/* // 双图片模式:一行并列显示
|
|
|
|
/* // 双图片模式:一行并列显示
|
|
|
|
float imageWidthPer = (imageAvailableWidth - CELL_PADDING) / 2;
|
|
|
|
float imageWidthPer = (imageAvailableWidth - CELL_PADDING) / 2;
|
|
|
|
|
|
|
|
|
|
|
|
@ -378,8 +536,239 @@ public class PDFGenerator {
|
|
|
|
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING + imageWidthPer + CELL_PADDING,
|
|
|
|
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING + imageWidthPer + CELL_PADDING,
|
|
|
|
startY - CELL_PADDING - scaledHeight2,
|
|
|
|
startY - CELL_PADDING - scaledHeight2,
|
|
|
|
scaledWidth2, scaledHeight2);*/
|
|
|
|
scaledWidth2, scaledHeight2);*/
|
|
|
|
|
|
|
|
}else {
|
|
|
|
|
|
|
|
// 多图片模式(超过2张图片):垂直堆叠显示,与双图片模式保持一致的样式
|
|
|
|
|
|
|
|
float imageHeightPer = (imageAvailableHeight - CELL_PADDING * (imageUrls.size() - 1)) / imageUrls.size();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制标题(垂直居中)
|
|
|
|
|
|
|
|
float titleY = startY - totalHeight / 2 + (titleLines.size() * LINE_HEIGHT) / 2;
|
|
|
|
|
|
|
|
cs.beginText();
|
|
|
|
|
|
|
|
cs.setFont(font, FONT_SIZE);
|
|
|
|
|
|
|
|
cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY);
|
|
|
|
|
|
|
|
for (String line : titleLines) {
|
|
|
|
|
|
|
|
cs.showText(line);
|
|
|
|
|
|
|
|
titleY -= LINE_HEIGHT;
|
|
|
|
|
|
|
|
cs.newLineAtOffset(0, -LINE_HEIGHT);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
cs.endText();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制所有图片,采用与单/双图片一致的样式
|
|
|
|
|
|
|
|
float currentY = startY - CELL_PADDING;
|
|
|
|
|
|
|
|
for (int i = 0; i < imageUrls.size(); i++) {
|
|
|
|
|
|
|
|
PDImageXObject image = loadImageFromUrl(doc, imageUrls.get(i));
|
|
|
|
|
|
|
|
if (image == null) continue; // 跳过加载失败的图片
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float scale = Math.min(
|
|
|
|
|
|
|
|
imageAvailableWidth / image.getWidth(),
|
|
|
|
|
|
|
|
imageHeightPer / image.getHeight()
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
float scaledHeight = image.getHeight() * scale;
|
|
|
|
|
|
|
|
float scaledWidth = image.getWidth() * scale;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否需要跨页
|
|
|
|
|
|
|
|
if (currentY - scaledHeight - CELL_PADDING < TABLE_MARGIN) {
|
|
|
|
|
|
|
|
// 当前页面空间不足,需要跨页
|
|
|
|
|
|
|
|
// 这里需要在实际应用中处理跨页逻辑
|
|
|
|
|
|
|
|
// 由于在单个drawImageGroup方法中无法直接创建新页面,
|
|
|
|
|
|
|
|
// 需要在调用此方法的循环中处理跨页
|
|
|
|
|
|
|
|
break; // 暂停绘制剩余图片,由外部循环处理跨页
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制当前图片
|
|
|
|
|
|
|
|
cs.drawImage(image,
|
|
|
|
|
|
|
|
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
|
|
|
|
currentY - scaledHeight,
|
|
|
|
|
|
|
|
scaledWidth, scaledHeight);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 更新下一个图片的起始Y坐标
|
|
|
|
|
|
|
|
currentY -= (scaledHeight + CELL_PADDING);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// private static void drawImageGroup(PDPageContentStream cs, PDDocument doc, PDType0Font font,
|
|
|
|
|
|
|
|
// List<String> titleLines, List<String> imageUrls,
|
|
|
|
|
|
|
|
// float startY, float totalHeight) throws IOException {
|
|
|
|
|
|
|
|
// // 绘制单元格边框
|
|
|
|
|
|
|
|
// cs.setLineWidth(0.5f);
|
|
|
|
|
|
|
|
// cs.addRect(TABLE_MARGIN, startY - totalHeight, LEFT_COL_WIDTH, totalHeight);
|
|
|
|
|
|
|
|
// cs.addRect(TABLE_MARGIN + LEFT_COL_WIDTH, startY - totalHeight, RIGHT_COL_WIDTH, totalHeight);
|
|
|
|
|
|
|
|
// cs.stroke();
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 计算每张图片的可用高度(根据图片数量动态调整)
|
|
|
|
|
|
|
|
// float imageAvailableHeight = totalHeight - 2 * CELL_PADDING;
|
|
|
|
|
|
|
|
// float imageAvailableWidth = RIGHT_COL_WIDTH - 2 * CELL_PADDING;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// if (imageUrls.size() == 1) {
|
|
|
|
|
|
|
|
// // 单图片模式:一页两行,每行占半页高度
|
|
|
|
|
|
|
|
// float singleImageHeight = imageAvailableHeight;
|
|
|
|
|
|
|
|
// // 绘制标题(垂直居中)
|
|
|
|
|
|
|
|
// float titleY = startY - totalHeight/2 + (titleLines.size() * LINE_HEIGHT)/2;
|
|
|
|
|
|
|
|
// // 绘制第一行标题(顶部)
|
|
|
|
|
|
|
|
// // float titleY = startY - CELL_PADDING - FONT_SIZE;
|
|
|
|
|
|
|
|
// cs.beginText();
|
|
|
|
|
|
|
|
// cs.setFont(font, FONT_SIZE);
|
|
|
|
|
|
|
|
// cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY);
|
|
|
|
|
|
|
|
// for (String line : titleLines) {
|
|
|
|
|
|
|
|
// cs.showText(line);
|
|
|
|
|
|
|
|
// titleY -= LINE_HEIGHT;
|
|
|
|
|
|
|
|
// cs.newLineAtOffset(0, -LINE_HEIGHT);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// cs.endText();
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制第一行图片(自动缩放)
|
|
|
|
|
|
|
|
// PDImageXObject image1 = PDImageXObject.createFromByteArray(doc,
|
|
|
|
|
|
|
|
// new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image");
|
|
|
|
|
|
|
|
// float scale1 = Math.min(
|
|
|
|
|
|
|
|
// imageAvailableWidth / image1.getWidth(),
|
|
|
|
|
|
|
|
// singleImageHeight / image1.getHeight()
|
|
|
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
// float scaledHeight1 = image1.getHeight() * scale1;
|
|
|
|
|
|
|
|
// float scaledWidth1 = image1.getWidth() * scale1;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// cs.drawImage(image1,
|
|
|
|
|
|
|
|
// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
|
|
|
|
// startY - CELL_PADDING - scaledHeight1,
|
|
|
|
|
|
|
|
// scaledWidth1, scaledHeight1);
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// } else if (imageUrls.size() == 2) {
|
|
|
|
|
|
|
|
// // 双图片模式:改为上下布局
|
|
|
|
|
|
|
|
// float imageHeightPer = (imageAvailableHeight - CELL_PADDING) / 2;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制标题(垂直居中)
|
|
|
|
|
|
|
|
// float titleY = startY - totalHeight/2 + (titleLines.size() * LINE_HEIGHT)/2;
|
|
|
|
|
|
|
|
// cs.beginText();
|
|
|
|
|
|
|
|
// cs.setFont(font, FONT_SIZE);
|
|
|
|
|
|
|
|
// cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY);
|
|
|
|
|
|
|
|
// for (String line : titleLines) {
|
|
|
|
|
|
|
|
// cs.showText(line);
|
|
|
|
|
|
|
|
// titleY -= LINE_HEIGHT;
|
|
|
|
|
|
|
|
// cs.newLineAtOffset(0, -LINE_HEIGHT);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// cs.endText();
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制第一张图片(上)
|
|
|
|
|
|
|
|
// PDImageXObject image1 = PDImageXObject.createFromByteArray(doc,
|
|
|
|
|
|
|
|
// new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image");
|
|
|
|
|
|
|
|
// float scale1 = Math.min(
|
|
|
|
|
|
|
|
// imageAvailableWidth / image1.getWidth(),
|
|
|
|
|
|
|
|
// imageHeightPer / image1.getHeight()
|
|
|
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
// float scaledHeight1 = image1.getHeight() * scale1;
|
|
|
|
|
|
|
|
// float scaledWidth1 = image1.getWidth() * scale1;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// cs.drawImage(image1,
|
|
|
|
|
|
|
|
// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
|
|
|
|
// startY - CELL_PADDING - scaledHeight1,
|
|
|
|
|
|
|
|
// scaledWidth1, scaledHeight1);
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制第二张图片(下)
|
|
|
|
|
|
|
|
// PDImageXObject image2 = PDImageXObject.createFromByteArray(doc,
|
|
|
|
|
|
|
|
// new URL(imageUrls.get(1)).openStream().readAllBytes(), "embedded-image");
|
|
|
|
|
|
|
|
// float scale2 = Math.min(
|
|
|
|
|
|
|
|
// imageAvailableWidth / image2.getWidth(),
|
|
|
|
|
|
|
|
// imageHeightPer / image2.getHeight()
|
|
|
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
// float scaledHeight2 = image2.getHeight() * scale2;
|
|
|
|
|
|
|
|
// float scaledWidth2 = image2.getWidth() * scale2;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// cs.drawImage(image2,
|
|
|
|
|
|
|
|
// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
|
|
|
|
// startY - CELL_PADDING - scaledHeight1 - CELL_PADDING - scaledHeight2,
|
|
|
|
|
|
|
|
// scaledWidth2, scaledHeight2);
|
|
|
|
|
|
|
|
// /* // 双图片模式:一行并列显示
|
|
|
|
|
|
|
|
// float imageWidthPer = (imageAvailableWidth - CELL_PADDING) / 2;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制标题(垂直居中)
|
|
|
|
|
|
|
|
// float titleY = startY - totalHeight/2 + (titleLines.size() * LINE_HEIGHT)/2;
|
|
|
|
|
|
|
|
// cs.beginText();
|
|
|
|
|
|
|
|
// cs.setFont(font, FONT_SIZE);
|
|
|
|
|
|
|
|
// cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY);
|
|
|
|
|
|
|
|
// for (String line : titleLines) {
|
|
|
|
|
|
|
|
// cs.showText(line);
|
|
|
|
|
|
|
|
// titleY -= LINE_HEIGHT;
|
|
|
|
|
|
|
|
// cs.newLineAtOffset(0, -LINE_HEIGHT);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// cs.endText();
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制第一张图片(左)
|
|
|
|
|
|
|
|
// PDImageXObject image1 = PDImageXObject.createFromByteArray(doc,
|
|
|
|
|
|
|
|
// new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image");
|
|
|
|
|
|
|
|
// float scale1 = Math.min(
|
|
|
|
|
|
|
|
// imageWidthPer / image1.getWidth(),
|
|
|
|
|
|
|
|
// imageAvailableHeight / image1.getHeight()
|
|
|
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
// float scaledHeight1 = image1.getHeight() * scale1;
|
|
|
|
|
|
|
|
// float scaledWidth1 = image1.getWidth() * scale1;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// cs.drawImage(image1,
|
|
|
|
|
|
|
|
// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
|
|
|
|
// startY - CELL_PADDING - scaledHeight1,
|
|
|
|
|
|
|
|
// scaledWidth1, scaledHeight1);
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制第二张图片(右)
|
|
|
|
|
|
|
|
// PDImageXObject image2 = PDImageXObject.createFromByteArray(doc,
|
|
|
|
|
|
|
|
// new URL(imageUrls.get(1)).openStream().readAllBytes(), "embedded-image");
|
|
|
|
|
|
|
|
// float scale2 = Math.min(
|
|
|
|
|
|
|
|
// imageWidthPer / image2.getWidth(),
|
|
|
|
|
|
|
|
// imageAvailableHeight / image2.getHeight()
|
|
|
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
// float scaledHeight2 = image2.getHeight() * scale2;
|
|
|
|
|
|
|
|
// float scaledWidth2 = image2.getWidth() * scale2;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// cs.drawImage(image2,
|
|
|
|
|
|
|
|
// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING + imageWidthPer + CELL_PADDING,
|
|
|
|
|
|
|
|
// startY - CELL_PADDING - scaledHeight2,
|
|
|
|
|
|
|
|
// scaledWidth2, scaledHeight2);*/
|
|
|
|
|
|
|
|
// }else {
|
|
|
|
|
|
|
|
// // 多图片模式(超过2张图片):垂直堆叠显示,与双图片模式保持一致的样式
|
|
|
|
|
|
|
|
// float imageHeightPer = (imageAvailableHeight - CELL_PADDING * (imageUrls.size() - 1)) / imageUrls.size();
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制标题(垂直居中)
|
|
|
|
|
|
|
|
// float titleY = startY - totalHeight / 2 + (titleLines.size() * LINE_HEIGHT) / 2;
|
|
|
|
|
|
|
|
// cs.beginText();
|
|
|
|
|
|
|
|
// cs.setFont(font, FONT_SIZE);
|
|
|
|
|
|
|
|
// cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY);
|
|
|
|
|
|
|
|
// for (String line : titleLines) {
|
|
|
|
|
|
|
|
// cs.showText(line);
|
|
|
|
|
|
|
|
// titleY -= LINE_HEIGHT;
|
|
|
|
|
|
|
|
// cs.newLineAtOffset(0, -LINE_HEIGHT);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// cs.endText();
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制所有图片,采用与单/双图片一致的样式
|
|
|
|
|
|
|
|
// float currentY = startY - CELL_PADDING;
|
|
|
|
|
|
|
|
// for (int i = 0; i < imageUrls.size(); i++) {
|
|
|
|
|
|
|
|
// PDImageXObject image = PDImageXObject.createFromByteArray(doc,
|
|
|
|
|
|
|
|
// new URL(imageUrls.get(i)).openStream().readAllBytes(), "embedded-image");
|
|
|
|
|
|
|
|
// float scale = Math.min(
|
|
|
|
|
|
|
|
// imageAvailableWidth / image.getWidth(),
|
|
|
|
|
|
|
|
// imageHeightPer / image.getHeight()
|
|
|
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
// float scaledHeight = image.getHeight() * scale;
|
|
|
|
|
|
|
|
// float scaledWidth = image.getWidth() * scale;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 检查是否需要跨页
|
|
|
|
|
|
|
|
// if (currentY - scaledHeight - CELL_PADDING < TABLE_MARGIN) {
|
|
|
|
|
|
|
|
// // 当前页面空间不足,需要跨页
|
|
|
|
|
|
|
|
// // 这里需要在实际应用中处理跨页逻辑
|
|
|
|
|
|
|
|
// // 由于在单个drawImageGroup方法中无法直接创建新页面,
|
|
|
|
|
|
|
|
// // 需要在调用此方法的循环中处理跨页
|
|
|
|
|
|
|
|
// break; // 暂停绘制剩余图片,由外部循环处理跨页
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制当前图片
|
|
|
|
|
|
|
|
// cs.drawImage(image,
|
|
|
|
|
|
|
|
// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
|
|
|
|
|
|
|
|
// currentY - scaledHeight,
|
|
|
|
|
|
|
|
// scaledWidth, scaledHeight);
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 更新下一个图片的起始Y坐标
|
|
|
|
|
|
|
|
// currentY -= (scaledHeight + CELL_PADDING);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// }
|
|
|
|
private static float calculateAdjustedImagesHeight(PDDocument doc, List<String> imageUrls,
|
|
|
|
private static float calculateAdjustedImagesHeight(PDDocument doc, List<String> imageUrls,
|
|
|
|
float maxWidth, float maxHeight) throws IOException {
|
|
|
|
float maxWidth, float maxHeight) throws IOException {
|
|
|
|
if (imageUrls.isEmpty()) return 0;
|
|
|
|
if (imageUrls.isEmpty()) return 0;
|
|
|
|
@ -388,9 +777,23 @@ public class PDFGenerator {
|
|
|
|
if (imageUrls.size() == 1) {
|
|
|
|
if (imageUrls.size() == 1) {
|
|
|
|
// 单图片模式:占用半页高度
|
|
|
|
// 单图片模式:占用半页高度
|
|
|
|
return maxHeight / 2;
|
|
|
|
return maxHeight / 2;
|
|
|
|
} else {
|
|
|
|
} else if(imageUrls.size() == 2){
|
|
|
|
// 双图片模式:整页高度
|
|
|
|
// 双图片模式:整页高度
|
|
|
|
return maxHeight;
|
|
|
|
return maxHeight;
|
|
|
|
|
|
|
|
}else {
|
|
|
|
|
|
|
|
// 多图片模式:根据图片数量动态计算高度
|
|
|
|
|
|
|
|
// 每张图片预留最小高度,防止页面过长
|
|
|
|
|
|
|
|
float minImageHeight = 50f; // 每张图片最小高度
|
|
|
|
|
|
|
|
float imageSpacing = 5f; // 图片间距
|
|
|
|
|
|
|
|
float totalSpacing = (imageUrls.size() - 1) * imageSpacing;
|
|
|
|
|
|
|
|
float estimatedHeight = (minImageHeight * imageUrls.size()) + totalSpacing;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果估算高度超过页面可用高度,则返回最大高度,需要分页处理
|
|
|
|
|
|
|
|
if (estimatedHeight > maxHeight) {
|
|
|
|
|
|
|
|
return maxHeight;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return estimatedHeight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -652,13 +1055,13 @@ public class PDFGenerator {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static float addImageRow(PDPageContentStream cs, PDDocument doc,
|
|
|
|
private static float addImageRow(PDPageContentStream cs, PDDocument doc,
|
|
|
|
PDType0Font font, String title, String imageUrl,
|
|
|
|
PDType0Font font, String title, String imageUrl,
|
|
|
|
float startX, float startY) throws IOException {
|
|
|
|
float startX, float startY) throws IOException {
|
|
|
|
// 加载图片
|
|
|
|
// 加载图片
|
|
|
|
PDImageXObject image = PDImageXObject.createFromByteArray(doc,
|
|
|
|
PDImageXObject image = loadImageFromUrl(doc, imageUrl);
|
|
|
|
new URL(imageUrl).openStream().readAllBytes(), "embedded-image");
|
|
|
|
if (image == null) return startY; // 如果图片加载失败,返回原位置
|
|
|
|
|
|
|
|
|
|
|
|
// 动态计算尺寸
|
|
|
|
// 动态计算尺寸
|
|
|
|
float imageWidth = RIGHT_COL_WIDTH - 10;
|
|
|
|
float imageWidth = RIGHT_COL_WIDTH - 10;
|
|
|
|
float scale = imageWidth / image.getWidth();
|
|
|
|
float scale = imageWidth / image.getWidth();
|
|
|
|
@ -725,6 +1128,79 @@ public class PDFGenerator {
|
|
|
|
return startY - dynamicRowHeight - 10;*/
|
|
|
|
return startY - dynamicRowHeight - 10;*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// private static float addImageRow(PDPageContentStream cs, PDDocument doc,
|
|
|
|
|
|
|
|
// PDType0Font font, String title, String imageUrl,
|
|
|
|
|
|
|
|
// float startX, float startY) throws IOException {
|
|
|
|
|
|
|
|
// // 加载图片
|
|
|
|
|
|
|
|
// PDImageXObject image = PDImageXObject.createFromByteArray(doc,
|
|
|
|
|
|
|
|
// new URL(imageUrl).openStream().readAllBytes(), "embedded-image");
|
|
|
|
|
|
|
|
// // 动态计算尺寸
|
|
|
|
|
|
|
|
// float imageWidth = RIGHT_COL_WIDTH - 10;
|
|
|
|
|
|
|
|
// float scale = imageWidth / image.getWidth();
|
|
|
|
|
|
|
|
// float imageHeight = image.getHeight() * scale;
|
|
|
|
|
|
|
|
// float rowHeight = imageHeight + 20;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制单元格边框
|
|
|
|
|
|
|
|
// cs.setLineWidth(0.5f);
|
|
|
|
|
|
|
|
// cs.addRect(startX, startY - rowHeight, LEFT_COL_WIDTH, rowHeight);
|
|
|
|
|
|
|
|
// cs.addRect(startX + LEFT_COL_WIDTH, startY - rowHeight, RIGHT_COL_WIDTH, rowHeight);
|
|
|
|
|
|
|
|
// cs.stroke();
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 垂直居中文本(改进算法)
|
|
|
|
|
|
|
|
// float textY = startY - rowHeight/2 + FONT_SIZE/2;
|
|
|
|
|
|
|
|
// //String title = "安装完成探测器工作图片\n检测时间:2025-08-14";
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// /* if(title.contains("\n")){
|
|
|
|
|
|
|
|
// String[] lines = title.split("\n");
|
|
|
|
|
|
|
|
// textY = startY - rowHeight/2 + FONT_SIZE/2 + (lines.length-1)*FONT_SIZE/2;
|
|
|
|
|
|
|
|
// }*/
|
|
|
|
|
|
|
|
// drawCenteredText(cs, font, FONT_SIZE, title,
|
|
|
|
|
|
|
|
// startX + LEFT_COL_WIDTH/2, textY);
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制图片(带5pt边距)
|
|
|
|
|
|
|
|
// cs.drawImage(image,
|
|
|
|
|
|
|
|
// startX + LEFT_COL_WIDTH + 5,
|
|
|
|
|
|
|
|
// startY - rowHeight + 10,
|
|
|
|
|
|
|
|
// imageWidth, imageHeight);
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// return startY - rowHeight - 10;
|
|
|
|
|
|
|
|
// /*// 计算图片尺寸(保持比例)
|
|
|
|
|
|
|
|
// float imageWidth = RIGHT_COL_WIDTH - 10;
|
|
|
|
|
|
|
|
// float scale = imageWidth / image.getWidth();
|
|
|
|
|
|
|
|
// float imageHeight = image.getHeight() * scale;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 动态计算行高(图片高度+上下边距20)
|
|
|
|
|
|
|
|
// float dynamicRowHeight = imageHeight + 20;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制左侧标题单元格
|
|
|
|
|
|
|
|
// cs.setLineWidth(0.5f);
|
|
|
|
|
|
|
|
// cs.addRect(TABLE_MARGIN, startY - dynamicRowHeight, LEFT_COL_WIDTH, dynamicRowHeight);
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 绘制右侧图片单元格(高度与左侧同步)
|
|
|
|
|
|
|
|
// cs.addRect(TABLE_MARGIN + LEFT_COL_WIDTH, startY - dynamicRowHeight, RIGHT_COL_WIDTH, dynamicRowHeight);
|
|
|
|
|
|
|
|
// cs.stroke();
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 居中绘制标题文本
|
|
|
|
|
|
|
|
// float titleWidth = font.getStringWidth(title) / 1000 * FONT_SIZE;
|
|
|
|
|
|
|
|
// float titleX = TABLE_MARGIN + (LEFT_COL_WIDTH - titleWidth) / 2;
|
|
|
|
|
|
|
|
// float titleY = startY - (dynamicRowHeight / 2) - (FONT_SIZE / 3);
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// cs.beginText();
|
|
|
|
|
|
|
|
// cs.setFont(font, FONT_SIZE);
|
|
|
|
|
|
|
|
// cs.newLineAtOffset(titleX, titleY);
|
|
|
|
|
|
|
|
// cs.showText(title);
|
|
|
|
|
|
|
|
// cs.endText();
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 居中绘制图片
|
|
|
|
|
|
|
|
// float imageX = TABLE_MARGIN + LEFT_COL_WIDTH + 5;
|
|
|
|
|
|
|
|
// float imageY = startY - dynamicRowHeight + 10;
|
|
|
|
|
|
|
|
// cs.drawImage(image, imageX, imageY, imageWidth, imageHeight);
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// // 返回新的Y坐标(当前位置减去行高和间距)
|
|
|
|
|
|
|
|
// return startY - dynamicRowHeight - 10;*/
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
/* private static float addImageRow(PDPageContentStream cs, PDDocument doc,
|
|
|
|
/* private static float addImageRow(PDPageContentStream cs, PDDocument doc,
|
|
|
|
PDType0Font font, String title, String imageUrl, float x, float y) throws IOException {
|
|
|
|
PDType0Font font, String title, String imageUrl, float x, float y) throws IOException {
|
|
|
|
// 绘制标题行
|
|
|
|
// 绘制标题行
|
|
|
|
|