Merge pull request #13377 from Snuffleupagus/pattern-class
Re-factor and convert the code in `src/core/pattern.js` to use standard classes
This commit is contained in:
commit
d2e7161f2c
@ -17,6 +17,7 @@ import {
|
||||
assert,
|
||||
FormatError,
|
||||
info,
|
||||
shadow,
|
||||
unreachable,
|
||||
UNSUPPORTED_FEATURES,
|
||||
Util,
|
||||
@ -36,22 +37,12 @@ const ShadingType = {
|
||||
TENSOR_PATCH_MESH: 7,
|
||||
};
|
||||
|
||||
const Pattern = (function PatternClosure() {
|
||||
// Constructor should define this.getPattern
|
||||
// eslint-disable-next-line no-shadow
|
||||
function Pattern() {
|
||||
unreachable("should not call Pattern constructor");
|
||||
class Pattern {
|
||||
constructor() {
|
||||
unreachable("Cannot initialize Pattern.");
|
||||
}
|
||||
|
||||
Pattern.prototype = {
|
||||
// Input: current Canvas context
|
||||
// Output: the appropriate fillStyle or strokeStyle
|
||||
getPattern: function Pattern_getPattern(ctx) {
|
||||
unreachable(`Should not call Pattern.getStyle: ${ctx}`);
|
||||
},
|
||||
};
|
||||
|
||||
Pattern.parseShading = function (
|
||||
static parseShading(
|
||||
shading,
|
||||
matrix,
|
||||
xref,
|
||||
@ -67,8 +58,7 @@ const Pattern = (function PatternClosure() {
|
||||
switch (type) {
|
||||
case ShadingType.AXIAL:
|
||||
case ShadingType.RADIAL:
|
||||
// Both radial and axial shadings are handled by RadialAxial shading.
|
||||
return new Shadings.RadialAxial(
|
||||
return new RadialAxialShading(
|
||||
dict,
|
||||
matrix,
|
||||
xref,
|
||||
@ -80,7 +70,7 @@ const Pattern = (function PatternClosure() {
|
||||
case ShadingType.LATTICE_FORM_MESH:
|
||||
case ShadingType.COONS_PATCH_MESH:
|
||||
case ShadingType.TENSOR_PATCH_MESH:
|
||||
return new Shadings.Mesh(
|
||||
return new MeshShading(
|
||||
shading,
|
||||
matrix,
|
||||
xref,
|
||||
@ -99,22 +89,33 @@ const Pattern = (function PatternClosure() {
|
||||
featureId: UNSUPPORTED_FEATURES.shadingPattern,
|
||||
});
|
||||
warn(ex);
|
||||
return new Shadings.Dummy();
|
||||
return new DummyShading();
|
||||
}
|
||||
};
|
||||
return Pattern;
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
const Shadings = {};
|
||||
class BaseShading {
|
||||
// A small number to offset the first/last color stops so we can insert ones
|
||||
// to support extend. Number.MIN_VALUE is too small and breaks the extend.
|
||||
static get SMALL_NUMBER() {
|
||||
return shadow(this, "SMALL_NUMBER", 1e-6);
|
||||
}
|
||||
|
||||
// A small number to offset the first/last color stops so we can insert ones to
|
||||
// support extend. Number.MIN_VALUE is too small and breaks the extend.
|
||||
Shadings.SMALL_NUMBER = 1e-6;
|
||||
constructor() {
|
||||
if (this.constructor === BaseShading) {
|
||||
unreachable("Cannot initialize BaseShading.");
|
||||
}
|
||||
}
|
||||
|
||||
getIR() {
|
||||
unreachable("Abstract method `getIR` called.");
|
||||
}
|
||||
}
|
||||
|
||||
// Radial and axial shading have very similar implementations
|
||||
// If needed, the implementations can be broken into two classes
|
||||
Shadings.RadialAxial = (function RadialAxialClosure() {
|
||||
function RadialAxial(
|
||||
// If needed, the implementations can be broken into two classes.
|
||||
class RadialAxialShading extends BaseShading {
|
||||
constructor(
|
||||
dict,
|
||||
matrix,
|
||||
xref,
|
||||
@ -122,10 +123,10 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
|
||||
pdfFunctionFactory,
|
||||
localColorSpaceCache
|
||||
) {
|
||||
super();
|
||||
this.matrix = matrix;
|
||||
this.coordsArr = dict.getArray("Coords");
|
||||
this.shadingType = dict.get("ShadingType");
|
||||
this.type = "Pattern";
|
||||
const cs = ColorSpace.parse({
|
||||
cs: dict.getRaw("ColorSpace") || dict.getRaw("CS"),
|
||||
xref,
|
||||
@ -133,7 +134,6 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
|
||||
pdfFunctionFactory,
|
||||
localColorSpaceCache,
|
||||
});
|
||||
this.cs = cs;
|
||||
const bbox = dict.getArray("BBox");
|
||||
if (Array.isArray(bbox) && bbox.length === 4) {
|
||||
this.bbox = Util.normalizeRect(bbox);
|
||||
@ -213,19 +213,18 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
|
||||
// Insert a color stop at the front and offset the first real color stop
|
||||
// so it doesn't conflict with the one we insert.
|
||||
colorStops.unshift([0, background]);
|
||||
colorStops[1][0] += Shadings.SMALL_NUMBER;
|
||||
colorStops[1][0] += BaseShading.SMALL_NUMBER;
|
||||
}
|
||||
if (!extendEnd) {
|
||||
// Same idea as above in extendStart but for the end.
|
||||
colorStops[colorStops.length - 1][0] -= Shadings.SMALL_NUMBER;
|
||||
colorStops[colorStops.length - 1][0] -= BaseShading.SMALL_NUMBER;
|
||||
colorStops.push([1, background]);
|
||||
}
|
||||
|
||||
this.colorStops = colorStops;
|
||||
}
|
||||
|
||||
RadialAxial.prototype = {
|
||||
getIR: function RadialAxial_getIR() {
|
||||
getIR() {
|
||||
const coordsArr = this.coordsArr;
|
||||
const shadingType = this.shadingType;
|
||||
let type, p0, p1, r0, r1;
|
||||
@ -256,16 +255,13 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
|
||||
r1,
|
||||
this.matrix,
|
||||
];
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return RadialAxial;
|
||||
})();
|
||||
|
||||
// All mesh shading. For now, they will be presented as set of the triangles
|
||||
// All mesh shadings. For now, they will be presented as set of the triangles
|
||||
// to be drawn on the canvas and rgb color for each vertex.
|
||||
Shadings.Mesh = (function MeshClosure() {
|
||||
function MeshStreamReader(stream, context) {
|
||||
class MeshStreamReader {
|
||||
constructor(stream, context) {
|
||||
this.stream = stream;
|
||||
this.context = context;
|
||||
this.buffer = 0;
|
||||
@ -278,7 +274,7 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
? new Float32Array(csNumComps)
|
||||
: this.tmpCompsBuf;
|
||||
}
|
||||
MeshStreamReader.prototype = {
|
||||
|
||||
get hasData() {
|
||||
if (this.stream.end) {
|
||||
return this.stream.pos < this.stream.end;
|
||||
@ -293,8 +289,9 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
this.buffer = nextByte;
|
||||
this.bufferLength = 8;
|
||||
return true;
|
||||
},
|
||||
readBits: function MeshStreamReader_readBits(n) {
|
||||
}
|
||||
|
||||
readBits(n) {
|
||||
let buffer = this.buffer;
|
||||
let bufferLength = this.bufferLength;
|
||||
if (n === 32) {
|
||||
@ -331,15 +328,18 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
this.bufferLength = bufferLength;
|
||||
this.buffer = buffer & ((1 << bufferLength) - 1);
|
||||
return buffer >> bufferLength;
|
||||
},
|
||||
align: function MeshStreamReader_align() {
|
||||
}
|
||||
|
||||
align() {
|
||||
this.buffer = 0;
|
||||
this.bufferLength = 0;
|
||||
},
|
||||
readFlag: function MeshStreamReader_readFlag() {
|
||||
}
|
||||
|
||||
readFlag() {
|
||||
return this.readBits(this.context.bitsPerFlag);
|
||||
},
|
||||
readCoordinate: function MeshStreamReader_readCoordinate() {
|
||||
}
|
||||
|
||||
readCoordinate() {
|
||||
const bitsPerCoordinate = this.context.bitsPerCoordinate;
|
||||
const xi = this.readBits(bitsPerCoordinate);
|
||||
const yi = this.readBits(bitsPerCoordinate);
|
||||
@ -352,8 +352,9 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
xi * scale * (decode[1] - decode[0]) + decode[0],
|
||||
yi * scale * (decode[3] - decode[2]) + decode[2],
|
||||
];
|
||||
},
|
||||
readComponents: function MeshStreamReader_readComponents() {
|
||||
}
|
||||
|
||||
readComponents() {
|
||||
const numComps = this.context.numComps;
|
||||
const bitsPerComponent = this.context.bitsPerComponent;
|
||||
const scale =
|
||||
@ -371,12 +372,141 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
this.context.colorFn(components, 0, color, 0);
|
||||
}
|
||||
return this.context.colorSpace.getRgb(color, 0);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function decodeType4Shading(mesh, reader) {
|
||||
const coords = mesh.coords;
|
||||
const colors = mesh.colors;
|
||||
const getB = (function getBClosure() {
|
||||
function buildB(count) {
|
||||
const lut = [];
|
||||
for (let i = 0; i <= count; i++) {
|
||||
const t = i / count,
|
||||
t_ = 1 - t;
|
||||
lut.push(
|
||||
new Float32Array([
|
||||
t_ * t_ * t_,
|
||||
3 * t * t_ * t_,
|
||||
3 * t * t * t_,
|
||||
t * t * t,
|
||||
])
|
||||
);
|
||||
}
|
||||
return lut;
|
||||
}
|
||||
const cache = [];
|
||||
|
||||
return function (count) {
|
||||
if (!cache[count]) {
|
||||
cache[count] = buildB(count);
|
||||
}
|
||||
return cache[count];
|
||||
};
|
||||
})();
|
||||
|
||||
class MeshShading extends BaseShading {
|
||||
static get MIN_SPLIT_PATCH_CHUNKS_AMOUNT() {
|
||||
return shadow(this, "MIN_SPLIT_PATCH_CHUNKS_AMOUNT", 3);
|
||||
}
|
||||
|
||||
static get MAX_SPLIT_PATCH_CHUNKS_AMOUNT() {
|
||||
return shadow(this, "MAX_SPLIT_PATCH_CHUNKS_AMOUNT", 20);
|
||||
}
|
||||
|
||||
// Count of triangles per entire mesh bounds.
|
||||
static get TRIANGLE_DENSITY() {
|
||||
return shadow(this, "TRIANGLE_DENSITY", 20);
|
||||
}
|
||||
|
||||
constructor(
|
||||
stream,
|
||||
matrix,
|
||||
xref,
|
||||
resources,
|
||||
pdfFunctionFactory,
|
||||
localColorSpaceCache
|
||||
) {
|
||||
super();
|
||||
if (!isStream(stream)) {
|
||||
throw new FormatError("Mesh data is not a stream");
|
||||
}
|
||||
const dict = stream.dict;
|
||||
this.matrix = matrix;
|
||||
this.shadingType = dict.get("ShadingType");
|
||||
const bbox = dict.getArray("BBox");
|
||||
if (Array.isArray(bbox) && bbox.length === 4) {
|
||||
this.bbox = Util.normalizeRect(bbox);
|
||||
} else {
|
||||
this.bbox = null;
|
||||
}
|
||||
const cs = ColorSpace.parse({
|
||||
cs: dict.getRaw("ColorSpace") || dict.getRaw("CS"),
|
||||
xref,
|
||||
resources,
|
||||
pdfFunctionFactory,
|
||||
localColorSpaceCache,
|
||||
});
|
||||
this.background = dict.has("Background")
|
||||
? cs.getRgb(dict.get("Background"), 0)
|
||||
: null;
|
||||
|
||||
const fnObj = dict.getRaw("Function");
|
||||
const fn = fnObj ? pdfFunctionFactory.createFromArray(fnObj) : null;
|
||||
|
||||
this.coords = [];
|
||||
this.colors = [];
|
||||
this.figures = [];
|
||||
|
||||
const decodeContext = {
|
||||
bitsPerCoordinate: dict.get("BitsPerCoordinate"),
|
||||
bitsPerComponent: dict.get("BitsPerComponent"),
|
||||
bitsPerFlag: dict.get("BitsPerFlag"),
|
||||
decode: dict.getArray("Decode"),
|
||||
colorFn: fn,
|
||||
colorSpace: cs,
|
||||
numComps: fn ? 1 : cs.numComps,
|
||||
};
|
||||
const reader = new MeshStreamReader(stream, decodeContext);
|
||||
|
||||
let patchMesh = false;
|
||||
switch (this.shadingType) {
|
||||
case ShadingType.FREE_FORM_MESH:
|
||||
this._decodeType4Shading(reader);
|
||||
break;
|
||||
case ShadingType.LATTICE_FORM_MESH:
|
||||
const verticesPerRow = dict.get("VerticesPerRow") | 0;
|
||||
if (verticesPerRow < 2) {
|
||||
throw new FormatError("Invalid VerticesPerRow");
|
||||
}
|
||||
this._decodeType5Shading(reader, verticesPerRow);
|
||||
break;
|
||||
case ShadingType.COONS_PATCH_MESH:
|
||||
this._decodeType6Shading(reader);
|
||||
patchMesh = true;
|
||||
break;
|
||||
case ShadingType.TENSOR_PATCH_MESH:
|
||||
this._decodeType7Shading(reader);
|
||||
patchMesh = true;
|
||||
break;
|
||||
default:
|
||||
unreachable("Unsupported mesh type.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (patchMesh) {
|
||||
// Dirty bounds calculation, to determine how dense the triangles will be.
|
||||
this._updateBounds();
|
||||
for (let i = 0, ii = this.figures.length; i < ii; i++) {
|
||||
this._buildFigureFromPatch(i);
|
||||
}
|
||||
}
|
||||
// Calculate bounds.
|
||||
this._updateBounds();
|
||||
|
||||
this._packData();
|
||||
}
|
||||
|
||||
_decodeType4Shading(reader) {
|
||||
const coords = this.coords;
|
||||
const colors = this.colors;
|
||||
const operators = [];
|
||||
const ps = []; // not maintaining cs since that will match ps
|
||||
let verticesLeft = 0; // assuming we have all data to start a new triangle
|
||||
@ -411,16 +541,16 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
|
||||
reader.align();
|
||||
}
|
||||
mesh.figures.push({
|
||||
this.figures.push({
|
||||
type: "triangles",
|
||||
coords: new Int32Array(ps),
|
||||
colors: new Int32Array(ps),
|
||||
});
|
||||
}
|
||||
|
||||
function decodeType5Shading(mesh, reader, verticesPerRow) {
|
||||
const coords = mesh.coords;
|
||||
const colors = mesh.colors;
|
||||
_decodeType5Shading(reader, verticesPerRow) {
|
||||
const coords = this.coords;
|
||||
const colors = this.colors;
|
||||
const ps = []; // not maintaining cs since that will match ps
|
||||
while (reader.hasData) {
|
||||
const coord = reader.readCoordinate();
|
||||
@ -429,7 +559,7 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
coords.push(coord);
|
||||
colors.push(color);
|
||||
}
|
||||
mesh.figures.push({
|
||||
this.figures.push({
|
||||
type: "lattice",
|
||||
coords: new Int32Array(ps),
|
||||
colors: new Int32Array(ps),
|
||||
@ -437,158 +567,10 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
});
|
||||
}
|
||||
|
||||
const MIN_SPLIT_PATCH_CHUNKS_AMOUNT = 3;
|
||||
const MAX_SPLIT_PATCH_CHUNKS_AMOUNT = 20;
|
||||
|
||||
const TRIANGLE_DENSITY = 20; // count of triangles per entire mesh bounds
|
||||
|
||||
const getB = (function getBClosure() {
|
||||
function buildB(count) {
|
||||
const lut = [];
|
||||
for (let i = 0; i <= count; i++) {
|
||||
const t = i / count,
|
||||
t_ = 1 - t;
|
||||
lut.push(
|
||||
new Float32Array([
|
||||
t_ * t_ * t_,
|
||||
3 * t * t_ * t_,
|
||||
3 * t * t * t_,
|
||||
t * t * t,
|
||||
])
|
||||
);
|
||||
}
|
||||
return lut;
|
||||
}
|
||||
const cache = [];
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
return function getB(count) {
|
||||
if (!cache[count]) {
|
||||
cache[count] = buildB(count);
|
||||
}
|
||||
return cache[count];
|
||||
};
|
||||
})();
|
||||
|
||||
function buildFigureFromPatch(mesh, index) {
|
||||
const figure = mesh.figures[index];
|
||||
assert(figure.type === "patch", "Unexpected patch mesh figure");
|
||||
|
||||
const coords = mesh.coords,
|
||||
colors = mesh.colors;
|
||||
const pi = figure.coords;
|
||||
const ci = figure.colors;
|
||||
|
||||
const figureMinX = Math.min(
|
||||
coords[pi[0]][0],
|
||||
coords[pi[3]][0],
|
||||
coords[pi[12]][0],
|
||||
coords[pi[15]][0]
|
||||
);
|
||||
const figureMinY = Math.min(
|
||||
coords[pi[0]][1],
|
||||
coords[pi[3]][1],
|
||||
coords[pi[12]][1],
|
||||
coords[pi[15]][1]
|
||||
);
|
||||
const figureMaxX = Math.max(
|
||||
coords[pi[0]][0],
|
||||
coords[pi[3]][0],
|
||||
coords[pi[12]][0],
|
||||
coords[pi[15]][0]
|
||||
);
|
||||
const figureMaxY = Math.max(
|
||||
coords[pi[0]][1],
|
||||
coords[pi[3]][1],
|
||||
coords[pi[12]][1],
|
||||
coords[pi[15]][1]
|
||||
);
|
||||
let splitXBy = Math.ceil(
|
||||
((figureMaxX - figureMinX) * TRIANGLE_DENSITY) /
|
||||
(mesh.bounds[2] - mesh.bounds[0])
|
||||
);
|
||||
splitXBy = Math.max(
|
||||
MIN_SPLIT_PATCH_CHUNKS_AMOUNT,
|
||||
Math.min(MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitXBy)
|
||||
);
|
||||
let splitYBy = Math.ceil(
|
||||
((figureMaxY - figureMinY) * TRIANGLE_DENSITY) /
|
||||
(mesh.bounds[3] - mesh.bounds[1])
|
||||
);
|
||||
splitYBy = Math.max(
|
||||
MIN_SPLIT_PATCH_CHUNKS_AMOUNT,
|
||||
Math.min(MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitYBy)
|
||||
);
|
||||
|
||||
const verticesPerRow = splitXBy + 1;
|
||||
const figureCoords = new Int32Array((splitYBy + 1) * verticesPerRow);
|
||||
const figureColors = new Int32Array((splitYBy + 1) * verticesPerRow);
|
||||
let k = 0;
|
||||
const cl = new Uint8Array(3),
|
||||
cr = new Uint8Array(3);
|
||||
const c0 = colors[ci[0]],
|
||||
c1 = colors[ci[1]],
|
||||
c2 = colors[ci[2]],
|
||||
c3 = colors[ci[3]];
|
||||
const bRow = getB(splitYBy),
|
||||
bCol = getB(splitXBy);
|
||||
for (let row = 0; row <= splitYBy; row++) {
|
||||
cl[0] = ((c0[0] * (splitYBy - row) + c2[0] * row) / splitYBy) | 0;
|
||||
cl[1] = ((c0[1] * (splitYBy - row) + c2[1] * row) / splitYBy) | 0;
|
||||
cl[2] = ((c0[2] * (splitYBy - row) + c2[2] * row) / splitYBy) | 0;
|
||||
|
||||
cr[0] = ((c1[0] * (splitYBy - row) + c3[0] * row) / splitYBy) | 0;
|
||||
cr[1] = ((c1[1] * (splitYBy - row) + c3[1] * row) / splitYBy) | 0;
|
||||
cr[2] = ((c1[2] * (splitYBy - row) + c3[2] * row) / splitYBy) | 0;
|
||||
|
||||
for (let col = 0; col <= splitXBy; col++, k++) {
|
||||
if (
|
||||
(row === 0 || row === splitYBy) &&
|
||||
(col === 0 || col === splitXBy)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let x = 0,
|
||||
y = 0;
|
||||
let q = 0;
|
||||
for (let i = 0; i <= 3; i++) {
|
||||
for (let j = 0; j <= 3; j++, q++) {
|
||||
const m = bRow[row][i] * bCol[col][j];
|
||||
x += coords[pi[q]][0] * m;
|
||||
y += coords[pi[q]][1] * m;
|
||||
}
|
||||
}
|
||||
figureCoords[k] = coords.length;
|
||||
coords.push([x, y]);
|
||||
figureColors[k] = colors.length;
|
||||
const newColor = new Uint8Array(3);
|
||||
newColor[0] = ((cl[0] * (splitXBy - col) + cr[0] * col) / splitXBy) | 0;
|
||||
newColor[1] = ((cl[1] * (splitXBy - col) + cr[1] * col) / splitXBy) | 0;
|
||||
newColor[2] = ((cl[2] * (splitXBy - col) + cr[2] * col) / splitXBy) | 0;
|
||||
colors.push(newColor);
|
||||
}
|
||||
}
|
||||
figureCoords[0] = pi[0];
|
||||
figureColors[0] = ci[0];
|
||||
figureCoords[splitXBy] = pi[3];
|
||||
figureColors[splitXBy] = ci[1];
|
||||
figureCoords[verticesPerRow * splitYBy] = pi[12];
|
||||
figureColors[verticesPerRow * splitYBy] = ci[2];
|
||||
figureCoords[verticesPerRow * splitYBy + splitXBy] = pi[15];
|
||||
figureColors[verticesPerRow * splitYBy + splitXBy] = ci[3];
|
||||
|
||||
mesh.figures[index] = {
|
||||
type: "lattice",
|
||||
coords: figureCoords,
|
||||
colors: figureColors,
|
||||
verticesPerRow,
|
||||
};
|
||||
}
|
||||
|
||||
function decodeType6Shading(mesh, reader) {
|
||||
_decodeType6Shading(reader) {
|
||||
// A special case of Type 7. The p11, p12, p21, p22 automatically filled
|
||||
const coords = mesh.coords;
|
||||
const colors = mesh.colors;
|
||||
const coords = this.coords;
|
||||
const colors = this.colors;
|
||||
const ps = new Int32Array(16); // p00, p10, ..., p30, p01, ..., p33
|
||||
const cs = new Int32Array(4); // c00, c30, c03, c33
|
||||
while (reader.hasData) {
|
||||
@ -709,7 +691,7 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
3 * (coords[ps[2]][1] + coords[ps[8]][1])) /
|
||||
9,
|
||||
]);
|
||||
mesh.figures.push({
|
||||
this.figures.push({
|
||||
type: "patch",
|
||||
coords: new Int32Array(ps), // making copies of ps and cs
|
||||
colors: new Int32Array(cs),
|
||||
@ -717,9 +699,9 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
}
|
||||
}
|
||||
|
||||
function decodeType7Shading(mesh, reader) {
|
||||
const coords = mesh.coords;
|
||||
const colors = mesh.colors;
|
||||
_decodeType7Shading(reader) {
|
||||
const coords = this.coords;
|
||||
const colors = this.colors;
|
||||
const ps = new Int32Array(16); // p00, p10, ..., p30, p01, ..., p33
|
||||
const cs = new Int32Array(4); // c00, c30, c03, c33
|
||||
while (reader.hasData) {
|
||||
@ -779,7 +761,7 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
cs[0] = cs[1]; cs[1] = ci + 1;
|
||||
break;
|
||||
}
|
||||
mesh.figures.push({
|
||||
this.figures.push({
|
||||
type: "patch",
|
||||
coords: new Int32Array(ps), // making copies of ps and cs
|
||||
colors: new Int32Array(cs),
|
||||
@ -787,35 +769,150 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
}
|
||||
}
|
||||
|
||||
function updateBounds(mesh) {
|
||||
let minX = mesh.coords[0][0],
|
||||
minY = mesh.coords[0][1],
|
||||
_buildFigureFromPatch(index) {
|
||||
const figure = this.figures[index];
|
||||
assert(figure.type === "patch", "Unexpected patch mesh figure");
|
||||
|
||||
const coords = this.coords,
|
||||
colors = this.colors;
|
||||
const pi = figure.coords;
|
||||
const ci = figure.colors;
|
||||
|
||||
const figureMinX = Math.min(
|
||||
coords[pi[0]][0],
|
||||
coords[pi[3]][0],
|
||||
coords[pi[12]][0],
|
||||
coords[pi[15]][0]
|
||||
);
|
||||
const figureMinY = Math.min(
|
||||
coords[pi[0]][1],
|
||||
coords[pi[3]][1],
|
||||
coords[pi[12]][1],
|
||||
coords[pi[15]][1]
|
||||
);
|
||||
const figureMaxX = Math.max(
|
||||
coords[pi[0]][0],
|
||||
coords[pi[3]][0],
|
||||
coords[pi[12]][0],
|
||||
coords[pi[15]][0]
|
||||
);
|
||||
const figureMaxY = Math.max(
|
||||
coords[pi[0]][1],
|
||||
coords[pi[3]][1],
|
||||
coords[pi[12]][1],
|
||||
coords[pi[15]][1]
|
||||
);
|
||||
let splitXBy = Math.ceil(
|
||||
((figureMaxX - figureMinX) * MeshShading.TRIANGLE_DENSITY) /
|
||||
(this.bounds[2] - this.bounds[0])
|
||||
);
|
||||
splitXBy = Math.max(
|
||||
MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT,
|
||||
Math.min(MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitXBy)
|
||||
);
|
||||
let splitYBy = Math.ceil(
|
||||
((figureMaxY - figureMinY) * MeshShading.TRIANGLE_DENSITY) /
|
||||
(this.bounds[3] - this.bounds[1])
|
||||
);
|
||||
splitYBy = Math.max(
|
||||
MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT,
|
||||
Math.min(MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitYBy)
|
||||
);
|
||||
|
||||
const verticesPerRow = splitXBy + 1;
|
||||
const figureCoords = new Int32Array((splitYBy + 1) * verticesPerRow);
|
||||
const figureColors = new Int32Array((splitYBy + 1) * verticesPerRow);
|
||||
let k = 0;
|
||||
const cl = new Uint8Array(3),
|
||||
cr = new Uint8Array(3);
|
||||
const c0 = colors[ci[0]],
|
||||
c1 = colors[ci[1]],
|
||||
c2 = colors[ci[2]],
|
||||
c3 = colors[ci[3]];
|
||||
const bRow = getB(splitYBy),
|
||||
bCol = getB(splitXBy);
|
||||
for (let row = 0; row <= splitYBy; row++) {
|
||||
cl[0] = ((c0[0] * (splitYBy - row) + c2[0] * row) / splitYBy) | 0;
|
||||
cl[1] = ((c0[1] * (splitYBy - row) + c2[1] * row) / splitYBy) | 0;
|
||||
cl[2] = ((c0[2] * (splitYBy - row) + c2[2] * row) / splitYBy) | 0;
|
||||
|
||||
cr[0] = ((c1[0] * (splitYBy - row) + c3[0] * row) / splitYBy) | 0;
|
||||
cr[1] = ((c1[1] * (splitYBy - row) + c3[1] * row) / splitYBy) | 0;
|
||||
cr[2] = ((c1[2] * (splitYBy - row) + c3[2] * row) / splitYBy) | 0;
|
||||
|
||||
for (let col = 0; col <= splitXBy; col++, k++) {
|
||||
if (
|
||||
(row === 0 || row === splitYBy) &&
|
||||
(col === 0 || col === splitXBy)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let x = 0,
|
||||
y = 0;
|
||||
let q = 0;
|
||||
for (let i = 0; i <= 3; i++) {
|
||||
for (let j = 0; j <= 3; j++, q++) {
|
||||
const m = bRow[row][i] * bCol[col][j];
|
||||
x += coords[pi[q]][0] * m;
|
||||
y += coords[pi[q]][1] * m;
|
||||
}
|
||||
}
|
||||
figureCoords[k] = coords.length;
|
||||
coords.push([x, y]);
|
||||
figureColors[k] = colors.length;
|
||||
const newColor = new Uint8Array(3);
|
||||
newColor[0] = ((cl[0] * (splitXBy - col) + cr[0] * col) / splitXBy) | 0;
|
||||
newColor[1] = ((cl[1] * (splitXBy - col) + cr[1] * col) / splitXBy) | 0;
|
||||
newColor[2] = ((cl[2] * (splitXBy - col) + cr[2] * col) / splitXBy) | 0;
|
||||
colors.push(newColor);
|
||||
}
|
||||
}
|
||||
figureCoords[0] = pi[0];
|
||||
figureColors[0] = ci[0];
|
||||
figureCoords[splitXBy] = pi[3];
|
||||
figureColors[splitXBy] = ci[1];
|
||||
figureCoords[verticesPerRow * splitYBy] = pi[12];
|
||||
figureColors[verticesPerRow * splitYBy] = ci[2];
|
||||
figureCoords[verticesPerRow * splitYBy + splitXBy] = pi[15];
|
||||
figureColors[verticesPerRow * splitYBy + splitXBy] = ci[3];
|
||||
|
||||
this.figures[index] = {
|
||||
type: "lattice",
|
||||
coords: figureCoords,
|
||||
colors: figureColors,
|
||||
verticesPerRow,
|
||||
};
|
||||
}
|
||||
|
||||
_updateBounds() {
|
||||
let minX = this.coords[0][0],
|
||||
minY = this.coords[0][1],
|
||||
maxX = minX,
|
||||
maxY = minY;
|
||||
for (let i = 1, ii = mesh.coords.length; i < ii; i++) {
|
||||
const x = mesh.coords[i][0],
|
||||
y = mesh.coords[i][1];
|
||||
for (let i = 1, ii = this.coords.length; i < ii; i++) {
|
||||
const x = this.coords[i][0],
|
||||
y = this.coords[i][1];
|
||||
minX = minX > x ? x : minX;
|
||||
minY = minY > y ? y : minY;
|
||||
maxX = maxX < x ? x : maxX;
|
||||
maxY = maxY < y ? y : maxY;
|
||||
}
|
||||
mesh.bounds = [minX, minY, maxX, maxY];
|
||||
this.bounds = [minX, minY, maxX, maxY];
|
||||
}
|
||||
|
||||
function packData(mesh) {
|
||||
_packData() {
|
||||
let i, ii, j, jj;
|
||||
|
||||
const coords = mesh.coords;
|
||||
const coords = this.coords;
|
||||
const coordsPacked = new Float32Array(coords.length * 2);
|
||||
for (i = 0, j = 0, ii = coords.length; i < ii; i++) {
|
||||
const xy = coords[i];
|
||||
coordsPacked[j++] = xy[0];
|
||||
coordsPacked[j++] = xy[1];
|
||||
}
|
||||
mesh.coords = coordsPacked;
|
||||
this.coords = coordsPacked;
|
||||
|
||||
const colors = mesh.colors;
|
||||
const colors = this.colors;
|
||||
const colorsPacked = new Uint8Array(colors.length * 3);
|
||||
for (i = 0, j = 0, ii = colors.length; i < ii; i++) {
|
||||
const c = colors[i];
|
||||
@ -823,9 +920,9 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
colorsPacked[j++] = c[1];
|
||||
colorsPacked[j++] = c[2];
|
||||
}
|
||||
mesh.colors = colorsPacked;
|
||||
this.colors = colorsPacked;
|
||||
|
||||
const figures = mesh.figures;
|
||||
const figures = this.figures;
|
||||
for (i = 0, ii = figures.length; i < ii; i++) {
|
||||
const figure = figures[i],
|
||||
ps = figure.coords,
|
||||
@ -837,97 +934,7 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
}
|
||||
}
|
||||
|
||||
function Mesh(
|
||||
stream,
|
||||
matrix,
|
||||
xref,
|
||||
resources,
|
||||
pdfFunctionFactory,
|
||||
localColorSpaceCache
|
||||
) {
|
||||
if (!isStream(stream)) {
|
||||
throw new FormatError("Mesh data is not a stream");
|
||||
}
|
||||
const dict = stream.dict;
|
||||
this.matrix = matrix;
|
||||
this.shadingType = dict.get("ShadingType");
|
||||
this.type = "Pattern";
|
||||
const bbox = dict.getArray("BBox");
|
||||
if (Array.isArray(bbox) && bbox.length === 4) {
|
||||
this.bbox = Util.normalizeRect(bbox);
|
||||
} else {
|
||||
this.bbox = null;
|
||||
}
|
||||
const cs = ColorSpace.parse({
|
||||
cs: dict.getRaw("ColorSpace") || dict.getRaw("CS"),
|
||||
xref,
|
||||
resources,
|
||||
pdfFunctionFactory,
|
||||
localColorSpaceCache,
|
||||
});
|
||||
this.cs = cs;
|
||||
this.background = dict.has("Background")
|
||||
? cs.getRgb(dict.get("Background"), 0)
|
||||
: null;
|
||||
|
||||
const fnObj = dict.getRaw("Function");
|
||||
const fn = fnObj ? pdfFunctionFactory.createFromArray(fnObj) : null;
|
||||
|
||||
this.coords = [];
|
||||
this.colors = [];
|
||||
this.figures = [];
|
||||
|
||||
const decodeContext = {
|
||||
bitsPerCoordinate: dict.get("BitsPerCoordinate"),
|
||||
bitsPerComponent: dict.get("BitsPerComponent"),
|
||||
bitsPerFlag: dict.get("BitsPerFlag"),
|
||||
decode: dict.getArray("Decode"),
|
||||
colorFn: fn,
|
||||
colorSpace: cs,
|
||||
numComps: fn ? 1 : cs.numComps,
|
||||
};
|
||||
const reader = new MeshStreamReader(stream, decodeContext);
|
||||
|
||||
let patchMesh = false;
|
||||
switch (this.shadingType) {
|
||||
case ShadingType.FREE_FORM_MESH:
|
||||
decodeType4Shading(this, reader);
|
||||
break;
|
||||
case ShadingType.LATTICE_FORM_MESH:
|
||||
const verticesPerRow = dict.get("VerticesPerRow") | 0;
|
||||
if (verticesPerRow < 2) {
|
||||
throw new FormatError("Invalid VerticesPerRow");
|
||||
}
|
||||
decodeType5Shading(this, reader, verticesPerRow);
|
||||
break;
|
||||
case ShadingType.COONS_PATCH_MESH:
|
||||
decodeType6Shading(this, reader);
|
||||
patchMesh = true;
|
||||
break;
|
||||
case ShadingType.TENSOR_PATCH_MESH:
|
||||
decodeType7Shading(this, reader);
|
||||
patchMesh = true;
|
||||
break;
|
||||
default:
|
||||
unreachable("Unsupported mesh type.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (patchMesh) {
|
||||
// dirty bounds calculation for determining, how dense shall be triangles
|
||||
updateBounds(this);
|
||||
for (let i = 0, ii = this.figures.length; i < ii; i++) {
|
||||
buildFigureFromPatch(this, i);
|
||||
}
|
||||
}
|
||||
// calculate bounds
|
||||
updateBounds(this);
|
||||
|
||||
packData(this);
|
||||
}
|
||||
|
||||
Mesh.prototype = {
|
||||
getIR: function Mesh_getIR() {
|
||||
getIR() {
|
||||
return [
|
||||
"Mesh",
|
||||
this.shadingType,
|
||||
@ -939,24 +946,14 @@ Shadings.Mesh = (function MeshClosure() {
|
||||
this.bbox,
|
||||
this.background,
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
return Mesh;
|
||||
})();
|
||||
|
||||
Shadings.Dummy = (function DummyClosure() {
|
||||
function Dummy() {
|
||||
this.type = "Pattern";
|
||||
}
|
||||
}
|
||||
|
||||
Dummy.prototype = {
|
||||
getIR: function Dummy_getIR() {
|
||||
class DummyShading extends BaseShading {
|
||||
getIR() {
|
||||
return ["Dummy"];
|
||||
},
|
||||
};
|
||||
return Dummy;
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
function getTilingPatternIR(operatorList, dict, color) {
|
||||
const matrix = dict.getArray("Matrix");
|
||||
|
Loading…
x
Reference in New Issue
Block a user