杜洪波
2025-09-17 a4d27567652c7cd81eeb0d4628cc55ffb07f8167
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
package com.product.file.util;
 
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
 
import javax.imageio.ImageIO;
 
import org.apache.fontbox.ttf.TrueTypeCollection;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper;
 
public class PDFOperateUtil {
 
    
    public static void createPdfWithCode(String javaFilePath, String outputPath) throws IOException {
        String javaCode = readJavaFileWithComments(javaFilePath);
        
        try (PDDocument document = new PDDocument()) {
            PDPage page = new PDPage(PDRectangle.A4);
            document.addPage(page);
     
            // 加载字体(如 SimSun)
            File fontFile = getSystemFontFile();
            if (!fontFile.exists()) {
                throw new IOException("未找到中文字体文件");
            }
            TrueTypeCollection ttc = new TrueTypeCollection(fontFile);
         // 通过字体名称获取(如 "SimSun")
            PDType0Font font = PDType0Font.load(document, ttc.getFontByName("SimSun"), true);
//            PDType0Font font = PDType0Font.load(document, fontFile);
            float fontSize = 10;
            float margin = 50;
            float leading = 1.2f * fontSize; // 行距
     
            // 可用宽度 = 页面宽度 - 左右边距
            PDRectangle mediaBox = page.getMediaBox();
            float availableWidth = mediaBox.getWidth() - 2 * margin;
     
            // 初始 Y 位置(从顶部开始)
            float yPosition = mediaBox.getHeight() - margin;
            float startX = margin;
     
            // 分割代码为行
            String[] lines = javaCode.split("\n");
     
            PDPageContentStream contentStream = null;
            try {
                contentStream = new PDPageContentStream(document, page);
                contentStream.setFont(font, fontSize);
     
                for (String line : lines) {
                    // 如果当前行超出页面底部,换页
                    if (yPosition < margin) {
                        contentStream.close();
                        page = new PDPage(PDRectangle.A4);
                        document.addPage(page);
                        yPosition = mediaBox.getHeight() - margin;
                        contentStream = new PDPageContentStream(document, page);
                        contentStream.setFont(font, fontSize);
                    }
     
                    // 处理超宽行:按字符拆分并换行
                    while (!line.isEmpty()) {
                        // 计算当前行能容纳的最大字符数
                        int maxChars = calculateMaxChars(font, fontSize, line, availableWidth);
                        String subLine = line.substring(0, Math.min(maxChars, line.length()));
                        line = line.substring(subLine.length());
     
                        // 写入当前子行
                        writeTextLine(contentStream, subLine, startX, yPosition);
                        yPosition -= leading;
     
                        // 如果剩余内容仍超出页面底部,换页
                        if (yPosition < margin && !line.isEmpty()) {
                            contentStream.close();
                            page = new PDPage(PDRectangle.A4);
                            document.addPage(page);
                            yPosition = mediaBox.getHeight() - margin;
                            contentStream = new PDPageContentStream(document, page);
                            contentStream.setFont(font, fontSize);
                        }
                    }
                }
            } finally {
                if (contentStream != null) {
                    contentStream.close();
                }
            }
     
            document.save(outputPath);
        }
    }
 
    // 计算一行能容纳的最大字符数
    private static int calculateMaxChars(PDType0Font font, float fontSize, String text, float availableWidth) throws IOException {
        float width = 0;
        int i = 0;
        for (; i < text.length(); i++) {
            width += font.getWidth(text.charAt(i)) / 1000 * fontSize;
            if (width > availableWidth) {
                break;
            }
        }
        return i;
    }
 
    // 写入单行文本(左对齐)
    private static void writeTextLine(PDPageContentStream contentStream, String text, float x, float y) throws IOException {
        contentStream.beginText();
        contentStream.newLineAtOffset(x, y);
        contentStream.showText(text);
        contentStream.endText();
    }
    
    /**
     * 读取 Java 文件内容(保留注释和空行)
     */
    private static String readJavaFileWithComments(String filePath) throws IOException {
        StringBuilder codeBuilder = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                line = line.replace("\t", "    ");
                // 保留所有行(包括注释和空行)
                codeBuilder.append(line).append("\n");
            }
        }
        return codeBuilder.toString();
    }
    
    /**
     * 计算字体高度(基于字体的 Ascent 和 Descent)
     */
    private static float calculateFontHeight(PDType0Font font, float fontSize) {
        float ascent = font.getFontDescriptor().getAscent();
        float descent = font.getFontDescriptor().getDescent();
        return (ascent - descent) / 1000 * fontSize;
    }
 
    /**
     * 根据文本文件创建 PDF(使用 PDFBox)
     * 
     * @param textFilePath 输入文本文件路径
     * @param pdfFilePath  输出 PDF 文件路径
     * @throws IOException
     */
    public static void createPdfFromTextFile(String textFilePath, String pdfFilePath) throws IOException {
        String content = readTextFile(textFilePath);
        content = content.replace("\t", "    ");
        try (PDDocument document = new PDDocument()) {
            PDPage page = new PDPage(PDRectangle.A4);
            document.addPage(page);
            
            // 获取字体文件(仅支持 Windows/Linux)
            File fontFile = getSystemFontFile();
            if (!fontFile.exists()) {
                throw new IOException("未找到支持的中文字体文件(Windows/Linux)");
            }
            TrueTypeCollection ttc = new TrueTypeCollection(fontFile);
 
         // 通过字体名称获取(如 "SimSun")
            PDType0Font font = PDType0Font.load(document, ttc.getFontByName("SimSun"), true);
 
            // 加载字体
//            PDType0Font font;
//            try (FileInputStream fontStream = new FileInputStream(fontFile)) {
//                font = PDType0Font.load(document, fontStream);
//            }
 
            // 写入 PDF 内容
            try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
                contentStream.beginText();
                contentStream.setFont(font, 12);
                contentStream.newLineAtOffset(50, 700);
 
                String[] lines = content.split("\n");
                for (String line : lines) {
                    contentStream.showText(line);
                    contentStream.newLineAtOffset(0, -15);
                }
                contentStream.endText();
            }
 
            document.save(pdfFilePath);
        }
    }
 
    /**
     * 读取 PDF 内容(使用 PDFBox)
     * 
     * @param pdfFilePath PDF 文件路径
     * @return 提取的文本内容
     * @throws IOException
     */
    public static String readPdfContent(String pdfFilePath) throws IOException {
        try (PDDocument document = PDDocument.load(new File(pdfFilePath))) {
            PDFTextStripper stripper = new PDFTextStripper();
            return stripper.getText(document);
        }
    }
 
    /**
     * 将 PDF 转换为图片(使用 PDFBox)
     * 
     * @param pdfFilePath    PDF 文件路径
     * @param outputFolder    输出图片文件夹
     * @param imageName        输出图片文件名
     * @param imageFormat    图片格式(如 "png", "jpg")
     * @throws IOException
     */
    public static void convertPdfToImages(String pdfFilePath, String outputFolder, String imageName, String imageFormat) throws IOException {
        try (PDDocument document = PDDocument.load(new File(pdfFilePath))) {
            PDFRenderer renderer = new PDFRenderer(document);
 
            // 确保输出文件夹存在
            new File(outputFolder).mkdirs();
 
            // 逐页渲染为图片(300 DPI)
            for (int page = 0; page < document.getNumberOfPages(); page++) {
                BufferedImage image = renderer.renderImageWithDPI(page, 300);
                String outputFileName = String.format("%s/%s%d.%s", outputFolder, imageName, page + 1, imageFormat);
                ImageIO.write(image, imageFormat, new File(outputFileName));
            }
        }
    }
 
    /**
     * 辅助方法:读取文本文件内容
     * 
     * @param filePath 文本文件路径
     * @return 文件内容
     * @throws IOException
     */
    private static String readTextFile(String filePath) throws IOException {
        StringBuilder content = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
        }
        return content.toString();
    }
    
    /**
     * 获取系统字体文件(仅 Windows/Linux)
     */
    private static File getSystemFontFile() {
        String os = System.getProperty("os.name").toLowerCase();
        String[] possiblePaths;
 
        if (os.contains("win")) {
            // Windows 字体目录(优先检查常见中文字体)
            possiblePaths = new String[]{
//                System.getenv("WINDIR") + "\\Fonts\\arial.ttf",  // 
//                System.getenv("WINDIR") + "\\Fonts\\simkai.ttf",  // 楷体
//                System.getenv("WINDIR") + "\\Fonts\\simfang.ttf",  // 宋体
                System.getenv("WINDIR") + "\\Fonts\\simsun.ttc",  // 宋体
                System.getenv("WINDIR") + "\\Fonts\\simhei.ttf",  // 黑体
                System.getenv("WINDIR") + "\\Fonts\\msyh.ttc",   // 微软雅黑
                System.getenv("WINDIR") + "\\Fonts\\mingliu.ttc"  // 明体(备用)
            };
        } else {
            // Linux 字体目录(优先检查开源中文字体)
            possiblePaths = new String[]{
                "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",  // 文泉驿微米黑
                "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",  // 思源黑体
                "/usr/share/fonts/truetype/arphic/ukai.ttc"       // 文鼎字体(备用)
            };
        }
 
        // 检查所有可能的路径
        for (String path : possiblePaths) {
            File fontFile = new File(path);
            if (fontFile.exists()) {
                return fontFile;
            }
        }
 
        // 如果系统字体未找到,尝试从项目资源目录加载(需手动放入字体文件)
        File embeddedFont = new File("fonts/simhei.ttf"); // 假设字体文件放在项目根目录的 fonts/ 文件夹下
        return embeddedFont.exists() ? embeddedFont : null;
    }
 
    // 示例用法
    public static void main(String[] args) {
        try {
            // 1. 从文本文件创建 PDF
//            createPdfFromTextFile("E://新建1.txt", "E://新建1.pdf");
            createPdfWithCode("E:\\PROJECT\\Product\\后端\\product\\product-server\\product-server-file\\src\\main\\java\\com\\product\\file\\util\\FileUtil.java", "E://新建1.pdf");
 
            // 2. 读取 PDF 内容
            String content = readPdfContent("E://新建1.pdf");
            System.out.println("PDF 内容:\n" + content);
 
            // 3. 将 PDF 转换为图片
            convertPdfToImages("E://新建1.pdf", "E://", "新建1", "png");
 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}