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

前言
本篇分析 src/shared/math_clamp.js,这是 PDF.js 中最小的模块之一。除去许可证与迁移说明,它只有一个三行函数。
文件很小,作用却不容忽视。PDF.js 反复需要把值限制在 PDF 规范要求的定义域中,或限制在浏览器 UI 允许的范围内:采样函数输入、输出范围、颜色分量、加载百分比、批注坐标和整数存储都使用同一种边界约束操作。
在标准 Math.clamp 提案尚未普遍可用时,MathClamp 集中承载这一操作。因此,这个模块是一处临时的兼容接缝,而不是一套新的数学框架。
模块总览
| 区块 | 行号 | 职责 |
|---|---|---|
| 迁移说明 | 16-17 | Math.clamp 普遍可用后替换所有调用并删除文件 |
MathClamp(v, min, max) | 18-21 | 依次应用下界与上界,约束输入值 |
| 导出 | 23 | 向核心层、显示层、脚本层和公开 API 入口共享辅助函数 |
文件没有状态、类、依赖或 PDF 对象解析。它的全部契约就是两个内建数值运算的顺序与行为。
MathClamp(v, min, max):应用双重边界
function MathClamp(v, min, max) {
return Math.min(Math.max(v, min), max);
}嵌套顺序很关键。Math.max(v, min) 先把低于下界的值抬高;随后 Math.min(..., max) 再把高于上界的值压低。对于满足 min <= max 的普通数值输入,这就是常见的闭区间 clamp。
| 调用 | 结果 | 原因 |
|---|---|---|
MathClamp(0.5, 0, 1) | 0.5 | 值已经位于区间内 |
MathClamp(-0.5, 0, 1) | 0 | 应用下界 |
MathClamp(1.5, 0, 1) | 1 | 应用上界 |
MathClamp(0, 0, 1) | 0 | 边界包含在区间内 |
MathClamp(NaN, 0, 1) | NaN | 两个内建运算都会传播 NaN |
MathClamp(5, 10, 0) | 0 | 边界反向时,最后应用的上界决定结果 |
由实现可以直接得出两条调用契约:
- 辅助函数不会验证或修复参数。 调用方应提供有意义的上下界。反向边界不会自动交换,非法数值输入也不会由本模块替换成回退值。
- 辅助函数不会取整或做其他变换。 数值强制转换和特殊数值行为直接继承自
Math.max与Math.min;取整、向下取整、缩放和插值仍由调用方负责。
这种最小化在规范实现中很重要:clamp 可以是更大算法中的一个步骤,而不会悄悄变成另一种运算。
真实调用方:同一操作,多个领域
同一个辅助函数同时出现在 worker 侧解析代码与主线程显示代码中。具有代表性的调用方说明了通用契约的价值:
| 调用方 | 边界 | clamp 的目的 |
|---|---|---|
src/core/function.js 采样函数 | PDF Domain 与 Range 条目 | 在插值或最终输出前,实现规范要求的输入、输出限制步骤 |
src/display/api.js 加载进度 | 0 到 100 | 让计算出的百分比保持在展示范围内 |
src/display/display_utils.js 的 applyOpacity | 0 到 1 | 在颜色与白色混合前限制不透明度 |
src/shared/scripting_utils.js 颜色转换 | 0 到 1 | 在缩放到 0–255 前限制归一化分量 |
src/core/data_builder.js 的 setSafeInt16 | -0x8000 到 0x7fff | 写入前把值限制在有符号 16 位存储范围内 |
src/display/pages_mapper.js | 0 到 remainingLen | 让调整后的页面插入目标位于剩余页面数组范围内 |
这些例子也展示了 MathClamp 不做什么。采样函数代码仍负责插值;颜色转换仍负责缩放和格式化分量;setSafeInt16 仍通过 DataView 写入。辅助函数只拥有边界约束这一步。
该模块还通过 src/pdf.js 再次导出,因此它的用途并不局限于某一层的私有实现细节。
为什么独立成模块?
源码中有明确的迁移说明:TC39 的 Math.clamp 提案普遍可用后,替换所有调用并删除本文件。这条说明同时解释了文件名与设计意图。
独立模块让所有现有调用方共享同一个实现,也为未来迁移提供清晰目标,同时避免核心层与显示层各自复制一份顺序容易写错的表达式。实现这一目标不需要额外抽象:一个函数、一个导出就足够了。
Math.min 上方的 ESLint 抑制注释也是一条线索。它让这处兼容实现不受“优先使用标准 clamp 操作”的项目规则限制,并不是额外的运行时分支或功能。
总结
MathClamp 刻意保持小巧,但其语义仍值得精确阅读:
- 先应用下界,再应用上界。
- 普通值被限制在闭区间内,不附加取整或缩放。
NaN会传播,反向边界不会被纠正,合法输入仍由调用方负责。- 真实调用方把它用于 PDF 定义域与值域、UI 进度与几何、颜色分量以及定宽存储。
- 独立文件是标准
Math.clamp提案的共享临时替代,并为目标平台能力就绪后的直接移除保留路径。
这里的设计启示不是“每个单行表达式都需要一个模块”,而是:一个反复出现且有明确标准化迁移计划的操作,值得拥有一个精确、可见的统一契约。
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 源码解读: css_utils.js2026年10月27日
- PDF.js 源码解读: scripting_utils2026年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日