PDF.js 源码解读: scripting_utils
2026年10月27日 · StreamPDF Team

前言
src/shared/scripting_utils.js 是 PDF.js 脚本与批注路径共用的小型值转换层。它导出 ColorConverters、DateFormats 与 TimeFormats,本身并不执行 JavaScript action、解析日期或创建批注元素。
这种职责边界是有意为之。文件头提醒 display 与 scripting 构建都会导入它,随意增加依赖可能放大原本无关的构建产物。它唯一的导入是 MathClamp,实现也始终停留在数值转换与常量查询上。
模块总览
| 导出或辅助函数 | 是否导出 | 职责 |
|---|---|---|
makeColorComp(n) | 否 | 将归一化分量限制在 0–1,向下取整到 0–255,再格式化为两个十六进制字符 |
scaleAndClamp(x) | 否 | 将归一化分量限制在 0–1 并缩放到 0–255,不做取整 |
ColorConverters | 是 | 在带标签的 PDF.js 颜色、CSS 颜色与批注存储 RGB 值之间转换 |
DateFormats | 是 | 十四个按索引查询的 Acrobat 日期格式预设 |
TimeFormats | 是 | 四个按索引查询的 Acrobat 时间格式预设 |
源码在 ColorConverters 上方引用了 PDF 规范第 10.3 节。实现做的是直接分量运算,并非 ICC 颜色管理引擎,也没有加入颜色配置或 gamma 处理。
makeColorComp 与 scaleAndClamp:输入相似,输出不同
function makeColorComp(n) {
return Math.floor(MathClamp(n, 0, 1) * 255)
.toString(16)
.padStart(2, '0');
}
function scaleAndClamp(x) {
return MathClamp(x, 0, 1) * 255;
}两个辅助函数都接收归一化分量,并把它限制在 0 到 1 的闭区间内,但输出契约不同:
scaleAndClamp返回 0 到 255 的 number,并保留小数。中点会变成127.5。makeColorComp使用Math.floor,再为经过范围限制和取整的字节值返回恰好两个十六进制字符。同一个中点会得到7f,而不是80。
单元测试清楚展示了这种差别:
const color = [0.5];
expect(ColorConverters.G_rgb(color)).toEqual([127.5, 127.5, 127.5]);
expect(ColorConverters.G_HTML(color)).toEqual('#7f7f7f');两个辅助函数都不导出,也不校验非数值输入。上层脚本代码会在转换前检查颜色数组;不能把它们理解成通用 CSS 颜色解析器。
ColorConverters:真实存在的十四个转换方法
命名与返回形态
方法名采用 源_目标 形式。大写 G、RGB、CMYK 与 T 分别表示灰度、RGB、CMYK 和透明形式,目标名称决定返回形态:
| 目标 | 返回形态 | 在外层代码中的用途 |
|---|---|---|
G、RGB、CMYK | 第一项为目标标签的数组 | 脚本颜色转换 |
rgb | 0–255 分量的裸数组,透明时为 [null] | 批注存储值 |
HTML | CSS 十六进制颜色字符串 | 批注元素样式 |
Color.convert 等调用方会在调用转换器前移除源标签,因此方法只接收分量数组。下表保留当前源码的运算与返回值,不会仅凭命名规律虚构其他转换。
灰度与 RGB 方法
| 方法 | 实际行为 |
|---|---|
G_CMYK([g]) | 返回 ["CMYK", 0, 0, 0, 1 - g] |
G_RGB([g]) | 返回 ["RGB", g, g, g] |
G_rgb([g]) | 对 g 缩放一次,返回三个相同数值 |
G_HTML([g]) | 用 makeColorComp 转换 g,并在 #rrggbb 中重复该分量 |
RGB_G([r, g, b]) | 返回 ["G", 0.3 * r + 0.59 * g + 0.11 * b] |
RGB_rgb(color) | 将每个分量映射到 scaleAndClamp |
RGB_HTML(color) | 将分量映射到 makeColorComp,再在 # 后拼接 |
RGB_CMYK([r, g, b]) | 计算 c = 1-r、m = 1-g、y = 1-b、k = min(c,m,y),返回 ["CMYK", c, m, y, k] |
RGB_CMYK 会计算共享的黑色分量,但不会从 c、m、y 中减去它。这就是源码里的简单转换,并非更复杂的底色去除流程。
CMYK 方法及其字面参数顺序
| 方法 | 实际行为 |
|---|---|
CMYK_G([c, y, m, k]) | 返回 ["G", 1 - min(1, 0.3*c + 0.59*m + 0.11*y + k)] |
CMYK_RGB([c, y, m, k]) | 返回带 RGB 标签的红色 1-min(1,c+k)、绿色 1-min(1,m+k)、蓝色 1-min(1,y+k) |
CMYK_rgb([c, y, m, k]) | 对上述三个 RGB 分量公式分别执行 scaleAndClamp,返回裸数组 |
CMYK_HTML(components) | 调用 CMYK_RGB,移除标签,再把分量交给 RGB_HTML |
这些签名必须按字面阅读:三个 CMYK_* 输入方法都解构为 [c, y, m, k],而 RGB_CMYK 构造的是 ["CMYK", c, m, y, k]。这就是当前实现,单元测试预期也固定了相同行为。复用这些方法时,不应悄悄重命名位置,或替换成其他库的 CMYK 约定。
CMYK_HTML 还通过 this 调用其他转换器。现有的 ColorConverters[method](components) 动态调用会保留 receiver;把方法拆出来再在无 receiver 的情况下调用,会破坏这种内部委托。
透明方法刻意只有两种输出
static T_HTML() {
return "#00000000";
}
static T_rgb() {
return [null];
}只有这两个转换器以 T 为源。在 T_HTML 与 T_rgb 中,目标分别是 HTML 和 rgb:前者返回用八个十六进制字符表示的透明黑,后者返回存储标记 [null],而不是 CSS 字符串或 [0, 0, 0]。文件中没有 T_G、T_RGB 或 G_T。请求透明目标时走的是另一条路径:Color.convert 会在向 ColorConverters 分派之前直接返回 ["T"],所以目标侧没有对应转换器本身就是实际 API 边界的一部分。
DateFormats 与 TimeFormats:数字预设表
const DateFormats = [
'm/d',
'm/d/yy',
'mm/dd/yy',
'mm/yy',
'd-mmm',
'd-mmm-yy',
'dd-mmm-yy',
'yy-mm-dd',
'mmm-yy',
'mmmm-yy',
'mmm d, yyyy',
'mmmm d, yyyy',
'm/d/yy h:MM tt',
'm/d/yy HH:MM',
];
const TimeFormats = ['HH:MM', 'h:MM tt', 'HH:MM:ss', 'h:MM:ss tt'];数组位置本身就是 Acrobat 脚本 API 的一部分。例如日期索引 0 选择 m/d,索引 1 选择 m/d/yy,时间索引 3 选择 h:MM:ss tt。重排或按字母排序这些数组,会改变现有 PDF action 的含义。
这些条目是交给 PDF.js 脚本日期工具解释的格式 token,不是 Intl.DateTimeFormat 生成的本地化字符串。m 与 mm、月份名称、12 小时制的 tt、24 小时制的 HH 和可选秒数都保留在表里,由下游解析器与格式化器解释。
真实调用方以两种不同方式使用这些表:
AForm.AFDate_Format(pdf)与AFTime_Format(pdf)计算table[pdf] ?? pdf,已知数字预设会展开,其他传入格式则继续交给扩展格式化方法。AFDate_Keystroke(pdf)与AFTime_Keystroke(pdf)只在pdf位于对应数组范围内时继续执行,并不使用格式化方法的 fallback 表达式。- core 的批注解析也会通过这些数组解析数字格式 action。所选日期格式中的时间 token 可以把字段设为
datetime-local;纯日期格式选择date,时间格式则选择time并设置合适的 step。
因此,共享数组连接了两层逻辑,却不实现其中任何一层:脚本沙箱得到预设字符串,批注代码得到足以选择原生 input 行为的格式信息。
总结
scripting_utils.js 刻意保持很窄的接口。颜色辅助函数区分保留小数的 0–255 值与向下取整的十六进制分量;ColorConverters 恰好暴露十四个转换,覆盖带标签颜色、CSS、存储 RGB 与透明形式;日期时间表则保留 Acrobat 数字预设的顺序。
这个模块的正确性取决于字面契约,而不是宽泛假设:CMYK 参数顺序、[null] 透明标记、CMYK_HTML 的 this 委托,以及格式化与 keystroke 方法不同的 fallback 行为都很重要。把这些细节放在轻依赖的共享文件中,脚本与批注代码就能保持一致,又不必把任一子系统拖进另一个子系统。
StreamPDF 的设计原则: 能前置处理的绝对不放到解析的时候才做, 由服务端一次解决所有性能问题. 浏览器只获取&渲染必须数据.
相关文章
- PDF.js 从 demo 深入浅出 10: CanvasGraphics 与 operatorList 绘制2026年10月27日
- PDF.js 从 demo 深入浅出 9: 网络流与 Range 请求2026年10月27日
- PDF.js 从 demo 深入浅出 8: PDFWorker.create 与真假 Worker 握手2026年10月27日
- PDF.js 从 demo 深入浅出 7: WorkerTransport 初始化2026年10月27日
- PDF.js 从 demo 深入浅出 6: MessageHandler 跨线程通讯2026年10月27日
- PDF.js 从 demo 深入浅出 5: render 方法全貌2026年10月27日
- PDF.js 从 demo 深入浅出 4: getViewport 方法全貌2026年10月27日
- PDF.js 从 demo 深入浅出 3: getPage 方法全貌2026年10月27日
- PDF.js 从 demo 深入浅出 2: getDocument 方法全貌2026年10月27日
- PDF.js 源码解读: TextLayer2026年10月27日
- PDF.js 源码解读: font_loader2026年10月27日
- PDF.js 源码解读: obj_bin_transform_display2026年10月27日
- PDF.js 源码解读: PDFObjects2026年10月27日
- PDF.js 源码解读: 网络流2026年10月27日
- PDF.js 源码解读: internal_evt.js2026年10月27日
- PDF.js 源码解读: math_clamp.js2026年10月27日
- PDF.js 源码解读: css_utils.js2026年10月27日
- PDF.js 源码解读: MurmurHash3_642026年10月27日
- PDF.js 源码解读: obj_bin_transform_utils2026年10月27日
- PDF.js 源码解读: image_utils.js2026年10月27日
- PDF.js 源码解读: BasePDFStream2026年10月27日
- PDF.js 源码解读: 共享工具层2026年10月27日
- PDF.js 源码解读: MessageHandler2026年10月27日
- PDF.js 源码解读: api2026年10月27日
- PDF.js 源码解读: fonts2026年10月27日
- PDF.js 源码解读: glyphlist2026年10月27日
- PDF.js 源码解读: binary_cmap2026年10月27日
- PDF.js 源码解读: cmap2026年10月27日
- PDF.js 源码解读: encodings2026年10月27日
- PDF.js 源码解读: metrics2026年10月27日
- PDF.js 源码解读: standard_fonts2026年10月27日
- PDF.js 源码解读: fonts_utils2026年10月27日
- PDF.js 源码解读: default_appearance2026年10月27日
- PDF.js 源码解读: metadata_parser2026年10月27日
- PDF.js 源码解读: name_number_tree2026年10月27日
- PDF.js 源码解读: struct_tree2026年10月27日
- PDF.js 源码解读: catalog2026年10月27日
- PDF.js 源码解读: pdf_manager2026年10月27日
- PDF.js 源码解读: object_loader2026年10月27日
- PDF.js 源码解读: obj_bin_transform_core2026年10月27日
- PDF.js 源码解读: crypto2026年10月27日
- PDF.js 源码解读: core_utils2026年10月27日
- PDF.js 源码解读: primitives2026年10月27日
- PDF.js 源码解读: worker_stream2026年10月27日
- PDF.js 源码解读: ccitt_stream2026年10月27日
- PDF.js 源码解读: jpx_stream2026年10月27日
- PDF.js 源码解读: jbig2_stream2026年10月27日
- PDF.js 源码解读: jpeg_stream2026年10月27日
- PDF.js 源码解读: decrypt_stream2026年10月27日
- PDF.js 源码解读: brotli_stream2026年10月27日
- PDF.js 源码解读: run_length_stream2026年10月27日
- PDF.js 源码解读: ascii_hex_stream2026年10月27日
- PDF.js 源码解读: ascii_85_stream2026年10月27日
- PDF.js 源码解读: lzw_stream2026年10月27日
- PDF.js 源码解读: predictor_stream2026年10月27日
- PDF.js 源码解读: flate_stream2026年10月27日
- PDF.js 源码解读: decode_stream2026年10月27日
- PDF.js 源码解读: chunked_stream2026年10月27日
- PDF.js 源码解读: stream2026年10月27日
- PDF.js 源码解读: base_stream2026年10月27日
- PDF.js 深入浅出 11: Parser2026年10月26日
- PDF.js 深入浅出 10: XRef2026年10月26日
- PDF.js 深入浅出 9: CanvasGraphics2026年10月26日
- PDF.js 深入浅出 8: Document 与 Page2026年10月26日
- AI 时代, 我们到底应该做什么才能活下去(StreamPDF 创立有感)2026年10月26日
- PDF.js 深入浅出 7: PartialEvaluator2026年10月25日
- PDF.js 深入浅出 6: PDFManager.initDocument2026年10月25日
- PDF.js 深入浅出 5: PDFFetchStream 和 PDFWorkerStream2026年10月25日
- PDF.js 深入浅出 4: MessageHandler2026年10月22日
- PDF.js 深入浅出 3: getViewport & render2026年10月22日
- PDF.js 从 demo 深入浅出 1: 自定义 viewer 渲染 PDF2026年10月19日
- PDF.js 深入浅出 2: getDocument & getPage2026年10月19日
- PDF.js 深入浅出 1: PDF.js 的架构设计2026年10月16日