Convert src/display/pattern_helper.js to use standard classes

Note that this patch only covers `TilingPattern`, since the `ShadingIRs`-implementation required additional re-factoring.
This commit is contained in:
Jonas Jenwald 2021-05-15 13:02:40 +02:00
parent 438cf1e438
commit 40939d5955

View File

@ -13,7 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { FormatError, info, Util } from "../shared/util.js"; import { FormatError, info, shadow, Util } from "../shared/util.js";
const ShadingIRs = {}; const ShadingIRs = {};
@ -430,19 +430,18 @@ function getShadingPatternFromIR(raw) {
return shadingIR.fromIR(raw); return shadingIR.fromIR(raw);
} }
/** const PaintType = {
* @type {any} COLORED: 1,
*/ UNCOLORED: 2,
const TilingPattern = (function TilingPatternClosure() { };
const PaintType = {
COLORED: 1,
UNCOLORED: 2,
};
const MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough class TilingPattern {
// 10in @ 300dpi shall be enough.
static get MAX_PATTERN_SIZE() {
return shadow(this, "MAX_PATTERN_SIZE", 3000);
}
// eslint-disable-next-line no-shadow constructor(IR, color, ctx, canvasGraphicsFactory, baseTransform) {
function TilingPattern(IR, color, ctx, canvasGraphicsFactory, baseTransform) {
this.operatorList = IR[2]; this.operatorList = IR[2];
this.matrix = IR[3] || [1, 0, 0, 1, 0, 0]; this.matrix = IR[3] || [1, 0, 0, 1, 0, 0];
this.bbox = IR[4]; this.bbox = IR[4];
@ -451,193 +450,178 @@ const TilingPattern = (function TilingPatternClosure() {
this.paintType = IR[7]; this.paintType = IR[7];
this.tilingType = IR[8]; this.tilingType = IR[8];
this.color = color; this.color = color;
this.ctx = ctx;
this.canvasGraphicsFactory = canvasGraphicsFactory; this.canvasGraphicsFactory = canvasGraphicsFactory;
this.baseTransform = baseTransform; this.baseTransform = baseTransform;
this.ctx = ctx;
} }
TilingPattern.prototype = { createPatternCanvas(owner) {
createPatternCanvas: function TilinPattern_createPatternCanvas(owner) { const operatorList = this.operatorList;
const operatorList = this.operatorList; const bbox = this.bbox;
const bbox = this.bbox; const xstep = this.xstep;
const xstep = this.xstep; const ystep = this.ystep;
const ystep = this.ystep; const paintType = this.paintType;
const paintType = this.paintType; const tilingType = this.tilingType;
const tilingType = this.tilingType; const color = this.color;
const color = this.color; const canvasGraphicsFactory = this.canvasGraphicsFactory;
const canvasGraphicsFactory = this.canvasGraphicsFactory;
info("TilingType: " + tilingType); info("TilingType: " + tilingType);
// A tiling pattern as defined by PDF spec 8.7.2 is a cell whose size is // A tiling pattern as defined by PDF spec 8.7.2 is a cell whose size is
// described by bbox, and may repeat regularly by shifting the cell by // described by bbox, and may repeat regularly by shifting the cell by
// xstep and ystep. // xstep and ystep.
// Because the HTML5 canvas API does not support pattern repetition with // Because the HTML5 canvas API does not support pattern repetition with
// gaps in between, we use the xstep/ystep instead of the bbox's size. // gaps in between, we use the xstep/ystep instead of the bbox's size.
// //
// This has the following consequences (similarly for ystep): // This has the following consequences (similarly for ystep):
// //
// - If xstep is the same as bbox, then there is no observable difference. // - If xstep is the same as bbox, then there is no observable difference.
// //
// - If xstep is larger than bbox, then the pattern canvas is partially // - If xstep is larger than bbox, then the pattern canvas is partially
// empty: the area bounded by bbox is painted, the outside area is void. // empty: the area bounded by bbox is painted, the outside area is void.
// //
// - If xstep is smaller than bbox, then the pixels between xstep and the // - If xstep is smaller than bbox, then the pixels between xstep and the
// bbox boundary will be missing. This is INCORRECT behavior. // bbox boundary will be missing. This is INCORRECT behavior.
// "Figures on adjacent tiles should not overlap" (PDF spec 8.7.3.1), // "Figures on adjacent tiles should not overlap" (PDF spec 8.7.3.1),
// but overlapping cells without common pixels are still valid. // but overlapping cells without common pixels are still valid.
// TODO: Fix the implementation, to allow this scenario to be painted // TODO: Fix the implementation, to allow this scenario to be painted
// correctly. // correctly.
const x0 = bbox[0], const x0 = bbox[0],
y0 = bbox[1], y0 = bbox[1],
x1 = bbox[2], x1 = bbox[2],
y1 = bbox[3]; y1 = bbox[3];
// Obtain scale from matrix and current transformation matrix. // Obtain scale from matrix and current transformation matrix.
const matrixScale = Util.singularValueDecompose2dScale(this.matrix); const matrixScale = Util.singularValueDecompose2dScale(this.matrix);
const curMatrixScale = Util.singularValueDecompose2dScale( const curMatrixScale = Util.singularValueDecompose2dScale(
this.baseTransform this.baseTransform
); );
const combinedScale = [ const combinedScale = [
matrixScale[0] * curMatrixScale[0], matrixScale[0] * curMatrixScale[0],
matrixScale[1] * curMatrixScale[1], matrixScale[1] * curMatrixScale[1],
]; ];
// Use width and height values that are as close as possible to the end // 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 // result when the pattern is used. Too low value makes the pattern look
// blurry. Too large value makes it look too crispy. // blurry. Too large value makes it look too crispy.
const dimx = this.getSizeAndScale( const dimx = this.getSizeAndScale(
xstep, xstep,
this.ctx.canvas.width, this.ctx.canvas.width,
combinedScale[0] combinedScale[0]
); );
const dimy = this.getSizeAndScale( const dimy = this.getSizeAndScale(
ystep, ystep,
this.ctx.canvas.height, this.ctx.canvas.height,
combinedScale[1] combinedScale[1]
); );
const tmpCanvas = owner.cachedCanvases.getCanvas( const tmpCanvas = owner.cachedCanvases.getCanvas(
"pattern", "pattern",
dimx.size, dimx.size,
dimy.size, dimy.size,
true true
); );
const tmpCtx = tmpCanvas.context; const tmpCtx = tmpCanvas.context;
const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx); const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx);
graphics.groupLevel = owner.groupLevel; graphics.groupLevel = owner.groupLevel;
this.setFillAndStrokeStyleToContext(graphics, paintType, color); this.setFillAndStrokeStyleToContext(graphics, paintType, color);
graphics.transform(dimx.scale, 0, 0, dimy.scale, 0, 0); graphics.transform(dimx.scale, 0, 0, dimy.scale, 0, 0);
this.clipBbox(graphics, bbox, x0, y0, x1, y1); this.clipBbox(graphics, bbox, x0, y0, x1, y1);
graphics.baseTransform = graphics.ctx.mozCurrentTransform.slice(); graphics.baseTransform = graphics.ctx.mozCurrentTransform.slice();
graphics.executeOperatorList(operatorList); graphics.executeOperatorList(operatorList);
graphics.endDrawing(); graphics.endDrawing();
return { return {
canvas: tmpCanvas.canvas, canvas: tmpCanvas.canvas,
scaleX: dimx.scale, scaleX: dimx.scale,
scaleY: dimy.scale, scaleY: dimy.scale,
}; };
}, }
getSizeAndScale: function TilingPattern_getSizeAndScale( getSizeAndScale(step, realOutputSize, scale) {
step, // xstep / ystep may be negative -- normalize.
realOutputSize, step = Math.abs(step);
scale // MAX_PATTERN_SIZE is used to avoid OOM situation.
) { // Use the destination canvas's size if it is bigger than the hard-coded
// xstep / ystep may be negative -- normalize. // limit of MAX_PATTERN_SIZE to avoid clipping patterns that cover the
step = Math.abs(step); // whole canvas.
// MAX_PATTERN_SIZE is used to avoid OOM situation. const maxSize = Math.max(TilingPattern.MAX_PATTERN_SIZE, realOutputSize);
// Use the destination canvas's size if it is bigger than the hard-coded let size = Math.ceil(step * scale);
// limit of MAX_PATTERN_SIZE to avoid clipping patterns that cover the if (size >= maxSize) {
// whole canvas. size = maxSize;
const maxSize = Math.max(MAX_PATTERN_SIZE, realOutputSize); } else {
let size = Math.ceil(step * scale); scale = size / step;
if (size >= maxSize) { }
size = maxSize; return { scale, size };
} else { }
scale = size / step;
clipBbox(graphics, bbox, x0, y0, x1, y1) {
if (Array.isArray(bbox) && bbox.length === 4) {
const bboxWidth = x1 - x0;
const bboxHeight = y1 - y0;
graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight);
graphics.clip();
graphics.endPath();
}
}
setFillAndStrokeStyleToContext(graphics, paintType, color) {
const context = graphics.ctx,
current = graphics.current;
switch (paintType) {
case PaintType.COLORED:
const ctx = this.ctx;
context.fillStyle = ctx.fillStyle;
context.strokeStyle = ctx.strokeStyle;
current.fillColor = ctx.fillStyle;
current.strokeColor = ctx.strokeStyle;
break;
case PaintType.UNCOLORED:
const cssColor = Util.makeHexColor(color[0], color[1], color[2]);
context.fillStyle = cssColor;
context.strokeStyle = cssColor;
// Set color needed by image masks (fixes issues 3226 and 8741).
current.fillColor = cssColor;
current.strokeColor = cssColor;
break;
default:
throw new FormatError(`Unsupported paint type: ${paintType}`);
}
}
getPattern(ctx, owner, shadingFill) {
ctx = this.ctx;
// PDF spec 8.7.2 NOTE 1: pattern's matrix is relative to initial matrix.
let matrix = ctx.mozCurrentTransformInverse;
if (!shadingFill) {
matrix = Util.transform(matrix, owner.baseTransform);
if (this.matrix) {
matrix = Util.transform(matrix, this.matrix);
} }
return { scale, size }; }
},
clipBbox: function clipBbox(graphics, bbox, x0, y0, x1, y1) { const temporaryPatternCanvas = this.createPatternCanvas(owner);
if (Array.isArray(bbox) && bbox.length === 4) {
const bboxWidth = x1 - x0;
const bboxHeight = y1 - y0;
graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight);
graphics.clip();
graphics.endPath();
}
},
setFillAndStrokeStyleToContext: function setFillAndStrokeStyleToContext( let domMatrix = createMatrix(matrix);
graphics, // Rescale and so that the ctx.createPattern call generates a pattern with
paintType, // the desired size.
color domMatrix = domMatrix.scale(
) { 1 / temporaryPatternCanvas.scaleX,
const context = graphics.ctx, 1 / temporaryPatternCanvas.scaleY
current = graphics.current; );
switch (paintType) {
case PaintType.COLORED:
const ctx = this.ctx;
context.fillStyle = ctx.fillStyle;
context.strokeStyle = ctx.strokeStyle;
current.fillColor = ctx.fillStyle;
current.strokeColor = ctx.strokeStyle;
break;
case PaintType.UNCOLORED:
const cssColor = Util.makeHexColor(color[0], color[1], color[2]);
context.fillStyle = cssColor;
context.strokeStyle = cssColor;
// Set color needed by image masks (fixes issues 3226 and 8741).
current.fillColor = cssColor;
current.strokeColor = cssColor;
break;
default:
throw new FormatError(`Unsupported paint type: ${paintType}`);
}
},
getPattern: function TilingPattern_getPattern(ctx, owner, shadingFill) { const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "repeat");
ctx = this.ctx; pattern.setTransform(domMatrix);
// PDF spec 8.7.2 NOTE 1: pattern's matrix is relative to initial matrix.
let matrix = ctx.mozCurrentTransformInverse;
if (!shadingFill) {
matrix = Util.transform(matrix, owner.baseTransform);
if (this.matrix) {
matrix = Util.transform(matrix, this.matrix);
}
}
const temporaryPatternCanvas = this.createPatternCanvas(owner); return pattern;
}
let domMatrix = createMatrix(matrix); }
// Rescale and so that the ctx.createPattern call generates a pattern with
// the desired size.
domMatrix = domMatrix.scale(
1 / temporaryPatternCanvas.scaleX,
1 / temporaryPatternCanvas.scaleY
);
const pattern = ctx.createPattern(
temporaryPatternCanvas.canvas,
"repeat"
);
pattern.setTransform(domMatrix);
return pattern;
},
};
return TilingPattern;
})();
export { getShadingPatternFromIR, TilingPattern }; export { getShadingPatternFromIR, TilingPattern };