1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-19 22:58:07 +02:00

Fix the rendering of tiling pattern when the steps are lower than the tile dimensions (bug 1837738)

It fixes #16038.

The idea is to create a pattern having the steps for dimensions and then draw
the base tile and the different overlapping parts on it.
This commit is contained in:
Calixte Denizet 2024-09-26 12:46:10 +02:00
parent 4ab381f52e
commit 6d88f9f154
5 changed files with 127 additions and 39 deletions

View file

@ -471,14 +471,17 @@ class TilingPattern {
}
createPatternCanvas(owner) {
const operatorList = this.operatorList;
const bbox = this.bbox;
const xstep = this.xstep;
const ystep = this.ystep;
const paintType = this.paintType;
const tilingType = this.tilingType;
const color = this.color;
const canvasGraphicsFactory = this.canvasGraphicsFactory;
const {
bbox,
operatorList,
paintType,
tilingType,
color,
canvasGraphicsFactory,
} = this;
let { xstep, ystep } = this;
xstep = Math.abs(xstep);
ystep = Math.abs(ystep);
info("TilingType: " + tilingType);
@ -499,36 +502,55 @@ class TilingPattern {
// bbox boundary will be missing. This is INCORRECT behavior.
// "Figures on adjacent tiles should not overlap" (PDF spec 8.7.3.1),
// but overlapping cells without common pixels are still valid.
// TODO: Fix the implementation, to allow this scenario to be painted
// correctly.
const x0 = bbox[0],
y0 = bbox[1],
x1 = bbox[2],
y1 = bbox[3];
const width = x1 - x0;
const height = y1 - y0;
// Obtain scale from matrix and current transformation matrix.
const matrixScale = Util.singularValueDecompose2dScale(this.matrix);
const curMatrixScale = Util.singularValueDecompose2dScale(
this.baseTransform
);
const combinedScale = [
matrixScale[0] * curMatrixScale[0],
matrixScale[1] * curMatrixScale[1],
];
const combinedScaleX = matrixScale[0] * curMatrixScale[0];
const combinedScaleY = matrixScale[1] * curMatrixScale[1];
let canvasWidth = width,
canvasHeight = height,
redrawHorizontally = false,
redrawVertically = false;
const xScaledStep = Math.ceil(xstep * combinedScaleX);
const yScaledStep = Math.ceil(ystep * combinedScaleY);
const xScaledWidth = Math.ceil(width * combinedScaleX);
const yScaledHeight = Math.ceil(height * combinedScaleY);
if (xScaledStep >= xScaledWidth) {
canvasWidth = xstep;
} else {
redrawHorizontally = true;
}
if (yScaledStep >= yScaledHeight) {
canvasHeight = ystep;
} else {
redrawVertically = true;
}
// Use width and height values that are as close as possible to the end
// result when the pattern is used. Too low value makes the pattern look
// blurry. Too large value makes it look too crispy.
const dimx = this.getSizeAndScale(
xstep,
canvasWidth,
this.ctx.canvas.width,
combinedScale[0]
combinedScaleX
);
const dimy = this.getSizeAndScale(
ystep,
canvasHeight,
this.ctx.canvas.height,
combinedScale[1]
combinedScaleY
);
const tmpCanvas = owner.cachedCanvases.getCanvas(
@ -543,29 +565,14 @@ class TilingPattern {
this.setFillAndStrokeStyleToContext(graphics, paintType, color);
let adjustedX0 = x0;
let adjustedY0 = y0;
let adjustedX1 = x1;
let adjustedY1 = y1;
// Some bounding boxes have negative x0/y0 coordinates which will cause the
// some of the drawing to be off of the canvas. To avoid this shift the
// bounding box over.
if (x0 < 0) {
adjustedX0 = 0;
adjustedX1 += Math.abs(x0);
}
if (y0 < 0) {
adjustedY0 = 0;
adjustedY1 += Math.abs(y0);
}
tmpCtx.translate(-(dimx.scale * adjustedX0), -(dimy.scale * adjustedY0));
tmpCtx.translate(-dimx.scale * x0, -dimy.scale * y0);
graphics.transform(dimx.scale, 0, 0, dimy.scale, 0, 0);
// To match CanvasGraphics beginDrawing we must save the context here or
// else we end up with unbalanced save/restores.
tmpCtx.save();
this.clipBbox(graphics, adjustedX0, adjustedY0, adjustedX1, adjustedY1);
this.clipBbox(graphics, x0, y0, x1, y1);
graphics.baseTransform = getCurrentTransform(graphics.ctx);
@ -573,18 +580,82 @@ class TilingPattern {
graphics.endDrawing();
tmpCtx.restore();
if (redrawHorizontally || redrawVertically) {
// The tile is overlapping itself, so we create a new tile with
// dimensions xstep * ystep.
// Then we draw the overlapping parts of the original tile on the new
// tile.
// Just as a side note, the code here works correctly even if we don't
// have to redraw the tile horizontally or vertically. In that case, the
// original tile is drawn on the new tile only once, but it's useless.
const image = tmpCanvas.canvas;
if (redrawHorizontally) {
canvasWidth = xstep;
}
if (redrawVertically) {
canvasHeight = ystep;
}
const dimx2 = this.getSizeAndScale(
canvasWidth,
this.ctx.canvas.width,
combinedScaleX
);
const dimy2 = this.getSizeAndScale(
canvasHeight,
this.ctx.canvas.height,
combinedScaleY
);
const xSize = dimx2.size;
const ySize = dimy2.size;
const tmpCanvas2 = owner.cachedCanvases.getCanvas(
"pattern-workaround",
xSize,
ySize,
true
);
const tmpCtx2 = tmpCanvas2.context;
const ii = redrawHorizontally ? Math.floor(width / xstep) : 0;
const jj = redrawVertically ? Math.floor(height / ystep) : 0;
// Draw the overlapping parts of the original tile on the new tile.
for (let i = 0; i <= ii; i++) {
for (let j = 0; j <= jj; j++) {
tmpCtx2.drawImage(
image,
xSize * i,
ySize * j,
xSize,
ySize,
0,
0,
xSize,
ySize
);
}
}
return {
canvas: tmpCanvas2.canvas,
scaleX: dimx2.scale,
scaleY: dimy2.scale,
offsetX: x0,
offsetY: y0,
};
}
return {
canvas: tmpCanvas.canvas,
scaleX: dimx.scale,
scaleY: dimy.scale,
offsetX: adjustedX0,
offsetY: adjustedY0,
offsetX: x0,
offsetY: y0,
};
}
getSizeAndScale(step, realOutputSize, scale) {
// xstep / ystep may be negative -- normalize.
step = Math.abs(step);
// MAX_PATTERN_SIZE is used to avoid OOM situation.
// Use the destination canvas's size if it is bigger than the hard-coded
// limit of MAX_PATTERN_SIZE to avoid clipping patterns that cover the