Compare commits

..

No commits in common. "9efd9fa2c7bb862ffaa1282c165a201868136092" and "a67b952118f5d799c8fcf899e6ac46e2c2fa20b4" have entirely different histories.

13 changed files with 76 additions and 175 deletions

View File

@ -23,8 +23,6 @@ import {
AnnotationType,
assert,
BASELINE_FACTOR,
BBOX_INIT,
F32_BBOX_INIT,
FeatureTest,
getModificationDate,
info,
@ -639,7 +637,7 @@ function getQuadPoints(dict, rect) {
function getTransformMatrix(rect, bbox, matrix) {
// 12.5.5: Algorithm: Appearance streams
const minMax = F32_BBOX_INIT.slice();
const minMax = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]);
Util.axialAlignedBoundingBox(bbox, matrix, minMax);
const [minX, minY, maxX, maxY] = minMax;
if (minX === maxX || minY === maxY) {
@ -1726,7 +1724,7 @@ class MarkupAnnotation extends Annotation {
fillAlpha,
pointsCallback,
}) {
const bbox = (this.data.rect = BBOX_INIT.slice());
const bbox = (this.data.rect = [Infinity, Infinity, -Infinity, -Infinity]);
const buffer = ["q"];
if (extra) {
@ -4465,7 +4463,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 = BBOX_INIT.slice();
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
for (let i = 0, ii = vertices.length; i < ii; i += 2) {
Util.rectBoundingBox(
vertices[i] - borderAdjust,
@ -4553,7 +4551,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 = BBOX_INIT.slice();
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
for (const inkList of this.data.inkLists) {
for (let i = 0, ii = inkList.length; i < ii; i += 2) {
Util.rectBoundingBox(

View File

@ -190,7 +190,6 @@ class ChunkedStream extends Stream {
if (strEnd > this.progressiveDataLength) {
this.ensureRange(pos, strEnd);
}
this.pos = strEnd;
return bytes.subarray(pos, strEnd);
}

View File

@ -16,9 +16,7 @@
import {
AbortException,
assert,
BBOX_INIT,
DrawOPS,
F32_BBOX_INIT,
FONT_IDENTITY_MATRIX,
FormatError,
info,
@ -2311,7 +2309,7 @@ class PartialEvaluator {
pathMinMax.slice(),
]);
pathBuffer.length = 0;
pathMinMax.set(BBOX_INIT, 0);
pathMinMax.set([Infinity, Infinity, -Infinity, -Infinity], 0);
}
continue;
}
@ -4982,7 +4980,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 ??= BBOX_INIT.slice();
this._bbox ??= [Infinity, Infinity, -Infinity, -Infinity];
Util.rectBoundingBox(...charBBox, this._bbox);
}
@ -5052,7 +5050,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 ??= BBOX_INIT.slice();
this._bbox ??= [Infinity, Infinity, -Infinity, -Infinity];
Util.rectBoundingBox(...minMax, this._bbox);
break;
}
@ -5178,7 +5176,7 @@ class EvalState {
currentPointY = 0;
pathMinMax = F32_BBOX_INIT.slice();
pathMinMax = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]);
pathBuffer = [];
@ -5202,7 +5200,12 @@ class EvalState {
const clone = Object.create(this);
if (newPath) {
clone.pathBuffer = [];
clone.pathMinMax = F32_BBOX_INIT.slice();
clone.pathMinMax = new Float32Array([
Infinity,
Infinity,
-Infinity,
-Infinity,
]);
}
return clone;
}

View File

@ -15,7 +15,6 @@
import {
assert,
BBOX_INIT,
FormatError,
info,
MeshFigureType,
@ -414,13 +413,20 @@ class FunctionBasedShading extends BaseShading {
const fn = pdfFunctionFactory.create(fnObj, /* parseArray = */ true);
// Domain [x0, x1, y0, y1]; defaults to [0, 1, 0, 1].
const [x0, x1, y0, y1] = lookupRect(dict.getArray("Domain"), [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;
}
// Matrix maps shading (domain) space to user space; defaults to identity.
const matrix = lookupMatrix(dict.getArray("Matrix"), IDENTITY_MATRIX);
// Transform the four domain corners to find the user-space bounding box.
this.bounds = BBOX_INIT.slice();
this.bounds = [Infinity, Infinity, -Infinity, -Infinity];
Util.axialAlignedBoundingBox([x0, y0, x1, y1], matrix, this.bounds);
const bboxW = this.bounds[2] - this.bounds[0];

View File

@ -18,7 +18,6 @@ import {
Dependencies,
} from "./canvas_dependency_tracker.js";
import {
F32_BBOX_INIT,
FeatureTest,
FONT_IDENTITY_MATRIX,
ImageKind,
@ -67,6 +66,14 @@ 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
@ -328,7 +335,7 @@ class CanvasExtraState {
transferMaps = "none";
minMax = F32_BBOX_INIT.slice();
minMax = MIN_MAX_INIT.slice();
constructor(width, height) {
this.clipBox = new Float32Array([0, 0, width, height]);
@ -372,7 +379,7 @@ class CanvasExtraState {
startNewPathAndClipBox(box) {
this.clipBox.set(box, 0);
this.minMax.set(F32_BBOX_INIT, 0);
this.minMax.set(MIN_MAX_INIT, 0);
}
getClippedPathBoundingBox(pathType = PathType.FILL, transform = null) {
@ -1049,7 +1056,7 @@ class CanvasGraphics {
0,
]);
maskToCanvas = Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]);
const minMax = F32_BBOX_INIT.slice();
const minMax = MIN_MAX_INIT.slice();
Util.axialAlignedBoundingBox([0, 0, width, height], maskToCanvas, minMax);
const [minX, minY, maxX, maxY] = minMax;
const drawnWidth = Math.round(maxX - minX) || 1;
@ -2522,7 +2529,7 @@ class CanvasGraphics {
const inv = getCurrentTransformInverse(ctx);
if (inv) {
const { width, height } = ctx.canvas;
const minMax = F32_BBOX_INIT.slice();
const minMax = MIN_MAX_INIT.slice();
Util.axialAlignedBoundingBox([0, 0, width, height], inv, minMax);
const [x0, y0, x1, y1] = minMax;
@ -2668,7 +2675,7 @@ class CanvasGraphics {
let bounds;
if (group.bbox) {
bounds = F32_BBOX_INIT.slice();
bounds = MIN_MAX_INIT.slice();
Util.axialAlignedBoundingBox(
group.bbox,
getCurrentTransform(currentCtx),
@ -2797,7 +2804,7 @@ class CanvasGraphics {
this.restore(opIdx);
this.ctx.save();
this.ctx.setTransform(...currentMtx);
const dirtyBox = F32_BBOX_INIT.slice();
const dirtyBox = MIN_MAX_INIT.slice();
Util.axialAlignedBoundingBox(
[0, 0, groupCtx.canvas.width, groupCtx.canvas.height],
currentMtx,

View File

@ -13,7 +13,7 @@
* limitations under the License.
*/
import { BBOX_INIT, FeatureTest, Util } from "../shared/util.js";
import { 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<minX, minY, maxX, maxY>
#pendingBBox = new Float64Array(BBOX_INIT);
#pendingBBox = new Float64Array([Infinity, Infinity, -Infinity, -Infinity]);
_pendingBBoxIdx = -1;
@ -209,7 +209,10 @@ class CanvasBBoxTracker {
resetBBox(idx) {
if (this._pendingBBoxIdx !== idx) {
this._pendingBBoxIdx = idx;
this.#pendingBBox.set(BBOX_INIT, 0);
this.#pendingBBox[0] = Infinity;
this.#pendingBBox[1] = Infinity;
this.#pendingBBox[2] = -Infinity;
this.#pendingBBox[3] = -Infinity;
}
return this;
}
@ -219,7 +222,7 @@ class CanvasBBoxTracker {
this.#baseTransformStack.at(-1),
ctx.getTransform()
);
const clipBox = BBOX_INIT.slice();
const clipBox = [Infinity, Infinity, -Infinity, -Infinity];
Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, clipBox);
const intersection = Util.intersect(this.#clipBox, clipBox);
if (intersection) {
@ -253,7 +256,7 @@ class CanvasBBoxTracker {
return this;
}
const bbox = BBOX_INIT.slice();
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, bbox);
this.#pendingBBox[0] = MathClamp(bbox[0], clipBox[0], this.#pendingBBox[0]);
@ -1127,7 +1130,7 @@ class CanvasImagesTracker {
let coords;
if (clipBox[0] !== Infinity) {
const bbox = BBOX_INIT.slice();
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
Util.axialAlignedBoundingBox([0, -height, width, 0], transform, bbox);
const finalBBox = Util.intersect(clipBox, bbox);

View File

@ -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,14 +588,22 @@ class FreeDrawOutline extends Outline {
lastPointX = ltrCallback(lastPointX, x);
}
} else {
bezierBbox.set(BBOX_INIT, 0);
bezierBbox[0] = bezierBbox[1] = Infinity;
bezierBbox[2] = bezierBbox[3] = -Infinity;
Util.bezierBoundingBox(
lastX,
lastY,
...outline.slice(i, i + 6),
bezierBbox
);
Util.rectBoundingBox(...bezierBbox, minMax);
Util.rectBoundingBox(
bezierBbox[0],
bezierBbox[1],
bezierBbox[2],
bezierBbox[3],
minMax
);
if (firstPointY > bezierBbox[1]) {
firstPointX = bezierBbox[0];

View File

@ -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 = BBOX_INIT.slice();
const minMax = [Infinity, Infinity, -Infinity, -Infinity];
// We round the coordinates to slightly reduce the number of edges in the
// final outlines.

View File

@ -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,7 +587,12 @@ class InkDrawOutline extends Outline {
}
#computeBbox() {
const bbox = (this.#bbox = F32_BBOX_INIT.slice());
const bbox = (this.#bbox = new Float32Array([
Infinity,
Infinity,
-Infinity,
-Infinity,
]));
for (const { line } of this.#lines) {
if (line.length <= 12) {

View File

@ -13,13 +13,7 @@
* limitations under the License.
*/
import {
assert,
BBOX_INIT,
FeatureTest,
MeshFigureType,
Util,
} from "../shared/util.js";
import { assert, FeatureTest, MeshFigureType, Util } from "../shared/util.js";
import {
CSS_FONT_INFO,
FONT_INFO,
@ -444,7 +438,7 @@ class PatternInfo {
const shadingType = this.data[PATTERN_INFO.SHADING_TYPE];
let bounds = null;
if (coords.length > 0) {
bounds = BBOX_INIT.slice();
bounds = [Infinity, Infinity, -Infinity, -Infinity];
for (let i = 0, ii = coords.length; i < ii; i += 2) {
Util.pointBoundingBox(coords[i], coords[i + 1], bounds);

View File

@ -25,9 +25,6 @@ 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
@ -1313,12 +1310,10 @@ export {
assert,
BaseException,
BASELINE_FACTOR,
BBOX_INIT,
bytesToString,
createValidAbsoluteUrl,
DocumentActionEventType,
DrawOPS,
F32_BBOX_INIT,
FeatureTest,
FONT_IDENTITY_MATRIX,
FormatError,

View File

@ -1784,113 +1784,4 @@ describe("PDF viewer", () => {
);
});
});
describe("PDFPrintService", () => {
describe("blob URL revocation (issue #19988)", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"basicapi.pdf",
".textLayer .endOfContent",
null,
{
earlySetup: () => {
// Track blob URLs created during the print phase (between
// beforeprint and afterprint).
let trackPrintURLs = false;
window._printBlobURLs = [];
const origCreate = URL.createObjectURL.bind(URL);
URL.createObjectURL = blob => {
const url = origCreate(blob);
if (trackPrintURLs) {
window._printBlobURLs.push(url);
}
return url;
};
// beforeprint fires before renderPages(); start tracking here.
window.addEventListener("beforeprint", () => {
trackPrintURLs = true;
});
// window.print() is called by performPrint() after renderPages()
// completes and all images are loaded into #printContainer.
window.print = () => {
const isFirefox = navigator.userAgent.includes("Firefox");
if (isFirefox) {
// Firefox re-fetches blob URLs when rendering the print
// preview (especially when a service worker is registered).
// Verify the URLs are still accessible at this point.
window._printImagesAccessible = Promise.all(
window._printBlobURLs.map(url =>
fetch(url).then(
() => true,
() => false
)
)
);
} else {
// Chrome uses the cached decoded data already in the <img>
// elements and does not re-fetch blob URLs for printing.
// Just verify the images rendered correctly.
const imgs = document.querySelectorAll("#printContainer img");
window._printImagesAccessible = Promise.resolve(
Array.from(imgs).map(
img => img.complete && img.naturalWidth > 0
)
);
}
};
},
appSetup: app => {
app._testPrintResolver = Promise.withResolvers();
},
eventBusSetup: eventBus => {
eventBus.on(
"afterprint",
() => {
// Wait for the checks initiated in window.print() before
// resolving, so the test can assert on them.
(window._printImagesAccessible ?? Promise.resolve([])).then(
window.PDFViewerApplication._testPrintResolver.resolve
);
},
{ once: true }
);
},
}
);
});
afterEach(async () => {
await closePages(pages);
});
it("must keep print image blob URLs accessible until destroy() is called", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await waitAndClick(page, "#printButton");
// Resolves with an array of booleans, one per print page image.
const accessible = await awaitPromise(
await page.evaluateHandle(() => [
window.PDFViewerApplication._testPrintResolver.promise,
])
);
expect(accessible.length)
.withContext(`In ${browserName}: print pages were rendered`)
.toBeGreaterThan(0);
expect(accessible.every(v => v))
.withContext(
`In ${browserName}: all print images accessible at print time`
)
.toBeTrue();
})
);
});
});
});
});

View File

@ -144,12 +144,6 @@ class PDFPrintService {
this.pageStyleSheet.remove();
this.pageStyleSheet = null;
}
if (this._blobURLs) {
for (const url of this._blobURLs) {
URL.revokeObjectURL(url);
}
this._blobURLs = null;
}
this.scratchCanvas.width = this.scratchCanvas.height = 0;
this.scratchCanvas = null;
activeService = null;
@ -195,13 +189,7 @@ class PDFPrintService {
this.throwIfInactive();
const img = document.createElement("img");
this.scratchCanvas.toBlob(blob => {
const blobURL = URL.createObjectURL(blob);
img.src = blobURL;
// Defer revocation until after printing completes (in destroy()) to avoid
// broken print images in Firefox when a service worker is registered,
// since Firefox re-fetches blob URLs when rendering the print dialog.
// See https://github.com/mozilla/pdf.js/issues/19988
(this._blobURLs ??= []).push(blobURL);
img.src = URL.createObjectURL(blob);
});
const wrapper = document.createElement("div");
@ -213,9 +201,13 @@ class PDFPrintService {
img.onload = resolve;
img.onerror = reject;
promise.catch(() => {
// Avoid "Uncaught promise" messages in the console.
});
promise
.catch(() => {
// Avoid "Uncaught promise" messages in the console.
})
.then(() => {
URL.revokeObjectURL(img.src);
});
return promise;
}