Merge pull request #13380 from Snuffleupagus/pattern_helper-class

Re-factor and convert the code in `src/display/pattern_helper.js` to use standard classes
This commit is contained in:
Tim van der Meij 2021-05-16 13:11:04 +02:00 committed by GitHub
commit 8a8a67de3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 469 additions and 484 deletions

View File

@ -27,7 +27,7 @@ import {
Util,
warn,
} from "../shared/util.js";
import { getShadingPatternFromIR, TilingPattern } from "./pattern_helper.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.
@ -1973,7 +1973,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
baseTransform
);
} else {
pattern = getShadingPatternFromIR(IR);
pattern = getShadingPattern(IR);
}
return pattern;
}
@ -2007,7 +2007,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
const ctx = this.ctx;
this.save();
const pattern = getShadingPatternFromIR(patternIR);
const pattern = getShadingPattern(patternIR);
ctx.fillStyle = pattern.getPattern(ctx, this, true);
const inv = ctx.mozCurrentTransformInverse;

View File

@ -13,9 +13,13 @@
* limitations under the License.
*/
import { FormatError, info, Util } from "../shared/util.js";
const ShadingIRs = {};
import {
FormatError,
info,
shadow,
unreachable,
Util,
} from "../shared/util.js";
let svgElement;
@ -41,19 +45,32 @@ function applyBoundingBox(ctx, bbox) {
ctx.clip(region);
}
ShadingIRs.RadialAxial = {
fromIR: function RadialAxial_fromIR(raw) {
const type = raw[1];
const bbox = raw[2];
const colorStops = raw[3];
const p0 = raw[4];
const p1 = raw[5];
const r0 = raw[6];
const r1 = raw[7];
const matrix = raw[8];
class BaseShadingPattern {
constructor() {
if (this.constructor === BaseShadingPattern) {
unreachable("Cannot initialize BaseShadingPattern.");
}
}
return {
getPattern: function RadialAxial_getPattern(ctx, owner, shadingFill) {
getPattern() {
unreachable("Abstract method `getPattern` called.");
}
}
class RadialAxialShadingPattern extends BaseShadingPattern {
constructor(IR) {
super();
this._type = IR[1];
this._bbox = IR[2];
this._colorStops = IR[3];
this._p0 = IR[4];
this._p1 = IR[5];
this._r0 = IR[6];
this._r1 = IR[7];
this._matrix = IR[8];
}
getPattern(ctx, owner, shadingFill) {
const tmpCanvas = owner.cachedCanvases.getCanvas(
"pattern",
ctx.canvas.width,
@ -68,45 +85,45 @@ ShadingIRs.RadialAxial = {
if (!shadingFill) {
tmpCtx.setTransform.apply(tmpCtx, owner.baseTransform);
if (matrix) {
tmpCtx.transform.apply(tmpCtx, matrix);
if (this._matrix) {
tmpCtx.transform.apply(tmpCtx, this._matrix);
}
} else {
tmpCtx.setTransform.apply(tmpCtx, ctx.mozCurrentTransform);
}
applyBoundingBox(tmpCtx, bbox);
applyBoundingBox(tmpCtx, this._bbox);
let grad;
if (type === "axial") {
grad = tmpCtx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
} else if (type === "radial") {
if (this._type === "axial") {
grad = tmpCtx.createLinearGradient(
this._p0[0],
this._p0[1],
this._p1[0],
this._p1[1]
);
} else if (this._type === "radial") {
grad = tmpCtx.createRadialGradient(
p0[0],
p0[1],
r0,
p1[0],
p1[1],
r1
this._p0[0],
this._p0[1],
this._r0,
this._p1[0],
this._p1[1],
this._r1
);
}
for (let i = 0, ii = colorStops.length; i < ii; ++i) {
const c = colorStops[i];
grad.addColorStop(c[0], c[1]);
for (const colorStop of this._colorStops) {
grad.addColorStop(colorStop[0], colorStop[1]);
}
tmpCtx.fillStyle = grad;
tmpCtx.fill();
const pattern = ctx.createPattern(tmpCanvas.canvas, "repeat");
pattern.setTransform(createMatrix(ctx.mozCurrentTransformInverse));
return pattern;
},
};
},
};
}
}
const createMeshCanvas = (function createMeshCanvasClosure() {
function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) {
// Very basic Gouraud-shaded triangle rasterization algorithm.
const coords = context.coords,
@ -274,16 +291,19 @@ const createMeshCanvas = (function createMeshCanvasClosure() {
}
}
// eslint-disable-next-line no-shadow
function createMeshCanvas(
bounds,
combinesScale,
coords,
colors,
figures,
backgroundColor,
cachedCanvases
) {
class MeshShadingPattern extends BaseShadingPattern {
constructor(IR) {
super();
this._coords = IR[2];
this._colors = IR[3];
this._figures = IR[4];
this._bounds = IR[5];
this._matrix = IR[6];
this._bbox = IR[7];
this._background = IR[8];
}
_createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) {
// we will increase scale on some weird factor to let antialiasing take
// care of "rough" edges
const EXPECTED_SCALE = 1.1;
@ -293,25 +313,25 @@ const createMeshCanvas = (function createMeshCanvasClosure() {
// createPattern with 'no-repeat' will bleed edges across entire area.
const BORDER_SIZE = 2;
const offsetX = Math.floor(bounds[0]);
const offsetY = Math.floor(bounds[1]);
const boundsWidth = Math.ceil(bounds[2]) - offsetX;
const boundsHeight = Math.ceil(bounds[3]) - offsetY;
const offsetX = Math.floor(this._bounds[0]);
const offsetY = Math.floor(this._bounds[1]);
const boundsWidth = Math.ceil(this._bounds[2]) - offsetX;
const boundsHeight = Math.ceil(this._bounds[3]) - offsetY;
const width = Math.min(
Math.ceil(Math.abs(boundsWidth * combinesScale[0] * EXPECTED_SCALE)),
Math.ceil(Math.abs(boundsWidth * combinedScale[0] * EXPECTED_SCALE)),
MAX_PATTERN_SIZE
);
const height = Math.min(
Math.ceil(Math.abs(boundsHeight * combinesScale[1] * EXPECTED_SCALE)),
Math.ceil(Math.abs(boundsHeight * combinedScale[1] * EXPECTED_SCALE)),
MAX_PATTERN_SIZE
);
const scaleX = boundsWidth / width;
const scaleY = boundsHeight / height;
const context = {
coords,
colors,
coords: this._coords,
colors: this._colors,
offsetX: -offsetX,
offsetY: -offsetY,
scaleX: 1 / scaleX,
@ -339,8 +359,8 @@ const createMeshCanvas = (function createMeshCanvasClosure() {
bytes[i + 3] = 255;
}
}
for (let i = 0, ii = figures.length; i < ii; i++) {
drawFigure(data, figures[i], context);
for (const figure of this._figures) {
drawFigure(data, figure, context);
}
tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE);
const canvas = tmpCanvas.canvas;
@ -353,50 +373,33 @@ const createMeshCanvas = (function createMeshCanvasClosure() {
scaleY,
};
}
return createMeshCanvas;
})();
ShadingIRs.Mesh = {
fromIR: function Mesh_fromIR(raw) {
// var type = raw[1];
const coords = raw[2];
const colors = raw[3];
const figures = raw[4];
const bounds = raw[5];
const matrix = raw[6];
const bbox = raw[7];
const background = raw[8];
return {
getPattern: function Mesh_getPattern(ctx, owner, shadingFill) {
applyBoundingBox(ctx, bbox);
getPattern(ctx, owner, shadingFill) {
applyBoundingBox(ctx, this._bbox);
let scale;
if (shadingFill) {
scale = Util.singularValueDecompose2dScale(ctx.mozCurrentTransform);
} else {
// Obtain scale from matrix and current transformation matrix.
scale = Util.singularValueDecompose2dScale(owner.baseTransform);
if (matrix) {
const matrixScale = Util.singularValueDecompose2dScale(matrix);
if (this._matrix) {
const matrixScale = Util.singularValueDecompose2dScale(this._matrix);
scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]];
}
}
// Rasterizing on the main thread since sending/queue large canvases
// might cause OOM.
const temporaryPatternCanvas = createMeshCanvas(
bounds,
const temporaryPatternCanvas = this._createMeshCanvas(
scale,
coords,
colors,
figures,
shadingFill ? null : background,
shadingFill ? null : this._background,
owner.cachedCanvases
);
if (!shadingFill) {
ctx.setTransform.apply(ctx, owner.baseTransform);
if (matrix) {
ctx.transform.apply(ctx, matrix);
if (this._matrix) {
ctx.transform.apply(ctx, this._matrix);
}
}
@ -407,42 +410,39 @@ ShadingIRs.Mesh = {
ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY);
return ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat");
},
};
},
};
}
}
ShadingIRs.Dummy = {
fromIR: function Dummy_fromIR() {
return {
getPattern: function Dummy_fromIR_getPattern() {
class DummyShadingPattern extends BaseShadingPattern {
getPattern() {
return "hotpink";
},
};
},
};
function getShadingPatternFromIR(raw) {
const shadingIR = ShadingIRs[raw[0]];
if (!shadingIR) {
throw new Error(`Unknown IR type: ${raw[0]}`);
}
return shadingIR.fromIR(raw);
}
/**
* @type {any}
*/
const TilingPattern = (function TilingPatternClosure() {
function getShadingPattern(IR) {
switch (IR[0]) {
case "RadialAxial":
return new RadialAxialShadingPattern(IR);
case "Mesh":
return new MeshShadingPattern(IR);
case "Dummy":
return new DummyShadingPattern();
}
throw new Error(`Unknown IR type: ${IR[0]}`);
}
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
function TilingPattern(IR, color, ctx, canvasGraphicsFactory, baseTransform) {
constructor(IR, color, ctx, canvasGraphicsFactory, baseTransform) {
this.operatorList = IR[2];
this.matrix = IR[3] || [1, 0, 0, 1, 0, 0];
this.bbox = IR[4];
@ -451,13 +451,12 @@ const TilingPattern = (function TilingPatternClosure() {
this.paintType = IR[7];
this.tilingType = IR[8];
this.color = color;
this.ctx = ctx;
this.canvasGraphicsFactory = canvasGraphicsFactory;
this.baseTransform = baseTransform;
this.ctx = ctx;
}
TilingPattern.prototype = {
createPatternCanvas: function TilinPattern_createPatternCanvas(owner) {
createPatternCanvas(owner) {
const operatorList = this.operatorList;
const bbox = this.bbox;
const xstep = this.xstep;
@ -545,20 +544,16 @@ const TilingPattern = (function TilingPatternClosure() {
scaleX: dimx.scale,
scaleY: dimy.scale,
};
},
}
getSizeAndScale: function TilingPattern_getSizeAndScale(
step,
realOutputSize,
scale
) {
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
// whole canvas.
const maxSize = Math.max(MAX_PATTERN_SIZE, realOutputSize);
const maxSize = Math.max(TilingPattern.MAX_PATTERN_SIZE, realOutputSize);
let size = Math.ceil(step * scale);
if (size >= maxSize) {
size = maxSize;
@ -566,9 +561,9 @@ const TilingPattern = (function TilingPatternClosure() {
scale = size / step;
}
return { scale, size };
},
}
clipBbox: function clipBbox(graphics, bbox, x0, y0, x1, y1) {
clipBbox(graphics, bbox, x0, y0, x1, y1) {
if (Array.isArray(bbox) && bbox.length === 4) {
const bboxWidth = x1 - x0;
const bboxHeight = y1 - y0;
@ -576,13 +571,9 @@ const TilingPattern = (function TilingPatternClosure() {
graphics.clip();
graphics.endPath();
}
},
}
setFillAndStrokeStyleToContext: function setFillAndStrokeStyleToContext(
graphics,
paintType,
color
) {
setFillAndStrokeStyleToContext(graphics, paintType, color) {
const context = graphics.ctx,
current = graphics.current;
switch (paintType) {
@ -604,9 +595,9 @@ const TilingPattern = (function TilingPatternClosure() {
default:
throw new FormatError(`Unsupported paint type: ${paintType}`);
}
},
}
getPattern: function TilingPattern_getPattern(ctx, owner, shadingFill) {
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;
@ -627,17 +618,11 @@ const TilingPattern = (function TilingPatternClosure() {
1 / temporaryPatternCanvas.scaleY
);
const pattern = ctx.createPattern(
temporaryPatternCanvas.canvas,
"repeat"
);
const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "repeat");
pattern.setTransform(domMatrix);
return pattern;
},
};
}
}
return TilingPattern;
})();
export { getShadingPatternFromIR, TilingPattern };
export { getShadingPattern, TilingPattern };