21 SVG 矢量图形嵌入:两条绑定路径与兼容边界
引入
运营大屏截图嵌入 PDF 后,缩放时文字模糊、线条出现锯齿;订单统计、组织拓扑、设备示意图这类内容用位图保存本身就是信息损失。矢量 SVG 在任意缩放下都能保持清晰。jquick-pdf 的文档层 jquick-pdfx 已支持 <svg> 元素,本文基于仓库中已验证的 special/svg.txt、sample/svg2.txt 两个模板,说明把服务端生成的 SVG 字符串嵌入 PDF 的两条路径,以及各自的适用范围。
环境为 JDK 8+、Maven 3.6+;文档层只依赖 io.github.paohaijiao:jquick-pdfx(4.1.0),若 SVG 来自图表模块再增加 io.github.paohaijiao:jquick-pdf-svg。
核心讲解
两条嵌入路径
<svg> 的内容可以来自变量绑定,也可以来自已注册资源:
${svg}:变量替换。用bind("svg", svgText)把 SVG 字符串放进上下文,模板写<svg>${svg}</svg>。&{svg}:资源引用。把对象注册为名为svg的资源,模板写<svg>&{svg}</svg>。
仓库两个已验证模板分别对应这两条路径:special/svg.txt 使用 ${svg},sample/svg2.txt 使用 &{svg}。README 的图表 Demo 通过 JGraphConfig.put("svg", graphContainer) 注册资源,再以 &{svg} 读取;绑定 Demo 则直接 factory.bind("svg", svgText)。两者在 <svg> 元素内都可用。
选择依据很直接:SVG 字符串由本次业务动态拼接、只服务这一份文档时用 ${svg},链路最短,不需要额外配置对象;SVG 来自图表模块或需要复用同一份资源时用 &{svg},注册一次可被多处引用,也便于与 JPdfConfig、JGraphConfig 的图表配置统一管理。
入口方法与模板
入口类是 com.github.paohaijiao.executor.JQuickPdfFactory,new JQuickPdfFactory() 与 JQuickPdfFactory.create() 等价。绑定用 bind(String,Object) 或 bindAll(Map<String,Object>)。执行有三个方法,均返回 byte[]:executeContent(String) 执行内存中的模板文本,executeResource(String) 读取 classpath 资源,executeFile(String) 读取磁盘模板文件。
模板根元素为 <pdf> 或 <html>,常用 <pdf><body>…</body></pdf>。文本字面量必须用单引号,变量用 ${name},已注册资源用 &{name}。
完整示例
import com.github.paohaijiao.executor.JQuickPdfFactory;
import java.nio.file.Files;
import java.nio.file.Paths;
public class SvgPdfDemo {
public static void main(String[] args) throws Exception {
// 服务端拼接 SVG 字符串,只使用基础 rect / text,兼容性最好
String svg = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"360\" height=\"180\">"
+ "<rect width=\"360\" height=\"180\" fill=\"#f4f7fb\"/>"
+ "<text x=\"24\" y=\"35\" font-size=\"20\" fill=\"#222\">月度订单</text>"
+ "<rect x=\"30\" y=\"120\" width=\"45\" height=\"35\" fill=\"#4169e1\"/>"
+ "<rect x=\"100\" y=\"80\" width=\"45\" height=\"75\" fill=\"#4169e1\"/>"
+ "<rect x=\"170\" y=\"50\" width=\"45\" height=\"105\" fill=\"#4169e1\"/>"
+ "</svg>";
String template = "<pdf><body><h1>'销售看板'</h1><svg>${svg}</svg>"
+ "<p>'矢量图由服务端生成'</p></body></pdf>";
// bind 注入上下文,executeContent 返回 PDF 字节数组
byte[] pdf = new JQuickPdfFactory().bind("svg", svg).executeContent(template);
Files.write(Paths.get("svg-report.pdf"), pdf);
}
}
效果
资源引用路径只改模板与调用方式:模板写成 <svg>&{svg}</svg>,注册资源后执行 executeResource("sample/svg2.txt") 或对应的模板路径。
关键细节
兼容范围
已确认可用的是基础 SVG 图形,示例中的 rect、text 是代表性元素。脚本、交互逻辑与外部资源引用不在支持范围内:PDF 是静态载体,<script> 与事件绑定不会执行;外部 CSS、外链图片和外部字体在服务端渲染时可能不可见或被忽略。不要按浏览器的 SVG 能力推断渲染结果,滤镜、动画、foreignObject 等特性需要逐项在当前版本实测。
合法性与转义
SVG 必须是合法 XML,未转义的 &(例如查询串里的 &x=1)会导致解析失败;属性值中的引号也要与 Java 字符串转义配合。建议在服务端先用任意 XML 解析器校验一次拼接结果,再交给渲染器。
中文字体
SVG 内 <text> 的中文显示依赖渲染环境。字体应通过 jquick-pdf-font 或项目已注册字体统一管理,不要假设 SVG 内的 font-family 名称一定命中;命中失败时中文可能显示为方框。字体名与回退规则以目标运行环境为准。
尺寸与版面对齐
SVG 自身的 width/height 与 PDF 页面可用宽度不匹配时,会出现裁切或留白。<svg> 元素本身也参与布局,可通过 width、height、margin*、padding* 约束,两处尺寸要一起核对。
复用与体积
同一图形在多处出现时应优先复用:把 SVG 注册为资源、模板多处引用,或用 bindAll 一次性绑定多个变量,避免在每个块里重复拼接整段字符串。字符串越大,解析与渲染开销越高,PDF 体积也随之增加。对固定图表的结果做缓存,并限制单个 SVG 字符串的大小。
实战说明
步骤
- 服务端生成 SVG 字符串,包含
xmlns、宽度高度与基础图形,去除脚本与外链。 - 建立
<pdf><body>骨架,把占位符放在目标位置:<svg>${svg}</svg>或<svg>&{svg}</svg>。 - 用
bind("svg", svg)注入字符串,或注册资源;图表场景通过JGraphConfig.put("svg", container)注册。 - 调用
executeContent/executeResource取得byte[],写出文件。
验证与结果预期
生成后放大到 200% 以上检查:
- 矩形边缘与标题在放大后是否仍锐利,出现马赛克说明实际落成了位图。
- 文本是否可选中,可选中说明走的是文本绘制而非图片嵌入。
- SVG 宽度与页面宽度是否一致,出现右侧截断或大面积留白说明尺寸没有对齐。
- 中文是否正常显示,出现方框即字体未命中。
再用 PDF 解析器做页数、文件大小与文本存在性检查,可把上述验证纳入自动化。
异常排查
- 解析失败:优先检查 SVG 是否为合法 XML、
&是否未转义。 - 画面空白:确认
bind的键名与模板变量名一致;资源引用时确认注册名一致。 - 元素缺失:检查是否引用了外部 CSS、图片或字体,这些不在支持范围内。
- 中文异常:确认字体资源已引入或已注册。
适用场景
财务图表、流程图、拓扑图与印刷型标签适合用 SVG。图表模块 jquick-pdf-svg 提供 30+ 种图表并以矢量 SVG 渲染,可先输出 SVG 再以变量绑定进入文档;仓库的 JSvgTest 与 SvgImage 也表明 SVG 可以经布局层绘制,这条路线适合需要在 PDFBox 布局引擎中直接落图的场景。模板绑定与直接绘制各有适用面,同一份报告不必混用两种路径。
总结
SVG 嵌入有两条路径:${svg} 绑定字符串、&{svg} 引用已注册资源,二者在 <svg> 中均可用,选择取决于是否需要在图表配置体系中复用资源。可用性边界应保守理解——基础图形兼容最好,脚本与外部资源引用不在支持范围内;SVG 内中文显示依赖字体注册;尺寸要与页面可用宽度对齐。
常见误区是把浏览器的 SVG 表现直接当成渲染结果,或忽略字符串体积对解析开销与文件大小的影响。验证时同时看清晰度、文本可选中性和中文显示,比只看截图可靠。
版本基线:jquick-pdfx 4.0.0、JDK 8+,升级前请核对 README_zh.md 的版本对照表;更多示例见 GitHub 仓库。