PDF.js Source Walkthrough: math_clamp.js
October 27, 2026 · StreamPDF Team

Introduction
This installment examines src/shared/math_clamp.js, one of the smallest modules in PDF.js. After the license and migration note, it contains a single three-line function.
Its size should not obscure its role. PDF.js repeatedly needs to keep values inside domains required by the PDF specification or inside limits imposed by the browser UI: sampled-function inputs, output ranges, color components, loading percentages, annotation coordinates, and integer storage all use the same bounding operation.
MathClamp centralizes that operation while the proposed standard Math.clamp is not generally available. The module is therefore a temporary compatibility seam, not a new mathematical framework.
Module Overview
| Block | Lines | Responsibility |
|---|---|---|
| Migration note | 16-17 | Replace calls and remove the file when Math.clamp is generally available |
MathClamp(v, min, max) | 18-21 | Bound a value by applying the lower and upper limits |
| Export | 23 | Share the helper with core, display, scripting, and public API entry points |
There is no state, class, dependency, or PDF object parsing in this file. Its entire contract is the order and behavior of two built-in numeric operations.
MathClamp(v, min, max): Applying Two Bounds
function MathClamp(v, min, max) {
return Math.min(Math.max(v, min), max);
}The nested order is significant. Math.max(v, min) first raises a value below the lower bound; Math.min(..., max) then lowers a value above the upper bound. For ordinary numeric inputs with min <= max, this is the familiar inclusive clamp.
| Call | Result | Reason |
|---|---|---|
MathClamp(0.5, 0, 1) | 0.5 | The value is already inside the interval |
MathClamp(-0.5, 0, 1) | 0 | The lower bound is applied |
MathClamp(1.5, 0, 1) | 1 | The upper bound is applied |
MathClamp(0, 0, 1) | 0 | Boundaries are inclusive |
MathClamp(NaN, 0, 1) | NaN | Both built-in operations propagate NaN |
MathClamp(5, 10, 0) | 0 | With reversed bounds, the final upper-bound operation wins |
Two parts of the caller contract follow directly from the implementation:
- The helper does not validate or repair its arguments. Callers are expected to supply a meaningful lower and upper bound. Reversed bounds are not swapped, and invalid numeric input is not converted into a fallback by this module.
- The helper does not round or otherwise transform the value. Numeric coercion and special-number behavior come directly from
Math.maxandMath.min; rounding, flooring, scaling, and interpolation remain the caller's responsibility.
That minimalism is important in specification code: a clamp can be one step in a larger algorithm without silently becoming a different operation.
Real Callers: One Operation, Several Domains
The same helper appears in both worker-side parsing and main-thread display code. Representative callers show why the generic contract is useful:
| Caller | Bounds | Purpose of clamping |
|---|---|---|
src/core/function.js sampled functions | PDF Domain and Range entries | Implement the specified input and output limiting steps before interpolation or final output |
src/display/api.js loading progress | 0 to 100 | Keep a computed percentage within its presentation range |
src/display/display_utils.js applyOpacity | 0 to 1 | Bound opacity before blending a color with white |
src/shared/scripting_utils.js color conversion | 0 to 1 | Bound normalized components before scaling them to 0–255 |
src/core/data_builder.js setSafeInt16 | -0x8000 to 0x7fff | Limit a value to the signed 16-bit storage range before writing it |
src/display/pages_mapper.js | 0 to remainingLen | Keep an adjusted page-insertion target within the remaining page array |
These examples also demonstrate what MathClamp does not do. Sampled-function code still performs interpolation; color conversion still scales and formats components; setSafeInt16 still writes through its DataView. The helper owns only the bounding step.
The module is additionally re-exported through src/pdf.js, so its use is not limited to private implementation details inside one layer.
Why Keep a Separate Module?
The source contains an explicit migration note: replace every occurrence and remove this file once the TC39 Math.clamp proposal is generally available. That note explains both the filename and the design.
A standalone module gives all current callers one identical implementation and gives the future migration one clear target. It also avoids duplicating a subtly ordered expression across core and display bundles. None of this requires extra abstraction: one function and one export are sufficient.
The ESLint suppression immediately above Math.min is another clue. It exempts this compatibility implementation from the project rule that prefers the standard clamp operation; it is not an additional runtime branch or feature.
Summary
MathClamp is intentionally small, but its semantics should still be read precisely:
- It applies the lower bound first and the upper bound second.
- Ordinary values are clamped to an inclusive interval, with no rounding or scaling added.
NaNpropagates, reversed bounds are not corrected, and callers retain responsibility for valid inputs.- Real callers use it for PDF domains and ranges, UI progress and geometry, color components, and fixed-width storage.
- The separate file exists as a shared, explicitly temporary replacement for the proposed
Math.clamp, with a straightforward removal path once the platform feature is available.
The design lesson is not that every one-line expression needs a module. It is that a repeated operation with a planned standards migration benefits from one exact, visible contract.
StreamPDF design principle: anything that can be handled ahead of time is absolutely not deferred to parse time; the server solves all performance problems once. The browser only fetches and renders the data it must.
Related articles
- PDF.js Deep Dive from Demo 10: CanvasGraphics and operatorList RenderingOctober 27, 2026
- PDF.js Deep Dive from Demo 9: Network Streams and Range RequestsOctober 27, 2026
- PDF.js Deep Dive from Demo 8: PDFWorker.create and the Real/Fake Worker HandshakeOctober 27, 2026
- PDF.js Deep Dive from Demo 7: WorkerTransport InitializationOctober 27, 2026
- PDF.js Deep Dive from Demo 6: MessageHandler Cross-Thread CommunicationOctober 27, 2026
- PDF.js Deep Dive from Demo 5: renderOctober 27, 2026
- PDF.js Deep Dive from Demo 4: getViewportOctober 27, 2026
- PDF.js Deep Dive from Demo 3: getPageOctober 27, 2026
- PDF.js Deep Dive from Demo 2: getDocumentOctober 27, 2026
- PDF.js Source Walkthrough: TextLayerOctober 27, 2026
- PDF.js Source Walkthrough: font_loaderOctober 27, 2026
- PDF.js Source Walkthrough: obj_bin_transform_displayOctober 27, 2026
- PDF.js Source Walkthrough: PDFObjectsOctober 27, 2026
- PDF.js Source Walkthrough: Network StreamsOctober 27, 2026
- PDF.js Source Walkthrough: internal_evt.jsOctober 27, 2026
- PDF.js Source Walkthrough: css_utils.jsOctober 27, 2026
- PDF.js Source Walkthrough: scripting_utilsOctober 27, 2026
- PDF.js Source Walkthrough: MurmurHash3_64October 27, 2026
- PDF.js Source Walkthrough: obj_bin_transform_utilsOctober 27, 2026
- PDF.js Source Walkthrough: image_utils.jsOctober 27, 2026
- PDF.js Source Walkthrough: BasePDFStreamOctober 27, 2026
- PDF.js Source Walkthrough: Shared UtilitiesOctober 27, 2026
- PDF.js Source Walkthrough: MessageHandlerOctober 27, 2026
- PDF.js Source Walkthrough: apiOctober 27, 2026
- PDF.js Source Walkthrough: fontsOctober 27, 2026
- PDF.js Source Walkthrough: glyphlistOctober 27, 2026
- PDF.js Source Walkthrough: binary_cmapOctober 27, 2026
- PDF.js Source Walkthrough: cmapOctober 27, 2026
- PDF.js Source Walkthrough: encodingsOctober 27, 2026
- PDF.js Source Walkthrough: metricsOctober 27, 2026
- PDF.js Source Walkthrough: standard_fontsOctober 27, 2026
- PDF.js Source Walkthrough: fonts_utilsOctober 27, 2026
- PDF.js Source Walkthrough: default_appearanceOctober 27, 2026
- PDF.js Source Walkthrough: metadata_parserOctober 27, 2026
- PDF.js Source Walkthrough: name_number_treeOctober 27, 2026
- PDF.js Source Walkthrough: struct_treeOctober 27, 2026
- PDF.js Source Walkthrough: catalogOctober 27, 2026
- PDF.js Source Walkthrough: pdf_managerOctober 27, 2026
- PDF.js Source Walkthrough: object_loaderOctober 27, 2026
- PDF.js Source Walkthrough: obj_bin_transform_coreOctober 27, 2026
- PDF.js Source Walkthrough: cryptoOctober 27, 2026
- PDF.js Source Walkthrough: core_utilsOctober 27, 2026
- PDF.js Source Walkthrough: primitivesOctober 27, 2026
- PDF.js Source Walkthrough: worker_streamOctober 27, 2026
- PDF.js Source Walkthrough: ccitt_streamOctober 27, 2026
- PDF.js Source Walkthrough: jpx_streamOctober 27, 2026
- PDF.js Source Walkthrough: jbig2_streamOctober 27, 2026
- PDF.js Source Walkthrough: jpeg_streamOctober 27, 2026
- PDF.js Source Walkthrough: decrypt_streamOctober 27, 2026
- PDF.js Source Walkthrough: brotli_streamOctober 27, 2026
- PDF.js Source Walkthrough: run_length_streamOctober 27, 2026
- PDF.js Source Walkthrough: ascii_hex_streamOctober 27, 2026
- PDF.js Source Walkthrough: ascii_85_streamOctober 27, 2026
- PDF.js Source Walkthrough: lzw_streamOctober 27, 2026
- PDF.js Source Walkthrough: predictor_streamOctober 27, 2026
- PDF.js Source Walkthrough: flate_streamOctober 27, 2026
- PDF.js Source Walkthrough: decode_streamOctober 27, 2026
- PDF.js Source Walkthrough: chunked_streamOctober 27, 2026
- PDF.js Source Walkthrough: streamOctober 27, 2026
- PDF.js Source Walkthrough: base_streamOctober 27, 2026
- PDF.js Deep Dive 11: ParserOctober 26, 2026
- PDF.js Deep Dive 10: XRefOctober 26, 2026
- PDF.js Deep Dive 9: CanvasGraphicsOctober 26, 2026
- PDF.js Deep Dive 8: PDFDocumentOctober 26, 2026
- The AI Era: What Should We Do to Survive? (Reflections on Founding StreamPDF)October 26, 2026
- PDF.js Deep Dive 7: PartialEvaluatorOctober 25, 2026
- PDF.js Deep Dive 6: PDFManager.initDocumentOctober 25, 2026
- PDF.js Deep Dive 5: PDFFetchStream and PDFWorkerStreamOctober 25, 2026
- PDF.js Deep Dive 4: MessageHandlerOctober 22, 2026
- PDF.js Deep Dive 3: getViewport & renderOctober 22, 2026
- PDF.js Deep Dive from Demo 1: Rendering a PDF with a Custom ViewerOctober 19, 2026
- PDF.js Deep Dive 2: getDocument & getPageOctober 19, 2026
- PDF.js Deep Dive 1: The Architecture of PDF.jsOctober 16, 2026