Skip to content

Commit dc3e24a

Browse files
committed
Inline PDFImage.createRawMask in the PDFImage.createMask method
After the introduction of `OffscreenCanvas` support we now have *two separate* mask-methods in the `PDFImage` class, and the reason that they were not combined is likely that we need the "raw" bytes when parsing Type3-glyph image masks. However, that case is easy to support simply by disabling `OffscreenCanvas` usage when parsing Type3-glyphs and that way we're able to reduce some code duplication. Another slightly strange property of the `PDFImage.createMask` method is that it needs various image-dictionary parameters *manually* provided, which is probably because this is very old code. That feels slightly unwieldy, and we instead change the method to pass in the image-stream directly and do the necessary data-lookup internally. A side-effect of this re-factoring is that we now support using the custom `isSingleOpaquePixel` operator in Type3-glyphs, which shouldn't hurt even though it seems extremely unlikely for that to ever happen in Type3-glyphs.
1 parent 85e6f3c commit dc3e24a

File tree

2 files changed

+85
-114
lines changed

2 files changed

+85
-114
lines changed

src/core/evaluator.js

Lines changed: 43 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ import { BaseStream } from "./base_stream.js";
7272
import { bidi } from "./bidi.js";
7373
import { ColorSpace } from "./colorspace.js";
7474
import { ColorSpaceUtils } from "./colorspace_utils.js";
75-
import { DecodeStream } from "./decode_stream.js";
7675
import { getFontSubstitution } from "./font_substitutions.js";
7776
import { getGlyphsUnicode } from "./glyphlist.js";
7877
import { getMetrics } from "./metrics.js";
@@ -571,7 +570,10 @@ class PartialEvaluator {
571570
localImageCache,
572571
localColorSpaceCache,
573572
}) {
574-
const dict = image.dict;
573+
const { maxImageSize, ignoreErrors, isOffscreenCanvasSupported } =
574+
this.options;
575+
576+
const { dict } = image;
575577
const imageRef = dict.objId;
576578
const w = dict.get("W", "Width");
577579
const h = dict.get("H", "Height");
@@ -580,15 +582,14 @@ class PartialEvaluator {
580582
warn("Image dimensions are missing, or not numbers.");
581583
return;
582584
}
583-
const maxImageSize = this.options.maxImageSize;
584585
if (maxImageSize !== -1 && w * h > maxImageSize) {
585586
const msg = "Image exceeded maximum allowed size and was removed.";
586587

587-
if (this.options.ignoreErrors) {
588-
warn(msg);
589-
return;
588+
if (!ignoreErrors) {
589+
throw new Error(msg);
590590
}
591-
throw new Error(msg);
591+
warn(msg);
592+
return;
592593
}
593594

594595
let optionalContent;
@@ -607,52 +608,10 @@ class PartialEvaluator {
607608
// data can't be done here. Instead of creating a
608609
// complete PDFImage, only read the information needed
609610
// for later.
610-
const interpolate = dict.get("I", "Interpolate");
611-
const bitStrideLength = (w + 7) >> 3;
612-
const imgArray = image.getBytes(bitStrideLength * h);
613-
const decode = dict.getArray("D", "Decode");
614-
615-
if (this.parsingType3Font) {
616-
// NOTE: Compared to other image resources we don't bother caching
617-
// Type3-glyph image masks, since we've not come across any cases
618-
// where that actually helps.
619-
// In Type3-glyphs image masks are "always" inline resources,
620-
// they're usually fairly small and aren't being re-used either.
621-
622-
imgData = PDFImage.createRawMask({
623-
imgArray,
624-
width: w,
625-
height: h,
626-
imageIsFromDecodeStream: image instanceof DecodeStream,
627-
inverseDecode: decode?.[0] > 0,
628-
interpolate,
629-
});
630-
args = compileType3Glyph(imgData);
631-
632-
if (args) {
633-
operatorList.addImageOps(OPS.constructPath, args, optionalContent);
634-
return;
635-
}
636-
warn("Cannot compile Type3 glyph.");
637-
638-
// If compilation failed, or was disabled, fallback to using an inline
639-
// image mask; this case should be extremely rare.
640-
operatorList.addImageOps(
641-
OPS.paintImageMaskXObject,
642-
[imgData],
643-
optionalContent
644-
);
645-
return;
646-
}
647-
648611
imgData = await PDFImage.createMask({
649-
imgArray,
650-
width: w,
651-
height: h,
652-
imageIsFromDecodeStream: image instanceof DecodeStream,
653-
inverseDecode: decode?.[0] > 0,
654-
interpolate,
655-
isOffscreenCanvasSupported: this.options.isOffscreenCanvasSupported,
612+
image,
613+
isOffscreenCanvasSupported:
614+
isOffscreenCanvasSupported && !this.parsingType3Font,
656615
});
657616

658617
if (imgData.isSingleOpaquePixel) {
@@ -677,6 +636,36 @@ class PartialEvaluator {
677636
return;
678637
}
679638

639+
if (this.parsingType3Font) {
640+
// NOTE: Compared to other image resources we don't bother caching
641+
// Type3-glyph image masks, since we've not come across any cases
642+
// where that actually helps.
643+
// In Type3-glyphs image masks are "always" inline resources,
644+
// they're usually fairly small and aren't being re-used either.
645+
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
646+
assert(
647+
imgData.data instanceof Uint8Array,
648+
"Type3 glyph image mask must be a TypedArray."
649+
);
650+
}
651+
args = compileType3Glyph(imgData);
652+
653+
if (args) {
654+
operatorList.addImageOps(OPS.constructPath, args, optionalContent);
655+
return;
656+
}
657+
warn("Cannot compile Type3 glyph.");
658+
659+
// If compilation failed, or was disabled, fallback to using an inline
660+
// image mask; this case should be extremely rare.
661+
operatorList.addImageOps(
662+
OPS.paintImageMaskXObject,
663+
[imgData],
664+
optionalContent
665+
);
666+
return;
667+
}
668+
680669
const objId = `mask_${this.idFactory.createObjId()}`;
681670
operatorList.addDependency(objId);
682671

@@ -736,7 +725,7 @@ class PartialEvaluator {
736725
} catch (reason) {
737726
const msg = `Unable to decode inline image: "${reason}".`;
738727

739-
if (!this.options.ignoreErrors) {
728+
if (!ignoreErrors) {
740729
throw new Error(msg);
741730
}
742731
warn(msg);
@@ -819,8 +808,7 @@ class PartialEvaluator {
819808
.then(async imageObj => {
820809
imgData = await imageObj.createImageData(
821810
/* forceRGBA = */ false,
822-
/* isOffscreenCanvasSupported = */ this.options
823-
.isOffscreenCanvasSupported
811+
isOffscreenCanvasSupported
824812
);
825813
imgData.dataLen = imgData.bitmap
826814
? imgData.width * imgData.height * 4

src/core/image.js

Lines changed: 42 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -348,58 +348,18 @@ class PDFImage {
348348
});
349349
}
350350

351-
static createRawMask({
352-
imgArray,
353-
width,
354-
height,
355-
imageIsFromDecodeStream,
356-
inverseDecode,
357-
interpolate,
358-
}) {
359-
// |imgArray| might not contain full data for every pixel of the mask, so
360-
// we need to distinguish between |computedLength| and |actualLength|.
361-
// In particular, if inverseDecode is true, then the array we return must
362-
// have a length of |computedLength|.
351+
static async createMask({ image, isOffscreenCanvasSupported = false }) {
352+
const { dict } = image;
353+
const width = dict.get("W", "Width");
354+
const height = dict.get("H", "Height");
363355

364-
const computedLength = ((width + 7) >> 3) * height;
365-
const actualLength = imgArray.byteLength;
366-
const haveFullData = computedLength === actualLength;
367-
let data, i;
356+
const interpolate = dict.get("I", "Interpolate");
357+
const decode = dict.getArray("D", "Decode");
358+
const inverseDecode = decode?.[0] > 0;
368359

369-
if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) {
370-
// imgArray came from a DecodeStream and its data is in an appropriate
371-
// form, so we can just transfer it.
372-
data = imgArray;
373-
} else if (!inverseDecode) {
374-
data = new Uint8Array(imgArray);
375-
} else {
376-
data = new Uint8Array(computedLength);
377-
data.set(imgArray);
378-
data.fill(0xff, actualLength);
379-
}
380-
381-
// If necessary, invert the original mask data (but not any extra we might
382-
// have added above). It's safe to modify the array -- whether it's the
383-
// original or a copy, we're about to transfer it anyway, so nothing else
384-
// in this thread can be relying on its contents.
385-
if (inverseDecode) {
386-
for (i = 0; i < actualLength; i++) {
387-
data[i] ^= 0xff;
388-
}
389-
}
390-
391-
return { data, width, height, interpolate };
392-
}
360+
const computedLength = ((width + 7) >> 3) * height;
361+
const imgArray = image.getBytes(computedLength);
393362

394-
static async createMask({
395-
imgArray,
396-
width,
397-
height,
398-
imageIsFromDecodeStream,
399-
inverseDecode,
400-
interpolate,
401-
isOffscreenCanvasSupported = false,
402-
}) {
403363
const isSingleOpaquePixel =
404364
width === 1 &&
405365
height === 1 &&
@@ -452,17 +412,40 @@ class PDFImage {
452412
bitmap,
453413
};
454414
}
455-
456-
// Get the data almost as they're and they'll be decoded
415+
// Fallback to get the data almost as they're and they'll be decoded
457416
// just before being drawn.
458-
return this.createRawMask({
459-
imgArray,
460-
width,
461-
height,
462-
inverseDecode,
463-
imageIsFromDecodeStream,
464-
interpolate,
465-
});
417+
418+
// |imgArray| might not contain full data for every pixel of the mask, so
419+
// we need to distinguish between |computedLength| and |actualLength|.
420+
// In particular, if inverseDecode is true, then the array we return must
421+
// have a length of |computedLength|.
422+
const actualLength = imgArray.byteLength;
423+
const haveFullData = computedLength === actualLength;
424+
let data;
425+
426+
if (image instanceof DecodeStream && (!inverseDecode || haveFullData)) {
427+
// imgArray came from a DecodeStream and its data is in an appropriate
428+
// form, so we can just transfer it.
429+
data = imgArray;
430+
} else if (!inverseDecode) {
431+
data = new Uint8Array(imgArray);
432+
} else {
433+
data = new Uint8Array(computedLength);
434+
data.set(imgArray);
435+
data.fill(0xff, actualLength);
436+
}
437+
438+
// If necessary, invert the original mask data (but not any extra we might
439+
// have added above). It's safe to modify the array -- whether it's the
440+
// original or a copy, we're about to transfer it anyway, so nothing else
441+
// in this thread can be relying on its contents.
442+
if (inverseDecode) {
443+
for (let i = 0; i < actualLength; i++) {
444+
data[i] ^= 0xff;
445+
}
446+
}
447+
448+
return { data, width, height, interpolate };
466449
}
467450

468451
get drawWidth() {

0 commit comments

Comments
 (0)