diff --git a/src/display/canvas.js b/src/display/canvas.js index 647cdc3a1..fe03af266 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -1625,6 +1625,8 @@ class CanvasGraphics { let needRestore = false; const intersect = this.current.getClippedPathBoundingBox(); + this.dependencyTracker?.recordDependencies(opIdx, Dependencies.fill); + if (isPatternFill) { const dims = this.current.tilingPatternDims; const tileIdx = dims && fillColor.canSkipPatternCanvas(dims); @@ -1670,8 +1672,6 @@ class CanvasGraphics { } } - this.dependencyTracker?.recordDependencies(opIdx, Dependencies.fill); - if (needRestore) { ctx.restore(); this.dependencyTracker?.restore(opIdx); diff --git a/src/display/pattern_helper.js b/src/display/pattern_helper.js index 70c390de3..d0434a547 100644 --- a/src/display/pattern_helper.js +++ b/src/display/pattern_helper.js @@ -15,6 +15,7 @@ import { drawMeshWithGPU, isGPUReady, loadMeshShader } from "./webgpu.js"; import { FormatError, info, unreachable, Util } from "../shared/util.js"; +import { CanvasNestedDependencyTracker } from "./canvas_dependency_tracker.js"; import { getCurrentTransform } from "./display_utils.js"; const PathType = { @@ -651,6 +652,14 @@ class TilingPattern { drawPattern(owner, path, useEOFill = false, [n, m], opIdx) { const [x0, y0, x1, y1] = this.bbox; + const dependencyTracker = owner.dependencyTracker; + if (dependencyTracker) { + owner.dependencyTracker = new CanvasNestedDependencyTracker( + dependencyTracker, + opIdx + ); + } + owner.save(); if (useEOFill) { owner.ctx.clip(path, "evenodd"); @@ -695,6 +704,9 @@ class TilingPattern { } owner.restore(); + if (dependencyTracker) { + owner.dependencyTracker = dependencyTracker; + } } createPatternCanvas(owner, opIdx) { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index b9a8ea6e6..c5dcca40e 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -903,3 +903,4 @@ !issue19634.pdf !three_pages_with_number.pdf !issue13520.pdf +!22060_A1_01_Plans.pdf diff --git a/test/pdfs/22060_A1_01_Plans.pdf b/test/pdfs/22060_A1_01_Plans.pdf new file mode 100644 index 000000000..a03e26921 Binary files /dev/null and b/test/pdfs/22060_A1_01_Plans.pdf differ diff --git a/test/unit/clitests.json b/test/unit/clitests.json index 876f84de7..744fb63a8 100644 --- a/test/unit/clitests.json +++ b/test/unit/clitests.json @@ -34,6 +34,7 @@ "network_utils_spec.js", "node_stream_spec.js", "obj_bin_transform_spec.js", + "operation_list_dependencies_spec.js", "parser_spec.js", "pattern_spec.js", "pdf.image_decoders_spec.js", diff --git a/test/unit/jasmine-boot.js b/test/unit/jasmine-boot.js index ee341b7e3..4f04beb73 100644 --- a/test/unit/jasmine-boot.js +++ b/test/unit/jasmine-boot.js @@ -77,6 +77,7 @@ async function initializePDFJS(callback) { "pdfjs-test/unit/network_spec.js", "pdfjs-test/unit/network_utils_spec.js", "pdfjs-test/unit/obj_bin_transform_spec.js", + "pdfjs-test/unit/operation_list_dependencies_spec.js", "pdfjs-test/unit/parser_spec.js", "pdfjs-test/unit/pattern_spec.js", "pdfjs-test/unit/pdf.image_decoders_spec.js", diff --git a/test/unit/operation_list_dependencies_spec.js b/test/unit/operation_list_dependencies_spec.js new file mode 100644 index 000000000..629ddc8a0 --- /dev/null +++ b/test/unit/operation_list_dependencies_spec.js @@ -0,0 +1,80 @@ +/* Copyright 2026 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getDocument, PDFPageProxy } from "../../src/display/api.js"; +import { buildGetDocumentParams } from "./test_utils.js"; +import { PixelsPerInch } from "../../src/display/display_utils.js"; + +describe("dependencies tracking", function () { + let dependencies; + + beforeAll(() => { + globalThis.StepperManager = { + enabled: true, + create() { + return { + init() {}, + updateOperatorList() {}, + getNextBreakPoint: () => null, + nextBreakPoint: null, + shouldSkip: () => false, + setOperatorBBoxes(_bboxes, deps) { + dependencies = deps; + }, + }; + }, + }; + }); + + afterEach(() => { + dependencies = null; + }); + + afterAll(() => { + delete globalThis.StepperManager; + }); + + it("pattern fill", async () => { + const loadingTask = getDocument( + buildGetDocumentParams("22060_A1_01_Plans.pdf") + ); + const pdfDocument = await loadingTask.promise; + const page = await pdfDocument.getPage(1); + + expect(page).toBeInstanceOf(PDFPageProxy); + page._pdfBug = true; + + const viewport = page.getViewport({ + scale: PixelsPerInch.PDF_TO_CSS_UNITS, + }); + + const { canvas } = pdfDocument.canvasFactory.create( + viewport.width, + viewport.height + ); + + const renderTask = page.render({ + canvas, + viewport, + recordOperations: true, + }); + await renderTask.promise; + + expect(dependencies.get(14)).toEqual({ + dependencies: new Set([0, 1, 2, 6, 7, 8, 12, 13]), + isRenderingOperation: true, + }); + }); +});