mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-11 17:45:49 +02:00
Draw non-isolated blend-mode groups against their backdrop (bug 1873345)
A non-isolated transparency group must blend with its backdrop, but a group containing a blend mode was forced onto a transparent intermediate canvas; e.g. a /Multiply highlight then painted opaquely over the text behind it, making that text invisible.
This commit is contained in:
parent
7f7b38b424
commit
082ad21387
@ -5025,16 +5025,7 @@ class HighlightAnnotation extends MarkupAnnotation {
|
|||||||
|
|
||||||
const quadPoints = (this.data.quadPoints = getQuadPoints(dict, null));
|
const quadPoints = (this.data.quadPoints = getQuadPoints(dict, null));
|
||||||
if (quadPoints) {
|
if (quadPoints) {
|
||||||
const resources = this.appearance?.dict.get("Resources");
|
if (!this.appearance) {
|
||||||
|
|
||||||
if (!this.appearance || !resources?.has("ExtGState")) {
|
|
||||||
if (this.appearance) {
|
|
||||||
// Workaround for cases where there's no /ExtGState-entry directly
|
|
||||||
// available, e.g. when the appearance stream contains a /XObject of
|
|
||||||
// the /Form-type, since that causes the highlighting to completely
|
|
||||||
// obscure the PDF content below it (fixes issue13242.pdf).
|
|
||||||
warn("HighlightAnnotation - ignoring built-in appearance stream.");
|
|
||||||
}
|
|
||||||
// Default color is yellow in Acrobat Reader
|
// Default color is yellow in Acrobat Reader
|
||||||
const fillColor = getPdfColorArray(this.color, [1, 1, 0]);
|
const fillColor = getPdfColorArray(this.color, [1, 1, 0]);
|
||||||
const fillAlpha = dict.get("CA");
|
const fillAlpha = dict.get("CA");
|
||||||
|
|||||||
@ -523,6 +523,7 @@ class PartialEvaluator {
|
|||||||
isolated: false,
|
isolated: false,
|
||||||
knockout: false,
|
knockout: false,
|
||||||
needsIsolation: false,
|
needsIsolation: false,
|
||||||
|
hasSoftMask: false,
|
||||||
isGray: false,
|
isGray: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -573,6 +574,7 @@ class PartialEvaluator {
|
|||||||
|
|
||||||
if (group) {
|
if (group) {
|
||||||
groupOptions.needsIsolation = newOpList.needsIsolation || !!smask;
|
groupOptions.needsIsolation = newOpList.needsIsolation || !!smask;
|
||||||
|
groupOptions.hasSoftMask = newOpList.hasSoftMask || !!smask;
|
||||||
operatorList.addOp(OPS.beginGroup, [groupOptions]);
|
operatorList.addOp(OPS.beginGroup, [groupOptions]);
|
||||||
operatorList.addOp(OPS.paintFormXObjectBegin, args);
|
operatorList.addOp(OPS.paintFormXObjectBegin, args);
|
||||||
operatorList.addOpList(newOpList);
|
operatorList.addOpList(newOpList);
|
||||||
|
|||||||
@ -830,24 +830,29 @@ class OperatorList {
|
|||||||
* operations require being drawn in isolation (i.e. on a separate canvas).
|
* operations require being drawn in isolation (i.e. on a separate canvas).
|
||||||
* A group/pattern needs isolation when it uses non-default compositing
|
* A group/pattern needs isolation when it uses non-default compositing
|
||||||
* (blend mode) or a soft mask. The result is exposed via `needsIsolation`.
|
* (blend mode) or a soft mask. The result is exposed via `needsIsolation`.
|
||||||
|
*
|
||||||
|
* `hasSoftMask` separately flags the use of a soft mask: unlike a plain blend
|
||||||
|
* mode, which a non-isolated group can apply directly against its backdrop, a
|
||||||
|
* soft mask always requires a real intermediate canvas (see bug 1873345).
|
||||||
*/
|
*/
|
||||||
class CheckedOperatorList extends OperatorList {
|
class CheckedOperatorList extends OperatorList {
|
||||||
needsIsolation = false;
|
needsIsolation = false;
|
||||||
|
|
||||||
|
hasSoftMask = false;
|
||||||
|
|
||||||
addOp(fn, args) {
|
addOp(fn, args) {
|
||||||
if (!this.needsIsolation) {
|
if (!this.needsIsolation || !this.hasSoftMask) {
|
||||||
if (fn === OPS.beginGroup) {
|
if (fn === OPS.beginGroup) {
|
||||||
// Propagate isolation only if the nested group itself needs it.
|
// Propagate isolation only if the nested group itself needs it.
|
||||||
this.needsIsolation = args[0].needsIsolation;
|
this.needsIsolation ||= args[0].needsIsolation;
|
||||||
|
this.hasSoftMask ||= args[0].hasSoftMask;
|
||||||
} else if (fn === OPS.setGState) {
|
} else if (fn === OPS.setGState) {
|
||||||
for (const [key, val] of args[0]) {
|
for (const [key, val] of args[0]) {
|
||||||
if (key === "BM" && val !== "source-over") {
|
if (key === "BM" && val !== "source-over") {
|
||||||
this.needsIsolation = true;
|
this.needsIsolation = true;
|
||||||
break;
|
} else if (key === "SMask" && val !== false) {
|
||||||
}
|
|
||||||
if (key === "SMask" && val !== false) {
|
|
||||||
this.needsIsolation = true;
|
this.needsIsolation = true;
|
||||||
break;
|
this.hasSoftMask = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3210,12 +3210,13 @@ class CanvasGraphics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentCtx = this.ctx;
|
const currentCtx = this.ctx;
|
||||||
if (!group.isolated && !group.knockout && this.#knockoutGroupLevel === 0) {
|
|
||||||
info("TODO: Fully support non-isolated non-knockout groups.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!group.needsIsolation &&
|
// A non-isolated group blends with its backdrop, so drawing it directly
|
||||||
|
// on the parent canvas (rather than on a transparent intermediate one)
|
||||||
|
// is correct even when it contains blend modes (bug 1873345). A soft
|
||||||
|
// mask still needs its own canvas though, and an isolated group requires
|
||||||
|
// a transparent backdrop, so both keep the intermediate canvas.
|
||||||
|
(!group.needsIsolation || (!group.isolated && !group.hasSoftMask)) &&
|
||||||
!group.knockout &&
|
!group.knockout &&
|
||||||
!group.isGray &&
|
!group.isGray &&
|
||||||
this.#knockoutGroupLevel === 0 &&
|
this.#knockoutGroupLevel === 0 &&
|
||||||
@ -3234,12 +3235,24 @@ class CanvasGraphics {
|
|||||||
}
|
}
|
||||||
currentCtx.clip(clip);
|
currentCtx.clip(clip);
|
||||||
}
|
}
|
||||||
|
// Unlike the intermediate-canvas path below, the content is drawn
|
||||||
|
// straight onto the parent canvas with no later compositing step, so the
|
||||||
|
// inherited blend mode, alpha constants and transfer function must stay
|
||||||
|
// active here rather than being reset (issue 20722); the conditions
|
||||||
|
// above already guarantee a Normal blend and an opaque (ca === 1) state.
|
||||||
this.groupStack.push(null); // null = no intermediate canvas
|
this.groupStack.push(null); // null = no intermediate canvas
|
||||||
this.#groupStackMeta.push(null);
|
this.#groupStackMeta.push(null);
|
||||||
this.groupLevel++;
|
this.groupLevel++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reached only when the direct path above didn't apply, e.g. a soft mask,
|
||||||
|
// non-default group alpha or blend mode: we still composite on a
|
||||||
|
// transparent intermediate canvas rather than the real backdrop.
|
||||||
|
if (!group.isolated && !group.knockout && this.#knockoutGroupLevel === 0) {
|
||||||
|
info("TODO: Fully support non-isolated non-knockout groups.");
|
||||||
|
}
|
||||||
|
|
||||||
const currentTransform = getCurrentTransform(currentCtx);
|
const currentTransform = getCurrentTransform(currentCtx);
|
||||||
if (group.matrix) {
|
if (group.matrix) {
|
||||||
currentCtx.transform(...group.matrix);
|
currentCtx.transform(...group.matrix);
|
||||||
|
|||||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -936,3 +936,4 @@
|
|||||||
!bug1802506.pdf
|
!bug1802506.pdf
|
||||||
!checkbox_no_appearance.pdf
|
!checkbox_no_appearance.pdf
|
||||||
!opt_demo.pdf
|
!opt_demo.pdf
|
||||||
|
!bug1873345.pdf
|
||||||
|
|||||||
BIN
test/pdfs/bug1873345.pdf
Normal file
BIN
test/pdfs/bug1873345.pdf
Normal file
Binary file not shown.
@ -6786,6 +6786,13 @@
|
|||||||
"type": "eq",
|
"type": "eq",
|
||||||
"about": "Every blend mode that PDF supports."
|
"about": "Every blend mode that PDF supports."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "bug1873345",
|
||||||
|
"file": "pdfs/bug1873345.pdf",
|
||||||
|
"md5": "03483b94a2c02ac5f98c17ccf14de8ea",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "transparency_group",
|
"id": "transparency_group",
|
||||||
"file": "pdfs/transparency_group.pdf",
|
"file": "pdfs/transparency_group.pdf",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user