[api-minor] Decode all JPEG images with the built-in PDF.js decoder in src/core/jpg.js

Currently some JPEG images are decoded by the built-in PDF.js decoder in `src/core/jpg.js`, while others attempt to use the browser JPEG decoder. This inconsistency seem unfortunate for a number of reasons:

 - It adds, compared to the other image formats supported in the PDF specification, a fair amount of code/complexity to the image handling in the PDF.js library.

 - The PDF specification support JPEG images with features, e.g. certain ColorSpaces, that browsers are unable to decode natively. Hence, determining if a JPEG image is possible to decode natively in the browser require a non-trivial amount of parsing. In particular, we're parsing (part of) the raw JPEG data to extract certain marker data and we also need to parse the ColorSpace for the JPEG image.

 - While some JPEG images may, for all intents and purposes, appear to be natively supported there's still cases where the browser may fail to decode some JPEG images. In order to support those cases, we've had to implement a fallback to the PDF.js JPEG decoder if there's any issues during the native decoding. This also means that it's no longer possible to simply send the JPEG image to the main-thread and continue parsing, but you now need to actually wait for the main-thread to indicate success/failure first.
   In practice this means that there's a code-path where the worker-thread is forced to wait for the main-thread, while the reverse should *always* be the case.

 - The native decoding, for anything except the *simplest* of JPEG images, result in increased peak memory usage because there's a handful of short-lived copies of the JPEG data (see PR 11707).
Furthermore this also leads to data being *parsed* on the main-thread, rather than the worker-thread, which you usually want to avoid for e.g. performance and UI-reponsiveness reasons.

 - Not all environments, e.g. Node.js, fully support native JPEG decoding. This has, historically, lead to some issues and support requests.

 - Different browsers may use different JPEG decoders, possibly leading to images being rendered slightly differently depending on the platform/browser where the PDF.js library is used.

Originally the implementation in `src/core/jpg.js` were unable to handle all of the JPEG images in the test-suite, but over the last couple of years I've fixed (hopefully) all of those issues.
At this point in time, there's two kinds of failure with this patch:

 - Changes which are basically imperceivable to the naked eye, where some pixels in the images are essentially off-by-one (in all components), which could probably be attributed to things such as different rounding behaviour in the browser/PDF.js JPEG decoder.
   This type of "failure" accounts for the *vast* majority of the total number of changes in the reference tests.

 - Changes where the JPEG images now looks *ever so slightly* blurrier than with the native browser decoder. For quite some time I've just assumed that this pointed to a general deficiency in the `src/core/jpg.js` implementation, however I've discovered when comparing two viewers side-by-side that the differences vanish at higher zoom levels (usually around 200% is enough).
   Basically if you disable [this downscaling in canvas.js](8fb82e939c/src/display/canvas.js (L2356-L2395)), which is what happens when zooming in, the differences simply vanish!
   Hence I'm pretty satisfied that there's no significant problems with the `src/core/jpg.js` implementation, and the problems are rather tied to the general quality of the downscaling algorithm used. It could even be seen as a positive that *all* images now share the same downscaling behaviour, since this actually fixes one old bug; see issue 7041.
This commit is contained in:
Jonas Jenwald 2020-01-20 12:10:16 +01:00
parent 4a3a24b002
commit 0351852d74
15 changed files with 32 additions and 602 deletions

View File

@ -87,9 +87,6 @@ function writeSvgToFile(svgElement, filePath) {
var loadingTask = pdfjsLib.getDocument({
data: data,
fontExtraProperties: true,
// Try to export JPEG images directly if they don't need any further
// processing.
nativeImageDecoderSupport: pdfjsLib.NativeImageDecoding.DISPLAY,
});
loadingTask.promise
.then(function (doc) {

View File

@ -25,7 +25,6 @@ import {
isArrayEqual,
isNum,
isString,
NativeImageDecoding,
OPS,
stringToPDFString,
TextRenderingMode,
@ -80,9 +79,7 @@ import { DecodeStream } from "./stream.js";
import { getGlyphsUnicode } from "./glyphlist.js";
import { getMetrics } from "./metrics.js";
import { isPDFFunction } from "./function.js";
import { JpegStream } from "./jpeg_stream.js";
import { MurmurHash3_64 } from "./murmurhash3.js";
import { NativeImageDecoder } from "./image_utils.js";
import { OperatorList } from "./operator_list.js";
import { PDFImage } from "./image.js";
@ -91,7 +88,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
forceDataSchema: false,
maxImageSize: -1,
disableFontFace: false,
nativeImageDecoderSupport: NativeImageDecoding.DECODE,
ignoreErrors: false,
isEvalSupported: true,
fontExtraProperties: false,
@ -450,7 +446,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList,
cacheKey,
imageCache,
forceDisableNativeImageDecoder = false,
}) {
var dict = image.dict;
const imageRef = dict.objId;
@ -510,13 +505,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var SMALL_IMAGE_DIMENSIONS = 200;
// Inlining small images into the queue as RGB data
if (
isInline &&
!softMask &&
!mask &&
!(image instanceof JpegStream) &&
w + h < SMALL_IMAGE_DIMENSIONS
) {
if (isInline && !softMask && !mask && w + h < SMALL_IMAGE_DIMENSIONS) {
const imageObj = new PDFImage({
xref: this.xref,
res: resources,
@ -531,20 +520,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
return undefined;
}
let nativeImageDecoderSupport = forceDisableNativeImageDecoder
? NativeImageDecoding.NONE
: this.options.nativeImageDecoderSupport;
// If there is no imageMask, create the PDFImage and a lot
// of image processing can be done here.
let objId = `img_${this.idFactory.createObjId()}`,
cacheGlobally = false;
if (this.parsingType3Font) {
assert(
nativeImageDecoderSupport === NativeImageDecoding.NONE,
"Type3 image resources should be completely decoded in the worker."
);
objId = `${this.idFactory.getDocId()}_type3res_${objId}`;
} else if (imageRef) {
cacheGlobally = this.globalImageCache.shouldCache(
@ -553,102 +534,19 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
);
if (cacheGlobally) {
// Ensure that the image is *completely* decoded on the worker-thread,
// in order to simplify the caching/rendering code on the main-thread.
nativeImageDecoderSupport = NativeImageDecoding.NONE;
objId = `${this.idFactory.getDocId()}_${objId}`;
}
}
if (
nativeImageDecoderSupport !== NativeImageDecoding.NONE &&
!softMask &&
!mask &&
image instanceof JpegStream &&
image.maybeValidDimensions &&
NativeImageDecoder.isSupported(
image,
this.xref,
resources,
this.pdfFunctionFactory
)
) {
// These JPEGs don't need any more processing so we can just send it.
return this.handler
.sendWithPromise("obj", [
objId,
this.pageIndex,
"JpegStream",
image.getIR(this.options.forceDataSchema),
])
.then(
() => {
// Only add the dependency once we know that the native JPEG
// decoding succeeded, to ensure that rendering will always
// complete.
operatorList.addDependency(objId);
args = [objId, w, h];
operatorList.addOp(OPS.paintJpegXObject, args);
if (cacheKey) {
imageCache[cacheKey] = {
fn: OPS.paintJpegXObject,
args,
};
if (imageRef) {
this.globalImageCache.addPageIndex(imageRef, this.pageIndex);
}
}
},
reason => {
warn(
"Native JPEG decoding failed -- trying to recover: " +
(reason && reason.message)
);
// Try to decode the JPEG image with the built-in decoder instead.
return this.buildPaintImageXObject({
resources,
image,
isInline,
operatorList,
cacheKey,
imageCache,
forceDisableNativeImageDecoder: true,
});
}
);
}
// Creates native image decoder only if a JPEG image or mask is present.
var nativeImageDecoder = null;
if (
nativeImageDecoderSupport === NativeImageDecoding.DECODE &&
(image instanceof JpegStream ||
mask instanceof JpegStream ||
softMask instanceof JpegStream)
) {
nativeImageDecoder = new NativeImageDecoder({
xref: this.xref,
resources,
handler: this.handler,
forceDataSchema: this.options.forceDataSchema,
pdfFunctionFactory: this.pdfFunctionFactory,
});
}
// Ensure that the dependency is added before the image is decoded.
operatorList.addDependency(objId);
args = [objId, w, h];
const imgPromise = PDFImage.buildImage({
handler: this.handler,
xref: this.xref,
res: resources,
image,
isInline,
nativeDecoder: nativeImageDecoder,
pdfFunctionFactory: this.pdfFunctionFactory,
})
.then(imageObj => {
@ -3393,7 +3291,6 @@ class TranslatedFont {
// the rendering code on the main-thread (see issue10717.pdf).
var type3Options = Object.create(evaluator.options);
type3Options.ignoreErrors = false;
type3Options.nativeImageDecoderSupport = NativeImageDecoding.NONE;
var type3Evaluator = evaluator.clone(type3Options);
type3Evaluator.parsingType3Font = true;

View File

@ -21,23 +21,6 @@ import { JpegStream } from "./jpeg_stream.js";
import { JpxImage } from "./jpx.js";
var PDFImage = (function PDFImageClosure() {
/**
* Decodes the image using native decoder if possible. Resolves the promise
* when the image data is ready.
*/
function handleImageData(image, nativeDecoder) {
if (nativeDecoder && nativeDecoder.canDecode(image)) {
return nativeDecoder.decode(image).catch(reason => {
warn(
"Native image decoding failed -- trying to recover: " +
(reason && reason.message)
);
return image;
});
}
return Promise.resolve(image);
}
/**
* Decode and clamp a value. The formula is different from the spec because we
* don't decode to float range [0,1], we decode it in the [0,max] range.
@ -266,42 +249,30 @@ var PDFImage = (function PDFImageClosure() {
* with a PDFImage when the image is ready to be used.
*/
PDFImage.buildImage = function ({
handler,
xref,
res,
image,
isInline = false,
nativeDecoder = null,
pdfFunctionFactory,
}) {
var imagePromise = handleImageData(image, nativeDecoder);
var smaskPromise;
var maskPromise;
const imageData = image;
let smaskData = null;
let maskData = null;
var smask = image.dict.get("SMask");
var mask = image.dict.get("Mask");
const smask = image.dict.get("SMask");
const mask = image.dict.get("Mask");
if (smask) {
smaskPromise = handleImageData(smask, nativeDecoder);
maskPromise = Promise.resolve(null);
} else {
smaskPromise = Promise.resolve(null);
if (mask) {
if (isStream(mask)) {
maskPromise = handleImageData(mask, nativeDecoder);
} else if (Array.isArray(mask)) {
maskPromise = Promise.resolve(mask);
smaskData = smask;
} else if (mask) {
if (isStream(mask) || Array.isArray(mask)) {
maskData = mask;
} else {
warn("Unsupported mask format.");
maskPromise = Promise.resolve(null);
}
} else {
maskPromise = Promise.resolve(null);
}
}
return Promise.all([imagePromise, smaskPromise, maskPromise]).then(
function ([imageData, smaskData, maskData]) {
return new PDFImage({
return Promise.resolve(
new PDFImage({
xref,
res,
image: imageData,
@ -309,8 +280,7 @@ var PDFImage = (function PDFImageClosure() {
smask: smaskData,
mask: maskData,
pdfFunctionFactory,
});
}
})
);
};

View File

@ -15,103 +15,7 @@
/* eslint no-var: error */
import { assert, info, shadow } from "../shared/util.js";
import { ColorSpace } from "./colorspace.js";
import { JpegStream } from "./jpeg_stream.js";
import { RefSetCache } from "./primitives.js";
import { Stream } from "./stream.js";
class NativeImageDecoder {
constructor({
xref,
resources,
handler,
forceDataSchema = false,
pdfFunctionFactory,
}) {
this.xref = xref;
this.resources = resources;
this.handler = handler;
this.forceDataSchema = forceDataSchema;
this.pdfFunctionFactory = pdfFunctionFactory;
}
canDecode(image) {
return (
image instanceof JpegStream &&
image.maybeValidDimensions &&
NativeImageDecoder.isDecodable(
image,
this.xref,
this.resources,
this.pdfFunctionFactory
)
);
}
decode(image) {
// For natively supported JPEGs send them to the main thread for decoding.
const dict = image.dict;
let colorSpace = dict.get("ColorSpace", "CS");
colorSpace = ColorSpace.parse(
colorSpace,
this.xref,
this.resources,
this.pdfFunctionFactory
);
return this.handler
.sendWithPromise("JpegDecode", [
image.getIR(this.forceDataSchema),
colorSpace.numComps,
])
.then(function ({ data, width, height }) {
return new Stream(data, 0, data.length, dict);
});
}
/**
* Checks if the image can be decoded and displayed by the browser without any
* further processing such as color space conversions.
*/
static isSupported(image, xref, res, pdfFunctionFactory) {
const dict = image.dict;
if (dict.has("DecodeParms") || dict.has("DP")) {
return false;
}
const cs = ColorSpace.parse(
dict.get("ColorSpace", "CS"),
xref,
res,
pdfFunctionFactory
);
// isDefaultDecode() of DeviceGray and DeviceRGB needs no `bpc` argument.
return (
(cs.name === "DeviceGray" || cs.name === "DeviceRGB") &&
cs.isDefaultDecode(dict.getArray("Decode", "D"))
);
}
/**
* Checks if the image can be decoded by the browser.
*/
static isDecodable(image, xref, res, pdfFunctionFactory) {
const dict = image.dict;
if (dict.has("DecodeParms") || dict.has("DP")) {
return false;
}
const cs = ColorSpace.parse(
dict.get("ColorSpace", "CS"),
xref,
res,
pdfFunctionFactory
);
const bpc = dict.get("BitsPerComponent", "BPC") || 1;
return (
(cs.numComps === 1 || cs.numComps === 3) &&
cs.isDefaultDecode(dict.getArray("Decode", "D"), bpc)
);
}
}
class GlobalImageCache {
static get NUM_PAGES_THRESHOLD() {
@ -205,4 +109,4 @@ class GlobalImageCache {
}
}
export { NativeImageDecoder, GlobalImageCache };
export { GlobalImageCache };

View File

@ -13,17 +13,14 @@
* limitations under the License.
*/
import { createObjectURL, shadow } from "../shared/util.js";
import { DecodeStream } from "./stream.js";
import { isDict } from "./primitives.js";
import { JpegImage } from "./jpg.js";
import { shadow } from "../shared/util.js";
/**
* Depending on the type of JPEG a JpegStream is handled in different ways. For
* JPEG's that are supported natively such as DeviceGray and DeviceRGB the image
* data is stored and then loaded by the browser. For unsupported JPEG's we use
* a library to decode these images and the stream behaves like all the other
* DecodeStreams.
* For JPEG's we use a library to decode these images and the stream behaves
* like all the other DecodeStreams.
*/
const JpegStream = (function JpegStreamClosure() {
// eslint-disable-next-line no-shadow
@ -110,150 +107,6 @@ const JpegStream = (function JpegStreamClosure() {
this.eof = true;
};
Object.defineProperty(JpegStream.prototype, "maybeValidDimensions", {
get: function JpegStream_maybeValidDimensions() {
const { dict, stream } = this;
const dictHeight = dict.get("Height", "H");
const startPos = stream.pos;
let validDimensions = true,
foundSOF = false,
b;
while ((b = stream.getByte()) !== -1) {
if (b !== 0xff) {
// Not a valid marker.
continue;
}
switch (stream.getByte()) {
case 0xc0: // SOF0
case 0xc1: // SOF1
case 0xc2: // SOF2
// These three SOF{n} markers are the only ones that the built-in
// PDF.js JPEG decoder currently supports.
foundSOF = true;
stream.pos += 2; // Skip marker length.
stream.pos += 1; // Skip precision.
const scanLines = stream.getUint16();
const samplesPerLine = stream.getUint16();
// Letting the browser handle the JPEG decoding, on the main-thread,
// will cause a *large* increase in peak memory usage since there's
// a handful of short-lived copies of the image data. For very big
// JPEG images, always let the PDF.js image decoder handle them to
// reduce overall memory usage during decoding (see issue 11694).
if (scanLines * samplesPerLine > 1e6) {
validDimensions = false;
break;
}
// The "normal" case, where the image data and dictionary agrees.
if (scanLines === dictHeight) {
break;
}
// A DNL (Define Number of Lines) marker is expected,
// which browsers (usually) cannot decode natively.
if (scanLines === 0) {
validDimensions = false;
break;
}
// The dimensions of the image, among other properties, should
// always be taken from the image data *itself* rather than the
// XObject dictionary. However there's cases of corrupt images that
// browsers cannot decode natively, for example:
// - JPEG images with DNL markers, where the SOF `scanLines`
// parameter has an unexpected value (see issue 8614).
// - JPEG images with too large SOF `scanLines` parameter, where
// the EOI marker is encountered prematurely (see issue 10880).
// In an attempt to handle these kinds of corrupt images, compare
// the dimensions in the image data with the dictionary and *always*
// let the PDF.js JPEG decoder (rather than the browser) handle the
// image if the difference is larger than one order of magnitude
// (since that would generally suggest that something is off).
if (scanLines > dictHeight * 10) {
validDimensions = false;
break;
}
break;
case 0xc3: // SOF3
/* falls through */
case 0xc5: // SOF5
case 0xc6: // SOF6
case 0xc7: // SOF7
/* falls through */
case 0xc9: // SOF9
case 0xca: // SOF10
case 0xcb: // SOF11
/* falls through */
case 0xcd: // SOF13
case 0xce: // SOF14
case 0xcf: // SOF15
foundSOF = true;
break;
case 0xc4: // DHT
case 0xcc: // DAC
/* falls through */
case 0xda: // SOS
case 0xdb: // DQT
case 0xdc: // DNL
case 0xdd: // DRI
case 0xde: // DHP
case 0xdf: // EXP
/* falls through */
case 0xe0: // APP0
case 0xe1: // APP1
case 0xe2: // APP2
case 0xe3: // APP3
case 0xe4: // APP4
case 0xe5: // APP5
case 0xe6: // APP6
case 0xe7: // APP7
case 0xe8: // APP8
case 0xe9: // APP9
case 0xea: // APP10
case 0xeb: // APP11
case 0xec: // APP12
case 0xed: // APP13
case 0xee: // APP14
case 0xef: // APP15
/* falls through */
case 0xfe: // COM
const markerLength = stream.getUint16();
if (markerLength > 2) {
stream.skip(markerLength - 2); // Jump to the next marker.
} else {
// The marker length is invalid, resetting the stream position.
stream.skip(-2);
}
break;
case 0xff: // Fill byte.
// Avoid skipping a valid marker, resetting the stream position.
stream.skip(-1);
break;
case 0xd9: // EOI
foundSOF = true;
break;
}
if (foundSOF) {
break;
}
}
// Finally, don't forget to reset the stream position.
stream.pos = startPos;
return shadow(this, "maybeValidDimensions", validDimensions);
},
configurable: true,
});
JpegStream.prototype.getIR = function (forceDataSchema = false) {
return createObjectURL(this.bytes, "image/jpeg", forceDataSchema);
};
return JpegStream;
})();

View File

@ -402,7 +402,6 @@ var WorkerMessageHandler = {
forceDataSchema: data.disableCreateObjectURL,
maxImageSize: data.maxImageSize,
disableFontFace: data.disableFontFace,
nativeImageDecoderSupport: data.nativeImageDecoderSupport,
ignoreErrors: data.ignoreErrors,
isEvalSupported: data.isEvalSupported,
fontExtraProperties: data.fontExtraProperties,

View File

@ -28,7 +28,6 @@ import {
isArrayBuffer,
isSameOrigin,
MissingPDFException,
NativeImageDecoding,
PasswordException,
setVerbosityLevel,
shadow,
@ -116,14 +115,6 @@ function setPDFNetworkStreamFactory(pdfNetworkStreamFactory) {
* @property {string} [docBaseUrl] - The base URL of the document,
* used when attempting to recover valid absolute URLs for annotations, and
* outline items, that (incorrectly) only specify relative URLs.
* @property {string} [nativeImageDecoderSupport] - Strategy for
* decoding certain (simple) JPEG images in the browser. This is useful for
* environments without DOM image and canvas support, such as e.g. Node.js.
* Valid values are 'decode', 'display' or 'none'; where 'decode' is intended
* for browsers with full image/canvas support, 'display' for environments
* with limited image support through stubs (useful for SVG conversion),
* and 'none' where JPEG images will be decoded entirely by PDF.js.
* The default value is 'decode'.
* @property {string} [cMapUrl] - The URL where the predefined
* Adobe CMaps are located. Include trailing slash.
* @property {boolean} [cMapPacked] - Specifies if the Adobe CMaps are
@ -260,15 +251,6 @@ function getDocument(src) {
params.fontExtraProperties = params.fontExtraProperties === true;
params.pdfBug = params.pdfBug === true;
const NativeImageDecoderValues = Object.values(NativeImageDecoding);
if (
params.nativeImageDecoderSupport === undefined ||
!NativeImageDecoderValues.includes(params.nativeImageDecoderSupport)
) {
params.nativeImageDecoderSupport =
apiCompatibilityParams.nativeImageDecoderSupport ||
NativeImageDecoding.DECODE;
}
if (!Number.isInteger(params.maxImageSize)) {
params.maxImageSize = -1;
}
@ -417,7 +399,6 @@ function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
disableCreateObjectURL: source.disableCreateObjectURL,
postMessageTransfers: worker.postMessageTransfers,
docBaseUrl: source.docBaseUrl,
nativeImageDecoderSupport: source.nativeImageDecoderSupport,
ignoreErrors: source.ignoreErrors,
isEvalSupported: source.isEvalSupported,
fontExtraProperties: source.fontExtraProperties,
@ -2309,26 +2290,6 @@ class WorkerTransport {
}
switch (type) {
case "JpegStream":
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function () {
resolve(img);
};
img.onerror = function () {
// Note that when the browser image loading/decoding fails,
// we'll fallback to the built-in PDF.js JPEG decoder; see
// `PartialEvaluator.buildPaintImageXObject` in the
// `src/core/evaluator.js` file.
reject(new Error("Error during JPEG image loading"));
// Always remember to release the image data if errors occurred.
releaseImageResources(img);
};
img.src = imageData;
}).then(img => {
pageProxy.objs.resolve(id, img);
});
case "Image":
pageProxy.objs.resolve(id, imageData);
@ -2366,69 +2327,6 @@ class WorkerTransport {
this._onUnsupportedFeature.bind(this)
);
messageHandler.on("JpegDecode", ([imageUrl, components]) => {
if (this.destroyed) {
return Promise.reject(new Error("Worker was destroyed"));
}
if (typeof document === "undefined") {
// Make sure that this code is not executing in node.js, as
// it's using DOM image, and there is no library to support that.
return Promise.reject(new Error('"document" is not defined.'));
}
if (components !== 3 && components !== 1) {
return Promise.reject(
new Error("Only 3 components or 1 component can be returned")
);
}
return new Promise(function (resolve, reject) {
const img = new Image();
img.onload = function () {
const { width, height } = img;
const size = width * height;
const rgbaLength = size * 4;
const buf = new Uint8ClampedArray(size * components);
let tmpCanvas = document.createElement("canvas");
tmpCanvas.width = width;
tmpCanvas.height = height;
let tmpCtx = tmpCanvas.getContext("2d");
tmpCtx.drawImage(img, 0, 0);
const data = tmpCtx.getImageData(0, 0, width, height).data;
if (components === 3) {
for (let i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
buf[j] = data[i];
buf[j + 1] = data[i + 1];
buf[j + 2] = data[i + 2];
}
} else if (components === 1) {
for (let i = 0, j = 0; i < rgbaLength; i += 4, j++) {
buf[j] = data[i];
}
}
resolve({ data: buf, width, height });
// Immediately release the image data once decoding has finished.
releaseImageResources(img);
// Zeroing the width and height cause Firefox to release graphics
// resources immediately, which can greatly reduce memory consumption.
tmpCanvas.width = 0;
tmpCanvas.height = 0;
tmpCanvas = null;
tmpCtx = null;
};
img.onerror = function () {
reject(new Error("JpegDecode failed to load image"));
// Always remember to release the image data if errors occurred.
releaseImageResources(img);
};
img.src = imageUrl;
});
});
messageHandler.on("FetchBuiltInCMap", (data, sink) => {
if (this.destroyed) {
sink.error(new Error("Worker was destroyed"));
@ -2610,7 +2508,6 @@ class WorkerTransport {
disableAutoFetch: params.disableAutoFetch,
disableCreateObjectURL: params.disableCreateObjectURL,
disableFontFace: params.disableFontFace,
nativeImageDecoderSupport: params.nativeImageDecoderSupport,
});
}
}

View File

@ -33,11 +33,10 @@ if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
})();
// Support: Node.js
(function checkFontFaceAndImage() {
// Node.js is missing native support for `@font-face` and `Image`.
(function checkFontFace() {
// Node.js is missing native support for `@font-face`.
if (isNodeJS) {
compatibilityParams.disableFontFace = true;
compatibilityParams.nativeImageDecoderSupport = "none";
}
})();
}

View File

@ -2113,46 +2113,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.restore();
},
paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
const domImage = objId.startsWith("g_")
? this.commonObjs.get(objId)
: this.objs.get(objId);
if (!domImage) {
warn("Dependent image isn't ready yet");
return;
}
this.save();
var ctx = this.ctx;
// scale the image to the unit square
ctx.scale(1 / w, -1 / h);
ctx.drawImage(
domImage,
0,
0,
domImage.width,
domImage.height,
0,
-h,
w,
h
);
if (this.imageLayer) {
var currentTransform = ctx.mozCurrentTransformInverse;
var position = this.getCanvasPosition(0, 0);
this.imageLayer.appendImage({
objId,
left: position[0],
top: position[1],
width: w / currentTransform[0],
height: h / currentTransform[3],
});
}
this.restore();
},
paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
var ctx = this.ctx;
var width = img.width,
@ -2353,9 +2313,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var paintWidth = width,
paintHeight = height;
var tmpCanvasId = "prescale1";
// Vertial or horizontal scaling shall not be more than 2 to not loose the
// Vertical or horizontal scaling shall not be more than 2 to not lose the
// pixels during drawImage operation, painting on the temporary canvas(es)
// that are twice smaller in size
// that are twice smaller in size.
while (
(widthScale > 2 && paintWidth > 1) ||
(heightScale > 2 && paintHeight > 1)

View File

@ -664,9 +664,6 @@ if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
case OPS.paintSolidColorImageMask:
this.paintSolidColorImageMask();
break;
case OPS.paintJpegXObject:
this.paintJpegXObject(args[0], args[1], args[2]);
break;
case OPS.paintImageXObject:
this.paintImageXObject(args[0]);
break;
@ -1559,23 +1556,6 @@ if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
this._ensureTransformGroup().appendChild(rect);
}
paintJpegXObject(objId, w, h) {
const imgObj = this.objs.get(objId);
const imgEl = this.svgFactory.createElement("svg:image");
imgEl.setAttributeNS(XLINK_NS, "xlink:href", imgObj.src);
imgEl.setAttributeNS(null, "width", pf(w));
imgEl.setAttributeNS(null, "height", pf(h));
imgEl.setAttributeNS(null, "x", "0");
imgEl.setAttributeNS(null, "y", pf(-h));
imgEl.setAttributeNS(
null,
"transform",
`scale(${pf(1 / w)} ${pf(-1 / h)})`
);
this._ensureTransformGroup().appendChild(imgEl);
}
paintImageXObject(objId) {
const imgData = this.objs.get(objId);
if (!imgData) {

View File

@ -39,7 +39,6 @@ import {
createValidAbsoluteUrl,
InvalidPDFException,
MissingPDFException,
NativeImageDecoding,
OPS,
PasswordResponses,
PermissionFlag,
@ -143,7 +142,6 @@ export {
createValidAbsoluteUrl,
InvalidPDFException,
MissingPDFException,
NativeImageDecoding,
OPS,
PasswordResponses,
PermissionFlag,

View File

@ -19,12 +19,6 @@ import "./compatibility.js";
const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
const NativeImageDecoding = {
NONE: "none",
DECODE: "decode",
DISPLAY: "display",
};
// Permission flags from Table 22, Section 7.6.3.2 of the PDF specification.
const PermissionFlag = {
PRINT: 0x04,
@ -917,7 +911,6 @@ export {
AbortException,
InvalidPDFException,
MissingPDFException,
NativeImageDecoding,
PasswordException,
PasswordResponses,
PermissionFlag,

View File

@ -387,7 +387,6 @@ var Driver = (function DriverClosure() {
const loadingTask = pdfjsLib.getDocument({
url: absoluteUrl,
password: task.password,
nativeImageDecoderSupport: task.nativeImageDecoderSupport,
cMapUrl: CMAP_URL,
cMapPacked: CMAP_PACKED,
disableRange: task.disableRange,

View File

@ -1631,8 +1631,7 @@
"link": true,
"firstPage": 2,
"lastPage": 2,
"type": "eq",
"nativeImageDecoderSupport": "none"
"type": "eq"
},
{ "id": "issue10529",
"file": "pdfs/issue10529.pdf",
@ -4175,22 +4174,12 @@
"link": true,
"type": "eq"
},
{ "id": "issue4926-built-in-jpg",
"file": "pdfs/issue4926.pdf",
"md5": "ed881c8ea2f9bc4be94ecb7f2b2c149b",
"rounds": 1,
"link": true,
"lastPage": 1,
"type": "eq",
"nativeImageDecoderSupport": "none"
},
{ "id": "decodeACSuccessive",
"file": "pdfs/decodeACSuccessive.pdf",
"md5": "7749c032624fe27ab8e8d7d5e9a4a93f",
"rounds": 1,
"link": false,
"type": "eq",
"nativeImageDecoderSupport": "none"
"type": "eq"
},
{ "id": "issue5592",
"file": "pdfs/issue5592.pdf",

View File

@ -18,7 +18,6 @@ import { setStubs, unsetStubs } from "../../examples/node/domstubs.js";
import { buildGetDocumentParams } from "./test_utils.js";
import { getDocument } from "../../src/display/api.js";
import { isNodeJS } from "../../src/shared/is_node.js";
import { NativeImageDecoding } from "../../src/shared/util.js";
import { SVGGraphics } from "../../src/display/svg.js";
const XLINK_NS = "http://www.w3.org/1999/xlink";
@ -62,11 +61,7 @@ describe("SVGGraphics", function () {
var loadingTask;
var page;
beforeAll(function (done) {
loadingTask = getDocument(
buildGetDocumentParams("xobject-image.pdf", {
nativeImageDecoderSupport: NativeImageDecoding.DISPLAY,
})
);
loadingTask = getDocument(buildGetDocumentParams("xobject-image.pdf"));
loadingTask.promise.then(function (doc) {
doc.getPage(1).then(function (firstPage) {
page = firstPage;