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,
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, shadow, 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,27 +410,25 @@ 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() {
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 = {
@ -624,4 +625,4 @@ class TilingPattern {
}
}
export { getShadingPatternFromIR, TilingPattern };
export { getShadingPattern, TilingPattern };