Skip to content

Commit 20d5332

Browse files
committed
For images that include SMask/Mask entries, ignore an SMask defined in the current graphics state
From section [11.6.4.3 Mask Shape and Opacity](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf#G10.4848628) in the PDF specification: - An image XObject may contain its own *soft-mask image* in the form of a subsidiary image XObject in the `SMask` entry of the image dictionary (see "Image Dictionaries"). This mask, if present, shall override any explicit or colour key mask specified by the image dictionary's `Mask` entry. Either form of mask in the image dictionary shall override the current soft mask in the graphics state.
1 parent 8a50d2d commit 20d5332

File tree

5 files changed

+32
-11
lines changed

5 files changed

+32
-11
lines changed

src/core/evaluator.js

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ function addLocallyCachedImageOps(opList, data) {
175175
if (data.objId) {
176176
opList.addDependency(data.objId);
177177
}
178-
opList.addImageOps(data.fn, data.args, data.optionalContent);
178+
opList.addImageOps(data.fn, data.args, data.optionalContent, data.hasMask);
179179

180180
if (data.fn === OPS.paintImageMaskXObject && data.args[0]?.count > 0) {
181181
data.args[0].count++;
@@ -730,13 +730,9 @@ class PartialEvaluator {
730730
}
731731

732732
const SMALL_IMAGE_DIMENSIONS = 200;
733+
const hasMask = dict.has("SMask") || dict.has("Mask");
733734
// Inlining small images into the queue as RGB data
734-
if (
735-
isInline &&
736-
w + h < SMALL_IMAGE_DIMENSIONS &&
737-
!dict.has("SMask") &&
738-
!dict.has("Mask")
739-
) {
735+
if (isInline && w + h < SMALL_IMAGE_DIMENSIONS && !hasMask) {
740736
try {
741737
const imageObj = new PDFImage({
742738
xref: this.xref,
@@ -793,7 +789,12 @@ class PartialEvaluator {
793789
// Ensure that the dependency is added before the image is decoded.
794790
operatorList.addDependency(objId);
795791
args = [objId, w, h];
796-
operatorList.addImageOps(OPS.paintImageXObject, args, optionalContent);
792+
operatorList.addImageOps(
793+
OPS.paintImageXObject,
794+
args,
795+
optionalContent,
796+
hasMask
797+
);
797798

798799
if (cacheGlobally) {
799800
if (this.globalImageCache.hasDecodeFailed(imageRef)) {
@@ -802,6 +803,7 @@ class PartialEvaluator {
802803
fn: OPS.paintImageXObject,
803804
args,
804805
optionalContent,
806+
hasMask,
805807
byteSize: 0, // Data is `null`, since decoding failed previously.
806808
});
807809

@@ -812,7 +814,7 @@ class PartialEvaluator {
812814
// For large (at least 500x500) or more complex images that we'll cache
813815
// globally, check if the image is still cached locally on the main-thread
814816
// to avoid having to re-parse the image (since that can be slow).
815-
if (w * h > 250000 || dict.has("SMask") || dict.has("Mask")) {
817+
if (w * h > 250000 || hasMask) {
816818
const localLength = await this.handler.sendWithPromise("commonobj", [
817819
objId,
818820
"CopyLocalImage",
@@ -825,6 +827,7 @@ class PartialEvaluator {
825827
fn: OPS.paintImageXObject,
826828
args,
827829
optionalContent,
830+
hasMask,
828831
byteSize: 0, // Temporary entry, to avoid `setData` returning early.
829832
});
830833
this.globalImageCache.addByteSize(imageRef, localLength);
@@ -872,6 +875,7 @@ class PartialEvaluator {
872875
fn: OPS.paintImageXObject,
873876
args,
874877
optionalContent,
878+
hasMask,
875879
};
876880
localImageCache.set(cacheKey, imageRef, cacheData);
877881

@@ -884,6 +888,7 @@ class PartialEvaluator {
884888
fn: OPS.paintImageXObject,
885889
args,
886890
optionalContent,
891+
hasMask,
887892
byteSize: 0, // Temporary entry, note `addByteSize` above.
888893
});
889894
}
@@ -1814,7 +1819,8 @@ class PartialEvaluator {
18141819
operatorList.addImageOps(
18151820
globalImage.fn,
18161821
globalImage.args,
1817-
globalImage.optionalContent
1822+
globalImage.optionalContent,
1823+
globalImage.hasMask
18181824
);
18191825

18201826
resolveXObject();

src/core/operator_list.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,11 @@ class OperatorList {
636636
}
637637
}
638638

639-
addImageOps(fn, args, optionalContent) {
639+
addImageOps(fn, args, optionalContent, hasMask = false) {
640+
if (hasMask) {
641+
this.addOp(OPS.save);
642+
this.addOp(OPS.setGState, [[["SMask", false]]]);
643+
}
640644
if (optionalContent !== undefined) {
641645
this.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
642646
}
@@ -646,6 +650,9 @@ class OperatorList {
646650
if (optionalContent !== undefined) {
647651
this.addOp(OPS.endMarkedContent, []);
648652
}
653+
if (hasMask) {
654+
this.addOp(OPS.restore);
655+
}
649656
}
650657

651658
addDependency(dependency) {

test/pdfs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@
232232
!issue5954.pdf
233233
!issue6612.pdf
234234
!alphatrans.pdf
235+
!issue14200.pdf
235236
!pattern_text_embedded_font.pdf
236237
!devicen.pdf
237238
!cmykjpeg.pdf

test/pdfs/issue14200.pdf

663 KB
Binary file not shown.

test/test_manifest.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5255,6 +5255,13 @@
52555255
"link": true,
52565256
"type": "eq"
52575257
},
5258+
{
5259+
"id": "issue14200",
5260+
"file": "pdfs/issue14200.pdf",
5261+
"md5": "4dba2cde1c6e65abe53e66eefc97a7f1",
5262+
"rounds": 1,
5263+
"type": "eq"
5264+
},
52585265
{
52595266
"id": "jbig2_huffman_1",
52605267
"file": "pdfs/jbig2_huffman_1.pdf",

0 commit comments

Comments
 (0)