PDF图层系统OCG的隐藏玩法:不只是显示隐藏那么简单
文章摘要
深入探讨PDF可选内容组(Optional Content Groups)的高级应用场景,包括条件渲染、打印预览差异化、动态水印,以及如何用OCG实现"同一文件多种视图"的黑科技。
上个月接了个需求:客户要在同一个PDF里给不同角色展示不同内容。销售看到价格表,技术支持看到参数详情,管理层看到汇总数据。一开始我想着生成三个文件,后来发现PDF有个叫OCG(可选内容组)的特性,完美解决问题。
OCG是什么?
Optional Content Groups,可选内容组,PDF 1.5引入的特性。简单说就是给PDF内容打上"标签",然后根据条件决定显示还是隐藏。类似Photoshop的图层,但功能强大得多。
常见翻译是"PDF图层",但这个叫法容易误导——OCG的能力远超传统意义上的图层。
基础结构:OCG在PDF里长什么样
每个OCG本质上是一个字典对象,定义在PDF的根对象里:
% 定义OCG
10 0 obj
/Type /OCG
/Name (价格信息)
/Intent /View
/Usage
/Print << /PrintState /OFF >>
/View << /ViewState /ON >>
>>
>>
endobj
% 在页面内容中使用
/OC /MC0 BDC % 开始OCG内容块
BT
/F1 12 Tf
50 700 Td
(内部价格: ¥2999) Tj
ET
EMC % 结束OCG内容块
这段代码定义了一个名为"价格信息"的OCG,屏幕上显示但打印时隐藏。核心机制是/OC标记包裹的内容块。
高级玩法一:条件可见性
OCG支持复杂的逻辑表达式,可以实现"A且B"、"A或B"、"非A"等组合:
/OCGs [10 0 R 11 0 R]
/Type /OCMD
/P /AllOn % 所有OCG都开启时才显示
/OCGs [10 0 R 11 0 R]
/Type /OCMD
/P /AnyOn % 任一OCG开启时就显示
/OCGs [10 0 R]
/Type /OCMD
/P /AllOff % OCG关闭时才显示(反向逻辑)
利用这个特性,可以实现"互斥图层":
% 定义"销售视图"和"技术视图"为互斥
/OCProperties
/OCGs [10 0 R 11 0 R] % 销售、技术
/D
/Order [10 0 R 11 0 R]
/RBGroups [[10 0 R 11 0 R]] % 互斥组定义
>>
>>
这样用户在PDF阅读器里只能同时开启一个视图,自动关闭另一个。
高级玩法二:基于缩放的自适应内容
OCG可以绑定到缩放级别。这意味着你可以在不同缩放比例下显示不同详细程度的内容:
• 缩放 < 50%:只显示主要道路和地标
• 缩放 50%-100%:显示次要道路和建筑轮廓
• 缩放 > 100%:显示所有细节,包括门牌号
实现方式是通过/Zoom属性:
/Type /OCG
/Name (详细信息)
/Usage
/Zoom
/min 1.0 % 缩放≥100%时显示
>>
>>
>>
高级玩法三:打印与屏幕显示分离
这是我最喜欢的功能。可以让某些内容只在屏幕上显示,打印时自动消失(或反之):
• 交互式按钮
• 导航链接
• 视频播放器占位符
• "请勿打印"水印
• 裁切标记
• 色彩校准条
• 页码和页眉页脚
• "机密"印章
配置代码:
% 仅屏幕显示
/Type /OCG
/Name (交互元素)
/Usage
/View << /ViewState /ON >>
/Print << /PrintState /OFF >>
>>
>>
% 仅打印显示
/Type /OCG
/Name (打印标记)
/Usage
/View << /ViewState /OFF >>
/Print << /PrintState /ON >>
>>
>>
实战:用Python生成带OCG的PDF
PyPDF2不支持创建OCG,但ReportLab可以。这里是个完整示例:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
def create_layered_pdf(filename):
c = canvas.Canvas(filename, pagesize=letter)
# 创建OCG(销售视图)
sales_layer = c.beginLayer('销售信息')
c.setFont('Helvetica', 12)
c.drawString(100, 700, '内部价格: ¥2999')
c.endLayer()
# 创建OCG(公开视图)
public_layer = c.beginLayer('公开信息')
c.drawString(100, 670, '市场价格: ¥3999')
c.endLayer()
# 普通内容(总是显示)
c.drawString(100, 750, '产品规格书')
# 配置OCG属性
c._doc.Catalog.OCProperties = {
'OCGs': [sales_layer, public_layer],
'D': {
'Order': [sales_layer, public_layer],
'RBGroups': [[sales_layer, public_layer]], # 互斥
'ON': [public_layer], # 默认显示公开信息
}
}
c.save()
create_layered_pdf('product_spec.pdf')
生成的PDF在Adobe Acrobat里可以看到图层面板,用户可以切换视图。
不是所有PDF阅读器都完整支持OCG。Chrome的内置PDF查看器会忽略图层设置,全部显示。Firefox稍好,但条件逻辑支持不完整。想要完整体验,还得用Adobe Acrobat或Foxit Reader。
骚操作:动态水印
结合OCG和JavaScript(是的,PDF支持JavaScript),可以实现动态水印:
% 在PDF里嵌入JavaScript
/Names
/JavaScript
/Names [
(CheckAuth)
/S /JavaScript
/JS (
var authLayer = this.getOCGs()[0];
var user = app.response("请输入授权码:");
if (user != "SECRET123") {
authLayer.state = false; % 关闭授权内容层
}
)
>>
]
>>
>>
这样打开PDF时会弹窗要授权码,输错了就看不到敏感内容。当然,这种"保护"很容易绕过(直接编辑PDF去掉JS),但能防君子不防小人。
实际应用场景
调试OCG的工具
OCG是PDF里最被低估的特性之一。大部分人只知道它能做图层开关,却不知道它背后是一套完整的条件渲染系统。掌握了OCG,你可以用一个PDF文件实现以前需要多个版本才能做到的事。下次遇到"同一内容给不同人看"的需求,别急着生成N个文件——试试OCG,会有惊喜。