StreamPDFStreamPDF
Back to blog

PDF.js Source Walkthrough: math_clamp.js

October 27, 2026 · StreamPDF Team

PDF.js Source Walkthrough: math_clamp.js

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

BlockLinesResponsibility
Migration note16-17Replace calls and remove the file when Math.clamp is generally available
MathClamp(v, min, max)18-21Bound a value by applying the lower and upper limits
Export23Share 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.

CallResultReason
MathClamp(0.5, 0, 1)0.5The value is already inside the interval
MathClamp(-0.5, 0, 1)0The lower bound is applied
MathClamp(1.5, 0, 1)1The upper bound is applied
MathClamp(0, 0, 1)0Boundaries are inclusive
MathClamp(NaN, 0, 1)NaNBoth built-in operations propagate NaN
MathClamp(5, 10, 0)0With 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.max and Math.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:

CallerBoundsPurpose of clamping
src/core/function.js sampled functionsPDF Domain and Range entriesImplement the specified input and output limiting steps before interpolation or final output
src/display/api.js loading progress0 to 100Keep a computed percentage within its presentation range
src/display/display_utils.js applyOpacity0 to 1Bound opacity before blending a color with white
src/shared/scripting_utils.js color conversion0 to 1Bound normalized components before scaling them to 0–255
src/core/data_builder.js setSafeInt16-0x8000 to 0x7fffLimit a value to the signed 16-bit storage range before writing it
src/display/pages_mapper.js0 to remainingLenKeep 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:

  1. It applies the lower bound first and the upper bound second.
  2. Ordinary values are clamped to an inclusive interval, with no rounding or scaling added.
  3. NaN propagates, reversed bounds are not corrected, and callers retain responsibility for valid inputs.
  4. Real callers use it for PDF domains and ranges, UI progress and geometry, color components, and fixed-width storage.
  5. 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