Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions external/dist/webpack.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ if (typeof window !== "undefined" && "Worker" in window) {
new URL("./build/pdf.worker.mjs", import.meta.url),
{ type: "module" }
);
GlobalWorkerOptions.rendererSrc = new URL(
"./build/pdf.renderer.mjs",
import.meta.url
).href;
}

export * from "./build/pdf.mjs";
54 changes: 45 additions & 9 deletions gulpfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,24 @@ function createWorkerBundle(defines) {
.pipe(webpack2Stream(workerFileConfig));
}

function createRendererWorkerBundle(defines) {
const rendererWorkerDefines = {
...defines,
WORKER_THREAD: true,
};
const rendererWorkerFileConfig = createWebpackConfig(rendererWorkerDefines, {
filename: rendererWorkerDefines.MINIFIED
? "pdf.renderer.min.mjs"
: "pdf.renderer.mjs",
library: {
type: "module",
},
});
return gulp
.src("./src/pdf.renderer.js", { encoding: false })
.pipe(webpack2Stream(rendererWorkerFileConfig));
}

function createWebBundle(defines, options) {
const viewerFileConfig = createWebpackConfig(defines, {
filename: "viewer.mjs",
Expand Down Expand Up @@ -1229,6 +1247,7 @@ function buildGeneric(defines, dir) {
return ordered([
createMainBundle(defines).pipe(gulp.dest(dir + "build")),
createWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
createRendererWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
createSandboxBundle(defines).pipe(gulp.dest(dir + "build")),
createWebBundle(defines).pipe(gulp.dest(dir + "web")),
gulp
Expand Down Expand Up @@ -1373,6 +1392,7 @@ function buildMinified(defines, dir) {
return ordered([
createMainBundle(defines).pipe(gulp.dest(dir + "build")),
createWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
createRendererWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
createSandboxBundle(defines).pipe(gulp.dest(dir + "build")),
createImageDecodersBundle({ ...defines, IMAGE_DECODERS: true }).pipe(
gulp.dest(dir + "image_decoders")
Expand Down Expand Up @@ -1498,6 +1518,9 @@ gulp.task(
createWorkerBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
createRendererWorkerBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
createWebBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "web")
),
Expand Down Expand Up @@ -1603,6 +1626,9 @@ gulp.task(
createWorkerBundle(defines).pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "build")
),
createRendererWorkerBundle(defines).pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "build")
),
createSandboxBundle(defines).pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "build")
),
Expand Down Expand Up @@ -1805,7 +1831,7 @@ function buildLib(defines, dir) {
gulp.src(
[
"src/{core,display,shared}/**/*.js",
"src/{pdf,pdf.image_decoders,pdf.worker}.js",
"src/{pdf,pdf.image_decoders,pdf.worker,pdf.renderer}.js",
],
{ base: "src/", encoding: false, sourcemaps: enableSourceMaps }
),
Expand Down Expand Up @@ -2639,6 +2665,7 @@ function buildInternalViewer(defines, dir) {
return ordered([
createMainBundle(defines).pipe(gulp.dest(dir + "build")),
createWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
createRendererWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
createInternalViewerBundle(defines).pipe(gulp.dest(dir + "web")),
preprocessHTML("web/internal/debugger.html", defines).pipe(
gulp.dest(dir + "web")
Expand Down Expand Up @@ -2857,25 +2884,33 @@ gulp.task(
gulp
.src(
[
GENERIC_DIR + "build/{pdf,pdf.worker,pdf.sandbox}.mjs",
GENERIC_DIR + "build/{pdf,pdf.worker,pdf.sandbox}.mjs.map",
GENERIC_DIR +
"build/{pdf,pdf.worker,pdf.sandbox,pdf.renderer}.mjs",
GENERIC_DIR +
"build/{pdf,pdf.worker,pdf.sandbox,pdf.renderer}.mjs.map",
],
{ encoding: false }
)
.pipe(gulp.dest(DIST_DIR + "build/")),
gulp
.src(
[
GENERIC_LEGACY_DIR + "build/{pdf,pdf.worker,pdf.sandbox}.mjs",
GENERIC_LEGACY_DIR + "build/{pdf,pdf.worker,pdf.sandbox}.mjs.map",
GENERIC_LEGACY_DIR +
"build/{pdf,pdf.worker,pdf.sandbox,pdf.renderer}.mjs",
GENERIC_LEGACY_DIR +
"build/{pdf,pdf.worker,pdf.sandbox,pdf.renderer}.mjs.map",
],
{ encoding: false }
)
.pipe(gulp.dest(DIST_DIR + "legacy/build/")),
gulp
.src(MINIFIED_DIR + "build/{pdf,pdf.worker,pdf.sandbox}.min.mjs", {
encoding: false,
})
.src(
MINIFIED_DIR +
"build/{pdf,pdf.worker,pdf.sandbox,pdf.renderer}.min.mjs",
{
encoding: false,
}
)
.pipe(gulp.dest(DIST_DIR + "build/")),
gulp
.src(MINIFIED_DIR + "image_decoders/pdf.image_decoders.min.mjs", {
Expand All @@ -2884,7 +2919,8 @@ gulp.task(
.pipe(gulp.dest(DIST_DIR + "image_decoders/")),
gulp
.src(
MINIFIED_LEGACY_DIR + "build/{pdf,pdf.worker,pdf.sandbox}.min.mjs",
MINIFIED_LEGACY_DIR +
"build/{pdf,pdf.worker,pdf.sandbox,pdf.renderer}.min.mjs",
{ encoding: false }
)
.pipe(gulp.dest(DIST_DIR + "legacy/build/")),
Expand Down
1 change: 1 addition & 0 deletions src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@ class Page {
resources,
this.nonBlendModesSet
),
hasCanvasFilters: partialEvaluator.hasCanvasFilters(resources),
pageIndex,
cacheKey,
});
Expand Down
115 changes: 115 additions & 0 deletions src/core/evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,121 @@ class PartialEvaluator {
return false;
}

_hasTransferMaps(transferObj) {
let transferArray;
if (Array.isArray(transferObj)) {
transferArray = transferObj;
} else if (isPDFFunction(transferObj)) {
transferArray = [transferObj];
} else {
return false;
}

const numFns = transferArray.length;
if (!(numFns === 1 || numFns === 4)) {
return false;
}

let numEffectfulFns = 0;
for (const entry of transferArray) {
const transfer = this.xref.fetchIfRef(entry);
if (isName(transfer, "Identity")) {
continue;
}
if (!isPDFFunction(transfer)) {
return false;
}
numEffectfulFns++;
}
return numEffectfulFns > 0;
}

hasCanvasFilters(resources) {
if (!(resources instanceof Dict)) {
return false;
}

const processed = new RefSet();
if (resources.objId) {
processed.put(resources.objId);
}
const xref = this.xref;
const nodes = [resources];
while (nodes.length) {
const node = nodes.shift();

const graphicStates = node.get("ExtGState");
if (graphicStates instanceof Dict) {
for (let graphicState of graphicStates.getRawValues()) {
if (graphicState instanceof Ref) {
if (processed.has(graphicState)) {
continue;
}
try {
graphicState = xref.fetch(graphicState);
} catch (ex) {
info(`hasCanvasFilters - failed to fetch ExtGState: "${ex}".`);
// A fetch failure means we can't inspect the resource, so fall
// back to main-thread rendering rather than misclassify a corrupt
// PDF as filter-free.
return true;
}
}
if (!(graphicState instanceof Dict)) {
continue;
}
if (graphicState.objId) {
processed.put(graphicState.objId);
}
try {
if (this._hasTransferMaps(graphicState.get("TR"))) {
return true;
}
} catch (ex) {
info(`hasCanvasFilters - failed to inspect filter data: "${ex}".`);
return true;
}
}
}

const xObjects = node.get("XObject");
if (xObjects instanceof Dict) {
for (let xObject of xObjects.getRawValues()) {
if (xObject instanceof Ref) {
if (processed.has(xObject)) {
continue;
}
try {
xObject = xref.fetch(xObject);
} catch (ex) {
info(`hasCanvasFilters - failed to fetch XObject: "${ex}".`);
return true;
}
}
if (!(xObject instanceof BaseStream)) {
continue;
}
if (xObject.dict.objId) {
processed.put(xObject.dict.objId);
}
const xResources = xObject.dict.get("Resources");
if (!(xResources instanceof Dict)) {
continue;
}
if (xResources.objId && processed.has(xResources.objId)) {
continue;
}

nodes.push(xResources);
if (xResources.objId) {
processed.put(xResources.objId);
}
}
}
}
Comment on lines +445 to +512

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two loops look very similar, maybe we could have an helper function to avoid redundancy.

@Aditi-1400 Aditi-1400 Jun 1, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try doing that, in one of the earlier versions of the patch, I think this is probably the commit which captures the idea, later though I felt it added too much complexity and reduced readability of the code without significant returns, so I isolated them, let me know if you prefer extracting the common bits though, it should be a minimal effort to go back to that approach

return false;
}

async fetchBuiltInCMap(name) {
const cachedData = this.builtInCMapCache.get(name);
if (cachedData) {
Expand Down
Loading
Loading