安卓Java环境下使用PDFbox实现PDF动态分页的技术咨询
PDFBox 动态分页功能实现方案
问题背景
我开发了一款将文本存储在Room数据库的应用,用户可选择导出从数据库查询到的文本生成PDF文件。使用PDFBox创建PDF文件时,PDFBox本身不支持自动分页,需要手动实现动态分页逻辑,现有基础代码如下:
现有文件写入核心代码
public void writeDataIntoFile() { if (adapter.getItemCount()> 0) { String filePath = createFile(); Log.d("saving pdf", filePath); PDFBoxResourceLoader.init(getApplicationContext()); PDDocument document = new PDDocument(); PDPage page = new PDPage(PDRectangle.A4); PDFont font = PDType1Font.TIMES_ROMAN; document.addPage(page); PDPageContentStream contentStream; try { // 定义用于添加PDF内容的内容流 contentStream = new PDPageContentStream(document, page); contentStream.beginText(); contentStream.setStrokingColor(44, 61, 79); contentStream.setFont(font, 18); contentStream.setLeading(14.5f); ExecutorService executor = Executors.newSingleThreadExecutor(); Handler handler = new Handler(Looper.getMainLooper()); showProgressBar(); executor.execute(() -> { try { // 后台任务处理逻辑 //TODO 需要添加按分类返回单词的函数 contentStream.newLineAtOffset(25, 770); contentStream.showText("Category: " + categoryName); contentStream.newLine(); contentStream.newLine(); contentStream.setNonStrokingColor(15, 38, 192); contentStream.setFont(font, 12); for (Word word : adapter.getCurrentList()) { showMultiLineText(word.getWord() + ": " + word.getTranslation(), 550, contentStream,font, 12); contentStream.newLine(); count++; } contentStream.endText(); contentStream.close(); document.save(filePath); saved = true; document.close(); } catch (IOException e) { Log.d("error", String.valueOf(e)); } handler.post(() -> { // UI线程处理逻辑,更新进度条 hideProgressBar(); openingPDF(filePath); Toast.makeText(this, "the file has been saved in /Download/MemorizeWords", Toast.LENGTH_SHORT).show(); }); }); executor.shutdown(); } catch (IOException e) { Log.d("error", String.valueOf(e)); } } else { Toast.makeText(this, "This category has no words!", Toast.LENGTH_SHORT).show(); } }
现有文本自动换行实现
已实现文本宽度检测函数,可在文本超出页面宽度时自动拆分换行:
private void showMultiLineText(String text, int allowedWidth, PDPageContentStream contentStream, PDFont font, int fontSize) throws IOException { List<String> lines = new ArrayList<String>(); String line = ""; // 按空格拆分文本 String[] words = text.split(" "); for(String word : words) { if(!line.isEmpty()) { line += " "; } // 检查添加当前单词后是否超出页面宽度 int size = (int) (fontSize * font.getStringWidth(line + word) / 1000); if(size > allowedWidth) { // 超出宽度则添加当前行(不含当前单词)到行列表 lines.add(line); // 用当前单词开启新行 line = word; } else { // 未超出宽度则将当前单词加入行 line += word; } } lines.add(line); for(String ln : lines) { count = lines.size(); contentStream.showText(ln); contentStream.newLine(); } }
动态分页实现方案
核心逻辑
通过跟踪当前页面的剩余垂直空间实现分页:A4纸高度为842pt,预留上下边距各25pt的情况下,页面有效内容高度范围为Y坐标25~770(PDF坐标原点在页面左下角),每次输出内容前计算需要占用的高度,剩余高度不足时新建页面即可。
改造步骤
- 新增分页处理方法,用于创建新页面、重置内容流和文本样式
- 修改
showMultiLineText方法,返回当前文本占用的行数,用于计算高度消耗 - 在遍历写入单词前新增剩余高度校验,不足时触发分页
完整修改后代码
首先修改showMultiLineText方法,返回占用行数:
private int showMultiLineText(String text, int allowedWidth, PDPageContentStream contentStream, PDFont font, int fontSize) throws IOException { List<String> lines = new ArrayList<>(); String line = ""; String[] words = text.split(" "); for(String word : words) { if(!line.isEmpty()) { line += " "; } int size = (int) (fontSize * font.getStringWidth(line + word) / 1000); if(size > allowedWidth) { lines.add(line); line = word; } else { line += word; } } lines.add(line); for(String ln : lines) { contentStream.showText(ln); contentStream.newLine(); } // 返回本次输出的行数 return lines.size(); }
然后修改writeDataIntoFile方法,新增分页逻辑:
// 定义全局常量 private static final float PAGE_BOTTOM_MARGIN = 25f; private static final float PAGE_TOP_MARGIN = 770f; private static final float LEADING = 14.5f; private float currentY; // 跟踪当前Y坐标 private PDDocument document; private PDFont font; private PDPageContentStream contentStream; public void writeDataIntoFile() { if (adapter.getItemCount() <= 0) { Toast.makeText(this, "This category has no words!", Toast.LENGTH_SHORT).show(); return; } String filePath = createFile(); Log.d("saving pdf", filePath); PDFBoxResourceLoader.init(getApplicationContext()); document = new PDDocument(); font = PDType1Font.TIMES_ROMAN; // 初始化第一页 createNewPage(); ExecutorService executor = Executors.newSingleThreadExecutor(); Handler handler = new Handler(Looper.getMainLooper()); showProgressBar(); executor.execute(() -> { try { contentStream.beginText(); contentStream.newLineAtOffset(25, currentY); // 写入分类标题 contentStream.setStrokingColor(44, 61, 79); contentStream.setFont(font, 18); contentStream.showText("Category: " + categoryName); contentStream.newLine(); contentStream.newLine(); currentY -= LEADING * 2; // 标题占2行 // 写入单词列表样式设置 contentStream.setNonStrokingColor(15, 38, 192); contentStream.setFont(font, 12); for (Word word : adapter.getCurrentList()) { String content = word.getWord() + ": " + word.getTranslation(); // 先计算当前内容需要占用的行数 int lineCount = calculateLineCount(content, 550, font, 12); // 检查剩余高度是否足够 if (currentY - lineCount * LEADING < PAGE_BOTTOM_MARGIN) { // 不足就新建页面 contentStream.endText(); contentStream.close(); createNewPage(); contentStream.beginText(); contentStream.newLineAtOffset(25, currentY); // 新页面重新设置样式 contentStream.setNonStrokingColor(15, 38, 192); contentStream.setFont(font, 12); } // 写入内容 showMultiLineText(content, 550, contentStream, font, 12); currentY -= lineCount * LEADING; contentStream.newLine(); currentY -= LEADING; } contentStream.endText(); contentStream.close(); document.save(filePath); saved = true; document.close(); } catch (IOException e) { Log.d("error", String.valueOf(e)); } handler.post(() -> { hideProgressBar(); openingPDF(filePath); Toast.makeText(this, "the file has been saved in /Download/MemorizeWords", Toast.LENGTH_SHORT).show(); }); }); executor.shutdown(); } // 新建页面方法 private void createNewPage() throws IOException { PDPage page = new PDPage(PDRectangle.A4); document.addPage(page); contentStream = new PDPageContentStream(document, page); contentStream.setLeading(LEADING); currentY = PAGE_TOP_MARGIN; } // 计算文本占用行数的方法,避免提前写入才知道行数 private int calculateLineCount(String text, int allowedWidth, PDFont font, int fontSize) throws IOException { List<String> lines = new ArrayList<>(); String line = ""; String[] words = text.split(" "); for(String word : words) { if(!line.isEmpty()) { line += " "; } int size = (int) (fontSize * font.getStringWidth(line + word) / 1000); if(size > allowedWidth) { lines.add(line); line = word; } else { line += word; } } lines.add(line); return lines.size(); }
内容的提问来源于stack exchange,提问作者Yasser Rostom
相关产品推荐
相关产品推荐

