问任何一个人怎么在浏览器里给PDF做暗黑模式,得到的答案几乎都是同一个套路:用pdf.js把页面渲染到canvas上,加一行filter: invert(1),完事。这个方案在每一个相关讨论帖里都是标准答案,但它在三个层面上都是错的。
前两个错误在第一张截图里就暴露了。照片直接变成胶片负片效果。而且整个效果只是"穿在观众身上的一件外套"——把文件发给同事,或者在平板上打开,又变回刺眼的白色,因为你压根没动过文档本身。
![]()
第三个错误花了我最长时间才接受。255 - x并不是"这个颜色的暗黑版本",它是补色。一个深海军蓝标题rgb(20, 40, 120)反转后变成rgb(235, 215, 135),那是卡其色。文档里每一个彩色元素都会以错误的色调呈现,而且无论怎么调参都修不好,因为这个操作本身回答的问题跟你问的就不是同一个。
从像素处理转向源码重写
我和大多数人一样,从滤镜的下一步开始尝试:把页面栅格化,对位图跑一个更聪明的逐像素映射,然后输出。这确实有效,至今仍是我代码里的兜底方案。但文档的位图不是文档,之后你想要的每一个属性都得手工重建。最终我彻底不再把页面当作图片来处理。下面是页面底层的真实结构。
一个PDF页面的/Contents是一个内容流:一串操作数加操作符的字节序列,像栈语言一样被求值。
0 0 0 rg % 设置填充色为黑色(DeviceRGB)BT /F1 12 Tf 72 700 Td (Hello) Tj ET % 绘制文本1 0 0 RG % 设置描边色为红色72 690 m 300 690 l S % 绘制一条线一旦看清这个结构,暗黑模式就不再是图像处理问题,而变成了源码重写问题。你不是重新绘制图片,而是找到设置颜色的指令,改写这些指令,其余字节原样透传。文本操作符照常运行,字形仍然是字形。
这一改变带来的属性是canvas滤镜永远无法拥有的:输出文本可选中、可搜索,矢量图形在800%缩放时依然锐利,文件体积几乎不变——因为你从未栅格化任何东西。
绝对操作符的处理
绝对操作符很简单,可以按操作数数量列成一张表:
const COLOR_OPS: Record = { g: 1, G: 1, // DeviceGray rg: 3, RG: 3, // DeviceRGB k: 4, K: 4, // DeviceCMYK};小写是填充,大写是描边。读取操作数,通过你的颜色函数映射,输出替换值。
然后是sc / scn,它在当前选中的颜色空间中设置颜色。这个空间由之前的cs / CS操作符选定,指向页面/ColorSpace资源字典中的一个条目。所以0.2 0.4 0.9 scn可能是三个RGB分量,也可能完全是别的东西——不解析上下文你根本无从判断。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.