Merge pull request #16151 from Snuffleupagus/DefaultFilterFactory

[api-minor] Extend general transfer function support to browsers without `OffscreenCanvas`
This commit is contained in:
Jonas Jenwald 2023-03-14 14:03:26 +01:00 committed by GitHub
commit 5e4b3d13eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 108 additions and 137 deletions

View File

@ -417,8 +417,6 @@ class Page {
this.resources, this.resources,
this.nonBlendModesSet this.nonBlendModesSet
), ),
isOffscreenCanvasSupported:
this.evaluatorOptions.isOffscreenCanvasSupported,
pageIndex: this.pageIndex, pageIndex: this.pageIndex,
cacheKey, cacheKey,
}); });

View File

@ -46,8 +46,8 @@ import {
deprecated, deprecated,
DOMCanvasFactory, DOMCanvasFactory,
DOMCMapReaderFactory, DOMCMapReaderFactory,
DOMFilterFactory,
DOMStandardFontDataFactory, DOMStandardFontDataFactory,
FilterFactory,
isDataScheme, isDataScheme,
isValidFetchUrl, isValidFetchUrl,
loadScript, loadScript,
@ -71,17 +71,20 @@ const DELAYED_CLEANUP_TIMEOUT = 5000; // ms
let DefaultCanvasFactory = DOMCanvasFactory; let DefaultCanvasFactory = DOMCanvasFactory;
let DefaultCMapReaderFactory = DOMCMapReaderFactory; let DefaultCMapReaderFactory = DOMCMapReaderFactory;
let DefaultFilterFactory = DOMFilterFactory;
let DefaultStandardFontDataFactory = DOMStandardFontDataFactory; let DefaultStandardFontDataFactory = DOMStandardFontDataFactory;
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS) { if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS) {
const { const {
NodeCanvasFactory, NodeCanvasFactory,
NodeCMapReaderFactory, NodeCMapReaderFactory,
NodeFilterFactory,
NodeStandardFontDataFactory, NodeStandardFontDataFactory,
} = require("./node_utils.js"); } = require("./node_utils.js");
DefaultCanvasFactory = NodeCanvasFactory; DefaultCanvasFactory = NodeCanvasFactory;
DefaultCMapReaderFactory = NodeCMapReaderFactory; DefaultCMapReaderFactory = NodeCMapReaderFactory;
DefaultFilterFactory = NodeFilterFactory;
DefaultStandardFontDataFactory = NodeStandardFontDataFactory; DefaultStandardFontDataFactory = NodeStandardFontDataFactory;
} }
@ -342,7 +345,7 @@ function getDocument(src) {
const canvasFactory = const canvasFactory =
src.canvasFactory || new DefaultCanvasFactory({ ownerDocument }); src.canvasFactory || new DefaultCanvasFactory({ ownerDocument });
const filterFactory = const filterFactory =
src.filterFactory || new FilterFactory({ docId, ownerDocument }); src.filterFactory || new DefaultFilterFactory({ docId, ownerDocument });
// Parameters only intended for development/testing purposes. // Parameters only intended for development/testing purposes.
const styleElement = const styleElement =
@ -784,6 +787,13 @@ class PDFDocumentProxy {
return this._transport.annotationStorage; return this._transport.annotationStorage;
} }
/**
* @type {Object} The filter factory instance.
*/
get filterFactory() {
return this._transport.filterFactory;
}
/** /**
* @type {number} Total number of pages in the PDF file. * @type {number} Total number of pages in the PDF file.
*/ */
@ -1512,25 +1522,19 @@ class PDFPageProxy {
intentState.displayReadyCapability.promise, intentState.displayReadyCapability.promise,
optionalContentConfigPromise, optionalContentConfigPromise,
]) ])
.then( .then(([transparency, optionalContentConfig]) => {
([ if (this.#pendingCleanup) {
{ transparency, isOffscreenCanvasSupported }, complete();
optionalContentConfig, return;
]) => {
if (this.#pendingCleanup) {
complete();
return;
}
this._stats?.time("Rendering");
internalRenderTask.initializeGraphics({
transparency,
isOffscreenCanvasSupported,
optionalContentConfig,
});
internalRenderTask.operatorListChanged();
} }
) this._stats?.time("Rendering");
internalRenderTask.initializeGraphics({
transparency,
optionalContentConfig,
});
internalRenderTask.operatorListChanged();
})
.catch(complete); .catch(complete);
return renderTask; return renderTask;
@ -1754,7 +1758,7 @@ class PDFPageProxy {
/** /**
* @private * @private
*/ */
_startRenderPage(transparency, isOffscreenCanvasSupported, cacheKey) { _startRenderPage(transparency, cacheKey) {
const intentState = this._intentStates.get(cacheKey); const intentState = this._intentStates.get(cacheKey);
if (!intentState) { if (!intentState) {
return; // Rendering was cancelled. return; // Rendering was cancelled.
@ -1763,10 +1767,7 @@ class PDFPageProxy {
// TODO Refactor RenderPageRequest to separate rendering // TODO Refactor RenderPageRequest to separate rendering
// and operator list logic // and operator list logic
intentState.displayReadyCapability?.resolve({ intentState.displayReadyCapability?.resolve(transparency);
transparency,
isOffscreenCanvasSupported,
});
} }
/** /**
@ -2728,11 +2729,7 @@ class WorkerTransport {
} }
const page = this.#pageCache.get(data.pageIndex); const page = this.#pageCache.get(data.pageIndex);
page._startRenderPage( page._startRenderPage(data.transparency, data.cacheKey);
data.transparency,
data.isOffscreenCanvasSupported,
data.cacheKey
);
}); });
messageHandler.on("commonobj", ([id, type, exportedData]) => { messageHandler.on("commonobj", ([id, type, exportedData]) => {
@ -3294,11 +3291,7 @@ class InternalRenderTask {
}); });
} }
initializeGraphics({ initializeGraphics({ transparency = false, optionalContentConfig }) {
transparency = false,
isOffscreenCanvasSupported = false,
optionalContentConfig,
}) {
if (this.cancelled) { if (this.cancelled) {
return; return;
} }
@ -3325,7 +3318,7 @@ class InternalRenderTask {
this.commonObjs, this.commonObjs,
this.objs, this.objs,
this.canvasFactory, this.canvasFactory,
isOffscreenCanvasSupported ? this.filterFactory : null, this.filterFactory,
{ optionalContentConfig }, { optionalContentConfig },
this.annotationCanvasMap, this.annotationCanvasMap,
this.pageColors this.pageColors
@ -3430,6 +3423,7 @@ export {
build, build,
DefaultCanvasFactory, DefaultCanvasFactory,
DefaultCMapReaderFactory, DefaultCMapReaderFactory,
DefaultFilterFactory,
DefaultStandardFontDataFactory, DefaultStandardFontDataFactory,
getDocument, getDocument,
LoopbackPort, LoopbackPort,

View File

@ -15,6 +15,20 @@
import { CMapCompressionType, unreachable } from "../shared/util.js"; import { CMapCompressionType, unreachable } from "../shared/util.js";
class BaseFilterFactory {
constructor() {
if (this.constructor === BaseFilterFactory) {
unreachable("Cannot initialize BaseFilterFactory.");
}
}
addFilter(maps) {
return "none";
}
destroy() {}
}
class BaseCanvasFactory { class BaseCanvasFactory {
constructor() { constructor() {
if (this.constructor === BaseCanvasFactory) { if (this.constructor === BaseCanvasFactory) {
@ -179,6 +193,7 @@ class BaseSVGFactory {
export { export {
BaseCanvasFactory, BaseCanvasFactory,
BaseCMapReaderFactory, BaseCMapReaderFactory,
BaseFilterFactory,
BaseStandardFontDataFactory, BaseStandardFontDataFactory,
BaseSVGFactory, BaseSVGFactory,
}; };

View File

@ -491,7 +491,7 @@ class CanvasExtraState {
this.strokeAlpha = 1; this.strokeAlpha = 1;
this.lineWidth = 1; this.lineWidth = 1;
this.activeSMask = null; this.activeSMask = null;
this.transferMaps = null; this.transferMaps = "none";
this.startNewPathAndClipBox([0, 0, width, height]); this.startNewPathAndClipBox([0, 0, width, height]);
} }
@ -588,7 +588,7 @@ class CanvasExtraState {
} }
} }
function putBinaryImageData(ctx, imgData, transferMaps = null) { function putBinaryImageData(ctx, imgData) {
if (typeof ImageData !== "undefined" && imgData instanceof ImageData) { if (typeof ImageData !== "undefined" && imgData instanceof ImageData) {
ctx.putImageData(imgData, 0, 0); ctx.putImageData(imgData, 0, 0);
return; return;
@ -618,24 +618,6 @@ function putBinaryImageData(ctx, imgData, transferMaps = null) {
const dest = chunkImgData.data; const dest = chunkImgData.data;
let i, j, thisChunkHeight, elemsInThisChunk; let i, j, thisChunkHeight, elemsInThisChunk;
let transferMapRed, transferMapGreen, transferMapBlue, transferMapGray;
if (transferMaps) {
switch (transferMaps.length) {
case 1:
transferMapRed = transferMaps[0];
transferMapGreen = transferMaps[0];
transferMapBlue = transferMaps[0];
transferMapGray = transferMaps[0];
break;
case 4:
transferMapRed = transferMaps[0];
transferMapGreen = transferMaps[1];
transferMapBlue = transferMaps[2];
transferMapGray = transferMaps[3];
break;
}
}
// There are multiple forms in which the pixel data can be passed, and // There are multiple forms in which the pixel data can be passed, and
// imgData.kind tells us which one this is. // imgData.kind tells us which one this is.
if (imgData.kind === ImageKind.GRAYSCALE_1BPP) { if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
@ -644,14 +626,8 @@ function putBinaryImageData(ctx, imgData, transferMaps = null) {
const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2); const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2);
const dest32DataLength = dest32.length; const dest32DataLength = dest32.length;
const fullSrcDiff = (width + 7) >> 3; const fullSrcDiff = (width + 7) >> 3;
let white = 0xffffffff; const white = 0xffffffff;
let black = FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; const black = FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
if (transferMapGray) {
if (transferMapGray[0] === 0xff && transferMapGray[0xff] === 0) {
[white, black] = [black, white];
}
}
for (i = 0; i < totalChunks; i++) { for (i = 0; i < totalChunks; i++) {
thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
@ -693,32 +669,12 @@ function putBinaryImageData(ctx, imgData, transferMaps = null) {
} }
} else if (imgData.kind === ImageKind.RGBA_32BPP) { } else if (imgData.kind === ImageKind.RGBA_32BPP) {
// RGBA, 32-bits per pixel. // RGBA, 32-bits per pixel.
const hasTransferMaps = !!(
transferMapRed ||
transferMapGreen ||
transferMapBlue
);
j = 0; j = 0;
elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4; elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
for (i = 0; i < fullChunks; i++) { for (i = 0; i < fullChunks; i++) {
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
srcPos += elemsInThisChunk; srcPos += elemsInThisChunk;
if (hasTransferMaps) {
for (let k = 0; k < elemsInThisChunk; k += 4) {
if (transferMapRed) {
dest[k + 0] = transferMapRed[dest[k + 0]];
}
if (transferMapGreen) {
dest[k + 1] = transferMapGreen[dest[k + 1]];
}
if (transferMapBlue) {
dest[k + 2] = transferMapBlue[dest[k + 2]];
}
}
}
ctx.putImageData(chunkImgData, 0, j); ctx.putImageData(chunkImgData, 0, j);
j += FULL_CHUNK_HEIGHT; j += FULL_CHUNK_HEIGHT;
} }
@ -726,30 +682,10 @@ function putBinaryImageData(ctx, imgData, transferMaps = null) {
elemsInThisChunk = width * partialChunkHeight * 4; elemsInThisChunk = width * partialChunkHeight * 4;
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
if (hasTransferMaps) {
for (let k = 0; k < elemsInThisChunk; k += 4) {
if (transferMapRed) {
dest[k + 0] = transferMapRed[dest[k + 0]];
}
if (transferMapGreen) {
dest[k + 1] = transferMapGreen[dest[k + 1]];
}
if (transferMapBlue) {
dest[k + 2] = transferMapBlue[dest[k + 2]];
}
}
}
ctx.putImageData(chunkImgData, 0, j); ctx.putImageData(chunkImgData, 0, j);
} }
} else if (imgData.kind === ImageKind.RGB_24BPP) { } else if (imgData.kind === ImageKind.RGB_24BPP) {
// RGB, 24-bits per pixel. // RGB, 24-bits per pixel.
const hasTransferMaps = !!(
transferMapRed ||
transferMapGreen ||
transferMapBlue
);
thisChunkHeight = FULL_CHUNK_HEIGHT; thisChunkHeight = FULL_CHUNK_HEIGHT;
elemsInThisChunk = width * thisChunkHeight; elemsInThisChunk = width * thisChunkHeight;
for (i = 0; i < totalChunks; i++) { for (i = 0; i < totalChunks; i++) {
@ -766,20 +702,6 @@ function putBinaryImageData(ctx, imgData, transferMaps = null) {
dest[destPos++] = 255; dest[destPos++] = 255;
} }
if (hasTransferMaps) {
for (let k = 0; k < destPos; k += 4) {
if (transferMapRed) {
dest[k + 0] = transferMapRed[dest[k + 0]];
}
if (transferMapGreen) {
dest[k + 1] = transferMapGreen[dest[k + 1]];
}
if (transferMapBlue) {
dest[k + 2] = transferMapBlue[dest[k + 2]];
}
}
}
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
} }
} else { } else {
@ -865,7 +787,10 @@ function resetCtxToDefault(ctx, foregroundColor) {
ctx.setLineDash([]); ctx.setLineDash([]);
ctx.lineDashOffset = 0; ctx.lineDashOffset = 0;
} }
if (!isNodeJS) { if (
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
!isNodeJS
) {
ctx.filter = "none"; ctx.filter = "none";
} }
} }
@ -1591,12 +1516,8 @@ class CanvasGraphics {
this.checkSMaskState(); this.checkSMaskState();
break; break;
case "TR": case "TR":
if (this.filterFactory) { this.ctx.filter = this.current.transferMaps =
this.ctx.filter = this.current.transferMaps = this.filterFactory.addFilter(value);
this.filterFactory.addFilter(value);
} else {
this.current.transferMaps = value;
}
break; break;
} }
} }
@ -3042,8 +2963,25 @@ class CanvasGraphics {
this.paintInlineImageXObjectGroup(imgData, map); this.paintInlineImageXObjectGroup(imgData, map);
} }
applyTransferMapsToCanvas(ctx) {
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
if (this.current.transferMaps !== "none") {
warn("Ignoring transferMaps - `OffscreenCanvas` support is disabled.");
}
return ctx.canvas;
}
if (this.current.transferMaps === "none") {
return ctx.canvas;
}
ctx.filter = this.current.transferMaps;
ctx.drawImage(ctx.canvas, 0, 0);
ctx.filter = "none";
return ctx.canvas;
}
applyTransferMapsToBitmap(imgData) { applyTransferMapsToBitmap(imgData) {
if (!this.current.transferMaps || this.current.transferMaps === "none") { if (this.current.transferMaps === "none") {
return imgData.bitmap; return imgData.bitmap;
} }
const { bitmap, width, height } = imgData; const { bitmap, width, height } = imgData;
@ -3070,7 +3008,10 @@ class CanvasGraphics {
this.save(); this.save();
if (!isNodeJS) { if (
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
!isNodeJS
) {
// The filter, if any, will be applied in applyTransferMapsToBitmap. // The filter, if any, will be applied in applyTransferMapsToBitmap.
// It must be applied to the image before rescaling else some artifacts // It must be applied to the image before rescaling else some artifacts
// could appear. // could appear.
@ -3097,8 +3038,8 @@ class CanvasGraphics {
height height
); );
const tmpCtx = tmpCanvas.context; const tmpCtx = tmpCanvas.context;
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps); putBinaryImageData(tmpCtx, imgData);
imgToPaint = tmpCanvas.canvas; imgToPaint = this.applyTransferMapsToCanvas(tmpCtx);
} }
const scaled = this._scaleImage( const scaled = this._scaleImage(
@ -3140,8 +3081,8 @@ class CanvasGraphics {
const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h); const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
const tmpCtx = tmpCanvas.context; const tmpCtx = tmpCanvas.context;
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps); putBinaryImageData(tmpCtx, imgData);
imgToPaint = tmpCanvas.canvas; imgToPaint = this.applyTransferMapsToCanvas(tmpCtx);
} }
for (const entry of map) { for (const entry of map) {

View File

@ -16,6 +16,7 @@
import { import {
BaseCanvasFactory, BaseCanvasFactory,
BaseCMapReaderFactory, BaseCMapReaderFactory,
BaseFilterFactory,
BaseStandardFontDataFactory, BaseStandardFontDataFactory,
BaseSVGFactory, BaseSVGFactory,
} from "./base_factory.js"; } from "./base_factory.js";
@ -48,7 +49,7 @@ class PixelsPerInch {
* an image without the need to apply them on the pixel arrays: the renderer * an image without the need to apply them on the pixel arrays: the renderer
* does the magic for us. * does the magic for us.
*/ */
class FilterFactory { class DOMFilterFactory extends BaseFilterFactory {
#_cache; #_cache;
#_defs; #_defs;
@ -60,6 +61,7 @@ class FilterFactory {
#id = 0; #id = 0;
constructor({ docId, ownerDocument = globalThis.document } = {}) { constructor({ docId, ownerDocument = globalThis.document } = {}) {
super();
this.#docId = docId; this.#docId = docId;
this.#document = ownerDocument; this.#document = ownerDocument;
} }
@ -823,9 +825,9 @@ export {
deprecated, deprecated,
DOMCanvasFactory, DOMCanvasFactory,
DOMCMapReaderFactory, DOMCMapReaderFactory,
DOMFilterFactory,
DOMStandardFontDataFactory, DOMStandardFontDataFactory,
DOMSVGFactory, DOMSVGFactory,
FilterFactory,
getColorValues, getColorValues,
getCurrentTransform, getCurrentTransform,
getCurrentTransformInverse, getCurrentTransformInverse,

View File

@ -17,6 +17,7 @@
import { import {
BaseCanvasFactory, BaseCanvasFactory,
BaseCMapReaderFactory, BaseCMapReaderFactory,
BaseFilterFactory,
BaseStandardFontDataFactory, BaseStandardFontDataFactory,
} from "./base_factory.js"; } from "./base_factory.js";
@ -39,6 +40,8 @@ const fetchData = function (url) {
}); });
}; };
class NodeFilterFactory extends BaseFilterFactory {}
class NodeCanvasFactory extends BaseCanvasFactory { class NodeCanvasFactory extends BaseCanvasFactory {
/** /**
* @ignore * @ignore
@ -72,5 +75,6 @@ class NodeStandardFontDataFactory extends BaseStandardFontDataFactory {
export { export {
NodeCanvasFactory, NodeCanvasFactory,
NodeCMapReaderFactory, NodeCMapReaderFactory,
NodeFilterFactory,
NodeStandardFontDataFactory, NodeStandardFontDataFactory,
}; };

View File

@ -51,7 +51,6 @@ import {
version, version,
} from "./display/api.js"; } from "./display/api.js";
import { import {
FilterFactory,
getFilenameFromUrl, getFilenameFromUrl,
getPdfFilenameFromUrl, getPdfFilenameFromUrl,
getXfaPageViewport, getXfaPageViewport,
@ -91,7 +90,6 @@ export {
createPromiseCapability, createPromiseCapability,
createValidAbsoluteUrl, createValidAbsoluteUrl,
FeatureTest, FeatureTest,
FilterFactory,
getDocument, getDocument,
getFilenameFromUrl, getFilenameFromUrl,
getPdfFilenameFromUrl, getPdfFilenameFromUrl,

View File

@ -469,6 +469,8 @@ class Driver {
.getElementsByTagName("head")[0] .getElementsByTagName("head")[0]
.append(xfaStyleElement); .append(xfaStyleElement);
} }
const isOffscreenCanvasSupported =
task.isOffscreenCanvasSupported === false ? false : undefined;
const loadingTask = getDocument({ const loadingTask = getDocument({
url: new URL(task.file, window.location), url: new URL(task.file, window.location),
@ -480,6 +482,7 @@ class Driver {
useSystemFonts: task.useSystemFonts, useSystemFonts: task.useSystemFonts,
useWorkerFetch: task.useWorkerFetch, useWorkerFetch: task.useWorkerFetch,
enableXfa: task.enableXfa, enableXfa: task.enableXfa,
isOffscreenCanvasSupported,
styleElement: xfaStyleElement, styleElement: xfaStyleElement,
}); });
let promise = loadingTask.promise; let promise = loadingTask.promise;

View File

@ -6281,6 +6281,13 @@
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "issue6931-disable-isOffscreenCanvasSupported",
"file": "pdfs/issue6931_reduced.pdf",
"md5": "e61388913821a5e044bf85a5846d6d9a",
"rounds": 1,
"type": "eq",
"isOffscreenCanvasSupported": false
},
{ "id": "annotation-button-widget-annotations", { "id": "annotation-button-widget-annotations",
"file": "pdfs/annotation-button-widget.pdf", "file": "pdfs/annotation-button-widget.pdf",
"md5": "5cf23adfff84256d9cfe261bea96dade", "md5": "5cf23adfff84256d9cfe261bea96dade",
@ -7474,6 +7481,15 @@
"link": true, "link": true,
"type": "eq" "type": "eq"
}, },
{
"id": "issue16114-disable-isOffscreenCanvasSupported",
"file": "pdfs/issue16114.pdf",
"md5": "c04827ea33692e0f94a5e51716d9aa2e",
"rounds": 1,
"link": true,
"type": "eq",
"isOffscreenCanvasSupported": false
},
{ {
"id": "bug1820909", "id": "bug1820909",
"file": "pdfs/bug1820909.pdf", "file": "pdfs/bug1820909.pdf",