diff --git a/src/display/api.js b/src/display/api.js index 2c17e277ae65d..e1f39d5ff243c 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -501,7 +501,8 @@ function getDocument(src = {}) { task, networkStream, transportParams, - transportFactory + transportFactory, + enableHWA ); task._transport = transport; messageHandler.send("Ready", null); @@ -1181,8 +1182,12 @@ class PDFDocumentProxy { * Page render parameters. * * @typedef {Object} RenderParameters - * @property {CanvasRenderingContext2D} canvasContext - A 2D context of a DOM - * Canvas object. + * @property {CanvasRenderingContext2D} canvasContext - Deprecated 2D context of + * a DOM Canvas object for backwards compatibility; it is recommended to use + * the `canvas` parameter instead. + * @property {HTMLCanvasElement} canvas - A DOM Canvas object. The default value + * is the canvas associated with the `canvasContext` parameter if no value is + * provided explicitly. * @property {PageViewport} viewport - Rendering viewport obtained by calling * the `PDFPageProxy.getViewport` method. * @property {string} [intent] - Rendering intent, can be 'display', 'print', @@ -1405,6 +1410,7 @@ class PDFPageProxy { */ render({ canvasContext, + canvas = canvasContext.canvas, viewport, intent = "display", annotationMode = AnnotationMode.ENABLE, @@ -1496,7 +1502,7 @@ class PDFPageProxy { callback: complete, // Only include the required properties, and *not* the entire object. params: { - canvasContext, + canvas, viewport, transform, background, @@ -1511,6 +1517,7 @@ class PDFPageProxy { useRequestAnimationFrame: !intentPrint, pdfBug: this._pdfBug, pageColors, + enableHWA: this._transport.enableHWA, }); (intentState.renderTasks ||= new Set()).add(internalRenderTask); @@ -2305,7 +2312,14 @@ class WorkerTransport { #passwordCapability = null; - constructor(messageHandler, loadingTask, networkStream, params, factory) { + constructor( + messageHandler, + loadingTask, + networkStream, + params, + factory, + enableHWA + ) { this.messageHandler = messageHandler; this.loadingTask = loadingTask; this.commonObjs = new PDFObjects(); @@ -2329,6 +2343,7 @@ class WorkerTransport { this._fullReader = null; this._lastProgress = null; this.downloadInfoCapability = Promise.withResolvers(); + this.enableHWA = enableHWA; this.setupMessageHandler(); @@ -3104,6 +3119,7 @@ class InternalRenderTask { useRequestAnimationFrame = false, pdfBug = false, pageColors = null, + enableHWA = false, }) { this.callback = callback; this.params = params; @@ -3131,7 +3147,8 @@ class InternalRenderTask { this._continueBound = this._continue.bind(this); this._scheduleNextBound = this._scheduleNext.bind(this); this._nextBound = this._next.bind(this); - this._canvas = params.canvasContext.canvas; + this._canvas = params.canvas; + this._enableHWA = enableHWA; } get completed() { @@ -3161,7 +3178,12 @@ class InternalRenderTask { this.stepper.init(this.operatorList); this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint(); } - const { canvasContext, viewport, transform, background } = this.params; + const { viewport, transform, background } = this.params; + + const canvasContext = this._canvas.getContext("2d", { + alpha: false, + willReadFrequently: !this._enableHWA, + }); this.gfx = new CanvasGraphics( canvasContext, diff --git a/test/driver.js b/test/driver.js index 67fda3c51682d..f7418d1796c9e 100644 --- a/test/driver.js +++ b/test/driver.js @@ -1016,7 +1016,7 @@ class Driver { } } const renderContext = { - canvasContext: ctx, + canvas: this.canvas, viewport, optionalContentConfigPromise: task.optionalContentConfigPromise, annotationCanvasMap, diff --git a/test/integration/jasmine-boot.js b/test/integration/jasmine-boot.js index 673dd6b07edd9..72a8412afe57a 100644 --- a/test/integration/jasmine-boot.js +++ b/test/integration/jasmine-boot.js @@ -41,6 +41,7 @@ async function runTests(results) { "stamp_editor_spec.mjs", "text_field_spec.mjs", "text_layer_spec.mjs", + "thumbnail_view_spec.mjs", "viewer_spec.mjs", ], }); diff --git a/test/integration/thumbnail_view_spec.mjs b/test/integration/thumbnail_view_spec.mjs new file mode 100644 index 0000000000000..9ad648050542c --- /dev/null +++ b/test/integration/thumbnail_view_spec.mjs @@ -0,0 +1,35 @@ +import { closePages, loadAndWait } from "./test_utils.mjs"; + +describe("PDF Thumbnail View", () => { + describe("Works without errors", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait("tracemonkey.pdf", "#sidebarToggleButton"); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("should render thumbnails without errors", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.click("#sidebarToggleButton"); + + const thumbSelector = "#thumbnailView .thumbnailImage"; + await page.waitForSelector(thumbSelector, { visible: true }); + + await page.waitForSelector( + '#thumbnailView .thumbnail[data-loaded="true"]' + ); + + const src = await page.$eval(thumbSelector, el => el.src); + expect(src) + .withContext(`In ${browserName}`) + .toMatch(/^data:image\//); + }) + ); + }); + }); +}); diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 6610de8a7ca3c..9609d48c1e086 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -4410,7 +4410,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask = pdfPage.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }); expect(renderTask instanceof RenderTask).toEqual(true); @@ -4446,7 +4446,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask = page.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }); expect(renderTask instanceof RenderTask).toEqual(true); @@ -4477,7 +4477,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask = page.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }); expect(renderTask instanceof RenderTask).toEqual(true); @@ -4494,7 +4494,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) } const reRenderTask = page.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }); expect(reRenderTask instanceof RenderTask).toEqual(true); @@ -4518,14 +4518,14 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask1 = page.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, optionalContentConfigPromise, }); expect(renderTask1 instanceof RenderTask).toEqual(true); const renderTask2 = page.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, optionalContentConfigPromise, }); @@ -4562,7 +4562,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask = pdfPage.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }); expect(renderTask instanceof RenderTask).toEqual(true); @@ -4591,7 +4591,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask = pdfPage.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, background: "#FF0000", // See comment below. }); @@ -4651,7 +4651,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask = pdfPage.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }); @@ -4755,7 +4755,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask = pdfPage.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }); @@ -4802,7 +4802,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask = pdfPage.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }); @@ -4852,7 +4852,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask = pdfPage.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, intent: "print", annotationMode: AnnotationMode.ENABLE_STORAGE, @@ -4911,6 +4911,34 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) await loadingTask.destroy(); }); + + it("should work with the legacy canvasContext parameter", async function () { + const loadingTask = getDocument(tracemonkeyGetDocumentParams); + const pdfDoc = await loadingTask.promise; + const pdfPage = await pdfDoc.getPage(1); + const viewport = pdfPage.getViewport({ scale: 1 }); + + const { canvasFactory } = pdfDoc; + const canvasAndCtx = canvasFactory.create( + viewport.width, + viewport.height + ); + const renderTask = pdfPage.render({ + canvasContext: canvasAndCtx.context, + viewport, + }); + expect(renderTask instanceof RenderTask).toEqual(true); + + await renderTask.promise; + expect( + canvasAndCtx.context + .getImageData(0, 0, viewport.width, viewport.height) + .data.some(channel => channel !== 0) + ).toEqual(true); + + canvasFactory.destroy(canvasAndCtx); + await loadingTask.destroy(); + }); }); describe("Multiple `getDocument` instances", function () { @@ -4939,7 +4967,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`) viewport.height ); const renderTask = page.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }); await renderTask.promise; diff --git a/test/unit/custom_spec.js b/test/unit/custom_spec.js index 2888fb020f0ed..a49fc368f7d8c 100644 --- a/test/unit/custom_spec.js +++ b/test/unit/custom_spec.js @@ -50,7 +50,7 @@ describe("custom canvas rendering", function () { const canvasAndCtx = canvasFactory.create(viewport.width, viewport.height); const renderTask = page.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }); await renderTask.promise; @@ -70,7 +70,7 @@ describe("custom canvas rendering", function () { const canvasAndCtx = canvasFactory.create(viewport.width, viewport.height); const renderTask = page.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, background: "rgba(255,0,0,1.0)", }); @@ -160,7 +160,7 @@ describe("custom ownerDocument", function () { const canvasAndCtx = canvasFactory.create(viewport.width, viewport.height); await page.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }).promise; @@ -194,7 +194,7 @@ describe("custom ownerDocument", function () { const canvasAndCtx = canvasFactory.create(viewport.width, viewport.height); await page.render({ - canvasContext: canvasAndCtx.context, + canvas: canvasAndCtx.canvas, viewport, }).promise; diff --git a/web/base_pdf_page_view.js b/web/base_pdf_page_view.js index b7c650586c496..71d7693bec956 100644 --- a/web/base_pdf_page_view.js +++ b/web/base_pdf_page_view.js @@ -17,8 +17,6 @@ import { RenderingCancelledException } from "pdfjs-lib"; import { RenderingStates } from "./ui_utils.js"; class BasePDFPageView { - #enableHWA = false; - #loadingId = null; #minDurationToUpdateCanvas = 0; @@ -51,8 +49,6 @@ class BasePDFPageView { resume = null; constructor(options) { - this.#enableHWA = - #enableHWA in options ? options.#enableHWA : options.enableHWA || false; this.eventBus = options.eventBus; this.id = options.id; this.pageColors = options.pageColors || null; @@ -166,12 +162,7 @@ class BasePDFPageView { } }; - const ctx = canvas.getContext("2d", { - alpha: false, - willReadFrequently: !this.#enableHWA, - }); - - return { canvas, prevCanvas, ctx }; + return { canvas, prevCanvas }; } #renderContinueCallback = cont => { diff --git a/web/firefox_print_service.js b/web/firefox_print_service.js index c82dcd6ecf1a1..6f207d1803904 100644 --- a/web/firefox_print_service.js +++ b/web/firefox_print_service.js @@ -72,7 +72,7 @@ function composePage( currentRenderTask = null; } const renderContext = { - canvasContext: ctx, + canvas: ctx.canvas, transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0], viewport: pdfPage.getViewport({ scale: 1, rotation: size.rotation }), intent: "print", diff --git a/web/pdf_page_detail_view.js b/web/pdf_page_detail_view.js index d9b84e42d2353..10269c5924dc3 100644 --- a/web/pdf_page_detail_view.js +++ b/web/pdf_page_detail_view.js @@ -214,7 +214,7 @@ class PDFPageDetailView extends BasePDFPageView { const canvasWrapper = this.pageView._ensureCanvasWrapper(); - const { canvas, prevCanvas, ctx } = this._createCanvas(newCanvas => { + const { canvas, prevCanvas } = this._createCanvas(newCanvas => { // If there is already the background canvas, inject this new canvas // after it. We cannot simply use .append because all canvases must // be before the SVG elements used for drawings. @@ -249,7 +249,7 @@ class PDFPageDetailView extends BasePDFPageView { style.left = `${(area.minX * 100) / width}%`; const renderingPromise = this._drawCanvas( - this.pageView._getRenderingContext(ctx, transform), + this.pageView._getRenderingContext(canvas, transform), () => { // If the rendering is cancelled, keep the old canvas visible. this.canvas?.remove(); diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index e742e46522c1d..c25c5dfcd2bcf 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -95,8 +95,6 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js"; * @property {IL10n} [l10n] - Localization service. * @property {Object} [layerProperties] - The object that is used to lookup * the necessary layer-properties. - * @property {boolean} [enableHWA] - Enables hardware acceleration for - * rendering. The default value is `false`. * @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from * text that look like URLs. The default value is `true`. */ @@ -912,9 +910,9 @@ class PDFPageView extends BasePDFPageView { return canvasWrapper; } - _getRenderingContext(canvasContext, transform) { + _getRenderingContext(canvas, transform) { return { - canvasContext, + canvas, transform, viewport: this.viewport, annotationMode: this.#annotationMode, @@ -1000,7 +998,7 @@ class PDFPageView extends BasePDFPageView { const { width, height } = viewport; this.#originalViewport = viewport; - const { canvas, prevCanvas, ctx } = this._createCanvas(newCanvas => { + const { canvas, prevCanvas } = this._createCanvas(newCanvas => { // Always inject the canvas as the first element in the wrapper. canvasWrapper.prepend(newCanvas); }); @@ -1042,7 +1040,7 @@ class PDFPageView extends BasePDFPageView { ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null; const resultPromise = this._drawCanvas( - this._getRenderingContext(ctx, transform), + this._getRenderingContext(canvas, transform), () => { prevCanvas?.remove(); this._resetCanvas(); diff --git a/web/pdf_print_service.js b/web/pdf_print_service.js index 417c90b0ed495..b4cc80e01bdb6 100644 --- a/web/pdf_print_service.js +++ b/web/pdf_print_service.js @@ -58,7 +58,7 @@ function renderPage( printAnnotationStoragePromise, ]).then(function ([pdfPage, printAnnotationStorage]) { const renderContext = { - canvasContext: ctx, + canvas: scratchCanvas, transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0], viewport: pdfPage.getViewport({ scale: 1, rotation: size.rotation }), intent: "print", diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index 0115c5756ab65..625b75278bda9 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -58,8 +58,6 @@ function zeroCanvas(c) { * @property {Object} [pageColors] - Overwrites background and foreground colors * with user defined ones in order to improve readability in high contrast * mode. - * @property {boolean} [enableHWA] - Enables hardware acceleration for - * rendering. The default value is `false`. */ class TempImageFactory { @@ -106,7 +104,6 @@ class PDFThumbnailView { maxCanvasPixels, maxCanvasDim, pageColors, - enableHWA, }) { this.id = id; this.renderingId = "thumbnail" + id; @@ -120,7 +117,6 @@ class PDFThumbnailView { this.maxCanvasPixels = maxCanvasPixels ?? AppOptions.get("maxCanvasPixels"); this.maxCanvasDim = maxCanvasDim || AppOptions.get("maxCanvasDim"); this.pageColors = pageColors || null; - this.enableHWA = enableHWA || false; this.eventBus = eventBus; this.linkService = linkService; @@ -214,14 +210,10 @@ class PDFThumbnailView { this.resume = null; } - #getPageDrawContext(upscaleFactor = 1, enableHWA = this.enableHWA) { + #getPageDrawContext(upscaleFactor = 1) { // Keep the no-thumbnail outline visible, i.e. `data-loaded === false`, // until rendering/image conversion is complete, to avoid display issues. const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d", { - alpha: false, - willReadFrequently: !enableHWA, - }); const outputScale = new OutputScale(); const width = upscaleFactor * this.canvasWidth, height = upscaleFactor * this.canvasHeight; @@ -239,7 +231,7 @@ class PDFThumbnailView { ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null; - return { ctx, canvas, transform }; + return { canvas, transform }; } #convertCanvasToImage(canvas) { @@ -280,8 +272,7 @@ class PDFThumbnailView { // the `draw` and `setImage` methods (fixes issue 8233). // NOTE: To primarily avoid increasing memory usage too much, but also to // reduce downsizing overhead, we purposely limit the up-scaling factor. - const { ctx, canvas, transform } = - this.#getPageDrawContext(DRAW_UPSCALE_FACTOR); + const { canvas, transform } = this.#getPageDrawContext(DRAW_UPSCALE_FACTOR); const drawViewport = this.viewport.clone({ scale: DRAW_UPSCALE_FACTOR * this.scale, }); @@ -298,7 +289,7 @@ class PDFThumbnailView { }; const renderContext = { - canvasContext: ctx, + canvas, transform, viewport: drawViewport, optionalContentConfigPromise: this._optionalContentConfigPromise, @@ -378,7 +369,11 @@ class PDFThumbnailView { } #reduceImage(img) { - const { ctx, canvas } = this.#getPageDrawContext(1, true); + const { canvas } = this.#getPageDrawContext(1); + const ctx = canvas.getContext("2d", { + alpha: false, + willReadFrequently: false, + }); if (img.width <= 2 * canvas.width) { ctx.drawImage(