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

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

引入

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

核心讲解

布局语义

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

分页决策发生在布局阶段

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

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

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

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

依赖与示例资源

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

关键细节

样式属性

分页块通常同时使用 paddingmarginBottomborderbackgroundColorkeepTogetherwidthheightminHeightmaxHeight 控制盒子尺寸;文字使用 fontSizefontColorboldtextAlignment。尺寸单位支持 pxptmmcmin。边框简写按仓库示例使用类型、宽度和颜色组合,例如 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,使用固定长文本、不同字体和临界高度样例,再结合表格、htmlPageBreakareaBreak 研究整份报告的章节编排。

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

相关推荐
vx_BS813301 小时前
【项目编号:project51381】Spring Boot 婚纱摄影管理系统:套餐预约、在线咨询、支付与客片展示的一站式业务实现
java·spring boot·eclipse·tomcat·mybatis
IT毕设实战小研1 小时前
基于大数据的商场商铺数据分析与可视化的设计与实现
android·java·大数据·django·课程设计
万年咸鱼1 小时前
Java Classpath 详解:从原理到实战
java
Ivanqhz1 小时前
矩阵引擎的数据流模式与 BM1684X 架构
java·服务器·网络·深度学习·神经网络
小蒜学长1 小时前
大学生健康饮食的智慧管理系统(代码+数据库+LW)
java·后端·springboot·大学生·健康饮食
计算机毕设定制辅导-无忧学长1 小时前
《基于SpringBoot的中学教师数字胜任力测评网站的设计与实现》
java·vue.js·spring boot·mysql·中学教师数字胜任力测评网站
智慧物业老杨1 小时前
人机协同的物业服务重构:技术落地路径与系统化思考
java·大数据·人工智能·重构·系统架构
小蒜学长1 小时前
基于Java的论坛数据可视化分析系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·数据可视化·论坛系统