PDFBox渲染生成pdf文档
使用PDFBox可以渲染生成pdf文档,并且自定义程度高,只是比较麻烦,pdf的内容位置都需要手动设置x(横向)和y(纵向)绝对位置,但是每个企业的单据都是不一样的,一般来说都会设置一个模板,然后内容再填充到适当位置,所以这个功能还是有用的
1. 实际效果

填充数据后效果

实现代码:
以下代码基于PDFBox依赖版本-2.0.23
public class Demo01 {public static void main(String[] args) throws Exception{// 设定中文字体File fontFile = new File("C:\\Windows\\Fonts\\simHei.ttf");try (PDDocument document = new PDDocument()) {PDType0Font load = PDType0Font.load(document, fontFile);PDPage page;for (int i = 0; i < 1; i++) {page = new PDPage();document.addPage(page);// 对具体PDPage设定内容try(PDPageContentStream contentStream = new PDPageContentStream(document, page)) {contentStream.setFont(load, 25);contentStream.beginText();// newLineAtOffset方法contentStream.newLineAtOffset(220, 750);contentStream.showText("借用出库打印单");contentStream.setFont(load, 12);contentStream.endText();// 仓库和会员渲染位置contentStream.beginText();contentStream.newLineAtOffset(80, 700); // 80,700contentStream.showText("仓库:");contentStream.newLineAtOffset(300, 0); //380,700contentStream.showText("会员:");contentStream.endText();// 销售员和操作人渲染位置contentStream.beginText();contentStream.newLineAtOffset(80, 675); // 80,675contentStream.showText("销售员:");contentStream.newLineAtOffset(300, 0); //380,675contentStream.showText("操作人:");contentStream.endText();// 操作时间位置contentStream.beginText();contentStream.newLineAtOffset(80, 650); // 80,650contentStream.showText("操作时间:");contentStream.endText();// ----------------实际内容-----------------------// 表头contentStream.beginText();contentStream.newLineAtOffset(80, 625); //80,625contentStream.showText("序号");contentStream.newLineAtOffset(40, 0); //120,625contentStream.showText("商品编号");contentStream.newLineAtOffset(80, 0); //200,625contentStream.showText("商品名称");contentStream.newLineAtOffset(70, 0); //270,625contentStream.showText("单位");contentStream.newLineAtOffset(40, 0); //310,625contentStream.showText("借出数量");contentStream.newLineAtOffset(70, 0); //380,625contentStream.showText("备注");contentStream.newLineAtOffset(100, 0); //480,625contentStream.showText("零售价");contentStream.endText();Map<String, String> contentMap = new HashMap<>();contentMap.put("序号", "1");contentMap.put("商品编号", "000212130023");contentMap.put("商品名称", "洗地机124123");contentMap.put("单位", "个");contentMap.put("借出数量", "13");contentMap.put("备注", "我是备注我是备注");contentMap.put("零售价", "1123300.34");fillContent(contentStream, contentMap, load);// 结尾结构渲染// 合计位置contentStream.beginText();contentStream.newLineAtOffset(80, 150); // 80,150contentStream.showText("合计");contentStream.endText();// 出库数量和总金额位置contentStream.beginText();contentStream.newLineAtOffset(110, 125); // 110,125contentStream.showText("出库数量:");contentStream.newLineAtOffset(270, 0); // 380,125contentStream.showText("总金额:");contentStream.endText();// 签名位置contentStream.beginText();contentStream.newLineAtOffset(80, 50); // 110,125contentStream.showText("签名:_______");contentStream.endText();// 模拟填充模板Map<String, String> map = new HashMap<>();map.put("仓库", "上海仓");map.put("会员", "小明");map.put("销售员", "销售员01");map.put("操作人", "系统管理员");map.put("操作时间", "2025年4月1日23点07分");map.put("出库数量", "1455");map.put("总金额", "285743835.45");fillTemplate(contentStream, map);}}document.save("demo01.pdf");System.out.println("PDF created successfully!");} catch (IOException e) {throw new RuntimeException(e);}}// 填充固定模板方法 该方法不填充中间详细内容public static void fillTemplate(PDPageContentStream contentStream, Map<String, String> map) {try {contentStream.beginText();contentStream.newLineAtOffset(130, 700);contentStream.showText(map.get("仓库"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(430, 700);contentStream.showText(map.get("会员"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(130, 675);contentStream.showText(map.get("销售员"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(430, 675);contentStream.showText(map.get("操作人"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(150, 650);contentStream.showText(map.get("操作时间"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(180, 125);contentStream.showText(map.get("出库数量"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(430, 125);contentStream.showText(map.get("总金额"));contentStream.endText();} catch (IOException e) {throw new RuntimeException(e);}}public static void fillContent(PDPageContentStream contentStream, Map<String, String> map, PDType0Font font) {try {contentStream.setFont(font, 10);contentStream.beginText();contentStream.newLineAtOffset(80, 600);contentStream.showText(map.get("序号"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(120, 600);contentStream.showText(map.get("商品编号"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(200, 600);contentStream.showText(map.get("商品名称"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(270, 600);contentStream.showText(map.get("单位"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(310, 600);contentStream.showText(map.get("借出数量"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(380, 600);contentStream.showText(map.get("备注"));contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(480, 600);contentStream.showText(map.get("零售价"));contentStream.endText();contentStream.setFont(font, 12);} catch (IOException e) {throw new RuntimeException(e);}}
}
上述代码看着确实是挺繁琐,每个内容的位置都需要设置x和y值,但是没办法
PDF文件本质是「坐标画布」:
- PDF的渲染模型基于绝对坐标系(原点在页面左下角),所有元素(文字、图形)必须明确指定位置(x,y)。
- 无布局引擎:PDF规范未定义“自动换行”或“文档流”等高级排版概念,开发者需自行计算坐标。
但是这样设计的好处就是自定义程度高,你可以任意设计一个PDF文档的模板应该是什么样子,内容该如何填充全部由你自由设定,就像低代码平台一样,市面上成熟开源的低代码平台有许多,但是逻辑都是一开始就定好的,如果你想加上许多符合自己公司需求的功能但是平台没有那么都得自行开发,并且自行开发的代码融合进已有的系统不是一件容易的事情,甚至比自行开发一套系统都麻烦。
所以如果你有这样的需求可以看下上述代码实现,上述代码只是一个简单的demo,我只是进行记录方便自己以后用到。
tips:关于一些方法的解释
contentStream.beginText();contentStream.newLineAtOffset(80, 700); // 80,700 --绝对定位contentStream.showText("仓库:");contentStream.newLineAtOffset(300, 0); //380,700 --相对定位(以'仓库:'的位置为准)contentStream.showText("会员:");contentStream.endText();contentStream.beginText();contentStream.newLineAtOffset(80, 675); // 80,675 --绝对定位contentStream.showText("销售员:");contentStream.newLineAtOffset(300, 0); //380,675 --相对定位(以'销售员'的位置为准)contentStream.showText("操作人:");contentStream.endText();
上述代码可以看到在渲染内容时是被包裹在beginText()和endText()方法中间的,这样当你调用newLineAtOffset(x, y)方法时参数中的x和y才从坐标系的绝对位置(绝对位置为画布的左下角0,0)进行定位。如果你在定位时没有重新开启beginText()和endText()时,调用newLineAtOffset(x, y)方法则是参照上一个文本的位置进行相对定位的,相对定位对于需要在同一行的不同位置渲染内容会比较方便。
newLineAtOffset(x, y)方法的官方注释有问题,官方说法是移动到下一行的开头,从当前行的开头进行偏移 (x, y),实测不对,并不会移动到下一行的开头,并且在相对定位时参考的位置也是你上一次的位置的起始点。
如果你需要像写文章那样一段一段的文字进行渲染,那么可以考虑使用另外一个方法
contentStream.beginText();contentStream.newLineAtOffset(80, 500); // 设定绝对位置的起点contentStream.setLeading(20); // 文本行距contentStream.showText("XXXXX"); //渲染内容contentStream.newLine(); //开启新行contentStream.showText("XXXXX"); //渲染内容contentStream.newLine(); //开启新行contentStream.showText("XXXXX"); //渲染内容contentStream.newLine(); //开启新行contentStream.endText();
这个方法更适合大段连贯的文字渲染,你只要设定好固定行距之后就可以直接开启新行,新行的位置会成功进入到下一行的开头并且行距就是你设定的值,这样你就不用每次都自行定位了,效果如下

相关文章:
PDFBox渲染生成pdf文档
使用PDFBox可以渲染生成pdf文档,并且自定义程度高,只是比较麻烦,pdf的内容位置都需要手动设置x(横向)和y(纵向)绝对位置,但是每个企业的单据都是不一样的,一般来说都会设…...
Batch Normalization:深度学习训练的加速引擎
引言 在深度学习的发展历程中,训练深度神经网络一直是一项极具挑战性的任务。随着网络层数的增加,梯度消失、梯度爆炸以及训练过程中的内部协变量偏移(Internal Covariate Shift)问题愈发严重,极大地影响了模型的收敛…...
低空经济基础设施建设方向与展望
随着科技的不断进步,低空经济正逐渐成为推动国家经济发展的新引擎。低空经济,指的是在低空范围内进行的各种经济活动,包括但不限于无人机物流、空中交通管理、低空旅游、农业监测等。本文将探讨低空经济基础设施建设的方向与未来展望。 1. 低…...
如何保证RabbitMQ消息的可靠传输?
在这个图中,消息可能丢失的场景是1,2,3 1.在生产者将消息发送给RabbitMQ的时候,消息到底有没有正确的到达服务器呢,RabbitMQ提供了两种解决方案: a. 通过事务机制实现(比较消耗性能࿰…...
Kotlin语言进阶:协程、Flow、Channel详解(二)
Kotlin语言进阶:协程、Flow、Channel详解(二) 一、Flow基础 1.1 什么是Flow Flow是Kotlin提供的用于处理异步数据流的解决方案,它建立在协程之上,具有以下特点: 冷流特性:只有在收集时才会开始发射数据背压处理:自动处理生产者和消费者速度不匹配的问题组合操作:提…...
Sentinel核心源码分析(上)
文章目录 前言一、客户端与Spring Boot整合二、SphU.entry2.1、构建责任链2.2、调用责任链2.2.1、NodeSelectorSlot2.2.2、ClusterBuilderSlot2.2.3、LogSlot2.2.4、StatisticSlot2.2.5、AuthoritySlot2.2.6、SystemSlot2.2.7、FlowSlot2.2.7.1、selectNodeByRequesterAndStrat…...
Systemd安全加密备份系统与智能通知
实训背景 你是一家金融科技公司的系统架构师,需为敏感数据设计一套安全备份系统,满足以下需求: 加密存储:自动解密插入的LUKS加密USB设备,挂载到安全目录。备份验证:备份完成后校验文件完整性,…...
6.0 使用Qt+ OpenCV+Python加载图片
本例作为python图像处理的入门课程1,使用Qt+ OpenCV+Python加载图片。 主要有如下几个地方需要注意: 1. OpenCV 默认使用 BGR 格式,而 Qt 使用 RGB。显示前需要转换:cv2.cvtColor(img, cv2.COLOR_BGR2RGB),一般使用某个QLabel控件进行显示。 pic = cv2.cvtColor(pic, cv2.C…...
深度学习篇---网络分析(1)
文章目录 前言1. ImprovedResBlock(改进的残差块)结构组成卷积层1卷积层2跳跃连接(Downsample) 前向传播流程主路径跳跃路径残差连接 2. EnhancedCNN(主模型)2.1 初始特征提取层功能参数变化 2.2 残差块堆叠…...
【Mac 从 0 到 1 保姆级配置教程 11】- Mac 基础配置 Finder、触控板、常用快捷键等
文章目录 前言配置 Finder1. 把我们的家目录请出来2. 显示文件扩展名3. 展示隐藏文件4. 显示路径栏和状态栏5. 固定文件夹到工具栏 基础快捷键1. Finder 导航快捷键2. 文件操作快捷键3. 视图和显示快捷键4. 搜索和选择快捷键5. 实用技巧6. 关于文件创建 配置触控板1. 右键设置2…...
C++Primer - 动态内存管理
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
DeepSeek本地部署(Ollama)
1. Ollama 安装 Ollama 官网地址: https://ollama.com/安装包网盘地址: https://pan.baidu.com 2. Deepseek 部署 根据自己电脑配置和应用需求选择不同模型,配置不足会导致运行时候卡顿。 版本安装指令模型大小硬盘(存储)显卡…...
Amodal3R ,南洋理工推出的 3D 生成模型
Amodal3R 是一款先进的条件式 3D 生成模型,能够从部分可见的 2D 物体图像中推断并重建完整的 3D 结构与外观。该模型建立在基础的 3D 生成模型 TRELLIS 之上,通过引入掩码加权多头交叉注意力机制与遮挡感知注意力层,利用遮挡先验知识优化重建…...
第二期:深入理解 Spring Web MVC [特殊字符](核心注解 + 进阶开发)
前言: 欢迎来到 Spring Web MVC 深入学习 的第二期!在第一期中,我们介绍了 Spring Web MVC 的基础知识,学习了如何 搭建开发环境、配置 Spring MVC、编写第一个应用,并初步了解了 控制器、视图解析、请求处理流程 等核…...
论伺服电机在轨道式巡检机器人中的优势及应用实践
一、引言 1.1 研究背景与意义 在现代工业生产、电力系统、轨道交通等诸多领域,保障设施设备的安全稳定运行至关重要。轨道式巡检机器人作为一种高效、智能的巡检工具,正逐渐在这些领域崭露头角。它能够沿着预设轨道,对目标区域进行全方位…...
开源软件与自由软件:一场理念与实践的交锋
在科技的世界里,“开源软件”和“自由软件”这两个词几乎无人不知。很多人或许都听说过,它们的代码是公开的,可以供所有人查看、修改和使用。然而,若要细究它们之间的区别,恐怕不少朋友会觉得云里雾里。今天࿰…...
(51单片机)独立按键控制流水灯LED流向(独立按键教程)(LED使用教程)
源代码 如上图将7个文放在Keli5 中即可,然后烧录在单片机中就行了 烧录软件用的是STC-ISP,不知道怎么安装的可以去看江科大的视频: 【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】https://www.bilibili.com/video/BV1Mb411e7re?…...
开发指南111-关闭所有打开的子窗口
门户系统是通过window.open通过单点登录的模式打开子系统的,这就要求门户系统退出时,关闭所有打开的子系统。 平台处理这一问题的核心原理如下: 主窗口定义: allChildWindows:[], //所有子窗口 pushChildWindow(childWindow){ …...
react-router children路由报错
项目场景: 写个路由页面,引发的问题 问题描述 报错: An absolute child route path must start with the combined path of all its parent routes. 代码: import { createBrowserRouter } from "react-router-dom";…...
双向链表示例
#include <stdio.h> #include <stdlib.h>// 定义双向链表节点结构体 typedef struct list {int data; // 数据部分struct list *next; // 指向下一个节点的指针struct list *prev; // 指向前一个节点的指针 } list_t;// 初始化链表,将链表的…...
Socket编程TCP
Socket编程TCP 1、V1——EchoServer单进程版2、V2——EchoServer多进程版3、V3——EchoServer多线程版4、V4——EchoServer线程池版5、V5——多线程远程命令执行6、验证TCP——Windows作为client访问Linux7、connect的断线重连 1、V1——EchoServer单进程版 在TcpServer.hpp中实…...
当网页受到DDOS网络攻击有哪些应对方法?
分布式拒绝服务攻击也是人们较为熟悉的DDOS攻击,这类攻击会通过大量受控制的僵尸网络向目标服务器发送请求,以此来消耗服务器中的资源,致使用户无法正常访问,当网页受到分布式拒绝服务攻击时都有哪些应对方法呢? 建立全…...
文件映射mmap与管道文件
在用户态申请内存,内存内容和磁盘内容建立一一映射 读写内存等价于读写磁盘 支持随机访问 简单来说,把磁盘里的数据与内存的用户态建立一一映射关系,让读写内存等价于读写磁盘,支持随机访问。 管道文件:进程间通信机…...
4.4刷题记录(哈希表)
1.242. 有效的字母异位词 - 力扣(LeetCode) class Solution { public:bool isAnagram(string s, string t) {unordered_map<char,int>cnt_s,cnt_t;for(int i0;i<s.size();i){cnt_s[s[i]];}for(int i0;i<t.size();i){cnt_t[t[i]];}if(cnt_sc…...
代码随想录回溯算法03
93.复原IP地址 本期本来是很有难度的,不过 大家做完 分割回文串 之后,本题就容易很多了 题目链接/文章讲解:代码随想录 视频讲解:回溯算法如何分割字符串并判断是合法IP?| LeetCode:93.复原IP地址_哔哩哔…...
批量改CAD图层颜色——CAD c#二次开发
一个文件夹下大量图纸(几百甚至几千个文件)需要改图层颜色时,可采用插件实现,效果如下: 转换前: 转换后: 使用方式如下:netload加载此dll插件,输入xx运行。 附部分代码如…...
【内网安全】DHCP 饿死攻击和防护
正常情况:PC2可以正常获取到DHCP SERVER分别的IP地址查看DHCP SERCER 的ip pool地址池可以看到分配了一个地址、Total 253个 Used 1个 使用kali工具进行模拟攻击 进行DHCP DISCOVER攻击 此时查看DHCP SERVER d大量的抓包:大量的DHCP Discover包 此时模…...
【愚公系列】《高效使用DeepSeek》055-可靠性评估与提升
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...
AI时代编程教育启示录:为什么基础原理依然不可或缺?
李升伟 编译 在生成式AI重塑编程教育的今天,我作为拥有十年开发者关系团队管理经验、编程训练营教学经历的专业软件工程师,想与大家探讨这个新时代的编程教育之道。 平衡之道:基础原理与AI工具的博弈 当GitHub Copilot、Amazon Q Deve…...
10种电阻综合对比——《器件手册--电阻》
二、电阻 前言 10种电阻对比数据表 电阻类型 原理 特点 应用 贴片电阻 贴片电阻是表面贴装元件,通过将电阻体直接贴在电路板上实现电路连接 体积小、重量轻,适合高密度电路板;精度高、稳定性好,便于自动化生产 广泛应用于…...
