Fix how patterns are applied to image mask objects.

Note, this only really fixes Radial/Axial shading patterns with masks.
I'm guessing tiling patterns and mesh patterns would also be broken
if applied like the test pdf. Hopefully I'll have some time to make
test cases for the other shadings.

Fixes #13372
This commit is contained in:
Brendan Dahl 2021-06-14 21:09:45 -07:00
parent f9a0568f96
commit 5efaaa0fea
5 changed files with 64 additions and 12 deletions

View File

@ -13,6 +13,11 @@
* limitations under the License.
*/
import {
createMatrix,
getShadingPattern,
TilingPattern,
} from "./pattern_helper.js";
import {
FONT_IDENTITY_MATRIX,
IDENTITY_MATRIX,
@ -27,7 +32,6 @@ import {
Util,
warn,
} from "../shared/util.js";
import { getShadingPattern, TilingPattern } from "./pattern_helper.js";
// <canvas> contexts store most of the state we need natively.
// However, PDF needs a bit more state, which we store here.
@ -193,6 +197,17 @@ function addContextCurrentTransform(ctx) {
};
}
function getAdjustmentTransformation(transform, width, height) {
// The pattern will be created at the size of the current page or form object,
// but the mask is usually scaled differently and offset, so we must account
// for these to shift and rescale the pattern to the correctly location.
let patternTransform = createMatrix(transform);
patternTransform = patternTransform.scale(1 / width, -1 / height);
patternTransform = patternTransform.translate(0, -height);
patternTransform = patternTransform.inverse();
return patternTransform;
}
class CachedCanvases {
constructor(canvasFactory) {
this.canvasFactory = canvasFactory;
@ -2294,8 +2309,16 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
maskCtx.globalCompositeOperation = "source-in";
let patternTransform = null;
if (isPatternFill) {
patternTransform = getAdjustmentTransformation(
ctx.mozCurrentTransform,
width,
height
);
}
maskCtx.fillStyle = isPatternFill
? fillColor.getPattern(maskCtx, this)
? fillColor.getPattern(maskCtx, this, false, patternTransform)
: fillColor;
maskCtx.fillRect(0, 0, width, height);
@ -2332,14 +2355,23 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
maskCtx.globalCompositeOperation = "source-in";
const ctx = this.ctx;
let patternTransform = null;
if (isPatternFill) {
patternTransform = getAdjustmentTransformation(
ctx.mozCurrentTransform,
width,
height
);
}
maskCtx.fillStyle = isPatternFill
? fillColor.getPattern(maskCtx, this)
? fillColor.getPattern(maskCtx, this, false, patternTransform)
: fillColor;
maskCtx.fillRect(0, 0, width, height);
maskCtx.restore();
const ctx = this.ctx;
for (let i = 0, ii = positions.length; i < ii; i += 2) {
ctx.save();
ctx.transform(
@ -2381,8 +2413,17 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
maskCtx.globalCompositeOperation = "source-in";
let patternTransform = null;
if (isPatternFill) {
patternTransform = getAdjustmentTransformation(
ctx.mozCurrentTransform,
width,
height
);
}
maskCtx.fillStyle = isPatternFill
? fillColor.getPattern(maskCtx, this)
? fillColor.getPattern(maskCtx, this, false, patternTransform)
: fillColor;
maskCtx.fillRect(0, 0, width, height);

View File

@ -72,11 +72,11 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
this._matrix = IR[8];
}
getPattern(ctx, owner, shadingFill) {
getPattern(ctx, owner, shadingFill = false, patternTransform = null) {
const tmpCanvas = owner.cachedCanvases.getCanvas(
"pattern",
ctx.canvas.width,
ctx.canvas.height,
owner.ctx.canvas.width,
owner.ctx.canvas.height,
true
);
@ -121,7 +121,11 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
tmpCtx.fill();
const pattern = ctx.createPattern(tmpCanvas.canvas, "repeat");
if (patternTransform) {
pattern.setTransform(patternTransform);
} else {
pattern.setTransform(createMatrix(ctx.mozCurrentTransformInverse));
}
return pattern;
}
}
@ -376,7 +380,7 @@ class MeshShadingPattern extends BaseShadingPattern {
};
}
getPattern(ctx, owner, shadingFill) {
getPattern(ctx, owner, shadingFill = false, patternTransform = null) {
applyBoundingBox(ctx, this._bbox);
let scale;
if (shadingFill) {
@ -599,7 +603,7 @@ class TilingPattern {
}
}
getPattern(ctx, owner, shadingFill) {
getPattern(ctx, owner, shadingFill = false, patternTransform = null) {
ctx = this.ctx;
// PDF spec 8.7.2 NOTE 1: pattern's matrix is relative to initial matrix.
let matrix = ctx.mozCurrentTransformInverse;
@ -627,4 +631,4 @@ class TilingPattern {
}
}
export { getShadingPattern, TilingPattern };
export { createMatrix, getShadingPattern, TilingPattern };

View File

@ -406,6 +406,7 @@
!issue13193.pdf
!annotation-underline-without-appearance.pdf
!issue269_2.pdf
!issue13372.pdf
!annotation-strikeout.pdf
!annotation-strikeout-without-appearance.pdf
!annotation-squiggly.pdf

BIN
test/pdfs/issue13372.pdf Normal file

Binary file not shown.

View File

@ -1566,6 +1566,12 @@
"rounds": 1,
"type": "eq"
},
{ "id": "issue13372",
"file": "pdfs/issue13372.pdf",
"md5": "0bc5329623fd554174c5e7653f904e28",
"rounds": 1,
"type": "eq"
},
{ "id": "simpletype3font-text",
"file": "pdfs/simpletype3font.pdf",
"md5": "b374c7543920840c61999e9e86939f99",