Merge pull request #19773 from Snuffleupagus/inline-PDFImage-createRawMask
Inline `PDFImage.createRawMask` in the `PDFImage.createMask` method
This commit is contained in:
commit
12c7c7b0af
@ -72,7 +72,6 @@ import { BaseStream } from "./base_stream.js";
|
|||||||
import { bidi } from "./bidi.js";
|
import { bidi } from "./bidi.js";
|
||||||
import { ColorSpace } from "./colorspace.js";
|
import { ColorSpace } from "./colorspace.js";
|
||||||
import { ColorSpaceUtils } from "./colorspace_utils.js";
|
import { ColorSpaceUtils } from "./colorspace_utils.js";
|
||||||
import { DecodeStream } from "./decode_stream.js";
|
|
||||||
import { getFontSubstitution } from "./font_substitutions.js";
|
import { getFontSubstitution } from "./font_substitutions.js";
|
||||||
import { getGlyphsUnicode } from "./glyphlist.js";
|
import { getGlyphsUnicode } from "./glyphlist.js";
|
||||||
import { getMetrics } from "./metrics.js";
|
import { getMetrics } from "./metrics.js";
|
||||||
@ -571,7 +570,10 @@ class PartialEvaluator {
|
|||||||
localImageCache,
|
localImageCache,
|
||||||
localColorSpaceCache,
|
localColorSpaceCache,
|
||||||
}) {
|
}) {
|
||||||
const dict = image.dict;
|
const { maxImageSize, ignoreErrors, isOffscreenCanvasSupported } =
|
||||||
|
this.options;
|
||||||
|
|
||||||
|
const { dict } = image;
|
||||||
const imageRef = dict.objId;
|
const imageRef = dict.objId;
|
||||||
const w = dict.get("W", "Width");
|
const w = dict.get("W", "Width");
|
||||||
const h = dict.get("H", "Height");
|
const h = dict.get("H", "Height");
|
||||||
@ -580,15 +582,14 @@ class PartialEvaluator {
|
|||||||
warn("Image dimensions are missing, or not numbers.");
|
warn("Image dimensions are missing, or not numbers.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const maxImageSize = this.options.maxImageSize;
|
|
||||||
if (maxImageSize !== -1 && w * h > maxImageSize) {
|
if (maxImageSize !== -1 && w * h > maxImageSize) {
|
||||||
const msg = "Image exceeded maximum allowed size and was removed.";
|
const msg = "Image exceeded maximum allowed size and was removed.";
|
||||||
|
|
||||||
if (this.options.ignoreErrors) {
|
if (!ignoreErrors) {
|
||||||
warn(msg);
|
throw new Error(msg);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
throw new Error(msg);
|
warn(msg);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let optionalContent;
|
let optionalContent;
|
||||||
@ -607,52 +608,10 @@ class PartialEvaluator {
|
|||||||
// data can't be done here. Instead of creating a
|
// data can't be done here. Instead of creating a
|
||||||
// complete PDFImage, only read the information needed
|
// complete PDFImage, only read the information needed
|
||||||
// for later.
|
// for later.
|
||||||
const interpolate = dict.get("I", "Interpolate");
|
|
||||||
const bitStrideLength = (w + 7) >> 3;
|
|
||||||
const imgArray = image.getBytes(bitStrideLength * h);
|
|
||||||
const decode = dict.getArray("D", "Decode");
|
|
||||||
|
|
||||||
if (this.parsingType3Font) {
|
|
||||||
// NOTE: Compared to other image resources we don't bother caching
|
|
||||||
// Type3-glyph image masks, since we've not come across any cases
|
|
||||||
// where that actually helps.
|
|
||||||
// In Type3-glyphs image masks are "always" inline resources,
|
|
||||||
// they're usually fairly small and aren't being re-used either.
|
|
||||||
|
|
||||||
imgData = PDFImage.createRawMask({
|
|
||||||
imgArray,
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
imageIsFromDecodeStream: image instanceof DecodeStream,
|
|
||||||
inverseDecode: decode?.[0] > 0,
|
|
||||||
interpolate,
|
|
||||||
});
|
|
||||||
args = compileType3Glyph(imgData);
|
|
||||||
|
|
||||||
if (args) {
|
|
||||||
operatorList.addImageOps(OPS.constructPath, args, optionalContent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
warn("Cannot compile Type3 glyph.");
|
|
||||||
|
|
||||||
// If compilation failed, or was disabled, fallback to using an inline
|
|
||||||
// image mask; this case should be extremely rare.
|
|
||||||
operatorList.addImageOps(
|
|
||||||
OPS.paintImageMaskXObject,
|
|
||||||
[imgData],
|
|
||||||
optionalContent
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
imgData = await PDFImage.createMask({
|
imgData = await PDFImage.createMask({
|
||||||
imgArray,
|
image,
|
||||||
width: w,
|
isOffscreenCanvasSupported:
|
||||||
height: h,
|
isOffscreenCanvasSupported && !this.parsingType3Font,
|
||||||
imageIsFromDecodeStream: image instanceof DecodeStream,
|
|
||||||
inverseDecode: decode?.[0] > 0,
|
|
||||||
interpolate,
|
|
||||||
isOffscreenCanvasSupported: this.options.isOffscreenCanvasSupported,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (imgData.isSingleOpaquePixel) {
|
if (imgData.isSingleOpaquePixel) {
|
||||||
@ -677,6 +636,36 @@ class PartialEvaluator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.parsingType3Font) {
|
||||||
|
// NOTE: Compared to other image resources we don't bother caching
|
||||||
|
// Type3-glyph image masks, since we've not come across any cases
|
||||||
|
// where that actually helps.
|
||||||
|
// In Type3-glyphs image masks are "always" inline resources,
|
||||||
|
// they're usually fairly small and aren't being re-used either.
|
||||||
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||||||
|
assert(
|
||||||
|
imgData.data instanceof Uint8Array,
|
||||||
|
"Type3 glyph image mask must be a TypedArray."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
args = compileType3Glyph(imgData);
|
||||||
|
|
||||||
|
if (args) {
|
||||||
|
operatorList.addImageOps(OPS.constructPath, args, optionalContent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
warn("Cannot compile Type3 glyph.");
|
||||||
|
|
||||||
|
// If compilation failed, or was disabled, fallback to using an inline
|
||||||
|
// image mask; this case should be extremely rare.
|
||||||
|
operatorList.addImageOps(
|
||||||
|
OPS.paintImageMaskXObject,
|
||||||
|
[imgData],
|
||||||
|
optionalContent
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const objId = `mask_${this.idFactory.createObjId()}`;
|
const objId = `mask_${this.idFactory.createObjId()}`;
|
||||||
operatorList.addDependency(objId);
|
operatorList.addDependency(objId);
|
||||||
|
|
||||||
@ -736,7 +725,7 @@ class PartialEvaluator {
|
|||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
const msg = `Unable to decode inline image: "${reason}".`;
|
const msg = `Unable to decode inline image: "${reason}".`;
|
||||||
|
|
||||||
if (!this.options.ignoreErrors) {
|
if (!ignoreErrors) {
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
warn(msg);
|
warn(msg);
|
||||||
@ -819,8 +808,7 @@ class PartialEvaluator {
|
|||||||
.then(async imageObj => {
|
.then(async imageObj => {
|
||||||
imgData = await imageObj.createImageData(
|
imgData = await imageObj.createImageData(
|
||||||
/* forceRGBA = */ false,
|
/* forceRGBA = */ false,
|
||||||
/* isOffscreenCanvasSupported = */ this.options
|
isOffscreenCanvasSupported
|
||||||
.isOffscreenCanvasSupported
|
|
||||||
);
|
);
|
||||||
imgData.dataLen = imgData.bitmap
|
imgData.dataLen = imgData.bitmap
|
||||||
? imgData.width * imgData.height * 4
|
? imgData.width * imgData.height * 4
|
||||||
|
|||||||
@ -348,58 +348,18 @@ class PDFImage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static createRawMask({
|
static async createMask({ image, isOffscreenCanvasSupported = false }) {
|
||||||
imgArray,
|
const { dict } = image;
|
||||||
width,
|
const width = dict.get("W", "Width");
|
||||||
height,
|
const height = dict.get("H", "Height");
|
||||||
imageIsFromDecodeStream,
|
|
||||||
inverseDecode,
|
const interpolate = dict.get("I", "Interpolate");
|
||||||
interpolate,
|
const decode = dict.getArray("D", "Decode");
|
||||||
}) {
|
const inverseDecode = decode?.[0] > 0;
|
||||||
// |imgArray| might not contain full data for every pixel of the mask, so
|
|
||||||
// we need to distinguish between |computedLength| and |actualLength|.
|
|
||||||
// In particular, if inverseDecode is true, then the array we return must
|
|
||||||
// have a length of |computedLength|.
|
|
||||||
|
|
||||||
const computedLength = ((width + 7) >> 3) * height;
|
const computedLength = ((width + 7) >> 3) * height;
|
||||||
const actualLength = imgArray.byteLength;
|
const imgArray = image.getBytes(computedLength);
|
||||||
const haveFullData = computedLength === actualLength;
|
|
||||||
let data, i;
|
|
||||||
|
|
||||||
if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) {
|
|
||||||
// imgArray came from a DecodeStream and its data is in an appropriate
|
|
||||||
// form, so we can just transfer it.
|
|
||||||
data = imgArray;
|
|
||||||
} else if (!inverseDecode) {
|
|
||||||
data = new Uint8Array(imgArray);
|
|
||||||
} else {
|
|
||||||
data = new Uint8Array(computedLength);
|
|
||||||
data.set(imgArray);
|
|
||||||
data.fill(0xff, actualLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If necessary, invert the original mask data (but not any extra we might
|
|
||||||
// have added above). It's safe to modify the array -- whether it's the
|
|
||||||
// original or a copy, we're about to transfer it anyway, so nothing else
|
|
||||||
// in this thread can be relying on its contents.
|
|
||||||
if (inverseDecode) {
|
|
||||||
for (i = 0; i < actualLength; i++) {
|
|
||||||
data[i] ^= 0xff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { data, width, height, interpolate };
|
|
||||||
}
|
|
||||||
|
|
||||||
static async createMask({
|
|
||||||
imgArray,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
imageIsFromDecodeStream,
|
|
||||||
inverseDecode,
|
|
||||||
interpolate,
|
|
||||||
isOffscreenCanvasSupported = false,
|
|
||||||
}) {
|
|
||||||
const isSingleOpaquePixel =
|
const isSingleOpaquePixel =
|
||||||
width === 1 &&
|
width === 1 &&
|
||||||
height === 1 &&
|
height === 1 &&
|
||||||
@ -452,17 +412,40 @@ class PDFImage {
|
|||||||
bitmap,
|
bitmap,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// Fallback to get the data almost as they're and they'll be decoded
|
||||||
// Get the data almost as they're and they'll be decoded
|
|
||||||
// just before being drawn.
|
// just before being drawn.
|
||||||
return this.createRawMask({
|
|
||||||
imgArray,
|
// |imgArray| might not contain full data for every pixel of the mask, so
|
||||||
width,
|
// we need to distinguish between |computedLength| and |actualLength|.
|
||||||
height,
|
// In particular, if inverseDecode is true, then the array we return must
|
||||||
inverseDecode,
|
// have a length of |computedLength|.
|
||||||
imageIsFromDecodeStream,
|
const actualLength = imgArray.byteLength;
|
||||||
interpolate,
|
const haveFullData = computedLength === actualLength;
|
||||||
});
|
let data;
|
||||||
|
|
||||||
|
if (image instanceof DecodeStream && (!inverseDecode || haveFullData)) {
|
||||||
|
// imgArray came from a DecodeStream and its data is in an appropriate
|
||||||
|
// form, so we can just transfer it.
|
||||||
|
data = imgArray;
|
||||||
|
} else if (!inverseDecode) {
|
||||||
|
data = new Uint8Array(imgArray);
|
||||||
|
} else {
|
||||||
|
data = new Uint8Array(computedLength);
|
||||||
|
data.set(imgArray);
|
||||||
|
data.fill(0xff, actualLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If necessary, invert the original mask data (but not any extra we might
|
||||||
|
// have added above). It's safe to modify the array -- whether it's the
|
||||||
|
// original or a copy, we're about to transfer it anyway, so nothing else
|
||||||
|
// in this thread can be relying on its contents.
|
||||||
|
if (inverseDecode) {
|
||||||
|
for (let i = 0; i < actualLength; i++) {
|
||||||
|
data[i] ^= 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data, width, height, interpolate };
|
||||||
}
|
}
|
||||||
|
|
||||||
get drawWidth() {
|
get drawWidth() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user