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.
This commit is contained in:
Jonas Jenwald 2024-12-30 14:13:27 +01:00
parent 8a50d2d302
commit 20d5332009
5 changed files with 32 additions and 11 deletions

View File

@ -175,7 +175,7 @@ function addLocallyCachedImageOps(opList, data) {
if (data.objId) { if (data.objId) {
opList.addDependency(data.objId); opList.addDependency(data.objId);
} }
opList.addImageOps(data.fn, data.args, data.optionalContent); opList.addImageOps(data.fn, data.args, data.optionalContent, data.hasMask);
if (data.fn === OPS.paintImageMaskXObject && data.args[0]?.count > 0) { if (data.fn === OPS.paintImageMaskXObject && data.args[0]?.count > 0) {
data.args[0].count++; data.args[0].count++;
@ -730,13 +730,9 @@ class PartialEvaluator {
} }
const SMALL_IMAGE_DIMENSIONS = 200; const SMALL_IMAGE_DIMENSIONS = 200;
const hasMask = dict.has("SMask") || dict.has("Mask");
// Inlining small images into the queue as RGB data // Inlining small images into the queue as RGB data
if ( if (isInline && w + h < SMALL_IMAGE_DIMENSIONS && !hasMask) {
isInline &&
w + h < SMALL_IMAGE_DIMENSIONS &&
!dict.has("SMask") &&
!dict.has("Mask")
) {
try { try {
const imageObj = new PDFImage({ const imageObj = new PDFImage({
xref: this.xref, xref: this.xref,
@ -793,7 +789,12 @@ class PartialEvaluator {
// Ensure that the dependency is added before the image is decoded. // Ensure that the dependency is added before the image is decoded.
operatorList.addDependency(objId); operatorList.addDependency(objId);
args = [objId, w, h]; args = [objId, w, h];
operatorList.addImageOps(OPS.paintImageXObject, args, optionalContent); operatorList.addImageOps(
OPS.paintImageXObject,
args,
optionalContent,
hasMask
);
if (cacheGlobally) { if (cacheGlobally) {
if (this.globalImageCache.hasDecodeFailed(imageRef)) { if (this.globalImageCache.hasDecodeFailed(imageRef)) {
@ -802,6 +803,7 @@ class PartialEvaluator {
fn: OPS.paintImageXObject, fn: OPS.paintImageXObject,
args, args,
optionalContent, optionalContent,
hasMask,
byteSize: 0, // Data is `null`, since decoding failed previously. byteSize: 0, // Data is `null`, since decoding failed previously.
}); });
@ -812,7 +814,7 @@ class PartialEvaluator {
// For large (at least 500x500) or more complex images that we'll cache // For large (at least 500x500) or more complex images that we'll cache
// globally, check if the image is still cached locally on the main-thread // globally, check if the image is still cached locally on the main-thread
// to avoid having to re-parse the image (since that can be slow). // to avoid having to re-parse the image (since that can be slow).
if (w * h > 250000 || dict.has("SMask") || dict.has("Mask")) { if (w * h > 250000 || hasMask) {
const localLength = await this.handler.sendWithPromise("commonobj", [ const localLength = await this.handler.sendWithPromise("commonobj", [
objId, objId,
"CopyLocalImage", "CopyLocalImage",
@ -825,6 +827,7 @@ class PartialEvaluator {
fn: OPS.paintImageXObject, fn: OPS.paintImageXObject,
args, args,
optionalContent, optionalContent,
hasMask,
byteSize: 0, // Temporary entry, to avoid `setData` returning early. byteSize: 0, // Temporary entry, to avoid `setData` returning early.
}); });
this.globalImageCache.addByteSize(imageRef, localLength); this.globalImageCache.addByteSize(imageRef, localLength);
@ -872,6 +875,7 @@ class PartialEvaluator {
fn: OPS.paintImageXObject, fn: OPS.paintImageXObject,
args, args,
optionalContent, optionalContent,
hasMask,
}; };
localImageCache.set(cacheKey, imageRef, cacheData); localImageCache.set(cacheKey, imageRef, cacheData);
@ -884,6 +888,7 @@ class PartialEvaluator {
fn: OPS.paintImageXObject, fn: OPS.paintImageXObject,
args, args,
optionalContent, optionalContent,
hasMask,
byteSize: 0, // Temporary entry, note `addByteSize` above. byteSize: 0, // Temporary entry, note `addByteSize` above.
}); });
} }
@ -1814,7 +1819,8 @@ class PartialEvaluator {
operatorList.addImageOps( operatorList.addImageOps(
globalImage.fn, globalImage.fn,
globalImage.args, globalImage.args,
globalImage.optionalContent globalImage.optionalContent,
globalImage.hasMask
); );
resolveXObject(); resolveXObject();

View File

@ -636,7 +636,11 @@ class OperatorList {
} }
} }
addImageOps(fn, args, optionalContent) { addImageOps(fn, args, optionalContent, hasMask = false) {
if (hasMask) {
this.addOp(OPS.save);
this.addOp(OPS.setGState, [[["SMask", false]]]);
}
if (optionalContent !== undefined) { if (optionalContent !== undefined) {
this.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); this.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
} }
@ -646,6 +650,9 @@ class OperatorList {
if (optionalContent !== undefined) { if (optionalContent !== undefined) {
this.addOp(OPS.endMarkedContent, []); this.addOp(OPS.endMarkedContent, []);
} }
if (hasMask) {
this.addOp(OPS.restore);
}
} }
addDependency(dependency) { addDependency(dependency) {

View File

@ -232,6 +232,7 @@
!issue5954.pdf !issue5954.pdf
!issue6612.pdf !issue6612.pdf
!alphatrans.pdf !alphatrans.pdf
!issue14200.pdf
!pattern_text_embedded_font.pdf !pattern_text_embedded_font.pdf
!devicen.pdf !devicen.pdf
!cmykjpeg.pdf !cmykjpeg.pdf

BIN
test/pdfs/issue14200.pdf Normal file

Binary file not shown.

View File

@ -5255,6 +5255,13 @@
"link": true, "link": true,
"type": "eq" "type": "eq"
}, },
{
"id": "issue14200",
"file": "pdfs/issue14200.pdf",
"md5": "4dba2cde1c6e65abe53e66eefc97a7f1",
"rounds": 1,
"type": "eq"
},
{ {
"id": "jbig2_huffman_1", "id": "jbig2_huffman_1",
"file": "pdfs/jbig2_huffman_1.pdf", "file": "pdfs/jbig2_huffman_1.pdf",