jquick-pdf 防止 PDF 内容分页断裂:keepTogether 属性妙用

jquick-pdf 防止 PDF 内容分页断裂:keepTogether 属性妙用

引入

业务 PDF 的分页问题通常不是"能不能换页",而是"应该在哪里换页"。订单摘要的标题落在页尾、金额卡片被拆成两半、审批结论和签名区分开,都会让文件显得不专业。直接用 PdfBox 绘制时,需要自行计算剩余高度并决定是否新建页面;内容一旦动态变化,坐标判断很快变得脆弱。jquick-pdf 提供 keepTogether,让模板可以明确表达"这个块尽量整体开始"。

核心讲解

布局语义

keepTogether 是写在元素 style 中的样式属性,值使用 true 或 false,只对块级元素有意义,尤其是包含标题、字段和结论的 <div>。它的语义是布局意图而不是绝对约束:当块被拆开时,布局引擎会倾向于把整块移到下一个有足够空间的位置开始。

分页决策发生在布局阶段

渲染块之前,布局引擎会估算该块所需空间,并检查当前光标与页面底部边距的关系。当剩余空间不足且块设置了 keepTogether:true 时,引擎先创建新页,再从新页开始绘制。这个策略把分页决策放在布局阶段,而不是等文字已经画到页尾以后再回滚。理解这一点很重要:keepTogether 保护的是块的起始位置和整体布局意图,它不能让一份超过整页的内容永不分页。

自然分页与强制分页的区别

  • keepTogether 属于自然分页:内容仍按顺序流动,只是调整某个块的起始页。
  • <htmlPageBreak> 与 <areaBreak> 属于显式分页,会直接从指定位置开始新页。

前者是自然布局中的空间控制,后者是强制换页方式,三者职责不同:不要为了避免一个小卡片断裂,就把整份文档都插入强制分页,那样容易产生大片空白。

依赖与示例资源

核心依赖仍是 jquick-pdfx。分页相关示例如 style/style.txt 中的 keepTogether:true,布局目录还有 htmlPageBreak.txt 和 areaBreak.txt。

关键细节

样式属性

分页块通常同时使用 padding、marginBottom、border、backgroundColor 和 keepTogether。width、height、minHeight、maxHeight 控制盒子尺寸;文字使用 fontSize、fontColor、bold 和 textAlignment。尺寸单位支持 px、pt、mm、cm、in。边框简写按仓库示例使用类型、宽度和颜色组合,例如 border:solid 1px #333,不要照搬浏览器中未经验证的写法。

临界高度的判断

布局引擎的空间判断存在临界点,因此只用"短样例"验证是不够的。验证时应准备三组数据:刚好能放下的短数据、只差几行空间的临界数据,以及远超一页的长数据。观察重点不是文件是否生成成功,而是新页是否按预期出现、边框和内边距是否完整、后续正文是否继续绘制。PDF 视觉回归比单纯断言字节数组更有价值,因为分页问题通常体现在位置和空白区域上。

内容超过一页时的降级策略

第一,块内容大于整页时,keepTogether 无法创造空间,只能拆分模板,把长内容切成多个小段。第二,过多使用会造成大片留白,页面效果甚至比自然断页更糟。第三,动态字体、长客户名称和多行地址都会改变估算高度,不能只用短样例验证。第四,keepWithNext 在样式模型中可解析,但当前测试说明其渲染消费边界需要自行验证,不能把它当作本篇分页方案的替代品。第五,生成失败时要保留模板版本和订单编号以便复现,但不要输出完整客户隐私。

分层设计:外层小块、内层自然流动

实际设计可以采用"外层小块、内层可流动"的方式。审批卡片中的标题、审批人和结论放在一个 keepTogether 的 div 中;结论正文如果可能很长,就拆成独立的 p 或多个小段。这样既保护了卡片的识别信息,又不会因为一段异常长的意见导致整块无法放置。订单明细则按订单或商品分组,分组标题与第一行摘要放在一起,明细主体允许正常分页。长表格不要把所有行包进一个 keepTogether div,应以表头、分组或小节为单位。

实战说明

依赖

xml 复制代码
<dependency>
    <!-- 引入分页布局和 PDF 渲染能力 -->
    <groupId>io.github.paohaijiao</groupId>
    <artifactId>jquick-pdfx</artifactId>
    <version>4.0.0</version>
</dependency>

环境为 JDK 8+。建议先用固定高度和固定数据验证分页,再加入真实订单明细和长文本。

KeepTogetherDemo

java 复制代码
import com.github.paohaijiao.executor.JQuickPdfFactory;
import java.nio.file.Files;
import java.nio.file.Paths;

public class KeepTogetherDemo {
    public static void main(String[] args) throws Exception {
        String template = "<pdf><body>"
                + "<h1>'订单确认单'</h1>"
                // 用正文把光标推近分页边界,便于观察分页行为
                + "<p>'以下是订单处理说明,正文用于把光标推近分页边界。'</p>"
                + "<p>'系统将在审核完成后通知客户。'</p>"
                // 整块设置 keepTogether,避免摘要被拆开
                + "<div style=\"keepTogether:true;border:solid 1px #333;padding:12px\">"
                + "<h2>'订单摘要'</h2>"
                + "<p>'订单号:'${orderNo}</p>"
                + "<p>'客户:'${customer}</p>"
                + "<p>'含税金额:'${amount}</p>"
                + "<p>'处理状态:'${status}</p>"
                + "</div>"
                + "<p>'摘要之后的说明从下一个可用位置继续。'</p>"
                + "</body></pdf>";
        // 绑定订单数据并渲染
        byte[] pdf = JQuickPdfFactory.create()
                .bind("orderNo", "SO-20260914-001")
                .bind("customer", "示例客户")
                .bind("amount", "12,800.00 元")
                .bind("status", "待审核")
                .executeContent(template);
        // 保存 PDF 结果
        Files.write(Paths.get("keep-together.pdf"), pdf);
    }
}

需要区分模板属性与 Java API:keepTogether 不属于工厂方法,只能写在元素的 style 里。executeContent 适合最小验证,资源模板可用 executeResource。

适用场景

适合保护的对象是"标题 + 少量内容"的完整业务块:订单摘要、审批结论、风险提示、签名栏和报告结论。先让正文自然流动,只有在块确实被拆开时才加 keepTogether。若必须从某个业务节点开始新页,可以使用示例中的 <htmlPageBreak> 或 <areaBreak>,但强制分页会增加空白页和版式波动风险,自然分页与强制分页应当组合使用而不是互相替代。这一策略可用于订单确认、发票说明、审批意见、信用报告结论、合同条款摘要和签名区。

方案对比与性能

手写 iText/PdfBox 需要业务代码计算剩余高度,灵活但维护成本高;jquick-pdf 用样式表达布局意图,适合规则块和模板协作。浏览器 CSS 的 page-break-inside 不能直接推断为 jquick-pdf 属性,必须使用仓库实际的 keepTogether:若需要复杂分页算法、目录和 PDF 对象控制,底层库可能更合适;若重点是稳定生成业务页面,模板方案更清晰。

性能方面,分页估算本身通常不是瓶颈,超长文本、图片和大量块才会放大布局成本。把一个巨型报告拆成语义清晰的小块,避免给每个节点都设置 keepTogether;批量任务控制并发和最大页数,给异常长字段设置业务上限。压测时同时覆盖"刚好放下""差一点放不下""块超过整页"三种情况,观察耗时、文件大小和空白比例。

总结

keepTogether 不是"禁止分页"的开关,而是给布局引擎的明确提示:这个完整业务块应尽量从有足够空间的位置开始。它适合保护整块识别信息,配合外层小块加内层自然流动的分层设计,分页才会既稳定又不浪费页面。

常见误区是把 keepTogether 当成绝对约束,或为避免一处断裂而给整份文档插入强制分页;此外 keepWithNext 当前只确认了解析层能力,不能作为分页承诺。建议把分页测试加入 CI,使用固定长文本、不同字体和临界高度样例,再结合表格、htmlPageBreak 和 areaBreak 研究整份报告的章节编排。

版本基线:jquick-pdfx 4.0.0、JDK 8+,升级前请核对 README_zh.md 的版本对照表(不同版本的渲染引擎与许可证边界可能不同);更多示例见 GitHub 仓库。

相关推荐
零基础1236 小时前
Ubuntu 常用命令汇总
linux·运维·开发语言
外收内放6 小时前
Python基础语法练习题(57-58)
开发语言·python
时间的拾荒人6 小时前
Qt 界面美化实战:QSS 样式表
开发语言·qt·面试
Joy T6 小时前
Spring AI 接入已有 Java 项目的三种架构设计
java·人工智能·springai·ai入门·chatclient·ai service·ai能力接入
杨运交7 小时前
[076][核心模块]构建优雅的Java异常处理框架:从错误码到全局异常处理
java·开发语言
caoerzhong7 小时前
仓库数字化成熟度分级:JeeWMS 开源 Java 仓库管理系统如何支撑从可见到自治的四级跃迁
java·开源
可乐鸡翅yeah_7 小时前
video.js 集成 hls.js 开发 M3U8 播放器,新手高频踩坑
开发语言·前端·javascript·后端·ecmascript·m3u8·音视频在线播放
MayBaymax7 小时前
RocketMQ 存储机制
java·rocketmq
小鹿的周先生7 小时前
第11章-Structured-Output
开发语言·人工智能·python
程序猿_极客7 小时前
【免费】分享一套优质的基于SpringBoot的服装商城管理系统的设计与实现(带可视化图表、协同过滤功能),源码+文档+视频详解(讲解)
java·spring boot·后端·服装商城管理系统