Modernize the ShadingIRs structure, in src/display/pattern_helper.js, to use standard classes

This patch replaces the old structure with an abstract base-class, which the new ShadingPattern classes then inherit from.
The old `createMeshCanvasClosure` can now be removed, since it's not necessary any more with modern JavaScript, and the `createMeshCanvas` function is now instead a method on the new `MeshShadingPattern` class (avoids unnecessary parameter passing).
This commit is contained in:
Jonas Jenwald 2021-05-15 13:25:10 +02:00
parent 40939d5955
commit a984431046
2 changed files with 309 additions and 308 deletions

View File

@ -27,7 +27,7 @@ import {
Util, Util,
warn, warn,
} from "../shared/util.js"; } 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. // <canvas> contexts store most of the state we need natively.
// However, PDF needs a bit more state, which we store here. // However, PDF needs a bit more state, which we store here.
@ -1973,7 +1973,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
baseTransform baseTransform
); );
} else { } else {
pattern = getShadingPatternFromIR(IR); pattern = getShadingPattern(IR);
} }
return pattern; return pattern;
} }
@ -2007,7 +2007,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
const ctx = this.ctx; const ctx = this.ctx;
this.save(); this.save();
const pattern = getShadingPatternFromIR(patternIR); const pattern = getShadingPattern(patternIR);
ctx.fillStyle = pattern.getPattern(ctx, this, true); ctx.fillStyle = pattern.getPattern(ctx, this, true);
const inv = ctx.mozCurrentTransformInverse; const inv = ctx.mozCurrentTransformInverse;

View File

@ -13,9 +13,13 @@
* limitations under the License. * limitations under the License.
*/ */
import { FormatError, info, shadow, Util } from "../shared/util.js"; import {
FormatError,
const ShadingIRs = {}; info,
shadow,
unreachable,
Util,
} from "../shared/util.js";
let svgElement; let svgElement;
@ -41,19 +45,32 @@ function applyBoundingBox(ctx, bbox) {
ctx.clip(region); ctx.clip(region);
} }
ShadingIRs.RadialAxial = { class BaseShadingPattern {
fromIR: function RadialAxial_fromIR(raw) { constructor() {
const type = raw[1]; if (this.constructor === BaseShadingPattern) {
const bbox = raw[2]; unreachable("Cannot initialize BaseShadingPattern.");
const colorStops = raw[3]; }
const p0 = raw[4]; }
const p1 = raw[5];
const r0 = raw[6];
const r1 = raw[7];
const matrix = raw[8];
return { getPattern() {
getPattern: function RadialAxial_getPattern(ctx, owner, shadingFill) { 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( const tmpCanvas = owner.cachedCanvases.getCanvas(
"pattern", "pattern",
ctx.canvas.width, ctx.canvas.width,
@ -68,45 +85,45 @@ ShadingIRs.RadialAxial = {
if (!shadingFill) { if (!shadingFill) {
tmpCtx.setTransform.apply(tmpCtx, owner.baseTransform); tmpCtx.setTransform.apply(tmpCtx, owner.baseTransform);
if (matrix) { if (this._matrix) {
tmpCtx.transform.apply(tmpCtx, matrix); tmpCtx.transform.apply(tmpCtx, this._matrix);
} }
} else { } else {
tmpCtx.setTransform.apply(tmpCtx, ctx.mozCurrentTransform); tmpCtx.setTransform.apply(tmpCtx, ctx.mozCurrentTransform);
} }
applyBoundingBox(tmpCtx, bbox); applyBoundingBox(tmpCtx, this._bbox);
let grad; let grad;
if (type === "axial") { if (this._type === "axial") {
grad = tmpCtx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); grad = tmpCtx.createLinearGradient(
} else if (type === "radial") { this._p0[0],
this._p0[1],
this._p1[0],
this._p1[1]
);
} else if (this._type === "radial") {
grad = tmpCtx.createRadialGradient( grad = tmpCtx.createRadialGradient(
p0[0], this._p0[0],
p0[1], this._p0[1],
r0, this._r0,
p1[0], this._p1[0],
p1[1], this._p1[1],
r1 this._r1
); );
} }
for (let i = 0, ii = colorStops.length; i < ii; ++i) { for (const colorStop of this._colorStops) {
const c = colorStops[i]; grad.addColorStop(colorStop[0], colorStop[1]);
grad.addColorStop(c[0], c[1]);
} }
tmpCtx.fillStyle = grad; tmpCtx.fillStyle = grad;
tmpCtx.fill(); tmpCtx.fill();
const pattern = ctx.createPattern(tmpCanvas.canvas, "repeat"); const pattern = ctx.createPattern(tmpCanvas.canvas, "repeat");
pattern.setTransform(createMatrix(ctx.mozCurrentTransformInverse)); pattern.setTransform(createMatrix(ctx.mozCurrentTransformInverse));
return pattern; return pattern;
}, }
}; }
},
};
const createMeshCanvas = (function createMeshCanvasClosure() {
function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) { function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) {
// Very basic Gouraud-shaded triangle rasterization algorithm. // Very basic Gouraud-shaded triangle rasterization algorithm.
const coords = context.coords, const coords = context.coords,
@ -274,16 +291,19 @@ const createMeshCanvas = (function createMeshCanvasClosure() {
} }
} }
// eslint-disable-next-line no-shadow class MeshShadingPattern extends BaseShadingPattern {
function createMeshCanvas( constructor(IR) {
bounds, super();
combinesScale, this._coords = IR[2];
coords, this._colors = IR[3];
colors, this._figures = IR[4];
figures, this._bounds = IR[5];
backgroundColor, this._matrix = IR[6];
cachedCanvases this._bbox = IR[7];
) { this._background = IR[8];
}
_createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) {
// we will increase scale on some weird factor to let antialiasing take // we will increase scale on some weird factor to let antialiasing take
// care of "rough" edges // care of "rough" edges
const EXPECTED_SCALE = 1.1; const EXPECTED_SCALE = 1.1;
@ -293,25 +313,25 @@ const createMeshCanvas = (function createMeshCanvasClosure() {
// createPattern with 'no-repeat' will bleed edges across entire area. // createPattern with 'no-repeat' will bleed edges across entire area.
const BORDER_SIZE = 2; const BORDER_SIZE = 2;
const offsetX = Math.floor(bounds[0]); const offsetX = Math.floor(this._bounds[0]);
const offsetY = Math.floor(bounds[1]); const offsetY = Math.floor(this._bounds[1]);
const boundsWidth = Math.ceil(bounds[2]) - offsetX; const boundsWidth = Math.ceil(this._bounds[2]) - offsetX;
const boundsHeight = Math.ceil(bounds[3]) - offsetY; const boundsHeight = Math.ceil(this._bounds[3]) - offsetY;
const width = Math.min( 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 MAX_PATTERN_SIZE
); );
const height = Math.min( 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 MAX_PATTERN_SIZE
); );
const scaleX = boundsWidth / width; const scaleX = boundsWidth / width;
const scaleY = boundsHeight / height; const scaleY = boundsHeight / height;
const context = { const context = {
coords, coords: this._coords,
colors, colors: this._colors,
offsetX: -offsetX, offsetX: -offsetX,
offsetY: -offsetY, offsetY: -offsetY,
scaleX: 1 / scaleX, scaleX: 1 / scaleX,
@ -339,8 +359,8 @@ const createMeshCanvas = (function createMeshCanvasClosure() {
bytes[i + 3] = 255; bytes[i + 3] = 255;
} }
} }
for (let i = 0, ii = figures.length; i < ii; i++) { for (const figure of this._figures) {
drawFigure(data, figures[i], context); drawFigure(data, figure, context);
} }
tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE); tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE);
const canvas = tmpCanvas.canvas; const canvas = tmpCanvas.canvas;
@ -353,50 +373,33 @@ const createMeshCanvas = (function createMeshCanvasClosure() {
scaleY, scaleY,
}; };
} }
return createMeshCanvas;
})();
ShadingIRs.Mesh = { getPattern(ctx, owner, shadingFill) {
fromIR: function Mesh_fromIR(raw) { applyBoundingBox(ctx, this._bbox);
// 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);
let scale; let scale;
if (shadingFill) { if (shadingFill) {
scale = Util.singularValueDecompose2dScale(ctx.mozCurrentTransform); scale = Util.singularValueDecompose2dScale(ctx.mozCurrentTransform);
} else { } else {
// Obtain scale from matrix and current transformation matrix. // Obtain scale from matrix and current transformation matrix.
scale = Util.singularValueDecompose2dScale(owner.baseTransform); scale = Util.singularValueDecompose2dScale(owner.baseTransform);
if (matrix) { if (this._matrix) {
const matrixScale = Util.singularValueDecompose2dScale(matrix); const matrixScale = Util.singularValueDecompose2dScale(this._matrix);
scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]]; scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]];
} }
} }
// Rasterizing on the main thread since sending/queue large canvases // Rasterizing on the main thread since sending/queue large canvases
// might cause OOM. // might cause OOM.
const temporaryPatternCanvas = createMeshCanvas( const temporaryPatternCanvas = this._createMeshCanvas(
bounds,
scale, scale,
coords, shadingFill ? null : this._background,
colors,
figures,
shadingFill ? null : background,
owner.cachedCanvases owner.cachedCanvases
); );
if (!shadingFill) { if (!shadingFill) {
ctx.setTransform.apply(ctx, owner.baseTransform); ctx.setTransform.apply(ctx, owner.baseTransform);
if (matrix) { if (this._matrix) {
ctx.transform.apply(ctx, matrix); ctx.transform.apply(ctx, this._matrix);
} }
} }
@ -407,27 +410,25 @@ ShadingIRs.Mesh = {
ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY); ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY);
return ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat"); return ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat");
},
};
},
};
ShadingIRs.Dummy = {
fromIR: function Dummy_fromIR() {
return {
getPattern: function Dummy_fromIR_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); }
class DummyShadingPattern extends BaseShadingPattern {
getPattern() {
return "hotpink";
}
}
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 = { const PaintType = {
@ -624,4 +625,4 @@ class TilingPattern {
} }
} }
export { getShadingPatternFromIR, TilingPattern }; export { getShadingPattern, TilingPattern };