From 654985c621121184c55141d0aa4fd8b0939acfff Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 2 Apr 2026 22:28:17 +0200 Subject: [PATCH 1/2] Add constants for defining the initial BBox and Float32 BBox Nowadays there's a lot of places in the code-base where we need to initialize or reset bounding boxes. Rather than spelling this out repeatedly, this patch adds new `Array`/`Float32Array` constants that can be copied or used as-is where appropriate. --- src/core/annotation.js | 10 ++++++---- src/core/evaluator.js | 17 +++++++---------- src/core/pattern.js | 3 ++- src/display/canvas.js | 21 +++++++-------------- src/display/canvas_dependency_tracker.js | 15 ++++++--------- src/display/editor/drawers/freedraw.js | 14 +++----------- src/display/editor/drawers/highlight.js | 4 ++-- src/display/editor/drawers/inkdraw.js | 9 ++------- src/display/obj_bin_transform_display.js | 10 ++++++++-- src/shared/util.js | 5 +++++ 10 files changed, 48 insertions(+), 60 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index 8d38ec8ae..6e78864be 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -23,6 +23,8 @@ import { AnnotationType, assert, BASELINE_FACTOR, + BBOX_INIT, + F32_BBOX_INIT, FeatureTest, getModificationDate, info, @@ -637,7 +639,7 @@ function getQuadPoints(dict, rect) { function getTransformMatrix(rect, bbox, matrix) { // 12.5.5: Algorithm: Appearance streams - const minMax = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]); + const minMax = F32_BBOX_INIT.slice(); Util.axialAlignedBoundingBox(bbox, matrix, minMax); const [minX, minY, maxX, maxY] = minMax; if (minX === maxX || minY === maxY) { @@ -1724,7 +1726,7 @@ class MarkupAnnotation extends Annotation { fillAlpha, pointsCallback, }) { - const bbox = (this.data.rect = [Infinity, Infinity, -Infinity, -Infinity]); + const bbox = (this.data.rect = BBOX_INIT.slice()); const buffer = ["q"]; if (extra) { @@ -4463,7 +4465,7 @@ class PolylineAnnotation extends MarkupAnnotation { // If the /Rect-entry is empty/wrong, create a fallback rectangle so that // we get similar rendering/highlighting behaviour as in Adobe Reader. - const bbox = [Infinity, Infinity, -Infinity, -Infinity]; + const bbox = BBOX_INIT.slice(); for (let i = 0, ii = vertices.length; i < ii; i += 2) { Util.rectBoundingBox( vertices[i] - borderAdjust, @@ -4551,7 +4553,7 @@ class InkAnnotation extends MarkupAnnotation { // If the /Rect-entry is empty/wrong, create a fallback rectangle so that // we get similar rendering/highlighting behaviour as in Adobe Reader. - const bbox = [Infinity, Infinity, -Infinity, -Infinity]; + const bbox = BBOX_INIT.slice(); for (const inkList of this.data.inkLists) { for (let i = 0, ii = inkList.length; i < ii; i += 2) { Util.rectBoundingBox( diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 7c51351d2..0ab0e524a 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -16,7 +16,9 @@ import { AbortException, assert, + BBOX_INIT, DrawOPS, + F32_BBOX_INIT, FONT_IDENTITY_MATRIX, FormatError, info, @@ -2309,7 +2311,7 @@ class PartialEvaluator { pathMinMax.slice(), ]); pathBuffer.length = 0; - pathMinMax.set([Infinity, Infinity, -Infinity, -Infinity], 0); + pathMinMax.set(BBOX_INIT, 0); } continue; } @@ -4980,7 +4982,7 @@ class TranslatedFont { // Override the fontBBox when it's undefined/empty, or when it's at least // (approximately) one order of magnitude smaller than the charBBox // (fixes issue14999_reduced.pdf). - this._bbox ??= [Infinity, Infinity, -Infinity, -Infinity]; + this._bbox ??= BBOX_INIT.slice(); Util.rectBoundingBox(...charBBox, this._bbox); } @@ -5050,7 +5052,7 @@ class TranslatedFont { case OPS.constructPath: const minMax = operatorList.argsArray[i][2]; // Override the fontBBox when it's undefined/empty (fixes 19624.pdf). - this._bbox ??= [Infinity, Infinity, -Infinity, -Infinity]; + this._bbox ??= BBOX_INIT.slice(); Util.rectBoundingBox(...minMax, this._bbox); break; } @@ -5176,7 +5178,7 @@ class EvalState { currentPointY = 0; - pathMinMax = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]); + pathMinMax = F32_BBOX_INIT.slice(); pathBuffer = []; @@ -5200,12 +5202,7 @@ class EvalState { const clone = Object.create(this); if (newPath) { clone.pathBuffer = []; - clone.pathMinMax = new Float32Array([ - Infinity, - Infinity, - -Infinity, - -Infinity, - ]); + clone.pathMinMax = F32_BBOX_INIT.slice(); } return clone; } diff --git a/src/core/pattern.js b/src/core/pattern.js index 161e16a1f..e153b54d5 100644 --- a/src/core/pattern.js +++ b/src/core/pattern.js @@ -15,6 +15,7 @@ import { assert, + BBOX_INIT, FormatError, info, MeshFigureType, @@ -426,7 +427,7 @@ class FunctionBasedShading extends BaseShading { const matrix = lookupMatrix(dict.getArray("Matrix"), IDENTITY_MATRIX); // Transform the four domain corners to find the user-space bounding box. - this.bounds = [Infinity, Infinity, -Infinity, -Infinity]; + this.bounds = BBOX_INIT.slice(); Util.axialAlignedBoundingBox([x0, y0, x1, y1], matrix, this.bounds); const bboxW = this.bounds[2] - this.bounds[0]; diff --git a/src/display/canvas.js b/src/display/canvas.js index d39b566a0..f65df318d 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -18,6 +18,7 @@ import { Dependencies, } from "./canvas_dependency_tracker.js"; import { + F32_BBOX_INIT, FeatureTest, FONT_IDENTITY_MATRIX, ImageKind, @@ -66,14 +67,6 @@ const SCALE_MATRIX = new DOMMatrix(); // Used to get some coordinates. const XY = new Float32Array(2); -// Initial rectangle values for the minMax array. -const MIN_MAX_INIT = new Float32Array([ - Infinity, - Infinity, - -Infinity, - -Infinity, -]); - /** * Overrides certain methods on a 2d ctx so that when they are called they * will also call the same method on the destCtx. The methods that are @@ -335,7 +328,7 @@ class CanvasExtraState { transferMaps = "none"; - minMax = MIN_MAX_INIT.slice(); + minMax = F32_BBOX_INIT.slice(); constructor(width, height) { this.clipBox = new Float32Array([0, 0, width, height]); @@ -379,7 +372,7 @@ class CanvasExtraState { startNewPathAndClipBox(box) { this.clipBox.set(box, 0); - this.minMax.set(MIN_MAX_INIT, 0); + this.minMax.set(F32_BBOX_INIT, 0); } getClippedPathBoundingBox(pathType = PathType.FILL, transform = null) { @@ -1056,7 +1049,7 @@ class CanvasGraphics { 0, ]); maskToCanvas = Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]); - const minMax = MIN_MAX_INIT.slice(); + const minMax = F32_BBOX_INIT.slice(); Util.axialAlignedBoundingBox([0, 0, width, height], maskToCanvas, minMax); const [minX, minY, maxX, maxY] = minMax; const drawnWidth = Math.round(maxX - minX) || 1; @@ -2529,7 +2522,7 @@ class CanvasGraphics { const inv = getCurrentTransformInverse(ctx); if (inv) { const { width, height } = ctx.canvas; - const minMax = MIN_MAX_INIT.slice(); + const minMax = F32_BBOX_INIT.slice(); Util.axialAlignedBoundingBox([0, 0, width, height], inv, minMax); const [x0, y0, x1, y1] = minMax; @@ -2675,7 +2668,7 @@ class CanvasGraphics { let bounds; if (group.bbox) { - bounds = MIN_MAX_INIT.slice(); + bounds = F32_BBOX_INIT.slice(); Util.axialAlignedBoundingBox( group.bbox, getCurrentTransform(currentCtx), @@ -2804,7 +2797,7 @@ class CanvasGraphics { this.restore(opIdx); this.ctx.save(); this.ctx.setTransform(...currentMtx); - const dirtyBox = MIN_MAX_INIT.slice(); + const dirtyBox = F32_BBOX_INIT.slice(); Util.axialAlignedBoundingBox( [0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx, diff --git a/src/display/canvas_dependency_tracker.js b/src/display/canvas_dependency_tracker.js index 936f10666..55ac72d38 100644 --- a/src/display/canvas_dependency_tracker.js +++ b/src/display/canvas_dependency_tracker.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { FeatureTest, Util } from "../shared/util.js"; +import { BBOX_INIT, FeatureTest, Util } from "../shared/util.js"; import { MathClamp } from "../shared/math_clamp.js"; const FORCED_DEPENDENCY_LABEL = "__forcedDependency"; @@ -81,7 +81,7 @@ class CanvasBBoxTracker { #clipBox = [-Infinity, -Infinity, Infinity, Infinity]; // Float32Array - #pendingBBox = new Float64Array([Infinity, Infinity, -Infinity, -Infinity]); + #pendingBBox = new Float64Array(BBOX_INIT); _pendingBBoxIdx = -1; @@ -209,10 +209,7 @@ class CanvasBBoxTracker { resetBBox(idx) { if (this._pendingBBoxIdx !== idx) { this._pendingBBoxIdx = idx; - this.#pendingBBox[0] = Infinity; - this.#pendingBBox[1] = Infinity; - this.#pendingBBox[2] = -Infinity; - this.#pendingBBox[3] = -Infinity; + this.#pendingBBox.set(BBOX_INIT, 0); } return this; } @@ -222,7 +219,7 @@ class CanvasBBoxTracker { this.#baseTransformStack.at(-1), ctx.getTransform() ); - const clipBox = [Infinity, Infinity, -Infinity, -Infinity]; + const clipBox = BBOX_INIT.slice(); Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, clipBox); const intersection = Util.intersect(this.#clipBox, clipBox); if (intersection) { @@ -256,7 +253,7 @@ class CanvasBBoxTracker { return this; } - const bbox = [Infinity, Infinity, -Infinity, -Infinity]; + const bbox = BBOX_INIT.slice(); Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, bbox); this.#pendingBBox[0] = MathClamp(bbox[0], clipBox[0], this.#pendingBBox[0]); @@ -1130,7 +1127,7 @@ class CanvasImagesTracker { let coords; if (clipBox[0] !== Infinity) { - const bbox = [Infinity, Infinity, -Infinity, -Infinity]; + const bbox = BBOX_INIT.slice(); Util.axialAlignedBoundingBox([0, -height, width, 0], transform, bbox); const finalBBox = Util.intersect(clipBox, bbox); diff --git a/src/display/editor/drawers/freedraw.js b/src/display/editor/drawers/freedraw.js index 8300ca3b3..7ec0517f0 100644 --- a/src/display/editor/drawers/freedraw.js +++ b/src/display/editor/drawers/freedraw.js @@ -13,8 +13,8 @@ * limitations under the License. */ +import { BBOX_INIT, Util } from "../../../shared/util.js"; import { Outline } from "./outline.js"; -import { Util } from "../../../shared/util.js"; class FreeDrawOutliner { #box; @@ -588,22 +588,14 @@ class FreeDrawOutline extends Outline { lastPointX = ltrCallback(lastPointX, x); } } else { - bezierBbox[0] = bezierBbox[1] = Infinity; - bezierBbox[2] = bezierBbox[3] = -Infinity; + bezierBbox.set(BBOX_INIT, 0); Util.bezierBoundingBox( lastX, lastY, ...outline.slice(i, i + 6), bezierBbox ); - - Util.rectBoundingBox( - bezierBbox[0], - bezierBbox[1], - bezierBbox[2], - bezierBbox[3], - minMax - ); + Util.rectBoundingBox(...bezierBbox, minMax); if (firstPointY > bezierBbox[1]) { firstPointX = bezierBbox[0]; diff --git a/src/display/editor/drawers/highlight.js b/src/display/editor/drawers/highlight.js index 2809dd5aa..eab8c8fab 100644 --- a/src/display/editor/drawers/highlight.js +++ b/src/display/editor/drawers/highlight.js @@ -13,9 +13,9 @@ * limitations under the License. */ +import { BBOX_INIT, Util } from "../../../shared/util.js"; import { FreeDrawOutline, FreeDrawOutliner } from "./freedraw.js"; import { Outline } from "./outline.js"; -import { Util } from "../../../shared/util.js"; class HighlightOutliner { #box; @@ -41,7 +41,7 @@ class HighlightOutliner { * the last point of the boxes. */ constructor(boxes, borderWidth = 0, innerMargin = 0, isLTR = true) { - const minMax = [Infinity, Infinity, -Infinity, -Infinity]; + const minMax = BBOX_INIT.slice(); // We round the coordinates to slightly reduce the number of edges in the // final outlines. diff --git a/src/display/editor/drawers/inkdraw.js b/src/display/editor/drawers/inkdraw.js index 3ecf3af8a..a7cde0f1e 100644 --- a/src/display/editor/drawers/inkdraw.js +++ b/src/display/editor/drawers/inkdraw.js @@ -13,9 +13,9 @@ * limitations under the License. */ +import { F32_BBOX_INIT, Util } from "../../../shared/util.js"; import { MathClamp } from "../../../shared/math_clamp.js"; import { Outline } from "./outline.js"; -import { Util } from "../../../shared/util.js"; class InkDrawOutliner { // The last 3 points of the line. @@ -587,12 +587,7 @@ class InkDrawOutline extends Outline { } #computeBbox() { - const bbox = (this.#bbox = new Float32Array([ - Infinity, - Infinity, - -Infinity, - -Infinity, - ])); + const bbox = (this.#bbox = F32_BBOX_INIT.slice()); for (const { line } of this.#lines) { if (line.length <= 12) { diff --git a/src/display/obj_bin_transform_display.js b/src/display/obj_bin_transform_display.js index a2db53d7e..30dabaae3 100644 --- a/src/display/obj_bin_transform_display.js +++ b/src/display/obj_bin_transform_display.js @@ -13,7 +13,13 @@ * limitations under the License. */ -import { assert, FeatureTest, MeshFigureType, Util } from "../shared/util.js"; +import { + assert, + BBOX_INIT, + FeatureTest, + MeshFigureType, + Util, +} from "../shared/util.js"; import { CSS_FONT_INFO, FONT_INFO, @@ -438,7 +444,7 @@ class PatternInfo { const shadingType = this.data[PATTERN_INFO.SHADING_TYPE]; let bounds = null; if (coords.length > 0) { - bounds = [Infinity, Infinity, -Infinity, -Infinity]; + bounds = BBOX_INIT.slice(); for (let i = 0, ii = coords.length; i < ii; i += 2) { Util.pointBoundingBox(coords[i], coords[i + 1], bounds); diff --git a/src/shared/util.js b/src/shared/util.js index 778fcbd89..ec0be9a9e 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -25,6 +25,9 @@ const isNodeJS = !process.versions.nw && !(process.versions.electron && process.type && process.type !== "browser"); +const BBOX_INIT = [Infinity, Infinity, -Infinity, -Infinity]; +const F32_BBOX_INIT = new Float32Array(BBOX_INIT); + const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; // Represent the percentage of the height of a single-line field over @@ -1310,10 +1313,12 @@ export { assert, BaseException, BASELINE_FACTOR, + BBOX_INIT, bytesToString, createValidAbsoluteUrl, DocumentActionEventType, DrawOPS, + F32_BBOX_INIT, FeatureTest, FONT_IDENTITY_MATRIX, FormatError, From 0fd556f435a5cfe49cff3af3a91763fc4384e31a Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Fri, 3 Apr 2026 10:36:19 +0200 Subject: [PATCH 2/2] Take full advantage of the `lookupRect` helper in the `FunctionBasedShading` constructor --- src/core/pattern.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/core/pattern.js b/src/core/pattern.js index e153b54d5..7cdee3fc3 100644 --- a/src/core/pattern.js +++ b/src/core/pattern.js @@ -414,14 +414,7 @@ class FunctionBasedShading extends BaseShading { const fn = pdfFunctionFactory.create(fnObj, /* parseArray = */ true); // Domain [x0, x1, y0, y1]; defaults to [0, 1, 0, 1]. - let x0 = 0, - x1 = 1, - y0 = 0, - y1 = 1; - const domainArr = lookupRect(dict.getArray("Domain"), null); - if (domainArr) { - [x0, x1, y0, y1] = domainArr; - } + const [x0, x1, y0, y1] = lookupRect(dict.getArray("Domain"), [0, 1, 0, 1]); // Matrix maps shading (domain) space to user space; defaults to identity. const matrix = lookupMatrix(dict.getArray("Matrix"), IDENTITY_MATRIX);